@maizzle/framework 6.0.0-rc.22 → 6.0.0-rc.23

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 (210) hide show
  1. package/dist/build.d.ts.map +1 -1
  2. package/dist/build.js +11 -0
  3. package/dist/build.js.map +1 -1
  4. package/dist/components/Heading.vue +1 -1
  5. package/dist/components/Img.vue +60 -10
  6. package/dist/components/Link.vue +1 -1
  7. package/dist/components/Preheader.vue +4 -2
  8. package/dist/components/Tailwind.vue +4 -2
  9. package/dist/components/Text.vue +1 -1
  10. package/dist/components/Vml.vue +354 -0
  11. package/dist/components/utils.d.ts.map +1 -1
  12. package/dist/components/utils.js.map +1 -1
  13. package/dist/composables/defineConfig.js.map +1 -1
  14. package/dist/composables/renderContext.d.ts.map +1 -1
  15. package/dist/composables/renderContext.js.map +1 -1
  16. package/dist/composables/useBaseUrl.d.ts.map +1 -1
  17. package/dist/composables/useBaseUrl.js.map +1 -1
  18. package/dist/composables/useConfig.d.ts.map +1 -1
  19. package/dist/composables/useConfig.js.map +1 -1
  20. package/dist/composables/useCurrentTemplate.d.ts.map +1 -1
  21. package/dist/composables/useCurrentTemplate.js +10 -3
  22. package/dist/composables/useCurrentTemplate.js.map +1 -1
  23. package/dist/composables/useDoctype.d.ts.map +1 -1
  24. package/dist/composables/useDoctype.js.map +1 -1
  25. package/dist/composables/useEvent.js.map +1 -1
  26. package/dist/composables/useFont.d.ts.map +1 -1
  27. package/dist/composables/useFont.js.map +1 -1
  28. package/dist/composables/useOutlookFallback.d.ts.map +1 -1
  29. package/dist/composables/useOutlookFallback.js.map +1 -1
  30. package/dist/composables/usePlaintext.d.ts.map +1 -1
  31. package/dist/composables/usePlaintext.js.map +1 -1
  32. package/dist/composables/usePreheader.d.ts.map +1 -1
  33. package/dist/composables/usePreheader.js.map +1 -1
  34. package/dist/composables/useTransformers.d.ts.map +1 -1
  35. package/dist/composables/useTransformers.js.map +1 -1
  36. package/dist/composables/useUrlQuery.d.ts.map +1 -1
  37. package/dist/composables/useUrlQuery.js.map +1 -1
  38. package/dist/config/defaults.d.ts.map +1 -1
  39. package/dist/config/defaults.js.map +1 -1
  40. package/dist/config/index.js +12 -0
  41. package/dist/config/index.js.map +1 -1
  42. package/dist/events/index.d.ts +5 -0
  43. package/dist/events/index.d.ts.map +1 -1
  44. package/dist/events/index.js +5 -0
  45. package/dist/events/index.js.map +1 -1
  46. package/dist/plaintext.d.ts.map +1 -1
  47. package/dist/plaintext.js.map +1 -1
  48. package/dist/plugin.js.map +1 -1
  49. package/dist/plugins/postcss/mergeMediaQueries.d.ts.map +1 -1
  50. package/dist/plugins/postcss/mergeMediaQueries.js.map +1 -1
  51. package/dist/plugins/postcss/pruneVars.d.ts.map +1 -1
  52. package/dist/plugins/postcss/pruneVars.js.map +1 -1
  53. package/dist/plugins/postcss/quoteFontFamilies.d.ts.map +1 -1
  54. package/dist/plugins/postcss/quoteFontFamilies.js.map +1 -1
  55. package/dist/plugins/postcss/removeDeclarations.d.ts.map +1 -1
  56. package/dist/plugins/postcss/removeDeclarations.js.map +1 -1
  57. package/dist/plugins/postcss/resolveMaizzleImports.d.ts.map +1 -1
  58. package/dist/plugins/postcss/resolveMaizzleImports.js.map +1 -1
  59. package/dist/plugins/postcss/resolveProps.d.ts.map +1 -1
  60. package/dist/plugins/postcss/resolveProps.js +14 -0
  61. package/dist/plugins/postcss/resolveProps.js.map +1 -1
  62. package/dist/plugins/postcss/tailwindCleanup.d.ts.map +1 -1
  63. package/dist/plugins/postcss/tailwindCleanup.js.map +1 -1
  64. package/dist/prepare.d.ts.map +1 -1
  65. package/dist/prepare.js.map +1 -1
  66. package/dist/render/active.d.ts.map +1 -1
  67. package/dist/render/active.js.map +1 -1
  68. package/dist/render/createRenderer.d.ts.map +1 -1
  69. package/dist/render/createRenderer.js +89 -1
  70. package/dist/render/createRenderer.js.map +1 -1
  71. package/dist/render/index.d.ts.map +1 -1
  72. package/dist/render/index.js +6 -0
  73. package/dist/render/index.js.map +1 -1
  74. package/dist/render/injectFonts.js.map +1 -1
  75. package/dist/render/plugins/codeBlockExtract.d.ts.map +1 -1
  76. package/dist/render/plugins/codeBlockExtract.js +4 -0
  77. package/dist/render/plugins/codeBlockExtract.js.map +1 -1
  78. package/dist/render/plugins/markdownExtract.d.ts.map +1 -1
  79. package/dist/render/plugins/markdownExtract.js.map +1 -1
  80. package/dist/render/plugins/rawExtract.d.ts.map +1 -1
  81. package/dist/render/plugins/rawExtract.js.map +1 -1
  82. package/dist/render/plugins/rowSourceLocation.d.ts.map +1 -1
  83. package/dist/render/plugins/rowSourceLocation.js.map +1 -1
  84. package/dist/serve.d.ts.map +1 -1
  85. package/dist/serve.js +48 -15
  86. package/dist/serve.js.map +1 -1
  87. package/dist/server/compatibility.d.ts.map +1 -1
  88. package/dist/server/compatibility.js +48 -0
  89. package/dist/server/compatibility.js.map +1 -1
  90. package/dist/server/email.js.map +1 -1
  91. package/dist/server/linter.js +6 -0
  92. package/dist/server/linter.js.map +1 -1
  93. package/dist/server/sfc-utils.d.ts.map +1 -1
  94. package/dist/server/sfc-utils.js.map +1 -1
  95. package/dist/server/ui/App.vue +17 -16
  96. package/dist/server/ui/components/Markdown.vue +17 -0
  97. package/dist/server/ui/components/SidebarClose.vue +1 -1
  98. package/dist/server/ui/components/ui/checkbox/Checkbox.vue +1 -1
  99. package/dist/server/ui/components/ui/command/CommandInput.vue +2 -2
  100. package/dist/server/ui/components/ui/dialog/DialogContent.vue +1 -1
  101. package/dist/server/ui/components/ui/dialog/DialogScrollContent.vue +1 -1
  102. package/dist/server/ui/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue +1 -1
  103. package/dist/server/ui/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +1 -1
  104. package/dist/server/ui/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue +1 -1
  105. package/dist/server/ui/components/ui/sheet/SheetContent.vue +1 -1
  106. package/dist/server/ui/components/ui/sidebar/SidebarTrigger.vue +1 -1
  107. package/dist/server/ui/components/ui/tags-input/TagsInputItemDelete.vue +1 -1
  108. package/dist/server/ui/lib/emulated-dark-mode.ts +25 -10
  109. package/dist/server/ui/pages/Home.vue +1 -1
  110. package/dist/server/ui/pages/Preview.vue +32 -18
  111. package/dist/tests/render/_helpers.js.map +1 -1
  112. package/dist/transformers/addAttributes.d.ts.map +1 -1
  113. package/dist/transformers/addAttributes.js.map +1 -1
  114. package/dist/transformers/attributeToStyle.d.ts.map +1 -1
  115. package/dist/transformers/attributeToStyle.js.map +1 -1
  116. package/dist/transformers/base.d.ts.map +1 -1
  117. package/dist/transformers/base.js +4 -0
  118. package/dist/transformers/base.js.map +1 -1
  119. package/dist/transformers/columnWidth.d.ts.map +1 -1
  120. package/dist/transformers/columnWidth.js.map +1 -1
  121. package/dist/transformers/entities.d.ts.map +1 -1
  122. package/dist/transformers/entities.js.map +1 -1
  123. package/dist/transformers/filters/defaults.d.ts.map +1 -1
  124. package/dist/transformers/filters/defaults.js.map +1 -1
  125. package/dist/transformers/filters/index.d.ts.map +1 -1
  126. package/dist/transformers/filters/index.js.map +1 -1
  127. package/dist/transformers/format.d.ts.map +1 -1
  128. package/dist/transformers/format.js.map +1 -1
  129. package/dist/transformers/index.d.ts.map +1 -1
  130. package/dist/transformers/index.js +26 -0
  131. package/dist/transformers/index.js.map +1 -1
  132. package/dist/transformers/inlineCss.d.ts.map +1 -1
  133. package/dist/transformers/inlineCss.js +25 -2
  134. package/dist/transformers/inlineCss.js.map +1 -1
  135. package/dist/transformers/inlineLink.d.ts.map +1 -1
  136. package/dist/transformers/inlineLink.js.map +1 -1
  137. package/dist/transformers/minify.d.ts.map +1 -1
  138. package/dist/transformers/minify.js.map +1 -1
  139. package/dist/transformers/minifyCodeInline.d.ts.map +1 -1
  140. package/dist/transformers/minifyCodeInline.js.map +1 -1
  141. package/dist/transformers/msoPlaceholders.d.ts.map +1 -1
  142. package/dist/transformers/msoPlaceholders.js.map +1 -1
  143. package/dist/transformers/purgeCss.d.ts.map +1 -1
  144. package/dist/transformers/purgeCss.js +29 -3
  145. package/dist/transformers/purgeCss.js.map +1 -1
  146. package/dist/transformers/removeAttributes.d.ts.map +1 -1
  147. package/dist/transformers/removeAttributes.js.map +1 -1
  148. package/dist/transformers/replaceStrings.d.ts.map +1 -1
  149. package/dist/transformers/replaceStrings.js.map +1 -1
  150. package/dist/transformers/safeSelectors.d.ts.map +1 -1
  151. package/dist/transformers/safeSelectors.js +13 -1
  152. package/dist/transformers/safeSelectors.js.map +1 -1
  153. package/dist/transformers/shorthandCss.d.ts.map +1 -1
  154. package/dist/transformers/shorthandCss.js.map +1 -1
  155. package/dist/transformers/sixHex.d.ts.map +1 -1
  156. package/dist/transformers/sixHex.js.map +1 -1
  157. package/dist/transformers/tailwindComponent.js +9 -0
  158. package/dist/transformers/tailwindComponent.js.map +1 -1
  159. package/dist/transformers/tailwindcss.d.ts.map +1 -1
  160. package/dist/transformers/tailwindcss.js +22 -0
  161. package/dist/transformers/tailwindcss.js.map +1 -1
  162. package/dist/transformers/urlQuery.d.ts.map +1 -1
  163. package/dist/transformers/urlQuery.js.map +1 -1
  164. package/dist/types/config.d.ts +4 -8
  165. package/dist/types/config.d.ts.map +1 -1
  166. package/dist/types/index.d.ts +1 -1
  167. package/dist/utils/ast/parser.d.ts.map +1 -1
  168. package/dist/utils/ast/parser.js.map +1 -1
  169. package/dist/utils/ast/serializer.d.ts.map +1 -1
  170. package/dist/utils/ast/serializer.js.map +1 -1
  171. package/dist/utils/ast/walker.d.ts.map +1 -1
  172. package/dist/utils/ast/walker.js.map +1 -1
  173. package/dist/utils/compileTailwindCss.d.ts.map +1 -1
  174. package/dist/utils/compileTailwindCss.js.map +1 -1
  175. package/dist/utils/componentSources.d.ts.map +1 -1
  176. package/dist/utils/componentSources.js.map +1 -1
  177. package/dist/utils/cssBox.d.ts.map +1 -1
  178. package/dist/utils/cssBox.js.map +1 -1
  179. package/dist/utils/decodeStyleEntities.d.ts.map +1 -1
  180. package/dist/utils/decodeStyleEntities.js.map +1 -1
  181. package/dist/utils/detect.d.ts.map +1 -1
  182. package/dist/utils/detect.js.map +1 -1
  183. package/dist/utils/output-markers.d.ts.map +1 -1
  184. package/dist/utils/output-markers.js.map +1 -1
  185. package/dist/utils/url.d.ts.map +1 -1
  186. package/dist/utils/url.js.map +1 -1
  187. package/dist/utils/watchPaths.js.map +1 -1
  188. package/node_modules/@clack/core/CHANGELOG.md +6 -0
  189. package/node_modules/@clack/core/dist/index.d.mts +1 -1
  190. package/node_modules/@clack/core/dist/index.mjs +8 -8
  191. package/node_modules/@clack/core/dist/index.mjs.map +1 -1
  192. package/node_modules/@clack/core/package.json +1 -1
  193. package/node_modules/@clack/prompts/CHANGELOG.md +13 -0
  194. package/node_modules/@clack/prompts/README.md +2 -2
  195. package/node_modules/@clack/prompts/dist/index.d.mts +98 -0
  196. package/node_modules/@clack/prompts/dist/index.mjs +122 -121
  197. package/node_modules/@clack/prompts/dist/index.mjs.map +1 -1
  198. package/node_modules/@clack/prompts/package.json +2 -2
  199. package/node_modules/fast-wrap-ansi/lib/main.js +0 -1
  200. package/node_modules/fast-wrap-ansi/package.json +10 -10
  201. package/node_modules/maizzle/dist/commands/make/config.mjs +7 -6
  202. package/node_modules/maizzle/dist/commands/new.mjs +15 -84
  203. package/node_modules/maizzle/package.json +2 -2
  204. package/node_modules/tinyexec/README.md +8 -0
  205. package/node_modules/tinyexec/dist/main.d.mts +16 -1
  206. package/node_modules/tinyexec/dist/main.mjs +163 -457
  207. package/node_modules/tinyexec/package.json +12 -14
  208. package/package.json +3 -4
  209. package/node_modules/fast-wrap-ansi/lib/main.js.map +0 -1
  210. package/node_modules/tinyexec/dist/LICENSES.txt +0 -83
