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

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 (67) hide show
  1. package/README.md +3 -3
  2. package/dist/components/Body.vue +9 -2
  3. package/dist/components/Button.vue +13 -8
  4. package/dist/components/Column.vue +22 -8
  5. package/dist/components/Container.vue +9 -5
  6. package/dist/components/Head.vue +1 -1
  7. package/dist/components/Html.vue +7 -2
  8. package/dist/components/Layout.vue +45 -15
  9. package/dist/components/Outlook.vue +38 -11
  10. package/dist/components/Overlap.vue +8 -3
  11. package/dist/components/Raw.vue +28 -0
  12. package/dist/components/Row.vue +72 -11
  13. package/dist/components/Section.vue +9 -5
  14. package/dist/components/Spacer.vue +9 -4
  15. package/dist/components/utils.d.mts +11 -1
  16. package/dist/components/utils.d.mts.map +1 -1
  17. package/dist/components/utils.mjs +11 -1
  18. package/dist/components/utils.mjs.map +1 -1
  19. package/dist/components/utils.ts +12 -0
  20. package/dist/composables/useOutlookFallback.d.mts +21 -0
  21. package/dist/composables/useOutlookFallback.d.mts.map +1 -0
  22. package/dist/composables/useOutlookFallback.mjs +30 -0
  23. package/dist/composables/useOutlookFallback.mjs.map +1 -0
  24. package/dist/index.d.mts +3 -1
  25. package/dist/index.mjs +3 -1
  26. package/dist/prepare.d.mts +17 -0
  27. package/dist/prepare.d.mts.map +1 -0
  28. package/dist/prepare.mjs +44 -0
  29. package/dist/prepare.mjs.map +1 -0
  30. package/dist/render/createRenderer.d.mts.map +1 -1
  31. package/dist/render/createRenderer.mjs +9 -75
  32. package/dist/render/createRenderer.mjs.map +1 -1
  33. package/dist/render/plugins/codeBlockExtract.d.mts +14 -0
  34. package/dist/render/plugins/codeBlockExtract.d.mts.map +1 -0
  35. package/dist/render/plugins/codeBlockExtract.mjs +34 -0
  36. package/dist/render/plugins/codeBlockExtract.mjs.map +1 -0
  37. package/dist/render/plugins/markdownExtract.d.mts +12 -0
  38. package/dist/render/plugins/markdownExtract.d.mts.map +1 -0
  39. package/dist/render/plugins/markdownExtract.mjs +50 -0
  40. package/dist/render/plugins/markdownExtract.mjs.map +1 -0
  41. package/dist/render/plugins/rawExtract.d.mts +14 -0
  42. package/dist/render/plugins/rawExtract.d.mts.map +1 -0
  43. package/dist/render/plugins/rawExtract.mjs +34 -0
  44. package/dist/render/plugins/rawExtract.mjs.map +1 -0
  45. package/dist/render/plugins/rowSourceLocation.d.mts +18 -0
  46. package/dist/render/plugins/rowSourceLocation.d.mts.map +1 -0
  47. package/dist/render/plugins/rowSourceLocation.mjs +45 -0
  48. package/dist/render/plugins/rowSourceLocation.mjs.map +1 -0
  49. package/dist/server/ui/App.vue +47 -2
  50. package/dist/server/ui/pages/Preview.vue +32 -3
  51. package/dist/transformers/columnWidth.d.mts +9 -9
  52. package/dist/transformers/columnWidth.d.mts.map +1 -1
  53. package/dist/transformers/columnWidth.mjs +422 -41
  54. package/dist/transformers/columnWidth.mjs.map +1 -1
  55. package/dist/transformers/filters/index.mjs +1 -1
  56. package/dist/transformers/filters/index.mjs.map +1 -1
  57. package/dist/transformers/index.mjs +1 -1
  58. package/dist/transformers/index.mjs.map +1 -1
  59. package/node_modules/maizzle/dist/commands/new.mjs +3 -3
  60. package/node_modules/maizzle/dist/index.d.mts +1 -0
  61. package/node_modules/maizzle/dist/index.mjs +3 -0
  62. package/node_modules/maizzle/package.json +3 -2
  63. package/node_modules/nypm/dist/cli.mjs +28 -5
  64. package/node_modules/nypm/dist/index.d.mts +0 -8
  65. package/node_modules/nypm/dist/index.mjs +27 -4
  66. package/node_modules/nypm/package.json +12 -12
  67. package/package.json +1 -1
