@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.
- package/dist/plugins/postcss/flattenGradients.d.ts +14 -0
- package/dist/plugins/postcss/flattenGradients.d.ts.map +1 -0
- package/dist/plugins/postcss/flattenGradients.js +144 -0
- package/dist/plugins/postcss/flattenGradients.js.map +1 -0
- package/dist/server/ui/.vite/deps/_metadata.json +13 -13
- package/dist/server/ui/App.vue +82 -36
- package/dist/server/ui/components/ui/command/Command.vue +4 -1
- package/dist/server/ui/components/ui/command/CommandInput.vue +3 -2
- package/dist/transformers/tailwindcss.d.ts.map +1 -1
- package/dist/transformers/tailwindcss.js +72 -2
- package/dist/transformers/tailwindcss.js.map +1 -1
- package/dist/utils/compileTailwindCss.d.ts +3 -2
- package/dist/utils/compileTailwindCss.d.ts.map +1 -1
- package/dist/utils/compileTailwindCss.js +5 -3
- package/dist/utils/compileTailwindCss.js.map +1 -1
- package/package.json +1 -1
|
@@ -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": "
|
|
3
|
-
"configHash": "
|
|
2
|
+
"hash": "d911c788",
|
|
3
|
+
"configHash": "f30e72cc",
|
|
4
4
|
"lockfileHash": "e3b0c442",
|
|
5
|
-
"browserHash": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
64
|
+
"fileHash": "b53353fd",
|
|
65
65
|
"needsInterop": false
|
|
66
66
|
}
|
|
67
67
|
},
|
package/dist/server/ui/App.vue
CHANGED
|
@@ -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
|
-
|
|
173
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
236
|
+
if (tokens.length === 0) return { groups, total: 0 }
|
|
237
|
+
let total = 0
|
|
223
238
|
for (const t of templates.value) {
|
|
224
|
-
|
|
225
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
{{
|
|
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
|
-
|
|
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":";;;;;;;;;;;;;;;;;;;
|
|
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
|
|
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":"
|
|
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":"
|
|
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"}
|