@ohhwells/bridge 0.1.79 → 0.1.80

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.cts CHANGED
@@ -139,6 +139,8 @@ declare function isRenderableTree(value: unknown): value is AiLayoutTree;
139
139
  interface AiTreeRendererProps {
140
140
  tree: AiLayoutTree;
141
141
  brand?: AiBrandKit;
142
+ /** The host template's button corner radius (e.g. "0px", "8px"); a form's submit adopts it. */
143
+ buttonRadius?: string | null;
142
144
  /** Resolves a media ref to a URL; null renders a placeholder. */
143
145
  resolveMedia?: (ref: string) => string | null;
144
146
  /**
@@ -148,7 +150,7 @@ interface AiTreeRendererProps {
148
150
  */
149
151
  editKeyPrefix?: string;
150
152
  }
151
- declare function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }: AiTreeRendererProps): react_jsx_runtime.JSX.Element | null;
153
+ declare function AiTreeRenderer({ tree, brand, buttonRadius, resolveMedia, editKeyPrefix, }: AiTreeRendererProps): react_jsx_runtime.JSX.Element | null;
152
154
 
153
155
  type LinkPage = {
154
156
  path: string;
package/dist/index.d.ts CHANGED
@@ -139,6 +139,8 @@ declare function isRenderableTree(value: unknown): value is AiLayoutTree;
139
139
  interface AiTreeRendererProps {
140
140
  tree: AiLayoutTree;
141
141
  brand?: AiBrandKit;
142
+ /** The host template's button corner radius (e.g. "0px", "8px"); a form's submit adopts it. */
143
+ buttonRadius?: string | null;
142
144
  /** Resolves a media ref to a URL; null renders a placeholder. */
143
145
  resolveMedia?: (ref: string) => string | null;
144
146
  /**
@@ -148,7 +150,7 @@ interface AiTreeRendererProps {
148
150
  */
149
151
  editKeyPrefix?: string;
150
152
  }
151
- declare function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }: AiTreeRendererProps): react_jsx_runtime.JSX.Element | null;
153
+ declare function AiTreeRenderer({ tree, brand, buttonRadius, resolveMedia, editKeyPrefix, }: AiTreeRendererProps): react_jsx_runtime.JSX.Element | null;
152
154
 