@@ -0,0 +1,34 @@
1
+ //#region src/render/plugins/codeBlockExtract.ts
2
+ /**
3
+ * Vite plugin that extracts raw slot content from <CodeBlock> tags
4
+ * and passes it as a :code prop before Vue compiles the template.
5
+ *
6
+ * This lets users write HTML naturally inside CodeBlock slots without
7
+ * Vue attempting to compile it as template syntax.
8
+ */
9
+ function codeBlockExtract() {
10
+ const re = /<(CodeBlock|code-block)((?:\s[^>]*?)?)>([\s\S]*?)<\/\1>/g;
11
+ return {
12
+ name: "maizzle:code-block-extract",
13
+ enforce: "pre",
14
+ transform(code, id) {
15
+ if (!id.endsWith(".vue") && !id.endsWith(".md")) return;
16
+ if (!code.includes("CodeBlock") && !code.includes("code-block")) return;
17
+ const transformed = code.replace(re, (_match, tag, attrs, content) => {
18
+ if (/(?:^|\s):code\b/.test(attrs) || /v-bind:code\b/.test(attrs)) return _match;
19
+ const stripped = content.replace(/^\n+/, "").replace(/\s+$/, "");
20
+ if (!stripped) return _match;
21
+ const minIndent = stripped.match(/^[ \t]*(?=\S)/gm)?.reduce((min, ws) => Math.min(min, ws.length), Infinity) ?? 0;
22
+ return `<${tag}${attrs} code="${(minIndent > 0 ? stripped.replace(new RegExp(`^[ \\t]{${minIndent}}`, "gm"), "") : stripped).replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;")}" />`;
23
+ });
24
+ if (transformed !== code) return {
25
+ code: transformed,
26
+ map: null
27
+ };
28
+ }
29
+ };
30
+ }
31
+
32
+ //#endregion
33
+ export { codeBlockExtract };
34
+ //# sourceMappingURL=codeBlockExtract.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codeBlockExtract.mjs","names":[],"sources":["../../../src/render/plugins/codeBlockExtract.ts"],"sourcesContent":["import type { Plugin } from 'vite'\n\n/**\n * Vite plugin that extracts raw slot content from <CodeBlock> tags\n * and passes it as a :code prop before Vue compiles the template.\n *\n * This lets users write HTML naturally inside CodeBlock slots without\n * Vue attempting to compile it as template syntax.\n */\nexport function codeBlockExtract(): Plugin {\n // Matches <CodeBlock ...>content</CodeBlock> (and kebab-case <code-block>)\n const re = /<(CodeBlock|code-block)((?:\\s[^>]*?)?)>([\\s\\S]*?)<\\/\\1>/g\n\n return {\n name: 'maizzle:code-block-extract',\n enforce: 'pre',\n transform(code: string, id: string) {\n if (!id.endsWith('.vue') && !id.endsWith('.md')) return\n if (!code.includes('CodeBlock') && !code.includes('code-block')) return\n\n const transformed = code.replace(re, (_match, tag, attrs, content) => {\n // Skip if already has a :code or v-bind:code prop\n if (/(?:^|\\s):code\\b/.test(attrs) || /v-bind:code\\b/.test(attrs)) return _match\n\n // Strip leading/trailing blank lines, then dedent based on\n // the minimum indent of non-empty lines (à la min-indent)\n const stripped = content.replace(/^\\n+/, '').replace(/\\s+$/, '')\n if (!stripped) return _match\n\n const minIndent = stripped.match(/^[ \\t]*(?=\\S)/gm)\n ?.reduce((min: number, ws: string) => Math.min(min, ws.length), Infinity) ?? 0\n\n const dedented = minIndent > 0\n ? stripped.replace(new RegExp(`^[ \\\\t]{${minIndent}}`, 'gm'), '')\n : stripped\n\n // HTML-escape for safe embedding in a static attribute value.\n const escaped = dedented\n .replace(/&/g, '&amp;')\n .replace(/\"/g, '&quot;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n\n return `<${tag}${attrs} code=\"${escaped}\" />`\n })\n\n if (transformed !== code) {\n return { code: transformed, map: null }\n }\n },\n }\n}\n"],"mappings":";;;;;;;;AASA,SAAgB,mBAA2B;CAEzC,MAAM,KAAK;AAEX,QAAO;EACL,MAAM;EACN,SAAS;EACT,UAAU,MAAc,IAAY;AAClC,OAAI,CAAC,GAAG,SAAS,OAAO,IAAI,CAAC,GAAG,SAAS,MAAM,CAAE;AACjD,OAAI,CAAC,KAAK,SAAS,YAAY,IAAI,CAAC,KAAK,SAAS,aAAa,CAAE;GAEjE,MAAM,cAAc,KAAK,QAAQ,KAAK,QAAQ,KAAK,OAAO,YAAY;AAEpE,QAAI,kBAAkB,KAAK,MAAM,IAAI,gBAAgB,KAAK,MAAM,CAAE,QAAO;IAIzE,MAAM,WAAW,QAAQ,QAAQ,QAAQ,GAAG,CAAC,QAAQ,QAAQ,GAAG;AAChE,QAAI,CAAC,SAAU,QAAO;IAEtB,MAAM,YAAY,SAAS,MAAM,kBAAkB,EAC/C,QAAQ,KAAa,OAAe,KAAK,IAAI,KAAK,GAAG,OAAO,EAAE,SAAS,IAAI;AAa/E,WAAO,IAAI,MAAM,MAAM,UAXN,YAAY,IACzB,SAAS,QAAQ,IAAI,OAAO,WAAW,UAAU,IAAI,KAAK,EAAE,GAAG,GAC/D,UAID,QAAQ,MAAM,QAAQ,CACtB,QAAQ,MAAM,SAAS,CACvB,QAAQ,MAAM,OAAO,CACrB,QAAQ,MAAM,OAAO,CAEgB;KACxC;AAEF,OAAI,gBAAgB,KAClB,QAAO;IAAE,MAAM;IAAa,KAAK;IAAM;;EAG5C"}
@@ -0,0 +1,12 @@
1
+ import { Plugin } from "vite";
2
+
3
+ //#region src/render/plugins/markdownExtract.d.ts
4
+ /**
5
+ * Vite plugin that pre-processes <Markdown> tags:
6
+ * - Extracts slot content, dedents it, and passes as :content prop
7
+ * - Resolves `src` prop to read file contents at build time
8
+ */
9
+ declare function markdownExtract(): Plugin;
10
+ //#endregion
11
+ export { markdownExtract };
12
+ //# sourceMappingURL=markdownExtract.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdownExtract.d.mts","names":[],"sources":["../../../src/render/plugins/markdownExtract.ts"],"mappings":";;;;;AASA;;;iBAAgB,eAAA,CAAA,GAAmB,MAAA"}
@@ -0,0 +1,50 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { dirname, resolve } from "node:path";
3
+
4
+ //#region src/render/plugins/markdownExtract.ts
5
+ /**
6
+ * Vite plugin that pre-processes <Markdown> tags:
7
+ * - Extracts slot content, dedents it, and passes as :content prop
8
+ * - Resolves `src` prop to read file contents at build time
9
+ */
10
+ function markdownExtract() {
11
+ const re = /<(Markdown|markdown)((?:\s[^>]*?)?)>([\s\S]*?)<\/\1>/g;
12
+ const selfClosingRe = /<(Markdown|markdown)((?:\s[^>]*?\bsrc\s*=\s*"[^"]*"[^>]*?))\/>/g;
13
+ return {
14
+ name: "maizzle:markdown-extract",
15
+ enforce: "pre",
16
+ transform(code, id) {
17
+ if (!id.endsWith(".vue") && !id.endsWith(".md")) return;
18
+ if (!code.includes("Markdown") && !code.includes("markdown")) return;
19
+ let transformed = code;
20
+ transformed = transformed.replace(re, (_match, tag, attrs, content) => {
21
+ if (/(?:^|\s):content\b/.test(attrs) || /v-bind:content\b/.test(attrs)) return _match;
22
+ const stripped = content.replace(/^\n+/, "").replace(/\s+$/, "");
23
+ if (!stripped) return _match;
24
+ const minIndent = stripped.match(/^[ \t]*(?=\S)/gm)?.reduce((min, ws) => Math.min(min, ws.length), Infinity) ?? 0;
25
+ return `<${tag}${attrs} content="${(minIndent > 0 ? stripped.replace(new RegExp(`^[ \\t]{${minIndent}}`, "gm"), "") : stripped).replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;")}" />`;
26
+ });
27
+ transformed = transformed.replace(selfClosingRe, (_match, tag, attrs) => {
28
+ const srcMatch = attrs.match(/\bsrc\s*=\s*"([^"]*)"/);
29
+ if (!srcMatch) return _match;
30
+ const srcPath = srcMatch[1];
31
+ const resolvedPath = resolve(dirname(id), srcPath);
32
+ let fileContent;
33
+ try {
34
+ fileContent = readFileSync(resolvedPath, "utf-8").trim();
35
+ } catch {
36
+ return _match;
37
+ }
38
+ return `<${tag}${attrs.replace(/\s*\bsrc\s*=\s*"[^"]*"/, "")} content="${fileContent.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;")}" />`;
39
+ });
40
+ if (transformed !== code) return {
41
+ code: transformed,
42
+ map: null
43
+ };
44
+ }
45
+ };
46
+ }
47
+
48
+ //#endregion
49
+ export { markdownExtract };
50
+ //# sourceMappingURL=markdownExtract.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdownExtract.mjs","names":[],"sources":["../../../src/render/plugins/markdownExtract.ts"],"sourcesContent":["import { dirname, resolve } from 'node:path'\nimport { readFileSync } from 'node:fs'\nimport type { Plugin } from 'vite'\n\n/**\n * Vite plugin that pre-processes <Markdown> tags:\n * - Extracts slot content, dedents it, and passes as :content prop\n * - Resolves `src` prop to read file contents at build time\n */\nexport function markdownExtract(): Plugin {\n const re = /<(Markdown|markdown)((?:\\s[^>]*?)?)>([\\s\\S]*?)<\\/\\1>/g\n const selfClosingRe = /<(Markdown|markdown)((?:\\s[^>]*?\\bsrc\\s*=\\s*\"[^\"]*\"[^>]*?))\\/>/g\n\n return {\n name: 'maizzle:markdown-extract',\n enforce: 'pre',\n transform(code: string, id: string) {\n if (!id.endsWith('.vue') && !id.endsWith('.md')) return\n if (!code.includes('Markdown') && !code.includes('markdown')) return\n\n let transformed = code\n\n // Handle <Markdown>content</Markdown>\n transformed = transformed.replace(re, (_match, tag, attrs, content) => {\n if (/(?:^|\\s):content\\b/.test(attrs) || /v-bind:content\\b/.test(attrs)) return _match\n\n const stripped = content.replace(/^\\n+/, '').replace(/\\s+$/, '')\n if (!stripped) return _match\n\n const minIndent = stripped.match(/^[ \\t]*(?=\\S)/gm)\n ?.reduce((min: number, ws: string) => Math.min(min, ws.length), Infinity) ?? 0\n\n const dedented = minIndent > 0\n ? stripped.replace(new RegExp(`^[ \\\\t]{${minIndent}}`, 'gm'), '')\n : stripped\n\n const escaped = dedented\n .replace(/&/g, '&amp;')\n .replace(/\"/g, '&quot;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n\n return `<${tag}${attrs} content=\"${escaped}\" />`\n })\n\n // Handle <Markdown src=\"./file.md\" /> — resolve and inline file content\n transformed = transformed.replace(selfClosingRe, (_match, tag, attrs) => {\n const srcMatch = attrs.match(/\\bsrc\\s*=\\s*\"([^\"]*)\"/)\n if (!srcMatch) return _match\n\n const srcPath = srcMatch[1]\n const resolvedPath = resolve(dirname(id), srcPath)\n\n let fileContent: string\n try {\n fileContent = readFileSync(resolvedPath, 'utf-8').trim()\n } catch {\n return _match\n }\n\n // Remove src prop, add content prop\n const cleanAttrs = attrs.replace(/\\s*\\bsrc\\s*=\\s*\"[^\"]*\"/, '')\n const escaped = fileContent\n .replace(/&/g, '&amp;')\n .replace(/\"/g, '&quot;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n\n return `<${tag}${cleanAttrs} content=\"${escaped}\" />`\n })\n\n if (transformed !== code) {\n return { code: transformed, map: null }\n }\n },\n }\n}\n"],"mappings":";;;;;;;;;AASA,SAAgB,kBAA0B;CACxC,MAAM,KAAK;CACX,MAAM,gBAAgB;AAEtB,QAAO;EACL,MAAM;EACN,SAAS;EACT,UAAU,MAAc,IAAY;AAClC,OAAI,CAAC,GAAG,SAAS,OAAO,IAAI,CAAC,GAAG,SAAS,MAAM,CAAE;AACjD,OAAI,CAAC,KAAK,SAAS,WAAW,IAAI,CAAC,KAAK,SAAS,WAAW,CAAE;GAE9D,IAAI,cAAc;AAGlB,iBAAc,YAAY,QAAQ,KAAK,QAAQ,KAAK,OAAO,YAAY;AACrE,QAAI,qBAAqB,KAAK,MAAM,IAAI,mBAAmB,KAAK,MAAM,CAAE,QAAO;IAE/E,MAAM,WAAW,QAAQ,QAAQ,QAAQ,GAAG,CAAC,QAAQ,QAAQ,GAAG;AAChE,QAAI,CAAC,SAAU,QAAO;IAEtB,MAAM,YAAY,SAAS,MAAM,kBAAkB,EAC/C,QAAQ,KAAa,OAAe,KAAK,IAAI,KAAK,GAAG,OAAO,EAAE,SAAS,IAAI;AAY/E,WAAO,IAAI,MAAM,MAAM,aAVN,YAAY,IACzB,SAAS,QAAQ,IAAI,OAAO,WAAW,UAAU,IAAI,KAAK,EAAE,GAAG,GAC/D,UAGD,QAAQ,MAAM,QAAQ,CACtB,QAAQ,MAAM,SAAS,CACvB,QAAQ,MAAM,OAAO,CACrB,QAAQ,MAAM,OAAO,CAEmB;KAC3C;AAGF,iBAAc,YAAY,QAAQ,gBAAgB,QAAQ,KAAK,UAAU;IACvE,MAAM,WAAW,MAAM,MAAM,wBAAwB;AACrD,QAAI,CAAC,SAAU,QAAO;IAEtB,MAAM,UAAU,SAAS;IACzB,MAAM,eAAe,QAAQ,QAAQ,GAAG,EAAE,QAAQ;IAElD,IAAI;AACJ,QAAI;AACF,mBAAc,aAAa,cAAc,QAAQ,CAAC,MAAM;YAClD;AACN,YAAO;;AAWT,WAAO,IAAI,MAPQ,MAAM,QAAQ,0BAA0B,GAAG,CAOlC,YANZ,YACb,QAAQ,MAAM,QAAQ,CACtB,QAAQ,MAAM,SAAS,CACvB,QAAQ,MAAM,OAAO,CACrB,QAAQ,MAAM,OAAO,CAEwB;KAChD;AAEF,OAAI,gBAAgB,KAClB,QAAO;IAAE,MAAM;IAAa,KAAK;IAAM;;EAG5C"}
@@ -0,0 +1,14 @@
1
+ import { Plugin } from "vite";
2
+
3
+ //#region src/render/plugins/rawExtract.d.ts
4
+ /**
5
+ * Vite plugin that extracts raw slot content from <Raw> tags
6
+ * and passes it as a :content prop before Vue compiles the template.
7
+ *
8
+ * Lets users write content (including `{{ }}` interpolation syntax used
9
+ * by ESPs / Handlebars / Liquid) inside <Raw> without Vue parsing it.
10
+ */
11
+ declare function rawExtract(): Plugin;
12
+ //#endregion
13
+ export { rawExtract };
14
+ //# sourceMappingURL=rawExtract.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rawExtract.d.mts","names":[],"sources":["../../../src/render/plugins/rawExtract.ts"],"mappings":";;;;;AASA;;;;;iBAAgB,UAAA,CAAA,GAAc,MAAA"}
@@ -0,0 +1,34 @@
1
+ //#region src/render/plugins/rawExtract.ts
2
+ /**
3
+ * Vite plugin that extracts raw slot content from <Raw> tags
4
+ * and passes it as a :content prop before Vue compiles the template.
5
+ *
6
+ * Lets users write content (including `{{ }}` interpolation syntax used
7
+ * by ESPs / Handlebars / Liquid) inside <Raw> without Vue parsing it.
8
+ */
9
+ function rawExtract() {
10
+ const re = /<(Raw)((?:\s[^>]*?)?)>([\s\S]*?)<\/\1>/g;
11
+ return {
12
+ name: "maizzle:raw-extract",
13
+ enforce: "pre",
14
+ transform(code, id) {
15
+ if (!id.endsWith(".vue") && !id.endsWith(".md")) return;
16
+ if (!code.includes("Raw")) return;
17
+ const transformed = code.replace(re, (_match, tag, attrs, content) => {
18
+ if (/(?:^|\s):content\b/.test(attrs) || /v-bind:content\b/.test(attrs)) return _match;
19
+ const stripped = content.replace(/^\n+/, "").replace(/\s+$/, "");
20
+ if (!stripped) return _match;
21
+ const minIndent = stripped.match(/^[ \t]*(?=\S)/gm)?.reduce((min, ws) => Math.min(min, ws.length), Infinity) ?? 0;
22
+ return `<${tag}${attrs} content="${(minIndent > 0 ? stripped.replace(new RegExp(`^[ \\t]{${minIndent}}`, "gm"), "") : stripped).replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;")}" />`;
23
+ });
24
+ if (transformed !== code) return {
25
+ code: transformed,
26
+ map: null
27
+ };
28
+ }
29
+ };
30
+ }
31
+
32
+ //#endregion
33
+ export { rawExtract };
34
+ //# sourceMappingURL=rawExtract.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rawExtract.mjs","names":[],"sources":["../../../src/render/plugins/rawExtract.ts"],"sourcesContent":["import type { Plugin } from 'vite'\n\n/**\n * Vite plugin that extracts raw slot content from <Raw> tags\n * and passes it as a :content prop before Vue compiles the template.\n *\n * Lets users write content (including `{{ }}` interpolation syntax used\n * by ESPs / Handlebars / Liquid) inside <Raw> without Vue parsing it.\n */\nexport function rawExtract(): Plugin {\n const re = /<(Raw)((?:\\s[^>]*?)?)>([\\s\\S]*?)<\\/\\1>/g\n\n return {\n name: 'maizzle:raw-extract',\n enforce: 'pre',\n transform(code: string, id: string) {\n if (!id.endsWith('.vue') && !id.endsWith('.md')) return\n if (!code.includes('Raw')) return\n\n const transformed = code.replace(re, (_match, tag, attrs, content) => {\n if (/(?:^|\\s):content\\b/.test(attrs) || /v-bind:content\\b/.test(attrs)) return _match\n\n const stripped = content.replace(/^\\n+/, '').replace(/\\s+$/, '')\n if (!stripped) return _match\n\n const minIndent = stripped.match(/^[ \\t]*(?=\\S)/gm)\n ?.reduce((min: number, ws: string) => Math.min(min, ws.length), Infinity) ?? 0\n\n const dedented = minIndent > 0\n ? stripped.replace(new RegExp(`^[ \\\\t]{${minIndent}}`, 'gm'), '')\n : stripped\n\n const escaped = dedented\n .replace(/&/g, '&amp;')\n .replace(/\"/g, '&quot;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n\n return `<${tag}${attrs} content=\"${escaped}\" />`\n })\n\n if (transformed !== code) {\n return { code: transformed, map: null }\n }\n },\n }\n}\n"],"mappings":";;;;;;;;AASA,SAAgB,aAAqB;CACnC,MAAM,KAAK;AAEX,QAAO;EACL,MAAM;EACN,SAAS;EACT,UAAU,MAAc,IAAY;AAClC,OAAI,CAAC,GAAG,SAAS,OAAO,IAAI,CAAC,GAAG,SAAS,MAAM,CAAE;AACjD,OAAI,CAAC,KAAK,SAAS,MAAM,CAAE;GAE3B,MAAM,cAAc,KAAK,QAAQ,KAAK,QAAQ,KAAK,OAAO,YAAY;AACpE,QAAI,qBAAqB,KAAK,MAAM,IAAI,mBAAmB,KAAK,MAAM,CAAE,QAAO;IAE/E,MAAM,WAAW,QAAQ,QAAQ,QAAQ,GAAG,CAAC,QAAQ,QAAQ,GAAG;AAChE,QAAI,CAAC,SAAU,QAAO;IAEtB,MAAM,YAAY,SAAS,MAAM,kBAAkB,EAC/C,QAAQ,KAAa,OAAe,KAAK,IAAI,KAAK,GAAG,OAAO,EAAE,SAAS,IAAI;AAY/E,WAAO,IAAI,MAAM,MAAM,aAVN,YAAY,IACzB,SAAS,QAAQ,IAAI,OAAO,WAAW,UAAU,IAAI,KAAK,EAAE,GAAG,GAC/D,UAGD,QAAQ,MAAM,QAAQ,CACtB,QAAQ,MAAM,SAAS,CACvB,QAAQ,MAAM,OAAO,CACrB,QAAQ,MAAM,OAAO,CAEmB;KAC3C;AAEF,OAAI,gBAAgB,KAClB,QAAO;IAAE,MAAM;IAAa,KAAK;IAAM;;EAG5C"}
@@ -0,0 +1,18 @@
1
+ import { Plugin } from "vite";
2
+
3
+ //#region src/render/plugins/rowSourceLocation.d.ts
4
+ /**
5
+ * Vite plugin that injects `data-maizzle-loc="<file>:<line>"` into every
6
+ * `<Row>`/`<row>` opening tag in user templates.
7
+ *
8
+ * Used by Row.vue's runtime to point the user at the exact line in their
9
+ * template when they misuse Row (e.g. without a Column child).
10
+ *
11
+ * Only transforms inside `<template>` blocks of SFCs (or the entire file
12
+ * for `.md` templates) so `<Row>` mentions in `<script>` blocks (e.g. in
13
+ * string literals or comments) are left untouched.
14
+ */
15
+ declare function rowSourceLocation(): Plugin;
16
+ //#endregion
17
+ export { rowSourceLocation };
18
+ //# sourceMappingURL=rowSourceLocation.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rowSourceLocation.d.mts","names":[],"sources":["../../../src/render/plugins/rowSourceLocation.ts"],"mappings":";;;;;AAaA;;;;;;;;;iBAAgB,iBAAA,CAAA,GAAqB,MAAA"}
@@ -0,0 +1,45 @@
1
+ //#region src/render/plugins/rowSourceLocation.ts
2
+ /**
3
+ * Vite plugin that injects `data-maizzle-loc="<file>:<line>"` into every
4
+ * `<Row>`/`<row>` opening tag in user templates.
5
+ *
6
+ * Used by Row.vue's runtime to point the user at the exact line in their
7
+ * template when they misuse Row (e.g. without a Column child).
8
+ *
9
+ * Only transforms inside `<template>` blocks of SFCs (or the entire file
10
+ * for `.md` templates) so `<Row>` mentions in `<script>` blocks (e.g. in
11
+ * string literals or comments) are left untouched.
12
+ */
13
+ function rowSourceLocation() {
14
+ const tagRe = /(<(?:Row|row))(\b[^>]*?)(\/?>)/g;
15
+ function injectLoc(html, htmlOffset, fullCode, id) {
16
+ return html.replace(tagRe, (match, tag, attrs, end, localOffset) => {
17
+ if (/\bdata-maizzle-loc\s*=/.test(attrs)) return match;
18
+ const absoluteOffset = htmlOffset + localOffset;
19
+ return `${tag}${attrs} data-maizzle-loc="${id}:${fullCode.slice(0, absoluteOffset).split("\n").length}"${end}`;
20
+ });
21
+ }
22
+ return {
23
+ name: "maizzle:row-loc",
24
+ enforce: "pre",
25
+ transform(code, id) {
26
+ const isVue = id.endsWith(".vue");
27
+ const isMd = id.endsWith(".md");
28
+ if (!isVue && !isMd) return;
29
+ if (!code.includes("<Row") && !code.includes("<row")) return;
30
+ let transformed;
31
+ if (isVue) transformed = code.replace(/(<template\b[^>]*>)([\s\S]*?)(<\/template>)/g, (_match, open, inner, close, offset) => {
32
+ return open + injectLoc(inner, offset + open.length, code, id) + close;
33
+ });
34
+ else transformed = injectLoc(code, 0, code, id);
35
+ if (transformed !== code) return {
36
+ code: transformed,
37
+ map: null
38
+ };
39
+ }
40
+ };
41
+ }
42
+
43
+ //#endregion
44
+ export { rowSourceLocation };
45
+ //# sourceMappingURL=rowSourceLocation.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rowSourceLocation.mjs","names":[],"sources":["../../../src/render/plugins/rowSourceLocation.ts"],"sourcesContent":["import type { Plugin } from 'vite'\n\n/**\n * Vite plugin that injects `data-maizzle-loc=\"<file>:<line>\"` into every\n * `<Row>`/`<row>` opening tag in user templates.\n *\n * Used by Row.vue's runtime to point the user at the exact line in their\n * template when they misuse Row (e.g. without a Column child).\n *\n * Only transforms inside `<template>` blocks of SFCs (or the entire file\n * for `.md` templates) so `<Row>` mentions in `<script>` blocks (e.g. in\n * string literals or comments) are left untouched.\n */\nexport function rowSourceLocation(): Plugin {\n const tagRe = /(<(?:Row|row))(\\b[^>]*?)(\\/?>)/g\n\n function injectLoc(html: string, htmlOffset: number, fullCode: string, id: string): string {\n return html.replace(tagRe, (match, tag, attrs, end, localOffset: number) => {\n if (/\\bdata-maizzle-loc\\s*=/.test(attrs)) return match\n const absoluteOffset = htmlOffset + localOffset\n const line = fullCode.slice(0, absoluteOffset).split('\\n').length\n return `${tag}${attrs} data-maizzle-loc=\"${id}:${line}\"${end}`\n })\n }\n\n return {\n name: 'maizzle:row-loc',\n enforce: 'pre',\n transform(code, id) {\n const isVue = id.endsWith('.vue')\n const isMd = id.endsWith('.md')\n if (!isVue && !isMd) return\n if (!code.includes('<Row') && !code.includes('<row')) return\n\n let transformed: string\n\n if (isVue) {\n // Replace inside every <template>...</template> block, leaving\n // <script> and <style> blocks alone.\n const templateBlock = /(<template\\b[^>]*>)([\\s\\S]*?)(<\\/template>)/g\n transformed = code.replace(templateBlock, (_match, open, inner, close, offset: number) => {\n const innerOffset = offset + open.length\n return open + injectLoc(inner, innerOffset, code, id) + close\n })\n } else {\n transformed = injectLoc(code, 0, code, id)\n }\n\n if (transformed !== code) {\n return { code: transformed, map: null }\n }\n },\n }\n}\n"],"mappings":";;;;;;;;;;;;AAaA,SAAgB,oBAA4B;CAC1C,MAAM,QAAQ;CAEd,SAAS,UAAU,MAAc,YAAoB,UAAkB,IAAoB;AACzF,SAAO,KAAK,QAAQ,QAAQ,OAAO,KAAK,OAAO,KAAK,gBAAwB;AAC1E,OAAI,yBAAyB,KAAK,MAAM,CAAE,QAAO;GACjD,MAAM,iBAAiB,aAAa;AAEpC,UAAO,GAAG,MAAM,MAAM,qBAAqB,GAAG,GADjC,SAAS,MAAM,GAAG,eAAe,CAAC,MAAM,KAAK,CAAC,OACL,GAAG;IACzD;;AAGJ,QAAO;EACL,MAAM;EACN,SAAS;EACT,UAAU,MAAM,IAAI;GAClB,MAAM,QAAQ,GAAG,SAAS,OAAO;GACjC,MAAM,OAAO,GAAG,SAAS,MAAM;AAC/B,OAAI,CAAC,SAAS,CAAC,KAAM;AACrB,OAAI,CAAC,KAAK,SAAS,OAAO,IAAI,CAAC,KAAK,SAAS,OAAO,CAAE;GAEtD,IAAI;AAEJ,OAAI,MAIF,eAAc,KAAK,QADG,iDACqB,QAAQ,MAAM,OAAO,OAAO,WAAmB;AAExF,WAAO,OAAO,UAAU,OADJ,SAAS,KAAK,QACU,MAAM,GAAG,GAAG;KACxD;OAEF,eAAc,UAAU,MAAM,GAAG,MAAM,GAAG;AAG5C,OAAI,gBAAgB,KAClB,QAAO;IAAE,MAAM;IAAa,KAAK;IAAM;;EAG5C"}
@@ -80,6 +80,10 @@ const deviceMenuOpen = ref(false)
80
80
  const darkMode = ref(false)
