@maizzle/framework 6.0.0-rc.9 → 6.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (624) hide show
  1. package/README.md +3 -3
  2. package/bin/maizzle.mjs +1 -1
  3. package/dist/build.d.ts +38 -0
  4. package/dist/build.d.ts.map +1 -0
  5. package/dist/build.js +234 -0
  6. package/dist/build.js.map +1 -0
  7. package/dist/components/Body.vue +32 -3
  8. package/dist/components/Button.vue +91 -62
  9. package/dist/components/CodeBlock.vue +6 -4
  10. package/dist/components/CodeInline.vue +77 -6
  11. package/dist/components/Column.vue +67 -31
  12. package/dist/components/Container.vue +73 -12
  13. package/dist/components/Font.vue +96 -0
  14. package/dist/components/Head.vue +1 -1
  15. package/dist/components/Heading.vue +1 -1
  16. package/dist/components/Hr.vue +33 -0
  17. package/dist/components/Html.vue +36 -3
  18. package/dist/components/Img.vue +332 -0
  19. package/dist/components/Layout.vue +73 -21
  20. package/dist/components/Link.vue +1 -1
  21. package/dist/components/Markdown.vue +51 -24
  22. package/dist/components/MarkdownLayout.vue +39 -0
  23. package/dist/components/NotPlaintext.vue +14 -0
  24. package/dist/components/Outlook.vue +38 -11
  25. package/dist/components/OutlookBg.vue +241 -0
  26. package/dist/components/Plaintext.vue +14 -0
  27. package/dist/components/Preheader.vue +35 -10
  28. package/dist/components/QrCode.vue +157 -0
  29. package/dist/components/Raw.vue +28 -0
  30. package/dist/components/Row.vue +115 -22
  31. package/dist/components/Section.vue +65 -26
  32. package/dist/components/Spacer.vue +35 -29
  33. package/dist/components/Tailwind.vue +45 -0
  34. package/dist/components/Text.vue +3 -3
  35. package/dist/components/Vml.vue +207 -94
  36. package/dist/components/utils.d.ts +53 -0
  37. package/dist/components/utils.d.ts.map +1 -0
  38. package/dist/components/utils.js +80 -0
  39. package/dist/components/utils.js.map +1 -0
  40. package/dist/components/utils.ts +102 -0
  41. package/dist/composables/defineConfig.d.ts +13 -0
  42. package/dist/composables/defineConfig.d.ts.map +1 -0
  43. package/dist/composables/{defineConfig.mjs → defineConfig.js} +7 -9
  44. package/dist/composables/defineConfig.js.map +1 -0
  45. package/dist/composables/renderContext.d.ts +37 -0
  46. package/dist/composables/renderContext.d.ts.map +1 -0
  47. package/dist/composables/renderContext.js +6 -0
  48. package/dist/composables/renderContext.js.map +1 -0
  49. package/dist/composables/useBaseUrl.d.ts +19 -0
  50. package/dist/composables/useBaseUrl.d.ts.map +1 -0
  51. package/dist/composables/useBaseUrl.js +26 -0
  52. package/dist/composables/useBaseUrl.js.map +1 -0
  53. package/dist/composables/useConfig.d.ts +16 -0
  54. package/dist/composables/useConfig.d.ts.map +1 -0
  55. package/dist/composables/useConfig.js +19 -0
  56. package/dist/composables/useConfig.js.map +1 -0
  57. package/dist/composables/useCurrentTemplate.d.ts +31 -0
  58. package/dist/composables/useCurrentTemplate.d.ts.map +1 -0
  59. package/dist/composables/useCurrentTemplate.js +42 -0
  60. package/dist/composables/useCurrentTemplate.js.map +1 -0
  61. package/dist/composables/{useDoctype.d.mts → useDoctype.d.ts} +1 -1
  62. package/dist/composables/useDoctype.d.ts.map +1 -0
  63. package/dist/composables/{useDoctype.mjs → useDoctype.js} +3 -4
  64. package/dist/composables/useDoctype.js.map +1 -0
  65. package/dist/composables/{useEvent.d.mts → useEvent.d.ts} +3 -3
  66. package/dist/composables/useEvent.d.ts.map +1 -0
  67. package/dist/composables/{useEvent.mjs → useEvent.js} +4 -5
  68. package/dist/composables/useEvent.js.map +1 -0
  69. package/dist/composables/useFont.d.ts +50 -0
  70. package/dist/composables/useFont.d.ts.map +1 -0
  71. package/dist/composables/useFont.js +92 -0
  72. package/dist/composables/useFont.js.map +1 -0
  73. package/dist/composables/useOutlookFallback.d.ts +21 -0
  74. package/dist/composables/useOutlookFallback.d.ts.map +1 -0
  75. package/dist/composables/useOutlookFallback.js +29 -0
  76. package/dist/composables/useOutlookFallback.js.map +1 -0
  77. package/dist/composables/useOutputPath.d.ts +17 -0
  78. package/dist/composables/useOutputPath.d.ts.map +1 -0
  79. package/dist/composables/useOutputPath.js +23 -0
  80. package/dist/composables/useOutputPath.js.map +1 -0
  81. package/dist/composables/{usePlaintext.d.mts → usePlaintext.d.ts} +3 -1
  82. package/dist/composables/usePlaintext.d.ts.map +1 -0
  83. package/dist/composables/{usePlaintext.mjs → usePlaintext.js} +4 -4
  84. package/dist/composables/usePlaintext.js.map +1 -0
  85. package/dist/composables/usePreheader.d.ts +25 -0
  86. package/dist/composables/usePreheader.d.ts.map +1 -0
  87. package/dist/composables/usePreheader.js +28 -0
  88. package/dist/composables/usePreheader.js.map +1 -0
  89. package/dist/composables/useTransformers.d.ts +34 -0
  90. package/dist/composables/useTransformers.d.ts.map +1 -0
  91. package/dist/composables/useTransformers.js +48 -0
  92. package/dist/composables/useTransformers.js.map +1 -0
  93. package/dist/composables/useUrlQuery.d.ts +19 -0
  94. package/dist/composables/useUrlQuery.d.ts.map +1 -0
  95. package/dist/composables/useUrlQuery.js +26 -0
  96. package/dist/composables/useUrlQuery.js.map +1 -0
  97. package/dist/config/{defaults.d.mts → defaults.d.ts} +2 -2
  98. package/dist/config/defaults.d.ts.map +1 -0
  99. package/dist/config/{defaults.mjs → defaults.js} +10 -6
  100. package/dist/config/defaults.js.map +1 -0
  101. package/dist/config/index.d.ts +33 -0
  102. package/dist/config/index.d.ts.map +1 -0
  103. package/dist/config/index.js +136 -0
  104. package/dist/config/index.js.map +1 -0
  105. package/dist/events/{index.d.mts → index.d.ts} +35 -12
  106. package/dist/events/index.d.ts.map +1 -0
  107. package/dist/events/{index.mjs → index.js} +31 -13
  108. package/dist/events/index.js.map +1 -0
  109. package/dist/index.d.ts +41 -0
  110. package/dist/index.js +40 -0
  111. package/dist/{plaintext.d.mts → plaintext.d.ts} +1 -1
  112. package/dist/plaintext.d.ts.map +1 -0
  113. package/dist/{plaintext.mjs → plaintext.js} +4 -5
  114. package/dist/plaintext.js.map +1 -0
  115. package/dist/{plugin.d.mts → plugin.d.ts} +2 -2
  116. package/dist/plugin.d.ts.map +1 -0
  117. package/dist/{plugin.mjs → plugin.js} +8 -9
  118. package/dist/plugin.js.map +1 -0
  119. package/dist/plugins/postcss/{mergeMediaQueries.d.mts → mergeMediaQueries.d.ts} +2 -2
  120. package/dist/plugins/postcss/mergeMediaQueries.d.ts.map +1 -0
  121. package/dist/plugins/postcss/{mergeMediaQueries.mjs → mergeMediaQueries.js} +2 -3
  122. package/dist/plugins/postcss/mergeMediaQueries.js.map +1 -0
  123. package/dist/plugins/postcss/{pruneVars.d.mts → pruneVars.d.ts} +1 -1
  124. package/dist/plugins/postcss/pruneVars.d.ts.map +1 -0
  125. package/dist/plugins/postcss/{pruneVars.mjs → pruneVars.js} +2 -2
  126. package/dist/plugins/postcss/pruneVars.js.map +1 -0
  127. package/dist/plugins/postcss/quoteFontFamilies.d.ts +13 -0
  128. package/dist/plugins/postcss/quoteFontFamilies.d.ts.map +1 -0
  129. package/dist/plugins/postcss/quoteFontFamilies.js +84 -0
  130. package/dist/plugins/postcss/quoteFontFamilies.js.map +1 -0
  131. package/dist/plugins/postcss/{removeDeclarations.d.mts → removeDeclarations.d.ts} +1 -1
  132. package/dist/plugins/postcss/removeDeclarations.d.ts.map +1 -0
  133. package/dist/plugins/postcss/{removeDeclarations.mjs → removeDeclarations.js} +2 -2
  134. package/dist/plugins/postcss/removeDeclarations.js.map +1 -0
  135. package/dist/plugins/postcss/resolveMaizzleImports.d.ts +16 -0
  136. package/dist/plugins/postcss/resolveMaizzleImports.d.ts.map +1 -0
  137. package/dist/plugins/postcss/resolveMaizzleImports.js +39 -0
  138. package/dist/plugins/postcss/resolveMaizzleImports.js.map +1 -0
  139. package/dist/plugins/postcss/resolveProps.d.ts +8 -0
  140. package/dist/plugins/postcss/resolveProps.d.ts.map +1 -0
  141. package/dist/plugins/postcss/resolveProps.js +155 -0
  142. package/dist/plugins/postcss/resolveProps.js.map +1 -0
  143. package/dist/plugins/postcss/{tailwindCleanup.d.mts → tailwindCleanup.d.ts} +2 -2
  144. package/dist/plugins/postcss/tailwindCleanup.d.ts.map +1 -0
  145. package/dist/plugins/postcss/{tailwindCleanup.mjs → tailwindCleanup.js} +5 -3
  146. package/dist/plugins/postcss/tailwindCleanup.js.map +1 -0
  147. package/dist/prepare.d.ts +17 -0
  148. package/dist/prepare.d.ts.map +1 -0
  149. package/dist/prepare.js +44 -0
  150. package/dist/prepare.js.map +1 -0
  151. package/dist/render/active.d.ts +8 -0
  152. package/dist/render/active.d.ts.map +1 -0
  153. package/dist/render/active.js +12 -0
  154. package/dist/render/active.js.map +1 -0
  155. package/dist/render/buildTemplate.d.ts +49 -0
  156. package/dist/render/buildTemplate.d.ts.map +1 -0
  157. package/dist/render/buildTemplate.js +141 -0
  158. package/dist/render/buildTemplate.js.map +1 -0
  159. package/dist/render/{createRenderer.d.mts → createRenderer.d.ts} +14 -6
  160. package/dist/render/createRenderer.d.ts.map +1 -0
  161. package/dist/render/createRenderer.js +468 -0
  162. package/dist/render/createRenderer.js.map +1 -0
  163. package/dist/render/index.d.ts +18 -0
  164. package/dist/render/index.d.ts.map +1 -0
  165. package/dist/render/index.js +59 -0
  166. package/dist/render/index.js.map +1 -0
  167. package/dist/render/injectFonts.d.ts +15 -0
  168. package/dist/render/injectFonts.d.ts.map +1 -0
  169. package/dist/render/injectFonts.js +45 -0
  170. package/dist/render/injectFonts.js.map +1 -0
  171. package/dist/render/parallel/buildWorker.d.ts +31 -0
  172. package/dist/render/parallel/buildWorker.d.ts.map +1 -0
  173. package/dist/render/parallel/buildWorker.js +66 -0
  174. package/dist/render/parallel/buildWorker.js.map +1 -0
  175. package/dist/render/parallel/worker.mjs +28 -0
  176. package/dist/render/plugins/codeBlockExtract.d.ts +14 -0
  177. package/dist/render/plugins/codeBlockExtract.d.ts.map +1 -0
  178. package/dist/render/plugins/codeBlockExtract.js +38 -0
  179. package/dist/render/plugins/codeBlockExtract.js.map +1 -0
  180. package/dist/render/plugins/markdownExtract.d.ts +12 -0
  181. package/dist/render/plugins/markdownExtract.d.ts.map +1 -0
  182. package/dist/render/plugins/markdownExtract.js +49 -0
  183. package/dist/render/plugins/markdownExtract.js.map +1 -0
  184. package/dist/render/plugins/rawExtract.d.ts +14 -0
  185. package/dist/render/plugins/rawExtract.d.ts.map +1 -0
  186. package/dist/render/plugins/rawExtract.js +34 -0
  187. package/dist/render/plugins/rawExtract.js.map +1 -0
  188. package/dist/render/plugins/rowSourceLocation.d.ts +18 -0
  189. package/dist/render/plugins/rowSourceLocation.d.ts.map +1 -0
  190. package/dist/render/plugins/rowSourceLocation.js +45 -0
  191. package/dist/render/plugins/rowSourceLocation.js.map +1 -0
  192. package/dist/{serve.d.mts → serve.d.ts} +5 -3
  193. package/dist/serve.d.ts.map +1 -0
  194. package/dist/{serve.mjs → serve.js} +248 -119
  195. package/dist/serve.js.map +1 -0
  196. package/dist/server/compatibility.d.ts +59 -0
  197. package/dist/server/compatibility.d.ts.map +1 -0
  198. package/dist/server/compatibility.js +959 -0
  199. package/dist/server/compatibility.js.map +1 -0
  200. package/dist/server/{email.d.mts → email.d.ts} +2 -2
  201. package/dist/server/email.d.ts.map +1 -0
  202. package/dist/server/{email.mjs → email.js} +2 -3
  203. package/dist/server/email.js.map +1 -0
  204. package/dist/server/linter.d.ts +20 -0
  205. package/dist/server/linter.d.ts.map +1 -0
  206. package/dist/server/linter.js +345 -0
  207. package/dist/server/linter.js.map +1 -0
  208. package/dist/server/sfc-utils.d.ts +21 -0
  209. package/dist/server/sfc-utils.d.ts.map +1 -0
  210. package/dist/server/sfc-utils.js +198 -0
  211. package/dist/server/sfc-utils.js.map +1 -0
  212. package/dist/server/ui/.vite/deps/@lucide_vue.js +44967 -0
  213. package/dist/server/ui/.vite/deps/@lucide_vue.js.map +1 -0
  214. package/dist/server/ui/.vite/deps/@vueuse_core.js +8155 -0
  215. package/dist/server/ui/.vite/deps/@vueuse_core.js.map +1 -0
  216. package/dist/server/ui/.vite/deps/@vueuse_shared.js +1859 -0
  217. package/dist/server/ui/.vite/deps/@vueuse_shared.js.map +1 -0
  218. package/dist/server/ui/.vite/deps/_metadata.json +78 -0
  219. package/dist/server/ui/.vite/deps/chunk-EAsCxrDo.js +14 -0
  220. package/dist/server/ui/.vite/deps/class-variance-authority.js +57 -0
  221. package/dist/server/ui/.vite/deps/class-variance-authority.js.map +1 -0
  222. package/dist/server/ui/.vite/deps/clsx.js +18 -0
  223. package/dist/server/ui/.vite/deps/clsx.js.map +1 -0
  224. package/dist/server/ui/.vite/deps/culori.js +4312 -0
  225. package/dist/server/ui/.vite/deps/culori.js.map +1 -0
  226. package/dist/server/ui/.vite/deps/package.json +3 -0
  227. package/dist/server/ui/.vite/deps/reka-ui.js +44464 -0
  228. package/dist/server/ui/.vite/deps/reka-ui.js.map +1 -0
  229. package/dist/server/ui/.vite/deps/tailwind-merge.js +3458 -0
  230. package/dist/server/ui/.vite/deps/tailwind-merge.js.map +1 -0
  231. package/dist/server/ui/.vite/deps/vue-router.js +6383 -0
  232. package/dist/server/ui/.vite/deps/vue-router.js.map +1 -0
  233. package/dist/server/ui/.vite/deps/vue.js +2 -0
  234. package/dist/server/ui/.vite/deps/vue.runtime.esm-bundler-N1X0OxKs.js +8800 -0
  235. package/dist/server/ui/.vite/deps/vue.runtime.esm-bundler-N1X0OxKs.js.map +1 -0
  236. package/dist/server/ui/App.vue +106 -66
  237. package/dist/server/ui/components/SidebarClose.vue +12 -0
  238. package/dist/server/ui/components/ui/checkbox/Checkbox.vue +1 -1
  239. package/dist/server/ui/components/ui/command/Command.vue +5 -1
  240. package/dist/server/ui/components/ui/command/CommandInput.vue +2 -2
  241. package/dist/server/ui/components/ui/dialog/DialogContent.vue +1 -1
  242. package/dist/server/ui/components/ui/dialog/DialogScrollContent.vue +1 -1
  243. package/dist/server/ui/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue +1 -1
  244. package/dist/server/ui/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +1 -1
  245. package/dist/server/ui/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue +1 -1
  246. package/dist/server/ui/components/ui/input/Input.vue +1 -1
  247. package/dist/server/ui/components/ui/sheet/SheetContent.vue +1 -1
  248. package/dist/server/ui/components/ui/sidebar/SidebarTrigger.vue +2 -2
  249. package/dist/server/ui/components/ui/tags-input/TagsInputInput.vue +1 -1
  250. package/dist/server/ui/components/ui/tags-input/TagsInputItemDelete.vue +1 -1
  251. package/dist/server/ui/lib/emulated-dark-mode.ts +146 -0
  252. package/dist/server/ui/main.css +25 -0
  253. package/dist/server/ui/pages/Home.vue +1 -1
  254. package/dist/server/ui/pages/Preview.vue +377 -186
  255. package/dist/server/ui/vite-env.d.ts +1 -0
  256. package/dist/tests/render/_helpers.d.ts +6 -0
  257. package/dist/tests/render/_helpers.d.ts.map +1 -0
  258. package/dist/tests/render/_helpers.js +16 -0
  259. package/dist/tests/render/_helpers.js.map +1 -0
  260. package/dist/transformers/addAttributes.d.ts +42 -0
  261. package/dist/transformers/addAttributes.d.ts.map +1 -0
  262. package/dist/transformers/{addAttributes.mjs → addAttributes.js} +40 -24
  263. package/dist/transformers/addAttributes.js.map +1 -0
  264. package/dist/transformers/attributeToStyle.d.ts +38 -0
  265. package/dist/transformers/attributeToStyle.d.ts.map +1 -0
  266. package/dist/transformers/attributeToStyle.js +94 -0
  267. package/dist/transformers/attributeToStyle.js.map +1 -0
  268. package/dist/transformers/base.d.ts +71 -0
  269. package/dist/transformers/base.d.ts.map +1 -0
  270. package/dist/transformers/{base.mjs → base.js} +65 -40
  271. package/dist/transformers/base.js.map +1 -0
  272. package/dist/transformers/columnWidth.d.ts +31 -0
  273. package/dist/transformers/columnWidth.d.ts.map +1 -0
  274. package/dist/transformers/columnWidth.js +527 -0
  275. package/dist/transformers/columnWidth.js.map +1 -0
  276. package/dist/transformers/entities.d.ts +37 -0
  277. package/dist/transformers/entities.d.ts.map +1 -0
  278. package/dist/transformers/entities.js +74 -0
  279. package/dist/transformers/entities.js.map +1 -0
  280. package/dist/transformers/filters/{defaults.d.mts → defaults.d.ts} +1 -1
  281. package/dist/transformers/filters/defaults.d.ts.map +1 -0
  282. package/dist/transformers/filters/{defaults.mjs → defaults.js} +2 -2
  283. package/dist/transformers/filters/defaults.js.map +1 -0
  284. package/dist/transformers/filters/index.d.ts +43 -0
  285. package/dist/transformers/filters/index.d.ts.map +1 -0
  286. package/dist/transformers/filters/index.js +89 -0
  287. package/dist/transformers/filters/index.js.map +1 -0
  288. package/dist/transformers/format.d.ts +22 -0
  289. package/dist/transformers/format.d.ts.map +1 -0
  290. package/dist/transformers/format.js +30 -0
  291. package/dist/transformers/format.js.map +1 -0
  292. package/dist/transformers/imgWidth.d.ts +20 -0
  293. package/dist/transformers/imgWidth.d.ts.map +1 -0
  294. package/dist/transformers/imgWidth.js +76 -0
  295. package/dist/transformers/imgWidth.js.map +1 -0
  296. package/dist/transformers/{index.d.mts → index.d.ts} +4 -3
  297. package/dist/transformers/index.d.ts.map +1 -0
  298. package/dist/transformers/index.js +163 -0
  299. package/dist/transformers/index.js.map +1 -0
  300. package/dist/transformers/inlineCss.d.ts +85 -0
  301. package/dist/transformers/inlineCss.d.ts.map +1 -0
  302. package/dist/transformers/inlineCss.js +112 -0
  303. package/dist/transformers/inlineCss.js.map +1 -0
  304. package/dist/transformers/inlineLink.d.ts +35 -0
  305. package/dist/transformers/inlineLink.d.ts.map +1 -0
  306. package/dist/transformers/{inlineLink.mjs → inlineLink.js} +35 -11
  307. package/dist/transformers/inlineLink.js.map +1 -0
  308. package/dist/transformers/minify.d.ts +21 -0
  309. package/dist/transformers/minify.d.ts.map +1 -0
  310. package/dist/transformers/minify.js +25 -0
  311. package/dist/transformers/minify.js.map +1 -0
  312. package/dist/transformers/minifyCodeInline.d.ts +29 -0
  313. package/dist/transformers/minifyCodeInline.d.ts.map +1 -0
  314. package/dist/transformers/minifyCodeInline.js +36 -0
  315. package/dist/transformers/minifyCodeInline.js.map +1 -0
  316. package/dist/transformers/msoPlaceholders.d.ts +33 -0
  317. package/dist/transformers/msoPlaceholders.d.ts.map +1 -0
  318. package/dist/transformers/msoPlaceholders.js +114 -0
  319. package/dist/transformers/msoPlaceholders.js.map +1 -0
  320. package/dist/transformers/purgeCss.d.ts +43 -0
  321. package/dist/transformers/purgeCss.d.ts.map +1 -0
  322. package/dist/transformers/purgeCss.js +207 -0
  323. package/dist/transformers/purgeCss.js.map +1 -0
  324. package/dist/transformers/removeAttributes.d.ts +54 -0
  325. package/dist/transformers/removeAttributes.d.ts.map +1 -0
  326. package/dist/transformers/removeAttributes.js +72 -0
  327. package/dist/transformers/removeAttributes.js.map +1 -0
  328. package/dist/transformers/{replaceStrings.d.mts → replaceStrings.d.ts} +2 -2
  329. package/dist/transformers/replaceStrings.d.ts.map +1 -0
  330. package/dist/transformers/{replaceStrings.mjs → replaceStrings.js} +2 -2
  331. package/dist/transformers/replaceStrings.js.map +1 -0
  332. package/dist/transformers/safeSelectors.d.ts +37 -0
  333. package/dist/transformers/safeSelectors.d.ts.map +1 -0
  334. package/dist/transformers/{safeClassNames.mjs → safeSelectors.js} +40 -10
  335. package/dist/transformers/safeSelectors.js.map +1 -0
  336. package/dist/transformers/shorthandCss.d.ts +47 -0
  337. package/dist/transformers/shorthandCss.d.ts.map +1 -0
  338. package/dist/transformers/shorthandCss.js +92 -0
  339. package/dist/transformers/shorthandCss.js.map +1 -0
  340. package/dist/transformers/sixHex.d.ts +25 -0
  341. package/dist/transformers/sixHex.d.ts.map +1 -0
  342. package/dist/transformers/sixHex.js +42 -0
  343. package/dist/transformers/sixHex.js.map +1 -0
  344. package/dist/transformers/tailwindComponent.d.ts +16 -0
  345. package/dist/transformers/tailwindComponent.d.ts.map +1 -0
  346. package/dist/transformers/tailwindComponent.js +101 -0
  347. package/dist/transformers/tailwindComponent.js.map +1 -0
  348. package/dist/transformers/{tailwindcss.d.mts → tailwindcss.d.ts} +2 -2
  349. package/dist/transformers/tailwindcss.d.ts.map +1 -0
  350. package/dist/transformers/{tailwindcss.mjs → tailwindcss.js} +33 -74
  351. package/dist/transformers/tailwindcss.js.map +1 -0
  352. package/dist/transformers/urlQuery.d.ts +36 -0
  353. package/dist/transformers/urlQuery.d.ts.map +1 -0
  354. package/dist/transformers/urlQuery.js +77 -0
  355. package/dist/transformers/urlQuery.js.map +1 -0
  356. package/dist/types/{config.d.mts → config.d.ts} +231 -46
  357. package/dist/types/config.d.ts.map +1 -0
  358. package/dist/types/config.js +1 -0
  359. package/dist/types/index.d.ts +2 -0
  360. package/dist/types/index.js +1 -0
  361. package/dist/utils/ast/index.d.ts +4 -0
  362. package/dist/utils/ast/index.js +4 -0
  363. package/dist/utils/ast/{parser.d.mts → parser.d.ts} +1 -1
  364. package/dist/utils/ast/parser.d.ts.map +1 -0
  365. package/dist/utils/ast/{parser.mjs → parser.js} +2 -3
  366. package/dist/utils/ast/parser.js.map +1 -0
  367. package/dist/utils/ast/{serializer.d.mts → serializer.d.ts} +1 -1
  368. package/dist/utils/ast/serializer.d.ts.map +1 -0
  369. package/dist/utils/ast/serializer.js +46 -0
  370. package/dist/utils/ast/serializer.js.map +1 -0
  371. package/dist/utils/ast/{walker.d.mts → walker.d.ts} +1 -1
  372. package/dist/utils/ast/walker.d.ts.map +1 -0
  373. package/dist/utils/ast/{walker.mjs → walker.js} +2 -2
  374. package/dist/utils/ast/walker.js.map +1 -0
  375. package/dist/utils/cloneConfig.d.ts +13 -0
  376. package/dist/utils/cloneConfig.d.ts.map +1 -0
  377. package/dist/utils/cloneConfig.js +21 -0
  378. package/dist/utils/cloneConfig.js.map +1 -0
  379. package/dist/utils/compileTailwindCss.d.ts +16 -0
  380. package/dist/utils/compileTailwindCss.d.ts.map +1 -0
  381. package/dist/utils/compileTailwindCss.js +55 -0
  382. package/dist/utils/compileTailwindCss.js.map +1 -0
  383. package/dist/utils/componentSources.d.ts +50 -0
  384. package/dist/utils/componentSources.d.ts.map +1 -0
  385. package/dist/utils/componentSources.js +50 -0
  386. package/dist/utils/componentSources.js.map +1 -0
  387. package/dist/utils/cssBox.d.ts +42 -0
  388. package/dist/utils/cssBox.d.ts.map +1 -0
  389. package/dist/utils/cssBox.js +151 -0
  390. package/dist/utils/cssBox.js.map +1 -0
  391. package/dist/utils/decodeStyleEntities.d.ts +15 -0
  392. package/dist/utils/decodeStyleEntities.d.ts.map +1 -0
  393. package/dist/utils/decodeStyleEntities.js +18 -0
  394. package/dist/utils/decodeStyleEntities.js.map +1 -0
  395. package/dist/utils/{detect.d.mts → detect.d.ts} +1 -1
  396. package/dist/utils/detect.d.ts.map +1 -0
  397. package/dist/utils/{detect.mjs → detect.js} +2 -3
  398. package/dist/utils/detect.js.map +1 -0
  399. package/dist/utils/output-markers.d.ts +29 -0
  400. package/dist/utils/output-markers.d.ts.map +1 -0
  401. package/dist/utils/output-markers.js +68 -0
  402. package/dist/utils/output-markers.js.map +1 -0
  403. package/dist/utils/{url.d.mts → url.d.ts} +1 -1
  404. package/dist/utils/url.d.ts.map +1 -0
  405. package/dist/utils/{url.mjs → url.js} +2 -3
  406. package/dist/utils/url.js.map +1 -0
  407. package/dist/utils/watchPaths.d.ts +11 -0
  408. package/dist/utils/watchPaths.d.ts.map +1 -0
  409. package/dist/utils/watchPaths.js +19 -0
  410. package/dist/utils/watchPaths.js.map +1 -0
  411. package/node_modules/@clack/core/CHANGELOG.md +44 -0
  412. package/node_modules/@clack/core/dist/index.d.mts +125 -5
  413. package/node_modules/@clack/core/dist/index.mjs +972 -11
  414. package/node_modules/@clack/core/package.json +6 -2
  415. package/node_modules/@clack/prompts/CHANGELOG.md +70 -0
  416. package/node_modules/@clack/prompts/README.md +129 -3
  417. package/node_modules/@clack/prompts/dist/index.d.mts +567 -33
  418. package/node_modules/@clack/prompts/dist/index.mjs +1378 -133
  419. package/node_modules/@clack/prompts/package.json +7 -4
  420. package/node_modules/fast-string-truncated-width/dist/index.js +36 -96
  421. package/node_modules/fast-string-truncated-width/dist/types.d.ts +0 -3
  422. package/node_modules/fast-string-truncated-width/dist/utils.d.ts +3 -3
  423. package/node_modules/fast-string-truncated-width/dist/utils.js +14 -9
  424. package/node_modules/fast-string-truncated-width/package.json +1 -1
  425. package/node_modules/fast-string-truncated-width/readme.md +2 -3
  426. package/node_modules/fast-string-width/package.json +2 -2
  427. package/node_modules/fast-string-width/readme.md +0 -3
  428. package/node_modules/fast-wrap-ansi/lib/main.js +4 -2
  429. package/node_modules/fast-wrap-ansi/package.json +11 -11
  430. package/node_modules/maizzle/README.md +24 -0
  431. package/node_modules/maizzle/dist/commands/make/component.mjs +1 -1
  432. package/node_modules/maizzle/dist/commands/make/config.mjs +8 -7
  433. package/node_modules/maizzle/dist/commands/make/layout.mjs +3 -3
  434. package/node_modules/maizzle/dist/commands/make/scaffold.mjs +1 -1
  435. package/node_modules/maizzle/dist/commands/make/stubs/Layout.vue +146 -0
  436. package/node_modules/maizzle/dist/commands/make/stubs/component.vue +2 -4
  437. package/node_modules/maizzle/dist/commands/make/stubs/config.ts +1 -5
  438. package/node_modules/maizzle/dist/commands/make/template.mjs +1 -1
  439. package/node_modules/maizzle/dist/commands/new.mjs +46 -135
  440. package/node_modules/maizzle/dist/index.d.mts +1 -0
  441. package/node_modules/maizzle/dist/index.mjs +30 -7
  442. package/node_modules/maizzle/package.json +5 -4
  443. package/node_modules/nypm/dist/cli.mjs +28 -5
  444. package/node_modules/nypm/dist/index.d.mts +0 -8
  445. package/node_modules/nypm/dist/index.mjs +27 -4
  446. package/node_modules/nypm/package.json +12 -12
  447. package/node_modules/tinyexec/README.md +9 -1
  448. package/node_modules/tinyexec/dist/main.d.mts +22 -7
  449. package/node_modules/tinyexec/dist/main.mjs +189 -491
  450. package/node_modules/tinyexec/package.json +14 -16
  451. package/package.json +38 -31
  452. package/dist/_virtual/_rolldown/runtime.mjs +0 -32
  453. package/dist/build.d.mts +0 -19
  454. package/dist/build.d.mts.map +0 -1
  455. package/dist/build.mjs +0 -141
  456. package/dist/build.mjs.map +0 -1
  457. package/dist/components/Divider.vue +0 -133
  458. package/dist/components/Image.vue +0 -70
  459. package/dist/components/Overlap.vue +0 -80
  460. package/dist/components/utils.d.mts +0 -5
  461. package/dist/components/utils.d.mts.map +0 -1
  462. package/dist/components/utils.mjs +0 -9
  463. package/dist/components/utils.mjs.map +0 -1
  464. package/dist/composables/defineConfig.d.mts +0 -14
  465. package/dist/composables/defineConfig.d.mts.map +0 -1
  466. package/dist/composables/defineConfig.mjs.map +0 -1
  467. package/dist/composables/renderContext.d.mts +0 -24
  468. package/dist/composables/renderContext.d.mts.map +0 -1
  469. package/dist/composables/renderContext.mjs +0 -6
  470. package/dist/composables/renderContext.mjs.map +0 -1
  471. package/dist/composables/useConfig.d.mts +0 -9
  472. package/dist/composables/useConfig.d.mts.map +0 -1
  473. package/dist/composables/useConfig.mjs +0 -13
  474. package/dist/composables/useConfig.mjs.map +0 -1
  475. package/dist/composables/useDoctype.d.mts.map +0 -1
  476. package/dist/composables/useDoctype.mjs.map +0 -1
  477. package/dist/composables/useEvent.d.mts.map +0 -1
  478. package/dist/composables/useEvent.mjs.map +0 -1
  479. package/dist/composables/usePlaintext.d.mts.map +0 -1
  480. package/dist/composables/usePlaintext.mjs.map +0 -1
  481. package/dist/composables/usePreviewText.d.mts +0 -24
  482. package/dist/composables/usePreviewText.d.mts.map +0 -1
  483. package/dist/composables/usePreviewText.mjs +0 -29
  484. package/dist/composables/usePreviewText.mjs.map +0 -1
  485. package/dist/config/defaults.d.mts.map +0 -1
  486. package/dist/config/defaults.mjs.map +0 -1
  487. package/dist/config/index.d.mts +0 -15
  488. package/dist/config/index.d.mts.map +0 -1
  489. package/dist/config/index.mjs +0 -71
  490. package/dist/config/index.mjs.map +0 -1
  491. package/dist/events/index.d.mts.map +0 -1
  492. package/dist/events/index.mjs.map +0 -1
  493. package/dist/index.d.mts +0 -31
  494. package/dist/index.mjs +0 -31
  495. package/dist/node_modules/picomatch/index.mjs +0 -13
  496. package/dist/node_modules/picomatch/index.mjs.map +0 -1
  497. package/dist/node_modules/picomatch/lib/constants.mjs +0 -174
  498. package/dist/node_modules/picomatch/lib/constants.mjs.map +0 -1
  499. package/dist/node_modules/picomatch/lib/parse.mjs +0 -1067
  500. package/dist/node_modules/picomatch/lib/parse.mjs.map +0 -1
  501. package/dist/node_modules/picomatch/lib/picomatch.mjs +0 -304
  502. package/dist/node_modules/picomatch/lib/picomatch.mjs.map +0 -1
  503. package/dist/node_modules/picomatch/lib/scan.mjs +0 -296
  504. package/dist/node_modules/picomatch/lib/scan.mjs.map +0 -1
  505. package/dist/node_modules/picomatch/lib/utils.mjs +0 -53
  506. package/dist/node_modules/picomatch/lib/utils.mjs.map +0 -1
  507. package/dist/plaintext.d.mts.map +0 -1
  508. package/dist/plaintext.mjs.map +0 -1
  509. package/dist/plugin.d.mts.map +0 -1
  510. package/dist/plugin.mjs.map +0 -1
  511. package/dist/plugins/postcss/mergeMediaQueries.d.mts.map +0 -1
  512. package/dist/plugins/postcss/mergeMediaQueries.mjs.map +0 -1
  513. package/dist/plugins/postcss/pruneVars.d.mts.map +0 -1
  514. package/dist/plugins/postcss/pruneVars.mjs.map +0 -1
  515. package/dist/plugins/postcss/removeDeclarations.d.mts.map +0 -1
  516. package/dist/plugins/postcss/removeDeclarations.mjs.map +0 -1
  517. package/dist/plugins/postcss/tailwindCleanup.d.mts.map +0 -1
  518. package/dist/plugins/postcss/tailwindCleanup.mjs.map +0 -1
  519. package/dist/render/createRenderer.d.mts.map +0 -1
  520. package/dist/render/createRenderer.mjs +0 -286
  521. package/dist/render/createRenderer.mjs.map +0 -1
  522. package/dist/render/index.d.mts +0 -26
  523. package/dist/render/index.d.mts.map +0 -1
  524. package/dist/render/index.mjs +0 -46
  525. package/dist/render/index.mjs.map +0 -1
  526. package/dist/serve.d.mts.map +0 -1
  527. package/dist/serve.mjs.map +0 -1
  528. package/dist/server/compatibility.d.mts +0 -5
  529. package/dist/server/compatibility.d.mts.map +0 -1
  530. package/dist/server/compatibility.mjs +0 -97
  531. package/dist/server/compatibility.mjs.map +0 -1
  532. package/dist/server/email.d.mts.map +0 -1
  533. package/dist/server/email.mjs.map +0 -1
  534. package/dist/server/linter.d.mts +0 -5
  535. package/dist/server/linter.d.mts.map +0 -1
  536. package/dist/server/linter.mjs +0 -189
  537. package/dist/server/linter.mjs.map +0 -1
  538. package/dist/transformers/addAttributes.d.mts +0 -32
  539. package/dist/transformers/addAttributes.d.mts.map +0 -1
  540. package/dist/transformers/addAttributes.mjs.map +0 -1
  541. package/dist/transformers/attributeToStyle.d.mts +0 -25
  542. package/dist/transformers/attributeToStyle.d.mts.map +0 -1
  543. package/dist/transformers/attributeToStyle.mjs +0 -80
  544. package/dist/transformers/attributeToStyle.mjs.map +0 -1
  545. package/dist/transformers/base.d.mts +0 -8
  546. package/dist/transformers/base.d.mts.map +0 -1
  547. package/dist/transformers/base.mjs.map +0 -1
  548. package/dist/transformers/entities.d.mts +0 -8
  549. package/dist/transformers/entities.d.mts.map +0 -1
  550. package/dist/transformers/entities.mjs +0 -41
  551. package/dist/transformers/entities.mjs.map +0 -1
  552. package/dist/transformers/filters/defaults.d.mts.map +0 -1
  553. package/dist/transformers/filters/defaults.mjs.map +0 -1
  554. package/dist/transformers/filters/index.d.mts +0 -22
  555. package/dist/transformers/filters/index.d.mts.map +0 -1
  556. package/dist/transformers/filters/index.mjs +0 -67
  557. package/dist/transformers/filters/index.mjs.map +0 -1
  558. package/dist/transformers/format.d.mts +0 -15
  559. package/dist/transformers/format.d.mts.map +0 -1
  560. package/dist/transformers/format.mjs +0 -26
  561. package/dist/transformers/format.mjs.map +0 -1
  562. package/dist/transformers/index.d.mts.map +0 -1
  563. package/dist/transformers/index.mjs +0 -81
  564. package/dist/transformers/index.mjs.map +0 -1
  565. package/dist/transformers/inlineCSS.d.mts +0 -17
  566. package/dist/transformers/inlineCSS.d.mts.map +0 -1
  567. package/dist/transformers/inlineCSS.mjs +0 -70
  568. package/dist/transformers/inlineCSS.mjs.map +0 -1
  569. package/dist/transformers/inlineLink.d.mts +0 -14
  570. package/dist/transformers/inlineLink.d.mts.map +0 -1
  571. package/dist/transformers/inlineLink.mjs.map +0 -1
  572. package/dist/transformers/minify.d.mts +0 -17
  573. package/dist/transformers/minify.d.mts.map +0 -1
  574. package/dist/transformers/minify.mjs +0 -24
  575. package/dist/transformers/minify.mjs.map +0 -1
  576. package/dist/transformers/purgeCSS.d.mts +0 -23
  577. package/dist/transformers/purgeCSS.d.mts.map +0 -1
  578. package/dist/transformers/purgeCSS.mjs +0 -132
  579. package/dist/transformers/purgeCSS.mjs.map +0 -1
  580. package/dist/transformers/removeAttributes.d.mts +0 -31
  581. package/dist/transformers/removeAttributes.d.mts.map +0 -1
  582. package/dist/transformers/removeAttributes.mjs +0 -63
  583. package/dist/transformers/removeAttributes.mjs.map +0 -1
  584. package/dist/transformers/replaceStrings.d.mts.map +0 -1
  585. package/dist/transformers/replaceStrings.mjs.map +0 -1
  586. package/dist/transformers/safeClassNames.d.mts +0 -22
  587. package/dist/transformers/safeClassNames.d.mts.map +0 -1
  588. package/dist/transformers/safeClassNames.mjs.map +0 -1
  589. package/dist/transformers/shorthandCSS.d.mts +0 -24
  590. package/dist/transformers/shorthandCSS.d.mts.map +0 -1
  591. package/dist/transformers/shorthandCSS.mjs +0 -48
  592. package/dist/transformers/shorthandCSS.mjs.map +0 -1
  593. package/dist/transformers/sixHex.d.mts +0 -16
  594. package/dist/transformers/sixHex.d.mts.map +0 -1
  595. package/dist/transformers/sixHex.mjs +0 -30
  596. package/dist/transformers/sixHex.mjs.map +0 -1
  597. package/dist/transformers/tailwindcss.d.mts.map +0 -1
  598. package/dist/transformers/tailwindcss.mjs.map +0 -1
  599. package/dist/transformers/urlQuery.d.mts +0 -24
  600. package/dist/transformers/urlQuery.d.mts.map +0 -1
  601. package/dist/transformers/urlQuery.mjs +0 -65
  602. package/dist/transformers/urlQuery.mjs.map +0 -1
  603. package/dist/types/config.d.mts.map +0 -1
  604. package/dist/types/config.mjs +0 -1
  605. package/dist/types/index.d.mts +0 -2
  606. package/dist/types/index.mjs +0 -1
  607. package/dist/utils/ast/index.d.mts +0 -4
  608. package/dist/utils/ast/index.mjs +0 -5
  609. package/dist/utils/ast/parser.d.mts.map +0 -1
  610. package/dist/utils/ast/parser.mjs.map +0 -1
  611. package/dist/utils/ast/serializer.d.mts.map +0 -1
  612. package/dist/utils/ast/serializer.mjs +0 -37
  613. package/dist/utils/ast/serializer.mjs.map +0 -1
  614. package/dist/utils/ast/walker.d.mts.map +0 -1
  615. package/dist/utils/ast/walker.mjs.map +0 -1
  616. package/dist/utils/detect.d.mts.map +0 -1
  617. package/dist/utils/detect.mjs.map +0 -1
  618. package/dist/utils/url.d.mts.map +0 -1
  619. package/dist/utils/url.mjs.map +0 -1
  620. package/node_modules/@clack/core/dist/index.mjs.map +0 -1
  621. package/node_modules/@clack/prompts/dist/index.mjs.map +0 -1
  622. package/node_modules/fast-wrap-ansi/lib/main.js.map +0 -1
  623. package/node_modules/maizzle/dist/commands/make/stubs/layout.vue +0 -39
  624. package/node_modules/tinyexec/dist/LICENSES.txt +0 -83
