@ohhwells/bridge 0.1.113-next.293 → 0.1.113-next.295

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.cjs CHANGED
@@ -105,6 +105,10 @@ function enforceLinkHrefs() {
105
105
 
106
106
  // src/lib/ai-tree.ts
107
107
  var AI_TREE_SCHEMA_VERSIONS = [1];
108
+ var BUTTON_FILL_TOKENS = ["dark", "primary", "accent", "light"];
109
+ var BUTTON_STYLES = ["solid", "outline", "ghost"];
110
+ var BUTTON_RADII = ["square", "rounded", "pill"];
111
+ var BUTTON_SIZES = ["s", "m", "l"];
108
112
  var AI_DEFAULT_BRAND = {
109
113
  palette: { dark: "#0C0A09", primary: "#0078E5", accent: "#747271", light: "#ECECEB" },
110
114
  fonts: {
@@ -362,6 +366,59 @@ var FONT_VARS = {
362
366
  var BRAND_FONT_LINK_ID = "ohw-brand-fonts";
363
367
  var CUSTOM_FONT_STYLE_ID = "ohw-brand-custom-fonts";
364
368
  var CUSTOM_FONT_FORMATS = /* @__PURE__ */ new Set(["woff2", "woff", "truetype", "opentype"]);
369
+ var BUTTON_FILL_VAR = {
370
+ dark: "var(--brand-text)",
371
+ primary: "var(--brand-primary)",
372
+ accent: "var(--brand-accent)",
373
+ light: "var(--brand-background)"
374
+ };
375
+ var BUTTON_RADIUS_PX = {
376
+ square: 0,
377
+ rounded: AI_TREE_TOKENS.radiusButton,
378
+ pill: 999
379
+ };
380
+ var BUTTON_SIZE_SPEC = {
381
+ s: {
382
+ paddingX: `${AI_TREE_TOKENS.spacing4}px`,
383
+ paddingY: `${AI_TREE_TOKENS.spacing2}px`,
384
+ fontSize: "14px"
385
+ },
386
+ m: {
387
+ paddingX: `${AI_TREE_TOKENS.spacing6}px`,
388
+ paddingY: `${AI_TREE_TOKENS.spacing3}px`,
389
+ fontSize: `${AI_TREE_TOKENS.type.button.size}px`
390
+ },
391
+ l: {
392
+ paddingX: `${AI_TREE_TOKENS.spacing8}px`,
393
+ paddingY: `${AI_TREE_TOKENS.spacing4}px`,
394
+ fontSize: "18px"
395
+ }
396
+ };
397
+ var BUTTON_VAR_ROLES = ["bg", "text", "border", "radius", "padding-x", "padding-y", "font-size"];
398
+ var BUTTON_VAR_NAMES = ["primary", "secondary"].flatMap(
399
+ (variant) => BUTTON_VAR_ROLES.map((role) => `--brand-button-${variant}-${role}`)
400
+ );
401
+ function buttonVariantVars(variant, style) {
402
+ const fill = BUTTON_FILL_VAR[style.fill];
403
+ const text = BUTTON_FILL_VAR[style.textColor];
404
+ const size = BUTTON_SIZE_SPEC[style.size];
405
+ const { bg, border } = style.style === "outline" ? { bg: "transparent", border: fill } : style.style === "ghost" ? { bg: "transparent", border: "transparent" } : { bg: fill, border: "transparent" };
406
+ return {
407
+ [`--brand-button-${variant}-bg`]: bg,
408
+ [`--brand-button-${variant}-text`]: text,
409
+ [`--brand-button-${variant}-border`]: border,
410
+ [`--brand-button-${variant}-radius`]: `${BUTTON_RADIUS_PX[style.radius]}px`,
411
+ [`--brand-button-${variant}-padding-x`]: size.paddingX,
412
+ [`--brand-button-${variant}-padding-y`]: size.paddingY,
413
+ [`--brand-button-${variant}-font-size`]: size.fontSize
414
+ };
415
+ }
416
+ function buttonStyleVars(buttons) {
417
+ return {
418
+ ...buttonVariantVars("primary", buttons.primary),
419
+ ...buttonVariantVars("secondary", buttons.secondary)
420
+ };
421
+ }
365
422
  function brandColorVars(palette) {
366
423
  const { dark, primary, accent, light } = palette;
367
424
  const mix = (a, aPct, b) => `color-mix(in srgb, ${a} ${aPct}%, ${b})`;
@@ -389,6 +446,11 @@ function brandColorVars(palette) {
389
446
  "--brand-on-primary": light
390
447
  };
391
448
  }
449
+ function isButtonStyle(value) {
450
+ if (!value || typeof value !== "object") return false;
451
+ const v = value;
452
+ return BUTTON_FILL_TOKENS.includes(v.fill) && BUTTON_FILL_TOKENS.includes(v.textColor) && BUTTON_STYLES.includes(v.style) && BUTTON_RADII.includes(v.radius) && BUTTON_SIZES.includes(v.size);
453
+ }
392
454
  function parseCustomFontWeight(raw) {
393
455
  if (typeof raw !== "object" || raw === null) return null;
394
456
  const w = raw;
@@ -417,11 +479,14 @@ function parseBrandKit(raw) {
417
479
  const palette = p && typeof p.dark === "string" && typeof p.primary === "string" && typeof p.accent === "string" && typeof p.light === "string" ? { dark: p.dark, primary: p.primary, accent: p.accent, light: p.light } : void 0;
418
480
  const fonts = f && typeof f.heading === "string" && typeof f.body === "string" ? { heading: f.heading, body: f.body } : void 0;
419
481
  const customFonts = Array.isArray(parsed?.customFonts) ? parsed.customFonts.map(parseCustomFont).filter((c) => c !== null) : void 0;
420
- if (!palette && !fonts && !customFonts) return null;
482
+ const b = parsed?.buttons;
483
+ const buttons = b && isButtonStyle(b.primary) && isButtonStyle(b.secondary) ? { primary: b.primary, secondary: b.secondary } : void 0;
484
+ if (!palette && !fonts && !customFonts && !buttons) return null;
421
485
  return {
422
486
  ...palette ? { palette } : {},
423
487
  ...fonts ? { fonts } : {},
424
- ...customFonts ? { customFonts } : {}
488
+ ...customFonts ? { customFonts } : {},
489
+ ...buttons ? { buttons } : {}
425
490
  };
426
491
  } catch {
427
492
  return null;
@@ -475,7 +540,9 @@ function loadCustomFonts(fonts) {
475
540
  function applyBrandToDom(kit) {
476
541
  const root = document.documentElement;
477
542
  if (!kit) {
478
- for (const name of [...BRAND_VAR_NAMES, ...LEGACY_BRAND_VAR_NAMES]) root.style.removeProperty(name);
543
+ for (const name of [...BRAND_VAR_NAMES, ...LEGACY_BRAND_VAR_NAMES, ...BUTTON_VAR_NAMES]) {
544
+ root.style.removeProperty(name);
545
+ }
479
546
  for (const name of [...FONT_VARS.heading, ...FONT_VARS.body]) root.style.removeProperty(name);
480
547
  document.getElementById(BRAND_FONT_LINK_ID)?.remove();
481
548
  document.getElementById(CUSTOM_FONT_STYLE_ID)?.remove();
@@ -484,6 +551,9 @@ function applyBrandToDom(kit) {
484
551
  if (kit.palette) {
485
552
  for (const [name, value] of Object.entries(brandColorVars(kit.palette))) root.style.setProperty(name, value);
486
553
  }
554
+ if (kit.buttons) {
555
+ for (const [name, value] of Object.entries(buttonStyleVars(kit.buttons))) root.style.setProperty(name, value);
556
+ }
487
557
  if (kit.customFonts) {
488
558
  loadCustomFonts(kit.customFonts);
489
559
  }
@@ -612,7 +682,6 @@ var NODE_PROPS = [
612
682
  "color",
613
683
  "font-family",
614
684
  "font-size",
615
- "background",
616
685
  "text-align",
617
686
  "justify-content",
618
687
  "align-items"
@@ -662,9 +731,6 @@ function clearNodeProps(root) {
662
731
  h.removeAttribute(NODE_WROTE_ATTR);
663
732
  }
664
733
  }
665
- function buttonSurfaceOf(el) {
666
- return el.closest("a, button") ?? el;
667
- }
668
734
  function alignSubjectOf(el) {
669
735
  const button = el.closest('[data-ohw-role="button"]');
670
736
  if (button?.parentElement) return button.parentElement;
@@ -780,18 +846,6 @@ function applyStylesToDom(store) {
780
846
  );
781
847
  subject.setAttribute(NODE_WROTE_ATTR, "");
782
848
  }
783
- if (override.buttonBackground !== void 0 || override.buttonText !== void 0) {
784
- const surface = buttonSurfaceOf(el);
785
- if (override.buttonBackground !== void 0) {
786
- saveInline(surface, "background");
787
- surface.style.setProperty("background", override.buttonBackground, "important");
788
- }
789
- if (override.buttonText !== void 0) {
790
- saveInline(surface, "color");
791
- surface.style.setProperty("color", override.buttonText, "important");
792
- }
793
- surface.setAttribute(NODE_WROTE_ATTR, "");
794
- }
795
849
  }
796
850
  }
797
851
  for (const [sectionId, override] of Object.entries(store.sections)) {
@@ -1386,7 +1440,59 @@ var FEATURE_LINE_CSS = [
1386
1440
  `background-color:currentColor;-webkit-mask:url("${CHECK_MASK}") no-repeat 0 0/24px 24px;`,
1387
1441
  `mask:url("${CHECK_MASK}") no-repeat 0 0/24px 24px}`
1388
1442
  ].join("");
1389
- function buttonShellStyle(ctx, fullWidth) {
1443
+ function buttonSizeSpec(size) {
1444
+ switch (size) {
1445
+ case "s":
1446
+ return { padding: `${AI_TREE_TOKENS.spacing2}px ${AI_TREE_TOKENS.spacing4}px`, fontSize: 14 };
1447
+ case "l":
1448
+ return { padding: `${AI_TREE_TOKENS.spacing4}px ${AI_TREE_TOKENS.spacing8}px`, fontSize: 18 };
1449
+ case "m":
1450
+ default:
1451
+ return { padding: `${AI_TREE_TOKENS.spacing3}px ${AI_TREE_TOKENS.spacing6}px`, fontSize: AI_TREE_TOKENS.type.button.size };
1452
+ }
1453
+ }
1454
+ function buttonRadiusPx(radius) {
1455
+ switch (radius) {
1456
+ case "square":
1457
+ return 0;
1458
+ case "pill":
1459
+ return 999;
1460
+ case "rounded":
1461
+ default:
1462
+ return AI_TREE_TOKENS.radiusButton;
1463
+ }
1464
+ }
1465
+ function resolveButtonToken(brand, token) {
1466
+ return brand.palette[token];
1467
+ }
1468
+ function buttonColorStyle(ctx, variant) {
1469
+ const token = ctx.brand.buttons?.[variant];
1470
+ if (!token) return null;
1471
+ const fill = resolveButtonToken(ctx.brand, token.fill);
1472
+ const text = resolveButtonToken(ctx.brand, token.textColor);
1473
+ switch (token.style) {
1474
+ case "outline":
1475
+ return { background: "transparent", border: `1px solid ${fill}`, color: text };
1476
+ case "ghost":
1477
+ return { background: "transparent", border: "1px solid transparent", color: text };
1478
+ case "solid":
1479
+ default:
1480
+ return { background: fill, color: text };
1481
+ }
1482
+ }
1483
+ function buttonShellStyle(ctx, variant, fullWidth) {
1484
+ const buttonToken = ctx.brand.buttons?.[variant];
1485
+ if (buttonToken) {
1486
+ const { padding, fontSize } = buttonSizeSpec(buttonToken.size);
1487
+ return {
1488
+ borderRadius: buttonRadiusPx(buttonToken.radius),
1489
+ padding,
1490
+ fontFamily: ctx.brand.fonts.body,
1491
+ fontSize,
1492
+ lineHeight: AI_TREE_TOKENS.type.button.line,
1493
+ fontWeight: AI_TREE_TOKENS.type.button.weight
1494
+ };
1495
+ }
1390
1496
  const bs = ctx.buttonStyle;
1391
1497
  if (bs) {
1392
1498
  return {
@@ -1534,6 +1640,8 @@ function ButtonEl({
1534
1640
  fullWidth
1535
1641
  }) {
1536
1642
  const secondary = slots.variant === "secondary";
1643
+ const variant = secondary ? "secondary" : "primary";
1644
+ const tokenColorStyle = buttonColorStyle(ctx, variant);
1537
1645
  const href = str(slots.href);
1538
1646
  const linkAttrs = ctx.keyFor && path ? { "data-ohw-href-key": ctx.keyFor(`${path}.href`), "data-ohw-role": "button" } : {};
1539
1647
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -1548,13 +1656,13 @@ function ButtonEl({
1548
1656
  justifyContent: "center",
1549
1657
  textDecoration: "none",
1550
1658
  cursor: "pointer",
1551
- ...buttonShellStyle(ctx, fullWidth),
1552
- ...secondary ? { border: `1px solid ${ctx.brand.palette.dark}`, color: ctx.brand.palette.dark } : (
1659
+ ...buttonShellStyle(ctx, variant, fullWidth),
1660
+ ...tokenColorStyle ?? (secondary ? { border: `1px solid ${ctx.brand.palette.dark}`, color: ctx.brand.palette.dark } : (
1553
1661
  // Off-band, prefer the template button's measured fill/label pair — a template may
1554
1662
  // fill its CTAs with any token (hvac: accent bg, primary text). On an accent band
1555
1663
  // the flipped palette keeps contrast, so the brand-driven colours stay.
1556
1664
  !ctx.buttonLabel && ctx.buttonStyle?.background ? { background: ctx.buttonStyle.background, color: ctx.buttonStyle.color } : { background: ctx.brand.palette.primary, color: ctx.buttonLabel ?? primaryButtonLabel(ctx.brand) }
1557
- )
1665
+ ))
1558
1666
  },
1559
1667
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { ...path ? textAttrs(ctx, `${path}.label`) : {}, children: str(slots.label) })
1560
1668
  }
@@ -2625,13 +2733,13 @@ function renderNode(node, ctx, path) {
2625
2733
  border: "none",
2626
2734
  cursor: "pointer",
2627
2735
  // Shape/padding/typography follow the host template's own buttons.
2628
- ...buttonShellStyle(ctx),
2736
+ ...buttonShellStyle(ctx, "primary"),
2629
2737
  // Brand-styled: the template button's measured fill/label pair off-band, else
2630
2738
  // primary fill with a brand-derived label so it reads on custom palettes.
2631
- ...!ctx.buttonLabel && ctx.buttonStyle?.background ? { background: ctx.buttonStyle.background, color: ctx.buttonStyle.color } : {
2739
+ ...buttonColorStyle(ctx, "primary") ?? (!ctx.buttonLabel && ctx.buttonStyle?.background ? { background: ctx.buttonStyle.background, color: ctx.buttonStyle.color } : {
2632
2740
  background: ctx.brand.palette.primary,
2633
2741
  color: ctx.buttonLabel ?? primaryButtonLabel(ctx.brand)
2634
- }
2742
+ })
2635
2743
  },
2636
2744
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { ...textAttrs(ctx, `${path}.c${i}.label`), children: str(cs.label) })
2637
2745
  },