81
81
  const panelWidth = ref(0)
82
82
  const panelHeight = ref(0)
83
+ const iframeWidth = ref<number | null>(null)
84
+ const iframeHeight = ref<number | null>(null)
85
+ const maxIframeWidth = ref(0)
86
+ const maxIframeHeight = ref(0)
83
87
  const isDragging = ref(false)
84
88
  const isFullSize = ref(true)
85
89
  const resetKey = ref(0)
@@ -89,6 +93,31 @@ function selectDevice(device: DevicePreset) {
89
93
  viewMode.value = 'preview'
90
94
  }
91
95
 
96
+ /**
97
+ * Writable proxies for the toolbar's size-indicator inputs. Display falls back
98
+ * to the measured panel size when the iframe dimension is null (the axis
99
+ * hasn't been explicitly set yet — e.g. user only dragged one axis).
100
+ * Setter rejects non-finite/non-positive values and clamps to
101
+ * [200, maxIframeWidth] / [100, maxIframeHeight] so users
102
+ * can't push the drag handles off-screen via the input.
103
+ */
104
+ const widthInput = computed<number>({
105
+ get: () => Math.round(iframeWidth.value ?? panelWidth.value),
106
+ set: (v) => {
107
+ if (typeof v !== 'number' || !Number.isFinite(v) || v <= 0) return
108
+ const max = maxIframeWidth.value || v
109
+ iframeWidth.value = Math.max(200, Math.min(max, Math.round(v)))
110
+ },
111
+ })
112
+ const heightInput = computed<number>({
113
+ get: () => Math.round(iframeHeight.value ?? panelHeight.value),
114
+ set: (v) => {
115
+ if (typeof v !== 'number' || !Number.isFinite(v) || v <= 0) return
116
+ const max = maxIframeHeight.value || v
117
+ iframeHeight.value = Math.max(100, Math.min(max, Math.round(v)))
118
+ },
119
+ })
120
+
92
121
  watch(sidebarOpen, (open) => {
93
122
  localStorage.setItem('maizzle:sidebar', open ? 'open' : 'closed')
94
123
  })