153
155
  type LinkPage = {
154
156
  path: string;
package/dist/index.js CHANGED
@@ -469,8 +469,6 @@ var AI_MOBILE_CSS = [
469
469
  // Group containers flatten to a column on phones; span placements come along for free.
470
470
  "[data-ai-group]{display:flex !important;flex-direction:column !important}",
471
471
  "[data-ai-group] > *{grid-column:auto !important}",
472
- // The 50:50 form collapses to a single stacked column on phones.
473
- "[data-ai-form]{grid-template-columns:1fr !important}",
474
472
  "[data-ai-section] img{max-width:100%}",
475
473
  "}",
476
474
  "@media (min-width: 769px) and (max-width: 1024px){",
@@ -1567,9 +1565,9 @@ function renderNode(node, ctx, path) {
1567
1565
  const fieldStyle = {
1568
1566
  width: "100%",
1569
1567
  boxSizing: "border-box",
1570
- border: `1px solid ${ctx.brand.palette.accent}`,
1571
- borderRadius: AI_TREE_TOKENS.radiusButton,
1572
- padding: "12px 14px",
1568
+ border: `1px solid color-mix(in srgb, ${ctx.brand.palette.dark} 45%, #ffffff)`,
1569
+ borderRadius: 0,
1570
+ padding: 12,
1573
1571
  background: "#fff",
1574
1572
  color: ctx.brand.palette.dark,
1575
1573
  outline: "none",
@@ -1577,81 +1575,90 @@ function renderNode(node, ctx, path) {
1577
1575
  };
1578
1576
  const labelStyle = {
1579
1577
  ...typeStyle(AI_TREE_TOKENS.type.bodyMBold, ctx.brand.fonts.body),
1580
- color: ctx.brand.palette.dark
1578
+ color: ctx.brand.palette.dark,
1579
+ textAlign: "left",
1580
+ width: "100%"
1581
1581
  };
1582
- return /* @__PURE__ */ jsx(
1583
- "form",
1584
- {
1585
- ...formAttrs,
1586
- "data-ai-form": "",
1587
- style: {
1588
- display: "grid",
1589
- gridTemplateColumns: "repeat(2, minmax(0, 1fr))",
1590
- columnGap: 64,
1591
- rowGap: AI_TREE_TOKENS.spacing4
1592
- },
1593
- children: (node.children ?? []).map((child, i) => {
1594
- if (child.type === "input") {
1595
- const cs2 = child.slots ?? {};
1596
- const kind = str(cs2.kind);
1597
- const label = str(cs2.label);
1598
- const placeholder = str(cs2.placeholder);
1599
- const name = label.toLowerCase().replace(/[^a-z0-9]+/gu, "-").replace(/^-+|-+$/gu, "") || `field-${i}`;
1600
- const isTextarea = kind === "textarea";
1601
- return /* @__PURE__ */ jsxs(
1602
- "div",
1603
- {
1604
- style: {
1605
- display: "flex",
1606
- flexDirection: "column",
1607
- gap: 8,
1608
- ...isTextarea ? { gridColumn: "1 / -1", maxWidth: 780 } : {}
1582
+ const centered = ctx.sectionAlignment === "center";
1583
+ const submitAlign = centered ? "center" : "flex-start";
1584
+ const children = node.children ?? [];
1585
+ return (
1586
+ // 32px between the field group and the submit. In a stacked (centered) section the form is
1587
+ // capped at 780px and centered — the section's 12-col grid would otherwise leave it hugging
1588
+ // the left edge; a split section lets it fill its own column.
1589
+ /* @__PURE__ */ jsxs(
1590
+ "form",
1591
+ {
1592
+ ...formAttrs,
1593
+ "data-ai-form": "",
1594
+ style: {
1595
+ display: "flex",
1596
+ flexDirection: "column",
1597
+ gap: 32,
1598
+ width: "100%",
1599
+ ...centered ? { maxWidth: 780, marginLeft: "auto", marginRight: "auto" } : {}
1600
+ },
1601
+ children: [
1602
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", flexDirection: "column", gap: 24, width: "100%", alignItems: "flex-start" }, children: children.map((child, i) => {
1603
+ if (child.type !== "input") return null;
1604
+ const cs = child.slots ?? {};
1605
+ const kind = str(cs.kind);
1606
+ const label = str(cs.label);
1607
+ const placeholder = str(cs.placeholder);
1608
+ const required = cs.required === true;
1609
+ const name = label.toLowerCase().replace(/[^a-z0-9]+/gu, "-").replace(/^-+|-+$/gu, "") || `field-${i}`;
1610
+ const isTextarea = kind === "textarea";
1611
+ return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 8, width: "100%" }, children: [
1612
+ /* @__PURE__ */ jsx("label", { ...textAttrs(ctx, `${path}.c${i}.label`), style: labelStyle, children: label }),
1613
+ isTextarea ? /* @__PURE__ */ jsx(
1614
+ "textarea",
1615
+ {
1616
+ name,
1617
+ placeholder,
1618
+ required,
1619
+ style: { ...fieldStyle, height: 180, resize: "vertical" }
1620
+ }
1621
+ ) : /* @__PURE__ */ jsx(
1622
+ "input",
1623
+ {
1624
+ name,
1625
+ type: kind === "email" ? "email" : "text",
1626
+ placeholder,
1627
+ required,
1628
+ style: { ...fieldStyle, height: 48 }
1629
+ }
1630
+ )
1631
+ ] }, i);
1632
+ }) }),
1633
+ children.map((child, i) => {
1634
+ if (child.type === "input") return null;
1635
+ const cs = child.slots ?? {};
1636
+ return /* @__PURE__ */ jsx(
1637
+ "button",
1638
+ {
1639
+ type: "submit",
1640
+ style: {
1641
+ alignSelf: submitAlign,
1642
+ border: "none",
1643
+ cursor: "pointer",
1644
+ padding: "12px 24px",
1645
+ // Corner radius follows the host template's own buttons (measured from a template
1646
+ // CTA); 8px only when the page has no template button to match.
1647
+ borderRadius: ctx.buttonRadius ?? 8,
1648
+ // Brand-styled: primary fill, brand-derived label colour (not a fixed token) so it
1649
+ // reads correctly on custom palettes.
1650
+ background: ctx.brand.palette.primary,
1651
+ color: ctx.buttonLabel ?? ctx.brand.palette.light,
1652
+ ...typeStyle(AI_TREE_TOKENS.type.button, ctx.brand.fonts.body)
1653
+ },
1654
+ children: /* @__PURE__ */ jsx("span", { ...textAttrs(ctx, `${path}.c${i}.label`), children: str(cs.label) })
1609
1655
  },
1610
- children: [
1611
- /* @__PURE__ */ jsx("label", { ...textAttrs(ctx, `${path}.c${i}.label`), style: labelStyle, children: label }),
1612
- isTextarea ? /* @__PURE__ */ jsx(
1613
- "textarea",
1614
- {
1615
- name,
1616
- placeholder,
1617
- style: { ...fieldStyle, height: 140, resize: "vertical" }
1618
- }
1619
- ) : /* @__PURE__ */ jsx(
1620
- "input",
1621
- {
1622
- name,
1623
- type: kind === "email" ? "email" : "text",
1624
- placeholder,
1625
- style: { ...fieldStyle, height: 48 }
1626
- }
1627
- )
1628
- ]
1629
- },
1630
- i
1631
- );
1632
- }
1633
- const cs = child.slots ?? {};
1634
- return /* @__PURE__ */ jsx(
1635
- "button",
1636
- {
1637
- type: "submit",
1638
- style: {
1639
- gridColumn: "1 / -1",
1640
- justifySelf: "start",
1641
- border: "none",
1642
- cursor: "pointer",
1643
- padding: `${AI_TREE_TOKENS.spacing3}px ${AI_TREE_TOKENS.spacing6}px`,
1644
- borderRadius: AI_TREE_TOKENS.radiusButton,
1645
- background: ctx.brand.palette.primary,
1646
- color: AI_TREE_TOKENS.textPrimaryForeground,
1647
- ...typeStyle(AI_TREE_TOKENS.type.button, ctx.brand.fonts.body)
1648
- },
1649
- children: /* @__PURE__ */ jsx("span", { ...textAttrs(ctx, `${path}.c${i}.label`), children: str(cs.label) })
1650
- },
1651
- i
1652
- );
1653
- })
1654
- }
1656
+ i
1657
+ );
1658
+ })
1659
+ ]
1660
+ }
1661
+ )
1655
1662
  );
