@bettercms-ai/convert 0.20.1 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -955,7 +955,7 @@ declare function attrsFor(path: string, kind: string, props?: AttrBinding[], sco
955
955
  declare function propsAttribute(props: AttrBinding[]): string;
956
956
 
957
957
  /** Bumped whenever the emitted source changes. `isKnownHelper` upgrades anything older. */
958
- declare const HELPER_VERSION = 9;
958
+ declare const HELPER_VERSION = 10;
959
959
  /** Where each dialect's helper lives. Null = this dialect reads nothing at build time. */
960
960
  declare const HELPER_PATH: Record<Dialect, string | null>;
961
961
  /** The directory the snapshots live in, at the repository root. */
package/dist/index.js CHANGED
@@ -4187,7 +4187,7 @@ async function bindImages(ctx, targets) {
4187
4187
 
4188
4188
  // src/helper.ts
4189
4189
  import { createHash as createHash2 } from "crypto";
4190
- var HELPER_VERSION = 9;
4190
+ var HELPER_VERSION = 10;
4191
4191
  var HEADER = "// @bettercms-ai/convert helper v";
4192
4192
  var HELPER_PATH = Object.fromEntries(
4193
4193
  Object.keys(DIALECT_RULES).map((d) => [d, DIALECT_RULES[d].helper])
@@ -4511,6 +4511,85 @@ export function bcmsFields(
4511
4511
  return (own ? undefined : moved(key, rest.slice(key?.length ?? 0))) ?? lane + rest;
4512
4512
  };
4513
4513
  }
4514
+
4515
+ /**
4516
+ * C3/C4 \u2014 a section's \`block.style\` as the inline CSS the BetterCMS SDK draws for it.
4517
+ *
4518
+ * A converted section root carries \`data-bcms-style\` beside \`data-bcms-block\`, which is the
4519
+ * site's promise to the editor that "this root's inline style is \`blockStyleToCss(block.style)\`"
4520
+ * \u2014 and the editor only offers a section's spacing handles where every stamped root makes it.
4521
+ * This is \`blockStyleToCss\` from \`@bettercms-ai/types\`, INLINED for the same reason \`readPath\`
4522
+ * is (zero dependencies); \`helper.test.ts\` runs it against the original over a token table.
4523
+ */
4524
+ const STYLE_SPACE_PX: Record<string, string> = { none: "0", xs: "4px", sm: "8px", md: "16px", lg: "32px", xl: "64px" };
4525
+ const STYLE_FONT_SIZE_REM: Record<string, string> = {
4526
+ sm: "0.875rem",
4527
+ base: "1rem",
4528
+ lg: "1.25rem",
4529
+ xl: "1.5rem",
4530
+ "2xl": "2rem",
4531
+ "3xl": "2.25rem",
4532
+ "4xl": "3rem",
4533
+ "5xl": "4rem",
4534
+ };
4535
+ const STYLE_WEIGHT: Record<string, number> = { normal: 400, medium: 500, semibold: 600, bold: 700 };
4536
+ const STYLE_RADIUS_PX: Record<string, string> = { none: "0", sm: "4px", md: "8px", lg: "16px", full: "9999px" };
4537
+ const STYLE_COLOR_HEX: Record<string, string> = { white: "#ffffff", black: "#111111", muted: "#6b7280", mutedBg: "#f3f4f6", primary: "#111111", accent: "#4f46e5" };
4538
+ const STYLE_SPACE_TOKEN_PX: Record<string, string> = {
4539
+ "section-space-none": "0px",
4540
+ "section-space-small": "32px",
4541
+ "section-space-medium": "64px",
4542
+ "section-space-large": "96px",
4543
+ "section-space-xlarge": "144px",
4544
+ "section-space-nav-overlap": "-72px",
4545
+ "space-none": "0px",
4546
+ "space-s": "8px",
4547
+ "space-m": "16px",
4548
+ "space-l": "32px",
4549
+ "space-xl": "64px",
4550
+ };
4551
+
4552
+ function styleSpaceToken(token: unknown): string {
4553
+ const px = typeof token === "string" ? STYLE_SPACE_TOKEN_PX[token] : undefined;
4554
+ return px ? "var(--bcms-" + token + "," + px + ")" : "";
4555
+ }
4556
+
4557
+ /** The declarations as a React-style object (camelCase), or undefined when there are none. */
4558
+ export function bcmsSectionStyle(style: unknown): Record<string, string | number> | undefined {
4559
+ if (!style || typeof style !== "object") return undefined;
4560
+ const s = style as Record<string, string>;
4561
+ const css: Record<string, string | number> = {};
4562
+ if (s.paddingY && STYLE_SPACE_PX[s.paddingY] != null) { css.paddingTop = STYLE_SPACE_PX[s.paddingY]!; css.paddingBottom = STYLE_SPACE_PX[s.paddingY]!; }
4563
+ if (s.paddingX && STYLE_SPACE_PX[s.paddingX] != null) { css.paddingLeft = STYLE_SPACE_PX[s.paddingX]!; css.paddingRight = STYLE_SPACE_PX[s.paddingX]!; }
4564
+ const top = styleSpaceToken(s.spaceTop);
4565
+ // A negative band that pulls content under the nav makes sense at the TOP only (SPACE_BOTTOM_TOKENS).
4566
+ const bottom = /^section-space-(none|small|medium|large|xlarge)$/.test(String(s.spaceBottom)) ? styleSpaceToken(s.spaceBottom) : "";
4567
+ const sides = styleSpaceToken(s.spaceSides);
4568
+ const navOverlap = s.spaceTop === "section-space-nav-overlap";
4569
+ if (top && !navOverlap) css.paddingTop = top;
4570
+ if (bottom) css.paddingBottom = bottom;
4571
+ if (sides) { css.paddingLeft = sides; css.paddingRight = sides; }
4572
+ if (s.marginTop && STYLE_SPACE_PX[s.marginTop] != null) css.marginTop = STYLE_SPACE_PX[s.marginTop]!;
4573
+ if (top && navOverlap) { css.marginTop = top; css.paddingTop = "calc(-1 * " + top + ")"; }
4574
+ if (s.marginBottom && STYLE_SPACE_PX[s.marginBottom] != null) css.marginBottom = STYLE_SPACE_PX[s.marginBottom]!;
4575
+ if (s.background && STYLE_COLOR_HEX[s.background]) css.background = STYLE_COLOR_HEX[s.background]!;
4576
+ if (s.textColor && STYLE_COLOR_HEX[s.textColor]) css.color = STYLE_COLOR_HEX[s.textColor]!;
4577
+ if (s.align === "left" || s.align === "center" || s.align === "right") css.textAlign = s.align;
4578
+ if (s.fontSize && STYLE_FONT_SIZE_REM[s.fontSize]) css.fontSize = STYLE_FONT_SIZE_REM[s.fontSize]!;
4579
+ if (s.weight && STYLE_WEIGHT[s.weight] != null) css.fontWeight = STYLE_WEIGHT[s.weight]!;
4580
+ if (s.radius && STYLE_RADIUS_PX[s.radius] != null) css.borderRadius = STYLE_RADIUS_PX[s.radius]!;
4581
+ if ((style as { border?: unknown }).border === true) css.border = "1px solid currentColor";
4582
+ return Object.keys(css).length ? css : undefined;
4583
+ }
4584
+
4585
+ /** The same declarations as an Astro \`style\` string (kebab-case, \`;\`-joined), or undefined. */
4586
+ export function bcmsSectionStyleText(style: unknown): string | undefined {
4587
+ const css = bcmsSectionStyle(style);
4588
+ if (!css) return undefined;
4589
+ return Object.entries(css)
4590
+ .map(([k, v]) => k.replace(/[A-Z]/g, (m) => "-" + m.toLowerCase()) + ":" + v)
4591
+ .join(";");
4592
+ }
4514
4593
  `;
4515
4594
  }
4516
4595
  var HISTORICAL = {
@@ -4522,7 +4601,8 @@ var HISTORICAL = {
4522
4601
  5: "0472fe3ffdcfa49e719c7b9da9cb63b6eba637cc22783bb00c3c2cadd3d800da",
4523
4602
  6: "c907e7245578d947c6541b1ab2c5c6659ea7789a1519e895b4a4aed122c6f649",
4524
4603
  7: "f22514ef48c50230985e605bea6cf9bf41756384a7dc80603e347baba9b65d93",
4525
- 8: "484e72852d2b496e016a1f2a2b1a77bb993f8a163b290781522d6114b6519bb9"
4604
+ 8: "484e72852d2b496e016a1f2a2b1a77bb993f8a163b290781522d6114b6519bb9",
4605
+ 9: "d6441db2ff4578c3c5d4b7d461f3374b89247ef6ba9e0d535791e840d803e4f3"
4526
4606
  },
4527
4607
  "src/lib/bcms-content.ts": {
4528
4608
  1: "ae72ae14d706943d481c880d70da5789b4816ba1ba319a10fbc63ded4d0b42f2",
@@ -4532,7 +4612,8 @@ var HISTORICAL = {
4532
4612
  5: "c8c93655320fa65664611ba84541f3fa7bfa292cc9a2fd655373440b99049fba",
4533
4613
  6: "63f1fa29e47e70c5193b81ee24addb238ba100247124d55bc17713102d587df5",
4534
4614
  7: "ed92fb88de953d9951abbe6a1edae1d048f4050eaf6fb607ac08167f94d8bcc7",
4535
- 8: "1a6f669d76d42effe77f082436e9d21c0efa6fc499e919e3d116bc3a27a7f3d5"
4615
+ 8: "1a6f669d76d42effe77f082436e9d21c0efa6fc499e919e3d116bc3a27a7f3d5",
4616
+ 9: "eee9fb1635ae7d6f8f33676a1f390600ca24a62ee992775252273c73a011c494"
4536
4617
  },
4537
4618
  "composables/bcms-content.ts": {
4538
4619
  1: "c272412532efed7db64b6cf313b2bed4291f9951430412289c8a588b7f565407",
@@ -4542,7 +4623,8 @@ var HISTORICAL = {
4542
4623
  5: "0472fe3ffdcfa49e719c7b9da9cb63b6eba637cc22783bb00c3c2cadd3d800da",
4543
4624
  6: "c907e7245578d947c6541b1ab2c5c6659ea7789a1519e895b4a4aed122c6f649",
4544
4625
  7: "f22514ef48c50230985e605bea6cf9bf41756384a7dc80603e347baba9b65d93",
4545
- 8: "484e72852d2b496e016a1f2a2b1a77bb993f8a163b290781522d6114b6519bb9"
4626
+ 8: "484e72852d2b496e016a1f2a2b1a77bb993f8a163b290781522d6114b6519bb9",
4627
+ 9: "d6441db2ff4578c3c5d4b7d461f3374b89247ef6ba9e0d535791e840d803e4f3"
4546
4628
  }
4547
4629
  };
4548
4630
  var sha256 = (value) => createHash2("sha256").update(value, "utf8").digest("hex");
@@ -8320,7 +8402,9 @@ function componentFile(dialect, path, fromFile, content, parsed, resolved, pathP
8320
8402
  ]);
8321
8403
  const fieldsName = introduced.get("bcmsFields");
8322
8404
  const fieldName = introduced.get("bcmsField");
8323
- const { markup, bound, duplicates } = sectionMarkup(content, resolved.root, fieldName);
8405
+ const styleReader = dialect === "astro" ? "bcmsSectionStyleText" : "bcmsSectionStyle";
8406
+ const styled = rootStyleIsFree(content, resolved.root) && !reserved.has("blockStyle") && !reserved.has(styleReader);
8407
+ const { markup, bound, duplicates } = sectionMarkup(content, resolved.root, fieldName, styled ? styleReader : null);
8324
8408
  if (duplicates.length > 0) return { content: "", free: [], duplicates, unconvertible: [] };
8325
8409
  const groupKey = resolved.section.groupKey;
8326
8410
  const locals = helperLocals(parsed, dialect, fromFile);
@@ -8331,7 +8415,8 @@ function componentFile(dialect, path, fromFile, content, parsed, resolved, pathP
8331
8415
  .../* @__PURE__ */ new Set([
8332
8416
  ...helperNames.map((h) => alias(h.name, h.local)),
8333
8417
  flat3 ? "bcmsSectionPaths" : "bcmsSection",
8334
- ...bound > 0 ? [alias("bcmsFields", fieldsName)] : []
8418
+ ...bound > 0 ? [alias("bcmsFields", fieldsName)] : [],
8419
+ ...styled ? [styleReader] : []
8335
8420
  ])
8336
8421
  ].sort();
8337
8422
  const helperSpecifier = relativeImport(path, HELPER_PATH[dialect]).replace(/\.tsx?$/, "");
@@ -8380,9 +8465,11 @@ interface Props {
8380
8465
  /** The instance's own copy, when an editor gave it any. Wins over the bound group. */
8381
8466
  overrides?: Record<string, unknown>;
8382
8467
  /** The page snapshot, so one component can serve every page whose group has this shape. */
8383
- page?: unknown;
8468
+ page?: unknown;${styled ? `
8469
+ /** The placement's \`block.style\` \u2014 painted on the root, which says so with \`data-bcms-style\`. */
8470
+ blockStyle?: Record<string, unknown>;` : ""}
8384
8471
  }
8385
- const { blockId, bind, bindPaths, overrides, page } = Astro.props;
8472
+ const { blockId, bind, bindPaths, overrides, page${styled ? ", blockStyle" : ""} } = Astro.props;
8386
8473
  ${carriedDeclarations}${declared}
8387
8474
  ---
8388
8475
  ${markup}
@@ -8394,7 +8481,7 @@ ${styles}
8394
8481
  ${[...carried, `import { ${helpers.join(", ")} } from "${helperSpecifier}";`, `import type { BcmsSectionProps } from "${relativeImport(path, SECTIONS_LIB).replace(/\.tsx?$/, "")}";`].join("\n")}
8395
8482
  ${carriedDeclarations === "" ? "" : `
8396
8483
  ${carriedDeclarations}`}
8397
- export default function ${resolved.name}({ blockId, bind, bindPaths, overrides, page }: BcmsSectionProps) {
8484
+ export default function ${resolved.name}({ blockId, bind, bindPaths, overrides, page${styled ? ", blockStyle" : ""} }: BcmsSectionProps) {
8398
8485
  ${declared.split("\n").join("\n ")}
8399
8486
  return (
8400
8487
  ${markup}
@@ -8402,13 +8489,21 @@ export default function ${resolved.name}({ blockId, bind, bindPaths, overrides,
8402
8489
  }
8403
8490
  ` };
8404
8491
  }