@@ -1,7 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import { ref, computed, watch, nextTick, onMounted, onUnmounted } from 'vue'
3
3
  import { useRoute } from 'vue-router'
4
- import { ChevronUp, ChevronDown, Check, Info } from 'lucide-vue-next'
4
+ import { ChevronUp, ChevronDown, Check } from '@lucide/vue'
5
5
  import {
6
6
  DropdownMenu,
7
7
  DropdownMenuContent,
@@ -20,9 +20,9 @@ import {
20
20
  TagsInputItemDelete,
21
21
  TagsInputItemText,
22
22
  } from '@/components/ui/tags-input'
23
- import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
24
23
 
25
24
  import stripesUrl from '../stripes.svg'
25
+ import { applyColorInversion, undoColorInversion } from '@/lib/emulated-dark-mode'
26
26
 
27
27
  interface Device {
28
28
  name: string
@@ -43,6 +43,7 @@ const props = defineProps<{
43
43
  }>()
44
44
 
45
45
  const viewMode = defineModel<'preview' | 'source'>('viewMode', { default: 'preview' })
46
+ const darkMode = defineModel<boolean>('darkMode', { default: false })
46
47
 
47
48
  const route = useRoute()
48
49
  const srcdoc = ref('')
@@ -52,26 +53,61 @@ const plaintextContent = ref('')
52
53
  const sourceView = ref<'compiled' | 'vue' | 'plaintext'>('compiled')
53
54
  const copied = ref(false)
54
55
 
56
+ /**
57
+ * Source views (Compiled HTML, Vue source, Plaintext) refresh lazily. On a
58
+ * save we only refetch the view currently on screen and bump
59
+ * `sourcesGeneration` to mark the others stale. Switching to a stale view
60
+ * keeps its previous content visible while a background refetch replaces it
61
+ * in place — so panels never flash empty. Each fetch stamps the generation
62
+ * it was initiated at so a save mid-fetch correctly leaves the view stale.
63
+ */
64
+ const sourcesGeneration = ref(0)
65
+ const compiledGen = ref(-1)
66
+ const vueGen = ref(-1)
67
+ const plaintextGen = ref(-1)
68
+
69
+ function refreshSourceView(view: 'compiled' | 'vue' | 'plaintext') {
70
+ if (view === 'compiled' && (!sourceHtml.value || compiledGen.value < sourcesGeneration.value)) fetchSource()
71
+ if (view === 'vue' && (!vueSourceHtml.value || vueGen.value < sourcesGeneration.value)) fetchVueSource()
72
+ if (view === 'plaintext' && (!plaintextContent.value || plaintextGen.value < sourcesGeneration.value)) fetchPlaintext()
73
+ }
74
+
55
75
  const iframeEl = ref<HTMLIFrameElement>()
56
76
  const compiledSourceEl = ref<HTMLElement>()
57
77
  const vueSourceEl = ref<HTMLElement>()
78
+ const plaintextEl = ref<HTMLElement>()
58
79
  const containerEl = ref<HTMLElement>()
59
80
  const wrapperEl = ref<HTMLElement>()
60
81
 
61
82
  const panelWidth = defineModel<number>('panelWidth', { default: 0 })
62
83
  const panelHeight = defineModel<number>('panelHeight', { default: 0 })
84
+ /**
85
+ * Container's available area, exposed to the toolbar so size inputs can
86
+ * clamp typed values without paying a layout-recalc cost on
87
+ * every drag tick. Kept in sync via a ResizeObserver.
88
+ */
89
+ const maxIframeWidth = defineModel<number>('maxIframeWidth', { default: 0 })
90
+ const maxIframeHeight = defineModel<number>('maxIframeHeight', { default: 0 })
63
91
  const isDragging = defineModel<boolean>('isDragging', { default: false })
64
92
  const isFullSize = defineModel<boolean>('isFullSize', { default: true })
65
93
 
66
- // Custom resizable: width/height of the iframe wrapper (null = fill container)
67
- const iframeWidth = ref<number | null>(null)
68
- const iframeHeight = ref<number | null>(null)
94
+ /**
95
+ * Custom resizable: width/height of the iframe wrapper (null = fill the
96
+ * container). Exposed as v-models so the toolbar's size indicator
97
+ * can drive these too, alongside the drag handles.
98
+ */
99
+ const iframeWidth = defineModel<number | null>('iframeWidth', { default: null })
100
+ const iframeHeight = defineModel<number | null>('iframeHeight', { default: null })
69
101
  const iframeContentHeight = ref<number | null>(null)
70
102
 
71
103
  function copySource() {
72
104
  let text: string
73
105
  if (sourceView.value === 'compiled') {
74
- text = srcdoc.value
106
+ /**
107
+ * `renderedHtml` holds the raw compiled HTML (srcdoc is only populated
108
+ * for the initial iframe load; subsequent renders use doc.write).
109
+ */
110
+ text = renderedHtml || srcdoc.value
75
111
  } else if (sourceView.value === 'plaintext') {
76
112
  text = plaintextContent.value
77
113
  } else {
@@ -80,28 +116,59 @@ function copySource() {
80
116
  text = el.textContent || ''
81
117
  }
82
118
 
83
- const blob = new Blob([text], { type: 'text/plain' })
84
- const item = new ClipboardItem({ 'text/plain': blob })
85
- navigator.clipboard.write([item]).then(() => {
119
+ navigator.clipboard.writeText(text).then(() => {
86
120
  copied.value = true
87
121
  setTimeout(() => { copied.value = false }, 2000)
122
+ }).catch((err) => {
123
+ console.error('Copy failed:', err)
88
124
  })
89
125
  }
90
126
 
91
- interface CompatibilityIssue {
92
- type: 'error' | 'warning'
127
+ interface CheckIssue {
128
+ kind: 'compat' | 'lint'
129
+ slug?: string
93
130
  title: string
94
- category: string
95
- clients: Array<{ name: string, notes: string[] }>
96
131
  url?: string
132
+ category: string
97
133
  line?: number
134
+ file: string
135
+ // compat-only
136
+ supportLevel?: 'unsupported' | 'mitigated' | 'unknown'
137
+ supportLabel?: string
138
+ affectedClients?: string[]
139
+ // lint-only
140
+ severity?: 'error' | 'warning'
141
+ message?: string
98
142
  }
99
143
 
100
- interface LintIssue {
101
- type: 'error' | 'warning'
102
- title: string
103
- message: string
104
- line?: number
144
+ function supportPrefix(issue: CheckIssue): string {
145
+ if (issue.supportLevel === 'unsupported') return 'Not supported in'
146
+ if (issue.supportLevel === 'mitigated') return 'Partial support in'
147
+ return 'Support unknown in'
148
+ }
149
+
150
+ /**
151
+ * Split a message on backtick-delimited code spans. Returns alternating
152
+ * { text } and { code } segments so the template can render <code> inline
153
+ * without needing v-html.
154
+ */
155
+ function messageSegments(raw: string | undefined): Array<{ code: boolean, text: string }> {
156
+ if (!raw) return []
157
+ const out: Array<{ code: boolean, text: string }> = []
158
+ const parts = raw.split('`')
159
+ for (let i = 0; i < parts.length; i++) {
160
+ if (parts[i]) out.push({ code: i % 2 === 1, text: parts[i] })
161
+ }
162
+ return out
163
+ }
164
+
165
+ function issueColorClass(issue: CheckIssue): string {
166
+ if (issue.kind === 'lint') {
167
+ return issue.severity === 'error' ? 'text-rose-600' : 'text-amber-600'
168
+ }
169
+ if (issue.supportLevel === 'unsupported') return 'text-rose-600'
170
+ if (issue.supportLevel === 'mitigated') return 'text-amber-600'
171
+ return 'text-gray-500 dark:text-gray-400'
105
172
  }
106
173
 
107
174
  interface TemplateStats {
@@ -110,10 +177,18 @@ interface TemplateStats {
110
177
  links: number
111
178
  }
112
179
 
113
- const compatibilityIssues = ref<CompatibilityIssue[]>([])
180
+ const compatibilityIssues = ref<CheckIssue[]>([])
114
181
  const compatibilityLoading = ref(false)
115
182
  const compatibilityError = ref('')
116
183
  const compatibilityCategory = ref('')
184
+ /**
185
+ * Injected by serveDevUI into index.html — synchronous, available before
186
+ * any HTTP calls, so the Checks tab never flashes in when disabled.
187
+ */
188
+ const checksConfig = (window as any).__MAIZZLE_CONFIG__?.checks
189
+ const compatibilityDisabled = ref(checksConfig === false)
190
+ const expandedIssueKeys = ref(new Set<string>())
191
+ const issueKey = (issue: CheckIssue, i: number): string => `${issue.file}|${issue.line ?? 0}|${issue.slug ?? issue.title}|${i}`
117
192
  const compatibilityCategories = ['css', 'html', 'image', 'others'] as const
118
193
  const activeCompatibilityCategories = computed(() =>
119
194
  compatibilityCategories.filter(cat => compatibilityIssues.value.some(i => i.category === cat))
@@ -122,8 +197,6 @@ const filteredCompatibilityIssues = computed(() => {
122
197
  if (!compatibilityCategory.value) return compatibilityIssues.value
123
198
  return compatibilityIssues.value.filter(i => i.category === compatibilityCategory.value)
124
199
  })
125
- const lintIssues = ref<LintIssue[]>([])
126
- const lintLoading = ref(false)
127
200
  const stats = ref<TemplateStats | null>(null)
128
201
  const statsLoading = ref(false)
129
202
 
@@ -187,7 +260,11 @@ function updateIframeContentHeight() {
187
260
 
188
261
  // Temporarily collapse to measure true content height
189
262
  iframe.style.height = '0'
190
- iframeContentHeight.value = doc.documentElement.scrollHeight
263
+ const contentHeight = doc.documentElement.scrollHeight
264
+ // Fill the preview viewport when the email is shorter than it; grow past it
265
+ // (and let the ScrollArea scroll) when the email is taller.
266
+ const availableHeight = viewport?.clientHeight ?? 0
267
+ iframeContentHeight.value = Math.max(contentHeight, availableHeight)
191
268
  iframe.style.height = `${iframeContentHeight.value}px`
192
269
 
193
270
  // Restore scroll position
@@ -196,6 +273,19 @@ function updateIframeContentHeight() {
196
273
  }
197
274
  }
198
275
 
276
+ function onIframeLoad() {
277
+ updateIframeContentHeight()
278
+ const iframe = iframeEl.value
279
+ if (darkMode.value && iframe) applyColorInversion(iframe)
280
+ }
281
+
282
+ watch(darkMode, (on) => {
283
+ const iframe = iframeEl.value
284
+ if (!iframe) return
285
+ if (on) applyColorInversion(iframe)
286
+ else undoColorInversion(iframe)
287
+ })
288
+
199
289
  async function fetchTemplate() {
200
290
  const res = await fetch(`/__maizzle/render/${route.params.template}`)
201
291
  renderedHtml = await res.text()
@@ -203,14 +293,17 @@ async function fetchTemplate() {
203
293
  const iframe = iframeEl.value
204
294
  const doc = iframe?.contentDocument
205
295
 
206
- // Write directly into the iframe document to avoid a full reload,
207
- // which preserves scroll position natively.
296
+ /**
297
+ * Write directly into the iframe document to avoid a full reload,
298
+ * which preserves scroll position natively.
299
+ */
208
300
  if (doc) {
209
301
  doc.open()
210
302
  doc.write(renderedHtml)
211
303
  doc.close()
212
304
  // Hide iframe body overflow — scrolling is handled by the outer ScrollArea
213
305
  if (doc.body) doc.body.style.overflow = 'hidden'
306
+ if (darkMode.value && iframe) applyColorInversion(iframe)
214
307
  await nextTick()
215
308
  updateIframeContentHeight()
216
309
  } else {
@@ -219,19 +312,58 @@ async function fetchTemplate() {
219
312
  }
220
313
  }
221
314
 
315
+ const sourceLoading = ref(false)
316
+ const vueSourceLoading = ref(false)
317
+ const plaintextLoading = ref(false)
318
+
222
319
  async function fetchSource() {
223
- const res = await fetch(`/__maizzle/source/${route.params.template}`)
224
- sourceHtml.value = await res.text()
320
+ if (sourceLoading.value) return
321
+ sourceLoading.value = true
322
+ const gen = sourcesGeneration.value
323
+ try {
324
+ const res = await fetch(`/__maizzle/source/${route.params.template}`)
325
+ sourceHtml.value = await res.text()
326
+ compiledGen.value = gen
327
+ } finally {
328
+ sourceLoading.value = false
329
+ }
225
330
  }
226
331
 
227
332
  async function fetchVueSource() {
228
- const res = await fetch(`/__maizzle/vue-source/${route.params.template}`)
229
- vueSourceHtml.value = await res.text()
333
+ if (vueSourceLoading.value) return
334
+ vueSourceLoading.value = true
335
+ const gen = sourcesGeneration.value
336
+ try {
337
+ const res = await fetch(`/__maizzle/vue-source/${route.params.template}`)
338
+ vueSourceHtml.value = await res.text()
339
+ vueGen.value = gen
340
+ } finally {
341
+ vueSourceLoading.value = false
342
+ }
230
343
  }
231
344
 
232
345
  async function fetchPlaintext() {
233
- const res = await fetch(`/__maizzle/plaintext/${route.params.template}`)
234
- plaintextContent.value = await res.text()
346
+ if (plaintextLoading.value) return
347
+ plaintextLoading.value = true
348
+ const gen = sourcesGeneration.value
349
+ try {
350
+ const res = await fetch(`/__maizzle/plaintext/${route.params.template}`)
351
+ plaintextContent.value = await res.text()
352
+ plaintextGen.value = gen
353
+ } finally {
354
+ plaintextLoading.value = false
355
+ }
356
+ }
357
+
358
+ /**
359
+ * Warm the three source views in the background so switching from the
360
+ * preview is instant. Single-flight guards above prevent duplication
361
+ * with any in-flight fetch from a view-switch watcher.
362
+ */
363
+ function prefetchSources() {
364
+ if (!sourceHtml.value) fetchSource()
365
+ if (!vueSourceHtml.value) fetchVueSource()
366
+ if (!plaintextContent.value) fetchPlaintext()
235
367
  }
236
368
 
237
369
  async function fetchStats() {
@@ -247,22 +379,33 @@ async function fetchStats() {
247
379
  }
248
380
 
249
381
  async function fetchCompatibility() {
382
+ if (compatibilityDisabled.value) return
383
+ const template = props.templates?.find(t => t.href === '/' + route.params.template)
384
+ if (!template) return
385
+
250
386
  compatibilityLoading.value = true
251
387
  compatibilityError.value = ''
252
388
  try {
253
- const res = await fetch('/__maizzle/compatibility', {
254
- method: 'POST',
255
- body: renderedHtml,
256
- })
389
+ const res = await fetch(`/__maizzle/compatibility/${template.path}`)
257
390
  const data = await res.json()
258
- if (data?.error) {
391
+ if (!Array.isArray(data) && data?.error) {
259
392
  compatibilityError.value = data.error
260
393
  compatibilityIssues.value = []
261
394
  } else {
262
- compatibilityIssues.value = data
263
- // Default to first category that has issues
264
- const firstCat = compatibilityCategories.find(cat => data.some((i: CompatibilityIssue) => i.category === cat))
265
- compatibilityCategory.value = firstCat || ''
395
+ const issues: CheckIssue[] = Array.isArray(data) ? data : []
396
+ compatibilityIssues.value = issues
397
+ /**
398
+ * Keep the current category if it still has issues; otherwise fall
399
+ * back to the first category that does. Prevents a "refresh"
400
+ * during edits from snapping back to CSS when the user is
401
+ * on HTML/Image.
402
+ */
403
+ const current = compatibilityCategory.value
404
+ const currentStillActive = current && issues.some((i) => i.category === current)
405
+ if (!currentStillActive) {
406
+ const firstCat = compatibilityCategories.find(cat => issues.some((i) => i.category === cat))
407
+ compatibilityCategory.value = firstCat || ''
408
+ }
266
409
  }
267
410
  } catch {
268
411
  compatibilityIssues.value = []
@@ -271,19 +414,21 @@ async function fetchCompatibility() {
271
414
  }
272
415
  }
273
416
 
274
- async function fetchLint() {
275
- lintLoading.value = true
276
- try {
277
- const template = props.templates?.find(t => t.href === '/' + route.params.template)
278
- const filePath = template?.path ?? route.params.template
279
- const res = await fetch(`/__maizzle/lint/${filePath}`)
280
- const data = await res.json()
281
- lintIssues.value = Array.isArray(data) ? data.filter((i: LintIssue) => i.title) : []
282
- } catch {
283
- lintIssues.value = []
284
- } finally {
285
- lintLoading.value = false
286
- }
417
+ /** Check if an issue is from the currently viewed template file */
418
+ function isCurrentFile(issue: { file: string }): boolean {
419
+ const template = props.templates?.find(t => t.href === '/' + route.params.template)
420
+ if (!template) return true
421
+ return issue.file.endsWith(template.path)
422
+ }
423
+
424
+ /** Get a short display name for a component file path */
425
+ function componentName(filePath: string): string {
426
+ const parts = filePath.replace(/\\/g, '/').split('/')
427
+ return parts[parts.length - 1]?.replace(/\.vue$/, '') ?? filePath
428
+ }
429
+
430
+ function openInEditor(file: string, line: number) {
431
+ fetch(`/__open-in-editor?file=${encodeURIComponent(file + ':' + line)}`)
287
432
  }
288
433
 
289
434
  watch(() => route.params.template, () => {
@@ -292,76 +437,98 @@ watch(() => route.params.template, () => {
292
437
  plaintextContent.value = ''
293
438
  compatibilityIssues.value = []
294
439
  compatibilityError.value = ''
295
- lintIssues.value = []
296
440
  stats.value = null
297
441
  emailResult.value = null
298
442
  sourceView.value = 'compiled'
299
- fetchTemplate().then(fetchCompatibility)
300
- fetchLint()
443
+ fetchTemplate().then(prefetchSources)
444
+ fetchCompatibility()
301
445
  fetchStats()
302
446
  fetchEmailConfig()
303
447
  if (viewMode.value === 'source') fetchSource()
304
448
  }, { immediate: true })
305
449
 
306
- watch(viewMode, (mode) => {
307
- if (mode === 'source') {
308
- if (sourceView.value === 'compiled' && !sourceHtml.value) fetchSource()
309
- if (sourceView.value === 'vue' && !vueSourceHtml.value) fetchVueSource()
310
- if (sourceView.value === 'plaintext' && !plaintextContent.value) fetchPlaintext()
450
+ // Templates list loads async from App.vue — re-trigger once available
451
+ watch(() => props.templates, (templates) => {
452
+ if (templates?.length && !compatibilityIssues.value.length && !compatibilityLoading.value) {
453
+ fetchCompatibility()
311
454
  }
312
455
  })
313
456
 
314
- watch(sourceView, (view) => {
315
- if (view === 'vue' && !vueSourceHtml.value) fetchVueSource()
316
- if (view === 'compiled' && !sourceHtml.value) fetchSource()
317
- if (view === 'plaintext' && !plaintextContent.value) fetchPlaintext()
457
+ watch(viewMode, (mode) => {
458
+ if (mode === 'source') refreshSourceView(sourceView.value)
318
459
  })
319
460
 
320
- if ((import.meta as any).hot) {
321
- ;(import.meta as any).hot.on('maizzle:template-updated', () => {
322
- fetchTemplate().then(fetchCompatibility)
323
- fetchLint()
324
- fetchStats()
325
-
326
- // Always clear all source views so they re-fetch when switched to
327
- sourceHtml.value = ''
328
- vueSourceHtml.value = ''
329
- plaintextContent.value = ''
461
+ watch(sourceView, (view) => {
462
+ refreshSourceView(view)
463
+ })
330
464
 
331
- // Re-fetch the active source view immediately if currently visible
332
- if (viewMode.value === 'source') {
333
- if (sourceView.value === 'compiled') fetchSource()
334
- if (sourceView.value === 'vue') fetchVueSource()
335
- if (sourceView.value === 'plaintext') fetchPlaintext()
336
- }
337
- })
465
+ /**
466
+ * Preserve scrollTop across in-place content updates (HMR refetch).
467
+ * Vue's default `flush: 'pre'` runs the watcher BEFORE the DOM is
468
+ * updated so we read the current scrollTop, then restore it on the
469
+ * next tick after the new content has rendered. Skip the case where
470
+ * the value transitions from empty (first paint / route change) so a
471
+ * fresh template doesn't snap to a stale offset.
472
+ */
473
+ function viewportFor(el: HTMLElement | undefined): HTMLElement | null {
474
+ return (el?.closest('[data-slot="scroll-area-viewport"]') as HTMLElement | null) ?? null
338
475
  }
339
476
 
340
-
341
- async function goToLine(line: number) {
342
- // Switch to source view showing Vue source
343
- viewMode.value = 'source'
344
- sourceView.value = 'vue'
345
-
346
- // Ensure vue source is loaded
347
- if (!vueSourceHtml.value) {
348
- await fetchVueSource()
477
+ function preserveScroll(getEl: () => HTMLElement | undefined) {
478
+ return async (newVal: string, oldVal: string) => {
479
+ if (!oldVal || !newVal) return
480
+ const vp = viewportFor(getEl())
481
+ if (!vp) return
482
+ const top = vp.scrollTop
483
+ await nextTick()
484
+ vp.scrollTop = top
349
485
  }
486
+ }
350
487
 
351
- await nextTick()
488
+ watch(sourceHtml, preserveScroll(() => compiledSourceEl.value))
489
+ watch(vueSourceHtml, preserveScroll(() => vueSourceEl.value))
490
+ watch(plaintextContent, preserveScroll(() => plaintextEl.value))
352
491
 
353
- const el = vueSourceEl.value
354
- if (!el) return
492
+ if ((import.meta as any).hot) {
493
+ ;(import.meta as any).hot.on('maizzle:template-updated', () => {
494
+ fetchCompatibility()
495
+ fetchStats()
355
496
 
356
- // Remove previous highlight
357
- el.querySelectorAll('.shiki-highlight-line').forEach(l => l.classList.remove('shiki-highlight-line'))
497
+ /**
498
+ * Refetch in place — don't clear the previous values first. v-html
499
+ * replaces the highlighted block atomically when the new content
500
+ * arrives, and the ScrollArea viewport keeps its scrollTop as
501
+ * long as the new content's height is similar. Plaintext
502
+ * interpolation updates a single text node, so scroll
503
+ * is naturally preserved.
504
+ *
505
+ * Only the preview iframe and the currently-visible source view
506
+ * refresh eagerly; hidden source views are marked stale (via the
507
+ * generation bump) and refresh on next switch, keeping their old
508
+ * content on screen until then so panels never flash empty.
509
+ */
510
+ fetchTemplate()
511
+ sourcesGeneration.value++
512
+ if (viewMode.value === 'source') refreshSourceView(sourceView.value)
513
+ })
358
514
 
359
- // Find and highlight the line
360
- const lineEl = el.querySelector(`[data-line="${line}"]`)
361
- if (lineEl) {
362
- lineEl.classList.add('shiki-highlight-line')
363
- lineEl.scrollIntoView({ block: 'center', behavior: 'smooth' })
364
- }
515
+ /**
516
+ * Keep the UI in sync with live config edits. Payload is the same shape
517
+ * as the initial `window.__MAIZZLE_CONFIG__` inject — we replace it
518
+ * and derive per-feature flags from there.
519
+ */
520
+ ;(import.meta as any).hot.on('maizzle:config-updated', (data: Record<string, unknown>) => {
521
+ ;(window as any).__MAIZZLE_CONFIG__ = data
522
+ const wasDisabled = compatibilityDisabled.value
523
+ const nowDisabled = data?.checks === false
524
+ compatibilityDisabled.value = nowDisabled
525
+ if (nowDisabled) {
526
+ compatibilityIssues.value = []
527
+ if (activeTab.value === 'compatibility') activeTab.value = 'stats'
528
+ } else if (wasDisabled) {
529
+ fetchCompatibility()
530
+ }
531
+ })
365
532
  }
366
533
 
367
534
  async function goToCompiledLine(line: number) {
@@ -412,6 +579,8 @@ function onEdgeDrag(e: MouseEvent | TouchEvent, edge: Edge) {
412
579
  const isHorizontal = edge === 'left' || edge === 'right'
413
580
  const sign = (edge === 'left' || edge === 'top') ? -1 : 1
414
581
 
582
+ document.documentElement.style.cursor = isHorizontal ? 'ew-resize' : 'ns-resize'
583
+
415
584
  const onMove = (ev: MouseEvent | TouchEvent) => {
416
585
  const point = ev.type === 'touchmove' ? (ev as TouchEvent).touches[0] : (ev as MouseEvent)
417
586
  if (isHorizontal) {
@@ -426,6 +595,7 @@ function onEdgeDrag(e: MouseEvent | TouchEvent, edge: Edge) {
426
595
 
427
596
  const onUp = () => {
428
597
  isDragging.value = false
598
+ document.documentElement.style.cursor = ''
429
599
  updateFullSize()
430
600
  document.removeEventListener('mousemove', onMove)
431
601
  document.removeEventListener('mouseup', onUp)
@@ -448,6 +618,7 @@ function updateFullSize() {
448
618
  && (iframeHeight.value === null || iframeHeight.value >= rect.height - gutter - 2)
449
619
  }
450
620
 
621
+
451
622
  function applyDeviceSize(device: Device | null | undefined) {
452
623
  if (!device) {
453
624
  iframeWidth.value = null
@@ -484,6 +655,7 @@ watch(viewMode, async (mode) => {
484
655
  })
485
656
 
486
657
  let observer: ResizeObserver | null = null
658
+ let containerObserver: ResizeObserver | null = null
487
659
 
488
660
  function forwardIframeKeys(iframe: HTMLIFrameElement) {
489
661
  try {
@@ -498,6 +670,7 @@ function forwardIframeKeys(iframe: HTMLIFrameElement) {
498
670
  metaKey: e.metaKey,
499
671
  shiftKey: e.shiftKey,
500
672
  altKey: e.altKey,
673
+ bubbles: true,
501
674
  }))
502
675
  })
503
676
  } catch {}
@@ -519,6 +692,21 @@ onMounted(() => {
519
692
  observer.observe(wrapper)
520
693
  }
521
694
 
695
+ const container = containerEl.value
696
+ if (container) {
697
+ const gutter = 40
698
+ const rect = container.getBoundingClientRect()
699
+ maxIframeWidth.value = Math.max(0, Math.round(rect.width - gutter))
700
+ maxIframeHeight.value = Math.max(0, Math.round(rect.height - gutter))
701
+ containerObserver = new ResizeObserver((entries) => {
702
+ for (const entry of entries) {
703
+ maxIframeWidth.value = Math.max(0, Math.round(entry.contentRect.width - gutter))
704
+ maxIframeHeight.value = Math.max(0, Math.round(entry.contentRect.height - gutter))
705
+ }
706
+ })
707
+ containerObserver.observe(container)
708
+ }
709
+
522
710
  const el = iframeEl.value
523
711
  if (el) {
524
712
  el.addEventListener('load', () => forwardIframeKeys(el))
@@ -527,17 +715,20 @@ onMounted(() => {
527
715
 
528
716
  onUnmounted(() => {
529
717
  observer?.disconnect()
718
+ containerObserver?.disconnect()
530
719
  })
531
720
 
532
721
  const bottomPanelOpen = ref(false)
533
722
  const tabsPanelHeight = ref(40)
534
723
  const activeTab = ref<string | undefined>(undefined)
535
724
 
725
+ const defaultTab = () => compatibilityDisabled.value ? 'stats' : 'compatibility'
726
+
536
727
  function toggleBottomPanel() {
537
728
  bottomPanelOpen.value = !bottomPanelOpen.value
538
729
  if (bottomPanelOpen.value) {
539
730
  tabsPanelHeight.value = 300
540
- if (!activeTab.value) activeTab.value = 'compatibility'
731
+ if (!activeTab.value) activeTab.value = defaultTab()
541
732
  } else {
542
733
  tabsPanelHeight.value = 40
543
734
  activeTab.value = undefined
@@ -560,35 +751,39 @@ function onTabClick(tab: string) {
560
751
 
561
752
  const tabsDragging = ref(false)
562
753
 
563
- function onTabsDragStart(e: MouseEvent) {
754
+ function onTabsDragStart(e: MouseEvent | TouchEvent) {
564
755
  e.preventDefault()
565
756
  tabsDragging.value = true
566
- const startY = e.clientY
757
+ const isTouch = e.type === 'touchstart'
758
+ const startY = isTouch ? (e as TouchEvent).touches[0].clientY : (e as MouseEvent).clientY
567
759
  const startHeight = tabsPanelHeight.value
568
760
 
569
761
  const rootEl = containerEl.value?.closest('.relative.h-full') as HTMLElement | null
570
762
  const maxHeight = rootEl ? rootEl.getBoundingClientRect().height : Infinity
571
763
 
572
- const onMouseMove = (e: MouseEvent) => {
573
- const newHeight = Math.max(40, Math.min(maxHeight, startHeight + startY - e.clientY))
764
+ const onMove = (e: MouseEvent | TouchEvent) => {
765
+ const clientY = e.type === 'touchmove' ? (e as TouchEvent).touches[0].clientY : (e as MouseEvent).clientY
766
+ const newHeight = Math.max(40, Math.min(maxHeight, startHeight + startY - clientY))
574
767
  tabsPanelHeight.value = newHeight
575
768
  bottomPanelOpen.value = newHeight > 40
576
769
 
577
770
  if (!bottomPanelOpen.value) {
578
771
  activeTab.value = undefined
579
772
  } else if (!activeTab.value) {
580
- activeTab.value = 'compatibility'
773
+ activeTab.value = defaultTab()
581
774
  }
582
775
  }
583
776
 
584
- const onMouseUp = () => {
777
+ const onEnd = () => {
585
778
  tabsDragging.value = false
586
- document.removeEventListener('mousemove', onMouseMove)
587
- document.removeEventListener('mouseup', onMouseUp)
779
+ document.removeEventListener('mousemove', onMove)
780
+ document.removeEventListener('mouseup', onEnd)
781
+ document.removeEventListener('touchmove', onMove)
782
+ document.removeEventListener('touchend', onEnd)
588
783
  }
589
784
 
590
- document.addEventListener('mousemove', onMouseMove)
591
- document.addEventListener('mouseup', onMouseUp)
785
+ document.addEventListener(isTouch ? 'touchmove' : 'mousemove', onMove)
786
+ document.addEventListener(isTouch ? 'touchend' : 'mouseup', onEnd)
592
787
  }
593
788
 
594
789
  const stripeBg = {
@@ -651,6 +846,7 @@ const stripeBg = {
651
846
  </ScrollArea>
652
847
  <ScrollArea v-show="sourceView === 'plaintext'" class="h-full [&_[data-slot=scroll-area-viewport]>div]:flex [&_[data-slot=scroll-area-viewport]>div]:flex-col [&_[data-slot=scroll-area-viewport]>div]:min-h-full">
653
848
  <pre
849
+ ref="plaintextEl"
654
850
  class="p-6 pt-14 text-sm leading-6 flex-1 text-gray-300 bg-[#27212e] dark:bg-gray-950 whitespace-pre-wrap break-words"
655
851
  >{{ plaintextContent }}</pre>
656
852
  </ScrollArea>
@@ -697,7 +893,7 @@ const stripeBg = {
697
893
  <iframe
698
894
  ref="iframeEl"
699
895
  :srcdoc="srcdoc"
700
- @load="updateIframeContentHeight"
896
+ @load="onIframeLoad"
701
897
  class="w-full border-0 bg-white dark:bg-gray-950"
702
898
  :style="{ height: iframeContentHeight ? `${iframeContentHeight}px` : '100%' }"
703
899
  />
@@ -719,20 +915,18 @@ const stripeBg = {
719
915
  <div
720
916
  class="relative h-0 cursor-row-resize before:absolute before:top-0 before:left-0 before:right-0 before:h-3.25 before:content-['']"
721
917
  @mousedown="onTabsDragStart"
918
+ @touchstart.prevent="onTabsDragStart"
722
919
  />
723
920
  <Tabs :model-value="activeTab" class="flex flex-col min-h-0 h-full">
724
921
  <div class="flex items-center justify-between min-h-10 pl-2 pr-3 shrink-0" :class="bottomPanelOpen ? 'border-b' : ''">
725
922
  <TabsList class="h-full bg-transparent! rounded-none! p-0 gap-1">
726
- <TabsTrigger value="compatibility" class="text-xs font-normal px-3 h-full rounded-none! border-0! shadow-none! border-b! border-transparent select-none data-[state=active]:border-gray-400 data-[state=active]:dark:border-gray-600 data-[state=active]:bg-transparent data-[state=inactive]:bg-transparent" @click="onTabClick('compatibility')">
727
- Compatibility
728
- </TabsTrigger>
729
- <TabsTrigger value="lint" class="text-xs font-normal px-3 h-full rounded-none! border-0! shadow-none! border-b! border-transparent select-none data-[state=active]:border-gray-400 data-[state=active]:dark:border-gray-600 data-[state=active]:bg-transparent data-[state=inactive]:bg-transparent" @click="onTabClick('lint')">
730
- Linter
923
+ <TabsTrigger v-if="!compatibilityDisabled" value="compatibility" class="text-xs font-normal px-3 h-full rounded-none! border-0! shadow-none! border-b! border-transparent select-none data-[state=active]:border-gray-400 data-[state=active]:dark:border-gray-600 data-[state=active]:bg-transparent data-[state=inactive]:bg-transparent dark:bg-transparent! dark:hover:bg-transparent!" @click="onTabClick('compatibility')">
924
+ Checks
731
925
  </TabsTrigger>
732
- <TabsTrigger value="stats" class="text-xs font-normal px-3 h-full rounded-none! border-0! shadow-none! border-b! border-transparent select-none data-[state=active]:border-gray-400 data-[state=active]:dark:border-gray-600 data-[state=active]:bg-transparent data-[state=inactive]:bg-transparent" @click="onTabClick('stats')">
926
+ <TabsTrigger value="stats" class="text-xs font-normal px-3 h-full rounded-none! border-0! shadow-none! border-b! border-transparent select-none data-[state=active]:border-gray-400 data-[state=active]:dark:border-gray-600 data-[state=active]:bg-transparent data-[state=inactive]:bg-transparent dark:bg-transparent! dark:hover:bg-transparent!" @click="onTabClick('stats')">
733
927
  Stats
734
928
  </TabsTrigger>
735
- <TabsTrigger value="test" class="text-xs font-normal px-3 h-full rounded-none! border-0! shadow-none! border-b! border-transparent select-none data-[state=active]:border-gray-400 data-[state=active]:dark:border-gray-600 data-[state=active]:bg-transparent data-[state=inactive]:bg-transparent" @click="onTabClick('test')">
929
+ <TabsTrigger value="test" class="text-xs font-normal px-3 h-full rounded-none! border-0! shadow-none! border-b! border-transparent select-none data-[state=active]:border-gray-400 data-[state=active]:dark:border-gray-600 data-[state=active]:bg-transparent data-[state=inactive]:bg-transparent dark:bg-transparent! dark:hover:bg-transparent!" @click="onTabClick('test')">
736
930
  Test
737
931
  </TabsTrigger>
738
932
  </TabsList>
@@ -746,7 +940,7 @@ const stripeBg = {
746
940
  <button
747
941
  v-for="cat in activeCompatibilityCategories"
748
942
  :key="cat"
749
- class="px-2 py-0.5 text-[11px] rounded-full cursor-pointer transition-colors"
943
+ class="px-2 py-0.5 text-[11px] rounded-full cursor-default transition-colors"
750
944
  :class="compatibilityCategory === cat
751
945
  ? 'bg-gray-900 text-white dark:bg-gray-600 dark:text-gray-100'
752
946
  : 'text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-white/10'"
@@ -757,48 +951,45 @@ const stripeBg = {
757
951
  </button>
758
952
  </div>
759
953
  <ScrollArea class="h-full flex-1 min-h-0 pl-5">
760
- <p v-if="compatibilityLoading" class="pr-4 py-3 text-xs text-gray-500 dark:text-gray-400">Checking compatibility...</p>
954
+ <p v-if="compatibilityLoading" class="pr-4 py-3 text-xs text-gray-500 dark:text-gray-400">Running checks...</p>
761
955
  <p v-else-if="compatibilityError" class="pr-4 py-3 text-xs text-red-500 dark:text-red-400">{{ compatibilityError }}</p>
762
- <p v-else-if="compatibilityIssues.length === 0" class="pr-4 py-3 text-xs text-gray-500 dark:text-gray-400">No compatibility issues found.</p>
956
+ <p v-else-if="compatibilityIssues.length === 0" class="pr-4 py-3 text-xs text-gray-500 dark:text-gray-400">No issues found.</p>
763
957
  <ul v-else class="text-xs divide-y">
764
958
  <li
765
959
  v-for="(issue, i) in filteredCompatibilityIssues"
766
960
  :key="i"
767
- class="pr-4 py-1.5 hover:bg-gray-50 dark:hover:bg-white/5"
768
- >
769
- <div class="flex items-center gap-2">
770
- <a v-if="issue.url" :href="issue.url" target="_blank" rel="noopener" class="font-medium hover:underline shrink-0" :class="issue.type === 'error' ? 'text-red-600' : 'text-amber-600'">
771
- {{ issue.title }}
772
- </a>
773
- <span v-else class="font-medium shrink-0" :class="issue.type === 'error' ? 'text-red-600' : 'text-amber-600'">
774
- {{ issue.title }}
775
- </span>
776
- <span class="text-gray-400 dark:text-gray-600 shrink-0">&middot;</span>
777
- <span class="text-gray-500 dark:text-gray-400 truncate">{{ issue.type === 'error' ? 'Not supported' : 'Partial support' }} in {{ issue.clients.map((c: any) => c.name).join(', ') }}</span>
778
- <button v-if="issue.line" class="text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 cursor-pointer tabular-nums shrink-0 ml-auto" @click="goToCompiledLine(issue.line!)">L{{ issue.line }}</button>
779
- </div>
780
- </li>
781
- </ul>
782
- </ScrollArea>
783
- </TabsContent>
784
- <TabsContent value="lint" class="mt-0 h-full">
785
- <ScrollArea class="h-full pl-5">
786
- <p v-if="lintLoading" class="pr-4 py-3 text-xs text-gray-500 dark:text-gray-400">Linting...</p>
787
- <p v-else-if="lintIssues.length === 0" class="pr-4 py-3 text-xs text-gray-500 dark:text-gray-400">No issues found.</p>
788
- <ul v-else class="text-xs divide-y">
789
- <li
790
- v-for="(issue, i) in lintIssues"
791
- :key="i"
792
- class="pr-4 py-2 hover:bg-gray-50 dark:hover:bg-white/5"
961
+ class="pr-4 py-2"
793
962
  >
794
- <div class="flex items-start justify-between gap-4">
963
+ <div class="flex items-center justify-between gap-4">
795
964
  <div>
796
- <span class="font-medium" :class="issue.type === 'error' ? 'text-red-600' : 'text-amber-600'">
965
+ <a v-if="issue.url" :href="issue.url" target="_blank" rel="noopener" class="font-medium hover:underline" :class="issueColorClass(issue)">
966
+ {{ issue.title }}
967
+ </a>
968
+ <span v-else class="font-medium" :class="issueColorClass(issue)">
797
969
  {{ issue.title }}
798
970
  </span>
799
- <div class="text-gray-500 dark:text-gray-400 mt-0.5">{{ issue.message }}</div>
971
+ <div class="text-gray-500 dark:text-gray-400 mt-0.5">
972
+ <template v-if="issue.kind === 'lint'">
973
+ <template v-for="(seg, j) in messageSegments(issue.message)" :key="j">
974
+ <code v-if="seg.code" class="px-1 py-0.5 rounded bg-gray-100 dark:bg-white/10 font-mono text-[11px]">{{ seg.text }}</code>
975
+ <template v-else>{{ seg.text }}</template>
976
+ </template>
977
+ </template>
978
+ <template v-else>
979
+ {{ supportPrefix(issue) }}
980
+ <template v-if="(issue.affectedClients?.length ?? 0) <= 4 || expandedIssueKeys.has(issueKey(issue, i))">
981
+ {{ (issue.affectedClients ?? []).join(', ') }}
982
+ </template>
983
+ <template v-else>
984
+ {{ issue.affectedClients!.slice(0, 4).join(', ') }}
985
+ <button class="underline cursor-pointer hover:text-gray-700 dark:hover:text-gray-200" @click="expandedIssueKeys.add(issueKey(issue, i)); expandedIssueKeys = new Set(expandedIssueKeys)">
986
+ + {{ issue.affectedClients!.length - 4 }} others
987
+ </button>
988
+ </template>
989
+ </template>
990
+ </div>
800
991
  </div>
801
- <button v-if="issue.line" class="text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 cursor-pointer tabular-nums shrink-0" @click="goToLine(issue.line!)">L{{ issue.line }}</button>
992
+ <button v-if="issue.line" class="text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 cursor-pointer tabular-nums shrink-0" @click="openInEditor(issue.file, issue.line!)">{{ isCurrentFile(issue) ? `L${issue.line}` : `${componentName(issue.file)}:${issue.line}` }}</button>
802
993
  </div>
803
994
  </li>
804
995
  </ul>
@@ -808,35 +999,35 @@ const stripeBg = {
808
999
  <ScrollArea class="h-full pl-5">
809
1000
  <p v-if="statsLoading" class="pr-4 py-3 text-xs text-gray-500 dark:text-gray-400">Loading stats...</p>
810
1001
  <p v-else-if="!stats" class="pr-4 py-3 text-xs text-gray-500 dark:text-gray-400">No stats available.</p>
811
- <div v-else class="pr-4 py-3 flex items-center gap-6 text-xs">
812
- <div class="flex items-center gap-1.5">
813
- <span class="text-gray-500 dark:text-gray-400">Size</span>
814
- <span
815
- class="font-medium tabular-nums"
816
- :class="stats.size.bytes > 102400 ? 'text-red-600' : stats.size.bytes > 51200 ? 'text-amber-600' : 'text-gray-900 dark:text-gray-300'"
817
- >{{ stats.size.formatted }}</span>
818
- <TooltipProvider :delay-duration="0">
819
- <Tooltip>
820
- <TooltipTrigger as-child>
821
- <button type="button">
822
- <Info class="size-3 text-gray-400 dark:text-gray-500" />
823
- </button>
824
- </TooltipTrigger>
825
- <TooltipContent class="max-w-60">
826
- Compiled HTML size, excludes image files. Gmail clips content at ~100KB.
827
- </TooltipContent>
828
- </Tooltip>
829
- </TooltipProvider>
830
- </div>
831
- <div class="flex items-center gap-1.5">
832
- <span class="text-gray-500 dark:text-gray-400">Images</span>
833
- <span class="font-medium tabular-nums">{{ stats.images }}</span>
834
- </div>
835
- <div class="flex items-center gap-1.5">
836
- <span class="text-gray-500 dark:text-gray-400">Links</span>
837
- <span class="font-medium tabular-nums">{{ stats.links }}</span>
838
- </div>
839
- </div>
1002
+ <ul v-else class="text-xs divide-y divide-gray-200 dark:divide-white/10">
1003
+ <li class="pr-4 py-2">
1004
+ <div class="flex items-center justify-between gap-4">
1005
+ <div>
1006
+ <span class="font-medium" :class="stats.size.bytes > 102400 ? 'text-red-600' : stats.size.bytes > 51200 ? 'text-amber-600' : 'text-gray-900 dark:text-gray-300'">Size</span>
1007
+ <div class="text-gray-500 dark:text-gray-400 mt-0.5">Compiled HTML size. Gmail clips emails larger than ~100KB.</div>
1008
+ </div>
1009
+ <span class="font-medium tabular-nums shrink-0" :class="stats.size.bytes > 102400 ? 'text-red-600' : stats.size.bytes > 51200 ? 'text-amber-600' : 'text-gray-900 dark:text-gray-300'">{{ stats.size.formatted }}</span>
1010
+ </div>
1011
+ </li>
1012
+ <li class="pr-4 py-2">
1013
+ <div class="flex items-center justify-between gap-4">
1014
+ <div>
1015
+ <span class="font-medium text-gray-900 dark:text-gray-300">Images</span>
1016
+ <div class="text-gray-500 dark:text-gray-400 mt-0.5">Total from &lt;img&gt; tags and CSS background images.</div>
1017
+ </div>
1018
+ <span class="font-medium tabular-nums shrink-0">{{ stats.images }}</span>
1019
+ </div>
1020
+ </li>
1021
+ <li class="pr-4 py-2">
1022
+ <div class="flex items-center justify-between gap-4">
1023
+ <div>
1024
+ <span class="font-medium text-gray-900 dark:text-gray-300">Links</span>
1025
+ <div class="text-gray-500 dark:text-gray-400 mt-0.5">Total &lt;a&gt; tags with an href attribute.</div>
1026
+ </div>
1027
+ <span class="font-medium tabular-nums shrink-0">{{ stats.links }}</span>
1028
+ </div>
1029
+ </li>
1030
+ </ul>
840
1031
  </ScrollArea>
841
1032
  </TabsContent>
842
1033
  <TabsContent value="test" class="mt-0 h-full">
@@ -844,19 +1035,19 @@ const stripeBg = {
844
1035
  <div class="pr-4 py-3 max-w-md">
845
1036
  <div class="space-y-2">
846
1037
  <div class="flex items-center gap-2">
847
- <label class="text-xs text-gray-500 dark:text-gray-400 w-12 shrink-0">To</label>
1038
+ <label for="email-to" class="text-xs text-gray-500 dark:text-gray-400 w-12 shrink-0 cursor-pointer">To</label>
848
1039
  <TagsInput v-model="emailTo" delimiter=" " add-on-paste add-on-blur class="flex-1 min-h-7 gap-1 px-2 py-1">
849
1040
  <TagsInputItem v-for="item in emailTo" :key="item" :value="item" class="h-5 text-xs rounded">
850
1041
  <TagsInputItemText class="px-1.5 py-0 text-xs" />
851
1042
  <TagsInputItemDelete class="size-3.5" />
852
1043
  </TagsInputItem>
853
- <TagsInputInput class="text-xs min-h-5 px-0.5" placeholder="Add emails..." />
1044
+ <TagsInputInput id="email-to" class="text-xs min-h-5 px-0.5" placeholder="Add emails..." />
854
1045
  </TagsInput>
855
1046
  </div>
856
1047
  <div class="flex items-center gap-2">
857
- <label class="text-xs text-gray-500 dark:text-gray-400 w-12 shrink-0">Subject</label>
1048
+ <label for="email-subject" class="text-xs text-gray-500 dark:text-gray-400 w-12 shrink-0 cursor-pointer">Subject</label>
858
1049
  <div class="flex-1 flex items-center gap-3">
859
- <Input v-model="emailSubject" :placeholder="String(route.params.template)" class="flex-1 h-7 text-xs! px-2" />
1050
+ <Input id="email-subject" v-model="emailSubject" :placeholder="String(route.params.template)" class="flex-1 h-7 text-xs! px-2" />
860
1051
  <label class="flex items-center gap-1.5 cursor-pointer select-none shrink-0">
861
1052
  <Checkbox v-model="emailPreventThreading" :default-checked="true" class="size-3.5" />
862
1053
  <span class="text-xs text-gray-500 dark:text-gray-400">Prevent threading</span>