meno-core 1.0.47 → 1.0.49

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 (97) hide show
  1. package/build-astro.ts +2 -2
  2. package/dist/build-static.js +7 -7
  3. package/dist/chunks/{chunk-UUA5LEWF.js → chunk-6IVUG7FY.js} +138 -7
  4. package/dist/chunks/chunk-6IVUG7FY.js.map +7 -0
  5. package/dist/chunks/{chunk-XSWR3QLI.js → chunk-AZQYF6KE.js} +261 -130
  6. package/dist/chunks/chunk-AZQYF6KE.js.map +7 -0
  7. package/dist/chunks/{chunk-47UNLQUU.js → chunk-CHD5UCFF.js} +57 -12
  8. package/dist/chunks/chunk-CHD5UCFF.js.map +7 -0
  9. package/dist/chunks/{chunk-FGUZOYJX.js → chunk-EQYDSPBB.js} +435 -131
  10. package/dist/chunks/chunk-EQYDSPBB.js.map +7 -0
  11. package/dist/chunks/{chunk-IF3RATBY.js → chunk-H4JSCDNW.js} +2 -2
  12. package/dist/chunks/{chunk-KITQJYZV.js → chunk-J23ZX5AP.js} +40 -4
  13. package/dist/chunks/chunk-J23ZX5AP.js.map +7 -0
  14. package/dist/chunks/{chunk-LJFB5EBT.js → chunk-JER5NQVM.js} +5 -5
  15. package/dist/chunks/{chunk-ZTKHJQ2Z.js → chunk-KPU2XHOS.js} +5 -2
  16. package/dist/chunks/{chunk-ZTKHJQ2Z.js.map → chunk-KPU2XHOS.js.map} +2 -2
  17. package/dist/chunks/{chunk-BCLGRZ3U.js → chunk-LKAGAQ3M.js} +2 -2
  18. package/dist/chunks/{chunk-FED5MME6.js → chunk-S2CX6HFM.js} +262 -26
  19. package/dist/chunks/chunk-S2CX6HFM.js.map +7 -0
  20. package/dist/chunks/{configService-DYCUEURL.js → configService-CCA6AIDI.js} +3 -3
  21. package/dist/entries/server-router.js +9 -9
  22. package/dist/entries/server-router.js.map +2 -2
  23. package/dist/lib/client/index.js +64 -20
  24. package/dist/lib/client/index.js.map +3 -3
  25. package/dist/lib/server/index.js +1737 -296
  26. package/dist/lib/server/index.js.map +4 -4
  27. package/dist/lib/shared/index.js +50 -10
  28. package/dist/lib/shared/index.js.map +3 -3
  29. package/entries/server-router.tsx +6 -2
  30. package/lib/client/core/ComponentBuilder.test.ts +17 -0
  31. package/lib/client/core/ComponentBuilder.ts +25 -1
  32. package/lib/client/core/builders/embedBuilder.ts +15 -2
  33. package/lib/client/core/builders/linkNodeBuilder.ts +15 -2
  34. package/lib/client/core/builders/localeListBuilder.ts +17 -6
  35. package/lib/client/styles/StyleInjector.ts +3 -2
  36. package/lib/client/theme.ts +4 -4
  37. package/lib/server/cssGenerator.test.ts +64 -1
  38. package/lib/server/cssGenerator.ts +48 -9
  39. package/lib/server/index.ts +1 -1
  40. package/lib/server/jsonLoader.test.ts +0 -17
  41. package/lib/server/jsonLoader.ts +0 -81
  42. package/lib/server/providers/fileSystemCMSProvider.test.ts +163 -0
  43. package/lib/server/providers/fileSystemCMSProvider.ts +200 -11
  44. package/lib/server/routes/api/variables.ts +4 -2
  45. package/lib/server/routes/index.ts +1 -1
  46. package/lib/server/routes/pages.ts +23 -1
  47. package/lib/server/services/cmsService.test.ts +246 -0
  48. package/lib/server/services/cmsService.ts +122 -5
  49. package/lib/server/services/configService.ts +5 -0
  50. package/lib/server/ssr/attributeBuilder.ts +41 -0
  51. package/lib/server/ssr/htmlGenerator.test.ts +114 -2
  52. package/lib/server/ssr/htmlGenerator.ts +53 -6
  53. package/lib/server/ssr/liveReloadIntegration.test.ts +209 -0
  54. package/lib/server/ssr/ssrRenderer.test.ts +362 -1
  55. package/lib/server/ssr/ssrRenderer.ts +216 -72
  56. package/lib/server/utils/jsonLineMapper.test.ts +53 -1
  57. package/lib/server/utils/jsonLineMapper.ts +43 -3
  58. package/lib/server/webflow/buildWebflow.ts +343 -123
  59. package/lib/server/webflow/index.ts +1 -0
  60. package/lib/server/webflow/nodeToWebflow.test.ts +3170 -0
  61. package/lib/server/webflow/nodeToWebflow.ts +2141 -129
  62. package/lib/server/webflow/styleMapper.test.ts +389 -0
  63. package/lib/server/webflow/styleMapper.ts +517 -63
  64. package/lib/server/webflow/templateWrapper.ts +49 -0
  65. package/lib/server/webflow/types.ts +218 -18
  66. package/lib/shared/cssGeneration.test.ts +267 -1
  67. package/lib/shared/cssGeneration.ts +240 -18
  68. package/lib/shared/cssProperties.test.ts +247 -1
  69. package/lib/shared/cssProperties.ts +196 -6
  70. package/lib/shared/elementClassName.test.ts +15 -0
  71. package/lib/shared/elementClassName.ts +7 -3
  72. package/lib/shared/interfaces/contentProvider.ts +39 -6
  73. package/lib/shared/pathSecurity.ts +16 -0
  74. package/lib/shared/registry/nodeTypes/ListNodeType.ts +1 -1
  75. package/lib/shared/responsiveScaling.test.ts +143 -0
  76. package/lib/shared/responsiveScaling.ts +253 -2
  77. package/lib/shared/themeDefaults.test.ts +3 -3
  78. package/lib/shared/themeDefaults.ts +3 -3
  79. package/lib/shared/types/cms.ts +28 -3
  80. package/lib/shared/types/index.ts +2 -0
  81. package/lib/shared/types/variables.ts +37 -0
  82. package/lib/shared/utilityClassConfig.ts +3 -0
  83. package/lib/shared/utilityClassMapper.test.ts +123 -0
  84. package/lib/shared/utilityClassMapper.ts +179 -8
  85. package/lib/shared/validation/schemas.ts +15 -1
  86. package/lib/shared/validation/validators.ts +26 -1
  87. package/package.json +1 -1
  88. package/dist/chunks/chunk-47UNLQUU.js.map +0 -7
  89. package/dist/chunks/chunk-FED5MME6.js.map +0 -7
  90. package/dist/chunks/chunk-FGUZOYJX.js.map +0 -7
  91. package/dist/chunks/chunk-KITQJYZV.js.map +0 -7
  92. package/dist/chunks/chunk-UUA5LEWF.js.map +0 -7
  93. package/dist/chunks/chunk-XSWR3QLI.js.map +0 -7
  94. /package/dist/chunks/{chunk-IF3RATBY.js.map → chunk-H4JSCDNW.js.map} +0 -0
  95. /package/dist/chunks/{chunk-LJFB5EBT.js.map → chunk-JER5NQVM.js.map} +0 -0
  96. /package/dist/chunks/{chunk-BCLGRZ3U.js.map → chunk-LKAGAQ3M.js.map} +0 -0
  97. /package/dist/chunks/{configService-DYCUEURL.js.map → configService-CCA6AIDI.js.map} +0 -0
@@ -3,7 +3,7 @@ import {
3
3
  } from "../../chunks/chunk-4OFZP5NQ.js";
4
4
  import {
5
5
  buildStaticPages
6
- } from "../../chunks/chunk-LJFB5EBT.js";
6
+ } from "../../chunks/chunk-JER5NQVM.js";
7
7
  import {
8
8
  ComponentService,
9
9
  EnumService,
@@ -32,7 +32,7 @@ import {
32
32
  logResponseTime,
33
33
  withErrorHandling,
34
34
  withLogging
35
- } from "../../chunks/chunk-47UNLQUU.js";
35
+ } from "../../chunks/chunk-CHD5UCFF.js";
36
36
  import {
37
37
  CMSService,
38
38
  ColorService,
@@ -62,13 +62,13 @@ import {
62
62
  generateThemeColorVariablesCSS,
63
63
  generateVariablesCSS,
64
64
  getJSValidationErrors,
65
+ getLocaleLinks,
65
66
  loadBreakpointConfig,
66
67
  loadComponentDirectory,
67
68
  loadI18nConfig,
68
69
  loadIconsConfig,
69
70
  loadJSONFile,
70
71
  loadProjectConfig,
71
- loadResponsiveScalesConfig,
72
72
  mapPageNameToPath,
73
73
  menoFilterScript,
74
74
  mergeLibraries,
@@ -83,11 +83,11 @@ import {
83
83
  styleToString,
84
84
  translatePath,
85
85
  variableService
86
- } from "../../chunks/chunk-FGUZOYJX.js";
86
+ } from "../../chunks/chunk-EQYDSPBB.js";
87
87
  import {
88
88
  ConfigService,
89
89
  configService
90
- } from "../../chunks/chunk-ZTKHJQ2Z.js";
90
+ } from "../../chunks/chunk-KPU2XHOS.js";
91
91
  import {
92
92
  bundleFile,
93
93
  createRuntimeServer,
@@ -116,19 +116,35 @@ import {
116
116
  spawnProcess,
117
117
  writeFile
118
118
  } from "../../chunks/chunk-WQFG7PAH.js";
119
- import "../../chunks/chunk-IF3RATBY.js";
120
- import "../../chunks/chunk-KITQJYZV.js";
121
- import "../../chunks/chunk-BCLGRZ3U.js";
119
+ import "../../chunks/chunk-H4JSCDNW.js";
122
120
  import {
121
+ resolvePaletteColor
122
+ } from "../../chunks/chunk-J23ZX5AP.js";
123
+ import {
124
+ hasTemplates,
125
+ isHtmlMapping,
126
+ processCodeTemplates,
127
+ resolveHtmlMapping
128
+ } from "../../chunks/chunk-LKAGAQ3M.js";
129
+ import {
130
+ addItemUrl,
131
+ buildTemplateContext,
123
132
  extractInteractiveStyleMappings,
124
133
  generateAllInteractiveCSS,
125
134
  generateElementClassName,
135
+ generateInteractiveCSS,
136
+ getNestedValue,
126
137
  hasIf,
127
138
  hasInteractiveStyleMappings,
139
+ isBooleanMapping,
140
+ isCssNamedColor,
128
141
  isItemDraftForLocale,
129
142
  isVoidElement,
143
+ resolveItemsTemplate,
144
+ resolvePropsFromDefinition,
145
+ shortHash,
130
146
  singularize
131
- } from "../../chunks/chunk-FED5MME6.js";
147
+ } from "../../chunks/chunk-S2CX6HFM.js";
132
148
  import {
133
149
  DEFAULT_BREAKPOINTS,
134
150
  DEFAULT_I18N_CONFIG,
@@ -136,8 +152,9 @@ import {
136
152
  getScaleMultiplier,
137
153
  isI18nValue,
138
154
  resolveI18nValue,
155
+ resolveVariableValueAtBreakpoint,
139
156
  scalePropertyValue
140
- } from "../../chunks/chunk-XSWR3QLI.js";
157
+ } from "../../chunks/chunk-AZQYF6KE.js";
141
158
  import "../../chunks/chunk-UB44F4Z2.js";
142
159
  import {
143
160
  HMR_ROUTE,
@@ -798,7 +815,7 @@ function emitAttrValue(key, value, ctx) {
798
815
  }
799
816
  return `${key}="${escapeJSX(value)}"`;
800
817
  }
801
- function isHtmlMapping(value) {
818
+ function isHtmlMapping2(value) {
802
819
  return typeof value === "object" && value !== null && "_mapping" in value && value._mapping === true;
803
820
  }
804
821
  function isResponsiveStyle2(style) {
@@ -968,7 +985,7 @@ function resolveTemplate(text, ctx) {
968
985
  return `{${trimmed}}`;
969
986
  });
970
987
  }