@@ -332,7 +361,23 @@ onUnmounted(() => {
332
361
  v-if="isPreviewRoute && (!isFullSize || selectedDevice) && panelWidth"
333
362
  class="hidden min-[430px]:inline text-xs font-medium tabular-nums text-gray-500 dark:text-gray-400 select-none"
334
363
  >
335
- {{ panelWidth }} <button class="hover:text-gray-700 dark:hover:text-gray-300" @click="selectedDevice = null; isFullSize = true; viewMode = 'preview'; resetKey++">&times;</button> {{ panelHeight }}
364
+ <input
365
+ v-model.number="widthInput"
366
+ type="number"
367
+ min="200"
368
+ :max="maxIframeWidth || undefined"
369
+ aria-label="Preview width"
370
+ class="bg-transparent border-0 outline-none p-0 m-0 text-inherit font-inherit text-center tabular-nums w-[4.5ch] focus:outline-none focus:ring-0 [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none"
371
+ >
372
+ <button class="hover:text-gray-700 dark:hover:text-gray-300" @click="selectedDevice = null; isFullSize = true; viewMode = 'preview'; resetKey++">&times;</button>
373
+ <input
374
+ v-model.number="heightInput"
375
+ type="number"
376
+ min="100"
377
+ :max="maxIframeHeight || undefined"
378
+ aria-label="Preview height"
379
+ class="bg-transparent border-0 outline-none p-0 m-0 text-inherit font-inherit text-center tabular-nums w-[4.5ch] focus:outline-none focus:ring-0 [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none"
380
+ >
336
381
  </span>
337
382
  <div v-if="isPreviewRoute" class="flex items-center gap-1">
338
383
  <DropdownMenu v-model:open="deviceMenuOpen" :modal="false">
@@ -367,7 +412,7 @@ onUnmounted(() => {
367
412
  <!-- Main content -->
368
413
  <div class="flex-1 overflow-hidden">
369
414
  <RouterView v-slot="{ Component }">
370
- <component :is="Component" v-model:view-mode="viewMode" :device="selectedDevice" :reset-key="resetKey" :templates="templates" v-model:panel-width="panelWidth" v-model:panel-height="panelHeight" v-model:is-dragging="isDragging" v-model:is-full-size="isFullSize" v-model:dark-mode="darkMode" @clear-device="selectedDevice = null; isFullSize = false" />
415
+ <component :is="Component" v-model:view-mode="viewMode" :device="selectedDevice" :reset-key="resetKey" :templates="templates" v-model:panel-width="panelWidth" v-model:panel-height="panelHeight" v-model:iframe-width="iframeWidth" v-model:iframe-height="iframeHeight" v-model:max-iframe-width="maxIframeWidth" v-model:max-iframe-height="maxIframeHeight" v-model:is-dragging="isDragging" v-model:is-full-size="isFullSize" v-model:dark-mode="darkMode" @clear-device="selectedDevice = null; isFullSize = false" />
371
416
  </RouterView>
372
417
  </div>
373
418
  </SidebarInset>
@@ -61,12 +61,23 @@ const wrapperEl = ref<HTMLElement>()
61
61
 
62
62
  const panelWidth = defineModel<number>('panelWidth', { default: 0 })
63
63
  const panelHeight = defineModel<number>('panelHeight', { default: 0 })
64
+ /**
65
+ * Container's available area, exposed to the toolbar so size inputs can
66
+ * clamp typed values without paying a layout-recalc cost on
67
+ * every drag tick. Kept in sync via a ResizeObserver.
68
+ */
69
+ const maxIframeWidth = defineModel<number>('maxIframeWidth', { default: 0 })
70
+ const maxIframeHeight = defineModel<number>('maxIframeHeight', { default: 0 })
64
71
  const isDragging = defineModel<boolean>('isDragging', { default: false })
65
72
  const isFullSize = defineModel<boolean>('isFullSize', { default: true })
66
73
 
67
- // Custom resizable: width/height of the iframe wrapper (null = fill container)
68
- const iframeWidth = ref<number | null>(null)
69
- const iframeHeight = ref<number | null>(null)
74
+ /**
75
+ * Custom resizable: width/height of the iframe wrapper (null = fill the
76
+ * container). Exposed as v-models so the toolbar's size indicator
77
+ * can drive these too, alongside the drag handles.
78
+ */
79
+ const iframeWidth = defineModel<number | null>('iframeWidth', { default: null })
80
+ const iframeHeight = defineModel<number | null>('iframeHeight', { default: null })
70
81
  const iframeContentHeight = ref<number | null>(null)
71
82
 
72
83
  function copySource() {
@@ -508,6 +519,7 @@ function updateFullSize() {
508
519
  && (iframeHeight.value === null || iframeHeight.value >= rect.height - gutter - 2)
509
520
  }
510
521
 
522
+
511
523
  function applyDeviceSize(device: Device | null | undefined) {
512
524
  if (!device) {
513
525
  iframeWidth.value = null
@@ -544,6 +556,7 @@ watch(viewMode, async (mode) => {
544
556
  })
545
557
 
546
558
  let observer: ResizeObserver | null = null
559
+ let containerObserver: ResizeObserver | null = null
547
560
 
548
561
  function forwardIframeKeys(iframe: HTMLIFrameElement) {
549
562
  try {
@@ -580,6 +593,21 @@ onMounted(() => {
580
593
  observer.observe(wrapper)
581
594
  }
582
595
 
596
+ const container = containerEl.value
597
+ if (container) {
598
+ const gutter = 40
599
+ const rect = container.getBoundingClientRect()
600
+ maxIframeWidth.value = Math.max(0, Math.round(rect.width - gutter))
601
+ maxIframeHeight.value = Math.max(0, Math.round(rect.height - gutter))
602
+ containerObserver = new ResizeObserver((entries) => {
603
+ for (const entry of entries) {
604
+ maxIframeWidth.value = Math.max(0, Math.round(entry.contentRect.width - gutter))
605
+ maxIframeHeight.value = Math.max(0, Math.round(entry.contentRect.height - gutter))
606
+ }
607
+ })
608
+ containerObserver.observe(container)
609
+ }
610
+
583
611
  const el = iframeEl.value
584
612
  if (el) {
585
613
  el.addEventListener('load', () => forwardIframeKeys(el))
@@ -588,6 +616,7 @@ onMounted(() => {
588
616
 
589
617
  onUnmounted(() => {
590
618
  observer?.disconnect()
619
+ containerObserver?.disconnect()
591
620
  })
592
621
 
593
622
  const bottomPanelOpen = ref(false)
@@ -5,14 +5,14 @@ import { ChildNode } from "domhandler";
5
5
  * Resolve `__MAIZZLE_COLW_{id}__` and `__MAIZZLE_OH_{id}__` placeholders.
6
6
  *
7
7
  * COLW (column width) — emitted by `<Column>` and `<Overlap>`. Walks up to
8
- * the nearest ancestor marked with `data-maizzle-cw` (Container/Section/
9
- * Row/another Column already resolved) and divides by `data-maizzle-cw-count`.
10
- * If `data-maizzle-cw-self` is present, reads from the element's own inlined
11
- * `max-width`/`width`/`min-width` instead of walking up used by `<Overlap>`
12
- * when it has its own width class/inline style.
8
+ * the nearest ancestor marked `data-maizzle-cw` (Container, Section,
9
+ * Row, or another Column already resolved) and divides the source
10
+ * width by `data-maizzle-cw-count`. With `data-maizzle-cw-self`,
11
+ * reads from the element's own inlined max/width/min-width
12
+ * instead used by `<Overlap>` with its own width class.
13
13
  *
14
- * OH (overlap height) — emitted by `<Overlap>`. Reads `max-height`/`height`/
15
- * `min-height` from the element's own inlined style.
14
+ * OH (overlap height) — emitted by `<Overlap>`. Reads max-height, height,
15
+ * or min-height from the element's own inlined style.
16
16
  *
17
17
  * Resolution rules:
18
18
  * - Style placeholders for `min-width`: replaced when resolvable, otherwise
@@ -22,8 +22,8 @@ import { ChildNode } from "domhandler";
22
22
  * - Comment placeholders: same fallback chain.
23
23
  *
24
24
  * Resolved column widths are written back to `data-maizzle-cw` so nested
25
- * rows cascade. All `data-maizzle-cw*` and `data-maizzle-oh-*` attrs are
26
- * stripped at the end.
25
+ * rows cascade. All `data-maizzle-cw*` and `data-maizzle-oh-*` are
26
+ * stripped at the end of the second walk pass.
27
27
  */
28
28
  declare function columnWidth(dom: ChildNode[]): ChildNode[];
29
29
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"columnWidth.d.mts","names":[],"sources":["../../src/transformers/columnWidth.ts"],"mappings":";;;;;AA4FA;;;;;;;;;;;;;;;;;;;;;;iBAAgB,WAAA,CAAY,GAAA,EAAK,SAAA,KAAc,SAAA"}
1
+ {"version":3,"file":"columnWidth.d.mts","names":[],"sources":["../../src/transformers/columnWidth.ts"],"mappings":";;;;;AAwTA;;;;;;;;;;;;;;;;;;;;;;iBAAgB,WAAA,CAAY,GAAA,EAAK,SAAA,KAAc,SAAA"}