@maizzle/framework 6.0.0-rc.13 → 6.0.0-rc.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/components/Button.vue +2 -2
  2. package/dist/components/CodeBlock.vue +2 -1
  3. package/dist/components/Column.vue +28 -22
  4. package/dist/components/Container.vue +47 -9
  5. package/dist/components/Font.vue +96 -0
  6. package/dist/components/Layout.vue +9 -4
  7. package/dist/components/Overlap.vue +75 -18
  8. package/dist/components/Row.vue +40 -19
  9. package/dist/components/Section.vue +35 -8
  10. package/dist/components/utils.d.mts +14 -1
  11. package/dist/components/utils.d.mts.map +1 -1
  12. package/dist/components/utils.mjs +32 -1
  13. package/dist/components/utils.mjs.map +1 -1
  14. package/dist/components/utils.ts +39 -0
  15. package/dist/composables/renderContext.d.mts +8 -1
  16. package/dist/composables/renderContext.d.mts.map +1 -1
  17. package/dist/composables/renderContext.mjs.map +1 -1
  18. package/dist/composables/useFont.d.mts +50 -0
  19. package/dist/composables/useFont.d.mts.map +1 -0
  20. package/dist/composables/useFont.mjs +93 -0
  21. package/dist/composables/useFont.mjs.map +1 -0
  22. package/dist/index.d.mts +2 -1
  23. package/dist/index.mjs +2 -1
  24. package/dist/plugins/postcss/quoteFontFamilies.d.mts +13 -0
  25. package/dist/plugins/postcss/quoteFontFamilies.d.mts.map +1 -0
  26. package/dist/plugins/postcss/quoteFontFamilies.mjs +84 -0
  27. package/dist/plugins/postcss/quoteFontFamilies.mjs.map +1 -0
  28. package/dist/render/createRenderer.mjs +8 -2
  29. package/dist/render/createRenderer.mjs.map +1 -1
  30. package/dist/render/injectFonts.d.mts +15 -0
  31. package/dist/render/injectFonts.d.mts.map +1 -0
  32. package/dist/render/injectFonts.mjs +46 -0
  33. package/dist/render/injectFonts.mjs.map +1 -0
  34. package/dist/serve.d.mts.map +1 -1
  35. package/dist/serve.mjs +6 -2
  36. package/dist/serve.mjs.map +1 -1
  37. package/dist/server/ui/App.vue +25 -11
  38. package/dist/server/ui/lib/emulated-dark-mode.ts +131 -0
  39. package/dist/server/ui/pages/Preview.vue +24 -5
  40. package/dist/transformers/columnWidth.d.mts +31 -0
  41. package/dist/transformers/columnWidth.d.mts.map +1 -0
  42. package/dist/transformers/columnWidth.mjs +166 -0
  43. package/dist/transformers/columnWidth.mjs.map +1 -0
  44. package/dist/transformers/index.d.mts.map +1 -1
  45. package/dist/transformers/index.mjs +4 -0
  46. package/dist/transformers/index.mjs.map +1 -1
  47. package/dist/transformers/msoWidthFromClass.d.mts +19 -0
  48. package/dist/transformers/msoWidthFromClass.d.mts.map +1 -0
  49. package/dist/transformers/msoWidthFromClass.mjs +61 -0
  50. package/dist/transformers/msoWidthFromClass.mjs.map +1 -0
  51. package/dist/transformers/tailwindcss.d.mts.map +1 -1
  52. package/dist/transformers/tailwindcss.mjs +4 -12
  53. package/dist/transformers/tailwindcss.mjs.map +1 -1
  54. package/dist/utils/decodeStyleEntities.d.mts +15 -0
  55. package/dist/utils/decodeStyleEntities.d.mts.map +1 -0
  56. package/dist/utils/decodeStyleEntities.mjs +18 -0
  57. package/dist/utils/decodeStyleEntities.mjs.map +1 -0
  58. package/package.json +2 -1
@@ -22,6 +22,7 @@ import {
22
22
  } from '@/components/ui/tags-input'
23
23
 
24
24
  import stripesUrl from '../stripes.svg'
25
+ import { applyColorInversion, undoColorInversion } from '@/lib/emulated-dark-mode'
25
26
 
26
27
  interface Device {
27
28
  name: string
@@ -42,6 +43,7 @@ const props = defineProps<{
42
43
  }>()
43
44
 
44
45
  const viewMode = defineModel<'preview' | 'source'>('viewMode', { default: 'preview' })
46
+ const darkMode = defineModel<boolean>('darkMode', { default: false })
45
47
 
46
48
  const route = useRoute()
47
49
  const srcdoc = ref('')
@@ -70,7 +72,9 @@ const iframeContentHeight = ref<number | null>(null)
70
72
  function copySource() {
71
73
  let text: string
72
74
  if (sourceView.value === 'compiled') {
73
- text = srcdoc.value
75
+ // `renderedHtml` holds the raw compiled HTML (srcdoc is only populated
76
+ // for the initial iframe load; subsequent renders use doc.write).
77
+ text = renderedHtml || srcdoc.value
74
78
  } else if (sourceView.value === 'plaintext') {
75
79
  text = plaintextContent.value
76
80
  } else {
@@ -79,11 +83,11 @@ function copySource() {
79
83
  text = el.textContent || ''
80
84
  }
81
85
 
82
- const blob = new Blob([text], { type: 'text/plain' })
83
- const item = new ClipboardItem({ 'text/plain': blob })
84
- navigator.clipboard.write([item]).then(() => {
86
+ navigator.clipboard.writeText(text).then(() => {
85
87
  copied.value = true
86
88
  setTimeout(() => { copied.value = false }, 2000)
89
+ }).catch((err) => {
90
+ console.error('Copy failed:', err)
87
91
  })
88
92
  }
89
93
 
@@ -230,6 +234,19 @@ function updateIframeContentHeight() {
230
234
  }
231
235
  }
232
236
 
237
+ function onIframeLoad() {
238
+ updateIframeContentHeight()
239
+ const iframe = iframeEl.value
240
+ if (darkMode.value && iframe) applyColorInversion(iframe)
241
+ }
242
+
243
+ watch(darkMode, (on) => {
244
+ const iframe = iframeEl.value
245
+ if (!iframe) return
246
+ if (on) applyColorInversion(iframe)
247
+ else undoColorInversion(iframe)
248
+ })
249
+
233
250
  async function fetchTemplate() {
234
251
  const res = await fetch(`/__maizzle/render/${route.params.template}`)
235
252
  renderedHtml = await res.text()
@@ -245,6 +262,7 @@ async function fetchTemplate() {
245
262
  doc.close()
246
263
  // Hide iframe body overflow — scrolling is handled by the outer ScrollArea
247
264
  if (doc.body) doc.body.style.overflow = 'hidden'
265
+ if (darkMode.value && iframe) applyColorInversion(iframe)
248
266
  await nextTick()
249
267
  updateIframeContentHeight()
250
268
  } else {
@@ -540,6 +558,7 @@ function forwardIframeKeys(iframe: HTMLIFrameElement) {
540
558
  metaKey: e.metaKey,
541
559
  shiftKey: e.shiftKey,
542
560
  altKey: e.altKey,
561
+ bubbles: true,
543
562
  }))
544
563
  })
545
564
  } catch {}