1656
1663
  }
1657
1664
  case "schedule-widget":
@@ -1674,7 +1681,13 @@ function renderNode(node, ctx, path) {
1674
1681
  return null;
1675
1682
  }
1676
1683
  }
1677
- function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1684
+ function AiTreeRenderer({
1685
+ tree,
1686
+ brand,
1687
+ buttonRadius,
1688
+ resolveMedia,
1689
+ editKeyPrefix
1690
+ }) {
1678
1691
  if (!isRenderableTree(tree)) {
1679
1692
  return null;
1680
1693
  }
@@ -1686,6 +1699,8 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1686
1699
  resolveMedia: resolveMedia ?? (() => null),
1687
1700
  cardSurface: blockBrand.palette.light.toUpperCase() === AI_DEFAULT_BRAND.palette.light.toUpperCase() ? "#ECECEB" : `color-mix(in srgb, ${blockBrand.palette.light} 90%, ${blockBrand.palette.dark})`,
1688
1701
  keyFor: editKeyPrefix ? (path) => `${editKeyPrefix}.${path}` : null,
1702
+ sectionAlignment: (tree.settings ?? {}).alignment === "center" ? "center" : "left",
1703
+ buttonRadius,
1689
1704
  ...band ? { buttonLabel: band.buttonLabel } : {}
1690
1705
  };
1691
1706
  const settings = tree.settings ?? {};
@@ -1815,6 +1830,13 @@ function deriveTemplateBrand() {
1815
1830
  }
1816
1831
  };
1817
1832
  }
1833
+ function deriveTemplateButtonRadius() {
1834
+ if (typeof document === "undefined") return null;
1835
+ const btn = document.querySelector('[data-ohw-role="button"]');
1836
+ if (!btn) return null;
1837
+ const radius = getComputedStyle(btn).borderTopLeftRadius;
1838
+ return radius || null;
1839
+ }
1818
1840
  var mounted = /* @__PURE__ */ new Map();
1819
1841
  function findTemplateSection(id) {
1820
1842
  for (const el of document.querySelectorAll(`[data-ohw-section="${CSS.escape(id)}"]`)) {
@@ -1966,6 +1988,7 @@ function applyAiSectionsToDom(state, options) {
1966
1988
  if (typeof document === "undefined") return;
1967
1989
  const brandOverride = deriveBrandOverride();
1968
1990
  const templateBrand = deriveTemplateBrand();
1991
+ const templateButtonRadius = deriveTemplateButtonRadius();
1969
1992
  const brandKey = brandOverride ? JSON.stringify(brandOverride) : "";
1970
1993
  const pagePath = window.location.pathname;
1971
1994
  const pageSections = state.sections.filter((entry) => (entry.path ?? "/") === pagePath);
@@ -2005,6 +2028,7 @@ function applyAiSectionsToDom(state, options) {
2005
2028
  {
2006
2029
  tree: entry.tree,
2007
2030
  brand: brandOverride ?? entry.brand ?? options?.brand ?? templateBrand ?? void 0,
2031
+ buttonRadius: templateButtonRadius,
2008
2032
  resolveMedia,
2009
2033
  editKeyPrefix: `ai.${entry.id}`
2010
2034
  }
@@ -20996,7 +21020,7 @@ function OhhwellsBridge() {
20996
21020
  postToParent2({
20997
21021
  type: "ow:ready",
20998
21022
  version: "1",
20999
- bridgeVersion: "0.1.78",
21023
+ bridgeVersion: "0.1.79",
21000
21024
  path: pathname,
21001
21025
  nodes: collectEditableNodes(editContentRef.current),
21002
21026
  sections