@@ -1 +1 @@
1
- {"version":3,"file":"columnWidth.js","names":[],"sources":["../../src/transformers/columnWidth.ts"],"sourcesContent":["import postcss, { type Root, type Declaration } from 'postcss'\nimport safeParser from 'postcss-safe-parser'\nimport { walk } from '../utils/ast/index.ts'\nimport { horizontalBorderPx, horizontalPaddingPx, lengthToPx } from '../utils/cssBox.ts'\nimport type { ChildNode, Element, ParentNode } from 'domhandler'\n\nconst RE_PERCENT = /^[\\d.]+%$/\n\n/**\n * Stringify decls into a `; `-joined inline-style attribute. PostCSS raws\n * preserve the original source spacing, which mixes poorly with\n * the fresh decls we inject — plain join keeps output uniform.\n */\nfunction serializeStyle(root: Root): string {\n const parts: string[] = []\n root.walkDecls((d) => {\n parts.push(`${d.prop}: ${d.value}${d.important ? ' !important' : ''}`)\n })\n return parts.join('; ')\n}\n\nfunction firstDeclValue(root: Root, prop: string): string | undefined {\n let found: string | undefined\n root.walkDecls(prop, (d) => {\n found = d.value\n return false\n })\n return found\n}\n\n/**\n * Find the user-set `min-width:` value on a column. Juice keeps both ours\n * and the one inlined from a class like `min-w-1/3` — we skip any\n * min-width whose value still contains our placeholder token,\n * returning the first remaining user value, or null.\n */\nfunction findUserMinWidth(root: Root): string | null {\n let userVal: string | null = null\n root.walkDecls('min-width', (d) => {\n if (!d.value.includes('__MAIZZLE_COLW_')) {\n userVal = d.value\n return false\n }\n })\n return userVal\n}\n\nfunction resolveLength(value: string): string | null {\n const trimmed = value.trim()\n if (RE_PERCENT.test(trimmed)) return trimmed\n const m = trimmed.match(/^([\\d.]+)(px|rem|em|pt)?$/i)\n if (!m) return null\n const n = parseFloat(m[1])\n switch ((m[2] || 'px').toLowerCase()) {\n case 'px': return `${Math.round(n)}px`\n case 'rem':\n case 'em': return `${Math.round(n * 16)}px`\n case 'pt': return `${Math.round(n * 1.333)}px`\n default: return null\n }\n}\n\nfunction divideLength(value: string, divisor: number): string | null {\n const m = value.match(/^([\\d.]+)(px|%)$/)\n if (!m || divisor < 1) return null\n const n = parseFloat(m[1])\n return `${parseFloat((n / divisor).toFixed(2))}${m[2]}`\n}\n\nfunction subtractInsetPx(width: string, insetPx: number): string {\n if (insetPx <= 0) return width\n const m = width.match(/^([\\d.]+)(px|%)$/)\n if (!m) return width\n // Don't subtract px from percentage widths — units don't match.\n if (m[2] === '%') return width\n const n = parseFloat(m[1]) - insetPx\n return `${Math.max(0, Math.round(n))}px`\n}\n\n/**\n * Return the smaller of two px lengths. Clamps our count-based min-width\n * down to the user's `max-width:` so the cap is never silently\n * violated when our computed min would exceed the user's max.\n */\nfunction minPxLength(a: string, b: string): string {\n const am = a.match(/^([\\d.]+)px$/)\n const bm = b.match(/^([\\d.]+)px$/)\n if (!am || !bm) return a\n return parseFloat(am[1]) < parseFloat(bm[1]) ? a : b\n}\n\nfunction depth(node: ChildNode): number {\n let d = 0\n let cur: ParentNode | null = node.parent\n while (cur) {\n d++\n cur = (cur as any).parent ?? null\n }\n return d\n}\n\nfunction readWidthFromRoot(root: Root): string | null {\n const raw = firstDeclValue(root, 'max-width')\n ?? firstDeclValue(root, 'width')\n ?? firstDeclValue(root, 'min-width')\n return raw ? resolveLength(raw) : null\n}\n\nfunction readHeightFromRoot(root: Root): string | null {\n const raw = firstDeclValue(root, 'max-height')\n ?? firstDeclValue(root, 'height')\n ?? firstDeclValue(root, 'min-height')\n return raw ? resolveLength(raw) : null\n}\n\nfunction readWidthSource(el: Element, root: Root | null): string | null {\n const explicit = el.attribs?.['data-maizzle-cw']\n if (explicit) {\n const r = resolveLength(explicit)\n if (r) return r\n }\n return root ? readWidthFromRoot(root) : null\n}\n\n/**\n * Convert a user-supplied length to absolute px against the column's source\n * width (post-inset). Percentages multiply against the source while\n * absolute units pass through `resolveLength`. Returns null when\n * the value or source can't be expressed in px.\n */\nfunction userValueToPx(rawValue: string, sourcePx: string | null): string | null {\n const trimmed = rawValue.trim()\n\n const absMatch = trimmed.match(/^([\\d.]+)(px|rem|em|pt)$/i)\n if (absMatch) return resolveLength(trimmed)\n\n const pctMatch = trimmed.match(/^([\\d.]+)%$/)\n if (!pctMatch || !sourcePx) return null\n const sourceMatch = sourcePx.match(/^([\\d.]+)px$/)\n if (!sourceMatch) return null\n const pct = parseFloat(pctMatch[1])\n const src = parseFloat(sourceMatch[1])\n return `${Math.round((pct / 100) * src)}px`\n}\n\n/**\n * Resolve `__MAIZZLE_COLW_{id}__` and `__MAIZZLE_OH_{id}__` placeholders.\n *\n * COLW (column width) — emitted by `<Column>` and `<Overlap>`. Walks up to\n * the nearest ancestor marked `data-maizzle-cw` (Container, Section,\n * Row, or another Column already resolved) and divides the source\n * width by `data-maizzle-cw-count`. With `data-maizzle-cw-self`,\n * reads from the element's own inlined max/width/min-width\n * instead — used by `<Overlap>` with its own width class.\n *\n * OH (overlap height) — emitted by `<Overlap>`. Reads max-height, height,\n * or min-height from the element's own inlined style.\n *\n * Resolution rules:\n * - Style placeholders for `min-width`: replaced when resolvable, otherwise\n * the entire `min-width` declaration is stripped.\n * - Other style placeholders (Overlap td `width`, etc.): replaced when\n * resolvable, otherwise replaced with the count-based fallback or `100%`.\n * - Comment placeholders: same fallback chain.\n *\n * Resolved column widths are written back to `data-maizzle-cw` so nested\n * rows cascade. All `data-maizzle-cw*` and `data-maizzle-oh-*` are\n * stripped at the end of the second walk pass.\n */\nexport function columnWidth(dom: ChildNode[]): ChildNode[] {\n /**\n * Cache parsed style ASTs for this columnWidth invocation. The walk-up\n * loop visits the same Section/Container once per column of a Row,\n * so without caching each column re-parses every ancestor's style.\n * Cache is function-local — no cross-build leak via the WeakMap.\n */\n const styleCache = new WeakMap<Element, Root>()\n const parseElStyle = (el: Element): Root => {\n const cached = styleCache.get(el)\n if (cached) return cached\n const style = el.attribs?.style ?? ''\n const root = style ? safeParser(style) : postcss.root()\n styleCache.set(el, root)\n return root\n }\n\n const columns: { el: Element; id: string; count: number; d: number; self: boolean }[] = []\n const heightTargets: { el: Element; id: string }[] = []\n\n walk(dom, (node) => {\n const el = node as Element\n if (!el.attribs) return\n\n const id = el.attribs['data-maizzle-cw-id']\n if (id) {\n const count = parseInt(el.attribs['data-maizzle-cw-count'] || '1', 10)\n const self = 'data-maizzle-cw-self' in el.attribs\n columns.push({ el, id, count, d: depth(node), self })\n }\n\n const ohId = el.attribs['data-maizzle-oh-id']\n if (ohId) heightTargets.push({ el, id: ohId })\n })\n\n columns.sort((a, b) => a.d - b.d)\n\n const widthResolutions = new Map<string, string>()\n const widthFallbacks = new Map<string, string>()\n /**\n * Column ids whose absolute user `width:` was promoted to `min-width:`\n * — the original `width:` declaration must be stripped from the\n * column's style (otherwise it'd compete with the min-width).\n */\n const stripWidth = new Set<string>()\n /**\n * Column ids where the user wrote a percentage `width:` (e.g. `w-1/2`) —\n * explicit opt-out of px-based stacking. Keep the user's `width: X%`\n * and drop our `min-width:` placeholder so the column stays at\n * that percentage of its parent forever and never stacks.\n */\n const dropMinWidth = new Set<string>()\n /**\n * Column ids where the user wrote their own `min-width:` (via `min-w-1/3`).\n * Juice inlines theirs after ours, so two `min-width:` decls land in\n * the style — we strip the user's after using its value as the\n * column's resolution, leaving our placeholder as last word.\n */\n const stripUserMinWidth = new Set<string>()\n /**\n * Column ids where the user already supplied a `max-width:` of their own.\n * Our default `max-width: 100%` would just be shadowed by it via\n * last-wins and bloat the style — so we skip emitting it.\n */\n const userHasMaxWidth = new Set<string>()\n /**\n * Horizontal padding+border (px) of each column whose width was auto-\n * derived from count-based math. The column's own border/padding eats\n * its content box under content-box sizing, so the emitted `width:`\n * must be the slice MINUS this inset; otherwise two bordered cols\n * sum past the container and wrap. Only populated for auto paths\n * — user-explicit `width:`/`min-width:`/`max-width:`-wins paths\n * leave the value alone so the user's number stays the user's.\n */\n const autoColumnInsets = new Map<string, number>()\n /**\n * Extra inline-style decls that get stamped onto each column's MSO `<td>`\n * via its COLTDX placeholder. Carries `background-color` (so Word\n * paints the cell behind any padding area or whitespace, not just\n * the div) and hoisted `padding*` decls (when no border is present\n * — without one Word silently drops div padding, with one a td\n * copy would double-pad). The div keeps both for modern clients\n * since the MSO td is conditional-comment-only.\n */\n const tdExtras = new Map<string, string>()\n /**\n * MSO td width override for hoisted columns. With padding on the td and\n * CSS content-box sizing for table cells, td_outer = width + 2*pad,\n * so we set td width to the slot MINUS 2*horizontal-padding to keep\n * the cell at its outer slot. Skipped when the slot is a %.\n */\n const hoistedTdWidths = new Map<string, string>()\n\n for (const { id, count } of columns) {\n widthFallbacks.set(id, `${Math.round(100 / Math.max(count, 1))}%`)\n }\n\n /**\n * Sibling-aware redistribution.\n *\n * Without this, every auto column slices its source `/ totalCount`,\n * ignoring siblings that already declared a fixed `w-5` (20px) or\n * `w-1/3` (%). Three auto cols next to a `w-5` would each take\n * `source/3` and overflow the row by 20px.\n *\n * Pre-pass: classify each col against its inlined style.\n * - explicit-px: `width:` or `min-width:` resolving to px. The col\n * occupies (px value + own horizontal padding +\n * own horizontal border) of the source.\n * - explicit-pct: `width:` or `min-width:` resolving to %. The col\n * occupies (pct * source / 100 + own inset).\n * - auto: everything else. Shares the remainder with other\n * auto siblings.\n *\n * Group cols by their immediate `el.parent` (the Row div). For each\n * group, sum the explicit contributions and count the autos. The\n * countBased path below uses these to redistribute leftover space\n * instead of dividing the raw source by total count.\n *\n * `data-maizzle-cw-self` cols (Overlap) are excluded — they own their\n * source independently and don't share a parent's slot with cols.\n */\n interface ColClassification {\n kind: 'auto' | 'explicit'\n pxOuter: number\n pctOuter: number\n }\n interface GroupInfo {\n explicitPxOuterSum: number\n explicitPctOuterSum: number\n autoCount: number\n }\n const groupInfos = new Map<ParentNode, GroupInfo>()\n\n for (const c of columns) {\n if (c.self) continue\n const ownRoot = parseElStyle(c.el)\n const ownInset = horizontalPaddingPx(ownRoot) + horizontalBorderPx(ownRoot)\n\n let cls: ColClassification = { kind: 'auto', pxOuter: 0, pctOuter: 0 }\n const userVal = findUserMinWidth(ownRoot) ?? firstDeclValue(ownRoot, 'width')\n if (userVal) {\n const resolved = resolveLength(userVal)\n if (resolved?.endsWith('px')) {\n const px = lengthToPx(resolved)\n if (px != null) cls = { kind: 'explicit', pxOuter: px + ownInset, pctOuter: 0 }\n }\n else if (resolved?.endsWith('%')) {\n cls = { kind: 'explicit', pxOuter: ownInset, pctOuter: parseFloat(resolved) }\n }\n }\n\n const parent = c.el.parent as ParentNode | null\n if (!parent) continue\n let info = groupInfos.get(parent)\n if (!info) {\n info = { explicitPxOuterSum: 0, explicitPctOuterSum: 0, autoCount: 0 }\n groupInfos.set(parent, info)\n }\n info.explicitPxOuterSum += cls.pxOuter\n info.explicitPctOuterSum += cls.pctOuter\n if (cls.kind === 'auto') info.autoCount++\n }\n\n for (const { el, id, count, self } of columns) {\n const ownRoot = parseElStyle(el)\n\n let sourceWidth: string | null = null\n let accumulatedInsetPx = 0\n\n if (self) {\n sourceWidth = readWidthFromRoot(ownRoot)\n accumulatedInsetPx = horizontalPaddingPx(ownRoot) + horizontalBorderPx(ownRoot)\n }\n else {\n /**\n * Walk up through every ancestor with attribs, accumulating horizontal\n * padding+border along the way (including the source). Stop at the\n * first `data-maizzle-cw` ancestor whose width is resolvable.\n * Markers without a resolvable width (Row emitted empty after\n * Tailwind dropped a bogus class) shouldn't shadow a real\n * width on a higher ancestor like `<Container>`.\n *\n * With CSS content-box this is technically generous toward the\n * source's own padding/border, but matches user expectations\n * when they put `px-9` or `border-2` on a wrapper.\n */\n let cur: ParentNode | null = el.parent\n while (cur) {\n const parentEl = cur as Element\n if (parentEl.attribs) {\n let pRoot: Root | null = null\n if (parentEl.attribs.style) {\n pRoot = parseElStyle(parentEl)\n accumulatedInsetPx += horizontalPaddingPx(pRoot) + horizontalBorderPx(pRoot)\n }\n if ('data-maizzle-cw' in parentEl.attribs) {\n const w = readWidthSource(parentEl, pRoot)\n if (w) {\n sourceWidth = w\n break\n }\n }\n }\n cur = (cur as any).parent ?? null\n }\n }\n\n const adjusted = sourceWidth ? subtractInsetPx(sourceWidth, accumulatedInsetPx) : null\n let countBased = adjusted ? divideLength(adjusted, count) : null\n\n /**\n * Sibling-aware redistribution kicks in only when the auto col has at\n * least one explicit-width sibling and the source is in px (can't\n * mix px subtraction with a % source). When that's true, the auto\n * col's share becomes `(source − sum_explicit_px − pct% of source)\n * / autoCount`, replacing the naive `source/totalCount`.\n */\n if (!self && adjusted?.endsWith('px') && countBased && el.parent) {\n const group = groupInfos.get(el.parent as ParentNode)\n if (group && group.autoCount > 0\n && (group.explicitPxOuterSum > 0 || group.explicitPctOuterSum > 0)) {\n const adjPx = lengthToPx(adjusted)\n if (adjPx != null) {\n const remaining = adjPx - group.explicitPxOuterSum - (adjPx * group.explicitPctOuterSum / 100)\n /**\n * Floor-to-2-decimals on the per-auto share. Two reasons:\n *\n * 1. Matches `divideLength`'s precision so the auto path and\n * group-aware path emit consistent units.\n * 2. Rounding up (e.g. 536/3 = 178.6̄ → round to 179) pushes\n * the sum past the slot — 3×179 + 2×20 = 577 > 576 →\n * inline-block wraps and the row stacks. Flooring at 2\n * decimals keeps the sum ≤ slot with at most ~0.0(autoCount)\n * px unused, which is sub-pixel and invisible.\n */\n const share = Math.max(0, Math.floor((remaining * 100) / group.autoCount) / 100)\n countBased = `${share}px`\n }\n }\n }\n\n /**\n * Four user-override paths, decided by which CSS property the user\n * actually wrote:\n *\n * - `min-width: X` → user's value wins. Convert to px against\n * the source (if %), use as the column's\n * resolution, and strip the user's min-width\n * declaration so our placeholder substitution\n * remains the last `min-width:` in style.\n * - `width: X%` → opt-out of px stacking. Keep `width:` in\n * style, drop our `min-width:` placeholder.\n * Cols stay at X% of parent forever, never stack.\n * - `width: Xpx` (or rem/em/pt) → fixed pixel column. Promote to\n * `min-width:`, strip the original `width:` so\n * it doesn't compete.\n * - `max-width: X` → CSS cap. Keep the `max-width:` declaration;\n * clamp our count-based min-width *down* to\n * the user's max-width when our min would\n * otherwise violate it.\n */\n const userMinRaw = findUserMinWidth(ownRoot)\n const widthRaw = firstDeclValue(ownRoot, 'width')\n const maxRaw = firstDeclValue(ownRoot, 'max-width')\n\n if (userMinRaw) {\n const minPx = userValueToPx(userMinRaw, adjusted) ?? resolveLength(userMinRaw)\n if (minPx) {\n widthResolutions.set(id, minPx)\n el.attribs['data-maizzle-cw'] = minPx\n stripUserMinWidth.add(id)\n continue\n }\n }\n\n if (widthRaw) {\n const widthVal = resolveLength(widthRaw)\n if (widthVal?.endsWith('%')) {\n widthResolutions.set(id, widthVal)\n el.attribs['data-maizzle-cw'] = widthVal\n dropMinWidth.add(id)\n continue\n }\n if (widthVal) {\n widthResolutions.set(id, widthVal)\n el.attribs['data-maizzle-cw'] = widthVal\n stripWidth.add(id)\n continue\n }\n }\n\n if (maxRaw && countBased) {\n const maxPx = userValueToPx(maxRaw, adjusted)\n if (maxPx) {\n const cappedMin = countBased.endsWith('px')\n ? minPxLength(countBased, maxPx)\n : maxPx\n widthResolutions.set(id, cappedMin)\n el.attribs['data-maizzle-cw'] = cappedMin\n userHasMaxWidth.add(id)\n if (cappedMin === countBased) {\n const ownInset = horizontalPaddingPx(ownRoot) + horizontalBorderPx(ownRoot)\n if (ownInset > 0) autoColumnInsets.set(id, ownInset)\n }\n continue\n }\n }\n\n if (countBased) {\n widthResolutions.set(id, countBased)\n el.attribs['data-maizzle-cw'] = countBased\n const ownPaddingPx = horizontalPaddingPx(ownRoot)\n const ownBorderPx = horizontalBorderPx(ownRoot)\n const ownInset = ownPaddingPx + ownBorderPx\n if (ownInset > 0) autoColumnInsets.set(id, ownInset)\n\n /**\n * Build the MSO td's \"extras\" string — decls that need to live on\n * the td in addition to width + vertical-align. Two contributors:\n *\n * - `background-color` (always, when present) — Word renders the\n * div bg inside the cell, but anything outside the div (the\n * td's padding area when hoisted, or any whitespace gap)\n * would show the parent's bg instead of the column's. Painting\n * the td matches the user's intent.\n *\n * - `padding*` (hoisted only when no horizontal border) — Word\n * drops div padding without a stabilizing border, so the td\n * has to carry it. With a border, Word renders div padding\n * and a td copy would double-pad. Skip when the slot is `%`:\n * td width math can't subtract px padding from a percentage.\n */\n const extras: string[] = []\n let bgColor: string | undefined\n ownRoot.walkDecls('background-color', (d) => { bgColor = d.value })\n if (bgColor) extras.push(`background-color: ${bgColor}`)\n\n if (ownPaddingPx > 0 && ownBorderPx === 0 && countBased.endsWith('px')) {\n ownRoot.walkDecls((d) => {\n if (/^padding(-|$)/.test(d.prop)) extras.push(`${d.prop}: ${d.value}`)\n })\n hoistedTdWidths.set(id, subtractInsetPx(countBased, ownPaddingPx))\n }\n\n if (extras.length) tdExtras.set(id, extras.join('; '))\n }\n }\n\n const heightResolutions = new Map<string, string>()\n for (const { el, id } of heightTargets) {\n if (!el.attribs?.style) continue\n const h = readHeightFromRoot(parseElStyle(el))\n if (h) heightResolutions.set(id, h)\n }\n\n walk(dom, (node) => {\n if (node.type === 'comment') {\n const data = (node as any).data as string\n if (!data) return\n const hasCW = data.includes('__MAIZZLE_COLW_')\n const hasOH = data.includes('__MAIZZLE_OH_')\n const hasTDX = data.includes('__MAIZZLE_COLTDX_')\n if (!hasCW && !hasOH && !hasTDX) return\n ;(node as any).data = data\n .replace(/__MAIZZLE_COLW_([^_]+)__/g,\n (_m, mid) => hoistedTdWidths.get(mid) ?? widthResolutions.get(mid) ?? widthFallbacks.get(mid) ?? '100%')\n .replace(/;\\s*__MAIZZLE_COLTDX_([^_]+)__/g,\n (_m, mid) => {\n const pad = tdExtras.get(mid)\n return pad ? `; ${pad}` : ''\n })\n .replace(/__MAIZZLE_OH_([^_]+)__/g,\n (_m, hid) => heightResolutions.get(hid) ?? '100%')\n return\n }\n\n const el = node as Element\n if (!el.attribs) return\n\n const style = el.attribs.style\n if (style && (style.includes('__MAIZZLE_COLW_') || style.includes('__MAIZZLE_OH_'))) {\n const root = parseElStyle(el)\n const cwId = el.attribs['data-maizzle-cw-id']\n\n /**\n * Strip user dups BEFORE substitution — last-wins CSS would\n * otherwise shadow our resolved values in the output.\n */\n if (cwId && stripUserMinWidth.has(cwId)) {\n root.walkDecls('min-width', (d) => {\n if (!d.value.includes('__MAIZZLE_COLW_')) d.remove()\n })\n }\n if (cwId && stripWidth.has(cwId)) {\n root.walkDecls('width', (d) => { d.remove() })\n }\n\n /**\n * Substitute the column's `min-width:` placeholder with `width: <res>;\n * max-width: 100%`. Width gives the same stacking trigger as\n * min-width — inline-block wraps when children sum > parent\n * — and the `max-width: 100%` clamp keeps the column from\n * overflowing the viewport once it drops to its own row on\n * mobile. Skip the clamp when the user supplied their own.\n *\n * Other placeholders (Overlap td `width`, comment markers,\n * OH height) get a plain value substitution.\n */\n root.walkDecls((d) => {\n if (d.prop === 'min-width') {\n const m = d.value.match(/^__MAIZZLE_COLW_([^_]+)__$/)\n if (m) {\n const mid = m[1]\n if (dropMinWidth.has(mid) || !widthResolutions.has(mid)) {\n d.remove()\n return\n }\n let resolved = widthResolutions.get(mid)!\n const inset = autoColumnInsets.get(mid)\n if (inset) resolved = subtractInsetPx(resolved, inset)\n const repl: Declaration[] = [postcss.decl({ prop: 'width', value: resolved })]\n if (!userHasMaxWidth.has(mid)) {\n repl.push(postcss.decl({ prop: 'max-width', value: '100%' }))\n }\n d.replaceWith(...repl)\n return\n }\n }\n if (d.value.includes('__MAIZZLE_COLW_') || d.value.includes('__MAIZZLE_OH_')) {\n d.value = d.value\n .replace(/__MAIZZLE_COLW_([^_]+)__/g,\n (_m, mid) => widthResolutions.get(mid) ?? widthFallbacks.get(mid) ?? '100%')\n .replace(/__MAIZZLE_OH_([^_]+)__/g,\n (_m, hid) => heightResolutions.get(hid) ?? '100%')\n }\n })\n\n const out = serializeStyle(root)\n if (out) el.attribs.style = out\n else delete el.attribs.style\n }\n\n delete el.attribs['data-maizzle-cw']\n delete el.attribs['data-maizzle-cw-id']\n delete el.attribs['data-maizzle-cw-count']\n delete el.attribs['data-maizzle-cw-self']\n delete el.attribs['data-maizzle-oh-id']\n })\n\n return dom\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"columnWidth.js","names":[],"sources":["../../src/transformers/columnWidth.ts"],"sourcesContent":["import postcss, { type Root, type Declaration } from 'postcss'\nimport safeParser from 'postcss-safe-parser'\nimport { walk } from '../utils/ast/index.ts'\nimport { horizontalBorderPx, horizontalPaddingPx, lengthToPx } from '../utils/cssBox.ts'\nimport type { ChildNode, Element, ParentNode } from 'domhandler'\n\nconst RE_PERCENT = /^[\\d.]+%$/\n\n/**\n * Stringify decls into a `; `-joined inline-style attribute. PostCSS raws\n * preserve the original source spacing, which mixes poorly with\n * the fresh decls we inject — plain join keeps output uniform.\n */\nfunction serializeStyle(root: Root): string {\n const parts: string[] = []\n root.walkDecls((d) => {\n parts.push(`${d.prop}: ${d.value}${d.important ? ' !important' : ''}`)\n })\n return parts.join('; ')\n}\n\nfunction firstDeclValue(root: Root, prop: string): string | undefined {\n let found: string | undefined\n root.walkDecls(prop, (d) => {\n found = d.value\n return false\n })\n return found\n}\n\n/**\n * Find the user-set `min-width:` value on a column. Juice keeps both ours\n * and the one inlined from a class like `min-w-1/3` — we skip any\n * min-width whose value still contains our placeholder token,\n * returning the first remaining user value, or null.\n */\nfunction findUserMinWidth(root: Root): string | null {\n let userVal: string | null = null\n root.walkDecls('min-width', (d) => {\n if (!d.value.includes('__MAIZZLE_COLW_')) {\n userVal = d.value\n return false\n }\n })\n return userVal\n}\n\nfunction resolveLength(value: string): string | null {\n const trimmed = value.trim()\n if (RE_PERCENT.test(trimmed)) return trimmed\n const m = trimmed.match(/^([\\d.]+)(px|rem|em|pt)?$/i)\n if (!m) return null\n const n = parseFloat(m[1])\n switch ((m[2] || 'px').toLowerCase()) {\n case 'px': return `${Math.round(n)}px`\n case 'rem':\n case 'em': return `${Math.round(n * 16)}px`\n case 'pt': return `${Math.round(n * 1.333)}px`\n default: return null\n }\n}\n\nfunction divideLength(value: string, divisor: number): string | null {\n const m = value.match(/^([\\d.]+)(px|%)$/)\n if (!m || divisor < 1) return null\n const n = parseFloat(m[1])\n return `${parseFloat((n / divisor).toFixed(2))}${m[2]}`\n}\n\nfunction subtractInsetPx(width: string, insetPx: number): string {\n if (insetPx <= 0) return width\n const m = width.match(/^([\\d.]+)(px|%)$/)\n if (!m) return width\n // Don't subtract px from percentage widths — units don't match.\n if (m[2] === '%') return width\n const n = parseFloat(m[1]) - insetPx\n return `${Math.max(0, Math.round(n))}px`\n}\n\n/**\n * Return the smaller of two px lengths. Clamps our count-based min-width\n * down to the user's `max-width:` so the cap is never silently\n * violated when our computed min would exceed the user's max.\n */\nfunction minPxLength(a: string, b: string): string {\n const am = a.match(/^([\\d.]+)px$/)\n const bm = b.match(/^([\\d.]+)px$/)\n if (!am || !bm) return a\n return parseFloat(am[1]) < parseFloat(bm[1]) ? a : b\n}\n\nfunction depth(node: ChildNode): number {\n let d = 0\n let cur: ParentNode | null = node.parent\n while (cur) {\n d++\n cur = (cur as any).parent ?? null\n }\n return d\n}\n\nfunction readWidthFromRoot(root: Root): string | null {\n const raw = firstDeclValue(root, 'max-width')\n ?? firstDeclValue(root, 'width')\n ?? firstDeclValue(root, 'min-width')\n return raw ? resolveLength(raw) : null\n}\n\nfunction readHeightFromRoot(root: Root): string | null {\n const raw = firstDeclValue(root, 'max-height')\n ?? firstDeclValue(root, 'height')\n ?? firstDeclValue(root, 'min-height')\n return raw ? resolveLength(raw) : null\n}\n\nfunction readWidthSource(el: Element, root: Root | null): string | null {\n const explicit = el.attribs?.['data-maizzle-cw']\n if (explicit) {\n const r = resolveLength(explicit)\n if (r) return r\n }\n return root ? readWidthFromRoot(root) : null\n}\n\n/**\n * Convert a user-supplied length to absolute px against the column's source\n * width (post-inset). Percentages multiply against the source while\n * absolute units pass through `resolveLength`. Returns null when\n * the value or source can't be expressed in px.\n */\nfunction userValueToPx(rawValue: string, sourcePx: string | null): string | null {\n const trimmed = rawValue.trim()\n\n const absMatch = trimmed.match(/^([\\d.]+)(px|rem|em|pt)$/i)\n if (absMatch) return resolveLength(trimmed)\n\n const pctMatch = trimmed.match(/^([\\d.]+)%$/)\n if (!pctMatch || !sourcePx) return null\n const sourceMatch = sourcePx.match(/^([\\d.]+)px$/)\n if (!sourceMatch) return null\n const pct = parseFloat(pctMatch[1])\n const src = parseFloat(sourceMatch[1])\n return `${Math.round((pct / 100) * src)}px`\n}\n\n/**\n * Resolve `__MAIZZLE_COLW_{id}__` and `__MAIZZLE_OH_{id}__` placeholders.\n *\n * COLW (column width) — emitted by `<Column>` and `<Overlap>`. Walks up to\n * the nearest ancestor marked `data-maizzle-cw` (Container, Section,\n * Row, or another Column already resolved) and divides the source\n * width by `data-maizzle-cw-count`. With `data-maizzle-cw-self`,\n * reads from the element's own inlined max/width/min-width\n * instead — used by `<Overlap>` with its own width class.\n *\n * OH (overlap height) — emitted by `<Overlap>`. Reads max-height, height,\n * or min-height from the element's own inlined style.\n *\n * Resolution rules:\n * - Style placeholders for `min-width`: replaced when resolvable, otherwise\n * the entire `min-width` declaration is stripped.\n * - Other style placeholders (Overlap td `width`, etc.): replaced when\n * resolvable, otherwise replaced with the count-based fallback or `100%`.\n * - Comment placeholders: same fallback chain.\n *\n * Resolved column widths are written back to `data-maizzle-cw` so nested\n * rows cascade. All `data-maizzle-cw*` and `data-maizzle-oh-*` are\n * stripped at the end of the second walk pass.\n */\nexport function columnWidth(dom: ChildNode[]): ChildNode[] {\n /**\n * Cache parsed style ASTs for this columnWidth invocation. The walk-up\n * loop visits the same Section/Container once per column of a Row,\n * so without caching each column re-parses every ancestor's style.\n * Cache is function-local — no cross-build leak via the WeakMap.\n */\n const styleCache = new WeakMap<Element, Root>()\n const parseElStyle = (el: Element): Root => {\n const cached = styleCache.get(el)\n if (cached) return cached\n const style = el.attribs?.style ?? ''\n const root = style ? safeParser(style) : postcss.root()\n styleCache.set(el, root)\n return root\n }\n\n const columns: { el: Element; id: string; count: number; d: number; self: boolean }[] = []\n const heightTargets: { el: Element; id: string }[] = []\n\n walk(dom, (node) => {\n const el = node as Element\n if (!el.attribs) return\n\n const id = el.attribs['data-maizzle-cw-id']\n if (id) {\n const count = parseInt(el.attribs['data-maizzle-cw-count'] || '1', 10)\n const self = 'data-maizzle-cw-self' in el.attribs\n columns.push({ el, id, count, d: depth(node), self })\n }\n\n const ohId = el.attribs['data-maizzle-oh-id']\n if (ohId) heightTargets.push({ el, id: ohId })\n })\n\n columns.sort((a, b) => a.d - b.d)\n\n const widthResolutions = new Map<string, string>()\n const widthFallbacks = new Map<string, string>()\n /**\n * Column ids whose absolute user `width:` was promoted to `min-width:`\n * — the original `width:` declaration must be stripped from the\n * column's style (otherwise it'd compete with the min-width).\n */\n const stripWidth = new Set<string>()\n /**\n * Column ids where the user wrote a percentage `width:` (e.g. `w-1/2`) —\n * explicit opt-out of px-based stacking. Keep the user's `width: X%`\n * and drop our `min-width:` placeholder so the column stays at\n * that percentage of its parent forever and never stacks.\n */\n const dropMinWidth = new Set<string>()\n /**\n * Column ids where the user wrote their own `min-width:` (via `min-w-1/3`).\n * Juice inlines theirs after ours, so two `min-width:` decls land in\n * the style — we strip the user's after using its value as the\n * column's resolution, leaving our placeholder as last word.\n */\n const stripUserMinWidth = new Set<string>()\n /**\n * Column ids where the user already supplied a `max-width:` of their own.\n * Our default `max-width: 100%` would just be shadowed by it via\n * last-wins and bloat the style — so we skip emitting it.\n */\n const userHasMaxWidth = new Set<string>()\n /**\n * Horizontal padding+border (px) of each column whose width was auto-\n * derived from count-based math. The column's own border/padding eats\n * its content box under content-box sizing, so the emitted `width:`\n * must be the slice MINUS this inset; otherwise two bordered cols\n * sum past the container and wrap. Only populated for auto paths\n * — user-explicit `width:`/`min-width:`/`max-width:`-wins paths\n * leave the value alone so the user's number stays the user's.\n */\n const autoColumnInsets = new Map<string, number>()\n /**\n * Extra inline-style decls that get stamped onto each column's MSO `<td>`\n * via its COLTDX placeholder. Carries `background-color` (so Word\n * paints the cell behind any padding area or whitespace, not just\n * the div) and hoisted `padding*` decls (when no border is present\n * — without one Word silently drops div padding, with one a td\n * copy would double-pad). The div keeps both for modern clients\n * since the MSO td is conditional-comment-only.\n */\n const tdExtras = new Map<string, string>()\n /**\n * MSO td width override for hoisted columns. With padding on the td and\n * CSS content-box sizing for table cells, td_outer = width + 2*pad,\n * so we set td width to the slot MINUS 2*horizontal-padding to keep\n * the cell at its outer slot. Skipped when the slot is a %.\n */\n const hoistedTdWidths = new Map<string, string>()\n\n for (const { id, count } of columns) {\n widthFallbacks.set(id, `${Math.round(100 / Math.max(count, 1))}%`)\n }\n\n /**\n * Sibling-aware redistribution.\n *\n * Without this, every auto column slices its source `/ totalCount`,\n * ignoring siblings that already declared a fixed `w-5` (20px) or\n * `w-1/3` (%). Three auto cols next to a `w-5` would each take\n * `source/3` and overflow the row by 20px.\n *\n * Pre-pass: classify each col against its inlined style.\n * - explicit-px: `width:` or `min-width:` resolving to px. The col\n * occupies (px value + own horizontal padding +\n * own horizontal border) of the source.\n * - explicit-pct: `width:` or `min-width:` resolving to %. The col\n * occupies (pct * source / 100 + own inset).\n * - auto: everything else. Shares the remainder with other\n * auto siblings.\n *\n * Group cols by their immediate `el.parent` (the Row div). For each\n * group, sum the explicit contributions and count the autos. The\n * countBased path below uses these to redistribute leftover space\n * instead of dividing the raw source by total count.\n *\n * `data-maizzle-cw-self` cols (Overlap) are excluded — they own their\n * source independently and don't share a parent's slot with cols.\n */\n interface ColClassification {\n kind: 'auto' | 'explicit'\n pxOuter: number\n pctOuter: number\n }\n interface GroupInfo {\n explicitPxOuterSum: number\n explicitPctOuterSum: number\n autoCount: number\n }\n const groupInfos = new Map<ParentNode, GroupInfo>()\n\n for (const c of columns) {\n if (c.self) continue\n const ownRoot = parseElStyle(c.el)\n const ownInset = horizontalPaddingPx(ownRoot) + horizontalBorderPx(ownRoot)\n\n let cls: ColClassification = { kind: 'auto', pxOuter: 0, pctOuter: 0 }\n const userVal = findUserMinWidth(ownRoot) ?? firstDeclValue(ownRoot, 'width')\n if (userVal) {\n const resolved = resolveLength(userVal)\n if (resolved?.endsWith('px')) {\n const px = lengthToPx(resolved)\n if (px != null) cls = { kind: 'explicit', pxOuter: px + ownInset, pctOuter: 0 }\n }\n else if (resolved?.endsWith('%')) {\n cls = { kind: 'explicit', pxOuter: ownInset, pctOuter: parseFloat(resolved) }\n }\n }\n\n const parent = c.el.parent as ParentNode | null\n if (!parent) continue\n let info = groupInfos.get(parent)\n if (!info) {\n info = { explicitPxOuterSum: 0, explicitPctOuterSum: 0, autoCount: 0 }\n groupInfos.set(parent, info)\n }\n info.explicitPxOuterSum += cls.pxOuter\n info.explicitPctOuterSum += cls.pctOuter\n if (cls.kind === 'auto') info.autoCount++\n }\n\n for (const { el, id, count, self } of columns) {\n const ownRoot = parseElStyle(el)\n\n let sourceWidth: string | null = null\n let accumulatedInsetPx = 0\n\n if (self) {\n sourceWidth = readWidthFromRoot(ownRoot)\n accumulatedInsetPx = horizontalPaddingPx(ownRoot) + horizontalBorderPx(ownRoot)\n }\n else {\n /**\n * Walk up through every ancestor with attribs, accumulating horizontal\n * padding+border along the way (including the source). Stop at the\n * first `data-maizzle-cw` ancestor whose width is resolvable.\n * Markers without a resolvable width (Row emitted empty after\n * Tailwind dropped a bogus class) shouldn't shadow a real\n * width on a higher ancestor like `<Container>`.\n *\n * With CSS content-box this is technically generous toward the\n * source's own padding/border, but matches user expectations\n * when they put `px-9` or `border-2` on a wrapper.\n */\n let cur: ParentNode | null = el.parent\n while (cur) {\n const parentEl = cur as Element\n if (parentEl.attribs) {\n let pRoot: Root | null = null\n if (parentEl.attribs.style) {\n pRoot = parseElStyle(parentEl)\n accumulatedInsetPx += horizontalPaddingPx(pRoot) + horizontalBorderPx(pRoot)\n }\n if ('data-maizzle-cw' in parentEl.attribs) {\n const w = readWidthSource(parentEl, pRoot)\n if (w) {\n sourceWidth = w\n break\n }\n }\n }\n cur = (cur as any).parent ?? null\n }\n }\n\n const adjusted = sourceWidth ? subtractInsetPx(sourceWidth, accumulatedInsetPx) : null\n let countBased = adjusted ? divideLength(adjusted, count) : null\n\n /**\n * Sibling-aware redistribution kicks in only when the auto col has at\n * least one explicit-width sibling and the source is in px (can't\n * mix px subtraction with a % source). When that's true, the auto\n * col's share becomes `(source − sum_explicit_px − pct% of source)\n * / autoCount`, replacing the naive `source/totalCount`.\n */\n if (!self && adjusted?.endsWith('px') && countBased && el.parent) {\n const group = groupInfos.get(el.parent as ParentNode)\n if (group && group.autoCount > 0\n && (group.explicitPxOuterSum > 0 || group.explicitPctOuterSum > 0)) {\n const adjPx = lengthToPx(adjusted)\n if (adjPx != null) {\n const remaining = adjPx - group.explicitPxOuterSum - (adjPx * group.explicitPctOuterSum / 100)\n /**\n * Floor-to-2-decimals on the per-auto share. Two reasons:\n *\n * 1. Matches `divideLength`'s precision so the auto path and\n * group-aware path emit consistent units.\n * 2. Rounding up (e.g. 536/3 = 178.6̄ → round to 179) pushes\n * the sum past the slot — 3×179 + 2×20 = 577 > 576 →\n * inline-block wraps and the row stacks. Flooring at 2\n * decimals keeps the sum ≤ slot with at most ~0.0(autoCount)\n * px unused, which is sub-pixel and invisible.\n */\n const share = Math.max(0, Math.floor((remaining * 100) / group.autoCount) / 100)\n countBased = `${share}px`\n }\n }\n }\n\n /**\n * Four user-override paths, decided by which CSS property the user\n * actually wrote:\n *\n * - `min-width: X` → user's value wins. Convert to px against\n * the source (if %), use as the column's\n * resolution, and strip the user's min-width\n * declaration so our placeholder substitution\n * remains the last `min-width:` in style.\n * - `width: X%` → opt-out of px stacking. Keep `width:` in\n * style, drop our `min-width:` placeholder.\n * Cols stay at X% of parent forever, never stack.\n * - `width: Xpx` (or rem/em/pt) → fixed pixel column. Promote to\n * `min-width:`, strip the original `width:` so\n * it doesn't compete.\n * - `max-width: X` → CSS cap. Keep the `max-width:` declaration;\n * clamp our count-based min-width *down* to\n * the user's max-width when our min would\n * otherwise violate it.\n */\n const userMinRaw = findUserMinWidth(ownRoot)\n const widthRaw = firstDeclValue(ownRoot, 'width')\n const maxRaw = firstDeclValue(ownRoot, 'max-width')\n\n if (userMinRaw) {\n const minPx = userValueToPx(userMinRaw, adjusted) ?? resolveLength(userMinRaw)\n if (minPx) {\n widthResolutions.set(id, minPx)\n el.attribs['data-maizzle-cw'] = minPx\n stripUserMinWidth.add(id)\n continue\n }\n }\n\n if (widthRaw) {\n const widthVal = resolveLength(widthRaw)\n if (widthVal?.endsWith('%')) {\n widthResolutions.set(id, widthVal)\n el.attribs['data-maizzle-cw'] = widthVal\n dropMinWidth.add(id)\n continue\n }\n if (widthVal) {\n widthResolutions.set(id, widthVal)\n el.attribs['data-maizzle-cw'] = widthVal\n stripWidth.add(id)\n continue\n }\n }\n\n if (maxRaw && countBased) {\n const maxPx = userValueToPx(maxRaw, adjusted)\n if (maxPx) {\n const cappedMin = countBased.endsWith('px')\n ? minPxLength(countBased, maxPx)\n : maxPx\n widthResolutions.set(id, cappedMin)\n el.attribs['data-maizzle-cw'] = cappedMin\n userHasMaxWidth.add(id)\n if (cappedMin === countBased) {\n const ownInset = horizontalPaddingPx(ownRoot) + horizontalBorderPx(ownRoot)\n if (ownInset > 0) autoColumnInsets.set(id, ownInset)\n }\n continue\n }\n }\n\n if (countBased) {\n widthResolutions.set(id, countBased)\n el.attribs['data-maizzle-cw'] = countBased\n const ownPaddingPx = horizontalPaddingPx(ownRoot)\n const ownBorderPx = horizontalBorderPx(ownRoot)\n const ownInset = ownPaddingPx + ownBorderPx\n if (ownInset > 0) autoColumnInsets.set(id, ownInset)\n\n /**\n * Build the MSO td's \"extras\" string — decls that need to live on\n * the td in addition to width + vertical-align. Two contributors:\n *\n * - `background-color` (always, when present) — Word renders the\n * div bg inside the cell, but anything outside the div (the\n * td's padding area when hoisted, or any whitespace gap)\n * would show the parent's bg instead of the column's. Painting\n * the td matches the user's intent.\n *\n * - `padding*` (hoisted only when no horizontal border) — Word\n * drops div padding without a stabilizing border, so the td\n * has to carry it. With a border, Word renders div padding\n * and a td copy would double-pad. Skip when the slot is `%`:\n * td width math can't subtract px padding from a percentage.\n */\n const extras: string[] = []\n let bgColor: string | undefined\n ownRoot.walkDecls('background-color', (d) => { bgColor = d.value })\n if (bgColor) extras.push(`background-color: ${bgColor}`)\n\n if (ownPaddingPx > 0 && ownBorderPx === 0 && countBased.endsWith('px')) {\n ownRoot.walkDecls((d) => {\n if (/^padding(-|$)/.test(d.prop)) extras.push(`${d.prop}: ${d.value}`)\n })\n hoistedTdWidths.set(id, subtractInsetPx(countBased, ownPaddingPx))\n }\n\n if (extras.length) tdExtras.set(id, extras.join('; '))\n }\n }\n\n const heightResolutions = new Map<string, string>()\n for (const { el, id } of heightTargets) {\n if (!el.attribs?.style) continue\n const h = readHeightFromRoot(parseElStyle(el))\n if (h) heightResolutions.set(id, h)\n }\n\n walk(dom, (node) => {\n if (node.type === 'comment') {\n const data = (node as any).data as string\n if (!data) return\n const hasCW = data.includes('__MAIZZLE_COLW_')\n const hasOH = data.includes('__MAIZZLE_OH_')\n const hasTDX = data.includes('__MAIZZLE_COLTDX_')\n if (!hasCW && !hasOH && !hasTDX) return\n ;(node as any).data = data\n .replace(/__MAIZZLE_COLW_([^_]+)__/g,\n (_m, mid) => hoistedTdWidths.get(mid) ?? widthResolutions.get(mid) ?? widthFallbacks.get(mid) ?? '100%')\n .replace(/;\\s*__MAIZZLE_COLTDX_([^_]+)__/g,\n (_m, mid) => {\n const pad = tdExtras.get(mid)\n return pad ? `; ${pad}` : ''\n })\n .replace(/__MAIZZLE_OH_([^_]+)__/g,\n (_m, hid) => heightResolutions.get(hid) ?? '100%')\n return\n }\n\n const el = node as Element\n if (!el.attribs) return\n\n const style = el.attribs.style\n if (style && (style.includes('__MAIZZLE_COLW_') || style.includes('__MAIZZLE_OH_'))) {\n const root = parseElStyle(el)\n const cwId = el.attribs['data-maizzle-cw-id']\n\n /**\n * Strip user dups BEFORE substitution — last-wins CSS would\n * otherwise shadow our resolved values in the output.\n */\n if (cwId && stripUserMinWidth.has(cwId)) {\n root.walkDecls('min-width', (d) => {\n if (!d.value.includes('__MAIZZLE_COLW_')) d.remove()\n })\n }\n if (cwId && stripWidth.has(cwId)) {\n root.walkDecls('width', (d) => { d.remove() })\n }\n\n /**\n * Substitute the column's `min-width:` placeholder with `width: <res>;\n * max-width: 100%`. Width gives the same stacking trigger as\n * min-width — inline-block wraps when children sum > parent\n * — and the `max-width: 100%` clamp keeps the column from\n * overflowing the viewport once it drops to its own row on\n * mobile. Skip the clamp when the user supplied their own.\n *\n * Other placeholders (Overlap td `width`, comment markers,\n * OH height) get a plain value substitution.\n */\n root.walkDecls((d) => {\n if (d.prop === 'min-width') {\n const m = d.value.match(/^__MAIZZLE_COLW_([^_]+)__$/)\n if (m) {\n const mid = m[1]\n if (dropMinWidth.has(mid) || !widthResolutions.has(mid)) {\n d.remove()\n return\n }\n let resolved = widthResolutions.get(mid)!\n const inset = autoColumnInsets.get(mid)\n if (inset) resolved = subtractInsetPx(resolved, inset)\n const repl: Declaration[] = [postcss.decl({ prop: 'width', value: resolved })]\n if (!userHasMaxWidth.has(mid)) {\n repl.push(postcss.decl({ prop: 'max-width', value: '100%' }))\n }\n d.replaceWith(...repl)\n return\n }\n }\n if (d.value.includes('__MAIZZLE_COLW_') || d.value.includes('__MAIZZLE_OH_')) {\n d.value = d.value\n .replace(/__MAIZZLE_COLW_([^_]+)__/g,\n (_m, mid) => widthResolutions.get(mid) ?? widthFallbacks.get(mid) ?? '100%')\n .replace(/__MAIZZLE_OH_([^_]+)__/g,\n (_m, hid) => heightResolutions.get(hid) ?? '100%')\n }\n })\n\n const out = serializeStyle(root)\n if (out) el.attribs.style = out\n else delete el.attribs.style\n }\n\n delete el.attribs['data-maizzle-cw']\n delete el.attribs['data-maizzle-cw-id']\n delete el.attribs['data-maizzle-cw-count']\n delete el.attribs['data-maizzle-cw-self']\n delete el.attribs['data-maizzle-oh-id']\n })\n\n return dom\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"entities.d.ts","names":[],"sources":["../../src/transformers/entities.ts"],"mappings":";;;;;;;AAsDA;;;;;;;;;AASA;;;;;;;;;;;iBATgB,QAAA,CAAS,IAAA,UAAc,MAAA,GAAQ,cAAA;;;;;;iBAS/B,WAAA,CAAY,GAAA,EAAK,SAAA,IAAa,MAAA,GAAQ,cAAA,GAAwB,SAAA"}
1
+ {"version":3,"file":"entities.d.ts","names":[],"sources":["../../src/transformers/entities.ts"],"mappings":";;;;;;;AAsDA;;;;;;;;AAAoE;AASpE;;;;;;;;;;;iBATgB,QAAA,CAAS,IAAA,UAAc,MAAA,GAAQ,cAAqB;;;AASmB;;;iBAAvE,WAAA,CAAY,GAAA,EAAK,SAAA,IAAa,MAAA,GAAQ,cAAA,GAAwB,SAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"entities.js","names":["merge"],"sources":["../../src/transformers/entities.ts"],"sourcesContent":["import { defu as merge } from 'defu'\nimport type { ChildNode } from 'domhandler'\nimport { parse, serialize, walk } from '../utils/ast/index.ts'\nimport type { EntitiesConfig } from '../types/index.ts'\n\nconst DEFAULT_ENTITIES: Record<string, string> = {\n '\\u200D': '&zwj;',\n '\\u200C': '&zwnj;',\n '\\u00A0': '&nbsp;',\n '\\u00AD': '&shy;',\n '\\u200B': '&#8203;',\n '\\u2007': '&#8199;',\n '\\uFEFF': '&#65279;',\n '\\u034F': '&#847;',\n '\\u2003': '&emsp;',\n '\\u2028': '&LineSeparator;',\n '\\u2029': '&ParagraphSeparator;',\n '\\u00B7': '&middot;',\n '\\u2013': '&ndash;',\n '\\u2014': '&mdash;',\n '\\u2018': '&lsquo;',\n '\\u2019': '&rsquo;',\n '\\u201C': '&ldquo;',\n '\\u201D': '&rdquo;',\n '\\u00AB': '&laquo;',\n '\\u00BB': '&raquo;',\n '\\u2022': '&bull;',\n '\\u2039': '&lsaquo;',\n '\\u203A': '&rsaquo;'\n}\n\n/**\n * Replace literal Unicode characters in text nodes with their HTML entity\n * equivalents (zero-width joiners, non-breaking spaces, smart quotes,\n * dashes, etc.) for better email-client rendering.\n *\n * @param html HTML string to transform.\n * @param custom Extra entries merged on top of the built-in entity map, or\n * `false` to disable the transform. Defaults to `true`\n * (built-ins only).\n * @returns The transformed HTML string.\n *\n * @example\n * import { entities } from '@maizzle/framework'\n *\n * // Defaults only\n * entities('hello world') // → 'hello&nbsp;world'\n *\n * // Add a custom mapping\n * entities('© Maizzle', { '©': '&copy;' })\n *\n * // Disable the transform\n * entities('hello world', false)\n */\nexport function entities(html: string, custom: EntitiesConfig = true): string {\n return serialize(entitiesDom(parse(html), custom))\n}\n\n/**\n * DOM-form of {@link entities} used by the internal transformer pipeline.\n * Takes a parsed DOM, returns a parsed DOM — avoids redundant\n * serialize/parse round-trips when chained with other transformers.\n */\nexport function entitiesDom(dom: ChildNode[], custom: EntitiesConfig = true): ChildNode[] {\n if (!custom) return dom\n\n const map = typeof custom === 'object'\n ? merge(custom as Record<string, string>, DEFAULT_ENTITIES)\n : DEFAULT_ENTITIES\n\n walk(dom, (node) => {\n if (node.type === 'text') {\n for (const [char, entity] of Object.entries(map)) {\n node.data = node.data.split(char).join(entity)\n }\n }\n })\n\n return dom\n}\n"],"mappings":";;;;;;AAKA,MAAM,mBAA2C;CAC/C,KAAU;CACV,KAAU;CACV,QAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,UAAU;CACV,UAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACX;;;;;;;;;;;;;;;;;;;;;;;;AAyBD,SAAgB,SAAS,MAAc,SAAyB,MAAc;CAC5E,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,OAAO,CAAC;;;;;;;AAQpD,SAAgB,YAAY,KAAkB,SAAyB,MAAmB;CACxF,IAAI,CAAC,QAAQ,OAAO;CAEpB,MAAM,MAAM,OAAO,WAAW,WAC1BA,OAAM,QAAkC,iBAAiB,GACzD;CAEJ,KAAK,MAAM,SAAS;EAClB,IAAI,KAAK,SAAS,QAChB,KAAK,MAAM,CAAC,MAAM,WAAW,OAAO,QAAQ,IAAI,EAC9C,KAAK,OAAO,KAAK,KAAK,MAAM,KAAK,CAAC,KAAK,OAAO;GAGlD;CAEF,OAAO"}
1
+ {"version":3,"file":"entities.js","names":["merge"],"sources":["../../src/transformers/entities.ts"],"sourcesContent":["import { defu as merge } from 'defu'\nimport type { ChildNode } from 'domhandler'\nimport { parse, serialize, walk } from '../utils/ast/index.ts'\nimport type { EntitiesConfig } from '../types/index.ts'\n\nconst DEFAULT_ENTITIES: Record<string, string> = {\n '\\u200D': '&zwj;',\n '\\u200C': '&zwnj;',\n '\\u00A0': '&nbsp;',\n '\\u00AD': '&shy;',\n '\\u200B': '&#8203;',\n '\\u2007': '&#8199;',\n '\\uFEFF': '&#65279;',\n '\\u034F': '&#847;',\n '\\u2003': '&emsp;',\n '\\u2028': '&LineSeparator;',\n '\\u2029': '&ParagraphSeparator;',\n '\\u00B7': '&middot;',\n '\\u2013': '&ndash;',\n '\\u2014': '&mdash;',\n '\\u2018': '&lsquo;',\n '\\u2019': '&rsquo;',\n '\\u201C': '&ldquo;',\n '\\u201D': '&rdquo;',\n '\\u00AB': '&laquo;',\n '\\u00BB': '&raquo;',\n '\\u2022': '&bull;',\n '\\u2039': '&lsaquo;',\n '\\u203A': '&rsaquo;'\n}\n\n/**\n * Replace literal Unicode characters in text nodes with their HTML entity\n * equivalents (zero-width joiners, non-breaking spaces, smart quotes,\n * dashes, etc.) for better email-client rendering.\n *\n * @param html HTML string to transform.\n * @param custom Extra entries merged on top of the built-in entity map, or\n * `false` to disable the transform. Defaults to `true`\n * (built-ins only).\n * @returns The transformed HTML string.\n *\n * @example\n * import { entities } from '@maizzle/framework'\n *\n * // Defaults only\n * entities('hello world') // → 'hello&nbsp;world'\n *\n * // Add a custom mapping\n * entities('© Maizzle', { '©': '&copy;' })\n *\n * // Disable the transform\n * entities('hello world', false)\n */\nexport function entities(html: string, custom: EntitiesConfig = true): string {\n return serialize(entitiesDom(parse(html), custom))\n}\n\n/**\n * DOM-form of {@link entities} used by the internal transformer pipeline.\n * Takes a parsed DOM, returns a parsed DOM — avoids redundant\n * serialize/parse round-trips when chained with other transformers.\n */\nexport function entitiesDom(dom: ChildNode[], custom: EntitiesConfig = true): ChildNode[] {\n if (!custom) return dom\n\n const map = typeof custom === 'object'\n ? merge(custom as Record<string, string>, DEFAULT_ENTITIES)\n : DEFAULT_ENTITIES\n\n walk(dom, (node) => {\n if (node.type === 'text') {\n for (const [char, entity] of Object.entries(map)) {\n node.data = node.data.split(char).join(entity)\n }\n }\n })\n\n return dom\n}\n"],"mappings":";;;;;;AAKA,MAAM,mBAA2C;CAC/C,KAAU;CACV,KAAU;CACV,QAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,UAAU;CACV,UAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;AACZ;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,SAAgB,SAAS,MAAc,SAAyB,MAAc;CAC5E,OAAO,UAAU,YAAY,MAAM,IAAI,GAAG,MAAM,CAAC;AACnD;;;;;;AAOA,SAAgB,YAAY,KAAkB,SAAyB,MAAmB;CACxF,IAAI,CAAC,QAAQ,OAAO;CAEpB,MAAM,MAAM,OAAO,WAAW,WAC1BA,OAAM,QAAkC,gBAAgB,IACxD;CAEJ,KAAK,MAAM,SAAS;EAClB,IAAI,KAAK,SAAS,QAChB,KAAK,MAAM,CAAC,MAAM,WAAW,OAAO,QAAQ,GAAG,GAC7C,KAAK,OAAO,KAAK,KAAK,MAAM,IAAI,EAAE,KAAK,MAAM;CAGnD,CAAC;CAED,OAAO;AACT"}
@@ -1 +1 @@
1
- {"version":3,"file":"defaults.d.ts","names":[],"sources":["../../../src/transformers/filters/defaults.ts"],"mappings":";KAAY,cAAA,IAAkB,GAAA,UAAa,KAAA;AAAA,cAgB9B,QAAA,EAAU,MAAA,SAAe,cAAA"}
1
+ {"version":3,"file":"defaults.d.ts","names":[],"sources":["../../../src/transformers/filters/defaults.ts"],"mappings":";KAAY,cAAA,IAAkB,GAAA,UAAa,KAAa;AAAA,cAgB3C,QAAA,EAAU,MAAM,SAAS,cAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"defaults.js","names":[],"sources":["../../../src/transformers/filters/defaults.ts"],"sourcesContent":["export type FilterFunction = (str: string, value: string) => string\n\nconst escapeMap: Record<string, string> = {\n '\"': '&#34;',\n '&': '&amp;',\n \"'\": '&#39;',\n '<': '&lt;',\n '>': '&gt;',\n}\n\nconst escapeRegex = /[\"&'<>]/g\n\nfunction escapeHtml(str: string): string {\n return str.replace(escapeRegex, ch => escapeMap[ch])\n}\n\nexport const defaults: Record<string, FilterFunction> = {\n append: (str, value) => str + value,\n prepend: (str, value) => value + str,\n uppercase: str => str.toUpperCase(),\n lowercase: str => str.toLowerCase(),\n capitalize: str => str.charAt(0).toUpperCase() + str.slice(1),\n ceil: str => String(Math.ceil(Number.parseFloat(str))),\n floor: str => String(Math.floor(Number.parseFloat(str))),\n round: str => String(Math.round(Number.parseFloat(str))),\n escape: str => escapeHtml(str),\n 'escape-once': str => {\n const decoded = str\n .replace(/&amp;/g, '&')\n .replace(/&lt;/g, '<')\n .replace(/&gt;/g, '>')\n .replace(/&#34;/g, '\"')\n .replace(/&quot;/g, '\"')\n .replace(/&#39;/g, \"'\")\n .replace(/&apos;/g, \"'\")\n\n return escapeHtml(decoded)\n },\n lstrip: str => str.trimStart(),\n rstrip: str => str.trimEnd(),\n trim: str => str.trim(),\n minus: (str, value) => String(Number.parseFloat(str) - Number.parseFloat(value)),\n plus: (str, value) => String(Number.parseFloat(str) + Number.parseFloat(value)),\n multiply: (str, value) => String(Number.parseFloat(str) * Number.parseFloat(value)),\n times: (str, value) => String(Number.parseFloat(str) * Number.parseFloat(value)),\n 'divide-by': (str, value) => String(Number.parseFloat(str) / Number.parseFloat(value)),\n divide: (str, value) => String(Number.parseFloat(str) / Number.parseFloat(value)),\n modulo: (str, value) => String(Number.parseFloat(str) % Number.parseFloat(value)),\n 'newline-to-br': str => str.replace(/\\n/g, '<br>'),\n 'strip-newlines': str => str.replace(/\\n/g, ''),\n remove: (str, value) => str.split(value).join(''),\n 'remove-first': (str, value) => {\n const i = str.indexOf(value)\n return i === -1 ? str : str.slice(0, i) + str.slice(i + value.length)\n },\n replace: (str, value) => {\n const [search = '', replacement = ''] = value.split('|')\n return str.split(search).join(replacement)\n },\n 'replace-first': (str, value) => {\n const [search = '', replacement = ''] = value.split('|')\n const i = str.indexOf(search)\n return i === -1 ? str : str.slice(0, i) + replacement + str.slice(i + search.length)\n },\n size: str => String(str.length),\n slice: (str, value) => {\n const args = value.split(',').map(s => Number.parseInt(s.trim(), 10))\n return str.slice(args[0], args[1])\n },\n truncate: (str, value) => {\n const commaIndex = value.indexOf(',')\n const length = Number.parseInt(commaIndex === -1 ? value : value.slice(0, commaIndex), 10)\n const ellipsis = commaIndex === -1 ? '...' : value.slice(commaIndex + 1)\n\n if (str.length <= length) return str\n\n return str.slice(0, length) + ellipsis\n },\n 'truncate-words': (str, value) => {\n const commaIndex = value.indexOf(',')\n const count = Number.parseInt(commaIndex === -1 ? value : value.slice(0, commaIndex), 10)\n const ellipsis = commaIndex === -1 ? '...' : value.slice(commaIndex + 1)\n const words = str.split(/\\s+/).filter(Boolean)\n\n if (words.length <= count) return str\n\n return words.slice(0, count).join(' ') + ellipsis\n },\n 'url-decode': str => decodeURIComponent(str.replace(/\\+/g, ' ')),\n 'url-encode': str => encodeURIComponent(str),\n}\n"],"mappings":";AAEA,MAAM,YAAoC;CACxC,MAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACN;AAED,MAAM,cAAc;AAEpB,SAAS,WAAW,KAAqB;CACvC,OAAO,IAAI,QAAQ,cAAa,OAAM,UAAU,IAAI;;AAGtD,MAAa,WAA2C;CACtD,SAAS,KAAK,UAAU,MAAM;CAC9B,UAAU,KAAK,UAAU,QAAQ;CACjC,YAAW,QAAO,IAAI,aAAa;CACnC,YAAW,QAAO,IAAI,aAAa;CACnC,aAAY,QAAO,IAAI,OAAO,EAAE,CAAC,aAAa,GAAG,IAAI,MAAM,EAAE;CAC7D,OAAM,QAAO,OAAO,KAAK,KAAK,OAAO,WAAW,IAAI,CAAC,CAAC;CACtD,QAAO,QAAO,OAAO,KAAK,MAAM,OAAO,WAAW,IAAI,CAAC,CAAC;CACxD,QAAO,QAAO,OAAO,KAAK,MAAM,OAAO,WAAW,IAAI,CAAC,CAAC;CACxD,SAAQ,QAAO,WAAW,IAAI;CAC9B,gBAAe,QAAO;EAUpB,OAAO,WATS,IACb,QAAQ,UAAU,IAAI,CACtB,QAAQ,SAAS,IAAI,CACrB,QAAQ,SAAS,IAAI,CACrB,QAAQ,UAAU,KAAI,CACtB,QAAQ,WAAW,KAAI,CACvB,QAAQ,UAAU,IAAI,CACtB,QAAQ,WAAW,IAEG,CAAC;;CAE5B,SAAQ,QAAO,IAAI,WAAW;CAC9B,SAAQ,QAAO,IAAI,SAAS;CAC5B,OAAM,QAAO,IAAI,MAAM;CACvB,QAAQ,KAAK,UAAU,OAAO,OAAO,WAAW,IAAI,GAAG,OAAO,WAAW,MAAM,CAAC;CAChF,OAAO,KAAK,UAAU,OAAO,OAAO,WAAW,IAAI,GAAG,OAAO,WAAW,MAAM,CAAC;CAC/E,WAAW,KAAK,UAAU,OAAO,OAAO,WAAW,IAAI,GAAG,OAAO,WAAW,MAAM,CAAC;CACnF,QAAQ,KAAK,UAAU,OAAO,OAAO,WAAW,IAAI,GAAG,OAAO,WAAW,MAAM,CAAC;CAChF,cAAc,KAAK,UAAU,OAAO,OAAO,WAAW,IAAI,GAAG,OAAO,WAAW,MAAM,CAAC;CACtF,SAAS,KAAK,UAAU,OAAO,OAAO,WAAW,IAAI,GAAG,OAAO,WAAW,MAAM,CAAC;CACjF,SAAS,KAAK,UAAU,OAAO,OAAO,WAAW,IAAI,GAAG,OAAO,WAAW,MAAM,CAAC;CACjF,kBAAiB,QAAO,IAAI,QAAQ,OAAO,OAAO;CAClD,mBAAkB,QAAO,IAAI,QAAQ,OAAO,GAAG;CAC/C,SAAS,KAAK,UAAU,IAAI,MAAM,MAAM,CAAC,KAAK,GAAG;CACjD,iBAAiB,KAAK,UAAU;EAC9B,MAAM,IAAI,IAAI,QAAQ,MAAM;EAC5B,OAAO,MAAM,KAAK,MAAM,IAAI,MAAM,GAAG,EAAE,GAAG,IAAI,MAAM,IAAI,MAAM,OAAO;;CAEvE,UAAU,KAAK,UAAU;EACvB,MAAM,CAAC,SAAS,IAAI,cAAc,MAAM,MAAM,MAAM,IAAI;EACxD,OAAO,IAAI,MAAM,OAAO,CAAC,KAAK,YAAY;;CAE5C,kBAAkB,KAAK,UAAU;EAC/B,MAAM,CAAC,SAAS,IAAI,cAAc,MAAM,MAAM,MAAM,IAAI;EACxD,MAAM,IAAI,IAAI,QAAQ,OAAO;EAC7B,OAAO,MAAM,KAAK,MAAM,IAAI,MAAM,GAAG,EAAE,GAAG,cAAc,IAAI,MAAM,IAAI,OAAO,OAAO;;CAEtF,OAAM,QAAO,OAAO,IAAI,OAAO;CAC/B,QAAQ,KAAK,UAAU;EACrB,MAAM,OAAO,MAAM,MAAM,IAAI,CAAC,KAAI,MAAK,OAAO,SAAS,EAAE,MAAM,EAAE,GAAG,CAAC;EACrE,OAAO,IAAI,MAAM,KAAK,IAAI,KAAK,GAAG;;CAEpC,WAAW,KAAK,UAAU;EACxB,MAAM,aAAa,MAAM,QAAQ,IAAI;EACrC,MAAM,SAAS,OAAO,SAAS,eAAe,KAAK,QAAQ,MAAM,MAAM,GAAG,WAAW,EAAE,GAAG;EAC1F,MAAM,WAAW,eAAe,KAAK,QAAQ,MAAM,MAAM,aAAa,EAAE;EAExE,IAAI,IAAI,UAAU,QAAQ,OAAO;EAEjC,OAAO,IAAI,MAAM,GAAG,OAAO,GAAG;;CAEhC,mBAAmB,KAAK,UAAU;EAChC,MAAM,aAAa,MAAM,QAAQ,IAAI;EACrC,MAAM,QAAQ,OAAO,SAAS,eAAe,KAAK,QAAQ,MAAM,MAAM,GAAG,WAAW,EAAE,GAAG;EACzF,MAAM,WAAW,eAAe,KAAK,QAAQ,MAAM,MAAM,aAAa,EAAE;EACxE,MAAM,QAAQ,IAAI,MAAM,MAAM,CAAC,OAAO,QAAQ;EAE9C,IAAI,MAAM,UAAU,OAAO,OAAO;EAElC,OAAO,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,IAAI,GAAG;;CAE3C,eAAc,QAAO,mBAAmB,IAAI,QAAQ,OAAO,IAAI,CAAC;CAChE,eAAc,QAAO,mBAAmB,IAAI;CAC7C"}
1
+ {"version":3,"file":"defaults.js","names":[],"sources":["../../../src/transformers/filters/defaults.ts"],"sourcesContent":["export type FilterFunction = (str: string, value: string) => string\n\nconst escapeMap: Record<string, string> = {\n '\"': '&#34;',\n '&': '&amp;',\n \"'\": '&#39;',\n '<': '&lt;',\n '>': '&gt;',\n}\n\nconst escapeRegex = /[\"&'<>]/g\n\nfunction escapeHtml(str: string): string {\n return str.replace(escapeRegex, ch => escapeMap[ch])\n}\n\nexport const defaults: Record<string, FilterFunction> = {\n append: (str, value) => str + value,\n prepend: (str, value) => value + str,\n uppercase: str => str.toUpperCase(),\n lowercase: str => str.toLowerCase(),\n capitalize: str => str.charAt(0).toUpperCase() + str.slice(1),\n ceil: str => String(Math.ceil(Number.parseFloat(str))),\n floor: str => String(Math.floor(Number.parseFloat(str))),\n round: str => String(Math.round(Number.parseFloat(str))),\n escape: str => escapeHtml(str),\n 'escape-once': str => {\n const decoded = str\n .replace(/&amp;/g, '&')\n .replace(/&lt;/g, '<')\n .replace(/&gt;/g, '>')\n .replace(/&#34;/g, '\"')\n .replace(/&quot;/g, '\"')\n .replace(/&#39;/g, \"'\")\n .replace(/&apos;/g, \"'\")\n\n return escapeHtml(decoded)\n },\n lstrip: str => str.trimStart(),\n rstrip: str => str.trimEnd(),\n trim: str => str.trim(),\n minus: (str, value) => String(Number.parseFloat(str) - Number.parseFloat(value)),\n plus: (str, value) => String(Number.parseFloat(str) + Number.parseFloat(value)),\n multiply: (str, value) => String(Number.parseFloat(str) * Number.parseFloat(value)),\n times: (str, value) => String(Number.parseFloat(str) * Number.parseFloat(value)),\n 'divide-by': (str, value) => String(Number.parseFloat(str) / Number.parseFloat(value)),\n divide: (str, value) => String(Number.parseFloat(str) / Number.parseFloat(value)),\n modulo: (str, value) => String(Number.parseFloat(str) % Number.parseFloat(value)),\n 'newline-to-br': str => str.replace(/\\n/g, '<br>'),\n 'strip-newlines': str => str.replace(/\\n/g, ''),\n remove: (str, value) => str.split(value).join(''),\n 'remove-first': (str, value) => {\n const i = str.indexOf(value)\n return i === -1 ? str : str.slice(0, i) + str.slice(i + value.length)\n },\n replace: (str, value) => {\n const [search = '', replacement = ''] = value.split('|')\n return str.split(search).join(replacement)\n },\n 'replace-first': (str, value) => {\n const [search = '', replacement = ''] = value.split('|')\n const i = str.indexOf(search)\n return i === -1 ? str : str.slice(0, i) + replacement + str.slice(i + search.length)\n },\n size: str => String(str.length),\n slice: (str, value) => {\n const args = value.split(',').map(s => Number.parseInt(s.trim(), 10))\n return str.slice(args[0], args[1])\n },\n truncate: (str, value) => {\n const commaIndex = value.indexOf(',')\n const length = Number.parseInt(commaIndex === -1 ? value : value.slice(0, commaIndex), 10)\n const ellipsis = commaIndex === -1 ? '...' : value.slice(commaIndex + 1)\n\n if (str.length <= length) return str\n\n return str.slice(0, length) + ellipsis\n },\n 'truncate-words': (str, value) => {\n const commaIndex = value.indexOf(',')\n const count = Number.parseInt(commaIndex === -1 ? value : value.slice(0, commaIndex), 10)\n const ellipsis = commaIndex === -1 ? '...' : value.slice(commaIndex + 1)\n const words = str.split(/\\s+/).filter(Boolean)\n\n if (words.length <= count) return str\n\n return words.slice(0, count).join(' ') + ellipsis\n },\n 'url-decode': str => decodeURIComponent(str.replace(/\\+/g, ' ')),\n 'url-encode': str => encodeURIComponent(str),\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/transformers/filters/index.ts"],"mappings":";;;;KAMY,aAAA,WAAwB,MAAA,UAAgB,GAAA,UAAa,KAAA;;AAAjE;;;;;;;;;AA2CA;;;;;;;;;AASA;;;;;;;;;iBATgB,OAAA,CAAQ,IAAA,UAAc,MAAA,GAAQ,aAAA;;;;;;iBAS9B,UAAA,CAAW,GAAA,EAAK,SAAA,IAAa,MAAA,GAAQ,aAAA,GAAqB,SAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/transformers/filters/index.ts"],"mappings":";;;;KAMY,aAAA,WAAwB,MAAM,UAAU,GAAA,UAAa,KAAA;;AAAjE;;;;;;;;AAA8E;AA2C9E;;;;;;;;AAAgE;AAShE;;;;;;;;;iBATgB,OAAA,CAAQ,IAAA,UAAc,MAAA,GAAQ,aAAkB;;;;;AASmB;iBAAnE,UAAA,CAAW,GAAA,EAAK,SAAA,IAAa,MAAA,GAAQ,aAAA,GAAqB,SAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/transformers/filters/index.ts"],"sourcesContent":["import { Text } from 'domhandler'\nimport type { ChildNode, Element } from 'domhandler'\nimport { parse, serialize } from '../../utils/ast/index.ts'\nimport { defaults } from './defaults.ts'\n\nexport type { FilterFunction } from './defaults.ts'\nexport type FiltersConfig = false | Record<string, (str: string, value: string) => string>\n\n/**\n * Process children before parents so nested filter elements work correctly.\n */\nfunction walkBottomUp(nodes: ChildNode[], callback: (node: ChildNode) => void): void {\n for (const node of nodes.slice()) {\n if ('children' in node && node.children?.length) {\n walkBottomUp(node.children as ChildNode[], callback)\n }\n\n callback(node)\n }\n}\n\n/**\n * Apply transformation functions to the content of elements that have\n * matching filter attributes. Multiple filters on the same element are\n * executed in the order the attributes are defined.\n *\n * Default filters include string manipulation (`uppercase`, `lowercase`,\n * `trim`, etc.), math operations (`plus`, `minus`, `multiply`, etc.),\n * and more.\n *\n * @param html HTML string to transform.\n * @param custom Custom filters to merge with the defaults. Pass `false`\n * to disable all filters (including the defaults).\n * @returns The transformed HTML string.\n *\n * @example\n * import { filters } from '@maizzle/framework'\n *\n * // Defaults only\n * const out = filters('<p uppercase>foo</p>') // → '<p>FOO</p>'\n *\n * // Add a custom filter\n * filters('<p suffix=\" world\">hello</p>', {\n * suffix: (s, v) => s + v,\n * })\n *\n * // Disable all filters\n * filters('<p uppercase>foo</p>', false)\n */\nexport function filters(html: string, custom: FiltersConfig = {}): string {\n return serialize(filtersDom(parse(html), custom))\n}\n\n/**\n * DOM-form of {@link filters} used by the internal transformer pipeline.\n * Takes a parsed DOM, returns a parsed DOM — avoids redundant\n * serialize/parse round-trips when chained with other transformers.\n */\nexport function filtersDom(dom: ChildNode[], custom: FiltersConfig = {}): ChildNode[] {\n if (custom === false) return dom\n\n const allFilters = { ...defaults, ...custom }\n const filterNames = new Set(Object.keys(allFilters))\n\n walkBottomUp(dom, (node) => {\n const el = node as Element\n\n if (!el.attribs) return\n\n // Collect matching filter attributes in source order\n const matched: Array<{ name: string; value: string }> = []\n\n for (const attr of Object.keys(el.attribs)) {\n if (filterNames.has(attr)) {\n matched.push({ name: attr, value: el.attribs[attr] })\n }\n }\n\n if (matched.length === 0) return\n\n // Serialize children to get innerHTML\n let content = serialize(el.children as ChildNode[])\n\n // Apply each filter in attribute order\n for (const { name, value } of matched) {\n content = allFilters[name](content, value)\n delete el.attribs[name]\n }\n\n // Replace children with the filtered content\n if (content === '') {\n el.children = []\n } else if (/<[a-z/!]/i.test(content)) {\n // Result contains HTML elements — parse back to DOM\n const newChildren = parse(content)\n\n for (const child of newChildren) {\n child.parent = el as any\n }\n\n el.children = newChildren as ChildNode[]\n } else {\n // Text-only result — create a text node directly to preserve entity strings\n const textNode = new Text(content)\n textNode.parent = el as any\n el.children = [textNode]\n }\n })\n\n return dom\n}\n"],"mappings":";;;;;;;;;AAWA,SAAS,aAAa,OAAoB,UAA2C;CACnF,KAAK,MAAM,QAAQ,MAAM,OAAO,EAAE;EAChC,IAAI,cAAc,QAAQ,KAAK,UAAU,QACvC,aAAa,KAAK,UAAyB,SAAS;EAGtD,SAAS,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgClB,SAAgB,QAAQ,MAAc,SAAwB,EAAE,EAAU;CACxE,OAAO,UAAU,WAAW,MAAM,KAAK,EAAE,OAAO,CAAC;;;;;;;AAQnD,SAAgB,WAAW,KAAkB,SAAwB,EAAE,EAAe;CACpF,IAAI,WAAW,OAAO,OAAO;CAE7B,MAAM,aAAa;EAAE,GAAG;EAAU,GAAG;EAAQ;CAC7C,MAAM,cAAc,IAAI,IAAI,OAAO,KAAK,WAAW,CAAC;CAEpD,aAAa,MAAM,SAAS;EAC1B,MAAM,KAAK;EAEX,IAAI,CAAC,GAAG,SAAS;EAGjB,MAAM,UAAkD,EAAE;EAE1D,KAAK,MAAM,QAAQ,OAAO,KAAK,GAAG,QAAQ,EACxC,IAAI,YAAY,IAAI,KAAK,EACvB,QAAQ,KAAK;GAAE,MAAM;GAAM,OAAO,GAAG,QAAQ;GAAO,CAAC;EAIzD,IAAI,QAAQ,WAAW,GAAG;EAG1B,IAAI,UAAU,UAAU,GAAG,SAAwB;EAGnD,KAAK,MAAM,EAAE,MAAM,WAAW,SAAS;GACrC,UAAU,WAAW,MAAM,SAAS,MAAM;GAC1C,OAAO,GAAG,QAAQ;;EAIpB,IAAI,YAAY,IACd,GAAG,WAAW,EAAE;OACX,IAAI,YAAY,KAAK,QAAQ,EAAE;GAEpC,MAAM,cAAc,MAAM,QAAQ;GAElC,KAAK,MAAM,SAAS,aAClB,MAAM,SAAS;GAGjB,GAAG,WAAW;SACT;GAEL,MAAM,WAAW,IAAI,KAAK,QAAQ;GAClC,SAAS,SAAS;GAClB,GAAG,WAAW,CAAC,SAAS;;GAE1B;CAEF,OAAO"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/transformers/filters/index.ts"],"sourcesContent":["import { Text } from 'domhandler'\nimport type { ChildNode, Element } from 'domhandler'\nimport { parse, serialize } from '../../utils/ast/index.ts'\nimport { defaults } from './defaults.ts'\n\nexport type { FilterFunction } from './defaults.ts'\nexport type FiltersConfig = false | Record<string, (str: string, value: string) => string>\n\n/**\n * Process children before parents so nested filter elements work correctly.\n */\nfunction walkBottomUp(nodes: ChildNode[], callback: (node: ChildNode) => void): void {\n for (const node of nodes.slice()) {\n if ('children' in node && node.children?.length) {\n walkBottomUp(node.children as ChildNode[], callback)\n }\n\n callback(node)\n }\n}\n\n/**\n * Apply transformation functions to the content of elements that have\n * matching filter attributes. Multiple filters on the same element are\n * executed in the order the attributes are defined.\n *\n * Default filters include string manipulation (`uppercase`, `lowercase`,\n * `trim`, etc.), math operations (`plus`, `minus`, `multiply`, etc.),\n * and more.\n *\n * @param html HTML string to transform.\n * @param custom Custom filters to merge with the defaults. Pass `false`\n * to disable all filters (including the defaults).\n * @returns The transformed HTML string.\n *\n * @example\n * import { filters } from '@maizzle/framework'\n *\n * // Defaults only\n * const out = filters('<p uppercase>foo</p>') // → '<p>FOO</p>'\n *\n * // Add a custom filter\n * filters('<p suffix=\" world\">hello</p>', {\n * suffix: (s, v) => s + v,\n * })\n *\n * // Disable all filters\n * filters('<p uppercase>foo</p>', false)\n */\nexport function filters(html: string, custom: FiltersConfig = {}): string {\n return serialize(filtersDom(parse(html), custom))\n}\n\n/**\n * DOM-form of {@link filters} used by the internal transformer pipeline.\n * Takes a parsed DOM, returns a parsed DOM — avoids redundant\n * serialize/parse round-trips when chained with other transformers.\n */\nexport function filtersDom(dom: ChildNode[], custom: FiltersConfig = {}): ChildNode[] {\n if (custom === false) return dom\n\n const allFilters = { ...defaults, ...custom }\n const filterNames = new Set(Object.keys(allFilters))\n\n walkBottomUp(dom, (node) => {\n const el = node as Element\n\n if (!el.attribs) return\n\n // Collect matching filter attributes in source order\n const matched: Array<{ name: string; value: string }> = []\n\n for (const attr of Object.keys(el.attribs)) {\n if (filterNames.has(attr)) {\n matched.push({ name: attr, value: el.attribs[attr] })\n }\n }\n\n if (matched.length === 0) return\n\n // Serialize children to get innerHTML\n let content = serialize(el.children as ChildNode[])\n\n // Apply each filter in attribute order\n for (const { name, value } of matched) {\n content = allFilters[name](content, value)\n delete el.attribs[name]\n }\n\n // Replace children with the filtered content\n if (content === '') {\n el.children = []\n } else if (/<[a-z/!]/i.test(content)) {\n // Result contains HTML elements — parse back to DOM\n const newChildren = parse(content)\n\n for (const child of newChildren) {\n child.parent = el as any\n }\n\n el.children = newChildren as ChildNode[]\n } else {\n // Text-only result — create a text node directly to preserve entity strings\n const textNode = new Text(content)\n textNode.parent = el as any\n el.children = [textNode]\n }\n })\n\n return dom\n}\n"],"mappings":";;;;;;;;;AAWA,SAAS,aAAa,OAAoB,UAA2C;CACnF,KAAK,MAAM,QAAQ,MAAM,MAAM,GAAG;EAChC,IAAI,cAAc,QAAQ,KAAK,UAAU,QACvC,aAAa,KAAK,UAAyB,QAAQ;EAGrD,SAAS,IAAI;CACf;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BA,SAAgB,QAAQ,MAAc,SAAwB,CAAC,GAAW;CACxE,OAAO,UAAU,WAAW,MAAM,IAAI,GAAG,MAAM,CAAC;AAClD;;;;;;AAOA,SAAgB,WAAW,KAAkB,SAAwB,CAAC,GAAgB;CACpF,IAAI,WAAW,OAAO,OAAO;CAE7B,MAAM,aAAa;EAAE,GAAG;EAAU,GAAG;CAAO;CAC5C,MAAM,cAAc,IAAI,IAAI,OAAO,KAAK,UAAU,CAAC;CAEnD,aAAa,MAAM,SAAS;EAC1B,MAAM,KAAK;EAEX,IAAI,CAAC,GAAG,SAAS;EAGjB,MAAM,UAAkD,CAAC;EAEzD,KAAK,MAAM,QAAQ,OAAO,KAAK,GAAG,OAAO,GACvC,IAAI,YAAY,IAAI,IAAI,GACtB,QAAQ,KAAK;GAAE,MAAM;GAAM,OAAO,GAAG,QAAQ;EAAM,CAAC;EAIxD,IAAI,QAAQ,WAAW,GAAG;EAG1B,IAAI,UAAU,UAAU,GAAG,QAAuB;EAGlD,KAAK,MAAM,EAAE,MAAM,WAAW,SAAS;GACrC,UAAU,WAAW,MAAM,SAAS,KAAK;GACzC,OAAO,GAAG,QAAQ;EACpB;EAGA,IAAI,YAAY,IACd,GAAG,WAAW,CAAC;OACV,IAAI,YAAY,KAAK,OAAO,GAAG;GAEpC,MAAM,cAAc,MAAM,OAAO;GAEjC,KAAK,MAAM,SAAS,aAClB,MAAM,SAAS;GAGjB,GAAG,WAAW;EAChB,OAAO;GAEL,MAAM,WAAW,IAAI,KAAK,OAAO;GACjC,SAAS,SAAS;GAClB,GAAG,WAAW,CAAC,QAAQ;EACzB;CACF,CAAC;CAED,OAAO;AACT"}
@@ -1 +1 @@
1
- {"version":3,"file":"format.d.ts","names":[],"sources":["../../src/transformers/format.ts"],"mappings":";;;;AA2BA;;;;;;;;;;;;;;iBAAsB,MAAA,CAAO,IAAA,UAAc,OAAA,GAAS,eAAA,GAAqB,OAAA"}
1
+ {"version":3,"file":"format.d.ts","names":[],"sources":["../../src/transformers/format.ts"],"mappings":";;;;AA2BA;;;;;;;;;AAAgF;;;;;iBAA1D,MAAA,CAAO,IAAA,UAAc,OAAA,GAAS,eAAA,GAAqB,OAAO"}
@@ -1 +1 @@
1
- {"version":3,"file":"format.js","names":["oxfmt","merge"],"sources":["../../src/transformers/format.ts"],"sourcesContent":["import { format as oxfmt } from 'oxfmt'\nimport { defu as merge } from 'defu'\nimport type { FormatOptions } from 'oxfmt'\n\nexport type { FormatOptions } from 'oxfmt'\n\nconst DEFAULT_OPTIONS: FormatOptions = {\n printWidth: 320,\n htmlWhitespaceSensitivity: 'ignore',\n embeddedLanguageFormatting: 'off',\n}\n\n/**\n * Pretty-print an HTML string with `oxfmt`. Maizzle defaults\n * (`printWidth: 320`, `htmlWhitespaceSensitivity: 'ignore'`,\n * `embeddedLanguageFormatting: 'off'`) are merged underneath any options\n * you pass.\n *\n * @param html HTML string to format.\n * @param options [oxfmt `FormatOptions`](https://github.com/oxc-project/oxfmt).\n * @returns The formatted HTML string.\n *\n * @example\n * import { format } from '@maizzle/framework'\n *\n * const pretty = await format(html, { useTabs: true, tabWidth: 4 })\n */\nexport async function format(html: string, options: FormatOptions = {}): Promise<string> {\n const merged = merge(options, DEFAULT_OPTIONS)\n const result = await oxfmt('input.html', html, merged)\n return result.code\n}\n"],"mappings":";;;AAMA,MAAM,kBAAiC;CACrC,YAAY;CACZ,2BAA2B;CAC3B,4BAA4B;CAC7B;;;;;;;;;;;;;;;;AAiBD,eAAsB,OAAO,MAAc,UAAyB,EAAE,EAAmB;CAGvF,QAAO,MADcA,SAAM,cAAc,MAD1BC,OAAM,SAAS,gBACuB,CAAC,EACxC"}
1
+ {"version":3,"file":"format.js","names":["oxfmt","merge"],"sources":["../../src/transformers/format.ts"],"sourcesContent":["import { format as oxfmt } from 'oxfmt'\nimport { defu as merge } from 'defu'\nimport type { FormatOptions } from 'oxfmt'\n\nexport type { FormatOptions } from 'oxfmt'\n\nconst DEFAULT_OPTIONS: FormatOptions = {\n printWidth: 320,\n htmlWhitespaceSensitivity: 'ignore',\n embeddedLanguageFormatting: 'off',\n}\n\n/**\n * Pretty-print an HTML string with `oxfmt`. Maizzle defaults\n * (`printWidth: 320`, `htmlWhitespaceSensitivity: 'ignore'`,\n * `embeddedLanguageFormatting: 'off'`) are merged underneath any options\n * you pass.\n *\n * @param html HTML string to format.\n * @param options [oxfmt `FormatOptions`](https://github.com/oxc-project/oxfmt).\n * @returns The formatted HTML string.\n *\n * @example\n * import { format } from '@maizzle/framework'\n *\n * const pretty = await format(html, { useTabs: true, tabWidth: 4 })\n */\nexport async function format(html: string, options: FormatOptions = {}): Promise<string> {\n const merged = merge(options, DEFAULT_OPTIONS)\n const result = await oxfmt('input.html', html, merged)\n return result.code\n}\n"],"mappings":";;;AAMA,MAAM,kBAAiC;CACrC,YAAY;CACZ,2BAA2B;CAC3B,4BAA4B;AAC9B;;;;;;;;;;;;;;;;AAiBA,eAAsB,OAAO,MAAc,UAAyB,CAAC,GAAoB;CAGvF,QAAO,MADcA,SAAM,cAAc,MAD1BC,OAAM,SAAS,eACsB,CAAC,GACvC;AAChB"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/transformers/index.ts"],"mappings":";;;;;;AAuDA;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAsB,eAAA,CACpB,IAAA,UACA,MAAA,EAAQ,aAAA,EACR,QAAA,WACA,OAAA,WACA,cAAA,GAAiB,aAAA,KAChB,OAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/transformers/index.ts"],"mappings":";;;;;;AAuDA;;;;;;;;;;;;;;;;;AAMU;;;;;;;;;;;iBANY,eAAA,CACpB,IAAA,UACA,MAAA,EAAQ,aAAA,EACR,QAAA,WACA,OAAA,WACA,cAAA,GAAiB,aAAA,KAChB,OAAA"}
@@ -54,6 +54,18 @@ import { minify } from "./minify.js";
54
54
  * 16. Minify
55
55
  */
56
56
  async function runTransformers(html, config, filePath, doctype, tailwindBlocks) {
57
+ /**
58
+ * Per-transformer skip map — only honored when useTransformers is an object.
59
+ * Whole-pipeline opt-out (`useTransformers === false`) is handled upstream
60
+ * in build.ts / render so we never reach this function in that case.
61
+ *
62
+ * A toggle set to `true` *force-enables* its transformer for this run
63
+ * by layering on the matching config slice (e.g. `prettify: true`
64
+ * sets `html.format = true`). This only applies to transformers
65
+ * whose enable flag is a plain boolean — data-driven ones
66
+ * (filters, baseURL, urlQuery, etc.) need actual config
67
+ * values, so a bare `true` for those is a no-op.
68
+ */
57
69
  const toggles = typeof config.useTransformers === "object" ? config.useTransformers : null;
58
70
  const enabled = (key) => toggles?.[key] !== false;
59
71
  let effective = config;
@@ -125,7 +137,21 @@ async function runTransformers(html, config, filePath, doctype, tailwindBlocks)
125
137
  const minifyOptions = typeof effective.html.minify === "object" ? effective.html.minify : {};
126
138
  result = minify(result, minifyOptions);
127
139
  }
140
+ /**
141
+ * Strip self-closing slashes for HTML5 doctypes, but preserve content
142
+ * inside MSO conditional comments (XML-ish, case/syntax sensitive).
143
+ * MUST run BEFORE minifyCodeInline: at this point, CodeInline's
144
+ * shiki output is still marker-encoded (§MZLT§/§MZGT§), so any
145
+ * ` />` in the highlighted source code (e.g. a Vue self-close
146
+ * tag) hasn't materialized yet and can't be mistakenly
147
+ * stripped from inside a `<code>` element.
148
+ */
128
149
  if (!isXhtml) result = result.replace(/<!--\[if [^\]]*\]>[\s\S]*?<!\[endif\]-->|( \/>)/g, (match, selfClose) => selfClose ? ">" : match);
150
+ /**
151
+ * 16.5. Strip whitespace inside `data-minify-inline` markers (CodeInline's
152
+ * Shiki output, etc.). Runs after format/minify so it cleans up the
153
+ * pretty-printer's indentation between sibling tags.
154
+ */
129
155
  result = minifyCodeInline(result);
130
156
  return result;
131
157
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/transformers/index.ts"],"sourcesContent":["import { parse, serialize } from '../utils/ast/index.ts'\nimport { inlineLinkDom } from './inlineLink.ts'\nimport { tailwindComponent } from './tailwindComponent.ts'\nimport { tailwindcss } from './tailwindcss.ts'\nimport { safeSelectorsDom } from './safeSelectors.ts'\nimport { attributeToStyleDom } from './attributeToStyle.ts'\nimport { inlineCssDom } from './inlineCss.ts'\nimport { msoPlaceholders } from './msoPlaceholders.ts'\nimport { columnWidth } from './columnWidth.ts'\nimport { removeAttributesDom } from './removeAttributes.ts'\nimport { shorthandCssDom } from './shorthandCss.ts'\nimport { sixHexDom } from './sixHex.ts'\nimport { addAttributesDom } from './addAttributes.ts'\nimport { filtersDom } from './filters/index.ts'\nimport { baseDom } from './base.ts'\nimport { entitiesDom } from './entities.ts'\nimport { urlQueryDom } from './urlQuery.ts'\nimport { purgeCssDom } from './purgeCss.ts'\nimport { replaceStrings } from './replaceStrings.ts'\nimport { format } from './format.ts'\nimport { minifyCodeInline } from './minifyCodeInline.ts'\nimport { minify } from './minify.ts'\nimport type { MaizzleConfig } from '../types/config.ts'\nimport type { TailwindBlock } from '../composables/renderContext.ts'\n\n/**\n * Run all Maizzle transformers on the rendered HTML.\n *\n * The HTML is parsed into a DOM once at the start and passed through all\n * DOM-based transformers as a shared `ChildNode[]`. After all DOM transformers\n * complete, the DOM is serialized back to a string exactly once.\n *\n * String-only transformers (those that rely on external tools that require a\n * raw HTML string) then run on the serialized output.\n *\n * Transformers run in a specific order:\n * 0. Inline link stylesheets — replace `<link rel=\"stylesheet\">` with `<style>` tags\n * 1. Tailwind CSS — compile CSS, lower syntax, optimize (cleanup + merge media queries)\n * 2. Safe class names\n * 3. Attribute to style\n * 4. CSS inliner\n * 5. Remove attributes\n * 6. Shorthand CSS\n * 7. Six-digit HEX\n * 8. Add attributes\n * 9. Filters\n * 10. Base URL\n * 11. URL query\n * 12. Purge CSS (serializes/parses internally around email-comb)\n * 13. Entities\n * + Vue-generated comments stripped here (on serialized string)\n * 14. Replace strings\n * 15. Prettify\n * 16. Minify\n */\nexport async function runTransformers(\n html: string,\n config: MaizzleConfig,\n filePath?: string,\n doctype?: string,\n tailwindBlocks?: TailwindBlock[],\n): Promise<string> {\n // Per-transformer skip map — only honored when useTransformers is an object.\n // Whole-pipeline opt-out (`useTransformers === false`) is handled upstream\n // in build.ts / render so we never reach this function in that case.\n //\n // A toggle set to `true` *force-enables* its transformer for this run by\n // layering on the matching config slice (e.g. `prettify: true` sets\n // `html.format = true`). This only applies to transformers whose\n // enable flag is a plain boolean — data-driven ones (filters,\n // baseURL, urlQuery, etc.) need actual config values, so a\n // bare `true` for those is a no-op.\n const toggles = typeof config.useTransformers === 'object' ? config.useTransformers : null\n const enabled = (key: keyof NonNullable<typeof toggles>) => toggles?.[key] !== false\n\n let effective = config\n if (toggles) {\n const cssOver: Record<string, unknown> = {}\n const htmlOver: Record<string, unknown> = {}\n if (toggles.inlineCss === true) cssOver.inline = true\n if (toggles.purgeCss === true) cssOver.purge = true\n if (toggles.safeSelectors === true) cssOver.safe = true\n if (toggles.shorthandCss === true) cssOver.shorthand = true\n if (toggles.sixHex === true) cssOver.sixHex = true\n if (toggles.prettify === true) htmlOver.format = true\n if (toggles.minify === true) htmlOver.minify = true\n if (toggles.entities === true) htmlOver.decodeEntities = true\n\n if (Object.keys(cssOver).length || Object.keys(htmlOver).length) {\n effective = {\n ...config,\n css: { ...config.css, ...cssOver },\n html: { ...config.html, ...htmlOver },\n }\n }\n }\n\n // Parse once — all DOM transformers share this array\n let dom = parse(html)\n\n // 0. Inline <link> stylesheets\n dom = await inlineLinkDom(dom, filePath)\n\n // 0.5. <Tailwind> component — compile per-block scoped CSS, inject into <head>\n if (tailwindBlocks?.length) {\n dom = await tailwindComponent(dom, tailwindBlocks, effective, filePath)\n }\n\n // 1. Tailwind CSS — always runs first\n dom = await tailwindcss(dom, effective, filePath)\n\n // 2. Safe class names\n if (enabled('safeSelectors')) dom = safeSelectorsDom(dom, effective.css)\n\n // 3. Attribute to style\n if (enabled('attributeToStyle') && typeof effective.css?.inline === 'object' && effective.css.inline.attributeToStyle) {\n dom = attributeToStyleDom(dom, effective.css.inline.attributeToStyle)\n }\n\n // 4. CSS inliner (serializes/parses internally around juice)\n if (enabled('inlineCss') && effective.css?.inline) {\n const inlineOptions = typeof effective.css.inline === 'object' ? effective.css.inline : {}\n dom = inlineCssDom(dom, inlineOptions)\n }\n\n // 4.5. Resolve MSO placeholders (table width + td style) from inlined CSS\n dom = msoPlaceholders(dom)\n\n // 4.6. Resolve Column min-width placeholders from nearest sized ancestor\n dom = columnWidth(dom)\n\n // 5. Remove attributes\n if (enabled('removeAttributes')) {\n const removeRules = effective.html?.attributes?.remove\n dom = removeAttributesDom(dom, Array.isArray(removeRules) ? removeRules : [])\n }\n\n // 6. Shorthand CSS\n if (enabled('shorthandCss') && effective.css?.shorthand) {\n const shorthandOptions = typeof effective.css.shorthand === 'object' ? effective.css.shorthand : {}\n dom = shorthandCssDom(dom, shorthandOptions)\n }\n\n // 7. Six-digit HEX\n if (enabled('sixHex') && effective.css?.sixHex !== false) dom = sixHexDom(dom)\n\n // 8. Add attributes\n if (enabled('addAttributes')) dom = addAttributesDom(dom, effective.html?.attributes)\n\n // 9. Filters\n if (enabled('filters')) dom = filtersDom(dom, effective.filters)\n\n // 10. Base URL (serializes/parses internally for VML/MSO regex passes)\n if (enabled('baseURL') && effective.url?.base) dom = baseDom(dom, effective.url.base)\n\n // 11. URL query\n if (enabled('urlQuery') && effective.url?.query && Object.keys(effective.url.query).length > 0) {\n const { _options: queryOptions, ...queryParams } = effective.url.query as Record<string, unknown>\n dom = urlQueryDom(dom, queryParams, (queryOptions ?? {}) as import('../types/config.ts').UrlQueryOptions)\n }\n\n // 12. Remove unused CSS (serializes/parses internally around email-comb)\n if (enabled('purgeCss') && effective.css?.purge) {\n const purgeOptions = typeof effective.css.purge === 'object' ? effective.css.purge : {}\n dom = purgeCssDom(dom, purgeOptions)\n }\n\n // 13. Entities\n if (enabled('entities')) dom = entitiesDom(dom, effective.html?.decodeEntities)\n\n // Serialize once — remaining transformers operate on the HTML string\n const isXhtml = doctype ? /xhtml/i.test(doctype) : false\n let result = serialize(dom, { selfClosingTags: isXhtml })\n\n // 14. Replace strings\n if (enabled('replaceStrings')) result = replaceStrings(result, effective)\n\n // 15. Format — skipped when `minify` is enabled\n const minifyWillRun = enabled('minify') && !!effective.html?.minify\n if (enabled('prettify') && !minifyWillRun && effective.html?.format) {\n const formatOptions = typeof effective.html.format === 'object' ? effective.html.format : {}\n result = await format(result, formatOptions)\n }\n\n // 16. Minify\n if (enabled('minify') && effective.html?.minify) {\n const minifyOptions = typeof effective.html.minify === 'object' ? effective.html.minify : {}\n result = minify(result, minifyOptions)\n }\n\n // Strip self-closing slashes for HTML5 doctypes, but preserve content\n // inside MSO conditional comments (which are XML-ish and case/syntax sensitive).\n // MUST run BEFORE minifyCodeInline: at this point, CodeInline's shiki output\n // is still marker-encoded (§MZLT§/§MZGT§), so any ` />` in the highlighted\n // source code (e.g. a Vue self-closing tag) hasn't materialized yet and\n // can't be mistakenly stripped from inside a `<code>` element.\n if (!isXhtml) {\n result = result.replace(\n /<!--\\[if [^\\]]*\\]>[\\s\\S]*?<!\\[endif\\]-->|( \\/>)/g,\n (match, selfClose) => selfClose ? '>' : match,\n )\n }\n\n // 16.5. Strip whitespace inside `data-minify-inline` markers (CodeInline's\n // Shiki output, etc.). Runs after format/minify so it cleans up the\n // pretty-printer's indentation between sibling tags.\n result = minifyCodeInline(result)\n\n return result\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/transformers/index.ts"],"sourcesContent":["import { parse, serialize } from '../utils/ast/index.ts'\nimport { inlineLinkDom } from './inlineLink.ts'\nimport { tailwindComponent } from './tailwindComponent.ts'\nimport { tailwindcss } from './tailwindcss.ts'\nimport { safeSelectorsDom } from './safeSelectors.ts'\nimport { attributeToStyleDom } from './attributeToStyle.ts'\nimport { inlineCssDom } from './inlineCss.ts'\nimport { msoPlaceholders } from './msoPlaceholders.ts'\nimport { columnWidth } from './columnWidth.ts'\nimport { removeAttributesDom } from './removeAttributes.ts'\nimport { shorthandCssDom } from './shorthandCss.ts'\nimport { sixHexDom } from './sixHex.ts'\nimport { addAttributesDom } from './addAttributes.ts'\nimport { filtersDom } from './filters/index.ts'\nimport { baseDom } from './base.ts'\nimport { entitiesDom } from './entities.ts'\nimport { urlQueryDom } from './urlQuery.ts'\nimport { purgeCssDom } from './purgeCss.ts'\nimport { replaceStrings } from './replaceStrings.ts'\nimport { format } from './format.ts'\nimport { minifyCodeInline } from './minifyCodeInline.ts'\nimport { minify } from './minify.ts'\nimport type { MaizzleConfig } from '../types/config.ts'\nimport type { TailwindBlock } from '../composables/renderContext.ts'\n\n/**\n * Run all Maizzle transformers on the rendered HTML.\n *\n * The HTML is parsed into a DOM once at the start and passed through all\n * DOM-based transformers as a shared `ChildNode[]`. After all DOM transformers\n * complete, the DOM is serialized back to a string exactly once.\n *\n * String-only transformers (those that rely on external tools that require a\n * raw HTML string) then run on the serialized output.\n *\n * Transformers run in a specific order:\n * 0. Inline link stylesheets — replace `<link rel=\"stylesheet\">` with `<style>` tags\n * 1. Tailwind CSS — compile CSS, lower syntax, optimize (cleanup + merge media queries)\n * 2. Safe class names\n * 3. Attribute to style\n * 4. CSS inliner\n * 5. Remove attributes\n * 6. Shorthand CSS\n * 7. Six-digit HEX\n * 8. Add attributes\n * 9. Filters\n * 10. Base URL\n * 11. URL query\n * 12. Purge CSS (serializes/parses internally around email-comb)\n * 13. Entities\n * + Vue-generated comments stripped here (on serialized string)\n * 14. Replace strings\n * 15. Prettify\n * 16. Minify\n */\nexport async function runTransformers(\n html: string,\n config: MaizzleConfig,\n filePath?: string,\n doctype?: string,\n tailwindBlocks?: TailwindBlock[],\n): Promise<string> {\n /**\n * Per-transformer skip map — only honored when useTransformers is an object.\n * Whole-pipeline opt-out (`useTransformers === false`) is handled upstream\n * in build.ts / render so we never reach this function in that case.\n *\n * A toggle set to `true` *force-enables* its transformer for this run\n * by layering on the matching config slice (e.g. `prettify: true`\n * sets `html.format = true`). This only applies to transformers\n * whose enable flag is a plain boolean — data-driven ones\n * (filters, baseURL, urlQuery, etc.) need actual config\n * values, so a bare `true` for those is a no-op.\n */\n const toggles = typeof config.useTransformers === 'object' ? config.useTransformers : null\n const enabled = (key: keyof NonNullable<typeof toggles>) => toggles?.[key] !== false\n\n let effective = config\n if (toggles) {\n const cssOver: Record<string, unknown> = {}\n const htmlOver: Record<string, unknown> = {}\n if (toggles.inlineCss === true) cssOver.inline = true\n if (toggles.purgeCss === true) cssOver.purge = true\n if (toggles.safeSelectors === true) cssOver.safe = true\n if (toggles.shorthandCss === true) cssOver.shorthand = true\n if (toggles.sixHex === true) cssOver.sixHex = true\n if (toggles.prettify === true) htmlOver.format = true\n if (toggles.minify === true) htmlOver.minify = true\n if (toggles.entities === true) htmlOver.decodeEntities = true\n\n if (Object.keys(cssOver).length || Object.keys(htmlOver).length) {\n effective = {\n ...config,\n css: { ...config.css, ...cssOver },\n html: { ...config.html, ...htmlOver },\n }\n }\n }\n\n // Parse once — all DOM transformers share this array\n let dom = parse(html)\n\n // 0. Inline <link> stylesheets\n dom = await inlineLinkDom(dom, filePath)\n\n // 0.5. <Tailwind> component — compile per-block scoped CSS, inject into <head>\n if (tailwindBlocks?.length) {\n dom = await tailwindComponent(dom, tailwindBlocks, effective, filePath)\n }\n\n // 1. Tailwind CSS — always runs first\n dom = await tailwindcss(dom, effective, filePath)\n\n // 2. Safe class names\n if (enabled('safeSelectors')) dom = safeSelectorsDom(dom, effective.css)\n\n // 3. Attribute to style\n if (enabled('attributeToStyle') && typeof effective.css?.inline === 'object' && effective.css.inline.attributeToStyle) {\n dom = attributeToStyleDom(dom, effective.css.inline.attributeToStyle)\n }\n\n // 4. CSS inliner (serializes/parses internally around juice)\n if (enabled('inlineCss') && effective.css?.inline) {\n const inlineOptions = typeof effective.css.inline === 'object' ? effective.css.inline : {}\n dom = inlineCssDom(dom, inlineOptions)\n }\n\n // 4.5. Resolve MSO placeholders (table width + td style) from inlined CSS\n dom = msoPlaceholders(dom)\n\n // 4.6. Resolve Column min-width placeholders from nearest sized ancestor\n dom = columnWidth(dom)\n\n // 5. Remove attributes\n if (enabled('removeAttributes')) {\n const removeRules = effective.html?.attributes?.remove\n dom = removeAttributesDom(dom, Array.isArray(removeRules) ? removeRules : [])\n }\n\n // 6. Shorthand CSS\n if (enabled('shorthandCss') && effective.css?.shorthand) {\n const shorthandOptions = typeof effective.css.shorthand === 'object' ? effective.css.shorthand : {}\n dom = shorthandCssDom(dom, shorthandOptions)\n }\n\n // 7. Six-digit HEX\n if (enabled('sixHex') && effective.css?.sixHex !== false) dom = sixHexDom(dom)\n\n // 8. Add attributes\n if (enabled('addAttributes')) dom = addAttributesDom(dom, effective.html?.attributes)\n\n // 9. Filters\n if (enabled('filters')) dom = filtersDom(dom, effective.filters)\n\n // 10. Base URL (serializes/parses internally for VML/MSO regex passes)\n if (enabled('baseURL') && effective.url?.base) dom = baseDom(dom, effective.url.base)\n\n // 11. URL query\n if (enabled('urlQuery') && effective.url?.query && Object.keys(effective.url.query).length > 0) {\n const { _options: queryOptions, ...queryParams } = effective.url.query as Record<string, unknown>\n dom = urlQueryDom(dom, queryParams, (queryOptions ?? {}) as import('../types/config.ts').UrlQueryOptions)\n }\n\n // 12. Remove unused CSS (serializes/parses internally around email-comb)\n if (enabled('purgeCss') && effective.css?.purge) {\n const purgeOptions = typeof effective.css.purge === 'object' ? effective.css.purge : {}\n dom = purgeCssDom(dom, purgeOptions)\n }\n\n // 13. Entities\n if (enabled('entities')) dom = entitiesDom(dom, effective.html?.decodeEntities)\n\n // Serialize once — remaining transformers operate on the HTML string\n const isXhtml = doctype ? /xhtml/i.test(doctype) : false\n let result = serialize(dom, { selfClosingTags: isXhtml })\n\n // 14. Replace strings\n if (enabled('replaceStrings')) result = replaceStrings(result, effective)\n\n // 15. Format — skipped when `minify` is enabled\n const minifyWillRun = enabled('minify') && !!effective.html?.minify\n if (enabled('prettify') && !minifyWillRun && effective.html?.format) {\n const formatOptions = typeof effective.html.format === 'object' ? effective.html.format : {}\n result = await format(result, formatOptions)\n }\n\n // 16. Minify\n if (enabled('minify') && effective.html?.minify) {\n const minifyOptions = typeof effective.html.minify === 'object' ? effective.html.minify : {}\n result = minify(result, minifyOptions)\n }\n\n /**\n * Strip self-closing slashes for HTML5 doctypes, but preserve content\n * inside MSO conditional comments (XML-ish, case/syntax sensitive).\n * MUST run BEFORE minifyCodeInline: at this point, CodeInline's\n * shiki output is still marker-encoded (§MZLT§/§MZGT§), so any\n * ` />` in the highlighted source code (e.g. a Vue self-close\n * tag) hasn't materialized yet and can't be mistakenly\n * stripped from inside a `<code>` element.\n */\n if (!isXhtml) {\n result = result.replace(\n /<!--\\[if [^\\]]*\\]>[\\s\\S]*?<!\\[endif\\]-->|( \\/>)/g,\n (match, selfClose) => selfClose ? '>' : match,\n )\n }\n\n /**\n * 16.5. Strip whitespace inside `data-minify-inline` markers (CodeInline's\n * Shiki output, etc.). Runs after format/minify so it cleans up the\n * pretty-printer's indentation between sibling tags.\n */\n result = minifyCodeInline(result)\n\n return result\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"inlineCss.d.ts","names":[],"sources":["../../src/transformers/inlineCss.ts"],"mappings":";;;;;;AASA;;UAAiB,gBAAA,SAAyB,OAAA;EAuBrB;;;;;EAjBnB,oBAAA;EAAA;;;;;;EAOA,QAAA;EA6BA;;;;EAxBA,SAAA;EA6BgD;;AAiBlD;;EAzCE,gBAAA,GAAmB,MAAA;EAyCiD;;;;EApCpE,kBAAA;EAoCoE;AAStE;;;;;EAtCE,aAAA;EAsCuF;;;;;;EA/BvF,cAAA;EA+BuF;;;;EA1BvF,UAAA,GAAa,MAAA;IAAiB,KAAA;IAAe,GAAA;EAAA;AAAA;;;;;;;;;;;;;;;iBAiB/B,SAAA,CAAU,IAAA,UAAc,OAAA,GAAS,gBAAA;;;;;;iBASjC,YAAA,CAAa,GAAA,EAAK,SAAA,IAAa,OAAA,GAAS,gBAAA,GAAwB,SAAA"}
1
+ {"version":3,"file":"inlineCss.d.ts","names":[],"sources":["../../src/transformers/inlineCss.ts"],"mappings":";;;;;;AASA;;UAAiB,gBAAA,SAAyB,OAAA;EAuBrB;;;;;EAjBnB,oBAAA;EAAA;;;;;;EAOA,QAAA;EA6BA;;;;EAxBA,SAAA;EA6BgD;AAAA;AAiBlD;;EAzCE,gBAAA,GAAmB,MAAA;EAyCiD;;;;EApCpE,kBAAA;EAoCoE;AAStE;;;;;EAtCE,aAAA;EAsCuF;;;;;;EA/BvF,cAAA;EA+BuF;AAAA;;;EA1BvF,UAAA,GAAa,MAAA;IAAiB,KAAA;IAAe,GAAA;EAAA;AAAA;;;;;;;;;;;;;;;iBAiB/B,SAAA,CAAU,IAAA,UAAc,OAAA,GAAS,gBAAqB;;;;;;iBAStD,YAAA,CAAa,GAAA,EAAK,SAAA,IAAa,OAAA,GAAS,gBAAA,GAAwB,SAAA"}
@@ -35,11 +35,28 @@ function inlineCssDom(dom, options = {}) {
35
35
  if (codeBlocks && typeof codeBlocks === "object") Object.entries(codeBlocks).forEach(([key, value]) => {
36
36
  if (value.start && value.end) juice.codeBlocks[key] = value;
37
37
  });
38
+ /**
39
+ * Handle style tags with embed attributes. We add a marker attribute
40
+ * that persists through the pipeline, then restore data-embed from
41
+ * it after Juice runs. `amp-custom` (AMP4Email's CSS attribute)
42
+ * is treated the same as embed: contents are preserved,
43
+ * never inlined.
44
+ */
38
45
  walk(dom, (node) => {
39
46
  const el = node;
40
47
  if (el.name === "style" && el.attribs) {
48
+ /**
49
+ * `amp-custom` → tell juice to skip via data-embed, but don't mirror
50
+ * back to `embed` (user wrote amp-custom, that's what stays in
51
+ * output).
52
+ */
53
+ if ("amp-custom" in el.attribs && !("data-embed" in el.attribs)) el.attribs["data-embed"] = "";
54
+ /**
55
+ * Sync data-embed ↔ embed. Use `in` so presence-only attrs
56
+ * (<style embed> → attribs.embed === '') still count.
57
+ */
41
58
  if ("embed" in el.attribs && !("data-embed" in el.attribs)) el.attribs["data-embed"] = "";
42
- if ("data-embed" in el.attribs && !("embed" in el.attribs)) el.attribs.embed = "";
59
+ if ("data-embed" in el.attribs && !("embed" in el.attribs) && !("amp-custom" in el.attribs)) el.attribs.embed = "";
43
60
  if ("data-embed" in el.attribs) el.attribs["data-maizzle-embed"] = "";
44
61
  }
45
62
  });
@@ -73,7 +90,13 @@ function inlineCssDom(dom, options = {}) {
73
90
  walk(result, (node) => {
74
91
  const el = node;
75
92
  if (el.name === "style" && el.attribs && "data-maizzle-embed" in el.attribs) {
76
- el.attribs.embed = "";
93
+ /**
94
+ * Only restore `embed` when the original signal was embed/data-embed —
95
+ * an `amp-custom` style was tagged for juice's benefit only and
96
+ * must not pick up a stray `embed` attribute in the rendered
97
+ * output.
98
+ */
99
+ if (!("amp-custom" in el.attribs)) el.attribs.embed = "";
77
100
  delete el.attribs["data-embed"];
78
101
  delete el.attribs["data-maizzle-embed"];
79
102
  }
@@ -1 +1 @@
1
- {"version":3,"file":"inlineCss.js","names":[],"sources":["../../src/transformers/inlineCss.ts"],"sourcesContent":["import juice from 'juice'\nimport { walk, parse, serialize } from '../utils/ast/index.ts'\nimport type { ChildNode, Element } from 'domhandler'\nimport type { Options as JuiceOptions } from 'juice'\n\n/**\n * Options for the `inlineCss` transformer. Accepts every Juice option plus a\n * handful of Maizzle-specific extras.\n */\nexport interface InlineCssOptions extends JuiceOptions {\n /**\n * Convert `0px`, `0em` etc. to `0` in inline styles.\n *\n * @default true\n */\n preferUnitlessValues?: boolean\n /**\n * CSS selectors to preserve in `<style>` tags, even after inlining.\n * Mapped to Juice's `preservedSelectors` option.\n *\n * @default []\n */\n safelist?: string[]\n /**\n * Additional CSS string to inline alongside `<style>` tag contents.\n * Mapped to Juice's `extraCss` option.\n */\n customCSS?: string\n /**\n * Duplicate CSS properties to HTML attributes.\n * Mapped to Juice's static `styleToAttribute` property.\n */\n styleToAttribute?: Record<string, string>\n /**\n * CSS properties to exclude from inlining.\n * Mapped to Juice's static `excludedProperties` property.\n */\n excludedProperties?: string[]\n /**\n * Elements that can receive `width` HTML attributes.\n * Mapped to Juice's static `widthElements` property.\n *\n * @default ['img', 'video']\n */\n widthElements?: string[]\n /**\n * Elements that can receive `height` HTML attributes.\n * Mapped to Juice's static `heightElements` property.\n *\n * @default ['img', 'video']\n */\n heightElements?: string[]\n /**\n * Template language code blocks to preserve during inlining.\n * Mapped to Juice's static `codeBlocks` property.\n */\n codeBlocks?: Record<string, { start: string; end: string }>\n}\n\n/**\n * Inline CSS from `<style>` tags into `style` attributes on matching elements.\n *\n * @param html HTML string to transform.\n * @param options Juice options plus Maizzle-specific extras.\n * @returns The transformed HTML string.\n *\n * @example\n * import { inlineCss } from '@maizzle/framework'\n *\n * const out = inlineCss('<style>.red{color:red}</style><p class=\"red\">x</p>', {\n * removeStyleTags: true,\n * })\n */\nexport function inlineCss(html: string, options: InlineCssOptions = {}): string {\n return serialize(inlineCssDom(parse(html), options))\n}\n\n/**\n * DOM-form of {@link inlineCss} used by the internal transformer pipeline.\n * Takes a parsed DOM, returns a parsed DOM — avoids the redundant\n * serialize/parse round-trips when chained with other transformers.\n */\nexport function inlineCssDom(dom: ChildNode[], options: InlineCssOptions = {}): ChildNode[] {\n const {\n preferUnitlessValues = true,\n safelist,\n customCSS = '',\n styleToAttribute,\n excludedProperties,\n widthElements,\n heightElements,\n codeBlocks,\n ...juicePassthrough\n } = options\n\n // Configure Juice static properties\n juice.styleToAttribute = styleToAttribute ?? {}\n juice.excludedProperties = ['--tw-shadow', ...(excludedProperties ?? [])]\n juice.widthElements = (widthElements ?? ['img', 'video']).map(i => i.toUpperCase()) as unknown as HTMLElement[]\n juice.heightElements = (heightElements ?? ['img', 'video']).map(i => i.toUpperCase()) as unknown as HTMLElement[]\n\n // Add custom code blocks\n if (codeBlocks && typeof codeBlocks === 'object') {\n Object.entries(codeBlocks).forEach(([key, value]) => {\n if (value.start && value.end) {\n juice.codeBlocks[key] = value\n }\n })\n }\n\n // Handle style tags with embed attributes.\n // We add a marker attribute that persists through the pipeline,\n // then restore data-embed from it after Juice runs.\n walk(dom, (node) => {\n const el = node as Element\n if (el.name === 'style' && el.attribs) {\n // Sync data-embed ↔ embed. Use `in` so presence-only attrs\n // (<style embed> → attribs.embed === '') still count.\n if ('embed' in el.attribs && !('data-embed' in el.attribs)) {\n el.attribs['data-embed'] = ''\n }\n if ('data-embed' in el.attribs && !('embed' in el.attribs)) {\n el.attribs.embed = ''\n }\n\n // Add marker that persists through the pipeline\n if ('data-embed' in el.attribs) {\n el.attribs['data-maizzle-embed'] = ''\n }\n }\n })\n\n // Serialize for juice (juice requires a string)\n const serialized = serialize(dom)\n\n let inlinedHtml: string\n\n try {\n const juiceOptions: JuiceOptions = {\n removeStyleTags: juicePassthrough.removeStyleTags ?? false,\n removeInlinedSelectors: juicePassthrough.removeInlinedSelectors ?? true,\n applyWidthAttributes: juicePassthrough.applyWidthAttributes ?? true,\n applyHeightAttributes: juicePassthrough.applyHeightAttributes ?? true,\n preservedSelectors: safelist ?? [],\n ...customCSS ? { extraCss: customCSS } : {},\n inlineDuplicateProperties: juicePassthrough.inlineDuplicateProperties ?? true,\n ...juicePassthrough,\n }\n\n inlinedHtml = juice(serialized, juiceOptions)\n } catch {\n // If Juice fails, return the dom unchanged\n return dom\n }\n\n const result = parse(inlinedHtml)\n\n if (preferUnitlessValues) {\n walk(result, (node) => {\n const el = node as Element\n if (el.attribs?.style) {\n el.attribs.style = el.attribs.style.replace(\n /\\b0(px|rem|em|%|vh|vw|vmin|vmax|in|cm|mm|pt|pc|ex|ch)\\b/g,\n '0'\n )\n }\n })\n }\n\n /**\n * Restore `embed` from our marker so the purge step can detect\n * these tags and skip them. Drop `data-embed` (juice's name)\n * since it's redundant once `embed` is back, and let purge\n * strip `embed` itself at the end of its run.\n */\n walk(result, (node) => {\n const el = node as Element\n if (el.name === 'style' && el.attribs && 'data-maizzle-embed' in el.attribs) {\n el.attribs.embed = ''\n delete el.attribs['data-embed']\n delete el.attribs['data-maizzle-embed']\n }\n })\n\n return result\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAyEA,SAAgB,UAAU,MAAc,UAA4B,EAAE,EAAU;CAC9E,OAAO,UAAU,aAAa,MAAM,KAAK,EAAE,QAAQ,CAAC;;;;;;;AAQtD,SAAgB,aAAa,KAAkB,UAA4B,EAAE,EAAe;CAC1F,MAAM,EACJ,uBAAuB,MACvB,UACA,YAAY,IACZ,kBACA,oBACA,eACA,gBACA,YACA,GAAG,qBACD;CAGJ,MAAM,mBAAmB,oBAAoB,EAAE;CAC/C,MAAM,qBAAqB,CAAC,eAAe,GAAI,sBAAsB,EAAE,CAAE;CACzE,MAAM,iBAAiB,iBAAiB,CAAC,OAAO,QAAQ,EAAE,KAAI,MAAK,EAAE,aAAa,CAAC;CACnF,MAAM,kBAAkB,kBAAkB,CAAC,OAAO,QAAQ,EAAE,KAAI,MAAK,EAAE,aAAa,CAAC;CAGrF,IAAI,cAAc,OAAO,eAAe,UACtC,OAAO,QAAQ,WAAW,CAAC,SAAS,CAAC,KAAK,WAAW;EACnD,IAAI,MAAM,SAAS,MAAM,KACvB,MAAM,WAAW,OAAO;GAE1B;CAMJ,KAAK,MAAM,SAAS;EAClB,MAAM,KAAK;EACX,IAAI,GAAG,SAAS,WAAW,GAAG,SAAS;GAGrC,IAAI,WAAW,GAAG,WAAW,EAAE,gBAAgB,GAAG,UAChD,GAAG,QAAQ,gBAAgB;GAE7B,IAAI,gBAAgB,GAAG,WAAW,EAAE,WAAW,GAAG,UAChD,GAAG,QAAQ,QAAQ;GAIrB,IAAI,gBAAgB,GAAG,SACrB,GAAG,QAAQ,wBAAwB;;GAGvC;CAGF,MAAM,aAAa,UAAU,IAAI;CAEjC,IAAI;CAEJ,IAAI;EAYF,cAAc,MAAM,YAAY;GAV9B,iBAAiB,iBAAiB,mBAAmB;GACrD,wBAAwB,iBAAiB,0BAA0B;GACnE,sBAAsB,iBAAiB,wBAAwB;GAC/D,uBAAuB,iBAAiB,yBAAyB;GACjE,oBAAoB,YAAY,EAAE;GAClC,GAAG,YAAY,EAAE,UAAU,WAAW,GAAG,EAAE;GAC3C,2BAA2B,iBAAiB,6BAA6B;GACzE,GAAG;GAGuC,CAAC;SACvC;EAEN,OAAO;;CAGT,MAAM,SAAS,MAAM,YAAY;CAEjC,IAAI,sBACF,KAAK,SAAS,SAAS;EACrB,MAAM,KAAK;EACX,IAAI,GAAG,SAAS,OACd,GAAG,QAAQ,QAAQ,GAAG,QAAQ,MAAM,QAClC,4DACA,IACD;GAEH;;;;;;;CASJ,KAAK,SAAS,SAAS;EACrB,MAAM,KAAK;EACX,IAAI,GAAG,SAAS,WAAW,GAAG,WAAW,wBAAwB,GAAG,SAAS;GAC3E,GAAG,QAAQ,QAAQ;GACnB,OAAO,GAAG,QAAQ;GAClB,OAAO,GAAG,QAAQ;;GAEpB;CAEF,OAAO"}
1
+ {"version":3,"file":"inlineCss.js","names":[],"sources":["../../src/transformers/inlineCss.ts"],"sourcesContent":["import juice from 'juice'\nimport { walk, parse, serialize } from '../utils/ast/index.ts'\nimport type { ChildNode, Element } from 'domhandler'\nimport type { Options as JuiceOptions } from 'juice'\n\n/**\n * Options for the `inlineCss` transformer. Accepts every Juice option plus a\n * handful of Maizzle-specific extras.\n */\nexport interface InlineCssOptions extends JuiceOptions {\n /**\n * Convert `0px`, `0em` etc. to `0` in inline styles.\n *\n * @default true\n */\n preferUnitlessValues?: boolean\n /**\n * CSS selectors to preserve in `<style>` tags, even after inlining.\n * Mapped to Juice's `preservedSelectors` option.\n *\n * @default []\n */\n safelist?: string[]\n /**\n * Additional CSS string to inline alongside `<style>` tag contents.\n * Mapped to Juice's `extraCss` option.\n */\n customCSS?: string\n /**\n * Duplicate CSS properties to HTML attributes.\n * Mapped to Juice's static `styleToAttribute` property.\n */\n styleToAttribute?: Record<string, string>\n /**\n * CSS properties to exclude from inlining.\n * Mapped to Juice's static `excludedProperties` property.\n */\n excludedProperties?: string[]\n /**\n * Elements that can receive `width` HTML attributes.\n * Mapped to Juice's static `widthElements` property.\n *\n * @default ['img', 'video']\n */\n widthElements?: string[]\n /**\n * Elements that can receive `height` HTML attributes.\n * Mapped to Juice's static `heightElements` property.\n *\n * @default ['img', 'video']\n */\n heightElements?: string[]\n /**\n * Template language code blocks to preserve during inlining.\n * Mapped to Juice's static `codeBlocks` property.\n */\n codeBlocks?: Record<string, { start: string; end: string }>\n}\n\n/**\n * Inline CSS from `<style>` tags into `style` attributes on matching elements.\n *\n * @param html HTML string to transform.\n * @param options Juice options plus Maizzle-specific extras.\n * @returns The transformed HTML string.\n *\n * @example\n * import { inlineCss } from '@maizzle/framework'\n *\n * const out = inlineCss('<style>.red{color:red}</style><p class=\"red\">x</p>', {\n * removeStyleTags: true,\n * })\n */\nexport function inlineCss(html: string, options: InlineCssOptions = {}): string {\n return serialize(inlineCssDom(parse(html), options))\n}\n\n/**\n * DOM-form of {@link inlineCss} used by the internal transformer pipeline.\n * Takes a parsed DOM, returns a parsed DOM — avoids the redundant\n * serialize/parse round-trips when chained with other transformers.\n */\nexport function inlineCssDom(dom: ChildNode[], options: InlineCssOptions = {}): ChildNode[] {\n const {\n preferUnitlessValues = true,\n safelist,\n customCSS = '',\n styleToAttribute,\n excludedProperties,\n widthElements,\n heightElements,\n codeBlocks,\n ...juicePassthrough\n } = options\n\n // Configure Juice static properties\n juice.styleToAttribute = styleToAttribute ?? {}\n juice.excludedProperties = ['--tw-shadow', ...(excludedProperties ?? [])]\n juice.widthElements = (widthElements ?? ['img', 'video']).map(i => i.toUpperCase()) as unknown as HTMLElement[]\n juice.heightElements = (heightElements ?? ['img', 'video']).map(i => i.toUpperCase()) as unknown as HTMLElement[]\n\n // Add custom code blocks\n if (codeBlocks && typeof codeBlocks === 'object') {\n Object.entries(codeBlocks).forEach(([key, value]) => {\n if (value.start && value.end) {\n juice.codeBlocks[key] = value\n }\n })\n }\n\n /**\n * Handle style tags with embed attributes. We add a marker attribute\n * that persists through the pipeline, then restore data-embed from\n * it after Juice runs. `amp-custom` (AMP4Email's CSS attribute)\n * is treated the same as embed: contents are preserved,\n * never inlined.\n */\n walk(dom, (node) => {\n const el = node as Element\n if (el.name === 'style' && el.attribs) {\n /**\n * `amp-custom` → tell juice to skip via data-embed, but don't mirror\n * back to `embed` (user wrote amp-custom, that's what stays in\n * output).\n */\n if ('amp-custom' in el.attribs && !('data-embed' in el.attribs)) {\n el.attribs['data-embed'] = ''\n }\n /**\n * Sync data-embed ↔ embed. Use `in` so presence-only attrs\n * (<style embed> → attribs.embed === '') still count.\n */\n if ('embed' in el.attribs && !('data-embed' in el.attribs)) {\n el.attribs['data-embed'] = ''\n }\n if ('data-embed' in el.attribs && !('embed' in el.attribs) && !('amp-custom' in el.attribs)) {\n el.attribs.embed = ''\n }\n\n // Add marker that persists through the pipeline\n if ('data-embed' in el.attribs) {\n el.attribs['data-maizzle-embed'] = ''\n }\n }\n })\n\n // Serialize for juice (juice requires a string)\n const serialized = serialize(dom)\n\n let inlinedHtml: string\n\n try {\n const juiceOptions: JuiceOptions = {\n removeStyleTags: juicePassthrough.removeStyleTags ?? false,\n removeInlinedSelectors: juicePassthrough.removeInlinedSelectors ?? true,\n applyWidthAttributes: juicePassthrough.applyWidthAttributes ?? true,\n applyHeightAttributes: juicePassthrough.applyHeightAttributes ?? true,\n preservedSelectors: safelist ?? [],\n ...customCSS ? { extraCss: customCSS } : {},\n inlineDuplicateProperties: juicePassthrough.inlineDuplicateProperties ?? true,\n ...juicePassthrough,\n }\n\n inlinedHtml = juice(serialized, juiceOptions)\n } catch {\n // If Juice fails, return the dom unchanged\n return dom\n }\n\n const result = parse(inlinedHtml)\n\n if (preferUnitlessValues) {\n walk(result, (node) => {\n const el = node as Element\n if (el.attribs?.style) {\n el.attribs.style = el.attribs.style.replace(\n /\\b0(px|rem|em|%|vh|vw|vmin|vmax|in|cm|mm|pt|pc|ex|ch)\\b/g,\n '0'\n )\n }\n })\n }\n\n /**\n * Restore `embed` from our marker so the purge step can detect\n * these tags and skip them. Drop `data-embed` (juice's name)\n * since it's redundant once `embed` is back, and let purge\n * strip `embed` itself at the end of its run.\n */\n walk(result, (node) => {\n const el = node as Element\n if (el.name === 'style' && el.attribs && 'data-maizzle-embed' in el.attribs) {\n /**\n * Only restore `embed` when the original signal was embed/data-embed —\n * an `amp-custom` style was tagged for juice's benefit only and\n * must not pick up a stray `embed` attribute in the rendered\n * output.\n */\n if (!('amp-custom' in el.attribs)) {\n el.attribs.embed = ''\n }\n delete el.attribs['data-embed']\n delete el.attribs['data-maizzle-embed']\n }\n })\n\n return result\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAyEA,SAAgB,UAAU,MAAc,UAA4B,CAAC,GAAW;CAC9E,OAAO,UAAU,aAAa,MAAM,IAAI,GAAG,OAAO,CAAC;AACrD;;;;;;AAOA,SAAgB,aAAa,KAAkB,UAA4B,CAAC,GAAgB;CAC1F,MAAM,EACJ,uBAAuB,MACvB,UACA,YAAY,IACZ,kBACA,oBACA,eACA,gBACA,YACA,GAAG,qBACD;CAGJ,MAAM,mBAAmB,oBAAoB,CAAC;CAC9C,MAAM,qBAAqB,CAAC,eAAe,GAAI,sBAAsB,CAAC,CAAE;CACxE,MAAM,iBAAiB,iBAAiB,CAAC,OAAO,OAAO,GAAG,KAAI,MAAK,EAAE,YAAY,CAAC;CAClF,MAAM,kBAAkB,kBAAkB,CAAC,OAAO,OAAO,GAAG,KAAI,MAAK,EAAE,YAAY,CAAC;CAGpF,IAAI,cAAc,OAAO,eAAe,UACtC,OAAO,QAAQ,UAAU,EAAE,SAAS,CAAC,KAAK,WAAW;EACnD,IAAI,MAAM,SAAS,MAAM,KACvB,MAAM,WAAW,OAAO;CAE5B,CAAC;;;;;;;;CAUH,KAAK,MAAM,SAAS;EAClB,MAAM,KAAK;EACX,IAAI,GAAG,SAAS,WAAW,GAAG,SAAS;;;;;;GAMrC,IAAI,gBAAgB,GAAG,WAAW,EAAE,gBAAgB,GAAG,UACrD,GAAG,QAAQ,gBAAgB;;;;;GAM7B,IAAI,WAAW,GAAG,WAAW,EAAE,gBAAgB,GAAG,UAChD,GAAG,QAAQ,gBAAgB;GAE7B,IAAI,gBAAgB,GAAG,WAAW,EAAE,WAAW,GAAG,YAAY,EAAE,gBAAgB,GAAG,UACjF,GAAG,QAAQ,QAAQ;GAIrB,IAAI,gBAAgB,GAAG,SACrB,GAAG,QAAQ,wBAAwB;EAEvC;CACF,CAAC;CAGD,MAAM,aAAa,UAAU,GAAG;CAEhC,IAAI;CAEJ,IAAI;EAYF,cAAc,MAAM,YAAY;GAV9B,iBAAiB,iBAAiB,mBAAmB;GACrD,wBAAwB,iBAAiB,0BAA0B;GACnE,sBAAsB,iBAAiB,wBAAwB;GAC/D,uBAAuB,iBAAiB,yBAAyB;GACjE,oBAAoB,YAAY,CAAC;GACjC,GAAG,YAAY,EAAE,UAAU,UAAU,IAAI,CAAC;GAC1C,2BAA2B,iBAAiB,6BAA6B;GACzE,GAAG;EAGsC,CAAC;CAC9C,QAAQ;EAEN,OAAO;CACT;CAEA,MAAM,SAAS,MAAM,WAAW;CAEhC,IAAI,sBACF,KAAK,SAAS,SAAS;EACrB,MAAM,KAAK;EACX,IAAI,GAAG,SAAS,OACd,GAAG,QAAQ,QAAQ,GAAG,QAAQ,MAAM,QAClC,4DACA,GACF;CAEJ,CAAC;;;;;;;CASH,KAAK,SAAS,SAAS;EACrB,MAAM,KAAK;EACX,IAAI,GAAG,SAAS,WAAW,GAAG,WAAW,wBAAwB,GAAG,SAAS;;;;;;;GAO3E,IAAI,EAAE,gBAAgB,GAAG,UACvB,GAAG,QAAQ,QAAQ;GAErB,OAAO,GAAG,QAAQ;GAClB,OAAO,GAAG,QAAQ;EACpB;CACF,CAAC;CAED,OAAO;AACT"}
@@ -1 +1 @@
1
- {"version":3,"file":"inlineLink.d.ts","names":[],"sources":["../../src/transformers/inlineLink.ts"],"mappings":";;;;;AA2BA;;;;;;;;;AASA;;;;;;;;;;;iBATsB,UAAA,CAAW,IAAA,UAAc,QAAA,YAAoB,OAAA;;;;;;iBAS7C,aAAA,CAAc,GAAA,EAAK,SAAA,IAAa,QAAA,YAAoB,OAAA,CAAQ,SAAA"}
1
+ {"version":3,"file":"inlineLink.d.ts","names":[],"sources":["../../src/transformers/inlineLink.ts"],"mappings":";;;;;AA2BA;;;;;;;;AAA0E;AAS1E;;;;;;;;;;;iBATsB,UAAA,CAAW,IAAA,UAAc,QAAA,YAAoB,OAAO;;;AASiB;;;iBAArE,aAAA,CAAc,GAAA,EAAK,SAAA,IAAa,QAAA,YAAoB,OAAA,CAAQ,SAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"inlineLink.js","names":["parse"],"sources":["../../src/transformers/inlineLink.ts"],"sourcesContent":["import { readFileSync } from 'node:fs'\nimport { resolve, dirname } from 'node:path'\nimport type { ChildNode, Element } from 'domhandler'\nimport { parse, serialize, walk } from '../utils/ast/index.ts'\n\n/**\n * Inline `<link rel=\"stylesheet\">` tags as `<style>` tags.\n *\n * - Local file paths are inlined when `filePath` is provided (resolved\n * relative to it).\n * - Remote URLs (`http://` / `https://`) are only inlined when the link\n * carries an `inline` attribute, e.g. `<link rel=\"stylesheet\" inline href=\"…\">`.\n *\n * @param html HTML string to transform.\n * @param filePath Path of the source file the HTML came from, used as the\n * base for resolving relative `href` values. Required for\n * local-file inlining; remote `inline` links work without it.\n * @returns The transformed HTML string.\n *\n * @example\n * import { inlineLink } from '@maizzle/framework'\n *\n * const out = await inlineLink(\n * '<link rel=\"stylesheet\" href=\"./styles.css\">',\n * '/path/to/template.html',\n * )\n */\nexport async function inlineLink(html: string, filePath?: string): Promise<string> {\n return serialize(await inlineLinkDom(parse(html), filePath))\n}\n\n/**\n * DOM-form of {@link inlineLink} used by the internal transformer pipeline.\n * Takes a parsed DOM, returns a parsed DOM — avoids redundant\n * serialize/parse round-trips when chained with other transformers.\n */\nexport async function inlineLinkDom(dom: ChildNode[], filePath?: string): Promise<ChildNode[]> {\n const links: { node: Element; parent: ChildNode; index: number }[] = []\n\n walk(dom, (node) => {\n if ((node as Element).name !== 'link') return\n\n const el = node as Element\n const attrs = el.attribs || {}\n\n if (attrs.rel !== 'stylesheet' || !attrs.href) return\n\n const parent = el.parent as ChildNode\n\n if (parent && 'children' in parent) {\n const index = (parent.children as ChildNode[]).indexOf(el)\n if (index !== -1) {\n links.push({ node: el, parent, index })\n }\n } else {\n // Top-level node\n const index = dom.indexOf(el)\n if (index !== -1) {\n links.push({ node: el, parent: null as any, index })\n }\n }\n })\n\n for (const { node, parent, index } of links) {\n const href = node.attribs.href\n const isRemote = href.startsWith('http://') || href.startsWith('https://')\n\n let css: string | undefined\n\n if (isRemote) {\n if (!('inline' in node.attribs)) continue\n\n try {\n const response = await fetch(href)\n css = await response.text()\n } catch {\n continue\n }\n } else {\n if (!filePath) continue\n\n try {\n const absolutePath = resolve(dirname(filePath), href)\n css = readFileSync(absolutePath, 'utf8')\n } catch {\n continue\n }\n }\n\n const styleNode = {\n type: 'tag',\n name: 'style',\n attribs: {},\n children: [{\n type: 'text',\n data: css,\n parent: null as any,\n }],\n parent: parent || null,\n } as any\n\n // Set parent reference on the text child\n styleNode.children[0].parent = styleNode\n\n const siblings = parent && 'children' in parent\n ? parent.children as ChildNode[]\n : dom\n\n siblings.splice(index, 1, styleNode)\n }\n\n return dom\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BA,eAAsB,WAAW,MAAc,UAAoC;CACjF,OAAO,UAAU,MAAM,cAAcA,QAAM,KAAK,EAAE,SAAS,CAAC;;;;;;;AAQ9D,eAAsB,cAAc,KAAkB,UAAyC;CAC7F,MAAM,QAA+D,EAAE;CAEvE,KAAK,MAAM,SAAS;EAClB,IAAK,KAAiB,SAAS,QAAQ;EAEvC,MAAM,KAAK;EACX,MAAM,QAAQ,GAAG,WAAW,EAAE;EAE9B,IAAI,MAAM,QAAQ,gBAAgB,CAAC,MAAM,MAAM;EAE/C,MAAM,SAAS,GAAG;EAElB,IAAI,UAAU,cAAc,QAAQ;GAClC,MAAM,QAAS,OAAO,SAAyB,QAAQ,GAAG;GAC1D,IAAI,UAAU,IACZ,MAAM,KAAK;IAAE,MAAM;IAAI;IAAQ;IAAO,CAAC;SAEpC;GAEL,MAAM,QAAQ,IAAI,QAAQ,GAAG;GAC7B,IAAI,UAAU,IACZ,MAAM,KAAK;IAAE,MAAM;IAAI,QAAQ;IAAa;IAAO,CAAC;;GAGxD;CAEF,KAAK,MAAM,EAAE,MAAM,QAAQ,WAAW,OAAO;EAC3C,MAAM,OAAO,KAAK,QAAQ;EAC1B,MAAM,WAAW,KAAK,WAAW,UAAU,IAAI,KAAK,WAAW,WAAW;EAE1E,IAAI;EAEJ,IAAI,UAAU;GACZ,IAAI,EAAE,YAAY,KAAK,UAAU;GAEjC,IAAI;IAEF,MAAM,OAAM,MADW,MAAM,KAAK,EACb,MAAM;WACrB;IACN;;SAEG;GACL,IAAI,CAAC,UAAU;GAEf,IAAI;IAEF,MAAM,aADe,QAAQ,QAAQ,SAAS,EAAE,KACjB,EAAE,OAAO;WAClC;IACN;;;EAIJ,MAAM,YAAY;GAChB,MAAM;GACN,MAAM;GACN,SAAS,EAAE;GACX,UAAU,CAAC;IACT,MAAM;IACN,MAAM;IACN,QAAQ;IACT,CAAC;GACF,QAAQ,UAAU;GACnB;EAGD,UAAU,SAAS,GAAG,SAAS;EAM/B,CAJiB,UAAU,cAAc,SACrC,OAAO,WACP,KAEK,OAAO,OAAO,GAAG,UAAU;;CAGtC,OAAO"}
1
+ {"version":3,"file":"inlineLink.js","names":["parse"],"sources":["../../src/transformers/inlineLink.ts"],"sourcesContent":["import { readFileSync } from 'node:fs'\nimport { resolve, dirname } from 'node:path'\nimport type { ChildNode, Element } from 'domhandler'\nimport { parse, serialize, walk } from '../utils/ast/index.ts'\n\n/**\n * Inline `<link rel=\"stylesheet\">` tags as `<style>` tags.\n *\n * - Local file paths are inlined when `filePath` is provided (resolved\n * relative to it).\n * - Remote URLs (`http://` / `https://`) are only inlined when the link\n * carries an `inline` attribute, e.g. `<link rel=\"stylesheet\" inline href=\"…\">`.\n *\n * @param html HTML string to transform.\n * @param filePath Path of the source file the HTML came from, used as the\n * base for resolving relative `href` values. Required for\n * local-file inlining; remote `inline` links work without it.\n * @returns The transformed HTML string.\n *\n * @example\n * import { inlineLink } from '@maizzle/framework'\n *\n * const out = await inlineLink(\n * '<link rel=\"stylesheet\" href=\"./styles.css\">',\n * '/path/to/template.html',\n * )\n */\nexport async function inlineLink(html: string, filePath?: string): Promise<string> {\n return serialize(await inlineLinkDom(parse(html), filePath))\n}\n\n/**\n * DOM-form of {@link inlineLink} used by the internal transformer pipeline.\n * Takes a parsed DOM, returns a parsed DOM — avoids redundant\n * serialize/parse round-trips when chained with other transformers.\n */\nexport async function inlineLinkDom(dom: ChildNode[], filePath?: string): Promise<ChildNode[]> {\n const links: { node: Element; parent: ChildNode; index: number }[] = []\n\n walk(dom, (node) => {\n if ((node as Element).name !== 'link') return\n\n const el = node as Element\n const attrs = el.attribs || {}\n\n if (attrs.rel !== 'stylesheet' || !attrs.href) return\n\n const parent = el.parent as ChildNode\n\n if (parent && 'children' in parent) {\n const index = (parent.children as ChildNode[]).indexOf(el)\n if (index !== -1) {\n links.push({ node: el, parent, index })\n }\n } else {\n // Top-level node\n const index = dom.indexOf(el)\n if (index !== -1) {\n links.push({ node: el, parent: null as any, index })\n }\n }\n })\n\n for (const { node, parent, index } of links) {\n const href = node.attribs.href\n const isRemote = href.startsWith('http://') || href.startsWith('https://')\n\n let css: string | undefined\n\n if (isRemote) {\n if (!('inline' in node.attribs)) continue\n\n try {\n const response = await fetch(href)\n css = await response.text()\n } catch {\n continue\n }\n } else {\n if (!filePath) continue\n\n try {\n const absolutePath = resolve(dirname(filePath), href)\n css = readFileSync(absolutePath, 'utf8')\n } catch {\n continue\n }\n }\n\n const styleNode = {\n type: 'tag',\n name: 'style',\n attribs: {},\n children: [{\n type: 'text',\n data: css,\n parent: null as any,\n }],\n parent: parent || null,\n } as any\n\n // Set parent reference on the text child\n styleNode.children[0].parent = styleNode\n\n const siblings = parent && 'children' in parent\n ? parent.children as ChildNode[]\n : dom\n\n siblings.splice(index, 1, styleNode)\n }\n\n return dom\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BA,eAAsB,WAAW,MAAc,UAAoC;CACjF,OAAO,UAAU,MAAM,cAAcA,QAAM,IAAI,GAAG,QAAQ,CAAC;AAC7D;;;;;;AAOA,eAAsB,cAAc,KAAkB,UAAyC;CAC7F,MAAM,QAA+D,CAAC;CAEtE,KAAK,MAAM,SAAS;EAClB,IAAK,KAAiB,SAAS,QAAQ;EAEvC,MAAM,KAAK;EACX,MAAM,QAAQ,GAAG,WAAW,CAAC;EAE7B,IAAI,MAAM,QAAQ,gBAAgB,CAAC,MAAM,MAAM;EAE/C,MAAM,SAAS,GAAG;EAElB,IAAI,UAAU,cAAc,QAAQ;GAClC,MAAM,QAAS,OAAO,SAAyB,QAAQ,EAAE;GACzD,IAAI,UAAU,IACZ,MAAM,KAAK;IAAE,MAAM;IAAI;IAAQ;GAAM,CAAC;EAE1C,OAAO;GAEL,MAAM,QAAQ,IAAI,QAAQ,EAAE;GAC5B,IAAI,UAAU,IACZ,MAAM,KAAK;IAAE,MAAM;IAAI,QAAQ;IAAa;GAAM,CAAC;EAEvD;CACF,CAAC;CAED,KAAK,MAAM,EAAE,MAAM,QAAQ,WAAW,OAAO;EAC3C,MAAM,OAAO,KAAK,QAAQ;EAC1B,MAAM,WAAW,KAAK,WAAW,SAAS,KAAK,KAAK,WAAW,UAAU;EAEzE,IAAI;EAEJ,IAAI,UAAU;GACZ,IAAI,EAAE,YAAY,KAAK,UAAU;GAEjC,IAAI;IAEF,MAAM,OAAM,MADW,MAAM,IAAI,GACZ,KAAK;GAC5B,QAAQ;IACN;GACF;EACF,OAAO;GACL,IAAI,CAAC,UAAU;GAEf,IAAI;IAEF,MAAM,aADe,QAAQ,QAAQ,QAAQ,GAAG,IAClB,GAAG,MAAM;GACzC,QAAQ;IACN;GACF;EACF;EAEA,MAAM,YAAY;GAChB,MAAM;GACN,MAAM;GACN,SAAS,CAAC;GACV,UAAU,CAAC;IACT,MAAM;IACN,MAAM;IACN,QAAQ;GACV,CAAC;GACD,QAAQ,UAAU;EACpB;EAGA,UAAU,SAAS,GAAG,SAAS;EAM/B,CAJiB,UAAU,cAAc,SACrC,OAAO,WACP,KAEK,OAAO,OAAO,GAAG,SAAS;CACrC;CAEA,OAAO;AACT"}
@@ -1 +1 @@
1
- {"version":3,"file":"minify.d.ts","names":[],"sources":["../../src/transformers/minify.ts"],"mappings":";;;;AAwBA;;;;;;;;;;;;;iBAAgB,MAAA,CAAO,IAAA,UAAc,OAAA,GAAS,OAAA,CAAQ,IAAA"}
1
+ {"version":3,"file":"minify.d.ts","names":[],"sources":["../../src/transformers/minify.ts"],"mappings":";;;;AAwBA;;;;;;;;;AAA4E;;;;iBAA5D,MAAA,CAAO,IAAA,UAAc,OAAA,GAAS,OAAO,CAAC,IAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"minify.js","names":["merge"],"sources":["../../src/transformers/minify.ts"],"sourcesContent":["import { crush } from 'html-crush'\nimport { defu as merge } from 'defu'\nimport type { Opts as HtmlCrushOptions } from 'html-crush'\n\nexport type { Opts as MinifyOptions } from 'html-crush'\n\nconst DEFAULT_OPTIONS: Partial<HtmlCrushOptions> = {\n removeLineBreaks: true,\n}\n\n/**\n * Minify an HTML string using `html-crush`. Maizzle's only default that\n * differs from html-crush's own defaults is `removeLineBreaks: true`.\n *\n * @param html HTML string to minify.\n * @param options [html-crush options](https://codsen.com/os/html-crush) merged\n * on top of the Maizzle defaults.\n * @returns The minified HTML string.\n *\n * @example\n * import { minify } from '@maizzle/framework'\n *\n * const tight = minify('<p> hello </p>', { removeIndentations: true })\n */\nexport function minify(html: string, options: Partial<HtmlCrushOptions> = {}): string {\n const merged = merge(options, DEFAULT_OPTIONS) as Partial<HtmlCrushOptions>\n return crush(html, merged).result\n}\n"],"mappings":";;;AAMA,MAAM,kBAA6C,EACjD,kBAAkB,MACnB;;;;;;;;;;;;;;;AAgBD,SAAgB,OAAO,MAAc,UAAqC,EAAE,EAAU;CAEpF,OAAO,MAAM,MADEA,OAAM,SAAS,gBACL,CAAC,CAAC"}
1
+ {"version":3,"file":"minify.js","names":["merge"],"sources":["../../src/transformers/minify.ts"],"sourcesContent":["import { crush } from 'html-crush'\nimport { defu as merge } from 'defu'\nimport type { Opts as HtmlCrushOptions } from 'html-crush'\n\nexport type { Opts as MinifyOptions } from 'html-crush'\n\nconst DEFAULT_OPTIONS: Partial<HtmlCrushOptions> = {\n removeLineBreaks: true,\n}\n\n/**\n * Minify an HTML string using `html-crush`. Maizzle's only default that\n * differs from html-crush's own defaults is `removeLineBreaks: true`.\n *\n * @param html HTML string to minify.\n * @param options [html-crush options](https://codsen.com/os/html-crush) merged\n * on top of the Maizzle defaults.\n * @returns The minified HTML string.\n *\n * @example\n * import { minify } from '@maizzle/framework'\n *\n * const tight = minify('<p> hello </p>', { removeIndentations: true })\n */\nexport function minify(html: string, options: Partial<HtmlCrushOptions> = {}): string {\n const merged = merge(options, DEFAULT_OPTIONS) as Partial<HtmlCrushOptions>\n return crush(html, merged).result\n}\n"],"mappings":";;;AAMA,MAAM,kBAA6C,EACjD,kBAAkB,KACpB;;;;;;;;;;;;;;;AAgBA,SAAgB,OAAO,MAAc,UAAqC,CAAC,GAAW;CAEpF,OAAO,MAAM,MADEA,OAAM,SAAS,eACN,CAAC,EAAE;AAC7B"}
@@ -1 +1 @@
1
- {"version":3,"file":"minifyCodeInline.d.ts","names":[],"sources":["../../src/transformers/minifyCodeInline.ts"],"mappings":";;AAwBA;;;;;;;;;;;;;;;;;;;;;;;iBAAgB,gBAAA,CAAiB,IAAA"}
1
+ {"version":3,"file":"minifyCodeInline.d.ts","names":[],"sources":["../../src/transformers/minifyCodeInline.ts"],"mappings":";;AAwBA;;;;AAA6C;;;;;;;;;;;;;;;;;;;iBAA7B,gBAAA,CAAiB,IAAY"}
@@ -1 +1 @@
1
- {"version":3,"file":"minifyCodeInline.js","names":[],"sources":["../../src/transformers/minifyCodeInline.ts"],"sourcesContent":["/**\n * Restore HTML inside elements marked `data-minify-inline`, then strip the\n * marker attribute and trim formatter-injected whitespace.\n *\n * Named for its primary client, `<CodeInline theme=\"…\">`. The component\n * replaces shiki's structural `<`/`>` with private markers `§MZLT§` /\n * `§MZGT§` so the format pass (oxfmt with `htmlWhitespaceSensitivity:\n * 'ignore'`) can't see them as real angle brackets and reflow the\n * chain of `<span>` tokens. Source-level entities like `&lt;` (a\n * literal `<` in the user's code) are made of `&`, `l`, `t`, `;` —\n * no real `<` — so they pass through this pipeline untouched and\n * land in the browser as entities, rendering correctly as `<`.\n *\n * Runs unconditionally near the end of the pipeline so:\n * 1. The markers always get decoded back to real `<` / `>`.\n * 2. The `data-minify-inline` attribute never leaks to final HTML\n * (whether or not the inner content had markers).\n * 3. Whitespace the formatter injected around the inner content\n * (e.g. between `<code>` and the text node) is trimmed so the\n * inline element lands flush.\n *\n * The marker attribute is intentionally generic so any component facing\n * the same formatter-vs-inline-structure problem can opt in.\n */\nexport function minifyCodeInline(html: string): string {\n if (!html.includes('data-minify-inline')) return html\n\n return html.replace(\n /<([a-zA-Z][\\w-]*)([^>]*?)\\s+data-minify-inline(?:=\"[^\"]*\")?([^>]*)>([\\s\\S]*?)<\\/\\1>/g,\n (_full, tag, before, after, contents) => {\n const cleanedAttrs = `${before}${after}`.replace(/\\s+/g, ' ').trim()\n const open = cleanedAttrs ? `<${tag} ${cleanedAttrs}>` : `<${tag}>`\n const decoded = contents\n .replace(/§MZLT§/g, '<')\n .replace(/§MZGT§/g, '>')\n .trim()\n return `${open}${decoded}</${tag}>`\n },\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAwBA,SAAgB,iBAAiB,MAAsB;CACrD,IAAI,CAAC,KAAK,SAAS,qBAAqB,EAAE,OAAO;CAEjD,OAAO,KAAK,QACV,yFACC,OAAO,KAAK,QAAQ,OAAO,aAAa;EACvC,MAAM,eAAe,GAAG,SAAS,QAAQ,QAAQ,QAAQ,IAAI,CAAC,MAAM;EAMpE,OAAO,GALM,eAAe,IAAI,IAAI,GAAG,aAAa,KAAK,IAAI,IAAI,KACjD,SACb,QAAQ,WAAW,IAAI,CACvB,QAAQ,WAAW,IAAI,CACvB,MACqB,CAAC,IAAI,IAAI;GAEpC"}
1
+ {"version":3,"file":"minifyCodeInline.js","names":[],"sources":["../../src/transformers/minifyCodeInline.ts"],"sourcesContent":["/**\n * Restore HTML inside elements marked `data-minify-inline`, then strip the\n * marker attribute and trim formatter-injected whitespace.\n *\n * Named for its primary client, `<CodeInline theme=\"…\">`. The component\n * replaces shiki's structural `<`/`>` with private markers `§MZLT§` /\n * `§MZGT§` so the format pass (oxfmt with `htmlWhitespaceSensitivity:\n * 'ignore'`) can't see them as real angle brackets and reflow the\n * chain of `<span>` tokens. Source-level entities like `&lt;` (a\n * literal `<` in the user's code) are made of `&`, `l`, `t`, `;` —\n * no real `<` — so they pass through this pipeline untouched and\n * land in the browser as entities, rendering correctly as `<`.\n *\n * Runs unconditionally near the end of the pipeline so:\n * 1. The markers always get decoded back to real `<` / `>`.\n * 2. The `data-minify-inline` attribute never leaks to final HTML\n * (whether or not the inner content had markers).\n * 3. Whitespace the formatter injected around the inner content\n * (e.g. between `<code>` and the text node) is trimmed so the\n * inline element lands flush.\n *\n * The marker attribute is intentionally generic so any component facing\n * the same formatter-vs-inline-structure problem can opt in.\n */\nexport function minifyCodeInline(html: string): string {\n if (!html.includes('data-minify-inline')) return html\n\n return html.replace(\n /<([a-zA-Z][\\w-]*)([^>]*?)\\s+data-minify-inline(?:=\"[^\"]*\")?([^>]*)>([\\s\\S]*?)<\\/\\1>/g,\n (_full, tag, before, after, contents) => {\n const cleanedAttrs = `${before}${after}`.replace(/\\s+/g, ' ').trim()\n const open = cleanedAttrs ? `<${tag} ${cleanedAttrs}>` : `<${tag}>`\n const decoded = contents\n .replace(/§MZLT§/g, '<')\n .replace(/§MZGT§/g, '>')\n .trim()\n return `${open}${decoded}</${tag}>`\n },\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAwBA,SAAgB,iBAAiB,MAAsB;CACrD,IAAI,CAAC,KAAK,SAAS,oBAAoB,GAAG,OAAO;CAEjD,OAAO,KAAK,QACV,yFACC,OAAO,KAAK,QAAQ,OAAO,aAAa;EACvC,MAAM,eAAe,GAAG,SAAS,QAAQ,QAAQ,QAAQ,GAAG,EAAE,KAAK;EAMnE,OAAO,GALM,eAAe,IAAI,IAAI,GAAG,aAAa,KAAK,IAAI,IAAI,KACjD,SACb,QAAQ,WAAW,GAAG,EACtB,QAAQ,WAAW,GAAG,EACtB,KACoB,EAAE,IAAI,IAAI;CACnC,CACF;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"msoPlaceholders.d.ts","names":[],"sources":["../../src/transformers/msoPlaceholders.ts"],"mappings":";;;;;AAkDA;;;;;;;;;;;;;;;;;;;;;;;;iBAAgB,eAAA,CAAgB,GAAA,EAAK,SAAA,KAAc,SAAA"}
1
+ {"version":3,"file":"msoPlaceholders.d.ts","names":[],"sources":["../../src/transformers/msoPlaceholders.ts"],"mappings":";;;;;AAkDA;;;;;;;;AAA4D;;;;;;;;;;;;;;;;iBAA5C,eAAA,CAAgB,GAAA,EAAK,SAAA,KAAc,SAAS"}