@@ -745,7 +764,7 @@ const stripeBg = {
745
764
  <iframe
746
765
  ref="iframeEl"
747
766
  :srcdoc="srcdoc"
748
- @load="updateIframeContentHeight"
767
+ @load="onIframeLoad"
749
768
  class="w-full border-0 bg-white dark:bg-gray-950"
750
769
  :style="{ height: iframeContentHeight ? `${iframeContentHeight}px` : '100%' }"
751
770
  />
@@ -0,0 +1,31 @@
1
+ import { ChildNode } from "domhandler";
2
+
3
+ //#region src/transformers/columnWidth.d.ts
4
+ /**
5
+ * Resolve `__MAIZZLE_COLW_{id}__` and `__MAIZZLE_OH_{id}__` placeholders.
6
+ *
7
+ * COLW (column width) — emitted by `<Column>` and `<Overlap>`. Walks up to
8
+ * the nearest ancestor marked with `data-maizzle-cw` (Container/Section/
9
+ * Row/another Column already resolved) and divides by `data-maizzle-cw-count`.
10
+ * If `data-maizzle-cw-self` is present, reads from the element's own inlined
11
+ * `max-width`/`width`/`min-width` instead of walking up — used by `<Overlap>`
12
+ * when it has its own width class/inline style.
13
+ *
14
+ * OH (overlap height) — emitted by `<Overlap>`. Reads `max-height`/`height`/
15
+ * `min-height` from the element's own inlined style.
16
+ *
17
+ * Resolution rules:
18
+ * - Style placeholders for `min-width`: replaced when resolvable, otherwise
19
+ * the entire `min-width` declaration is stripped.
20
+ * - Other style placeholders (Overlap td `width`, etc.): replaced when
21
+ * resolvable, otherwise replaced with the count-based fallback or `100%`.
22
+ * - Comment placeholders: same fallback chain.
23
+ *
24
+ * Resolved column widths are written back to `data-maizzle-cw` so nested
25
+ * rows cascade. All `data-maizzle-cw*` and `data-maizzle-oh-*` attrs are
26
+ * stripped at the end.
27
+ */
28
+ declare function columnWidth(dom: ChildNode[]): ChildNode[];
29
+ //#endregion
30
+ export { columnWidth };
31
+ //# sourceMappingURL=columnWidth.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"columnWidth.d.mts","names":[],"sources":["../../src/transformers/columnWidth.ts"],"mappings":";;;;;AA4FA;;;;;;;;;;;;;;;;;;;;;;iBAAgB,WAAA,CAAY,GAAA,EAAK,SAAA,KAAc,SAAA"}
@@ -0,0 +1,166 @@
1
+ import { walk } from "../utils/ast/walker.mjs";
2
+ import "../utils/ast/index.mjs";
3
+
4
+ //#region src/transformers/columnWidth.ts
5
+ const RE_MAX_WIDTH = /(?:^|;\s*)max-width:\s*([^;]+)/i;
6
+ const RE_WIDTH = /(?:^|;\s*)width:\s*([^;]+)/i;
7
+ const RE_MIN_WIDTH = /(?:^|;\s*)min-width:\s*([^;]+)/i;
8
+ const RE_MAX_HEIGHT = /(?:^|;\s*)max-height:\s*([^;]+)/i;
9
+ const RE_HEIGHT = /(?:^|;\s*)height:\s*([^;]+)/i;
10
+ const RE_MIN_HEIGHT = /(?:^|;\s*)min-height:\s*([^;]+)/i;
11
+ const RE_PERCENT = /^[\d.]+%$/;
12
+ function resolveLength(value) {
13
+ const trimmed = value.trim();
14
+ if (RE_PERCENT.test(trimmed)) return trimmed;
15
+ const m = trimmed.match(/^([\d.]+)(px|rem|em|pt)?$/i);
16
+ if (!m) return null;
17
+ const n = parseFloat(m[1]);
18
+ switch ((m[2] || "px").toLowerCase()) {
19
+ case "px": return `${Math.round(n)}px`;
20
+ case "rem":
21
+ case "em": return `${Math.round(n * 16)}px`;
22
+ case "pt": return `${Math.round(n * 1.333)}px`;
23
+ default: return null;
24
+ }
25
+ }
26
+ function divideLength(value, divisor) {
27
+ const m = value.match(/^([\d.]+)(px|%)$/);
28
+ if (!m || divisor < 1) return null;
29
+ const n = parseFloat(m[1]);
30
+ return `${parseFloat((n / divisor).toFixed(2))}${m[2]}`;
31
+ }
32
+ function depth(node) {
33
+ let d = 0;
34
+ let cur = node.parent;
35
+ while (cur) {
36
+ d++;
37
+ cur = cur.parent ?? null;
38
+ }
39
+ return d;
40
+ }
41
+ function readWidthSource(el) {
42
+ const explicit = el.attribs?.["data-maizzle-cw"];
43
+ if (explicit) {
44
+ const r = resolveLength(explicit);
45
+ if (r) return r;
46
+ }
47
+ return readWidthFromStyle(el);
48
+ }
49
+ function readWidthFromStyle(el) {
50
+ const style = el.attribs?.style ?? "";
51
+ const raw = style.match(RE_MAX_WIDTH)?.[1] ?? style.match(RE_WIDTH)?.[1] ?? style.match(RE_MIN_WIDTH)?.[1];
52
+ return raw ? resolveLength(raw) : null;
53
+ }
54
+ function readHeightFromStyle(el) {
55
+ const style = el.attribs?.style ?? "";
56
+ const raw = style.match(RE_MAX_HEIGHT)?.[1] ?? style.match(RE_HEIGHT)?.[1] ?? style.match(RE_MIN_HEIGHT)?.[1];
57
+ return raw ? resolveLength(raw) : null;
58
+ }
59
+ /**
60
+ * Resolve `__MAIZZLE_COLW_{id}__` and `__MAIZZLE_OH_{id}__` placeholders.
61
+ *
62
+ * COLW (column width) — emitted by `<Column>` and `<Overlap>`. Walks up to
63
+ * the nearest ancestor marked with `data-maizzle-cw` (Container/Section/
64
+ * Row/another Column already resolved) and divides by `data-maizzle-cw-count`.
65
+ * If `data-maizzle-cw-self` is present, reads from the element's own inlined
66
+ * `max-width`/`width`/`min-width` instead of walking up — used by `<Overlap>`
67
+ * when it has its own width class/inline style.
68
+ *
69
+ * OH (overlap height) — emitted by `<Overlap>`. Reads `max-height`/`height`/
70
+ * `min-height` from the element's own inlined style.
71
+ *
72
+ * Resolution rules:
73
+ * - Style placeholders for `min-width`: replaced when resolvable, otherwise
74
+ * the entire `min-width` declaration is stripped.
75
+ * - Other style placeholders (Overlap td `width`, etc.): replaced when
76
+ * resolvable, otherwise replaced with the count-based fallback or `100%`.
77
+ * - Comment placeholders: same fallback chain.
78
+ *
79
+ * Resolved column widths are written back to `data-maizzle-cw` so nested
80
+ * rows cascade. All `data-maizzle-cw*` and `data-maizzle-oh-*` attrs are
81
+ * stripped at the end.
82
+ */
83
+ function columnWidth(dom) {
84
+ const columns = [];
85
+ const heightTargets = [];
86
+ walk(dom, (node) => {
87
+ const el = node;
88
+ if (!el.attribs) return;
89
+ const id = el.attribs["data-maizzle-cw-id"];
90
+ if (id) {
91
+ const count = parseInt(el.attribs["data-maizzle-cw-count"] || "1", 10);
92
+ const self = "data-maizzle-cw-self" in el.attribs;
93
+ columns.push({
94
+ el,
95
+ id,
96
+ count,
97
+ d: depth(node),
98
+ self
99
+ });
100
+ }
101
+ const ohId = el.attribs["data-maizzle-oh-id"];
102
+ if (ohId) heightTargets.push({
103
+ el,
104
+ id: ohId
105
+ });
106
+ });
107
+ columns.sort((a, b) => a.d - b.d);
108
+ const widthResolutions = /* @__PURE__ */ new Map();
109
+ const widthFallbacks = /* @__PURE__ */ new Map();
110
+ for (const { id, count } of columns) widthFallbacks.set(id, `${Math.round(100 / Math.max(count, 1))}%`);
111
+ for (const { el, id, count, self } of columns) {
112
+ let sourceWidth = null;
113
+ if (self) sourceWidth = readWidthFromStyle(el);
114
+ else {
115
+ let cur = el.parent;
116
+ while (cur) {
117
+ const parentEl = cur;
118
+ if (parentEl.attribs && "data-maizzle-cw" in parentEl.attribs) {
119
+ sourceWidth = readWidthSource(parentEl);
120
+ break;
121
+ }
122
+ cur = cur.parent ?? null;
123
+ }
124
+ }
125
+ if (sourceWidth) {
126
+ const div = divideLength(sourceWidth, count);
127
+ if (div) {
128
+ widthResolutions.set(id, div);
129
+ el.attribs["data-maizzle-cw"] = div;
130
+ }
131
+ }
132
+ }
133
+ const heightResolutions = /* @__PURE__ */ new Map();
134
+ for (const { el, id } of heightTargets) {
135
+ const h = readHeightFromStyle(el);
136
+ if (h) heightResolutions.set(id, h);
137
+ }
138
+ walk(dom, (node) => {
139
+ if (node.type === "comment") {
140
+ const data = node.data;
141
+ if (!data || !data.includes("__MAIZZLE_COLW_") && !data.includes("__MAIZZLE_OH_")) return;
142
+ node.data = data.replace(/__MAIZZLE_COLW_([^_]+)__/g, (_m, mid) => widthResolutions.get(mid) ?? widthFallbacks.get(mid) ?? "100%").replace(/__MAIZZLE_OH_([^_]+)__/g, (_m, hid) => heightResolutions.get(hid) ?? "100%");
143
+ return;
144
+ }
145
+ const el = node;
146
+ if (!el.attribs) return;
147
+ const style = el.attribs.style;
148
+ if (style && (style.includes("__MAIZZLE_COLW_") || style.includes("__MAIZZLE_OH_"))) {
149
+ let next = style.replace(/(?:^|;\s*)min-width:\s*__MAIZZLE_COLW_([^_]+)__\s*;?/g, (_m, mid) => widthResolutions.has(mid) ? `; min-width: ${widthResolutions.get(mid)}` : "");
150
+ next = next.replace(/__MAIZZLE_COLW_([^_]+)__/g, (_m, mid) => widthResolutions.get(mid) ?? widthFallbacks.get(mid) ?? "100%").replace(/__MAIZZLE_OH_([^_]+)__/g, (_m, hid) => heightResolutions.get(hid) ?? "100%");
151
+ next = next.replace(/^;\s*/, "").replace(/;\s*$/, "").trim();
152
+ if (next) el.attribs.style = next;
153
+ else delete el.attribs.style;
154
+ }
155
+ delete el.attribs["data-maizzle-cw"];
156
+ delete el.attribs["data-maizzle-cw-id"];
157
+ delete el.attribs["data-maizzle-cw-count"];
158
+ delete el.attribs["data-maizzle-cw-self"];
159
+ delete el.attribs["data-maizzle-oh-id"];
160
+ });
161
+ return dom;
162
+ }
163
+
164
+ //#endregion
165
+ export { columnWidth };
166
+ //# sourceMappingURL=columnWidth.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"columnWidth.mjs","names":[],"sources":["../../src/transformers/columnWidth.ts"],"sourcesContent":["import { walk } from '../utils/ast/index.ts'\nimport type { ChildNode, Element, ParentNode } from 'domhandler'\n\nconst RE_MAX_WIDTH = /(?:^|;\\s*)max-width:\\s*([^;]+)/i\nconst RE_WIDTH = /(?:^|;\\s*)width:\\s*([^;]+)/i\nconst RE_MIN_WIDTH = /(?:^|;\\s*)min-width:\\s*([^;]+)/i\nconst RE_MAX_HEIGHT = /(?:^|;\\s*)max-height:\\s*([^;]+)/i\nconst RE_HEIGHT = /(?:^|;\\s*)height:\\s*([^;]+)/i\nconst RE_MIN_HEIGHT = /(?:^|;\\s*)min-height:\\s*([^;]+)/i\nconst RE_PERCENT = /^[\\d.]+%$/\n\nfunction resolveLength(value: string): string | null {\n const trimmed = value.trim()\n if (RE_PERCENT.test(trimmed)) return trimmed\n const m = trimmed.match(/^([\\d.]+)(px|rem|em|pt)?$/i)\n if (!m) return null\n const n = parseFloat(m[1])\n switch ((m[2] || 'px').toLowerCase()) {\n case 'px': return `${Math.round(n)}px`\n case 'rem':\n case 'em': return `${Math.round(n * 16)}px`\n case 'pt': return `${Math.round(n * 1.333)}px`\n default: return null\n }\n}\n\nfunction divideLength(value: string, divisor: number): string | null {\n const m = value.match(/^([\\d.]+)(px|%)$/)\n if (!m || divisor < 1) return null\n const n = parseFloat(m[1])\n return `${parseFloat((n / divisor).toFixed(2))}${m[2]}`\n}\n\nfunction depth(node: ChildNode): number {\n let d = 0\n let cur: ParentNode | null = node.parent\n while (cur) {\n d++\n cur = (cur as any).parent ?? null\n }\n return d\n}\n\nfunction readWidthSource(el: Element): string | null {\n const explicit = el.attribs?.['data-maizzle-cw']\n if (explicit) {\n const r = resolveLength(explicit)\n if (r) return r\n }\n return readWidthFromStyle(el)\n}\n\nfunction readWidthFromStyle(el: Element): string | null {\n const style = el.attribs?.style ?? ''\n const raw = style.match(RE_MAX_WIDTH)?.[1]\n ?? style.match(RE_WIDTH)?.[1]\n ?? style.match(RE_MIN_WIDTH)?.[1]\n return raw ? resolveLength(raw) : null\n}\n\nfunction readHeightFromStyle(el: Element): string | null {\n const style = el.attribs?.style ?? ''\n const raw = style.match(RE_MAX_HEIGHT)?.[1]\n ?? style.match(RE_HEIGHT)?.[1]\n ?? style.match(RE_MIN_HEIGHT)?.[1]\n return raw ? resolveLength(raw) : null\n}\n\n/**\n * Resolve `__MAIZZLE_COLW_{id}__` and `__MAIZZLE_OH_{id}__` placeholders.\n *\n * COLW (column width) — emitted by `<Column>` and `<Overlap>`. Walks up to\n * the nearest ancestor marked with `data-maizzle-cw` (Container/Section/\n * Row/another Column already resolved) and divides by `data-maizzle-cw-count`.\n * If `data-maizzle-cw-self` is present, reads from the element's own inlined\n * `max-width`/`width`/`min-width` instead of walking up — used by `<Overlap>`\n * when it has its own width class/inline style.\n *\n * OH (overlap height) — emitted by `<Overlap>`. Reads `max-height`/`height`/\n * `min-height` from the element's own inlined style.\n *\n * Resolution rules:\n * - Style placeholders for `min-width`: replaced when resolvable, otherwise\n * the entire `min-width` declaration is stripped.\n * - Other style placeholders (Overlap td `width`, etc.): replaced when\n * resolvable, otherwise replaced with the count-based fallback or `100%`.\n * - Comment placeholders: same fallback chain.\n *\n * Resolved column widths are written back to `data-maizzle-cw` so nested\n * rows cascade. All `data-maizzle-cw*` and `data-maizzle-oh-*` attrs are\n * stripped at the end.\n */\nexport function columnWidth(dom: ChildNode[]): ChildNode[] {\n const columns: { el: Element; id: string; count: number; d: number; self: boolean }[] = []\n const heightTargets: { el: Element; id: string }[] = []\n\n walk(dom, (node) => {\n const el = node as Element\n if (!el.attribs) return\n\n const id = el.attribs['data-maizzle-cw-id']\n if (id) {\n const count = parseInt(el.attribs['data-maizzle-cw-count'] || '1', 10)\n const self = 'data-maizzle-cw-self' in el.attribs\n columns.push({ el, id, count, d: depth(node), self })\n }\n\n const ohId = el.attribs['data-maizzle-oh-id']\n if (ohId) heightTargets.push({ el, id: ohId })\n })\n\n columns.sort((a, b) => a.d - b.d)\n\n const widthResolutions = new Map<string, string>()\n const widthFallbacks = new Map<string, string>()\n\n for (const { id, count } of columns) {\n widthFallbacks.set(id, `${Math.round(100 / Math.max(count, 1))}%`)\n }\n\n for (const { el, id, count, self } of columns) {\n let sourceWidth: string | null = null\n\n if (self) {\n sourceWidth = readWidthFromStyle(el)\n } else {\n let cur: ParentNode | null = el.parent\n while (cur) {\n const parentEl = cur as Element\n if (parentEl.attribs && 'data-maizzle-cw' in parentEl.attribs) {\n sourceWidth = readWidthSource(parentEl)\n break\n }\n cur = (cur as any).parent ?? null\n }\n }\n\n if (sourceWidth) {\n const div = divideLength(sourceWidth, count)\n if (div) {\n widthResolutions.set(id, div)\n el.attribs['data-maizzle-cw'] = div\n }\n }\n }\n\n const heightResolutions = new Map<string, string>()\n for (const { el, id } of heightTargets) {\n const h = readHeightFromStyle(el)\n if (h) heightResolutions.set(id, h)\n }\n\n walk(dom, (node) => {\n if (node.type === 'comment') {\n const data = (node as any).data as string\n if (!data || (!data.includes('__MAIZZLE_COLW_') && !data.includes('__MAIZZLE_OH_'))) return\n ;(node as any).data = data\n .replace(/__MAIZZLE_COLW_([^_]+)__/g,\n (_m, mid) => widthResolutions.get(mid) ?? widthFallbacks.get(mid) ?? '100%')\n .replace(/__MAIZZLE_OH_([^_]+)__/g,\n (_m, hid) => heightResolutions.get(hid) ?? '100%')\n return\n }\n\n const el = node as Element\n if (!el.attribs) return\n\n const style = el.attribs.style\n if (style && (style.includes('__MAIZZLE_COLW_') || style.includes('__MAIZZLE_OH_'))) {\n let next = style.replace(\n /(?:^|;\\s*)min-width:\\s*__MAIZZLE_COLW_([^_]+)__\\s*;?/g,\n (_m, mid) => widthResolutions.has(mid) ? `; min-width: ${widthResolutions.get(mid)}` : ''\n )\n next = next\n .replace(/__MAIZZLE_COLW_([^_]+)__/g,\n (_m, mid) => widthResolutions.get(mid) ?? widthFallbacks.get(mid) ?? '100%')\n .replace(/__MAIZZLE_OH_([^_]+)__/g,\n (_m, hid) => heightResolutions.get(hid) ?? '100%')\n next = next.replace(/^;\\s*/, '').replace(/;\\s*$/, '').trim()\n if (next) el.attribs.style = next\n else delete el.attribs.style\n }\n\n delete el.attribs['data-maizzle-cw']\n delete el.attribs['data-maizzle-cw-id']\n delete el.attribs['data-maizzle-cw-count']\n delete el.attribs['data-maizzle-cw-self']\n delete el.attribs['data-maizzle-oh-id']\n })\n\n return dom\n}\n"],"mappings":";;;;AAGA,MAAM,eAAe;AACrB,MAAM,WAAW;AACjB,MAAM,eAAe;AACrB,MAAM,gBAAgB;AACtB,MAAM,YAAY;AAClB,MAAM,gBAAgB;AACtB,MAAM,aAAa;AAEnB,SAAS,cAAc,OAA8B;CACnD,MAAM,UAAU,MAAM,MAAM;AAC5B,KAAI,WAAW,KAAK,QAAQ,CAAE,QAAO;CACrC,MAAM,IAAI,QAAQ,MAAM,6BAA6B;AACrD,KAAI,CAAC,EAAG,QAAO;CACf,MAAM,IAAI,WAAW,EAAE,GAAG;AAC1B,UAAS,EAAE,MAAM,MAAM,aAAa,EAApC;EACE,KAAK,KAAM,QAAO,GAAG,KAAK,MAAM,EAAE,CAAC;EACnC,KAAK;EACL,KAAK,KAAM,QAAO,GAAG,KAAK,MAAM,IAAI,GAAG,CAAC;EACxC,KAAK,KAAM,QAAO,GAAG,KAAK,MAAM,IAAI,MAAM,CAAC;EAC3C,QAAS,QAAO;;;AAIpB,SAAS,aAAa,OAAe,SAAgC;CACnE,MAAM,IAAI,MAAM,MAAM,mBAAmB;AACzC,KAAI,CAAC,KAAK,UAAU,EAAG,QAAO;CAC9B,MAAM,IAAI,WAAW,EAAE,GAAG;AAC1B,QAAO,GAAG,YAAY,IAAI,SAAS,QAAQ,EAAE,CAAC,GAAG,EAAE;;AAGrD,SAAS,MAAM,MAAyB;CACtC,IAAI,IAAI;CACR,IAAI,MAAyB,KAAK;AAClC,QAAO,KAAK;AACV;AACA,QAAO,IAAY,UAAU;;AAE/B,QAAO;;AAGT,SAAS,gBAAgB,IAA4B;CACnD,MAAM,WAAW,GAAG,UAAU;AAC9B,KAAI,UAAU;EACZ,MAAM,IAAI,cAAc,SAAS;AACjC,MAAI,EAAG,QAAO;;AAEhB,QAAO,mBAAmB,GAAG;;AAG/B,SAAS,mBAAmB,IAA4B;CACtD,MAAM,QAAQ,GAAG,SAAS,SAAS;CACnC,MAAM,MAAM,MAAM,MAAM,aAAa,GAAG,MACnC,MAAM,MAAM,SAAS,GAAG,MACxB,MAAM,MAAM,aAAa,GAAG;AACjC,QAAO,MAAM,cAAc,IAAI,GAAG;;AAGpC,SAAS,oBAAoB,IAA4B;CACvD,MAAM,QAAQ,GAAG,SAAS,SAAS;CACnC,MAAM,MAAM,MAAM,MAAM,cAAc,GAAG,MACpC,MAAM,MAAM,UAAU,GAAG,MACzB,MAAM,MAAM,cAAc,GAAG;AAClC,QAAO,MAAM,cAAc,IAAI,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BpC,SAAgB,YAAY,KAA+B;CACzD,MAAM,UAAkF,EAAE;CAC1F,MAAM,gBAA+C,EAAE;AAEvD,MAAK,MAAM,SAAS;EAClB,MAAM,KAAK;AACX,MAAI,CAAC,GAAG,QAAS;EAEjB,MAAM,KAAK,GAAG,QAAQ;AACtB,MAAI,IAAI;GACN,MAAM,QAAQ,SAAS,GAAG,QAAQ,4BAA4B,KAAK,GAAG;GACtE,MAAM,OAAO,0BAA0B,GAAG;AAC1C,WAAQ,KAAK;IAAE;IAAI;IAAI;IAAO,GAAG,MAAM,KAAK;IAAE;IAAM,CAAC;;EAGvD,MAAM,OAAO,GAAG,QAAQ;AACxB,MAAI,KAAM,eAAc,KAAK;GAAE;GAAI,IAAI;GAAM,CAAC;GAC9C;AAEF,SAAQ,MAAM,GAAG,MAAM,EAAE,IAAI,EAAE,EAAE;CAEjC,MAAM,mCAAmB,IAAI,KAAqB;CAClD,MAAM,iCAAiB,IAAI,KAAqB;AAEhD,MAAK,MAAM,EAAE,IAAI,WAAW,QAC1B,gBAAe,IAAI,IAAI,GAAG,KAAK,MAAM,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC,CAAC,GAAG;AAGpE,MAAK,MAAM,EAAE,IAAI,IAAI,OAAO,UAAU,SAAS;EAC7C,IAAI,cAA6B;AAEjC,MAAI,KACF,eAAc,mBAAmB,GAAG;OAC/B;GACL,IAAI,MAAyB,GAAG;AAChC,UAAO,KAAK;IACV,MAAM,WAAW;AACjB,QAAI,SAAS,WAAW,qBAAqB,SAAS,SAAS;AAC7D,mBAAc,gBAAgB,SAAS;AACvC;;AAEF,UAAO,IAAY,UAAU;;;AAIjC,MAAI,aAAa;GACf,MAAM,MAAM,aAAa,aAAa,MAAM;AAC5C,OAAI,KAAK;AACP,qBAAiB,IAAI,IAAI,IAAI;AAC7B,OAAG,QAAQ,qBAAqB;;;;CAKtC,MAAM,oCAAoB,IAAI,KAAqB;AACnD,MAAK,MAAM,EAAE,IAAI,QAAQ,eAAe;EACtC,MAAM,IAAI,oBAAoB,GAAG;AACjC,MAAI,EAAG,mBAAkB,IAAI,IAAI,EAAE;;AAGrC,MAAK,MAAM,SAAS;AAClB,MAAI,KAAK,SAAS,WAAW;GAC3B,MAAM,OAAQ,KAAa;AAC3B,OAAI,CAAC,QAAS,CAAC,KAAK,SAAS,kBAAkB,IAAI,CAAC,KAAK,SAAS,gBAAgB,CAAG;AACpF,GAAC,KAAa,OAAO,KACnB,QAAQ,8BACN,IAAI,QAAQ,iBAAiB,IAAI,IAAI,IAAI,eAAe,IAAI,IAAI,IAAI,OAAO,CAC7E,QAAQ,4BACN,IAAI,QAAQ,kBAAkB,IAAI,IAAI,IAAI,OAAO;AACtD;;EAGF,MAAM,KAAK;AACX,MAAI,CAAC,GAAG,QAAS;EAEjB,MAAM,QAAQ,GAAG,QAAQ;AACzB,MAAI,UAAU,MAAM,SAAS,kBAAkB,IAAI,MAAM,SAAS,gBAAgB,GAAG;GACnF,IAAI,OAAO,MAAM,QACf,0DACC,IAAI,QAAQ,iBAAiB,IAAI,IAAI,GAAG,gBAAgB,iBAAiB,IAAI,IAAI,KAAK,GACxF;AACD,UAAO,KACJ,QAAQ,8BACN,IAAI,QAAQ,iBAAiB,IAAI,IAAI,IAAI,eAAe,IAAI,IAAI,IAAI,OAAO,CAC7E,QAAQ,4BACN,IAAI,QAAQ,kBAAkB,IAAI,IAAI,IAAI,OAAO;AACtD,UAAO,KAAK,QAAQ,SAAS,GAAG,CAAC,QAAQ,SAAS,GAAG,CAAC,MAAM;AAC5D,OAAI,KAAM,IAAG,QAAQ,QAAQ;OACxB,QAAO,GAAG,QAAQ;;AAGzB,SAAO,GAAG,QAAQ;AAClB,SAAO,GAAG,QAAQ;AAClB,SAAO,GAAG,QAAQ;AAClB,SAAO,GAAG,QAAQ;AAClB,SAAO,GAAG,QAAQ;GAClB;AAEF,QAAO"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/transformers/index.ts"],"mappings":";;;;;AAkDA;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAsB,eAAA,CACpB,IAAA,UACA,MAAA,EAAQ,aAAA,EACR,QAAA,WACA,OAAA,YACC,OAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/transformers/index.ts"],"mappings":";;;;;AAoDA;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAsB,eAAA,CACpB,IAAA,UACA,MAAA,EAAQ,aAAA,EACR,QAAA,WACA,OAAA,YACC,OAAA"}
@@ -6,6 +6,8 @@ import { tailwindcss } from "./tailwindcss.mjs";
6
6
  import { safeClassNames } from "./safeClassNames.mjs";
7
7
  import { attributeToStyle } from "./attributeToStyle.mjs";
8
8
  import { inlineCSS } from "./inlineCSS.mjs";
9
+ import { msoWidthFromClass } from "./msoWidthFromClass.mjs";
10
+ import { columnWidth } from "./columnWidth.mjs";
9
11
  import { removeAttributes } from "./removeAttributes.mjs";
10
12
  import { shorthandCSS } from "./shorthandCSS.mjs";
11
13
  import { sixHex } from "./sixHex.mjs";
@@ -57,6 +59,8 @@ async function runTransformers(html, config, filePath, doctype) {
57
59
  dom = safeClassNames(dom, config.css);
58
60
  dom = attributeToStyle(dom, config.css);
59
61
  dom = inlineCSS(dom, config.css);
62
+ dom = msoWidthFromClass(dom);
63
+ dom = columnWidth(dom);
60
64
  dom = removeAttributes(dom, config.html?.attributes);
61
65
  dom = shorthandCSS(dom, config.css);
62
66
  dom = sixHex(dom, config.css);
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/transformers/index.ts"],"sourcesContent":["import { parse, serialize } from '../utils/ast/index.ts'\nimport { inlineLink } from './inlineLink.ts'\nimport { tailwindcss } from './tailwindcss.ts'\nimport { safeClassNames } from './safeClassNames.ts'\nimport { attributeToStyle } from './attributeToStyle.ts'\nimport { inlineCSS } from './inlineCSS.ts'\nimport { removeAttributes } from './removeAttributes.ts'\nimport { shorthandCSS } from './shorthandCSS.ts'\nimport { sixHex } from './sixHex.ts'\nimport { addAttributes } from './addAttributes.ts'\nimport { filters } from './filters/index.ts'\nimport { base } from './base.ts'\nimport { entities } from './entities.ts'\nimport { urlQuery } from './urlQuery.ts'\nimport { purgeCSS } from './purgeCSS.ts'\nimport { replaceStrings } from './replaceStrings.ts'\nimport { format } from './format.ts'\nimport { minify } from './minify.ts'\nimport type { MaizzleConfig } from '../types/config.ts'\n\n/**\n * Run all Maizzle transformers on the rendered HTML.\n *\n * The HTML is parsed into a DOM once at the start and passed through all\n * DOM-based transformers as a shared `ChildNode[]`. After all DOM transformers\n * complete, the DOM is serialized back to a string exactly once.\n *\n * String-only transformers (those that rely on external tools that require a\n * raw HTML string) then run on the serialized output.\n *\n * Transformers run in a specific order:\n * 0. Inline link stylesheets — replace `<link rel=\"stylesheet\">` with `<style>` tags\n * 1. Tailwind CSS — compile CSS, lower syntax, optimize (cleanup + merge media queries)\n * 2. Safe class names\n * 3. Attribute to style\n * 4. CSS inliner\n * 5. Remove attributes\n * 6. Shorthand CSS\n * 7. Six-digit HEX\n * 8. Add attributes\n * 9. Filters\n * 10. Base URL\n * 11. URL query\n * 12. Purge CSS (serializes/parses internally around email-comb)\n * 13. Entities\n * + Vue-generated comments stripped here (on serialized string)\n * 14. Replace strings\n * 15. Prettify\n * 16. Minify\n */\nexport async function runTransformers(\n html: string,\n config: MaizzleConfig,\n filePath?: string,\n doctype?: string,\n): Promise<string> {\n // Parse once — all DOM transformers share this array\n let dom = parse(html)\n\n // 0. Inline <link> stylesheets\n dom = await inlineLink(dom, filePath)\n\n // 1. Tailwind CSS — always runs first\n dom = await tailwindcss(dom, config, filePath)\n\n // 2. Safe class names\n dom = safeClassNames(dom, config.css)\n\n // 3. Attribute to style\n dom = attributeToStyle(dom, config.css)\n\n // 4. CSS inliner (serializes/parses internally around juice)\n dom = inlineCSS(dom, config.css)\n\n // 5. Remove attributes\n dom = removeAttributes(dom, config.html?.attributes)\n\n // 6. Shorthand CSS\n dom = shorthandCSS(dom, config.css)\n\n // 7. Six-digit HEX\n dom = sixHex(dom, config.css)\n\n // 8. Add attributes\n dom = addAttributes(dom, config.html?.attributes)\n\n // 9. Filters\n dom = filters(dom, config.filters)\n\n // 10. Base URL (serializes/parses internally for VML/MSO regex passes)\n dom = base(dom, config.url)\n\n // 11. URL query\n dom = urlQuery(dom, config.url)\n\n // 12. Purge CSS (serializes/parses internally around email-comb)\n dom = purgeCSS(dom, config.css)\n\n // 13. Entities\n dom = entities(dom, config.html?.decodeEntities)\n\n // Serialize once — remaining transformers operate on the HTML string\n const isXhtml = doctype ? /xhtml/i.test(doctype) : false\n let result = serialize(dom, { selfClosingTags: isXhtml })\n\n // Remove Vue-generated comments after serializing\n result = result\n .replaceAll('<!--[-->', '')\n .replaceAll('<!--]-->', '')\n .replaceAll('<!--teleport start anchor-->', '')\n .replaceAll('<!--teleport anchor-->', '')\n .replaceAll('<!--teleport start-->', '')\n .replaceAll('<!--teleport end-->', '')\n\n // 14. Replace strings\n result = replaceStrings(result, config)\n\n // 15. Format\n result = await format(result, config)\n\n // 16. Minify\n result = minify(result, config)\n\n // Strip self-closing slashes for HTML5 doctypes\n if (!isXhtml) {\n result = result.replace(/ \\/>/g, '>')\n }\n\n return result\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkDA,eAAsB,gBACpB,MACA,QACA,UACA,SACiB;CAEjB,IAAI,MAAM,MAAM,KAAK;AAGrB,OAAM,MAAM,WAAW,KAAK,SAAS;AAGrC,OAAM,MAAM,YAAY,KAAK,QAAQ,SAAS;AAG9C,OAAM,eAAe,KAAK,OAAO,IAAI;AAGrC,OAAM,iBAAiB,KAAK,OAAO,IAAI;AAGvC,OAAM,UAAU,KAAK,OAAO,IAAI;AAGhC,OAAM,iBAAiB,KAAK,OAAO,MAAM,WAAW;AAGpD,OAAM,aAAa,KAAK,OAAO,IAAI;AAGnC,OAAM,OAAO,KAAK,OAAO,IAAI;AAG7B,OAAM,cAAc,KAAK,OAAO,MAAM,WAAW;AAGjD,OAAM,QAAQ,KAAK,OAAO,QAAQ;AAGlC,OAAM,KAAK,KAAK,OAAO,IAAI;AAG3B,OAAM,SAAS,KAAK,OAAO,IAAI;AAG/B,OAAM,SAAS,KAAK,OAAO,IAAI;AAG/B,OAAM,SAAS,KAAK,OAAO,MAAM,eAAe;CAGhD,MAAM,UAAU,UAAU,SAAS,KAAK,QAAQ,GAAG;CACnD,IAAI,SAAS,UAAU,KAAK,EAAE,iBAAiB,SAAS,CAAC;AAGzD,UAAS,OACN,WAAW,YAAY,GAAG,CAC1B,WAAW,YAAY,GAAG,CAC1B,WAAW,gCAAgC,GAAG,CAC9C,WAAW,0BAA0B,GAAG,CACxC,WAAW,yBAAyB,GAAG,CACvC,WAAW,uBAAuB,GAAG;AAGxC,UAAS,eAAe,QAAQ,OAAO;AAGvC,UAAS,MAAM,OAAO,QAAQ,OAAO;AAGrC,UAAS,OAAO,QAAQ,OAAO;AAG/B,KAAI,CAAC,QACH,UAAS,OAAO,QAAQ,SAAS,IAAI;AAGvC,QAAO"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/transformers/index.ts"],"sourcesContent":["import { parse, serialize } from '../utils/ast/index.ts'\nimport { inlineLink } from './inlineLink.ts'\nimport { tailwindcss } from './tailwindcss.ts'\nimport { safeClassNames } from './safeClassNames.ts'\nimport { attributeToStyle } from './attributeToStyle.ts'\nimport { inlineCSS } from './inlineCSS.ts'\nimport { msoWidthFromClass } from './msoWidthFromClass.ts'\nimport { columnWidth } from './columnWidth.ts'\nimport { removeAttributes } from './removeAttributes.ts'\nimport { shorthandCSS } from './shorthandCSS.ts'\nimport { sixHex } from './sixHex.ts'\nimport { addAttributes } from './addAttributes.ts'\nimport { filters } from './filters/index.ts'\nimport { base } from './base.ts'\nimport { entities } from './entities.ts'\nimport { urlQuery } from './urlQuery.ts'\nimport { purgeCSS } from './purgeCSS.ts'\nimport { replaceStrings } from './replaceStrings.ts'\nimport { format } from './format.ts'\nimport { minify } from './minify.ts'\nimport type { MaizzleConfig } from '../types/config.ts'\n\n/**\n * Run all Maizzle transformers on the rendered HTML.\n *\n * The HTML is parsed into a DOM once at the start and passed through all\n * DOM-based transformers as a shared `ChildNode[]`. After all DOM transformers\n * complete, the DOM is serialized back to a string exactly once.\n *\n * String-only transformers (those that rely on external tools that require a\n * raw HTML string) then run on the serialized output.\n *\n * Transformers run in a specific order:\n * 0. Inline link stylesheets — replace `<link rel=\"stylesheet\">` with `<style>` tags\n * 1. Tailwind CSS — compile CSS, lower syntax, optimize (cleanup + merge media queries)\n * 2. Safe class names\n * 3. Attribute to style\n * 4. CSS inliner\n * 5. Remove attributes\n * 6. Shorthand CSS\n * 7. Six-digit HEX\n * 8. Add attributes\n * 9. Filters\n * 10. Base URL\n * 11. URL query\n * 12. Purge CSS (serializes/parses internally around email-comb)\n * 13. Entities\n * + Vue-generated comments stripped here (on serialized string)\n * 14. Replace strings\n * 15. Prettify\n * 16. Minify\n */\nexport async function runTransformers(\n html: string,\n config: MaizzleConfig,\n filePath?: string,\n doctype?: string,\n): Promise<string> {\n // Parse once — all DOM transformers share this array\n let dom = parse(html)\n\n // 0. Inline <link> stylesheets\n dom = await inlineLink(dom, filePath)\n\n // 1. Tailwind CSS — always runs first\n dom = await tailwindcss(dom, config, filePath)\n\n // 2. Safe class names\n dom = safeClassNames(dom, config.css)\n\n // 3. Attribute to style\n dom = attributeToStyle(dom, config.css)\n\n // 4. CSS inliner (serializes/parses internally around juice)\n dom = inlineCSS(dom, config.css)\n\n // 4.5. Resolve MSO width placeholders from inlined max-width/width\n dom = msoWidthFromClass(dom)\n\n // 4.6. Resolve Column min-width placeholders from nearest sized ancestor\n dom = columnWidth(dom)\n\n // 5. Remove attributes\n dom = removeAttributes(dom, config.html?.attributes)\n\n // 6. Shorthand CSS\n dom = shorthandCSS(dom, config.css)\n\n // 7. Six-digit HEX\n dom = sixHex(dom, config.css)\n\n // 8. Add attributes\n dom = addAttributes(dom, config.html?.attributes)\n\n // 9. Filters\n dom = filters(dom, config.filters)\n\n // 10. Base URL (serializes/parses internally for VML/MSO regex passes)\n dom = base(dom, config.url)\n\n // 11. URL query\n dom = urlQuery(dom, config.url)\n\n // 12. Purge CSS (serializes/parses internally around email-comb)\n dom = purgeCSS(dom, config.css)\n\n // 13. Entities\n dom = entities(dom, config.html?.decodeEntities)\n\n // Serialize once — remaining transformers operate on the HTML string\n const isXhtml = doctype ? /xhtml/i.test(doctype) : false\n let result = serialize(dom, { selfClosingTags: isXhtml })\n\n // Remove Vue-generated comments after serializing\n result = result\n .replaceAll('<!--[-->', '')\n .replaceAll('<!--]-->', '')\n .replaceAll('<!--teleport start anchor-->', '')\n .replaceAll('<!--teleport anchor-->', '')\n .replaceAll('<!--teleport start-->', '')\n .replaceAll('<!--teleport end-->', '')\n\n // 14. Replace strings\n result = replaceStrings(result, config)\n\n // 15. Format\n result = await format(result, config)\n\n // 16. Minify\n result = minify(result, config)\n\n // Strip self-closing slashes for HTML5 doctypes\n if (!isXhtml) {\n result = result.replace(/ \\/>/g, '>')\n }\n\n return result\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoDA,eAAsB,gBACpB,MACA,QACA,UACA,SACiB;CAEjB,IAAI,MAAM,MAAM,KAAK;AAGrB,OAAM,MAAM,WAAW,KAAK,SAAS;AAGrC,OAAM,MAAM,YAAY,KAAK,QAAQ,SAAS;AAG9C,OAAM,eAAe,KAAK,OAAO,IAAI;AAGrC,OAAM,iBAAiB,KAAK,OAAO,IAAI;AAGvC,OAAM,UAAU,KAAK,OAAO,IAAI;AAGhC,OAAM,kBAAkB,IAAI;AAG5B,OAAM,YAAY,IAAI;AAGtB,OAAM,iBAAiB,KAAK,OAAO,MAAM,WAAW;AAGpD,OAAM,aAAa,KAAK,OAAO,IAAI;AAGnC,OAAM,OAAO,KAAK,OAAO,IAAI;AAG7B,OAAM,cAAc,KAAK,OAAO,MAAM,WAAW;AAGjD,OAAM,QAAQ,KAAK,OAAO,QAAQ;AAGlC,OAAM,KAAK,KAAK,OAAO,IAAI;AAG3B,OAAM,SAAS,KAAK,OAAO,IAAI;AAG/B,OAAM,SAAS,KAAK,OAAO,IAAI;AAG/B,OAAM,SAAS,KAAK,OAAO,MAAM,eAAe;CAGhD,MAAM,UAAU,UAAU,SAAS,KAAK,QAAQ,GAAG;CACnD,IAAI,SAAS,UAAU,KAAK,EAAE,iBAAiB,SAAS,CAAC;AAGzD,UAAS,OACN,WAAW,YAAY,GAAG,CAC1B,WAAW,YAAY,GAAG,CAC1B,WAAW,gCAAgC,GAAG,CAC9C,WAAW,0BAA0B,GAAG,CACxC,WAAW,yBAAyB,GAAG,CACvC,WAAW,uBAAuB,GAAG;AAGxC,UAAS,eAAe,QAAQ,OAAO;AAGvC,UAAS,MAAM,OAAO,QAAQ,OAAO;AAGrC,UAAS,OAAO,QAAQ,OAAO;AAG/B,KAAI,CAAC,QACH,UAAS,OAAO,QAAQ,SAAS,IAAI;AAGvC,QAAO"}
@@ -0,0 +1,19 @@
1
+ import { ChildNode } from "domhandler";
2
+
3
+ //#region src/transformers/msoWidthFromClass.d.ts
4
+ /**
5
+ * Resolve `__MAIZZLE_MSOW_{id}__` placeholders inside MSO conditional
6
+ * comments by reading the inlined `max-width` (or `width`) of the
7
+ * paired element marked with `data-maizzle-msow-id`.
8
+ *
9
+ * Used by `<Container>` and `<Section>` to derive Outlook's table width
10
+ * from the resolved Tailwind class or inline style on the inner div,
11
+ * after CSS inlining.
12
+ *
13
+ * Falls back to the value of `data-maizzle-msow-fallback` (default
14
+ * `600px`) when the value can't be parsed.
15
+ */
16
+ declare function msoWidthFromClass(dom: ChildNode[]): ChildNode[];
17
+ //#endregion
18
+ export { msoWidthFromClass };
19
+ //# sourceMappingURL=msoWidthFromClass.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"msoWidthFromClass.d.mts","names":[],"sources":["../../src/transformers/msoWidthFromClass.ts"],"mappings":";;;;;AAkCA;;;;;;;;;;iBAAgB,iBAAA,CAAkB,GAAA,EAAK,SAAA,KAAc,SAAA"}
@@ -0,0 +1,61 @@
1
+ import { walk } from "../utils/ast/walker.mjs";
2
+ import "../utils/ast/index.mjs";
3
+
4
+ //#region src/transformers/msoWidthFromClass.ts
5
+ const RE_MAX_WIDTH = /(?:^|;\s*)max-width:\s*([^;]+)/i;
6
+ const RE_WIDTH = /(?:^|;\s*)width:\s*([^;]+)/i;
7
+ const RE_PERCENT = /^[\d.]+%$/;
8
+ function resolveWidth(value) {
9
+ const trimmed = value.trim();
10
+ if (RE_PERCENT.test(trimmed)) return trimmed;
11
+ const m = trimmed.match(/^([\d.]+)(px|rem|em|pt)?$/i);
12
+ if (!m) return null;
13
+ const n = parseFloat(m[1]);
14
+ switch ((m[2] || "px").toLowerCase()) {
15
+ case "px": return `${Math.round(n)}px`;
16
+ case "rem":
17
+ case "em": return `${Math.round(n * 16)}px`;
18
+ case "pt": return `${Math.round(n * 1.333)}px`;
19
+ default: return null;
20
+ }
21
+ }
22
+ /**
23
+ * Resolve `__MAIZZLE_MSOW_{id}__` placeholders inside MSO conditional
24
+ * comments by reading the inlined `max-width` (or `width`) of the
25
+ * paired element marked with `data-maizzle-msow-id`.
26
+ *
27
+ * Used by `<Container>` and `<Section>` to derive Outlook's table width
28
+ * from the resolved Tailwind class or inline style on the inner div,
29
+ * after CSS inlining.
30
+ *
31
+ * Falls back to the value of `data-maizzle-msow-fallback` (default
32
+ * `600px`) when the value can't be parsed.
33
+ */
34
+ function msoWidthFromClass(dom) {
35
+ const widths = /* @__PURE__ */ new Map();
36
+ walk(dom, (node) => {
37
+ const el = node;
38
+ const id = el.attribs?.["data-maizzle-msow-id"];
39
+ if (!id) return;
40
+ delete el.attribs["data-maizzle-msow-id"];
41
+ const fallback = el.attribs["data-maizzle-msow-fallback"] ?? "600px";
42
+ delete el.attribs["data-maizzle-msow-fallback"];
43
+ const style = el.attribs.style ?? "";
44
+ const raw = style.match(RE_MAX_WIDTH)?.[1] ?? style.match(RE_WIDTH)?.[1];
45
+ const resolved = raw ? resolveWidth(raw) : null;
46
+ widths.set(id, resolved ?? fallback);
47
+ });
48
+ if (widths.size === 0) return dom;
49
+ walk(dom, (node) => {
50
+ if (node.type !== "comment") return;
51
+ let data = node.data;
52
+ if (!data.includes("__MAIZZLE_MSOW_")) return;
53
+ for (const [id, px] of widths) data = data.replaceAll(`__MAIZZLE_MSOW_${id}__`, px);
54
+ node.data = data;
55
+ });
56
+ return dom;
57
+ }
58
+
59
+ //#endregion
60
+ export { msoWidthFromClass };
61
+ //# sourceMappingURL=msoWidthFromClass.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"msoWidthFromClass.mjs","names":[],"sources":["../../src/transformers/msoWidthFromClass.ts"],"sourcesContent":["import { walk } from '../utils/ast/index.ts'\nimport type { ChildNode, Element } from 'domhandler'\n\nconst RE_MAX_WIDTH = /(?:^|;\\s*)max-width:\\s*([^;]+)/i\nconst RE_WIDTH = /(?:^|;\\s*)width:\\s*([^;]+)/i\nconst RE_PERCENT = /^[\\d.]+%$/\n\nfunction resolveWidth(value: string): string | null {\n const trimmed = value.trim()\n if (RE_PERCENT.test(trimmed)) return trimmed\n const m = trimmed.match(/^([\\d.]+)(px|rem|em|pt)?$/i)\n if (!m) return null\n const n = parseFloat(m[1])\n switch ((m[2] || 'px').toLowerCase()) {\n case 'px': return `${Math.round(n)}px`\n case 'rem':\n case 'em': return `${Math.round(n * 16)}px`\n case 'pt': return `${Math.round(n * 1.333)}px`\n default: return null\n }\n}\n\n/**\n * Resolve `__MAIZZLE_MSOW_{id}__` placeholders inside MSO conditional\n * comments by reading the inlined `max-width` (or `width`) of the\n * paired element marked with `data-maizzle-msow-id`.\n *\n * Used by `<Container>` and `<Section>` to derive Outlook's table width\n * from the resolved Tailwind class or inline style on the inner div,\n * after CSS inlining.\n *\n * Falls back to the value of `data-maizzle-msow-fallback` (default\n * `600px`) when the value can't be parsed.\n */\nexport function msoWidthFromClass(dom: ChildNode[]): ChildNode[] {\n const widths = new Map<string, string>()\n\n walk(dom, (node) => {\n const el = node as Element\n const id = el.attribs?.['data-maizzle-msow-id']\n if (!id) return\n delete el.attribs['data-maizzle-msow-id']\n\n const fallback = el.attribs['data-maizzle-msow-fallback'] ?? '600px'\n delete el.attribs['data-maizzle-msow-fallback']\n\n const style = el.attribs.style ?? ''\n const raw = style.match(RE_MAX_WIDTH)?.[1] ?? style.match(RE_WIDTH)?.[1]\n const resolved = raw ? resolveWidth(raw) : null\n widths.set(id, resolved ?? fallback)\n })\n\n if (widths.size === 0) return dom\n\n walk(dom, (node) => {\n if (node.type !== 'comment') return\n let data = (node as any).data as string\n if (!data.includes('__MAIZZLE_MSOW_')) return\n for (const [id, px] of widths) {\n data = data.replaceAll(`__MAIZZLE_MSOW_${id}__`, px)\n }\n ;(node as any).data = data\n })\n\n return dom\n}\n"],"mappings":";;;;AAGA,MAAM,eAAe;AACrB,MAAM,WAAW;AACjB,MAAM,aAAa;AAEnB,SAAS,aAAa,OAA8B;CAClD,MAAM,UAAU,MAAM,MAAM;AAC5B,KAAI,WAAW,KAAK,QAAQ,CAAE,QAAO;CACrC,MAAM,IAAI,QAAQ,MAAM,6BAA6B;AACrD,KAAI,CAAC,EAAG,QAAO;CACf,MAAM,IAAI,WAAW,EAAE,GAAG;AAC1B,UAAS,EAAE,MAAM,MAAM,aAAa,EAApC;EACE,KAAK,KAAM,QAAO,GAAG,KAAK,MAAM,EAAE,CAAC;EACnC,KAAK;EACL,KAAK,KAAM,QAAO,GAAG,KAAK,MAAM,IAAI,GAAG,CAAC;EACxC,KAAK,KAAM,QAAO,GAAG,KAAK,MAAM,IAAI,MAAM,CAAC;EAC3C,QAAS,QAAO;;;;;;;;;;;;;;;AAgBpB,SAAgB,kBAAkB,KAA+B;CAC/D,MAAM,yBAAS,IAAI,KAAqB;AAExC,MAAK,MAAM,SAAS;EAClB,MAAM,KAAK;EACX,MAAM,KAAK,GAAG,UAAU;AACxB,MAAI,CAAC,GAAI;AACT,SAAO,GAAG,QAAQ;EAElB,MAAM,WAAW,GAAG,QAAQ,iCAAiC;AAC7D,SAAO,GAAG,QAAQ;EAElB,MAAM,QAAQ,GAAG,QAAQ,SAAS;EAClC,MAAM,MAAM,MAAM,MAAM,aAAa,GAAG,MAAM,MAAM,MAAM,SAAS,GAAG;EACtE,MAAM,WAAW,MAAM,aAAa,IAAI,GAAG;AAC3C,SAAO,IAAI,IAAI,YAAY,SAAS;GACpC;AAEF,KAAI,OAAO,SAAS,EAAG,QAAO;AAE9B,MAAK,MAAM,SAAS;AAClB,MAAI,KAAK,SAAS,UAAW;EAC7B,IAAI,OAAQ,KAAa;AACzB,MAAI,CAAC,KAAK,SAAS,kBAAkB,CAAE;AACvC,OAAK,MAAM,CAAC,IAAI,OAAO,OACrB,QAAO,KAAK,WAAW,kBAAkB,GAAG,KAAK,GAAG;AAErD,EAAC,KAAa,OAAO;GACtB;AAEF,QAAO"}
@@ -1 +1 @@
1
- {"version":3,"file":"tailwindcss.d.mts","names":[],"sources":["../../src/transformers/tailwindcss.ts"],"mappings":";;;;;;AAoJA;;;;;;;;;;;;;;iBAAsB,WAAA,CAAY,GAAA,EAAK,SAAA,IAAa,MAAA,EAAQ,aAAA,EAAe,QAAA,YAAoB,OAAA,CAAQ,SAAA"}
1
+ {"version":3,"file":"tailwindcss.d.mts","names":[],"sources":["../../src/transformers/tailwindcss.ts"],"mappings":";;;;;;AAqIA;;;;;;;;;;;;;;iBAAsB,WAAA,CAAY,GAAA,EAAK,SAAA,IAAa,MAAA,EAAQ,aAAA,EAAe,QAAA,YAAoB,OAAA,CAAQ,SAAA"}
@@ -4,6 +4,8 @@ import resolveProps_default from "../plugins/postcss/resolveProps.mjs";
4
4
  import pruneVars_default from "../plugins/postcss/pruneVars.mjs";
5
5
  import { tailwindCleanup } from "../plugins/postcss/tailwindCleanup.mjs";
6
6
  import { mergeMediaQueries } from "../plugins/postcss/mergeMediaQueries.mjs";
7
+ import { quoteFontFamilies } from "../plugins/postcss/quoteFontFamilies.mjs";
8
+ import { decodeStyleEntities } from "../utils/decodeStyleEntities.mjs";
7
9
  import { dirname, relative, resolve } from "node:path";
8
10
  import postcss from "postcss";
9
11
  import tailwindcssPostcss from "@tailwindcss/postcss";
@@ -25,16 +27,6 @@ function createProcessor(config) {
25
27
  ]);
26
28
  }
27
29
  /**
28
- * Decode HTML entities that Vue SSR encodes inside <style> tags.
29
- *
30
- * Vue's renderToString HTML-encodes quotes and other characters
31
- * inside <style> tags within templates, breaking CSS like
32
- * `@import "@maizzle/tailwindcss"` → `@import &quot;...&quot;`
33
- */
34
- function decodeEntities(str) {
35
- return str.replace(/&quot;/g, "\"").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&#39;/g, "'").replace(/&apos;/g, "'");
36
- }
37
- /**
38
30
  * Check if CSS content uses Tailwind features that require source scanning.
39
31
  *
40
32
  * Only CSS that imports Tailwind (or @maizzle/tailwindcss) needs @source
@@ -66,7 +58,7 @@ function lowerSyntax(css) {
66
58
  * then sorts and merges media queries.
67
59
  */
68
60
  async function optimizeCss(css, config) {
69
- const plugins = [...tailwindCleanup(config)];
61
+ const plugins = [...tailwindCleanup(config), quoteFontFamilies()];
70
62
  const mediaPlugin = mergeMediaQueries(config);
71
63
  if (mediaPlugin) plugins.push(mediaPlugin);
72
64
  return (await postcss(plugins).process(css, { from: void 0 })).css;
@@ -122,7 +114,7 @@ async function tailwindcss(dom, config, filePath) {
122
114
  if (!rawContent.trim()) return;
123
115
  styleTags.push({
124
116
  node: el,
125
- cssContent: decodeEntities(rawContent)
117
+ cssContent: decodeStyleEntities(rawContent)
126
118
  });
127
119
  });
128
120
  if (!styleTags.length) return dom;
@@ -1 +1 @@
1
- {"version":3,"file":"tailwindcss.mjs","names":["resolveProps","pruneVars"],"sources":["../../src/transformers/tailwindcss.ts"],"sourcesContent":["import postcss from 'postcss'\nimport tailwindcssPostcss from '@tailwindcss/postcss'\nimport postcssCalc from 'postcss-calc'\nimport resolveProps from '../plugins/postcss/resolveProps.ts'\nimport pruneVars from '../plugins/postcss/pruneVars.ts'\nimport safeParser from 'postcss-safe-parser'\nimport { transform } from 'lightningcss'\nimport { resolve, dirname, relative } from 'node:path'\nimport type { ChildNode, Element } from 'domhandler'\nimport { walk } from '../utils/ast/index.ts'\nimport { tailwindCleanup } from '../plugins/postcss/tailwindCleanup.ts'\nimport { mergeMediaQueries } from '../plugins/postcss/mergeMediaQueries.ts'\nimport type { MaizzleConfig } from '../types/config.ts'\n\nfunction createProcessor(config: MaizzleConfig) {\n return postcss([\n tailwindcssPostcss({\n base: config.css?.base,\n transformAssetUrls: false,\n optimize: false, // we run Lightning CSS manually\n }),\n resolveProps(),\n postcssCalc({}),\n pruneVars(),\n ])\n}\n\n/**\n * Decode HTML entities that Vue SSR encodes inside <style> tags.\n *\n * Vue's renderToString HTML-encodes quotes and other characters\n * inside <style> tags within templates, breaking CSS like\n * `@import \"@maizzle/tailwindcss\"` → `@import &quot;...&quot;`\n */\nfunction decodeEntities(str: string): string {\n return str\n .replace(/&quot;/g, '\"')\n .replace(/&amp;/g, '&')\n .replace(/&lt;/g, '<')\n .replace(/&gt;/g, '>')\n .replace(/&#39;/g, \"'\")\n .replace(/&apos;/g, \"'\")\n}\n\n/**\n * Check if CSS content uses Tailwind features that require source scanning.\n *\n * Only CSS that imports Tailwind (or @maizzle/tailwindcss) needs @source\n * directives. Plain CSS without Tailwind imports doesn't need scanning\n * and would pass through @source directives unconsumed.\n */\nfunction usesTailwind(css: string): boolean {\n return /((@import|@reference)\\s+[\"'](tailwindcss|@maizzle\\/tailwindcss)|@tailwind\\s)/.test(css)\n}\n\n/**\n * Lower modern CSS syntax using lightningcss.\n *\n * Targets IE 1 to maximize syntax lowering — converts modern features\n * like nesting, oklch(), color-mix(), @property, etc. into simple CSS\n * that email clients can understand.\n */\nfunction lowerSyntax(css: string): string {\n const result = transform({\n filename: 'email.css',\n code: Buffer.from(css),\n minify: false,\n targets: {\n ie: 4 << 5,\n },\n })\n\n return result.code.toString()\n}\n\n/**\n * Run cleanup and media query merging on the compiled CSS.\n *\n * Removes unwanted selectors (:host, :lang) and at-rules (@layer, @property),\n * then sorts and merges media queries.\n */\nasync function optimizeCss(css: string, config: MaizzleConfig): Promise<string> {\n const plugins: postcss.Plugin[] = [...tailwindCleanup(config)]\n\n const mediaPlugin = mergeMediaQueries(config)\n if (mediaPlugin) plugins.push(mediaPlugin)\n\n const result = await postcss(plugins).process(css, { from: undefined })\n\n return result.css\n}\n\n/**\n * Build @source directives for Tailwind CSS scanning.\n *\n * Configures two types of sources:\n * 1. Exclusions for output dir and user-configured paths\n * 2. Inline source with all class attribute values from the rendered DOM,\n * capturing classes from all components (built-in + user), dynamic\n * expressions, and the template itself — Tailwind's scanner handles\n * the actual class extraction from these raw values\n */\nfunction buildSourceDirectives(dom: ChildNode[], config: MaizzleConfig, fromDir: string): string {\n const directives: string[] = []\n\n // Exclude output dir and user-configured paths\n const excludePaths = [\n resolve(config.output?.path ?? 'dist'),\n ...(config.css?.exclude ?? []).map(p => resolve(p)),\n ]\n\n for (const p of excludePaths) {\n directives.push(`@source not \"${relative(fromDir, resolve(p))}\";`)\n }\n\n // Inline source: collect all class attribute values from the rendered DOM.\n // After Vue SSR, the DOM contains every class from every component\n // (built-in framework components, user components, dynamic bindings).\n // We pass these raw values to Tailwind's scanner via @source inline().\n const classes: string[] = []\n walk(dom, (n) => {\n const cls = (n as Element).attribs?.class\n if (cls) classes.push(cls)\n })\n\n if (classes.length) {\n directives.push(`@source inline(\"${classes.join(' ')}\");`)\n }\n\n return directives.join('\\n')\n}\n\n/**\n * Tailwind CSS transformer.\n *\n * Compiles CSS inside <style> tags in the DOM using\n * @tailwindcss/postcss, then lowers modern CSS syntax with lightningcss.\n *\n * Configures Tailwind sources to scan:\n * - Rendered class attributes (via `@source inline`) for all classes from all components\n * - User project files (via Tailwind's auto-detection from base/from path)\n *\n * User `@source` and `@source not directives` in style tags are preserved.\n * Source directives are only added to style tags that import Tailwind.\n *\n * Runs as the first transformer in the pipeline so that subsequent\n * transformers (inliner, purge, etc.) work with fully compiled CSS.\n */\nexport async function tailwindcss(dom: ChildNode[], config: MaizzleConfig, filePath?: string): Promise<ChildNode[]> {\n const styleTags: { node: Element; cssContent: string }[] = []\n\n walk(dom, (node) => {\n if ((node as Element).name !== 'style') return\n\n const el = node as Element\n const attrs = el.attribs || {}\n\n // `raw` opts out of compilation entirely (marker is consumed here).\n // `embed`/`data-embed` only signal \"preserve tag after inlining\" — they\n // still need to go through compile so Tailwind/@apply resolves.\n if ('raw' in attrs) {\n delete el.attribs.raw\n return\n }\n\n // Get text content from children and decode HTML entities\n const rawContent = el.children\n .filter(child => child.type === 'text')\n .map(child => (child as any).data)\n .join('')\n\n if (!rawContent.trim()) return\n\n styleTags.push({ node: el, cssContent: decodeEntities(rawContent) })\n })\n\n if (!styleTags.length) return dom\n\n const fromPath = filePath ?? resolve(process.cwd(), 'template.vue')\n const fromDir = dirname(fromPath)\n\n // Only compute source directives if at least one style tag uses Tailwind\n const hasTailwindStyles = styleTags.some(({ cssContent }) => usesTailwind(cssContent))\n const sourceDirectives = hasTailwindStyles\n ? buildSourceDirectives(dom, config, fromDir)\n : ''\n\n // Create processor once — reused for all style tags in this template\n const processor = createProcessor(config)\n\n for (let i = 0; i < styleTags.length; i++) {\n const { node, cssContent } = styleTags[i]\n\n // Only add source directives to style tags that import Tailwind —\n // plain CSS doesn't need them and @tailwindcss/postcss would leave\n // the directives unconsumed in the output\n const fullCss = usesTailwind(cssContent)\n ? `${cssContent}\\n${sourceDirectives}`\n : cssContent\n\n try {\n const result = await processor.process(\n fullCss,\n {\n from: `${fromPath}?style=${i}`,\n parser: safeParser,\n }\n )\n\n const lowered = lowerSyntax(result.css)\n const optimized = await optimizeCss(lowered, config)\n\n // Replace the style tag's children with the compiled CSS\n node.children = [{\n type: 'text',\n data: optimized,\n parent: node,\n } as any]\n } catch {\n // If CSS processing fails, still replace with decoded content\n // so HTML entities don't break the CSS\n node.children = [{\n type: 'text',\n data: cssContent,\n parent: node,\n } as any]\n }\n }\n\n return dom\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"tailwindcss.mjs","names":["resolveProps","pruneVars"],"sources":["../../src/transformers/tailwindcss.ts"],"sourcesContent":["import postcss from 'postcss'\nimport tailwindcssPostcss from '@tailwindcss/postcss'\nimport postcssCalc from 'postcss-calc'\nimport resolveProps from '../plugins/postcss/resolveProps.ts'\nimport pruneVars from '../plugins/postcss/pruneVars.ts'\nimport safeParser from 'postcss-safe-parser'\nimport { transform } from 'lightningcss'\nimport { resolve, dirname, relative } from 'node:path'\nimport type { ChildNode, Element } from 'domhandler'\nimport { walk } from '../utils/ast/index.ts'\nimport { tailwindCleanup } from '../plugins/postcss/tailwindCleanup.ts'\nimport { mergeMediaQueries } from '../plugins/postcss/mergeMediaQueries.ts'\nimport { quoteFontFamilies } from '../plugins/postcss/quoteFontFamilies.ts'\nimport { decodeStyleEntities } from '../utils/decodeStyleEntities.ts'\nimport type { MaizzleConfig } from '../types/config.ts'\n\nfunction createProcessor(config: MaizzleConfig) {\n return postcss([\n tailwindcssPostcss({\n base: config.css?.base,\n transformAssetUrls: false,\n optimize: false, // we run Lightning CSS manually\n }),\n resolveProps(),\n postcssCalc({}),\n pruneVars(),\n ])\n}\n\n/**\n * Check if CSS content uses Tailwind features that require source scanning.\n *\n * Only CSS that imports Tailwind (or @maizzle/tailwindcss) needs @source\n * directives. Plain CSS without Tailwind imports doesn't need scanning\n * and would pass through @source directives unconsumed.\n */\nfunction usesTailwind(css: string): boolean {\n return /((@import|@reference)\\s+[\"'](tailwindcss|@maizzle\\/tailwindcss)|@tailwind\\s)/.test(css)\n}\n\n/**\n * Lower modern CSS syntax using lightningcss.\n *\n * Targets IE 1 to maximize syntax lowering — converts modern features\n * like nesting, oklch(), color-mix(), @property, etc. into simple CSS\n * that email clients can understand.\n */\nfunction lowerSyntax(css: string): string {\n const result = transform({\n filename: 'email.css',\n code: Buffer.from(css),\n minify: false,\n targets: {\n ie: 4 << 5,\n },\n })\n\n return result.code.toString()\n}\n\n/**\n * Run cleanup and media query merging on the compiled CSS.\n *\n * Removes unwanted selectors (:host, :lang) and at-rules (@layer, @property),\n * then sorts and merges media queries.\n */\nasync function optimizeCss(css: string, config: MaizzleConfig): Promise<string> {\n const plugins: postcss.Plugin[] = [...tailwindCleanup(config), quoteFontFamilies()]\n\n const mediaPlugin = mergeMediaQueries(config)\n if (mediaPlugin) plugins.push(mediaPlugin)\n\n const result = await postcss(plugins).process(css, { from: undefined })\n\n return result.css\n}\n\n/**\n * Build @source directives for Tailwind CSS scanning.\n *\n * Configures two types of sources:\n * 1. Exclusions for output dir and user-configured paths\n * 2. Inline source with all class attribute values from the rendered DOM,\n * capturing classes from all components (built-in + user), dynamic\n * expressions, and the template itself — Tailwind's scanner handles\n * the actual class extraction from these raw values\n */\nfunction buildSourceDirectives(dom: ChildNode[], config: MaizzleConfig, fromDir: string): string {\n const directives: string[] = []\n\n // Exclude output dir and user-configured paths\n const excludePaths = [\n resolve(config.output?.path ?? 'dist'),\n ...(config.css?.exclude ?? []).map(p => resolve(p)),\n ]\n\n for (const p of excludePaths) {\n directives.push(`@source not \"${relative(fromDir, resolve(p))}\";`)\n }\n\n // Inline source: collect all class attribute values from the rendered DOM.\n // After Vue SSR, the DOM contains every class from every component\n // (built-in framework components, user components, dynamic bindings).\n // We pass these raw values to Tailwind's scanner via @source inline().\n const classes: string[] = []\n walk(dom, (n) => {\n const cls = (n as Element).attribs?.class\n if (cls) classes.push(cls)\n })\n\n if (classes.length) {\n directives.push(`@source inline(\"${classes.join(' ')}\");`)\n }\n\n return directives.join('\\n')\n}\n\n/**\n * Tailwind CSS transformer.\n *\n * Compiles CSS inside <style> tags in the DOM using\n * @tailwindcss/postcss, then lowers modern CSS syntax with lightningcss.\n *\n * Configures Tailwind sources to scan:\n * - Rendered class attributes (via `@source inline`) for all classes from all components\n * - User project files (via Tailwind's auto-detection from base/from path)\n *\n * User `@source` and `@source not directives` in style tags are preserved.\n * Source directives are only added to style tags that import Tailwind.\n *\n * Runs as the first transformer in the pipeline so that subsequent\n * transformers (inliner, purge, etc.) work with fully compiled CSS.\n */\nexport async function tailwindcss(dom: ChildNode[], config: MaizzleConfig, filePath?: string): Promise<ChildNode[]> {\n const styleTags: { node: Element; cssContent: string }[] = []\n\n walk(dom, (node) => {\n if ((node as Element).name !== 'style') return\n\n const el = node as Element\n const attrs = el.attribs || {}\n\n // `raw` opts out of compilation entirely (marker is consumed here).\n // `embed`/`data-embed` only signal \"preserve tag after inlining\" — they\n // still need to go through compile so Tailwind/@apply resolves.\n if ('raw' in attrs) {\n delete el.attribs.raw\n return\n }\n\n // Get text content from children and decode HTML entities\n const rawContent = el.children\n .filter(child => child.type === 'text')\n .map(child => (child as any).data)\n .join('')\n\n if (!rawContent.trim()) return\n\n styleTags.push({ node: el, cssContent: decodeStyleEntities(rawContent) })\n })\n\n if (!styleTags.length) return dom\n\n const fromPath = filePath ?? resolve(process.cwd(), 'template.vue')\n const fromDir = dirname(fromPath)\n\n // Only compute source directives if at least one style tag uses Tailwind\n const hasTailwindStyles = styleTags.some(({ cssContent }) => usesTailwind(cssContent))\n const sourceDirectives = hasTailwindStyles\n ? buildSourceDirectives(dom, config, fromDir)\n : ''\n\n // Create processor once — reused for all style tags in this template\n const processor = createProcessor(config)\n\n for (let i = 0; i < styleTags.length; i++) {\n const { node, cssContent } = styleTags[i]\n\n // Only add source directives to style tags that import Tailwind —\n // plain CSS doesn't need them and @tailwindcss/postcss would leave\n // the directives unconsumed in the output\n const fullCss = usesTailwind(cssContent)\n ? `${cssContent}\\n${sourceDirectives}`\n : cssContent\n\n try {\n const result = await processor.process(\n fullCss,\n {\n from: `${fromPath}?style=${i}`,\n parser: safeParser,\n }\n )\n\n const lowered = lowerSyntax(result.css)\n const optimized = await optimizeCss(lowered, config)\n\n // Replace the style tag's children with the compiled CSS\n node.children = [{\n type: 'text',\n data: optimized,\n parent: node,\n } as any]\n } catch {\n // If CSS processing fails, still replace with decoded content\n // so HTML entities don't break the CSS\n node.children = [{\n type: 'text',\n data: cssContent,\n parent: node,\n } as any]\n }\n }\n\n return dom\n}\n"],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ //#region src/utils/decodeStyleEntities.d.ts
2
+ /**
3
+ * Decode HTML entities that Vue SSR encodes inside `<style>` tags.
4
+ *
5
+ * Vue's `renderToString` HTML-encodes quotes and angle brackets within
6
+ * style elements in templates, breaking CSS like
7
+ * `@import "tailwindcss"` → `@import &quot;tailwindcss&quot;`.
8
+ *
9
+ * `&amp;` is decoded last so previously-decoded entities are not
10
+ * re-processed.
11
+ */
12
+ declare function decodeStyleEntities(s: string): string;
13
+ //#endregion
14
+ export { decodeStyleEntities };
15
+ //# sourceMappingURL=decodeStyleEntities.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decodeStyleEntities.d.mts","names":[],"sources":["../../src/utils/decodeStyleEntities.ts"],"mappings":";;AAUA;;;;;;;;;iBAAgB,mBAAA,CAAoB,CAAA"}
@@ -0,0 +1,18 @@
1
+ //#region src/utils/decodeStyleEntities.ts
2
+ /**
3
+ * Decode HTML entities that Vue SSR encodes inside `<style>` tags.
4
+ *
5
+ * Vue's `renderToString` HTML-encodes quotes and angle brackets within
6
+ * style elements in templates, breaking CSS like
7
+ * `@import "tailwindcss"` → `@import &quot;tailwindcss&quot;`.
8
+ *
9
+ * `&amp;` is decoded last so previously-decoded entities are not
10
+ * re-processed.
11
+ */
12
+ function decodeStyleEntities(s) {
13
+ return s.replace(/&quot;/g, "\"").replace(/&#39;/g, "'").replace(/&apos;/g, "'").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
14
+ }
15
+
16
+ //#endregion
17
+ export { decodeStyleEntities };
18
+ //# sourceMappingURL=decodeStyleEntities.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decodeStyleEntities.mjs","names":[],"sources":["../../src/utils/decodeStyleEntities.ts"],"sourcesContent":["/**\n * Decode HTML entities that Vue SSR encodes inside `<style>` tags.\n *\n * Vue's `renderToString` HTML-encodes quotes and angle brackets within\n * style elements in templates, breaking CSS like\n * `@import \"tailwindcss\"` → `@import &quot;tailwindcss&quot;`.\n *\n * `&amp;` is decoded last so previously-decoded entities are not\n * re-processed.\n */\nexport function decodeStyleEntities(s: string): string {\n return s\n .replace(/&quot;/g, '\"')\n .replace(/&#39;/g, \"'\")\n .replace(/&apos;/g, \"'\")\n .replace(/&lt;/g, '<')\n .replace(/&gt;/g, '>')\n .replace(/&amp;/g, '&')\n}\n"],"mappings":";;;;;;;;;;;AAUA,SAAgB,oBAAoB,GAAmB;AACrD,QAAO,EACJ,QAAQ,WAAW,KAAI,CACvB,QAAQ,UAAU,IAAI,CACtB,QAAQ,WAAW,IAAI,CACvB,QAAQ,SAAS,IAAI,CACrB,QAAQ,SAAS,IAAI,CACrB,QAAQ,UAAU,IAAI"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maizzle/framework",
3
- "version": "6.0.0-rc.13",
3
+ "version": "6.0.0-rc.14",
4
4
  "description": "Maizzle is a framework that helps you quickly build HTML emails with Tailwind CSS.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -43,6 +43,7 @@
43
43
  "clsx": "^2.1.1",
44
44
  "color-shorthand-hex-to-six-digit": "^5.1.3",
45
45
  "css-select": "^7.0.0",
46
+ "culori": "^4.0.2",
46
47
  "defu": "^6.1.4",
47
48
  "dom-serializer": "^3.0.0",
48
49
  "domhandler": "^6.0.1",