971
- function hasTemplates(text) {
988
+ function hasTemplates2(text) {
972
989
  return /\{\{.+?\}\}/.test(text);
973
990
  }
974
991
  function buildElementClass(ctx, label) {
@@ -987,7 +1004,7 @@ function buildAttributesString(attributes, ctx) {
987
1004
  if (value) parts.push(key);
988
1005
  } else {
989
1006
  const strVal = String(value);
990
- if (hasTemplates(strVal) && ctx.isComponentDef) {
1007
+ if (hasTemplates2(strVal) && ctx.isComponentDef) {
991
1008
  const fullMatch = strVal.match(/^\{\{(.+)\}\}$/);
992
1009
  if (fullMatch) {
993
1010
  let expr = fullMatch[1].trim();
@@ -1009,7 +1026,7 @@ function buildAttributesString(attributes, ctx) {
1009
1026
  });
1010
1027
  parts.push(`${key}={\`${resolved}\`}`);
1011
1028
  }
1012
- } else if (ctx.listItemBinding && hasTemplates(strVal)) {
1029
+ } else if (ctx.listItemBinding && hasTemplates2(strVal)) {
1013
1030
  const fullMatch = strVal.match(/^\{\{(.+)\}\}$/);
1014
1031
  if (fullMatch) {
1015
1032
  let expr = fullMatch[1].trim();
@@ -1084,7 +1101,7 @@ function nodeToAstro(node, ctx) {
1084
1101
  return `${ind(ctx)}${transformed}
1085
1102
  `;
1086
1103
  }
1087
- if (hasTemplates(node) && ctx.isComponentDef) {
1104
+ if (hasTemplates2(node) && ctx.isComponentDef) {
1088
1105
  return `${ind(ctx)}${resolveTemplate(node, ctx)}
1089
1106
  `;
1090
1107
  }
@@ -1225,7 +1242,7 @@ function imageImportPath(srcPath, fileDepth) {
1225
1242
  function isStaticImageSrc(src, ctx) {
1226
1243
  if (!src) return false;
1227
1244
  if (typeof src !== "string") return false;
1228
- if (hasTemplates(src)) return false;
1245
+ if (hasTemplates2(src)) return false;
1229
1246
  if (!src.startsWith("/images/")) return false;
1230
1247
  if (!ctx.imageMetadataMap?.has(src)) return false;
1231
1248
  return true;
@@ -1420,7 +1437,7 @@ function emitHtmlNode(node, ctx) {
1420
1437
  const style = node.style;
1421
1438
  let isDynamic = false;
1422
1439
  let dynamicTagVar = "";
1423
- if (hasTemplates(tag) && ctx.isComponentDef) {
1440
+ if (hasTemplates2(tag) && ctx.isComponentDef) {
1424
1441
  isDynamic = true;
1425
1442
  dynamicTagVar = `Tag_${ctx.elementPath.join("_")}`;
1426
1443
  const resolved = tag.replace(/\{\{(.+?)\}\}/g, (_, expr) => `\${${expr.trim()}}`);
@@ -1461,7 +1478,7 @@ function emitComponentInstance(node, ctx) {
1461
1478
  for (const [key, rawValue] of Object.entries(node.props)) {
1462
1479
  if (key === "children") continue;
1463
1480
  const value = resolveI18n(rawValue, ctx);
1464
- if (typeof value === "string" && hasTemplates(value) && ctx.isComponentDef) {
1481
+ if (typeof value === "string" && hasTemplates2(value) && ctx.isComponentDef) {
1465
1482
  const fullMatch = value.match(/^\{\{(.+)\}\}$/);
1466
1483
  if (fullMatch) {
1467
1484
  let expr = fullMatch[1].trim();
@@ -1544,7 +1561,7 @@ function emitEmbedNode(node, ctx) {
1544
1561
  }
1545
1562
  const { classExpr, styleAttr } = buildClassAndStyleExpression(style, node.interactiveStyles, elementClass, ctx);
1546
1563
  const attrs = buildAttributesString(node.attributes, ctx);
1547
- if (isHtmlMapping(node.html)) {
1564
+ if (isHtmlMapping2(node.html)) {
1548
1565
  if (ctx.isComponentDef) {
1549
1566
  const propRef = node.html.prop;
1550
1567
  return `${ifExpr}${ind(ctx)}<div${classExpr.replace('"', '"oem ') || ' class="oem"'}${attrs}>
@@ -1553,7 +1570,7 @@ ${ind(ctx)}</div>
1553
1570
  ${emitIfClose(node, ctx)}`;
1554
1571
  }
1555
1572
  }
1556
- if (typeof node.html === "string" && hasTemplates(node.html) && ctx.isComponentDef) {
1573
+ if (typeof node.html === "string" && hasTemplates2(node.html) && ctx.isComponentDef) {
1557
1574
  const fullMatch = node.html.match(/^\{\{(.+)\}\}$/);
1558
1575
  if (fullMatch) {
1559
1576
  let propRef = fullMatch[1].trim();
@@ -1619,7 +1636,7 @@ function emitLinkNode(node, ctx) {
1619
1636
  }
1620
1637
  } else {
1621
1638
  const href = typeof nodeHref === "string" ? nodeHref : "#";
1622
- if (hasTemplates(href) && ctx.isComponentDef) {
1639
+ if (hasTemplates2(href) && ctx.isComponentDef) {
1623
1640
  const fullMatch = href.match(/^\{\{(.+)\}\}$/);
1624
1641
  if (fullMatch) {
1625
1642
  let expr = fullMatch[1].trim();
@@ -2935,8 +2952,8 @@ async function buildAstroProject(projectRoot, outputDir) {
2935
2952
  const themeConfig = await colorService.loadThemeConfig();
2936
2953
  const variablesConfig = await variableService.loadConfig();
2937
2954
  const breakpoints = await loadBreakpointConfig();
2938
- const responsiveScales = await loadResponsiveScalesConfig();
2939
2955
  await configService.load();
2956
+ const responsiveScales = configService.getResponsiveScales();
2940
2957
  const globalLibraries = configService.getLibraries();
2941
2958
  const componentLibraries = collectComponentLibraries(globalComponents);
2942
2959
  const imageMetadataMap = await buildImageMetadataMap();
@@ -3498,38 +3515,19 @@ export default defineConfig({${siteUrl ? `
3498
3515
 
3499
3516
  // lib/server/webflow/buildWebflow.ts
3500
3517
  import { existsSync as existsSync2, readdirSync as readdirSync2 } from "fs";
3501
- import { join as join2 } from "path";
3502
-
3503
- // lib/server/webflow/types.ts
3504
- function mapCMSFieldType(menoType) {
3505
- switch (menoType) {
3506
- case "string":
3507
- return "PlainText";
3508
- case "text":
3509
- case "rich-text":
3510
- return "RichText";
3511
- case "number":
3512
- return "Number";
3513
- case "boolean":
3514
- return "Switch";
3515
- case "image":
3516
- return "Image";
3517
- case "date":
3518
- return "Date";
3519
- case "select":
3520
- return "Option";
3521
- case "file":
3522
- return "File";
3523
- case "reference":
3524
- return "Reference";
3525
- default:
3526
- return "PlainText";
3527
- }
3528
- }
3518
+ import { join as join3 } from "path";
3519
+ init_constants();
3529
3520
 
3530
3521
  // lib/server/webflow/nodeToWebflow.ts
3531
3522
  init_constants();
3532
3523
 
3524
+ // lib/server/webflow/types.ts
3525
+ var MENO_BIND_SENTINEL_PREFIX = "__MENO_BIND__:";
3526
+ var MENO_BIND_SENTINEL_SUFFIX = ":__";
3527
+ var MENO_BIND_SENTINEL_RE = /__MENO_BIND__:([^:]+):__/g;
3528
+ var MENO_BIND_SENTINEL_EXACT_RE = /^__MENO_BIND__:([^:]+):__$/;
3529
+ var COLLECTION_LIST_TAG = "__collection_list__";
3530
+
3533
3531
  // lib/server/webflow/styleMapper.ts
3534
3532
  var UNITLESS_PROPERTIES = /* @__PURE__ */ new Set([
3535
3533
  "opacity",
@@ -3544,6 +3542,28 @@ var UNITLESS_PROPERTIES = /* @__PURE__ */ new Set([
3544
3542
  "font-weight",
3545
3543
  "tab-size"
3546
3544
  ]);
3545
+ var TIME_PROPERTIES = /* @__PURE__ */ new Set([
3546
+ "transition-duration",
3547
+ "transition-delay",
3548
+ "animation-duration",
3549
+ "animation-delay"
3550
+ ]);
3551
+ function normalizeZero(cssProp, cssValue) {
3552
+ if (cssValue !== "0") return cssValue;
3553
+ if (UNITLESS_PROPERTIES.has(cssProp)) return cssValue;
3554
+ if (TIME_PROPERTIES.has(cssProp)) return cssValue;
3555
+ return "0px";
3556
+ }
3557
+ var COLOR_PROPS_CAMEL = /* @__PURE__ */ new Set(["color", "backgroundColor", "borderColor"]);
3558
+ function maybeWrapColorVar(camelProp, value) {
3559
+ if (!COLOR_PROPS_CAMEL.has(camelProp)) return value;
3560
+ if (!value) return value;
3561
+ if (value.startsWith("#")) return value;
3562
+ if (value.startsWith("var(")) return value;
3563
+ if (value.includes("(")) return value;
3564
+ if (isCssNamedColor(value)) return value;
3565
+ return `var(--${value})`;
3566
+ }
3547
3567
  function isStyleMapping4(value) {
3548
3568
  return typeof value === "object" && value !== null && "_mapping" in value && value._mapping === true;
3549
3569
  }
@@ -3553,6 +3573,65 @@ function isResponsiveStyle4(style) {
3553
3573
  function toKebabCase(prop) {
3554
3574
  return prop.replace(/([A-Z])/g, "-$1").toLowerCase();
3555
3575
  }
3576
+ function splitTopLevel(value) {
3577
+ const out = [];
3578
+ let depth = 0;
3579
+ let buf = "";
3580
+ for (const ch of value.trim()) {
3581
+ if (ch === "(") depth++;
3582
+ else if (ch === ")") depth--;
3583
+ if (depth === 0 && /\s/.test(ch)) {
3584
+ if (buf) {
3585
+ out.push(buf);
3586
+ buf = "";
3587
+ }
3588
+ continue;
3589
+ }
3590
+ buf += ch;
3591
+ }
3592
+ if (buf) out.push(buf);
3593
+ return out;
3594
+ }
3595
+ function expandShorthand(cssProp, cssValue) {
3596
+ if (cssProp !== "margin" && cssProp !== "padding" && cssProp !== "gap") {
3597
+ return null;
3598
+ }
3599
+ const parts = splitTopLevel(cssValue);
3600
+ if (cssProp === "gap") {
3601
+ if (parts.length === 1) {
3602
+ const v = normalizeZero("row-gap", parts[0]);
3603
+ return { "row-gap": v, "column-gap": v };
3604
+ }
3605
+ if (parts.length === 2) {
3606
+ return {
3607
+ "row-gap": normalizeZero("row-gap", parts[0]),
3608
+ "column-gap": normalizeZero("column-gap", parts[1])
3609
+ };
3610
+ }
3611
+ return null;
3612
+ }
3613
+ let top, right, bottom, left;
3614
+ if (parts.length === 1) {
3615
+ top = right = bottom = left = parts[0];
3616
+ } else if (parts.length === 2) {
3617
+ top = bottom = parts[0];
3618
+ right = left = parts[1];
3619
+ } else if (parts.length === 3) {
3620
+ top = parts[0];
3621
+ right = left = parts[1];
3622
+ bottom = parts[2];
3623
+ } else if (parts.length === 4) {
3624
+ [top, right, bottom, left] = parts;
3625
+ } else {
3626
+ return null;
3627
+ }
3628
+ return {
3629
+ [`${cssProp}-top`]: normalizeZero(`${cssProp}-top`, top),
3630
+ [`${cssProp}-right`]: normalizeZero(`${cssProp}-right`, right),
3631
+ [`${cssProp}-bottom`]: normalizeZero(`${cssProp}-bottom`, bottom),
3632
+ [`${cssProp}-left`]: normalizeZero(`${cssProp}-left`, left)
3633
+ };
3634
+ }
3556
3635
  function styleObjectToCSS(style) {
3557
3636
  const css = {};
3558
3637
  for (const [prop, value] of Object.entries(style)) {
@@ -3560,11 +3639,18 @@ function styleObjectToCSS(style) {
3560
3639
  if (value === "" || value === void 0 || value === null) continue;
3561
3640
  if (typeof value === "boolean" || typeof value === "object") continue;
3562
3641
  const cssProp = toKebabCase(prop);
3642
+ let cssValue;
3563
3643
  if (typeof value === "number") {
3564
3644
  if (isNaN(value)) continue;
3565
- css[cssProp] = UNITLESS_PROPERTIES.has(cssProp) ? String(value) : `${value}px`;
3645
+ cssValue = UNITLESS_PROPERTIES.has(cssProp) ? String(value) : `${value}px`;
3646
+ } else {
3647
+ cssValue = maybeWrapColorVar(prop, String(value));
3648
+ }
3649
+ const expanded = expandShorthand(cssProp, cssValue);
3650
+ if (expanded) {
3651
+ Object.assign(css, expanded);
3566
3652
  } else {
3567
- css[cssProp] = String(value);
3653
+ css[cssProp] = normalizeZero(cssProp, cssValue);
3568
3654
  }
3569
3655
  }
3570
3656
  return css;
@@ -3591,14 +3677,85 @@ function collectStyleMappings2(style) {
3591
3677
  return result;
3592
3678
  }
3593
3679
  function postfixToPseudoState(postfix) {
3594
- if (postfix.includes(":hover")) return "hover";
3595
3680
  if (postfix.includes(":focus-visible")) return "focus-visible";
3681
+ if (postfix.includes(":focus-within")) return "focus-within";
3682
+ if (postfix.includes(":nth-child(odd)")) return "nth-child(odd)";
3683
+ if (postfix.includes(":nth-child(even)")) return "nth-child(even)";
3684
+ if (postfix.includes(":first-child")) return "first-child";
3685
+ if (postfix.includes(":last-child")) return "last-child";
3686
+ if (postfix.includes(":placeholder")) return "placeholder";
3687
+ if (postfix.includes(":empty")) return "empty";
3688
+ if (postfix.includes(":before")) return "before";
3689
+ if (postfix.includes(":after")) return "after";
3690
+ if (postfix.includes(":hover")) return "hover";
3596
3691
  if (postfix.includes(":focus")) return "focus";
3597
3692
  if (postfix.includes(":active")) return "active";
3598
3693
  if (postfix.includes(":visited")) return "visited";
3694
+ if (postfix.includes(":pressed")) return "pressed";
3695
+ if (postfix.includes("::before")) return "before";
3696
+ if (postfix.includes("::after")) return "after";
3697
+ if (postfix.includes("::placeholder")) return "placeholder";
3599
3698
  return null;
3600
3699
  }
3601
- function mapStylesToWebflow(className, style, interactiveStyles, breakpoints) {
3700
+ function isWebflowHandledRule(rule) {
3701
+ if (rule.prefix && rule.prefix.trim().length > 0) return false;
3702
+ if (!rule.postfix) return false;
3703
+ if (postfixToPseudoState(rule.postfix) === null) return false;
3704
+ const s = rule.style;
3705
+ if (!s || typeof s !== "object") return false;
3706
+ const responsiveKeys = Object.keys(s).filter((k) => k !== "base");
3707
+ return responsiveKeys.length === 0;
3708
+ }
3709
+ function widthToWebflowBreakpoint(maxWidthPx) {
3710
+ if (maxWidthPx < 480) return "tiny";
3711
+ if (maxWidthPx < 768) return "small";
3712
+ if (maxWidthPx < 992) return "medium";
3713
+ if (maxWidthPx < 1280) return "main";
3714
+ if (maxWidthPx < 1440) return "large";
3715
+ if (maxWidthPx < 1920) return "xl";
3716
+ return "xxl";
3717
+ }
3718
+ function menoBreakpointToWebflow(bpName, breakpoints) {
3719
+ if (bpName === "tablet") return "medium";
3720
+ if (bpName === "mobile") return "small";
3721
+ const entry = breakpoints[bpName];
3722
+ if (entry && typeof entry.breakpoint === "number") {
3723
+ return widthToWebflowBreakpoint(entry.breakpoint);
3724
+ }
3725
+ return "main";
3726
+ }
3727
+ function mergeIntoBreakpoint(cls, tier, css) {
3728
+ if (!cls.breakpoints) cls.breakpoints = {};
3729
+ cls.breakpoints[tier] = { ...cls.breakpoints[tier], ...css };
3730
+ }
3731
+ function kebabToCamel(s) {
3732
+ return s.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
3733
+ }
3734
+ function applyAutoScaling(cls, breakpoints, responsiveScales) {
3735
+ if (!responsiveScales?.enabled) return;
3736
+ const baseRef = responsiveScales.baseReference || 16;
3737
+ for (const [prop, baseValue] of Object.entries(cls.base)) {
3738
+ if (!baseValue || baseValue.includes("var(--")) continue;
3739
+ const camelProp = kebabToCamel(prop);
3740
+ for (const [bpName, bpEntry] of Object.entries(breakpoints)) {
3741
+ if (!bpEntry) continue;
3742
+ const scale = getScaleMultiplier(responsiveScales, camelProp, bpName);
3743
+ if (scale === null) continue;
3744
+ const scaled = scalePropertyValue(baseValue, baseRef, scale);
3745
+ if (scaled === null || scaled === baseValue) continue;
3746
+ const tier = menoBreakpointToWebflow(bpName, breakpoints);
3747
+ if (!cls.breakpoints) cls.breakpoints = {};
3748
+ const bucket = cls.breakpoints[tier] || {};
3749
+ if (bucket[prop] !== void 0) continue;
3750
+ bucket[prop] = scaled;
3751
+ cls.breakpoints[tier] = bucket;
3752
+ }
3753
+ }
3754
+ }
3755
+ function mapStylesToWebflow(className, style, interactiveStyles, breakpoints, responsiveScales, options) {
3756
+ const instanceProps = options?.instanceProps;
3757
+ const componentDefaults = options?.componentDefaults;
3758
+ const themeSuffix = options?.themeSuffix ?? "";
3602
3759
  const webflowClassName = className.replace(/_/g, "-");
3603
3760
  const primaryClass = {
3604
3761
  name: webflowClassName,
@@ -3610,21 +3767,12 @@ function mapStylesToWebflow(className, style, interactiveStyles, breakpoints) {
3610
3767
  if (responsive.base) {
3611
3768
  primaryClass.base = styleObjectToCSS(responsive.base);
3612
3769
  }
3613
- if (responsive.tablet) {
3614
- if (!primaryClass.breakpoints) primaryClass.breakpoints = {};
3615
- primaryClass.breakpoints.Tablet = styleObjectToCSS(responsive.tablet);
3616
- }
3617
- if (responsive.mobile) {
3618
- if (!primaryClass.breakpoints) primaryClass.breakpoints = {};
3619
- primaryClass.breakpoints.MobilePortrait = styleObjectToCSS(responsive.mobile);
3620
- }
3621
3770
  for (const [bpName, bpStyle] of Object.entries(responsive)) {
3622
- if (!bpStyle || bpName === "base" || bpName === "tablet" || bpName === "mobile") continue;
3623
- if (!primaryClass.breakpoints) primaryClass.breakpoints = {};
3624
- primaryClass.breakpoints.Tablet = {
3625
- ...primaryClass.breakpoints.Tablet,
3626
- ...styleObjectToCSS(bpStyle)
3627
- };
3771
+ if (!bpStyle || bpName === "base") continue;
3772
+ const css = styleObjectToCSS(bpStyle);
3773
+ if (Object.keys(css).length === 0) continue;
3774
+ const tier = menoBreakpointToWebflow(bpName, breakpoints);
3775
+ mergeIntoBreakpoint(primaryClass, tier, css);
3628
3776
  }
3629
3777
  } else {
3630
3778
  primaryClass.base = styleObjectToCSS(style);
@@ -3632,92 +3780,546 @@ function mapStylesToWebflow(className, style, interactiveStyles, breakpoints) {
3632
3780
  }
3633
3781
  if (interactiveStyles && interactiveStyles.length > 0) {
3634
3782
  for (const rule of interactiveStyles) {
3635
- if (!rule.postfix) continue;
3783
+ if (!isWebflowHandledRule(rule)) continue;
3784
+ const baseProps = isResponsiveStyle4(rule.style) ? styleObjectToCSS(rule.style.base || {}) : styleObjectToCSS(rule.style);
3785
+ if (Object.keys(baseProps).length === 0) continue;
3636
3786
  const pseudoState = postfixToPseudoState(rule.postfix);
3637
3787
  if (!pseudoState) continue;
3638
- const ruleStyle = rule.style;
3639
3788
  if (!primaryClass.pseudoStates) primaryClass.pseudoStates = {};
3640
- if (isResponsiveStyle4(ruleStyle)) {
3641
- const responsive = ruleStyle;
3642
- if (responsive.base) {
3643
- primaryClass.pseudoStates[pseudoState] = {
3644
- ...primaryClass.pseudoStates[pseudoState],
3645
- ...styleObjectToCSS(responsive.base)
3646
- };
3647
- }
3648
- } else {
3649
- primaryClass.pseudoStates[pseudoState] = {
3650
- ...primaryClass.pseudoStates[pseudoState],
3651
- ...styleObjectToCSS(ruleStyle)
3652
- };
3653
- }
3789
+ primaryClass.pseudoStates[pseudoState] = {
3790
+ ...primaryClass.pseudoStates[pseudoState],
3791
+ ...baseProps
3792
+ };
3654
3793
  }
3655
3794
  }
3656
3795
  const comboClasses = [];
3657
3796
  const mappings = collectStyleMappings2(style);
3797
+ const comboCss = {};
3798
+ const comboNameParts = [];
3658
3799
  for (const { property, mapping } of mappings) {
3659
- for (const [value, cssValue] of Object.entries(mapping.values)) {
3660
- if (cssValue === "" || cssValue === void 0) continue;
3661
- const comboName = `is-${sanitizeClassName(mapping.prop)}-${sanitizeClassName(String(value))}`;
3662
- const comboClass = {
3663
- name: comboName,
3664
- base: {
3665
- [toKebabCase(property)]: typeof cssValue === "number" ? UNITLESS_PROPERTIES.has(toKebabCase(property)) ? String(cssValue) : `${cssValue}px` : String(cssValue)
3666
- },
3667
- comboParent: webflowClassName
3668
- };
3669
- comboClasses.push(comboClass);
3800
+ const defaultValue = componentDefaults?.[mapping.prop];
3801
+ const defaultKey = defaultValue != null ? String(defaultValue) : void 0;
3802
+ if (defaultKey !== void 0 && defaultKey in mapping.values) {
3803
+ const defaultCss = mappingValueToCSS(property, mapping.values[defaultKey]);
3804
+ if (defaultCss) {
3805
+ primaryClass.base = { ...primaryClass.base, ...defaultCss };
3806
+ }
3670
3807
  }
3808
+ if (!instanceProps) continue;
3809
+ const instanceValue = instanceProps[mapping.prop];
3810
+ if (instanceValue == null) continue;
3811
+ const instanceKey = String(instanceValue);
3812
+ if (instanceKey === defaultKey) continue;
3813
+ if (!(instanceKey in mapping.values)) continue;
3814
+ const css = mappingValueToCSS(property, mapping.values[instanceKey]);
3815
+ if (!css) continue;
3816
+ Object.assign(comboCss, css);
3817
+ const part = `${sanitizeClassName(mapping.prop)}-${sanitizeClassName(instanceKey)}`;
3818
+ if (!comboNameParts.includes(part)) comboNameParts.push(part);
3819
+ }
3820
+ if (comboNameParts.length > 0 && Object.keys(comboCss).length > 0) {
3821
+ comboNameParts.sort();
3822
+ comboClasses.push({
3823
+ name: `is-${comboNameParts.join("-")}${themeSuffix}`,
3824
+ base: comboCss,
3825
+ comboParent: webflowClassName
3826
+ });
3827
+ }
3828
+ applyAutoScaling(primaryClass, breakpoints, responsiveScales);
3829
+ for (const combo of comboClasses) {
3830
+ applyAutoScaling(combo, breakpoints, responsiveScales);
3671
3831
  }
3672
3832
  return { primaryClass, comboClasses };
3673
3833
  }
3834
+ function mappingValueToCSS(property, rawValue) {
3835
+ if (rawValue === "" || rawValue === void 0 || rawValue === null) return null;
3836
+ const cssProp = toKebabCase(property);
3837
+ const cssValueStr = typeof rawValue === "number" ? UNITLESS_PROPERTIES.has(cssProp) ? String(rawValue) : `${rawValue}px` : maybeWrapColorVar(property, String(rawValue));
3838
+ return expandShorthand(cssProp, cssValueStr) ?? { [cssProp]: normalizeZero(cssProp, cssValueStr) };
3839
+ }
3674
3840
  function sanitizeClassName(name) {
3675
3841
  return name.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
3676
3842
  }
3843
+ function buildInstanceStyleCombo(comboName, rootClassName, style, interactiveStyles, breakpoints, responsiveScales) {
3844
+ const base = {};
3845
+ const bps = {};
3846
+ const pseudos = {};
3847
+ if (style) {
3848
+ if (isResponsiveStyle4(style)) {
3849
+ const responsive = style;
3850
+ if (responsive.base) Object.assign(base, styleObjectToCSS(responsive.base));
3851
+ for (const [bpName, bpStyle] of Object.entries(responsive)) {
3852
+ if (!bpStyle || bpName === "base") continue;
3853
+ const css = styleObjectToCSS(bpStyle);
3854
+ if (Object.keys(css).length === 0) continue;
3855
+ const tier = menoBreakpointToWebflow(bpName, breakpoints);
3856
+ bps[tier] = { ...bps[tier], ...css };
3857
+ }
3858
+ } else {
3859
+ Object.assign(base, styleObjectToCSS(style));
3860
+ }
3861
+ }
3862
+ if (interactiveStyles && interactiveStyles.length > 0) {
3863
+ for (const rule of interactiveStyles) {
3864
+ if (!isWebflowHandledRule(rule)) continue;
3865
+ const ruleStyle = rule.style;
3866
+ const flat = isResponsiveStyle4(ruleStyle) ? ruleStyle.base : ruleStyle;
3867
+ if (!flat) continue;
3868
+ const css = styleObjectToCSS(flat);
3869
+ if (Object.keys(css).length === 0) continue;
3870
+ const pseudo = postfixToPseudoState(rule.postfix);
3871
+ if (!pseudo) continue;
3872
+ pseudos[pseudo] = { ...pseudos[pseudo], ...css };
3873
+ }
3874
+ }
3875
+ if (Object.keys(base).length === 0 && Object.keys(bps).length === 0 && Object.keys(pseudos).length === 0) return null;
3876
+ const cls = {
3877
+ name: comboName,
3878
+ base,
3879
+ comboParent: rootClassName
3880
+ };
3881
+ if (Object.keys(bps).length > 0) cls.breakpoints = bps;
3882
+ if (Object.keys(pseudos).length > 0) cls.pseudoStates = pseudos;
3883
+ applyAutoScaling(cls, breakpoints, responsiveScales);
3884
+ return cls;
3885
+ }
3677
3886
 
3678
3887
  // lib/server/webflow/nodeToWebflow.ts
3888
+ import { readFile as readFile2 } from "fs/promises";
3889
+ import { join as join2, basename, extname } from "path";
3890
+ var PROMOTED_TO_WEBFLOW_COMPONENT = /* @__PURE__ */ new Set(["Navigation", "Footer"]);
3679
3891
  function buildElementClass2(ctx, label) {
3680
- return generateElementClassName({
3892
+ const generated = generateElementClassName({
3681
3893
  fileType: ctx.fileType,
3682
3894
  fileName: ctx.fileName,
3683
3895
  label,
3684
3896
  path: ctx.elementPath
3685
3897
  });
3898
+ if (ctx.fileType !== "component") return generated;
3899
+ const stripped = generated.replace(/^c_/, "");
3900
+ return stripped.replace(/^([a-z0-9-]+)_\1$/, "$1");
3901
+ }
3902
+ function themedClassSuffix(ctx) {
3903
+ const t = ctx.currentTheme;
3904
+ if (!t) return "";
3905
+ if (ctx.defaultTheme && t === ctx.defaultTheme) return "";
3906
+ const safe = t.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
3907
+ if (!safe) return "";
3908
+ return `-theme-${safe}`;
3909
+ }
3910
+ function withThemeSuffix(name, ctx) {
3911
+ return name + themedClassSuffix(ctx);
3912
+ }
3913
+ function mintInstanceComboName(ctx, identity) {
3914
+ let attempt = 0;
3915
+ let name = `is-${shortHash(identity)}`;
3916
+ while (true) {
3917
+ const claimedBy = ctx.comboIdentityByName.get(name);
3918
+ if (!claimedBy || claimedBy === identity) {
3919
+ ctx.comboIdentityByName.set(name, identity);
3920
+ return name;
3921
+ }
3922
+ attempt++;
3923
+ name = `is-${shortHash(`${identity}#${attempt}`)}`;
3924
+ }
3925
+ }
3926
+ function extractInterfaceDefaults(iface) {
3927
+ if (!iface) return void 0;
3928
+ const out = {};
3929
+ let any = false;
3930
+ for (const [key, def] of Object.entries(iface)) {
3931
+ if (def && "default" in def) {
3932
+ out[key] = def.default;
3933
+ any = true;
3934
+ }
3935
+ }
3936
+ return any ? out : void 0;
3937
+ }
3938
+ function buildTemplateProps(ctx, instanceProps) {
3939
+ return {
3940
+ ...ctx.templateContext ?? {},
3941
+ ...ctx.cmsContext ?? {},
3942
+ ...ctx.componentDefaults ?? {},
3943
+ ...ctx.slotInstanceProps ?? {},
3944
+ ...instanceProps ?? {}
3945
+ };
3686
3946
  }
3687
- function resolveTemplate2(text, props) {
3688
- if (!props) return text;
3689
- return text.replace(/\{\{(.+?)\}\}/g, (_, expr) => {
3690
- const trimmed = expr.trim();
3691
- const orMatch = trimmed.match(/^(.+?)\s*\|\|\s*['"](.+?)['"]$/);
3692
- if (orMatch) {
3693
- const value2 = resolveNestedProp(props, orMatch[1].trim());
3694
- return value2 !== void 0 && value2 !== "" && value2 !== null ? String(value2) : orMatch[2];
3695
- }
3696
- const ternaryMatch = trimmed.match(/^(.+?)\s*\?\s*['"](.+?)['"]\s*:\s*['"](.+?)['"]$/);
3697
- if (ternaryMatch) {
3698
- const value2 = resolveNestedProp(props, ternaryMatch[1].trim());
3699
- return value2 ? ternaryMatch[2] : ternaryMatch[3];
3700
- }
3701
- const value = resolveNestedProp(props, trimmed);
3702
- return value !== void 0 ? String(value) : "";
3703
- });
3947
+ function resolveStringTemplate(text, ctx, instanceProps) {
3948
+ if (!hasTemplates(text)) return text;
3949
+ const out = processCodeTemplates(text, buildTemplateProps(ctx, instanceProps));
3950
+ return out.startsWith(RAW_HTML_PREFIX) ? flattenInlineHtmlToText(out.slice(RAW_HTML_PREFIX.length)) : out;
3951
+ }
3952
+ var HEADING_TAGS = /* @__PURE__ */ new Set(["h1", "h2", "h3", "h4", "h5", "h6"]);
3953
+ var LINK_LIKE_TAGS = /* @__PURE__ */ new Set(["button"]);
3954
+ var LIST_TAGS = /* @__PURE__ */ new Set(["ul", "ol"]);
3955
+ function applyHeadingMarginDefaults(cls) {
3956
+ const base = cls.base;
3957
+ if (!("margin-top" in base)) base["margin-top"] = "0px";
3958
+ if (!("margin-bottom" in base)) base["margin-bottom"] = "0px";
3959
+ }
3960
+ function applyParagraphMarginDefaults(cls) {
3961
+ const base = cls.base;
3962
+ if (!("margin-bottom" in base)) base["margin-bottom"] = "0px";
3963
+ }
3964
+ function applyListMarginDefaults(cls) {
3965
+ const base = cls.base;
3966
+ if (!("margin-bottom" in base)) base["margin-bottom"] = "0px";
3967
+ }
3968
+ function applyGridRowsDefault(cls) {
3969
+ const base = cls.base;
3970
+ const display = base["display"];
3971
+ if (display !== "grid" && display !== "inline-grid") return;
3972
+ if ("grid" in base) return;
3973
+ if ("grid-template" in base) return;
3974
+ if ("grid-template-rows" in base) return;
3975
+ base["grid-template-rows"] = "1fr";
3976
+ }
3977
+ function applyLinkTextDecorationDefault(cls) {
3978
+ const base = cls.base;
3979
+ if ("text-decoration" in base || "text-decoration-line" in base) return;
3980
+ base["text-decoration"] = "none";
3981
+ }
3982
+ function mergeComboClasses(inner, outer, parentName) {
3983
+ const innerSuffix = inner.name.startsWith("is-") ? inner.name.slice(3) : inner.name;
3984
+ const merged = {
3985
+ name: `${outer.name}_${innerSuffix}`,
3986
+ base: { ...inner.base, ...outer.base },
3987
+ comboParent: parentName
3988
+ };
3989
+ if (inner.breakpoints || outer.breakpoints) {
3990
+ const bps = {};
3991
+ const tiers = /* @__PURE__ */ new Set([
3992
+ ...Object.keys(inner.breakpoints || {}),
3993
+ ...Object.keys(outer.breakpoints || {})
3994
+ ]);
3995
+ for (const tier of tiers) {
3996
+ bps[tier] = { ...inner.breakpoints?.[tier] || {}, ...outer.breakpoints?.[tier] || {} };
3997
+ }
3998
+ merged.breakpoints = bps;
3999
+ }
4000
+ if (inner.pseudoStates || outer.pseudoStates) {
4001
+ const ps = {};
4002
+ const states = /* @__PURE__ */ new Set([
4003
+ ...Object.keys(inner.pseudoStates || {}),
4004
+ ...Object.keys(outer.pseudoStates || {})
4005
+ ]);
4006
+ for (const state of states) {
4007
+ ps[state] = { ...inner.pseudoStates?.[state] || {}, ...outer.pseudoStates?.[state] || {} };
4008
+ }
4009
+ merged.pseudoStates = ps;
4010
+ }
4011
+ return merged;
4012
+ }
4013
+ var IMAGE_EXT_MIME = {
4014
+ ".png": "image/png",
4015
+ ".jpg": "image/jpeg",
4016
+ ".jpeg": "image/jpeg",
4017
+ ".gif": "image/gif",
4018
+ ".webp": "image/webp",
4019
+ ".avif": "image/avif",
4020
+ ".svg": "image/svg+xml",
4021
+ ".bmp": "image/bmp",
4022
+ ".ico": "image/x-icon"
4023
+ };
4024
+ function isAbsoluteUrl(s) {
4025
+ return /^[a-z][a-z0-9+.-]*:\/\//i.test(s) || s.startsWith("data:");
4026
+ }
4027
+ async function maybeInlineLocalImage(element, src) {
4028
+ if (!src || isAbsoluteUrl(src)) return;
4029
+ const projectRoot = getProjectRoot();
4030
+ const rel = src.replace(/^\/+/, "");
4031
+ const abs = join2(projectRoot, rel);
4032
+ if (!abs.startsWith(projectRoot)) return;
4033
+ try {
4034
+ const buf = await readFile2(abs);
4035
+ const ext = extname(abs).toLowerCase();
4036
+ const mime = IMAGE_EXT_MIME[ext] || "application/octet-stream";
4037
+ element.imageDataBase64 = buf.toString("base64");
4038
+ element.imageDataMime = mime;
4039
+ element.imageDataFileName = basename(abs);
4040
+ } catch {
4041
+ }
4042
+ }
4043
+ function flattenInlineHtmlToText(raw) {
4044
+ const decode = (s) => s.replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&nbsp;/g, " ");
4045
+ let out = "";
4046
+ const tagRe = /<!--[\s\S]*?-->|<\/?([a-zA-Z][a-zA-Z0-9]*)[^>]*>/g;
4047
+ let lastIndex = 0;
4048
+ let match;
4049
+ while ((match = tagRe.exec(raw)) !== null) {
4050
+ if (match.index > lastIndex) {
4051
+ out += decode(raw.slice(lastIndex, match.index));
4052
+ }
4053
+ lastIndex = match.index + match[0].length;
4054
+ if (match[0].startsWith("<!--")) continue;
4055
+ if ((match[1] || "").toLowerCase() === "br") out += "\n";
4056
+ }
4057
+ if (lastIndex < raw.length) out += decode(raw.slice(lastIndex));
4058
+ return out;
3704
4059
  }
3705
- function resolveNestedProp(obj, path) {
3706
- const parts = path.split(".");
3707
- let current = obj;
3708
- for (const part of parts) {
3709
- if (current === null || current === void 0 || typeof current !== "object") return void 0;
3710
- current = current[part];
4060
+ function resolveVarsInValue(value, ctx, breakpoint = "base") {
4061
+ if (!value || !ctx.themeVars && !ctx.projectVars) return value;
4062
+ const themeMap = ctx.themeVars ? (ctx.currentTheme && ctx.themeVars[ctx.currentTheme]) ?? (ctx.defaultTheme ? ctx.themeVars[ctx.defaultTheme] : void 0) : void 0;
4063
+ const projectMap = ctx.projectVars ? ctx.projectVars[breakpoint] || ctx.projectVars.base : void 0;
4064
+ const pattern = /var\(\s*(--[\w-]+)\s*(?:,\s*([^)]+))?\)/g;
4065
+ let current = value;
4066
+ for (let pass = 0; pass < 3; pass++) {
4067
+ let changed = false;
4068
+ current = current.replace(pattern, (match, name, fallback) => {
4069
+ const resolved = (themeMap && themeMap[name]) ?? (projectMap && projectMap[name]);
4070
+ if (resolved !== void 0) {
4071
+ changed = true;
4072
+ return resolved;
4073
+ }
4074
+ if (fallback !== void 0) {
4075
+ changed = true;
4076
+ return fallback.trim();
4077
+ }
4078
+ return match;
4079
+ });
4080
+ if (!changed) break;
3711
4081
  }
3712
4082
  return current;
3713
4083
  }
3714
- function hasTemplates2(text) {
3715
- return /\{\{.+?\}\}/.test(text);
4084
+ function substituteVarsInStyleObject(style, ctx, breakpoint = "base") {
4085
+ const out = {};
4086
+ for (const [k, v] of Object.entries(style)) {
4087
+ out[k] = typeof v === "string" ? resolveVarsInValue(v, ctx, breakpoint) : v;
4088
+ }
4089
+ return out;
4090
+ }
4091
+ function projectBreakpointNames(ctx) {
4092
+ return Object.keys(ctx.breakpoints);
4093
+ }
4094
+ function expandResponsiveVarsInto(baseStyle, responsive, ctx) {
4095
+ if (!ctx.projectVars) return;
4096
+ const bps = projectBreakpointNames(ctx);
4097
+ if (bps.length === 0) return;
4098
+ for (const [prop, rawVal] of Object.entries(baseStyle)) {
4099
+ if (typeof rawVal !== "string" || !rawVal.includes("var(--")) continue;
4100
+ const baseResolved = resolveVarsInValue(rawVal, ctx, "base");
4101
+ for (const bp of bps) {
4102
+ const bpResolved = resolveVarsInValue(rawVal, ctx, bp);
4103
+ if (bpResolved === baseResolved) continue;
4104
+ const bucket = responsive[bp] || {};
4105
+ if (bucket[prop] !== void 0) continue;
4106
+ bucket[prop] = bpResolved;
4107
+ responsive[bp] = bucket;
4108
+ }
4109
+ }
4110
+ }
4111
+ function substituteVarsInStyle(style, ctx) {
4112
+ if (!style) return style;
4113
+ if (isResponsiveStyleObject(style)) {
4114
+ const out = {};
4115
+ for (const [bp, obj] of Object.entries(style)) {
4116
+ if (!obj || typeof obj !== "object") continue;
4117
+ out[bp] = substituteVarsInStyleObject(obj, ctx, bp);
4118
+ }
4119
+ const baseObj = style.base;
4120
+ if (baseObj) expandResponsiveVarsInto(baseObj, out, ctx);
4121
+ return out;
4122
+ }
4123
+ const flatStyle = style;
4124
+ const baseSubstituted = substituteVarsInStyleObject(flatStyle, ctx, "base");
4125
+ const responsive = { base: baseSubstituted };
4126
+ expandResponsiveVarsInto(flatStyle, responsive, ctx);
4127
+ if (Object.keys(responsive).length === 1) return baseSubstituted;
4128
+ return responsive;
4129
+ }
4130
+ function substituteVarsInInteractive(rules, ctx) {
4131
+ if (!rules) return rules;
4132
+ return rules.map((rule) => ({
4133
+ ...rule,
4134
+ style: substituteVarsInStyle(rule.style, ctx) ?? rule.style
4135
+ }));
4136
+ }
4137
+ function substituteVarsInStyleClass(cls, ctx) {
4138
+ const varBaseProps = [];
4139
+ for (const [k, v] of Object.entries(cls.base)) {
4140
+ if (typeof v === "string" && v.includes("var(--")) {
4141
+ varBaseProps.push({ prop: k, raw: v });
4142
+ }
4143
+ }
4144
+ for (const k of Object.keys(cls.base)) {
4145
+ cls.base[k] = resolveVarsInValue(cls.base[k], ctx, "base");
4146
+ }
4147
+ if (cls.breakpoints) {
4148
+ for (const [tier, bp] of Object.entries(cls.breakpoints)) {
4149
+ if (!bp) continue;
4150
+ for (const k of Object.keys(bp)) {
4151
+ bp[k] = resolveVarsInValue(bp[k], ctx, tier);
4152
+ }
4153
+ }
4154
+ }
4155
+ if (cls.pseudoStates) {
4156
+ for (const ps of Object.values(cls.pseudoStates)) {
4157
+ if (!ps) continue;
4158
+ for (const k of Object.keys(ps)) ps[k] = resolveVarsInValue(ps[k], ctx, "base");
4159
+ }
4160
+ }
4161
+ if (varBaseProps.length === 0 || !ctx.projectVars) return;
4162
+ for (const bpName of Object.keys(ctx.breakpoints)) {
4163
+ const tier = menoBreakpointToWebflowTier(bpName, ctx.breakpoints);
4164
+ for (const { prop, raw } of varBaseProps) {
4165
+ const baseResolved = cls.base[prop];
4166
+ const bpResolved = resolveVarsInValue(raw, ctx, bpName);
4167
+ if (bpResolved === baseResolved) continue;
4168
+ if (!cls.breakpoints) cls.breakpoints = {};
4169
+ const bucket = cls.breakpoints[tier] || {};
4170
+ if (bucket[prop] !== void 0) continue;
4171
+ bucket[prop] = bpResolved;
4172
+ cls.breakpoints[tier] = bucket;
4173
+ }
4174
+ }
4175
+ }
4176
+ function menoBreakpointToWebflowTier(bpName, breakpoints) {
4177
+ if (bpName === "tablet") return "medium";
4178
+ if (bpName === "mobile") return "small";
4179
+ const entry = breakpoints[bpName];
4180
+ const w = entry && typeof entry.breakpoint === "number" ? entry.breakpoint : 992;
4181
+ if (w < 480) return "tiny";
4182
+ if (w < 768) return "small";
4183
+ if (w < 992) return "medium";
4184
+ if (w < 1280) return "main";
4185
+ if (w < 1440) return "large";
4186
+ if (w < 1920) return "xl";
4187
+ return "xxl";
4188
+ }
4189
+ function isResponsiveStyleObject(style) {
4190
+ return "base" in style || "tablet" in style || "mobile" in style;
4191
+ }
4192
+ function extractBaseColor(style, ctx, instanceProps) {
4193
+ if (!style) return void 0;
4194
+ const flat = isResponsiveStyleObject(style) ? style.base : style;
4195
+ const c = flat?.color;
4196
+ if (typeof c === "string") return c;
4197
+ if (c && typeof c === "object" && c._mapping === true) {
4198
+ const mapping = c;
4199
+ const propValue = instanceProps?.[mapping.prop] ?? ctx?.componentDefaults?.[mapping.prop];
4200
+ if (propValue == null) return void 0;
4201
+ const key = String(propValue);
4202
+ const raw = mapping.values[key];
4203
+ if (typeof raw !== "string" || raw === "") return void 0;
4204
+ return ctx ? resolveVarsInValue(raw, ctx) : raw;
4205
+ }
4206
+ return void 0;
4207
+ }
4208
+ function concreteColor(c) {
4209
+ if (!c) return void 0;
4210
+ const lower = c.trim().toLowerCase();
4211
+ if (lower === "currentcolor" || lower === "inherit" || lower === "unset" || lower === "initial") {
4212
+ return void 0;
4213
+ }
4214
+ return c;
4215
+ }
4216
+ function inlineCurrentColorInSvg(svg, color) {
4217
+ return svg.replace(/currentColor/gi, color);
4218
+ }
4219
+ function resolveTemplatesInStyleObject(style, props) {
4220
+ const result = {};
4221
+ for (const [key, value] of Object.entries(style)) {
4222
+ if (typeof value === "string" && hasTemplates(value)) {
4223
+ result[key] = processCodeTemplates(value, props);
4224
+ } else {
4225
+ result[key] = value;
4226
+ }
4227
+ }
4228
+ return result;
4229
+ }
4230
+ function resolveStyleTemplates(style, props) {
4231
+ if (!style || !props) return style;
4232
+ if (isResponsiveStyleObject(style)) {
4233
+ const result = {};
4234
+ for (const [bp, styleObj] of Object.entries(style)) {
4235
+ if (styleObj && typeof styleObj === "object") {
4236
+ result[bp] = resolveTemplatesInStyleObject(styleObj, props);
4237
+ }
4238
+ }
4239
+ return result;
4240
+ }
4241
+ return resolveTemplatesInStyleObject(style, props);
4242
+ }
4243
+ var SINGLE_TEMPLATE_RE = /^([^{]*)\{\{\s*([A-Za-z_$][\w$]*)\s*\}\}([^{]*)$/;
4244
+ function extractSingleTemplateProp(value) {
4245
+ const m = SINGLE_TEMPLATE_RE.exec(value);
4246
+ if (!m) return null;
4247
+ return { prefix: m[1] ?? "", prop: m[2], suffix: m[3] ?? "" };
3716
4248
  }
3717
- function nodeToWebflow(node, ctx, instanceProps) {
4249
+ function templatesToSyntheticMappings(style, componentDefaults, instanceProps) {
4250
+ const out = {};
4251
+ for (const [key, value] of Object.entries(style)) {
4252
+ if (typeof value !== "string" || !hasTemplates(value)) {
4253
+ out[key] = value;
4254
+ continue;
4255
+ }
4256
+ const parsed = extractSingleTemplateProp(value);
4257
+ if (!parsed) {
4258
+ out[key] = value;
4259
+ continue;
4260
+ }
4261
+ const { prop } = parsed;
4262
+ const defaultPropValue = componentDefaults[prop];
4263
+ if (defaultPropValue == null) {
4264
+ out[key] = value;
4265
+ continue;
4266
+ }
4267
+ const instancePropValue = instanceProps[prop] ?? defaultPropValue;
4268
+ const defaultResolved = processCodeTemplates(value, { ...instanceProps, [prop]: defaultPropValue });
4269
+ const instanceResolved = processCodeTemplates(value, { ...instanceProps, [prop]: instancePropValue });
4270
+ out[key] = {
4271
+ _mapping: true,
4272
+ prop,
4273
+ values: {
4274
+ [String(defaultPropValue)]: defaultResolved,
4275
+ [String(instancePropValue)]: instanceResolved
4276
+ }
4277
+ };
4278
+ }
4279
+ return out;
4280
+ }
4281
+ function convertStyleTemplatesToMappings(style, componentDefaults, instanceProps) {
4282
+ if (!style || !componentDefaults || !instanceProps) return style;
4283
+ if (isResponsiveStyleObject(style)) {
4284
+ const result = {};
4285
+ for (const [bp, styleObj] of Object.entries(style)) {
4286
+ if (styleObj && typeof styleObj === "object") {
4287
+ result[bp] = templatesToSyntheticMappings(styleObj, componentDefaults, instanceProps);
4288
+ }
4289
+ }
4290
+ return result;
4291
+ }
4292
+ return templatesToSyntheticMappings(style, componentDefaults, instanceProps);
4293
+ }
4294
+ function shouldRenderNode(node, instanceProps) {
4295
+ if (!hasIf(node)) return true;
4296
+ const ifValue = node.if;
4297
+ if (typeof ifValue === "boolean") return ifValue;
4298
+ if (isBooleanMapping(ifValue)) {
4299
+ if (!instanceProps) return true;
4300
+ const propValue = instanceProps[ifValue.prop];
4301
+ const mapped = ifValue.values[String(propValue)];
4302
+ return mapped !== void 0 ? Boolean(mapped) : true;
4303
+ }
4304
+ if (typeof ifValue === "string") {
4305
+ if (!instanceProps) return true;
4306
+ const resolved = processCodeTemplates(ifValue, instanceProps);
4307
+ return Boolean(resolved) && resolved !== "false" && resolved !== "0" && resolved !== "";
4308
+ }
4309
+ return true;
4310
+ }
4311
+ function resolveInteractiveStyleTemplates(rules, props) {
4312
+ if (!rules || !props) return rules;
4313
+ return rules.map((rule) => ({
4314
+ ...rule,
4315
+ style: resolveStyleTemplates(rule.style, props) ?? rule.style
4316
+ }));
4317
+ }
4318
+ async function nodeToWebflow(node, ctx, instanceProps) {
3718
4319
  if (node === null || node === void 0) return [];
3719
4320
  if (typeof node === "string") {
3720
- const text = instanceProps ? resolveTemplate2(node, instanceProps) : node;
4321
+ const text = resolveStringTemplate(node, ctx, instanceProps);
4322
+ if (text === "" || text.trim() === "") return [];
3721
4323
  return [{ tag: "span", textContent: text }];
3722
4324
  }
3723
4325
  if (typeof node === "number") {
@@ -3729,88 +4331,114 @@ function nodeToWebflow(node, ctx, instanceProps) {
3729
4331
  const child = node[i];
3730
4332
  const savedPath = [...ctx.elementPath];
3731
4333
  ctx.elementPath = [...ctx.elementPath, i];
3732
- results.push(...nodeToWebflow(child, ctx, instanceProps));
4334
+ results.push(...await nodeToWebflow(child, ctx, instanceProps));
3733
4335
  ctx.elementPath = savedPath;
3734
4336
  }
3735
4337
  return results;
3736
4338
  }
4339
+ if (!shouldRenderNode(node, instanceProps)) return [];
3737
4340
  switch (node.type) {
3738
4341
  case NODE_TYPE.NODE:
3739
- return [emitHtmlNode2(node, ctx, instanceProps)];
4342
+ return [await emitHtmlNode2(node, ctx, instanceProps)];
3740
4343
  case NODE_TYPE.COMPONENT:
3741
4344
  return emitComponentInstance2(node, ctx, instanceProps);
3742
4345
  case NODE_TYPE.SLOT:
3743
4346
  return emitSlotMarker2(node, ctx, instanceProps);
3744
4347
  case NODE_TYPE.EMBED:
3745
- return [emitEmbedNode2(node, ctx, instanceProps)];
4348
+ return [await emitEmbedNode2(node, ctx, instanceProps)];
3746
4349
  case NODE_TYPE.LINK:
3747
- return [emitLinkNode2(node, ctx, instanceProps)];
4350
+ return [await emitLinkNode2(node, ctx, instanceProps)];
3748
4351
  case NODE_TYPE.LIST:
3749
4352
  case "cms-list":
4353
+ return emitListNode2(node, ctx, instanceProps);
3750
4354
  case NODE_TYPE.LOCALE_LIST:
3751
- return [{ tag: "div", attributes: { "data-meno-type": node.type } }];
4355
+ return emitLocaleListNode2(node, ctx, instanceProps);
3752
4356
  default:
3753
4357
  return [];
3754
4358
  }
3755
4359
  }
3756
- function emitHtmlNode2(node, ctx, instanceProps) {
3757
- const tag = hasTemplates2(node.tag) && instanceProps ? resolveTemplate2(node.tag, instanceProps) : node.tag;
3758
- const style = node.style;
3759
- const interactiveStyles = node.interactiveStyles;
3760
- const needsClass = style || interactiveStyles && interactiveStyles.length > 0 || node.generateElementClass;
4360
+ async function emitHtmlNode2(node, ctx, instanceProps) {
4361
+ const tag = resolveStringTemplate(node.tag, ctx, instanceProps);
4362
+ const attributes = {};
4363
+ if (node.attributes) {
4364
+ for (const [key, value] of Object.entries(node.attributes)) {
4365
+ attributes[key] = typeof value === "string" ? resolveStringTemplate(value, ctx, instanceProps) : value;
4366
+ }
4367
+ }
4368
+ const themedCtx = typeof attributes.theme === "string" && attributes.theme ? { ...ctx, currentTheme: attributes.theme } : ctx;
4369
+ const rawStyle = node.style;
4370
+ const rawInteractiveStyles = node.interactiveStyles;
4371
+ const styleWithMappings = convertStyleTemplatesToMappings(
4372
+ rawStyle,
4373
+ ctx.componentDefaults,
4374
+ instanceProps
4375
+ );
4376
+ const style = substituteVarsInStyle(
4377
+ resolveStyleTemplates(styleWithMappings, instanceProps),
4378
+ themedCtx
4379
+ );
4380
+ const interactiveStyles = substituteVarsInInteractive(
4381
+ resolveInteractiveStyleTemplates(rawInteractiveStyles, instanceProps),
4382
+ themedCtx
4383
+ );
4384
+ const tagLower = tag.toLowerCase();
4385
+ const isHeading = HEADING_TAGS.has(tagLower);
4386
+ const isLinkLike = LINK_LIKE_TAGS.has(tagLower);
4387
+ const isParagraph = tagLower === "p";
4388
+ const isList = LIST_TAGS.has(tagLower);
4389
+ const needsClass = style || interactiveStyles && interactiveStyles.length > 0 || node.generateElementClass || isHeading || isLinkLike || isParagraph || isList;
3761
4390
  let className;
3762
4391
  let comboClassNames;
3763
4392
  if (needsClass) {
3764
- const elementClass = buildElementClass2(ctx, node.label);
4393
+ const elementClass = withThemeSuffix(buildElementClass2(ctx, node.label), themedCtx);
3765
4394
  const { primaryClass, comboClasses } = mapStylesToWebflow(
3766
4395
  elementClass,
3767
4396
  style,
3768
4397
  interactiveStyles,
3769
- ctx.breakpoints
4398
+ ctx.breakpoints,
4399
+ ctx.responsiveScales,
4400
+ { instanceProps, componentDefaults: ctx.componentDefaults, themeSuffix: themedClassSuffix(themedCtx) }
3770
4401
  );
4402
+ substituteVarsInStyleClass(primaryClass, themedCtx);
4403
+ if (isHeading) applyHeadingMarginDefaults(primaryClass);
4404
+ if (isParagraph) applyParagraphMarginDefaults(primaryClass);
4405
+ if (isList) applyListMarginDefaults(primaryClass);
4406
+ if (isLinkLike) applyLinkTextDecorationDefault(primaryClass);
4407
+ applyGridRowsDefault(primaryClass);
3771
4408
  className = primaryClass.name;
3772
4409
  ctx.styleClasses.set(primaryClass.name, primaryClass);
3773
4410
  if (comboClasses.length > 0) {
3774
4411
  comboClassNames = [];
3775
4412
  for (const combo of comboClasses) {
4413
+ substituteVarsInStyleClass(combo, themedCtx);
3776
4414
  ctx.styleClasses.set(combo.name, combo);
3777
4415
  comboClassNames.push(combo.name);
3778
4416
  }
3779
4417
  }
3780
- }
3781
- const attributes = {};
3782
- if (node.attributes) {
3783
- for (const [key, value] of Object.entries(node.attributes)) {
3784
- if (instanceProps && typeof value === "string" && hasTemplates2(value)) {
3785
- attributes[key] = resolveTemplate2(value, instanceProps);
3786
- } else {
3787
- attributes[key] = value;
3788
- }
4418
+ if (ctx.interactiveStylesMap && Array.isArray(interactiveStyles) && interactiveStyles.length > 0) {
4419
+ ctx.interactiveStylesMap.set(primaryClass.name, interactiveStyles);
3789
4420
  }
3790
4421
  }
3791
4422
  let children;
3792
4423
  let textContent;
3793
4424
  if (!isVoidElement(tag) && node.children) {
3794
4425
  if (typeof node.children === "string") {
3795
- textContent = instanceProps ? resolveTemplate2(node.children, instanceProps) : node.children;
4426
+ textContent = resolveStringTemplate(node.children, ctx, instanceProps);
3796
4427
  } else if (Array.isArray(node.children) && node.children.length === 1 && typeof node.children[0] === "string") {
3797
4428
  const text = node.children[0];
3798
- textContent = instanceProps ? resolveTemplate2(text, instanceProps) : text;
4429
+ textContent = resolveStringTemplate(text, ctx, instanceProps);
3799
4430
  } else {
3800
- const innerCtx = { ...ctx, elementPath: [...ctx.elementPath] };
3801
- children = convertChildren(node.children, innerCtx, instanceProps);
4431
+ const innerCtx = { ...themedCtx, elementPath: [...themedCtx.elementPath] };
4432
+ const ownColor = concreteColor(extractBaseColor(style, themedCtx, instanceProps));
4433
+ if (ownColor) innerCtx.inheritedColor = ownColor;
4434
+ children = await convertChildren(node.children, innerCtx, instanceProps);
3802
4435
  if (children.length === 0) children = void 0;
3803
4436
  }
3804
4437
  }
3805
- let conditional;
3806
- const ifValue = node.if;
3807
- if (ifValue !== void 0 && ifValue !== true) {
3808
- if (typeof ifValue === "object" && ifValue._mapping) {
3809
- conditional = { prop: ifValue.prop, condition: "truthy" };
3810
- } else if (typeof ifValue === "string") {
3811
- const match = ifValue.match(/^\{\{(.+)\}\}$/);
3812
- conditional = { prop: match ? match[1].trim() : ifValue, condition: "truthy" };
3813
- }
4438
+ if (textContent && textContent.startsWith(RAW_HTML_PREFIX)) {
4439
+ textContent = flattenInlineHtmlToText(textContent.slice(RAW_HTML_PREFIX.length));
4440
+ } else if (textContent && (isHeading || isParagraph) && /<[a-zA-Z!]/.test(textContent)) {
4441
+ textContent = flattenInlineHtmlToText(textContent);
3814
4442
  }
3815
4443
  const element = { tag };
3816
4444
  if (className) element.className = className;
@@ -3818,115 +4446,786 @@ function emitHtmlNode2(node, ctx, instanceProps) {
3818
4446
  if (textContent) element.textContent = textContent;
3819
4447
  if (children) element.children = children;
3820
4448
  if (Object.keys(attributes).length > 0) element.attributes = attributes;
3821
- if (conditional) element.conditional = conditional;
4449
+ if (tag.toLowerCase() === "img" && typeof attributes.src === "string") {
4450
+ await maybeInlineLocalImage(element, attributes.src);
4451
+ }
3822
4452
  return element;
3823
4453
  }
3824
- function emitComponentInstance2(node, ctx, parentProps) {
4454
+ async function emitComponentInstance2(node, ctx, parentProps) {
3825
4455
  const compDef = ctx.globalComponents[node.component];
3826
4456
  if (!compDef) {
3827
4457
  return [{ tag: "div", attributes: { "data-component": node.component } }];
3828
4458
  }
3829
- const resolvedProps = {};
3830
4459
  const structured = compDef.component;
3831
- if (structured?.interface) {
3832
- for (const [key, propDef] of Object.entries(structured.interface)) {
3833
- resolvedProps[key] = propDef.default;
3834
- }
3835
- }
4460
+ const passedProps = {};
3836
4461
  if (node.props) {
4462
+ const parentTemplateProps = parentProps ?? buildTemplateProps(ctx);
3837
4463
  for (const [key, value] of Object.entries(node.props)) {
3838
4464
  if (key === "children") continue;
3839
- if (typeof value === "string" && hasTemplates2(value) && parentProps) {
3840
- resolvedProps[key] = resolveTemplate2(value, parentProps);
3841
- } else {
3842
- resolvedProps[key] = value;
3843
- }
4465
+ passedProps[key] = typeof value === "string" && hasTemplates(value) ? processCodeTemplates(value, parentTemplateProps) : value;
3844
4466
  }
3845
4467
  }
4468
+ const resolvedProps = structured ? resolvePropsFromDefinition(
4469
+ structured,
4470
+ passedProps,
4471
+ node.children,
4472
+ ctx.locale,
4473
+ ctx.i18nConfig
4474
+ ) : { ...passedProps };
4475
+ delete resolvedProps.children;
3846
4476
  const body = structured?.structure || compDef.node;
3847
4477
  if (!body) return [];
4478
+ const hasInstanceStyleOverrides = Boolean(structured?.acceptsStyles) && (node.style && Object.keys(node.style).length > 0 || Array.isArray(node.interactiveStyles) && node.interactiveStyles.length > 0);
4479
+ const promotedNames = ctx.promotedComponentNames || PROMOTED_TO_WEBFLOW_COMPONENT;
4480
+ if (promotedNames.has(node.component) && ctx.promotedComponents && !hasInstanceStyleOverrides) {
4481
+ const promoted = ctx.promotedComponents;
4482
+ if (!promoted.has(node.component)) {
4483
+ const defaultProps = {};
4484
+ if (structured?.interface) {
4485
+ for (const [k, p] of Object.entries(structured.interface)) {
4486
+ defaultProps[k] = p.default;
4487
+ }
4488
+ }
4489
+ const compBodyCtx = {
4490
+ ...ctx,
4491
+ fileType: "component",
4492
+ fileName: node.component,
4493
+ elementPath: [0],
4494
+ slotChildren: void 0,
4495
+ slotEmitContext: void 0,
4496
+ // Disable further promotion inside the component body — a Navigation
4497
+ // that nests Footer (or itself) would loop. Nested promoted names
4498
+ // inline within the registered Component's body.
4499
+ promotedComponents: null,
4500
+ componentDefaults: defaultProps
4501
+ };
4502
+ const elements = await nodeToWebflow(body, compBodyCtx, defaultProps);
4503
+ promoted.set(node.component, { name: node.component, elements });
4504
+ }
4505
+ const fallback = await emitInlineComponentBody(
4506
+ node,
4507
+ compDef,
4508
+ resolvedProps,
4509
+ body,
4510
+ { ...ctx, promotedComponents: null },
4511
+ parentProps
4512
+ );
4513
+ return [{ tag: "div", componentRef: node.component, inlineFallback: fallback }];
4514
+ }
4515
+ return emitInlineComponentBody(node, compDef, resolvedProps, body, ctx, parentProps);
4516
+ }
4517
+ async function emitInlineComponentBody(node, compDef, resolvedProps, body, ctx, parentProps) {
4518
+ const structured = compDef.component;
4519
+ let effectiveChildren = node.children;
4520
+ let inheritedSlotEmitContext = ctx.slotEmitContext;
4521
+ let inheritedSlotInstanceProps = void 0;
4522
+ let didForwardSlot = false;
4523
+ if (node.children != null && typeof node.children !== "string") {
4524
+ const childArr = Array.isArray(node.children) ? node.children : [node.children];
4525
+ if (childArr.some((c) => c && typeof c === "object" && c.type === NODE_TYPE.SLOT)) {
4526
+ const out = [];
4527
+ for (const c of childArr) {
4528
+ if (c && typeof c === "object" && c.type === NODE_TYPE.SLOT) {
4529
+ didForwardSlot = true;
4530
+ if (ctx.slotChildren !== void 0) {
4531
+ const sc = ctx.slotChildren;
4532
+ const subArr = Array.isArray(sc) ? sc : [sc];
4533
+ out.push(...subArr);
4534
+ } else {
4535
+ const def = c.default;
4536
+ if (def !== void 0) {
4537
+ const defArr = Array.isArray(def) ? def : [def];
4538
+ out.push(...defArr);
4539
+ }
4540
+ }
4541
+ } else {
4542
+ out.push(c);
4543
+ }
4544
+ }
4545
+ if (didForwardSlot) {
4546
+ effectiveChildren = out;
4547
+ inheritedSlotInstanceProps = ctx.slotInstanceProps;
4548
+ }
4549
+ }
4550
+ }
3848
4551
  const compCtx = {
3849
4552
  ...ctx,
3850
4553
  fileType: "component",
3851
4554
  fileName: node.component,
3852
4555
  elementPath: [0],
3853
- slotChildren: node.children
4556
+ slotChildren: effectiveChildren,
4557
+ slotEmitContext: didForwardSlot && inheritedSlotEmitContext ? inheritedSlotEmitContext : {
4558
+ fileType: ctx.fileType,
4559
+ fileName: ctx.fileName,
4560
+ elementPath: [...ctx.elementPath]
4561
+ },
4562
+ // Slot children authored alongside this instance refer to the OUTER
4563
+ // component's interface (the one whose body contains `<Component …>{slot
4564
+ // children}</Component>`). When the body hits its `<slot/>`, those
4565
+ // children must resolve `{{title}}` etc. against `parentProps` — not
4566
+ // against this component's resolved props. For forwarded slot children,
4567
+ // use the original outer slot's `slotInstanceProps` instead so they keep
4568
+ // their original authoring scope through the wrapper.
4569
+ slotInstanceProps: didForwardSlot ? inheritedSlotInstanceProps : parentProps,
4570
+ componentDefaults: extractInterfaceDefaults(structured?.interface)
3854
4571
  };
3855
- return nodeToWebflow(body, compCtx, resolvedProps);
4572
+ const ctxTemplate = ctx.templateContext;
4573
+ const bodyProps = ctxTemplate ? { ...ctxTemplate, ...resolvedProps } : resolvedProps;
4574
+ const emitted = await nodeToWebflow(body, compCtx, bodyProps);
4575
+ if (structured?.acceptsStyles && emitted.length > 0 && (node.style && Object.keys(node.style).length > 0 || Array.isArray(node.interactiveStyles) && node.interactiveStyles.length > 0)) {
4576
+ const root = emitted[0];
4577
+ if (root && typeof root === "object") {
4578
+ if (!root.className) {
4579
+ const minted = withThemeSuffix(buildElementClass2(compCtx, void 0), compCtx);
4580
+ ctx.styleClasses.set(minted, { name: minted, base: {} });
4581
+ root.className = minted;
4582
+ }
4583
+ const rawStyle = node.style;
4584
+ const rawInteractive = node.interactiveStyles;
4585
+ const resolvedStyle = substituteVarsInStyle(
4586
+ resolveStyleTemplates(rawStyle, parentProps),
4587
+ ctx
4588
+ );
4589
+ const resolvedInteractive = substituteVarsInInteractive(
4590
+ resolveInteractiveStyleTemplates(rawInteractive, parentProps),
4591
+ ctx
4592
+ );
4593
+ const instanceLocClass = withThemeSuffix(buildElementClass2(ctx, node.label), ctx);
4594
+ const comboName = mintInstanceComboName(ctx, instanceLocClass);
4595
+ const combo = buildInstanceStyleCombo(
4596
+ comboName,
4597
+ root.className,
4598
+ resolvedStyle,
4599
+ resolvedInteractive,
4600
+ ctx.breakpoints,
4601
+ ctx.responsiveScales
4602
+ );
4603
+ if (combo) {
4604
+ substituteVarsInStyleClass(combo, ctx);
4605
+ const existingComboName = Array.isArray(root.comboClasses) && root.comboClasses.length > 0 ? root.comboClasses[0] : void 0;
4606
+ const inner = existingComboName ? ctx.styleClasses.get(existingComboName) : void 0;
4607
+ if (inner) {
4608
+ const merged = mergeComboClasses(inner, combo, root.className);
4609
+ ctx.styleClasses.set(merged.name, merged);
4610
+ root.comboClasses = [merged.name];
4611
+ } else {
4612
+ ctx.styleClasses.set(combo.name, combo);
4613
+ root.comboClasses = [combo.name];
4614
+ }
4615
+ }
4616
+ }
4617
+ }
4618
+ if (emitted.length > 0 && structured?.javascript) {
4619
+ const root = emitted[0];
4620
+ if (root && typeof root === "object") {
4621
+ root.attributes = root.attributes || {};
4622
+ const existing = root.attributes["data-component"];
4623
+ root.attributes["data-component"] = existing ? `${existing} ${node.component}` : node.component;
4624
+ const defineVars = structured.defineVars;
4625
+ if (defineVars) {
4626
+ const varsToExpose = defineVars === true ? Object.keys(structured.interface || {}) : defineVars;
4627
+ const propsForJS = {};
4628
+ for (const varName of varsToExpose) {
4629
+ if (resolvedProps[varName] !== void 0) {
4630
+ propsForJS[varName] = resolvedProps[varName];
4631
+ }
4632
+ }
4633
+ let existingPropsMap = {};
4634
+ const existingPropsStr = root.attributes["data-props"];
4635
+ if (typeof existingPropsStr === "string" && existingPropsStr) {
4636
+ try {
4637
+ const parsed = JSON.parse(decodeURIComponent(existingPropsStr));
4638
+ if (parsed && typeof parsed === "object") {
4639
+ existingPropsMap = parsed;
4640
+ }
4641
+ } catch {
4642
+ }
4643
+ }
4644
+ existingPropsMap[node.component] = propsForJS;
4645
+ root.attributes["data-props"] = encodeURIComponent(JSON.stringify(existingPropsMap));
4646
+ }
4647
+ }
4648
+ }
4649
+ return emitted;
3856
4650
  }
3857
- function emitSlotMarker2(node, ctx, instanceProps) {
4651
+ async function emitSlotMarker2(node, ctx, instanceProps) {
3858
4652
  if (ctx.slotChildren) {
4653
+ const restored = ctx.slotEmitContext;
3859
4654
  const parentCtx = {
3860
4655
  ...ctx,
3861
- slotChildren: void 0
4656
+ ...restored ? {
4657
+ fileType: restored.fileType,
4658
+ fileName: restored.fileName,
4659
+ elementPath: [...restored.elementPath]
4660
+ } : {},
4661
+ slotChildren: void 0,
3862
4662
  // prevent infinite slot nesting
4663
+ slotEmitContext: void 0,
4664
+ // consumed
4665
+ slotInstanceProps: void 0
4666
+ // consumed
3863
4667
  };
3864
- return convertChildren(ctx.slotChildren, parentCtx, instanceProps);
4668
+ const slotProps = ctx.slotInstanceProps ?? instanceProps;
4669
+ return convertChildren(ctx.slotChildren, parentCtx, slotProps);
3865
4670
  }
3866
4671
  if (node.default) {
3867
4672
  return convertChildren(node.default, ctx, instanceProps);
3868
4673
  }
3869
4674
  return [];
3870
4675
  }
3871
- function emitEmbedNode2(node, ctx, instanceProps) {
3872
- const style = node.style;
3873
- const interactiveStyles = node.interactiveStyles;
4676
+ async function emitEmbedNode2(node, ctx, instanceProps) {
4677
+ const style = substituteVarsInStyle(
4678
+ resolveStyleTemplates(
4679
+ node.style,
4680
+ instanceProps
4681
+ ),
4682
+ ctx
4683
+ );
4684
+ const interactiveStyles = substituteVarsInInteractive(
4685
+ resolveInteractiveStyleTemplates(
4686
+ node.interactiveStyles,
4687
+ instanceProps
4688
+ ),
4689
+ ctx
4690
+ );
3874
4691
  let className;
3875
4692
  if (style || interactiveStyles && interactiveStyles.length > 0) {
3876
- const elementClass = buildElementClass2(ctx, node.label);
4693
+ const elementClass = withThemeSuffix(buildElementClass2(ctx, node.label), ctx);
3877
4694
  const { primaryClass } = mapStylesToWebflow(
3878
4695
  elementClass,
3879
4696
  style,
3880
4697
  interactiveStyles,
3881
- ctx.breakpoints
4698
+ ctx.breakpoints,
4699
+ ctx.responsiveScales
3882
4700
  );
4701
+ substituteVarsInStyleClass(primaryClass, ctx);
3883
4702
  className = primaryClass.name;
3884
4703
  ctx.styleClasses.set(primaryClass.name, primaryClass);
3885
4704
  }
3886
- const htmlStr = typeof node.html === "string" ? node.html : "";
3887
- const element = {
3888
- tag: "div",
3889
- rawHtml: htmlStr
3890
- };
4705
+ const props = buildTemplateProps(ctx, instanceProps);
4706
+ let htmlStr;
4707
+ if (isHtmlMapping(node.html)) {
4708
+ htmlStr = resolveHtmlMapping(node.html, props) ?? "";
4709
+ } else if (typeof node.html === "string") {
4710
+ htmlStr = hasTemplates(node.html) ? processCodeTemplates(node.html, props) : node.html;
4711
+ } else {
4712
+ htmlStr = "";
4713
+ }
4714
+ const element = { tag: "div" };
3891
4715
  if (className) element.className = className;
4716
+ const trimmed = htmlStr.trim();
4717
+ if (/^<svg\b/i.test(trimmed) && /<\/svg\s*>\s*$/i.test(trimmed)) {
4718
+ let svg = trimmed;
4719
+ if (/currentColor/i.test(svg)) {
4720
+ const resolved = concreteColor(extractBaseColor(style, ctx, instanceProps)) ?? ctx.inheritedColor;
4721
+ if (resolved) svg = inlineCurrentColorInSvg(svg, resolved);
4722
+ }
4723
+ element.svgSource = svg;
4724
+ if (node.label) element.imageAlt = node.label;
4725
+ return element;
4726
+ }
4727
+ const imgMatch = trimmed.match(/^<img\b([^>]*)\/?>\s*$/i);
4728
+ if (imgMatch) {
4729
+ const attrs = imgMatch[1];
4730
+ const src = /\bsrc\s*=\s*"([^"]*)"|\bsrc\s*=\s*'([^']*)'/i.exec(attrs);
4731
+ const alt = /\balt\s*=\s*"([^"]*)"|\balt\s*=\s*'([^']*)'/i.exec(attrs);
4732
+ if (src) {
4733
+ element.imageSrc = src[1] ?? src[2] ?? "";
4734
+ element.imageAlt = (alt?.[1] ?? alt?.[2] ?? node.label) || "";
4735
+ await maybeInlineLocalImage(element, element.imageSrc);
4736
+ return element;
4737
+ }
4738
+ }
4739
+ if (trimmed.length) {
4740
+ element.unsupportedEmbed = {
4741
+ reason: "embed-not-svg-or-image",
4742
+ preview: trimmed.slice(0, 120),
4743
+ label: node.label
4744
+ };
4745
+ }
3892
4746
  return element;
3893
4747
  }
3894
- function emitLinkNode2(node, ctx, instanceProps) {
3895
- const style = node.style;
3896
- const interactiveStyles = node.interactiveStyles;
4748
+ function resolveLinkHref(rawHref, ctx) {
4749
+ if (!rawHref) return rawHref;
4750
+ if (!rawHref.startsWith("/") || rawHref.startsWith("//")) return rawHref;
4751
+ if (!ctx.i18nConfig || !ctx.locale || !ctx.slugMappings) return rawHref;
4752
+ const hashIdx = rawHref.indexOf("#");
4753
+ const queryIdx = rawHref.indexOf("?");
4754
+ let cut = -1;
4755
+ if (hashIdx >= 0 && queryIdx >= 0) cut = Math.min(hashIdx, queryIdx);
4756
+ else if (hashIdx >= 0) cut = hashIdx;
4757
+ else if (queryIdx >= 0) cut = queryIdx;
4758
+ const path = cut >= 0 ? rawHref.slice(0, cut) : rawHref;
4759
+ const suffix = cut >= 0 ? rawHref.slice(cut) : "";
4760
+ if (!ctx.slugIndex) {
4761
+ ctx.slugIndex = buildSlugIndex(ctx.slugMappings);
4762
+ }
4763
+ const translated = translatePath(
4764
+ path,
4765
+ ctx.locale,
4766
+ ctx.i18nConfig.defaultLocale,
4767
+ ctx.i18nConfig.defaultLocale,
4768
+ ctx.slugIndex
4769
+ );
4770
+ return translated + suffix;
4771
+ }
4772
+ async function emitLinkNode2(node, ctx, instanceProps) {
4773
+ const style = substituteVarsInStyle(
4774
+ resolveStyleTemplates(
4775
+ node.style,
4776
+ instanceProps
4777
+ ),
4778
+ ctx
4779
+ );
4780
+ const interactiveStyles = substituteVarsInInteractive(
4781
+ resolveInteractiveStyleTemplates(
4782
+ node.interactiveStyles,
4783
+ instanceProps
4784
+ ),
4785
+ ctx
4786
+ );
3897
4787
  let className;
3898
- if (style || interactiveStyles && interactiveStyles.length > 0) {
3899
- const elementClass = buildElementClass2(ctx, node.label);
3900
- const { primaryClass } = mapStylesToWebflow(
4788
+ let comboClassNames;
4789
+ {
4790
+ const elementClass = withThemeSuffix(buildElementClass2(ctx, node.label), ctx);
4791
+ const { primaryClass, comboClasses } = mapStylesToWebflow(
3901
4792
  elementClass,
3902
4793
  style,
3903
4794
  interactiveStyles,
3904
- ctx.breakpoints
4795
+ ctx.breakpoints,
4796
+ ctx.responsiveScales,
4797
+ { instanceProps, componentDefaults: ctx.componentDefaults, themeSuffix: themedClassSuffix(ctx) }
3905
4798
  );
4799
+ substituteVarsInStyleClass(primaryClass, ctx);
4800
+ applyLinkTextDecorationDefault(primaryClass);
4801
+ applyGridRowsDefault(primaryClass);
3906
4802
  className = primaryClass.name;
3907
4803
  ctx.styleClasses.set(primaryClass.name, primaryClass);
4804
+ if (comboClasses.length > 0) {
4805
+ comboClassNames = [];
4806
+ for (const combo of comboClasses) {
4807
+ substituteVarsInStyleClass(combo, ctx);
4808
+ ctx.styleClasses.set(combo.name, combo);
4809
+ comboClassNames.push(combo.name);
4810
+ }
4811
+ }
4812
+ if (ctx.interactiveStylesMap && Array.isArray(interactiveStyles) && interactiveStyles.length > 0) {
4813
+ ctx.interactiveStylesMap.set(primaryClass.name, interactiveStyles);
4814
+ }
3908
4815
  }
3909
4816
  let href = "#";
3910
4817
  if (typeof node.href === "string") {
3911
- href = instanceProps && hasTemplates2(node.href) ? resolveTemplate2(node.href, instanceProps) : node.href;
4818
+ href = resolveStringTemplate(node.href, ctx, instanceProps);
3912
4819
  }
4820
+ href = resolveLinkHref(href, ctx);
3913
4821
  const attributes = { href };
3914
4822
  if (node.attributes) {
3915
4823
  for (const [key, value] of Object.entries(node.attributes)) {
3916
- attributes[key] = value;
4824
+ attributes[key] = typeof value === "string" ? resolveStringTemplate(value, ctx, instanceProps) : value;
3917
4825
  }
3918
4826
  }
3919
4827
  let children;
3920
4828
  if (node.children) {
3921
- children = convertChildren(node.children, ctx, instanceProps);
4829
+ const ownColor = concreteColor(extractBaseColor(style, ctx, instanceProps));
4830
+ const childCtx = ownColor ? { ...ctx, inheritedColor: ownColor } : ctx;
4831
+ children = await convertChildren(node.children, childCtx, instanceProps);
3922
4832
  if (children.length === 0) children = void 0;
3923
4833
  }
3924
4834
  const element = { tag: "a", attributes };
3925
4835
  if (className) element.className = className;
4836
+ if (comboClassNames) element.comboClasses = comboClassNames;
3926
4837
  if (children) element.children = children;
3927
4838
  return element;
3928
4839
  }
3929
- function convertChildren(children, ctx, instanceProps) {
4840
+ function singularize2(name) {
4841
+ if (name.endsWith("ies") && name.length > 3) return name.slice(0, -3) + "y";
4842
+ if (name.endsWith("s") && name.length > 1) return name.slice(0, -1);
4843
+ return name;
4844
+ }
4845
+ function resolveItemI18n(item, ctx) {
4846
+ if (!ctx.locale || !ctx.i18nConfig) return item;
4847
+ const out = {};
4848
+ for (const [k, v] of Object.entries(item)) {
4849
+ out[k] = isI18nValue(v) ? resolveI18nValue(v, ctx.locale, ctx.i18nConfig) : v;
4850
+ }
4851
+ return out;
4852
+ }
4853
+ async function getCollectionItemsForExport(node, source, ctx) {
4854
+ if (!ctx.cmsService) return [];
4855
+ let items = [];
4856
+ if (node.items) {
4857
+ const itemsField = node.items;
4858
+ let resolvedIds;
4859
+ if (typeof itemsField === "string" && itemsField.startsWith("{{")) {
4860
+ if (itemsField.startsWith("{{cms.") && ctx.cmsContext?.cms) {
4861
+ const fieldPath = itemsField.slice(6, -2);
4862
+ let value = ctx.cmsContext.cms;
4863
+ for (const part of fieldPath.split(".")) {
4864
+ if (value && typeof value === "object" && part in value) {
4865
+ value = value[part];
4866
+ } else {
4867
+ value = void 0;
4868
+ break;
4869
+ }
4870
+ }
4871
+ if (value !== null && value !== void 0) {
4872
+ resolvedIds = Array.isArray(value) ? value.map((v) => String(v)) : String(value);
4873
+ }
4874
+ } else {
4875
+ resolvedIds = resolveItemsTemplate(
4876
+ itemsField,
4877
+ ctx.templateContext || { _type: "template" }
4878
+ );
4879
+ }
4880
+ if (!resolvedIds) return [];
4881
+ const ids = Array.isArray(resolvedIds) ? resolvedIds : [resolvedIds];
4882
+ items = await ctx.cmsService.getItemsByIds(source, ids);
4883
+ } else {
4884
+ const ids = Array.isArray(itemsField) ? itemsField : [itemsField];
4885
+ items = await ctx.cmsService.getItemsByIds(source, ids);
4886
+ }
4887
+ if (ctx.locale) {
4888
+ items = items.filter((it) => !isItemDraftForLocale(it, ctx.locale));
4889
+ }
4890
+ } else {
4891
+ items = await ctx.cmsService.queryItems({
4892
+ collection: source,
4893
+ filter: node.filter,
4894
+ sort: node.sort,
4895
+ limit: node.limit,
4896
+ offset: node.offset,
4897
+ excludeDraftLocale: ctx.locale
4898
+ });
4899
+ }
4900
+ if (node.excludeCurrentItem && ctx.cmsContext?.cms?._id) {
4901
+ const currentId = ctx.cmsContext.cms._id;
4902
+ items = items.filter((it) => it._id !== currentId);
4903
+ }
4904
+ return items;
4905
+ }
4906
+ function getPropItemsForExport(source, ctx, instanceProps) {
4907
+ if (source.startsWith("{{") && source.endsWith("}}")) {
4908
+ const path = source.slice(2, -2).trim();
4909
+ const ctxObj = ctx.templateContext;
4910
+ if (ctxObj) {
4911
+ const resolved = getNestedValue(ctxObj, path);
4912
+ if (Array.isArray(resolved)) return resolved;
4913
+ }
4914
+ if (instanceProps) {
4915
+ const resolved = getNestedValue(instanceProps, path);
4916
+ if (Array.isArray(resolved)) return resolved;
4917
+ }
4918
+ return [];
4919
+ }
4920
+ if (instanceProps && Array.isArray(instanceProps[source])) {
4921
+ return instanceProps[source];
4922
+ }
4923
+ if (ctx.cmsContext?.cms) {
4924
+ const cmsValue = ctx.cmsContext.cms[source];
4925
+ if (Array.isArray(cmsValue)) return cmsValue;
4926
+ }
4927
+ return [];
4928
+ }
4929
+ function buildBindingPlaceholderItem(schema) {
4930
+ const item = {};
4931
+ if (schema?.fields) {
4932
+ for (const fieldSlug of Object.keys(schema.fields)) {
4933
+ item[fieldSlug] = `${MENO_BIND_SENTINEL_PREFIX}${fieldSlug}${MENO_BIND_SENTINEL_SUFFIX}`;
4934
+ }
4935
+ }
4936
+ item._id = `${MENO_BIND_SENTINEL_PREFIX}_id${MENO_BIND_SENTINEL_SUFFIX}`;
4937
+ item._slug = `${MENO_BIND_SENTINEL_PREFIX}_slug${MENO_BIND_SENTINEL_SUFFIX}`;
4938
+ item._url = `${MENO_BIND_SENTINEL_PREFIX}_url${MENO_BIND_SENTINEL_SUFFIX}`;
4939
+ return item;
4940
+ }
4941
+ function applyBindingMarkers(el) {
4942
+ if (typeof el.textContent === "string") {
4943
+ const exact = el.textContent.match(MENO_BIND_SENTINEL_EXACT_RE);
4944
+ if (exact) {
4945
+ const field = exact[1];
4946
+ el.menoBind = { ...el.menoBind || {}, textField: field };
4947
+ el.textContent = `{${field}}`;
4948
+ } else if (MENO_BIND_SENTINEL_RE.test(el.textContent)) {
4949
+ MENO_BIND_SENTINEL_RE.lastIndex = 0;
4950
+ const fields = [];
4951
+ el.textContent = el.textContent.replace(MENO_BIND_SENTINEL_RE, (_m, f) => {
4952
+ fields.push(f);
4953
+ return `{${f}}`;
4954
+ });
4955
+ if (fields.length) {
4956
+ el.menoBind = { ...el.menoBind || {}, textField: fields[0] };
4957
+ }
4958
+ }
4959
+ MENO_BIND_SENTINEL_RE.lastIndex = 0;
4960
+ }
4961
+ if (el.attributes) {
4962
+ const attrFields = el.menoBind?.attrFields || {};
4963
+ for (const [key, rawValue] of Object.entries(el.attributes)) {
4964
+ if (typeof rawValue !== "string") continue;
4965
+ const exact = rawValue.match(MENO_BIND_SENTINEL_EXACT_RE);
4966
+ if (exact) {
4967
+ attrFields[key] = exact[1];
4968
+ el.attributes[key] = `{${exact[1]}}`;
4969
+ continue;
4970
+ }
4971
+ MENO_BIND_SENTINEL_RE.lastIndex = 0;
4972
+ if (MENO_BIND_SENTINEL_RE.test(rawValue)) {
4973
+ MENO_BIND_SENTINEL_RE.lastIndex = 0;
4974
+ let firstField;
4975
+ const replaced = rawValue.replace(MENO_BIND_SENTINEL_RE, (_m, f) => {
4976
+ if (!firstField) firstField = f;
4977
+ return `{${f}}`;
4978
+ });
4979
+ if (firstField) attrFields[key] = firstField;
4980
+ el.attributes[key] = replaced;
4981
+ }
4982
+ MENO_BIND_SENTINEL_RE.lastIndex = 0;
4983
+ }
4984
+ if (Object.keys(attrFields).length > 0) {
4985
+ el.menoBind = { ...el.menoBind || {}, attrFields };
4986
+ }
4987
+ }
4988
+ if (el.menoBind?.attrFields?.src && (el.imageDataBase64 || el.imageSrc)) {
4989
+ delete el.imageDataBase64;
4990
+ delete el.imageDataMime;
4991
+ delete el.imageDataFileName;
4992
+ delete el.imageSrc;
4993
+ }
4994
+ if (Array.isArray(el.children)) {
4995
+ for (const child of el.children) {
4996
+ if (typeof child !== "string") applyBindingMarkers(child);
4997
+ }
4998
+ }
4999
+ if (Array.isArray(el.inlineFallback)) {
5000
+ for (const child of el.inlineFallback) applyBindingMarkers(child);
5001
+ }
5002
+ }
5003
+ function normalizeListChildren(elements) {
5004
+ if (!Array.isArray(elements)) return;
5005
+ for (const el of elements) {
5006
+ if (typeof el === "string") continue;
5007
+ const tag = (el.tag || "").toLowerCase();
5008
+ if ((tag === "ul" || tag === "ol") && Array.isArray(el.children)) {
5009
+ el.children = el.children.map((child) => {
5010
+ if (typeof child === "string") return { tag: "li", children: [child] };
5011
+ if ((child.tag || "").toLowerCase() === "li") return child;
5012
+ return { tag: "li", children: [child] };
5013
+ });
5014
+ }
5015
+ if (Array.isArray(el.children)) normalizeListChildren(el.children);
5016
+ if (Array.isArray(el.inlineFallback)) normalizeListChildren(el.inlineFallback);
5017
+ }
5018
+ }
5019
+ async function emitListNode2(node, ctx, instanceProps) {
5020
+ const nodeType = node.type;
5021
+ const isLegacyCMSList = nodeType === "cms-list";
5022
+ const sourceType = isLegacyCMSList ? "collection" : node.sourceType || "prop";
5023
+ if (sourceType === "collection" && !ctx.cmsService) return [];
5024
+ const rawSource = node.source || node.collection;
5025
+ const source = typeof rawSource === "string" ? rawSource : "";
5026
+ const sourceIsResolved = Array.isArray(rawSource);
5027
+ let variableName;
5028
+ if (node.itemAs) variableName = node.itemAs;
5029
+ else if (sourceType === "collection") variableName = singularize2(source);
5030
+ else variableName = "item";
5031
+ if (sourceType === "collection" && ctx.bindCollectionLists && ctx.cmsService && source) {
5032
+ const schema = ctx.cmsService.getSchema(source) || void 0;
5033
+ const placeholder = buildBindingPlaceholderItem(schema);
5034
+ const templateContext = buildTemplateContext(
5035
+ variableName,
5036
+ placeholder,
5037
+ 0,
5038
+ 1,
5039
+ ctx.templateContext
5040
+ );
5041
+ const childCtx = {
5042
+ ...ctx,
5043
+ templateContext
5044
+ };
5045
+ const mergedProps = {
5046
+ ...instanceProps || {},
5047
+ ...templateContext
5048
+ };
5049
+ const renderedChildren = node.children ? await convertChildren(node.children, childCtx, mergedProps) : [];
5050
+ for (const child of renderedChildren) {
5051
+ if (typeof child !== "string") applyBindingMarkers(child);
5052
+ }
5053
+ return [{
5054
+ tag: COLLECTION_LIST_TAG,
5055
+ menoCollectionRef: source,
5056
+ children: renderedChildren
5057
+ }];
5058
+ }
5059
+ let items;
5060
+ if (sourceType === "collection") {
5061
+ items = await getCollectionItemsForExport(node, source, ctx);
5062
+ } else if (sourceIsResolved) {
5063
+ items = rawSource;
5064
+ } else if (source) {
5065
+ items = getPropItemsForExport(source, ctx, instanceProps);
5066
+ if (node.offset) items = items.slice(node.offset);
5067
+ if (node.limit) items = items.slice(0, node.limit);
5068
+ } else {
5069
+ items = [];
5070
+ }
5071
+ return expandListItems(node, ctx, instanceProps, items, variableName, source);
5072
+ }
5073
+ async function expandListItems(node, ctx, instanceProps, items, variableName, source) {
5074
+ if (items.length === 0) return [];
5075
+ const schema = ctx.cmsService ? ctx.cmsService.getSchema(source) || void 0 : void 0;
5076
+ const out = [];
5077
+ for (let i = 0; i < items.length; i++) {
5078
+ const rawItem = items[i];
5079
+ const enriched = schema && ctx.locale && ctx.i18nConfig ? addItemUrl(rawItem, schema, ctx.locale, ctx.i18nConfig) : rawItem;
5080
+ const item = resolveItemI18n(enriched, ctx);
5081
+ const templateContext = buildTemplateContext(
5082
+ variableName,
5083
+ item,
5084
+ i,
5085
+ items.length,
5086
+ ctx.templateContext
5087
+ );
5088
+ const childCtx = {
5089
+ ...ctx,
5090
+ templateContext
5091
+ };
5092
+ const mergedProps = {
5093
+ ...instanceProps || {},
5094
+ ...templateContext
5095
+ };
5096
+ if (node.children) {
5097
+ const savedPath = [...childCtx.elementPath];
5098
+ childCtx.elementPath = [...savedPath, i];
5099
+ out.push(...await convertChildren(node.children, childCtx, mergedProps));
5100
+ childCtx.elementPath = savedPath;
5101
+ }
5102
+ }
5103
+ return out;
5104
+ }
5105
+ async function emitLocaleListNode2(node, ctx, instanceProps) {
5106
+ if (!ctx.slugMappings || !ctx.pagePath || !ctx.i18nConfig || !ctx.locale) {
5107
+ return [{ tag: "div", attributes: { "data-locale-list": "true" } }];
5108
+ }
5109
+ if (!ctx.slugIndex) {
5110
+ ctx.slugIndex = buildSlugIndex(ctx.slugMappings);
5111
+ }
5112
+ const localeLinks = getLocaleLinks(ctx.pagePath, ctx.locale, ctx.i18nConfig, ctx.slugIndex);
5113
+ const showCurrent = node.showCurrent !== false;
5114
+ const showSeparator = node.showSeparator !== false;
5115
+ const showFlag = node.showFlag !== false;
5116
+ const displayType = node.displayType || "nativeName";
5117
+ const localeIconMap = /* @__PURE__ */ new Map();
5118
+ for (const lc of ctx.i18nConfig.locales) {
5119
+ if (lc.icon) localeIconMap.set(lc.code, lc.icon);
5120
+ }
5121
+ const containerStyle = substituteVarsInStyle(
5122
+ resolveStyleTemplates(
5123
+ node.style,
5124
+ instanceProps
5125
+ ),
5126
+ ctx
5127
+ );
5128
+ const containerInteractive = substituteVarsInInteractive(
5129
+ resolveInteractiveStyleTemplates(
5130
+ node.interactiveStyles,
5131
+ instanceProps
5132
+ ),
5133
+ ctx
5134
+ );
5135
+ let containerClass;
5136
+ if (containerStyle || containerInteractive && containerInteractive.length > 0) {
5137
+ const elementClass = withThemeSuffix(buildElementClass2(ctx, node.label), ctx);
5138
+ const { primaryClass } = mapStylesToWebflow(
5139
+ elementClass,
5140
+ containerStyle,
5141
+ containerInteractive,
5142
+ ctx.breakpoints,
5143
+ ctx.responsiveScales
5144
+ );
5145
+ substituteVarsInStyleClass(primaryClass, ctx);
5146
+ containerClass = primaryClass.name;
5147
+ ctx.styleClasses.set(primaryClass.name, primaryClass);
5148
+ }
5149
+ const buildSubClass = (style, suffix) => {
5150
+ const resolved = substituteVarsInStyle(
5151
+ resolveStyleTemplates(
5152
+ style,
5153
+ instanceProps
5154
+ ),
5155
+ ctx
5156
+ );
5157
+ if (!resolved) return void 0;
5158
+ const className = withThemeSuffix(`${buildElementClass2(ctx, node.label)}-${suffix}`, ctx);
5159
+ const { primaryClass } = mapStylesToWebflow(className, resolved, void 0, ctx.breakpoints, ctx.responsiveScales);
5160
+ substituteVarsInStyleClass(primaryClass, ctx);
5161
+ if (Object.keys(primaryClass.base).length === 0 && !primaryClass.breakpoints && !primaryClass.pseudoStates) return void 0;
5162
+ ctx.styleClasses.set(primaryClass.name, primaryClass);
5163
+ return primaryClass.name;
5164
+ };
5165
+ const itemClassName = buildSubClass(node.itemStyle, "item");
5166
+ const activeItemClassName = buildSubClass(node.activeItemStyle, "item-active");
5167
+ const separatorClassName = buildSubClass(node.separatorStyle, "separator");
5168
+ const flagClassName = buildSubClass(node.flagStyle, "flag");
5169
+ const linkChildren = [];
5170
+ for (let i = 0; i < localeLinks.length; i++) {
5171
+ const link = localeLinks[i];
5172
+ if (!showCurrent && link.isCurrent) continue;
5173
+ if (i > 0 && showSeparator) {
5174
+ const sep = { tag: "span" };
5175
+ if (separatorClassName) sep.className = separatorClassName;
5176
+ linkChildren.push(sep);
5177
+ }
5178
+ const anchor = {
5179
+ tag: "a",
5180
+ attributes: {
5181
+ href: link.path,
5182
+ hreflang: link.langTag,
5183
+ "data-current": link.isCurrent ? "true" : "false",
5184
+ "data-locale": link.locale
5185
+ }
5186
+ };
5187
+ if (link.isCurrent && (activeItemClassName || itemClassName)) {
5188
+ anchor.className = itemClassName;
5189
+ if (activeItemClassName) anchor.comboClasses = [activeItemClassName];
5190
+ } else if (itemClassName) {
5191
+ anchor.className = itemClassName;
5192
+ }
5193
+ const innerChildren = [];
5194
+ const icon = localeIconMap.get(link.locale);
5195
+ if (showFlag && icon) {
5196
+ const img = {
5197
+ tag: "img",
5198
+ attributes: { src: icon, alt: `${link.nativeName} flag` }
5199
+ };
5200
+ if (flagClassName) img.className = flagClassName;
5201
+ innerChildren.push(img);
5202
+ }
5203
+ let displayText;
5204
+ switch (displayType) {
5205
+ case "code":
5206
+ displayText = link.locale.toUpperCase();
5207
+ break;
5208
+ case "name":
5209
+ displayText = link.name;
5210
+ break;
5211
+ case "nativeName":
5212
+ default:
5213
+ displayText = link.nativeName;
5214
+ break;
5215
+ }
5216
+ innerChildren.push({ tag: "div", textContent: displayText });
5217
+ anchor.children = innerChildren;
5218
+ linkChildren.push(anchor);
5219
+ }
5220
+ const wrapper = {
5221
+ tag: "div",
5222
+ attributes: { "data-locale-list": "true" },
5223
+ children: linkChildren
5224
+ };
5225
+ if (containerClass) wrapper.className = containerClass;
5226
+ return [wrapper];
5227
+ }
5228
+ async function convertChildren(children, ctx, instanceProps) {
3930
5229
  if (!children) return [];
3931
5230
  if (typeof children === "string") {
3932
5231
  return nodeToWebflow(children, ctx, instanceProps);
@@ -3946,7 +5245,7 @@ function scanJSONFiles2(dir, prefix = "") {
3946
5245
  if (entry.isFile() && entry.name.endsWith(".json")) {
3947
5246
  results.push(prefix ? `${prefix}/${entry.name}` : entry.name);
3948
5247
  } else if (entry.isDirectory()) {
3949
- results.push(...scanJSONFiles2(join2(dir, entry.name), prefix ? `${prefix}/${entry.name}` : entry.name));
5248
+ results.push(...scanJSONFiles2(join3(dir, entry.name), prefix ? `${prefix}/${entry.name}` : entry.name));
3950
5249
  }
3951
5250
  }
3952
5251
  return results;
@@ -3954,12 +5253,38 @@ function scanJSONFiles2(dir, prefix = "") {
3954
5253
  function isCMSPage2(pageData) {
3955
5254
  return pageData.meta?.source === "cms" && !!pageData.meta?.cms;
3956
5255
  }
3957
- function buildCMSItemPath(urlPattern, item, slugField, locale, i18nConfig) {
3958
- let slug = item[slugField] ?? item._slug ?? item._id;
3959
- if (isI18nValue(slug)) {
3960
- slug = resolveI18nValue(slug, locale, i18nConfig);
5256
+ function flattenCMSItemForLocale(item, locale, i18nConfig) {
5257
+ const flatten = (value) => {
5258
+ if (value === null || value === void 0) return value;
5259
+ if (isI18nValue(value)) {
5260
+ return flatten(resolveI18nValue(value, locale, i18nConfig));
5261
+ }
5262
+ if (typeof value === "object" && "__richtext__" in value) {
5263
+ const html = value.html;
5264
+ if (typeof html === "string") return RAW_HTML_PREFIX + html;
5265
+ }
5266
+ return value;
5267
+ };
5268
+ const out = {};
5269
+ for (const [key, value] of Object.entries(item)) {
5270
+ out[key] = flatten(value);
3961
5271
  }
3962
- return urlPattern.replace("{{slug}}", String(slug));
5272
+ return out;
5273
+ }
5274
+ function resolveMetaString(value, locale, i18nConfig) {
5275
+ if (value === void 0 || value === null || value === "") return void 0;
5276
+ const resolved = isI18nValue(value) ? resolveI18nValue(value, locale, i18nConfig) : value;
5277
+ if (resolved === void 0 || resolved === null || resolved === "") return void 0;
5278
+ return String(resolved);
5279
+ }
5280
+ function buildPageMetaForLocale(meta, locale, i18nConfig) {
5281
+ return {
5282
+ description: resolveMetaString(meta.description, locale, i18nConfig),
5283
+ keywords: resolveMetaString(meta.keywords, locale, i18nConfig),
5284
+ ogTitle: resolveMetaString(meta.ogTitle, locale, i18nConfig),
5285
+ ogDescription: resolveMetaString(meta.ogDescription, locale, i18nConfig),
5286
+ ogImage: resolveMetaString(meta.ogImage, locale, i18nConfig)
5287
+ };
3963
5288
  }
3964
5289
  function scanAssets(projectRoot) {
3965
5290
  const assets = [];
@@ -3970,7 +5295,7 @@ function scanAssets(projectRoot) {
3970
5295
  { dir: "assets", type: "file" }
3971
5296
  ];
3972
5297
  for (const { dir, type } of assetDirs) {
3973
- const fullDir = join2(projectRoot, dir);
5298
+ const fullDir = join3(projectRoot, dir);
3974
5299
  if (!existsSync2(fullDir)) continue;
3975
5300
  const files = scanJSONFiles2(fullDir).map((f) => f.replace(".json", ""));
3976
5301
  const allFiles = scanAllFiles(fullDir);
@@ -3993,27 +5318,50 @@ function scanAllFiles(dir, prefix = "") {
3993
5318
  if (entry.isFile()) {
3994
5319
  results.push(relativePath);
3995
5320
  } else if (entry.isDirectory()) {
3996
- results.push(...scanAllFiles(join2(dir, entry.name), relativePath));
5321
+ results.push(...scanAllFiles(join3(dir, entry.name), relativePath));
3997
5322
  }
3998
5323
  }
3999
5324
  return results;
4000
5325
  }
4001
- function extractCSSVariables(themeColorCSS, variablesCSS) {
4002
- const vars = {};
4003
- const regex = /--([\w-]+)\s*:\s*([^;]+)/g;
4004
- for (const css of [themeColorCSS, variablesCSS]) {
4005
- let match;
4006
- while ((match = regex.exec(css)) !== null) {
4007
- vars[`--${match[1]}`] = match[2].trim();
5326
+ function buildThemeVarMaps(themeConfig) {
5327
+ const palette = themeConfig.palette;
5328
+ const byTheme = {};
5329
+ for (const [themeName, theme] of Object.entries(themeConfig.themes)) {
5330
+ const vars = {};
5331
+ for (const [name, value] of Object.entries(theme.colors)) {
5332
+ vars[`--${name}`] = resolvePaletteColor(value, palette);
5333
+ }
5334
+ byTheme[themeName] = vars;
5335
+ }
5336
+ return { byTheme, defaultTheme: themeConfig.default };
5337
+ }
5338
+ function buildProjectVarMaps(variablesConfig, breakpoints, responsiveScales) {
5339
+ const out = { base: {} };
5340
+ for (const bpName of Object.keys(breakpoints)) out[bpName] = {};
5341
+ for (const variable of variablesConfig.variables) {
5342
+ if (!variable.cssVar) continue;
5343
+ out.base[variable.cssVar] = variable.value;
5344
+ for (const bpName of Object.keys(breakpoints)) {
5345
+ const resolved = resolveVariableValueAtBreakpoint(
5346
+ { value: variable.value, type: variable.type, scales: variable.scales },
5347
+ bpName,
5348
+ responsiveScales
5349
+ );
5350
+ out[bpName][variable.cssVar] = resolved;
4008
5351
  }
4009
5352
  }
4010
- return vars;
5353
+ return out;
4011
5354
  }
4012
- async function buildWebflowPayload(projectRoot) {
5355
+ async function buildWebflowPayload(options) {
5356
+ const bindCollectionLists = options?.bindCollectionLists === true;
5357
+ const promotedComponentNames = Array.isArray(options?.promotedComponentNames) ? new Set(options.promotedComponentNames.filter((n) => typeof n === "string" && n.length > 0)) : void 0;
4013
5358
  configService.reset();
4014
5359
  const projectConfig = await loadProjectConfig();
4015
5360
  const siteUrl = projectConfig.siteUrl?.replace(/\/$/, "") || "";
4016
5361
  const i18nConfig = await loadI18nConfig();
5362
+ const requestedLocale = options?.locale;
5363
+ const selectedLocale = requestedLocale && i18nConfig.locales.some((l) => l.code === requestedLocale) ? requestedLocale : i18nConfig.defaultLocale;
5364
+ const localesToBuild = i18nConfig.locales.filter((l) => l.code === selectedLocale);
4017
5365
  await migrateTemplatesDirectory();
4018
5366
  const { components } = await loadComponentDirectory(projectPaths.components());
4019
5367
  const globalComponents = {};
@@ -4026,8 +5374,8 @@ async function buildWebflowPayload(projectRoot) {
4026
5374
  const themeConfig = await colorService.loadThemeConfig();
4027
5375
  const variablesConfig = await variableService.loadConfig();
4028
5376
  const breakpoints = await loadBreakpointConfig();
4029
- const responsiveScales = await loadResponsiveScalesConfig();
4030
5377
  await configService.load();
5378
+ const responsiveScales = configService.getResponsiveScales();
4031
5379
  const pagesDir = projectPaths.pages();
4032
5380
  if (!existsSync2(pagesDir)) {
4033
5381
  return emptyPayload();
@@ -4040,7 +5388,7 @@ async function buildWebflowPayload(projectRoot) {
4040
5388
  for (const file of pageFiles) {
4041
5389
  const pageName = file.replace(".json", "");
4042
5390
  const basePath = mapPageNameToPath(pageName);
4043
- const pageContent = await loadJSONFile(join2(pagesDir, file));
5391
+ const pageContent = await loadJSONFile(join3(pagesDir, file));
4044
5392
  if (!pageContent) continue;
4045
5393
  try {
4046
5394
  const pageData = parseJSON(pageContent);
@@ -4053,16 +5401,22 @@ async function buildWebflowPayload(projectRoot) {
4053
5401
  }
4054
5402
  const allPages = [];
4055
5403
  const allStyleClasses = /* @__PURE__ */ new Map();
5404
+ const allInteractiveStylesMap = /* @__PURE__ */ new Map();
5405
+ const allComboIdentityByName = /* @__PURE__ */ new Map();
5406
+ const promotedComponents = /* @__PURE__ */ new Map();
5407
+ const variablesCSS = generateVariablesCSS(variablesConfig, breakpoints, responsiveScales);
5408
+ const projectVars = buildProjectVarMaps(variablesConfig, breakpoints, responsiveScales);
5409
+ const { byTheme: themeVars, defaultTheme } = buildThemeVarMaps(themeConfig);
4056
5410
  for (const file of pageFiles) {
4057
5411
  const pageName = file.replace(".json", "");
4058
5412
  const basePath = mapPageNameToPath(pageName);
4059
- const pageContent = await loadJSONFile(join2(pagesDir, file));
5413
+ const pageContent = await loadJSONFile(join3(pagesDir, file));
4060
5414
  if (!pageContent) continue;
4061
5415
  try {
4062
5416
  const pageData = parseJSON(pageContent);
4063
5417
  if (pageData.meta?.draft === true) continue;
4064
5418
  const slugs = pageData.meta?.slugs;
4065
- for (const localeConfig of i18nConfig.locales) {
5419
+ for (const localeConfig of localesToBuild) {
4066
5420
  const locale = localeConfig.code;
4067
5421
  const isDefault = locale === i18nConfig.defaultLocale;
4068
5422
  let slug;
@@ -4092,16 +5446,32 @@ async function buildWebflowPayload(projectRoot) {
4092
5446
  fileType: "page",
4093
5447
  fileName: pageName,
4094
5448
  breakpoints,
4095
- styleClasses: allStyleClasses
5449
+ styleClasses: allStyleClasses,
5450
+ comboIdentityByName: allComboIdentityByName,
5451
+ interactiveStylesMap: allInteractiveStylesMap,
5452
+ cmsService,
5453
+ i18nConfig,
5454
+ locale,
5455
+ slugMappings,
5456
+ pagePath: urlPath,
5457
+ themeVars,
5458
+ projectVars,
5459
+ defaultTheme,
5460
+ responsiveScales,
5461
+ promotedComponents,
5462
+ promotedComponentNames,
5463
+ bindCollectionLists
4096
5464
  };
4097
5465
  const body = pageData.root || pageData.node;
4098
- const elements = body ? nodeToWebflow(body, ctx) : [];
5466
+ const elements = body ? await nodeToWebflow(body, ctx) : [];
5467
+ normalizeListChildren(elements);
5468
+ const pageMeta = extractPageMeta(pageData);
4099
5469
  allPages.push({
4100
5470
  title: result.title,
4101
5471
  slug: slug || "index",
4102
- metaDescription: typeof pageData.meta?.description === "string" ? pageData.meta.description : void 0,
4103
5472
  elements,
4104
- locale
5473
+ locale,
5474
+ ...buildPageMetaForLocale(pageMeta, locale, i18nConfig)
4105
5475
  });
4106
5476
  }
4107
5477
  } catch (error) {
@@ -4109,11 +5479,10 @@ async function buildWebflowPayload(projectRoot) {
4109
5479
  }
4110
5480
  }
4111
5481
  const templatesDir = projectPaths.templates();
4112
- const cmsCollections = [];
4113
5482
  if (existsSync2(templatesDir)) {
4114
5483
  const templateFiles = readdirSync2(templatesDir).filter((f) => f.endsWith(".json"));
4115
5484
  for (const file of templateFiles) {
4116
- const templateContent = await loadJSONFile(join2(templatesDir, file));
5485
+ const templateContent = await loadJSONFile(join3(templatesDir, file));
4117
5486
  if (!templateContent) continue;
4118
5487
  try {
4119
5488
  const pageData = parseJSON(templateContent);
@@ -4121,92 +5490,137 @@ async function buildWebflowPayload(projectRoot) {
4121
5490
  if (!isCMSPage2(pageData)) continue;
4122
5491
  const cmsSchema = pageData.meta.cms;
4123
5492
  const items = await cmsService.queryItems({ collection: cmsSchema.id });
4124
- const fields = [];
4125
- if (cmsSchema.fields) {
4126
- for (const [fieldName, fieldDef] of Object.entries(cmsSchema.fields)) {
4127
- fields.push({
4128
- name: fieldDef.label || fieldName,
4129
- slug: fieldName,
4130
- type: mapCMSFieldType(fieldDef.type),
4131
- required: fieldDef.required,
4132
- options: fieldDef.options
4133
- });
4134
- }
4135
- }
4136
- const resolvedItems = [];
4137
- for (const item of items) {
4138
- const resolved = {};
4139
- for (const [key, value] of Object.entries(item)) {
4140
- if (isI18nValue(value)) {
4141
- resolved[key] = resolveI18nValue(value, i18nConfig.defaultLocale, i18nConfig);
4142
- } else {
4143
- resolved[key] = value;
4144
- }
4145
- }
4146
- resolvedItems.push(resolved);
4147
- }
4148
- cmsCollections.push({
4149
- name: cmsSchema.id,
4150
- slug: cmsSchema.id,
4151
- urlPattern: cmsSchema.urlPattern,
4152
- fields,
4153
- items: resolvedItems
4154
- });
4155
- for (const item of items) {
4156
- for (const localeConfig of i18nConfig.locales) {
4157
- const locale = localeConfig.code;
4158
- if (isItemDraftForLocale(item, locale)) continue;
4159
- const itemPath = buildCMSItemPath(cmsSchema.urlPattern, item, cmsSchema.slugField, locale, i18nConfig);
4160
- const itemWithUrl = { ...item, _url: itemPath };
4161
- const result = await renderPageSSR(
4162
- pageData,
4163
- globalComponents,
4164
- itemPath,
4165
- siteUrl,
4166
- locale,
4167
- i18nConfig,
4168
- slugMappings,
4169
- { cms: itemWithUrl },
4170
- cmsService,
4171
- true
4172
- );
4173
- const ctx = {
4174
- globalComponents,
4175
- elementPath: [0],
4176
- fileType: "page",
4177
- fileName: file.replace(".json", ""),
4178
- breakpoints,
4179
- styleClasses: allStyleClasses
4180
- };
4181
- const body = pageData.root || pageData.node;
4182
- const cmsProps = { cms: itemWithUrl };
4183
- const elements = body ? nodeToWebflow(body, ctx, cmsProps) : [];
4184
- const slug = itemPath.startsWith("/") ? itemPath.substring(1) : itemPath;
4185
- allPages.push({
4186
- title: result.title,
4187
- slug,
4188
- elements,
4189
- locale
4190
- });
5493
+ if (items.length === 0) continue;
5494
+ const item = items[0];
5495
+ const pageName = file.replace(".json", "");
5496
+ for (const localeConfig of localesToBuild) {
5497
+ const locale = localeConfig.code;
5498
+ let itemSlug = item[cmsSchema.slugField] ?? item._slug ?? item._id;
5499
+ if (isI18nValue(itemSlug)) {
5500
+ itemSlug = resolveI18nValue(itemSlug, locale, i18nConfig);
4191
5501
  }
5502
+ const resolvedItemPath = cmsSchema.urlPattern.replace("{{slug}}", String(itemSlug));
5503
+ const isDefault = locale === i18nConfig.defaultLocale;
5504
+ const localizedItemPath = isDefault ? resolvedItemPath : `/${locale}${resolvedItemPath.startsWith("/") ? "" : "/"}${resolvedItemPath}`;
5505
+ const slug = localizedItemPath.startsWith("/") ? localizedItemPath.substring(1) : localizedItemPath;
5506
+ const urlPath = localizedItemPath;
5507
+ const flatItem = flattenCMSItemForLocale(item, locale, i18nConfig);
5508
+ const itemWithUrl = { ...flatItem, _url: urlPath };
5509
+ const ssrItem = { ...item, _url: urlPath };
5510
+ const result = await renderPageSSR(
5511
+ pageData,
5512
+ globalComponents,
5513
+ urlPath,
5514
+ siteUrl,
5515
+ locale,
5516
+ i18nConfig,
5517
+ slugMappings,
5518
+ { cms: ssrItem },
5519
+ cmsService,
5520
+ true
5521
+ );
5522
+ const ctx = {
5523
+ globalComponents,
5524
+ elementPath: [0],
5525
+ fileType: "page",
5526
+ fileName: pageName,
5527
+ breakpoints,
5528
+ styleClasses: allStyleClasses,
5529
+ comboIdentityByName: allComboIdentityByName,
5530
+ interactiveStylesMap: allInteractiveStylesMap,
5531
+ cmsService,
5532
+ i18nConfig,
5533
+ locale,
5534
+ slugMappings,
5535
+ pagePath: urlPath,
5536
+ cmsContext: { cms: itemWithUrl },
5537
+ // Seed `templateContext` with the bound item so descendants inside
5538
+ // a component slot still see `cms`. `emitInlineComponentBody`
5539
+ // merges `ctx.templateContext` into the body's `instanceProps`
5540
+ // (nodeToWebflow.ts:1140-1143), and `<slot>` rendering forwards
5541
+ // those merged props to slot children — without this, anything
5542
+ // wrapped in (e.g.) Section loses the page-level CMS scope and
5543
+ // `{{cms.title}}` resolves to empty.
5544
+ templateContext: { cms: itemWithUrl },
5545
+ themeVars,
5546
+ projectVars,
5547
+ defaultTheme,
5548
+ responsiveScales,
5549
+ promotedComponents,
5550
+ promotedComponentNames,
5551
+ bindCollectionLists
5552
+ };
5553
+ const body = pageData.root || pageData.node;
5554
+ const elements = body ? await nodeToWebflow(body, ctx, { cms: itemWithUrl }) : [];
5555
+ normalizeListChildren(elements);
5556
+ const cmsPageMeta = extractPageMeta(pageData);
5557
+ allPages.push({
5558
+ title: result.title,
5559
+ slug,
5560
+ elements,
5561
+ locale,
5562
+ ...buildPageMetaForLocale(cmsPageMeta, locale, i18nConfig)
5563
+ });
4192
5564
  }
4193
5565
  } catch (error) {
4194
5566
  console.error(`Error processing template ${file}:`, error?.message);
4195
5567
  }
4196
5568
  }
4197
5569
  }
4198
- const themeColorCSS = generateThemeColorVariablesCSS(themeConfig);
4199
- const variablesCSS = generateVariablesCSS(variablesConfig, breakpoints, responsiveScales);
4200
- const cssVariables = extractCSSVariables(themeColorCSS, variablesCSS);
5570
+ for (const def of promotedComponents.values()) {
5571
+ normalizeListChildren(def.elements);
5572
+ }
5573
+ const styleClasses = Array.from(allStyleClasses.values());
4201
5574
  const assets = scanAssets(projectPaths.project);
5575
+ const scripts = [];
5576
+ const cssBlocks = [];
5577
+ for (const [name, def] of Object.entries(globalComponents)) {
5578
+ const code = def.component?.javascript;
5579
+ if (typeof code === "string" && code.trim().length > 0) {
5580
+ scripts.push({
5581
+ componentName: name,
5582
+ code,
5583
+ defineVars: def.component?.defineVars
5584
+ });
5585
+ }
5586
+ const css = def.component?.css;
5587
+ if (typeof css === "string" && css.trim().length > 0) {
5588
+ cssBlocks.push(`/* ${name}.css */
5589
+ ${css.trim()}`);
5590
+ }
5591
+ }
5592
+ const componentCss = cssBlocks.length > 0 ? cssBlocks.join("\n\n") : void 0;
5593
+ const interactiveCssBlocks = [];
5594
+ for (const [elementClass, rules] of allInteractiveStylesMap) {
5595
+ const customRules = rules.filter((r) => !isWebflowHandledRule(r));
5596
+ if (customRules.length === 0) continue;
5597
+ const css = generateInteractiveCSS(elementClass, customRules, breakpoints, void 0, responsiveScales);
5598
+ if (css && css.trim().length > 0) {
5599
+ interactiveCssBlocks.push(css);
5600
+ }
5601
+ }
5602
+ const interactiveCss = interactiveCssBlocks.length > 0 ? interactiveCssBlocks.join("\n\n") : void 0;
4202
5603
  return {
4203
5604
  version: 1,
4204
5605
  exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
4205
5606
  pages: allPages,
4206
- styles: Array.from(allStyleClasses.values()),
4207
- cms: cmsCollections,
5607
+ styles: styleClasses,
5608
+ cms: [],
4208
5609
  assets,
4209
- cssVariables: Object.keys(cssVariables).length > 0 ? cssVariables : void 0
5610
+ slugMappings: slugMappings.length > 0 ? slugMappings : void 0,
5611
+ scripts: scripts.length > 0 ? scripts : void 0,
5612
+ components: promotedComponents.size > 0 ? Array.from(promotedComponents.values()) : void 0,
5613
+ componentCss,
5614
+ interactiveCss,
5615
+ i18n: {
5616
+ defaultLocale: i18nConfig.defaultLocale,
5617
+ locales: i18nConfig.locales.map((l) => ({
5618
+ code: l.code,
5619
+ name: l.name,
5620
+ nativeName: l.nativeName
5621
+ })),
5622
+ selectedLocale
5623
+ }
4210
5624
  };
4211
5625
  }
4212
5626
  function emptyPayload() {
@@ -4220,6 +5634,32 @@ function emptyPayload() {
4220
5634
  };
4221
5635
  }
4222
5636
 
5637
+ // lib/server/webflow/templateWrapper.ts
5638
+ import { readFile as readFile3 } from "fs/promises";
5639
+ var cachedTemplate = null;
5640
+ async function getWebflowTemplate(appName) {
5641
+ if (cachedTemplate) return cachedTemplate;
5642
+ const url = `https://webflow-ext.com/template/v2?name=${encodeURIComponent(appName)}`;
5643
+ const res = await fetch(url);
5644
+ if (!res.ok) throw new Error(`Failed to fetch Webflow template: ${res.status}`);
5645
+ cachedTemplate = await res.text();
5646
+ return cachedTemplate;
5647
+ }
5648
+ async function wrapInWebflowTemplate(html, manifestPath) {
5649
+ try {
5650
+ const manifest = JSON.parse(await readFile3(manifestPath, "utf-8"));
5651
+ const template = await getWebflowTemplate(manifest.name || "Meno Import");
5652
+ const headMatch = html.match(/<head[^>]*>([\s\S]*?)<\/head>/i);
5653
+ const bodyMatch = html.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
5654
+ const headContent = headMatch ? headMatch[1] : "";
5655
+ const bodyContent = bodyMatch ? bodyMatch[1] : "";
5656
+ return template.replace("{{ui}}", headContent + bodyContent);
5657
+ } catch (err) {
5658
+ console.warn("Could not fetch Webflow wrapper template:", err.message);
5659
+ return html;
5660
+ }
5661
+ }
5662
+
4223
5663
  // lib/server/index.ts
4224
5664
  init_constants();
4225
5665
  export {
@@ -4315,6 +5755,7 @@ export {
4315
5755
  variableService,
4316
5756
  withErrorHandling,
4317
5757
  withLogging,
5758
+ wrapInWebflowTemplate,
4318
5759
  writeFile
4319
5760
  };
4320
5761
  //# sourceMappingURL=index.js.map