@maizzle/framework 6.0.14 → 6.1.0

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.
@@ -0,0 +1,14 @@
1
+ import { Plugin } from "postcss";
2
+ //#region src/plugins/postcss/flattenGradients.d.ts
3
+ /** A gradient combo found on a DOM element. */
4
+ interface GradientCombo {
5
+ /** Generated class name to emit the flat rule for. */
6
+ className: string;
7
+ /** The gradient utility class tokens present on the element. */
8
+ classes: string[];
9
+ }
10
+ declare const _default: (combos?: GradientCombo[]) => Plugin;
11
+ declare const postcss = true;
12
+ //#endregion
13
+ export { GradientCombo, _default as default, postcss };
14
+ //# sourceMappingURL=flattenGradients.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flattenGradients.d.ts","names":[],"sources":["../../../src/plugins/postcss/flattenGradients.ts"],"mappings":";;;UA6BiB;;EAEf;;EAEA;;cAuCc,WAAA,SAAQ,oBAAuB;cA4HlC"}
@@ -0,0 +1,144 @@
1
+ import { Rule } from "postcss";
2
+ //#region src/plugins/postcss/flattenGradients.ts
3
+ /**
4
+ * postcss-flatten-gradients
5
+ *
6
+ * Tailwind v4 renders gradients as CSS-variable machinery split across
7
+ * separate utility rules (`bg-linear-*`, `from-*`, `via-*`, `to-*`),
8
+ * combining only on the element that carries every class. Email clients
9
+ * don't support `var()` and Maizzle inlines styles, so the variables
10
+ * never resolve and the gradient renders nothing.
11
+ *
12
+ * Given the per-element gradient combos (computed from the DOM by the
13
+ * tailwindcss transformer), this plugin reads the `--tw-gradient-*`
14
+ * values off the utility rules and emits a single flat rule per combo:
15
+ *
16
+ * .bg-linear-gradient-to-bl-from-indigo-50-to-indigo-600 {
17
+ * background-image: linear-gradient(to bottom left,
18
+ * var(--color-indigo-50), var(--color-indigo-600));
19
+ * }
20
+ *
21
+ * Colors stay as `var(--color-*)` (or literals) so the downstream
22
+ * resolveProps + lightningcss steps convert them to hex, exactly like
23
+ * every other Tailwind color. The dead utility rules are then removed.
24
+ */
25
+ const PLUGIN_NAME = "postcss-flatten-gradients";
26
+ const GRADIENT_UTILITY_RE = /^(from|via|to)-|^bg-(linear|radial|conic)\b/;
27
+ const GENERATED_RE = /^bg-(linear|radial|conic)-gradient-/;
28
+ /**
29
+ * Color-interpolation method Tailwind appends to the position (e.g.
30
+ * `in oklab`, `in oklch longer hue`). It always trails the direction and
31
+ * is unsupported in email, so strip from `in <space>` to the end.
32
+ */
33
+ const INTERP_RE = /(?:^|\s+)in\s+\S.*$/i;
34
+ /** Turn a single-class selector into its raw class token (`.from-\[\#f00\]` -> `from-[#f00]`). */
35
+ function selectorToClass(selector) {
36
+ const match = selector.match(/^\.((?:\\.|[^\s,>+~.])+)$/);
37
+ if (!match) return null;
38
+ return match[1].replace(/\\(.)/g, "$1");
39
+ }
40
+ /** Strip the interpolation method so `to top in oklab` becomes `to top`. */
41
+ function stripInterpolation(position) {
42
+ return position.replace(INTERP_RE, "").trim();
43
+ }
44
+ var flattenGradients_default = (combos = []) => {
45
+ return {
46
+ postcssPlugin: PLUGIN_NAME,
47
+ Once(root) {
48
+ if (!combos.length) return;
49
+ const utilities = /* @__PURE__ */ new Map();
50
+ root.walkRules((rule) => {
51
+ const cls = selectorToClass(rule.selector);
52
+ if (!cls || !GRADIENT_UTILITY_RE.test(cls)) return;
53
+ const info = utilities.get(cls) ?? {};
54
+ rule.each((node) => {
55
+ if (node.type !== "decl") return;
56
+ switch (node.prop) {
57
+ case "--tw-gradient-position":
58
+ info.position = node.value;
59
+ break;
60
+ case "--tw-gradient-from":
61
+ info.from = node.value;
62
+ break;
63
+ case "--tw-gradient-via":
64
+ info.via = node.value;
65
+ break;
66
+ case "--tw-gradient-to":
67
+ info.to = node.value;
68
+ break;
69
+ case "--tw-gradient-from-position":
70
+ info.fromPosition = node.value;
71
+ break;
72
+ case "--tw-gradient-via-position":
73
+ info.viaPosition = node.value;
74
+ break;
75
+ case "--tw-gradient-to-position":
76
+ info.toPosition = node.value;
77
+ break;
78
+ case "background-image": {
79
+ const fn = node.value.match(/^(linear|radial|conic)-gradient\(/);
80
+ if (fn) info.fn = fn[1];
81
+ break;
82
+ }
83
+ }
84
+ });
85
+ info.rule = rule;
86
+ utilities.set(cls, info);
87
+ });
88
+ for (const combo of combos) {
89
+ const fnClass = combo.classes.find((c) => /^bg-(linear|radial|conic)\b/.test(c));
90
+ if (!fnClass) continue;
91
+ const fnInfo = utilities.get(fnClass);
92
+ const fn = fnInfo?.fn;
93
+ if (!fn) continue;
94
+ let from;
95
+ let via;
96
+ let to;
97
+ let fromPosition;
98
+ let viaPosition;
99
+ let toPosition;
100
+ for (const cls of combo.classes) {
101
+ const info = utilities.get(cls);
102
+ if (!info) continue;
103
+ if (info.from !== void 0) from = info.from;
104
+ if (info.via !== void 0) via = info.via;
105
+ if (info.to !== void 0) to = info.to;
106
+ if (info.fromPosition !== void 0) fromPosition = info.fromPosition;
107
+ if (info.viaPosition !== void 0) viaPosition = info.viaPosition;
108
+ if (info.toPosition !== void 0) toPosition = info.toPosition;
109
+ }
110
+ from = from ?? "transparent";
111
+ to = to ?? "transparent";
112
+ const direction = fnInfo?.position ? stripInterpolation(fnInfo.position) : "";
113
+ const stops = [];
114
+ stops.push(fromPosition && fromPosition !== "0%" ? `${from} ${fromPosition}` : from);
115
+ if (via !== void 0) stops.push(viaPosition && viaPosition !== "50%" ? `${via} ${viaPosition}` : via);
116
+ stops.push(toPosition && toPosition !== "100%" ? `${to} ${toPosition}` : to);
117
+ const args = direction ? `${direction}, ${stops.join(", ")}` : stops.join(", ");
118
+ const generated = new Rule({ selector: `.${combo.className}` });
119
+ generated.append({
120
+ prop: "background-image",
121
+ value: `${fn}-gradient(${args})`
122
+ });
123
+ /**
124
+ * Insert at the position of the source utility rule (still present
125
+ * — removal happens below) so the gradient keeps that rule's place
126
+ * in the cascade. Appending to the root would move it past any
127
+ * later author CSS that should override it.
128
+ */
129
+ const anchor = fnInfo?.rule;
130
+ if (anchor?.parent) anchor.before(generated);
131
+ else root.append(generated);
132
+ }
133
+ root.walkRules((rule) => {
134
+ const cls = selectorToClass(rule.selector);
135
+ if (cls && !GENERATED_RE.test(cls) && GRADIENT_UTILITY_RE.test(cls)) rule.remove();
136
+ });
137
+ }
138
+ };
139
+ };
140
+ const postcss = true;
141
+ //#endregion
142
+ export { flattenGradients_default as default, postcss };
143
+
144
+ //# sourceMappingURL=flattenGradients.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flattenGradients.js","names":[],"sources":["../../../src/plugins/postcss/flattenGradients.ts"],"sourcesContent":["/**\n * postcss-flatten-gradients\n *\n * Tailwind v4 renders gradients as CSS-variable machinery split across\n * separate utility rules (`bg-linear-*`, `from-*`, `via-*`, `to-*`),\n * combining only on the element that carries every class. Email clients\n * don't support `var()` and Maizzle inlines styles, so the variables\n * never resolve and the gradient renders nothing.\n *\n * Given the per-element gradient combos (computed from the DOM by the\n * tailwindcss transformer), this plugin reads the `--tw-gradient-*`\n * values off the utility rules and emits a single flat rule per combo:\n *\n * .bg-linear-gradient-to-bl-from-indigo-50-to-indigo-600 {\n * background-image: linear-gradient(to bottom left,\n * var(--color-indigo-50), var(--color-indigo-600));\n * }\n *\n * Colors stay as `var(--color-*)` (or literals) so the downstream\n * resolveProps + lightningcss steps convert them to hex, exactly like\n * every other Tailwind color. The dead utility rules are then removed.\n */\n\nimport { Rule } from 'postcss'\nimport type { Plugin, Root } from 'postcss'\n\nconst PLUGIN_NAME = 'postcss-flatten-gradients'\n\n/** A gradient combo found on a DOM element. */\nexport interface GradientCombo {\n /** Generated class name to emit the flat rule for. */\n className: string\n /** The gradient utility class tokens present on the element. */\n classes: string[]\n}\n\ntype GradientFn = 'linear' | 'radial' | 'conic'\n\ninterface UtilityInfo {\n fn?: GradientFn\n position?: string\n from?: string\n via?: string\n to?: string\n fromPosition?: string\n viaPosition?: string\n toPosition?: string\n /** The source rule, used to anchor the generated rule in the cascade. */\n rule?: Rule\n}\n\nconst GRADIENT_UTILITY_RE = /^(from|via|to)-|^bg-(linear|radial|conic)\\b/\nconst GENERATED_RE = /^bg-(linear|radial|conic)-gradient-/\n/**\n * Color-interpolation method Tailwind appends to the position (e.g.\n * `in oklab`, `in oklch longer hue`). It always trails the direction and\n * is unsupported in email, so strip from `in <space>` to the end.\n */\nconst INTERP_RE = /(?:^|\\s+)in\\s+\\S.*$/i\n\n/** Turn a single-class selector into its raw class token (`.from-\\[\\#f00\\]` -> `from-[#f00]`). */\nfunction selectorToClass(selector: string): string | null {\n const match = selector.match(/^\\.((?:\\\\.|[^\\s,>+~.])+)$/)\n if (!match) return null\n return match[1].replace(/\\\\(.)/g, '$1')\n}\n\n/** Strip the interpolation method so `to top in oklab` becomes `to top`. */\nfunction stripInterpolation(position: string): string {\n return position.replace(INTERP_RE, '').trim()\n}\n\nexport default (combos: GradientCombo[] = []): Plugin => {\n return {\n postcssPlugin: PLUGIN_NAME,\n\n Once(root: Root) {\n if (!combos.length) return\n\n // Index every single-class gradient utility rule by its class token.\n const utilities = new Map<string, UtilityInfo>()\n\n root.walkRules((rule) => {\n const cls = selectorToClass(rule.selector)\n if (!cls || !GRADIENT_UTILITY_RE.test(cls)) return\n\n const info = utilities.get(cls) ?? {}\n\n // Only read declarations directly on the rule, skipping the\n // nested @supports overrides Tailwind adds for oklab.\n rule.each((node) => {\n if (node.type !== 'decl') return\n switch (node.prop) {\n case '--tw-gradient-position':\n info.position = node.value\n break\n case '--tw-gradient-from':\n info.from = node.value\n break\n case '--tw-gradient-via':\n info.via = node.value\n break\n case '--tw-gradient-to':\n info.to = node.value\n break\n case '--tw-gradient-from-position':\n info.fromPosition = node.value\n break\n case '--tw-gradient-via-position':\n info.viaPosition = node.value\n break\n case '--tw-gradient-to-position':\n info.toPosition = node.value\n break\n case 'background-image': {\n const fn = node.value.match(/^(linear|radial|conic)-gradient\\(/)\n if (fn) info.fn = fn[1] as GradientFn\n break\n }\n }\n })\n\n info.rule = rule\n utilities.set(cls, info)\n })\n\n // Emit one flat rule per combo.\n for (const combo of combos) {\n const fnClass = combo.classes.find(c => /^bg-(linear|radial|conic)\\b/.test(c))\n if (!fnClass) continue\n\n const fnInfo = utilities.get(fnClass)\n const fn = fnInfo?.fn\n if (!fn) continue\n\n let from: string | undefined\n let via: string | undefined\n let to: string | undefined\n let fromPosition: string | undefined\n let viaPosition: string | undefined\n let toPosition: string | undefined\n\n for (const cls of combo.classes) {\n const info = utilities.get(cls)\n if (!info) continue\n if (info.from !== undefined) from = info.from\n if (info.via !== undefined) via = info.via\n if (info.to !== undefined) to = info.to\n if (info.fromPosition !== undefined) fromPosition = info.fromPosition\n if (info.viaPosition !== undefined) viaPosition = info.viaPosition\n if (info.toPosition !== undefined) toPosition = info.toPosition\n }\n\n // Tailwind defaults an unset from/to to transparent (#0000).\n from = from ?? 'transparent'\n to = to ?? 'transparent'\n\n const direction = fnInfo?.position ? stripInterpolation(fnInfo.position) : ''\n\n const stops: string[] = []\n stops.push(fromPosition && fromPosition !== '0%' ? `${from} ${fromPosition}` : from)\n if (via !== undefined) {\n stops.push(viaPosition && viaPosition !== '50%' ? `${via} ${viaPosition}` : via)\n }\n stops.push(toPosition && toPosition !== '100%' ? `${to} ${toPosition}` : to)\n\n const args = direction ? `${direction}, ${stops.join(', ')}` : stops.join(', ')\n\n const generated = new Rule({ selector: `.${combo.className}` })\n generated.append({ prop: 'background-image', value: `${fn}-gradient(${args})` })\n\n /**\n * Insert at the position of the source utility rule (still present\n * — removal happens below) so the gradient keeps that rule's place\n * in the cascade. Appending to the root would move it past any\n * later author CSS that should override it.\n */\n const anchor = fnInfo?.rule\n if (anchor?.parent) {\n anchor.before(generated)\n } else {\n root.append(generated)\n }\n }\n\n // Remove the now-dead gradient utility rules.\n root.walkRules((rule) => {\n const cls = selectorToClass(rule.selector)\n if (cls && !GENERATED_RE.test(cls) && GRADIENT_UTILITY_RE.test(cls)) {\n rule.remove()\n }\n })\n },\n }\n}\n\nexport const postcss = true\n"],"mappings":"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"}
@@ -1,67 +1,67 @@
1
1
  {
2
- "hash": "23747cad",
3
- "configHash": "0b6de85b",
2
+ "hash": "d911c788",
3
+ "configHash": "f30e72cc",
4
4
  "lockfileHash": "e3b0c442",
5
- "browserHash": "8b48d1b3",
5
+ "browserHash": "e06e436e",
6
6
  "optimized": {
7
7
  "@lucide/vue": {
8
8
  "src": "../../../../../node_modules/@lucide/vue/dist/esm/lucide-vue.mjs",
9
9
  "file": "@lucide_vue.js",
10
- "fileHash": "50377f8c",
10
+ "fileHash": "9aa466eb",
11
11
  "needsInterop": false
12
12
  },
13
13
  "@vueuse/core": {
14
14
  "src": "../../../../../node_modules/@vueuse/core/dist/index.js",
15
15
  "file": "@vueuse_core.js",
16
- "fileHash": "d92a36c7",
16
+ "fileHash": "ab3d1d06",
17
17
  "needsInterop": false
18
18
  },
19
19
  "@vueuse/shared": {
20
20
  "src": "../../../../../node_modules/@vueuse/shared/dist/index.js",
21
21
  "file": "@vueuse_shared.js",
22
- "fileHash": "aecbfdf5",
22
+ "fileHash": "45075773",
23
23
  "needsInterop": false
24
24
  },
25
25
  "class-variance-authority": {
26
26
  "src": "../../../../../node_modules/class-variance-authority/dist/index.mjs",
27
27
  "file": "class-variance-authority.js",
28
- "fileHash": "f5f51e55",
28
+ "fileHash": "16ab9b85",
29
29
  "needsInterop": false
30
30
  },
31
31
  "clsx": {
32
32
  "src": "../../../../../node_modules/clsx/dist/clsx.mjs",
33
33
  "file": "clsx.js",
34
- "fileHash": "51424891",
34
+ "fileHash": "30b50a06",
35
35
  "needsInterop": false
36
36
  },
37
37
  "culori": {
38
38
  "src": "../../../../../node_modules/culori/src/index.js",
39
39
  "file": "culori.js",
40
- "fileHash": "701d4061",
40
+ "fileHash": "06b22b9a",
41
41
  "needsInterop": false
42
42
  },
43
43
  "reka-ui": {
44
44
  "src": "../../../../../node_modules/reka-ui/dist/index.js",
45
45
  "file": "reka-ui.js",
46
- "fileHash": "13c97ce1",
46
+ "fileHash": "e2455ae5",
47
47
  "needsInterop": false
48
48
  },
49
49
  "tailwind-merge": {
50
50
  "src": "../../../../../node_modules/tailwind-merge/dist/bundle-cjs.js",
51
51
  "file": "tailwind-merge.js",
52
- "fileHash": "2480ed27",
52
+ "fileHash": "d658c1c9",
53
53
  "needsInterop": true
54
54
  },
55
55
  "vue-router": {
56
56
  "src": "../../../../../node_modules/vue-router/dist/vue-router.js",
57
57
  "file": "vue-router.js",
58
- "fileHash": "ae39d1fb",
58
+ "fileHash": "47dd3c59",
59
59
  "needsInterop": false
60
60
  },
61
61
  "vue": {
62
62
  "src": "../../../../../node_modules/vue/dist/vue.runtime.esm-bundler.js",
63
63
  "file": "vue.js",
64
- "fileHash": "f2da2c76",
64
+ "fileHash": "b53353fd",
65
65
  "needsInterop": false
66
66
  }
67
67
  },
@@ -1,5 +1,5 @@
1
1
  <script setup lang="ts">
2
- import { ref, computed, onMounted, onUnmounted, watch, watchEffect } from 'vue'
2
+ import { ref, computed, nextTick, onMounted, onUnmounted, watch, watchEffect } from 'vue'
3
3
  import { RouterLink, RouterView, useRoute, useRouter } from 'vue-router'
4
4
  import { Monitor, CodeXml, Smartphone, ChevronDown, ArrowUp, ArrowDown, CornerDownLeft, Check, Search, FileCode, FileText, Code, BookText, MailQuestion, Moon, Sun } from '@lucide/vue'
5
5
  import SidebarClose from '@/components/SidebarClose.vue'
@@ -129,16 +129,22 @@ watch(sidebarOpen, (open) => {
129
129
  localStorage.setItem('maizzle:sidebar', open ? 'open' : 'closed')
130
130
  })
131
131
 
132
- async function fetchTemplates() {
132
+ async function fetchTemplates(revealActive = false) {
133
133
  const res = await fetch('/__maizzle/templates')
134
134
  templates.value = await res.json()
135
135
  loading.value = false
136
+ // On first load (e.g. after a dev-server restart) reveal the template being
137
+ // viewed so a restart doesn't leave you lost in a long sidebar.
138
+ if (revealActive && isPreviewRoute.value) {
139
+ await nextTick()
140
+ scrollSidebarToTemplate(route.path)
141
+ }
136
142
  }
137
143
 
138
- onMounted(fetchTemplates)
144
+ onMounted(() => fetchTemplates(true))
139
145
 
140
146
  if ((import.meta as any).hot) {
141
- (import.meta as any).hot.on('maizzle:templates-changed', fetchTemplates)
147
+ (import.meta as any).hot.on('maizzle:templates-changed', () => fetchTemplates())
142
148
  }
143
149
 
144
150
  const grouped = computed(() => {
@@ -169,13 +175,23 @@ const modKey = isMac ? '⌘' : 'Ctrl'
169
175
  const commandOpen = ref(false)
170
176
  const commandSearch = ref('')
171
177
 
172
- watch(commandOpen, (open) => {
173
- if (!open) commandSearch.value = ''
174
- })
178
+ /**
179
+ * The search survives closing the palette so you can refine a query instead
180
+ * of retyping it (handy when hunting through hundreds of emails). It is only
181
+ * cleared when an actual command runs; selecting a template keeps the query
182
+ * so you can come back to the same list.
183
+ *
184
+ * Reka resets its filter whenever an item is selected, which would wipe the
185
+ * query, so template selection restores it on the next tick.
186
+ */
187
+ function closeCommandPalette(clear = false) {
188
+ commandOpen.value = false
189
+ if (clear) commandSearch.value = ''
190
+ }
175
191
 
176
192
 
177
193
  async function copyHtml() {
178
- commandOpen.value = false
194
+ closeCommandPalette(true)
179
195
  const slug = route.params.template as string
180
196
  if (!slug) return
181
197
  const res = await fetch(`/__maizzle/render/${slug}`)
@@ -183,7 +199,7 @@ async function copyHtml() {
183
199
  }
184
200
 
185
201
  async function copyPlaintext() {
186
- commandOpen.value = false
202
+ closeCommandPalette(true)
187
203
  const slug = route.params.template as string
188
204
  if (!slug) return
189
205
  const res = await fetch(`/__maizzle/plaintext/${slug}`)
@@ -191,7 +207,7 @@ async function copyPlaintext() {
191
207
  }
192
208
 
193
209
  async function copySource() {
194
- commandOpen.value = false
210
+ closeCommandPalette(true)
195
211
  const slug = route.params.template as string
196
212
  if (!slug) return
197
213
  const res = await fetch(`/__maizzle/vue-source/${slug}`)
@@ -201,44 +217,72 @@ async function copySource() {
201
217
  await navigator.clipboard.writeText(el.textContent || '')
202
218
  }
203
219
 
204
- const commandGrouped = computed(() => {
205
- const groups: Record<string, Template[]> = {}
206
-
207
- for (const t of templates.value) {
208
- const parts = t.path.split('/')
209
- const dir = parts.length > 1 ? parts.slice(0, -1).join('/') : '.'
210
- if (!groups[dir]) groups[dir] = []
211
- groups[dir].push(t)
212
- }
213
-
214
- return groups
215
- })
216
-
217
220
  const { contains } = useFilter({ sensitivity: 'base' })
218
221
 
219
- const filteredTemplatesCount = computed(() => {
222
+ /**
223
+ * Cap how many template results render at once. Rendering every template as
224
+ * a CommandItem freezes the palette on the first keystroke in large projects
225
+ * (100s–1000s of templates), so we only render the matches, up to this many.
226
+ */
227
+ const MAX_TEMPLATE_RESULTS = 50
228
+
229
+ /**
230
+ * Match templates against the search in a single pass: count every match for
231
+ * the footer, but only group the first MAX_TEMPLATE_RESULTS for rendering.
232
+ */
233
+ const templateResults = computed(() => {
234
+ const groups: Record<string, Template[]> = {}
220
235
  const tokens = commandSearch.value.split(/\s+/).filter(Boolean)
221
- if (tokens.length === 0) return 0
222
- let count = 0
236
+ if (tokens.length === 0) return { groups, total: 0 }
237
+ let total = 0
223
238
  for (const t of templates.value) {
224
- const haystack = `${getFileName(t.path)} ${t.path.split('/').join(' ')}`
225
- if (tokens.every(token => contains(haystack, token))) count++
239
+ // Include the raw path so slash-style queries (e.g. "marketing/headers")
240
+ // match, alongside the space-split path for whitespace tokens.
241
+ const haystack = `${getFileName(t.path)} ${t.path} ${t.path.split('/').join(' ')}`
242
+ if (!tokens.every(token => contains(haystack, token))) continue
243
+ total++
244
+ if (total <= MAX_TEMPLATE_RESULTS) {
245
+ const parts = t.path.split('/')
246
+ const dir = parts.length > 1 ? parts.slice(0, -1).join('/') : '.'
247
+ ;(groups[dir] ??= []).push(t)
248
+ }
226
249
  }
227
- return count
250
+ return { groups, total }
228
251
  })
229
252
 
253
+ const filteredCommandGrouped = computed(() => templateResults.value.groups)
254
+ const filteredTemplatesCount = computed(() => templateResults.value.total)
255
+ const templatesTruncated = computed(() => templateResults.value.total > MAX_TEMPLATE_RESULTS)
230
256
 
231
257
  function getFileName(path: string) {
232
258
  return path.split('/').pop() || path
233
259
  }
234
260
 
261
+ /**
262
+ * Reveal a template in the sidebar. Jumping via the palette (or landing on a
263
+ * template after a dev-server restart) otherwise leaves the active item
264
+ * scrolled out of view in large projects.
265
+ */
266
+ function scrollSidebarToTemplate(href: string) {
267
+ // CSS.escape the value — hrefs come from file paths and may contain
268
+ // characters that would otherwise break the attribute selector.
269
+ document.querySelector(`[data-sidebar-template=${CSS.escape(href)}]`)?.scrollIntoView({ block: 'center' })
270
+ }
271
+
235
272
  function onCommandSelect(href: string) {
236
- commandOpen.value = false
273
+ // Keep the query so navigating to a template doesn't lose the search;
274
+ // restore it after Reka's select-reset has wiped the filter.
275
+ const query = commandSearch.value
276
+ closeCommandPalette()
237
277
  router.push(href)
278
+ nextTick(() => {
279
+ commandSearch.value = query
280
+ scrollSidebarToTemplate(href)
281
+ })
238
282
  }
239
283
 
240
284
  function openExternal(url: string) {
241
- commandOpen.value = false
285
+ closeCommandPalette(true)
242
286
  window.open(url, '_blank', 'noopener')
243
287
  }
244
288
 
@@ -286,7 +330,7 @@ function onWindowBlur() {
286
330
  }
287
331
 
288
332
  function toggleDarkMode() {
289
- commandOpen.value = false
333
+ closeCommandPalette(true)
290
334
  darkMode.value = !darkMode.value
291
335
  }
292
336
 
@@ -364,8 +408,9 @@ onUnmounted(() => {
364
408
  as-child
365
409
  size="sm"
366
410
  :is-active="isActive(t.href)"
411
+ class="data-[active=true]:font-semibold"
367
412
  >
368
- <RouterLink :to="t.href" class="truncate">
413
+ <RouterLink :to="t.href" :data-sidebar-template="t.href" class="truncate">
369
414
  <span class="mz-tpl-icon size-4 shrink-0 opacity-70" :class="t.path.endsWith('.md') ? 'mz-tpl-icon-md' : 'mz-tpl-icon-vue'" />
370
415
  <span class="truncate">{{ t.name }}</span>
371
416
  </RouterLink>
@@ -526,7 +571,7 @@ onUnmounted(() => {
526
571
 
527
572
  <!-- Templates -->
528
573
  <template v-if="commandSearch">
529
- <CommandGroup v-for="(items, dir) in commandGrouped" :key="dir" :heading="String(dir)">
574
+ <CommandGroup v-for="(items, dir) in filteredCommandGrouped" :key="dir" :heading="String(dir)">
530
575
  <CommandItem
531
576
  v-for="t in items"
532
577
  :key="t.path"
@@ -535,7 +580,7 @@ onUnmounted(() => {
535
580
  >
536
581
  <span class="mz-tpl-icon size-3 shrink-0 opacity-70" :class="t.path.endsWith('.md') ? 'mz-tpl-icon-md' : 'mz-tpl-icon-vue'" />
537
582
  <span>{{ getFileName(t.path) }}</span>
538
- <span class="sr-only">{{ ' ' + t.path.split('/').join(' ') }}</span>
583
+ <span class="sr-only">{{ ' ' + t.path + ' ' + t.path.split('/').join(' ') }}</span>
539
584
  </CommandItem>
540
585
  </CommandGroup>
541
586
  </template>
@@ -555,7 +600,8 @@ onUnmounted(() => {
555
600
  Close
556
601
  </span>
557
602
  <span v-if="commandSearch" class="ml-auto">
558
- {{ filteredTemplatesCount }} {{ filteredTemplatesCount === 1 ? 'result' : 'results' }}
603
+ <template v-if="templatesTruncated">Showing {{ MAX_TEMPLATE_RESULTS }} of {{ filteredTemplatesCount }} refine to narrow</template>
604
+ <template v-else>{{ filteredTemplatesCount }} {{ filteredTemplatesCount === 1 ? 'result' : 'results' }}</template>
559
605
  </span>
560
606
  </div>
561
607
  </CommandDialog>
@@ -68,7 +68,10 @@ function filterItems() {
68
68
  filterState.filtered.count = itemCount
69
69
  }
70
70
 
71
- watch(() => filterState.search, () => {
71
+ // Re-run on search change and whenever the item set changes, so a search
72
+ // applied before items register (e.g. a restored query on re-open) filters
73
+ // once those items mount instead of being stuck on "no results".
74
+ watch([() => filterState.search, () => allItems.value.size], () => {
72
75
  filterItems()
73
76
  })
74
77
 
@@ -27,12 +27,13 @@ const forwardedProps = useForwardProps(delegatedProps)
27
27
 
28
28
  const { filterState } = useCommand()
29
29
 
30
- // Sync external v-model → internal filter
30
+ // Sync external v-model → internal filter (immediate so a preset value
31
+ // populates the filter when the palette re-opens)
31
32
  watch(() => props.modelValue, (val) => {
32
33
  if (val !== undefined && val !== filterState.search) {
33
34
  filterState.search = val
34
35
  }
35
- })
36
+ }, { immediate: true })
36
37
 
37
38
  // Sync internal filter → external v-model
38
39
  watch(() => filterState.search, (val) => {
@@ -1 +1 @@
1
- {"version":3,"file":"tailwindcss.d.ts","names":[],"sources":["../../src/transformers/tailwindcss.ts"],"mappings":";;;;;;;;;;;;;;;;;;;iBA6EsB,YAAY,KAAK,aAAa,QAAQ,eAAe,oBAAoB,QAAQ"}
1
+ {"version":3,"file":"tailwindcss.d.ts","names":[],"sources":["../../src/transformers/tailwindcss.ts"],"mappings":";;;;;;;;;;;;;;;;;;;iBA4JsB,YAAY,KAAK,aAAa,QAAQ,eAAe,oBAAoB,QAAQ"}
@@ -43,6 +43,66 @@ function buildSourceDirectives(dom, config, fromDir) {
43
43
  if (classes.length) directives.push(`@source inline("${classes.join(" ")}");`);
44
44
  return directives.join("\n");
45
45
  }
46
+ const GRADIENT_FN_RE = /^bg-(linear|radial|conic)\b/;
47
+ const GRADIENT_GENERATED_RE = /^bg-(linear|radial|conic)-gradient-/;
48
+ const GRADIENT_STOP_RE = /^(from|via|to)-/;
49
+ /** Rank a stop class so generated names are stable regardless of author order. */
50
+ function stopRank(cls) {
51
+ const prefix = cls.startsWith("from-") ? 0 : cls.startsWith("via-") ? 1 : 2;
52
+ const isPosition = /^(from|via|to)-\d+%$/.test(cls) ? 1 : 0;
53
+ return prefix * 2 + isPosition;
54
+ }
55
+ /** Sanitize a class token into a valid, readable CSS class name fragment. */
56
+ function sanitize(token) {
57
+ return token.replace(/[[\]#()]/g, "").replace(/[/,.%\s]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
58
+ }
59
+ /**
60
+ * Detect Tailwind gradient class combinations on DOM elements.
61
+ *
62
+ * A gradient only works once its `bg-linear/radial/conic` direction class
63
+ * combines with `from-*`/`via-*`/`to-*` stops on the same element. This
64
+ * collects those per-element combos, rewrites each element to a single
65
+ * readable class (e.g. `bg-linear-gradient-to-bl-from-indigo-50-to-indigo-600`),
66
+ * and returns the combos so the flattenGradients plugin can emit one flat
67
+ * `background-image` rule per unique combo.
68
+ */
69
+ function collectGradientCombos(dom) {
70
+ const bySignature = /* @__PURE__ */ new Map();
71
+ const usedNames = /* @__PURE__ */ new Map();
72
+ walk(dom, (node) => {
73
+ const el = node;
74
+ const cls = el.attribs?.class;
75
+ if (!cls) return;
76
+ const tokens = cls.split(/\s+/).filter(Boolean);
77
+ const fnClass = tokens.find((t) => GRADIENT_FN_RE.test(t) && !GRADIENT_GENERATED_RE.test(t));
78
+ if (!fnClass) return;
79
+ const gradientClasses = tokens.filter((t) => GRADIENT_FN_RE.test(t) && !GRADIENT_GENERATED_RE.test(t) || GRADIENT_STOP_RE.test(t));
80
+ const stops = gradientClasses.filter((t) => t !== fnClass).sort((a, b) => stopRank(a) - stopRank(b) || a.localeCompare(b));
81
+ const ordered = [fnClass, ...stops];
82
+ const signature = ordered.join(" ");
83
+ let combo = bySignature.get(signature);
84
+ if (!combo) {
85
+ const fn = fnClass.match(GRADIENT_FN_RE)[1];
86
+ const parts = [fnClass.replace(new RegExp(`^bg-${fn}-?`), ""), ...stops].filter(Boolean).map(sanitize);
87
+ let name = `bg-${fn}-gradient${parts.length ? `-${parts.join("-")}` : ""}`;
88
+ const existing = usedNames.get(name);
89
+ if (existing && existing !== signature) {
90
+ let n = 2;
91
+ while (usedNames.has(`${name}-${n}`)) n++;
92
+ name = `${name}-${n}`;
93
+ }
94
+ usedNames.set(name, signature);
95
+ combo = {
96
+ className: name,
97
+ classes: ordered
98
+ };
99
+ bySignature.set(signature, combo);
100
+ }
101
+ const rest = tokens.filter((t) => !gradientClasses.includes(t));
102
+ el.attribs.class = [...rest, combo.className].join(" ");
103
+ });
104
+ return [...bySignature.values()];
105
+ }
46
106
  /**
47
107
  * Tailwind CSS transformer.
48
108
  *
@@ -84,7 +144,16 @@ async function tailwindcss(dom, config, filePath) {
84
144
  if (!styleTags.length) return dom;
85
145
  const fromPath = filePath ?? resolve(process.cwd(), "template.vue");
86
146
  const fromDir = dirname(fromPath);
87
- const sourceDirectives = styleTags.some(({ cssContent }) => usesTailwind(cssContent)) ? buildSourceDirectives(dom, config, fromDir) : "";
147
+ const hasTailwindStyles = styleTags.some(({ cssContent }) => usesTailwind(cssContent));
148
+ const sourceDirectives = hasTailwindStyles ? buildSourceDirectives(dom, config, fromDir) : "";
149
+ /**
150
+ * Collect gradient combos and rewrite elements to single classes.
151
+ * Runs after source directives are built (so the utility classes are
152
+ * still scanned) and only feeds the first Tailwind style tag, whose
153
+ * `:root` holds the theme colors the flat rules reference.
154
+ */
155
+ const gradientCombos = hasTailwindStyles ? collectGradientCombos(dom) : [];
156
+ const firstTailwindStyle = styleTags.findIndex(({ cssContent }) => usesTailwind(cssContent));
88
157
  for (let i = 0; i < styleTags.length; i++) {
89
158
  const { node, cssContent } = styleTags[i];
90
159
  /**
@@ -93,10 +162,11 @@ async function tailwindcss(dom, config, filePath) {
93
162
  * leave the directives unconsumed in the output.
94
163
  */
95
164
  const fullCss = usesTailwind(cssContent) ? `${cssContent}\n${sourceDirectives}` : cssContent;
165
+ const combos = i === firstTailwindStyle ? gradientCombos : [];
96
166
  try {
97
167
  node.children = [{
98
168
  type: "text",
99
- data: await compileTailwindCss(fullCss, config, `${fromPath}?style=${i}`),
169
+ data: await compileTailwindCss(fullCss, config, `${fromPath}?style=${i}`, combos),
100
170
  parent: node
101
171
  }];
102
172
  } catch {
@@ -1 +1 @@
1
- {"version":3,"file":"tailwindcss.js","names":[],"sources":["../../src/transformers/tailwindcss.ts"],"sourcesContent":["import { resolve, dirname, relative } from 'pathe'\nimport type { ChildNode, Element } from 'domhandler'\nimport { walk } from '../utils/ast/index.ts'\nimport { decodeStyleEntities } from '../utils/decodeStyleEntities.ts'\nimport { compileTailwindCss } from '../utils/compileTailwindCss.ts'\nimport type { MaizzleConfig } from '../types/config.ts'\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 * 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 /**\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\n * bindings). We pass these raw values to Tailwind's\n * scanner via @source inline().\n */\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 /**\n * `raw` opts out of compilation entirely (marker is consumed here).\n * `embed`/`data-embed` only signal \"preserve tag after inlining\"\n * — they still need to go through compile so Tailwind/@apply\n * resolves.\n */\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 for (let i = 0; i < styleTags.length; i++) {\n const { node, cssContent } = styleTags[i]\n\n /**\n * Only add source directives to style tags that import Tailwind —\n * plain CSS doesn't need them and @tailwindcss/postcss would\n * leave the directives unconsumed in the output.\n */\n const fullCss = usesTailwind(cssContent)\n ? `${cssContent}\\n${sourceDirectives}`\n : cssContent\n\n try {\n const optimized = await compileTailwindCss(fullCss, config, `${fromPath}?style=${i}`)\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 /**\n * If CSS processing fails, still replace with decoded content\n * so HTML entities don't break the CSS.\n */\n node.children = [{\n type: 'text',\n data: cssContent,\n parent: node,\n } as any]\n }\n }\n\n return dom\n}\n"],"mappings":";;;;;;;;;;;;;AAcA,SAAS,aAAa,KAAsB;CAC1C,OAAO,+EAA+E,KAAK,GAAG;AAChG;;;;;;;;;;;AAYA,SAAS,sBAAsB,KAAkB,QAAuB,SAAyB;CAC/F,MAAM,aAAuB,CAAC;CAG9B,MAAM,eAAe,CACnB,QAAQ,OAAO,QAAQ,QAAQ,MAAM,GACrC,IAAI,OAAO,KAAK,WAAW,CAAC,EAAA,CAAG,KAAI,MAAK,QAAQ,CAAC,CAAC,CACpD;CAEA,KAAK,MAAM,KAAK,cACd,WAAW,KAAK,gBAAgB,SAAS,SAAS,QAAQ,CAAC,CAAC,EAAE,GAAG;;;;;;;;CAUnE,MAAM,UAAoB,CAAC;CAC3B,KAAK,MAAM,MAAM;EACf,MAAM,MAAO,EAAc,SAAS;EACpC,IAAI,KAAK,QAAQ,KAAK,GAAG;CAC3B,CAAC;CAED,IAAI,QAAQ,QACV,WAAW,KAAK,mBAAmB,QAAQ,KAAK,GAAG,EAAE,IAAI;CAG3D,OAAO,WAAW,KAAK,IAAI;AAC7B;;;;;;;;;;;;;;;;;AAkBA,eAAsB,YAAY,KAAkB,QAAuB,UAAyC;CAClH,MAAM,YAAqD,CAAC;CAE5D,KAAK,MAAM,SAAS;EAClB,IAAK,KAAiB,SAAS,SAAS;EAExC,MAAM,KAAK;;;;;;;EASX,IAAI,SARU,GAAG,SAQG;GAClB,OAAO,GAAG,QAAQ;GAClB;EACF;EAGA,MAAM,aAAa,GAAG,SACnB,QAAO,UAAS,MAAM,SAAS,MAAM,CAAC,CACtC,KAAI,UAAU,MAAc,IAAI,CAAC,CACjC,KAAK,EAAE;EAEV,IAAI,CAAC,WAAW,KAAK,GAAG;EAExB,UAAU,KAAK;GAAE,MAAM;GAAI,YAAY,oBAAoB,UAAU;EAAE,CAAC;CAC1E,CAAC;CAED,IAAI,CAAC,UAAU,QAAQ,OAAO;CAE9B,MAAM,WAAW,YAAY,QAAQ,QAAQ,IAAI,GAAG,cAAc;CAClE,MAAM,UAAU,QAAQ,QAAQ;CAIhC,MAAM,mBADoB,UAAU,MAAM,EAAE,iBAAiB,aAAa,UAAU,CAC3C,IACrC,sBAAsB,KAAK,QAAQ,OAAO,IAC1C;CAEJ,KAAK,IAAI,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK;EACzC,MAAM,EAAE,MAAM,eAAe,UAAU;;;;;;EAOvC,MAAM,UAAU,aAAa,UAAU,IACnC,GAAG,WAAW,IAAI,qBAClB;EAEJ,IAAI;GAIF,KAAK,WAAW,CAAC;IACf,MAAM;IACN,MAAM,MALgB,mBAAmB,SAAS,QAAQ,GAAG,SAAS,SAAS,GAAG;IAMlF,QAAQ;GACV,CAAQ;EACV,QAAQ;;;;;GAKN,KAAK,WAAW,CAAC;IACf,MAAM;IACN,MAAM;IACN,QAAQ;GACV,CAAQ;EACV;CACF;CAEA,OAAO;AACT"}
1
+ {"version":3,"file":"tailwindcss.js","names":[],"sources":["../../src/transformers/tailwindcss.ts"],"sourcesContent":["import { resolve, dirname, relative } from 'pathe'\nimport type { ChildNode, Element } from 'domhandler'\nimport { walk } from '../utils/ast/index.ts'\nimport { decodeStyleEntities } from '../utils/decodeStyleEntities.ts'\nimport { compileTailwindCss } from '../utils/compileTailwindCss.ts'\nimport type { GradientCombo } from '../plugins/postcss/flattenGradients.ts'\nimport type { MaizzleConfig } from '../types/config.ts'\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 * 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 /**\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\n * bindings). We pass these raw values to Tailwind's\n * scanner via @source inline().\n */\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\nconst GRADIENT_FN_RE = /^bg-(linear|radial|conic)\\b/\nconst GRADIENT_GENERATED_RE = /^bg-(linear|radial|conic)-gradient-/\nconst GRADIENT_STOP_RE = /^(from|via|to)-/\n\n/** Rank a stop class so generated names are stable regardless of author order. */\nfunction stopRank(cls: string): number {\n const prefix = cls.startsWith('from-') ? 0 : cls.startsWith('via-') ? 1 : 2\n const isPosition = /^(from|via|to)-\\d+%$/.test(cls) ? 1 : 0\n return prefix * 2 + isPosition\n}\n\n/** Sanitize a class token into a valid, readable CSS class name fragment. */\nfunction sanitize(token: string): string {\n return token\n .replace(/[[\\]#()]/g, '')\n .replace(/[/,.%\\s]+/g, '-')\n .replace(/-+/g, '-')\n .replace(/^-|-$/g, '')\n}\n\n/**\n * Detect Tailwind gradient class combinations on DOM elements.\n *\n * A gradient only works once its `bg-linear/radial/conic` direction class\n * combines with `from-*`/`via-*`/`to-*` stops on the same element. This\n * collects those per-element combos, rewrites each element to a single\n * readable class (e.g. `bg-linear-gradient-to-bl-from-indigo-50-to-indigo-600`),\n * and returns the combos so the flattenGradients plugin can emit one flat\n * `background-image` rule per unique combo.\n */\nfunction collectGradientCombos(dom: ChildNode[]): GradientCombo[] {\n const bySignature = new Map<string, GradientCombo>()\n const usedNames = new Map<string, string>()\n\n walk(dom, (node) => {\n const el = node as Element\n const cls = el.attribs?.class\n if (!cls) return\n\n const tokens = cls.split(/\\s+/).filter(Boolean)\n const fnClass = tokens.find(t => GRADIENT_FN_RE.test(t) && !GRADIENT_GENERATED_RE.test(t))\n if (!fnClass) return\n\n const gradientClasses = tokens.filter(\n t => (GRADIENT_FN_RE.test(t) && !GRADIENT_GENERATED_RE.test(t)) || GRADIENT_STOP_RE.test(t),\n )\n const stops = gradientClasses.filter(t => t !== fnClass).sort((a, b) => stopRank(a) - stopRank(b) || a.localeCompare(b))\n const ordered = [fnClass, ...stops]\n const signature = ordered.join(' ')\n\n let combo = bySignature.get(signature)\n if (!combo) {\n const fn = fnClass.match(GRADIENT_FN_RE)![1]\n const dirRemainder = fnClass.replace(new RegExp(`^bg-${fn}-?`), '')\n const parts = [dirRemainder, ...stops].filter(Boolean).map(sanitize)\n let name = `bg-${fn}-gradient${parts.length ? `-${parts.join('-')}` : ''}`\n\n // Guard against sanitize collisions from distinct combos.\n const existing = usedNames.get(name)\n if (existing && existing !== signature) {\n let n = 2\n while (usedNames.has(`${name}-${n}`)) n++\n name = `${name}-${n}`\n }\n usedNames.set(name, signature)\n\n combo = { className: name, classes: ordered }\n bySignature.set(signature, combo)\n }\n\n // Replace the gradient utilities with the single generated class.\n const rest = tokens.filter(t => !gradientClasses.includes(t))\n el.attribs.class = [...rest, combo.className].join(' ')\n })\n\n return [...bySignature.values()]\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 /**\n * `raw` opts out of compilation entirely (marker is consumed here).\n * `embed`/`data-embed` only signal \"preserve tag after inlining\"\n * — they still need to go through compile so Tailwind/@apply\n * resolves.\n */\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 /**\n * Collect gradient combos and rewrite elements to single classes.\n * Runs after source directives are built (so the utility classes are\n * still scanned) and only feeds the first Tailwind style tag, whose\n * `:root` holds the theme colors the flat rules reference.\n */\n const gradientCombos = hasTailwindStyles ? collectGradientCombos(dom) : []\n const firstTailwindStyle = styleTags.findIndex(({ cssContent }) => usesTailwind(cssContent))\n\n for (let i = 0; i < styleTags.length; i++) {\n const { node, cssContent } = styleTags[i]\n\n /**\n * Only add source directives to style tags that import Tailwind —\n * plain CSS doesn't need them and @tailwindcss/postcss would\n * leave the directives unconsumed in the output.\n */\n const fullCss = usesTailwind(cssContent)\n ? `${cssContent}\\n${sourceDirectives}`\n : cssContent\n\n const combos = i === firstTailwindStyle ? gradientCombos : []\n\n try {\n const optimized = await compileTailwindCss(fullCss, config, `${fromPath}?style=${i}`, combos)\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 /**\n * If CSS processing fails, still replace with decoded content\n * so HTML entities don't break the CSS.\n */\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,7 +1,8 @@
1
1
  import { MaizzleConfig } from "../types/config.js";
2
+ import { GradientCombo } from "../plugins/postcss/flattenGradients.js";
2
3
  import postcss from "postcss";
3
4
  //#region src/utils/compileTailwindCss.d.ts
4
- declare function createTailwindProcessor(config: MaizzleConfig): postcss.Processor;
5
+ declare function createTailwindProcessor(config: MaizzleConfig, gradientCombos?: GradientCombo[]): postcss.Processor;
5
6
  declare function lowerCssSyntax(css: string): string;
6
7
  declare function optimizeTailwindCss(css: string, config: MaizzleConfig): Promise<string>;
7
8
  /**
@@ -9,7 +10,7 @@ declare function optimizeTailwindCss(css: string, config: MaizzleConfig): Promis
9
10
  * runs @tailwindcss/postcss, lowers modern syntax via lightningcss,
10
11
  * then applies cleanup + media-query merging.
11
12
  */
12
- declare function compileTailwindCss(cssInput: string, config: MaizzleConfig, from: string): Promise<string>;
13
+ declare function compileTailwindCss(cssInput: string, config: MaizzleConfig, from: string, gradientCombos?: GradientCombo[]): Promise<string>;
13
14
  //#endregion
14
15
  export { compileTailwindCss, createTailwindProcessor, lowerCssSyntax, optimizeTailwindCss };
15
16
  //# sourceMappingURL=compileTailwindCss.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"compileTailwindCss.d.ts","names":[],"sources":["../../src/utils/compileTailwindCss.ts"],"mappings":";;;iBAagB,wBAAwB,QAAQ,gBAAa,QAAA;iBAe7C,eAAe;iBAWT,oBAAoB,aAAa,QAAQ,gBAAgB;;;;;;iBAgBzD,mBACpB,kBACA,QAAQ,eACR,eACC"}
1
+ {"version":3,"file":"compileTailwindCss.d.ts","names":[],"sources":["../../src/utils/compileTailwindCss.ts"],"mappings":";;;;iBAcgB,wBAAwB,QAAQ,eAAe,iBAAgB,kBAAoB,QAAA;iBAkBnF,eAAe;iBAWT,oBAAoB,aAAa,QAAQ,gBAAgB;;;;;;iBAgBzD,mBACpB,kBACA,QAAQ,eACR,cACA,iBAAgB,kBACf"}
@@ -1,5 +1,6 @@
1
1
  import resolveProps_default from "../plugins/postcss/resolveProps.js";
2
2
  import pruneVars_default from "../plugins/postcss/pruneVars.js";
3
+ import flattenGradients_default from "../plugins/postcss/flattenGradients.js";
3
4
  import { tailwindCleanup } from "../plugins/postcss/tailwindCleanup.js";
4
5
  import { mergeMediaQueries } from "../plugins/postcss/mergeMediaQueries.js";
5
6
  import { quoteFontFamilies } from "../plugins/postcss/quoteFontFamilies.js";
@@ -10,7 +11,7 @@ import postcssCalc from "postcss-calc";
10
11
  import safeParser from "postcss-safe-parser";
11
12
  import { transform } from "lightningcss";
12
13
  //#region src/utils/compileTailwindCss.ts
13
- function createTailwindProcessor(config) {
14
+ function createTailwindProcessor(config, gradientCombos = []) {
14
15
  return postcss([
15
16
  resolveMaizzleImports(),
16
17
  tailwindcssPostcss({
@@ -18,6 +19,7 @@ function createTailwindProcessor(config) {
18
19
  transformAssetUrls: false,
19
20
  optimize: false
20
21
  }),
22
+ flattenGradients_default(gradientCombos),
21
23
  resolveProps_default(),
22
24
  postcssCalc({}),
23
25
  pruneVars_default()
@@ -43,8 +45,8 @@ async function optimizeTailwindCss(css, config) {
43
45
  * runs @tailwindcss/postcss, lowers modern syntax via lightningcss,
44
46
  * then applies cleanup + media-query merging.
45
47
  */
46
- async function compileTailwindCss(cssInput, config, from) {
47
- return optimizeTailwindCss(lowerCssSyntax((await createTailwindProcessor(config).process(cssInput, {
48
+ async function compileTailwindCss(cssInput, config, from, gradientCombos = []) {
49
+ return optimizeTailwindCss(lowerCssSyntax((await createTailwindProcessor(config, gradientCombos).process(cssInput, {
48
50
  from,
49
51
  parser: safeParser
50
52
  })).css), config);
@@ -1 +1 @@
1
- {"version":3,"file":"compileTailwindCss.js","names":["resolveProps","pruneVars"],"sources":["../../src/utils/compileTailwindCss.ts"],"sourcesContent":["import postcss from 'postcss'\nimport tailwindcssPostcss from '@tailwindcss/postcss'\nimport postcssCalc from 'postcss-calc'\nimport safeParser from 'postcss-safe-parser'\nimport { transform } from 'lightningcss'\nimport resolveProps from '../plugins/postcss/resolveProps.ts'\nimport pruneVars from '../plugins/postcss/pruneVars.ts'\nimport { tailwindCleanup } from '../plugins/postcss/tailwindCleanup.ts'\nimport { mergeMediaQueries } from '../plugins/postcss/mergeMediaQueries.ts'\nimport { quoteFontFamilies } from '../plugins/postcss/quoteFontFamilies.ts'\nimport { resolveMaizzleImports } from '../plugins/postcss/resolveMaizzleImports.ts'\nimport type { MaizzleConfig } from '../types/config.ts'\n\nexport function createTailwindProcessor(config: MaizzleConfig) {\n return postcss([\n // Must run before @tailwindcss/postcss so it sees absolute import paths\n resolveMaizzleImports(),\n tailwindcssPostcss({\n base: config.css?.base,\n transformAssetUrls: false,\n optimize: false,\n }),\n resolveProps(),\n postcssCalc({}),\n pruneVars(),\n ])\n}\n\nexport function lowerCssSyntax(css: string): string {\n const result = transform({\n filename: 'email.css',\n code: new TextEncoder().encode(css),\n minify: false,\n targets: { ie: 4 << 5 },\n })\n\n return new TextDecoder().decode(result.code)\n}\n\nexport async function optimizeTailwindCss(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 * Compile a Tailwind CSS source string into final email-safe CSS:\n * runs @tailwindcss/postcss, lowers modern syntax via lightningcss,\n * then applies cleanup + media-query merging.\n */\nexport async function compileTailwindCss(\n cssInput: string,\n config: MaizzleConfig,\n from: string,\n): Promise<string> {\n const processor = createTailwindProcessor(config)\n const result = await processor.process(cssInput, { from, parser: safeParser })\n const lowered = lowerCssSyntax(result.css)\n return optimizeTailwindCss(lowered, config)\n}\n"],"mappings":";;;;;;;;;;;;AAaA,SAAgB,wBAAwB,QAAuB;CAC7D,OAAO,QAAQ;EAEb,sBAAsB;EACtB,mBAAmB;GACjB,MAAM,OAAO,KAAK;GAClB,oBAAoB;GACpB,UAAU;EACZ,CAAC;EACDA,qBAAa;EACb,YAAY,CAAC,CAAC;EACdC,kBAAU;CACZ,CAAC;AACH;AAEA,SAAgB,eAAe,KAAqB;CAClD,MAAM,SAAS,UAAU;EACvB,UAAU;EACV,MAAM,IAAI,YAAY,CAAC,CAAC,OAAO,GAAG;EAClC,QAAQ;EACR,SAAS,EAAE,IAAI,IAAO;CACxB,CAAC;CAED,OAAO,IAAI,YAAY,CAAC,CAAC,OAAO,OAAO,IAAI;AAC7C;AAEA,eAAsB,oBAAoB,KAAa,QAAwC;CAC7F,MAAM,UAA4B,CAAC,GAAG,gBAAgB,MAAM,GAAG,kBAAkB,CAAC;CAElF,MAAM,cAAc,kBAAkB,MAAM;CAC5C,IAAI,aAAa,QAAQ,KAAK,WAAW;CAIzC,QAAO,MAFc,QAAQ,OAAO,CAAC,CAAC,QAAQ,KAAK,EAAE,MAAM,KAAA,EAAU,CAAC,EAAA,CAExD;AAChB;;;;;;AAOA,eAAsB,mBACpB,UACA,QACA,MACiB;CAIjB,OAAO,oBADS,gBAAe,MAFb,wBAAwB,MACb,CAAC,CAAC,QAAQ,UAAU;EAAE;EAAM,QAAQ;CAAW,CAAC,EAAA,CACvC,GACL,GAAG,MAAM;AAC5C"}
1
+ {"version":3,"file":"compileTailwindCss.js","names":["flattenGradients","resolveProps","pruneVars"],"sources":["../../src/utils/compileTailwindCss.ts"],"sourcesContent":["import postcss from 'postcss'\nimport tailwindcssPostcss from '@tailwindcss/postcss'\nimport postcssCalc from 'postcss-calc'\nimport safeParser from 'postcss-safe-parser'\nimport { transform } from 'lightningcss'\nimport resolveProps from '../plugins/postcss/resolveProps.ts'\nimport pruneVars from '../plugins/postcss/pruneVars.ts'\nimport flattenGradients, { type GradientCombo } from '../plugins/postcss/flattenGradients.ts'\nimport { tailwindCleanup } from '../plugins/postcss/tailwindCleanup.ts'\nimport { mergeMediaQueries } from '../plugins/postcss/mergeMediaQueries.ts'\nimport { quoteFontFamilies } from '../plugins/postcss/quoteFontFamilies.ts'\nimport { resolveMaizzleImports } from '../plugins/postcss/resolveMaizzleImports.ts'\nimport type { MaizzleConfig } from '../types/config.ts'\n\nexport function createTailwindProcessor(config: MaizzleConfig, gradientCombos: GradientCombo[] = []) {\n return postcss([\n // Must run before @tailwindcss/postcss so it sees absolute import paths\n resolveMaizzleImports(),\n tailwindcssPostcss({\n base: config.css?.base,\n transformAssetUrls: false,\n optimize: false,\n }),\n // Flatten Tailwind's gradient var machinery into email-safe declarations\n // before the var()/color resolution steps run.\n flattenGradients(gradientCombos),\n resolveProps(),\n postcssCalc({}),\n pruneVars(),\n ])\n}\n\nexport function lowerCssSyntax(css: string): string {\n const result = transform({\n filename: 'email.css',\n code: new TextEncoder().encode(css),\n minify: false,\n targets: { ie: 4 << 5 },\n })\n\n return new TextDecoder().decode(result.code)\n}\n\nexport async function optimizeTailwindCss(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 * Compile a Tailwind CSS source string into final email-safe CSS:\n * runs @tailwindcss/postcss, lowers modern syntax via lightningcss,\n * then applies cleanup + media-query merging.\n */\nexport async function compileTailwindCss(\n cssInput: string,\n config: MaizzleConfig,\n from: string,\n gradientCombos: GradientCombo[] = [],\n): Promise<string> {\n const processor = createTailwindProcessor(config, gradientCombos)\n const result = await processor.process(cssInput, { from, parser: safeParser })\n const lowered = lowerCssSyntax(result.css)\n return optimizeTailwindCss(lowered, config)\n}\n"],"mappings":";;;;;;;;;;;;;AAcA,SAAgB,wBAAwB,QAAuB,iBAAkC,CAAC,GAAG;CACnG,OAAO,QAAQ;EAEb,sBAAsB;EACtB,mBAAmB;GACjB,MAAM,OAAO,KAAK;GAClB,oBAAoB;GACpB,UAAU;EACZ,CAAC;EAGDA,yBAAiB,cAAc;EAC/BC,qBAAa;EACb,YAAY,CAAC,CAAC;EACdC,kBAAU;CACZ,CAAC;AACH;AAEA,SAAgB,eAAe,KAAqB;CAClD,MAAM,SAAS,UAAU;EACvB,UAAU;EACV,MAAM,IAAI,YAAY,CAAC,CAAC,OAAO,GAAG;EAClC,QAAQ;EACR,SAAS,EAAE,IAAI,IAAO;CACxB,CAAC;CAED,OAAO,IAAI,YAAY,CAAC,CAAC,OAAO,OAAO,IAAI;AAC7C;AAEA,eAAsB,oBAAoB,KAAa,QAAwC;CAC7F,MAAM,UAA4B,CAAC,GAAG,gBAAgB,MAAM,GAAG,kBAAkB,CAAC;CAElF,MAAM,cAAc,kBAAkB,MAAM;CAC5C,IAAI,aAAa,QAAQ,KAAK,WAAW;CAIzC,QAAO,MAFc,QAAQ,OAAO,CAAC,CAAC,QAAQ,KAAK,EAAE,MAAM,KAAA,EAAU,CAAC,EAAA,CAExD;AAChB;;;;;;AAOA,eAAsB,mBACpB,UACA,QACA,MACA,iBAAkC,CAAC,GAClB;CAIjB,OAAO,oBADS,gBAAe,MAFb,wBAAwB,QAAQ,cACrB,CAAC,CAAC,QAAQ,UAAU;EAAE;EAAM,QAAQ;CAAW,CAAC,EAAA,CACvC,GACL,GAAG,MAAM;AAC5C"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maizzle/framework",
3
- "version": "6.0.14",
3
+ "version": "6.1.0",
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",