8405
- function sectionMarkup(content, root, fieldName) {
8492
+ function rootStyleIsFree(content, root) {
8493
+ return root.attrs.every((attr) => {
8494
+ if (attr.name === "style") return false;
8495
+ const open = attr.expressionRange ? content.slice(attr.expressionRange.start, attr.expressionRange.end).trimStart() : "";
8496
+ return !open.startsWith("{...");
8497
+ });
8498
+ }
8499
+ function sectionMarkup(content, root, fieldName, styleReader = null) {
8406
8500
  const slice = new MagicString5(content.slice(root.start, root.end));
8407
8501
  const at = (offset) => offset - root.start;
8502
+ const stamp = styleReader ? `data-bcms-block={blockId} data-bcms-style="" style={${styleReader}(blockStyle)}` : "data-bcms-block={blockId}";
8408
8503
  const stamps = root.attrs.filter((attr) => attr.name === "data-bcms-block");
8409
- if (stamps.length === 0) slice.appendLeft(at(root.attrInsertAt), " data-bcms-block={blockId}");
8504
+ if (stamps.length === 0) slice.appendLeft(at(root.attrInsertAt), ` ${stamp}`);
8410
8505
  else {
8411
- slice.overwrite(at(stamps[0].range.start), at(stamps[0].range.end), "data-bcms-block={blockId}");
8506
+ slice.overwrite(at(stamps[0].range.start), at(stamps[0].range.end), stamp);
8412
8507
  for (const extra of stamps.slice(1)) {
8413
8508
  let from = extra.range.start;
8414
8509
  while (from > root.start && /\s/.test(content[from - 1])) from--;
@@ -8759,6 +8854,8 @@ export interface BcmsSectionProps {
8759
8854
  bindPaths?: Record<string, string>;
8760
8855
  overrides?: Record<string, unknown>;
8761
8856
  page?: unknown;
8857
+ /** The placement's \`block.style\`, which the section root paints (\`data-bcms-style\`). */
8858
+ blockStyle?: Record<string, unknown>;
8762
8859
  }
8763
8860
 
8764
8861
  /** One placement on one page: which component, bound to which page field group. */
@@ -8769,6 +8866,8 @@ export interface BcmsPlacement {
8769
8866
  /** A locale copy's own field path for each prop, when its keys differ from the component's. */
8770
8867
  bindPaths?: Record<string, string>;
8771
8868
  overrides?: Record<string, unknown>;
8869
+ /** The placement's \`block.style\` \u2014 what the editor's section spacing and style controls write. */
8870
+ style?: Record<string, unknown>;
8772
8871
  }
8773
8872
 
8774
8873
  /** The page's own snapshot \u2014 the copy every placement on it reads through \`bind\`. */
@@ -8830,6 +8929,7 @@ export function pageSections(pageSlug: string): BcmsPlacement[] {
8830
8929
  bind: typeof props.bind === "string" ? props.bind : undefined,
8831
8930
  bindPaths: isRecord(props.bindPaths) ? (props.bindPaths as Record<string, string>) : undefined,
8832
8931
  overrides: isRecord(props.overrides) ? props.overrides : undefined,
8932
+ style: isRecord(block.style) ? block.style : undefined,
8833
8933
  });
8834
8934
  }
8835
8935
  return out;
@@ -8896,6 +8996,7 @@ const snapshot = pageSnapshot(page);
8896
8996
  bind={placement.bind}
8897
8997
  bindPaths={placement.bindPaths}
8898
8998
  overrides={placement.overrides}
8999
+ blockStyle={placement.style}
8899
9000
  page={snapshot}
8900
9001
  />
8901
9002
  ) : null;
@@ -8919,6 +9020,7 @@ export default function Sections({ page }: { page: string }) {
8919
9020
  bind={placement.bind}
8920
9021
  bindPaths={placement.bindPaths}
8921
9022
  overrides={placement.overrides}
9023
+ blockStyle={placement.style}
8922
9024
  page={snapshot}
8923
9025
  />
8924
9026
  ) : null;