@ohhwells/bridge 0.1.139-next.373 → 0.1.139-next.374

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
@@ -2919,14 +2919,22 @@ function isInactiveFooterChrome(el) {
2919
2919
  if (footer && footer.style.display === "none") return true;
2920
2920
  return false;
2921
2921
  }
2922
+ function isIncidentalFooter(el) {
2923
+ return Boolean(el.closest("blockquote, article, figure"));
2924
+ }
2922
2925
  function getFooterRoot() {
2923
2926
  const ai = document.querySelector("footer[data-ai-footer]");
2924
2927
  if (ai) return ai;
2925
2928
  for (const el of Array.from(
2926
- document.querySelectorAll('footer[data-ohw-section="footer"], footer')
2929
+ document.querySelectorAll('footer[data-ohw-section="footer"]')
2927
2930
  )) {
2931
+ if (isInactiveFooterChrome(el)) continue;
2932
+ return el;
2933
+ }
2934
+ for (const el of Array.from(document.querySelectorAll("footer"))) {
2928
2935
  if (el.hasAttribute("data-ai-footer")) continue;
2929
2936
  if (isInactiveFooterChrome(el)) continue;
2937
+ if (isIncidentalFooter(el)) continue;
2930
2938
  return el;
2931
2939
  }
2932
2940
  return null;
@@ -3337,8 +3345,8 @@ function buildFooterLink(colIndex, itemIndex, href, label, template) {
3337
3345
  return anchor;
3338
3346
  }
3339
3347
  function footerColumnExistsInDom(colIndex) {
3340
- const root = getFooterRoot() ?? document;
3341
- return root.querySelector(`[data-ohw-footer-col="${colIndex}"]`) !== null || root.querySelector(`[data-ohw-href-key^="footer-${colIndex}-"]`) !== null || root.querySelector(`[data-ohw-key="footer-${colIndex}-heading"]`) !== null;
3348
+ const scope = getFooterLinksContainer() ?? getFooterRoot() ?? document;
3349
+ return scope.querySelector(`[data-ohw-footer-col="${colIndex}"]`) !== null || scope.querySelector(`[data-ohw-href-key^="footer-${colIndex}-"]`) !== null || scope.querySelector(`[data-ohw-key="footer-${colIndex}-heading"]`) !== null;
3342
3350
  }
3343
3351
  function getFooterLinkTemplate() {
3344
3352
  for (const col of listFooterColumns()) {
@@ -6355,6 +6363,20 @@ function applyBrandChrome(content) {
6355
6363
 
6356
6364
  // src/lib/brand-kit.ts
6357
6365
  var BRAND_KIT_KEY = "__ohw_brand";
6366
+ var appliedPalette = null;
6367
+ var appliedFonts = null;
6368
+ function getAppliedBrand() {
6369
+ if (!appliedPalette) return null;
6370
+ return appliedFonts ? { palette: appliedPalette, fonts: appliedFonts } : { palette: appliedPalette };
6371
+ }
6372
+ function brandForGenerated(fallback) {
6373
+ const applied = getAppliedBrand();
6374
+ if (!applied) return fallback ?? void 0;
6375
+ return {
6376
+ palette: applied.palette,
6377
+ fonts: applied.fonts ?? fallback?.fonts ?? AI_DEFAULT_BRAND.fonts
6378
+ };
6379
+ }
6358
6380
  var BRAND_VAR_PREFIX = "--ohw-brand-";
6359
6381
  var BRAND_VAR_NAMES = ["primary", "accent", "light", "dark", "surface", "border", "muted"].map(
6360
6382
  (role) => `${BRAND_VAR_PREFIX}${role}`
@@ -6658,6 +6680,8 @@ function loadCustomFonts(fonts) {
6658
6680
  function applyBrandToDom(kit) {
6659
6681
  const root = document.documentElement;
6660
6682
  if (!kit) {
6683
+ appliedPalette = null;
6684
+ appliedFonts = null;
6661
6685
  for (const name of [...BRAND_VAR_NAMES, ...LEGACY_BRAND_VAR_NAMES, ...BUTTON_VAR_NAMES]) {
6662
6686
  root.style.removeProperty(name);
6663
6687
  }
@@ -6671,11 +6695,16 @@ function applyBrandToDom(kit) {
6671
6695
  const applyFonts = kit.touched ? kit.touched.fonts === true : true;
6672
6696
  if (kit.palette) {
6673
6697
  if (applyPalette) {
6698
+ appliedPalette = kit.palette;
6674
6699
  for (const [name, value] of Object.entries(brandColorVars(kit.palette))) root.style.setProperty(name, value);
6675
6700
  } else {
6701
+ appliedPalette = null;
6676
6702
  for (const name of [...BRAND_VAR_NAMES, ...LEGACY_BRAND_VAR_NAMES]) root.style.removeProperty(name);
6677
6703
  }
6678
6704
  }
6705
+ if (kit.fonts) {
6706
+ appliedFonts = applyFonts ? kit.fonts : null;
6707
+ }
6679
6708
  if (kit.buttons) {
6680
6709
  for (const name of BUTTON_VAR_NAMES) root.style.removeProperty(name);
6681
6710
  for (const [name, value] of Object.entries(buttonStyleVars({ ...kit, buttons: kit.buttons }))) root.style.setProperty(name, value);
@@ -7208,6 +7237,35 @@ function withThemeFonts(brand) {
7208
7237
  }
7209
7238
  };
7210
7239
  }
7240
+ function liveBrandColor(role, fallback) {
7241
+ if (role === "dark") {
7242
+ return `var(--brand-text, var(--ohw-brand-dark, var(--color-dark, ${fallback})))`;
7243
+ }
7244
+ if (role === "light") {
7245
+ return `var(--brand-background, var(--ohw-brand-light, var(--color-light, ${fallback})))`;
7246
+ }
7247
+ return `var(--brand-${role}, var(--ohw-brand-${role}, var(--color-${role}, ${fallback})))`;
7248
+ }
7249
+ function withLivePalette(brand, source) {
7250
+ const sourceOf = (color) => {
7251
+ const value = color.trim().toLowerCase();
7252
+ const entries = Object.entries(source.palette);
7253
+ return entries.find(([, hex]) => hex.trim().toLowerCase() === value)?.[0] ?? null;
7254
+ };
7255
+ const map = (color) => {
7256
+ const role = sourceOf(color);
7257
+ return role ? liveBrandColor(role, source.palette[role]) : color;
7258
+ };
7259
+ return {
7260
+ ...brand,
7261
+ palette: {
7262
+ dark: map(brand.palette.dark),
7263
+ primary: map(brand.palette.primary),
7264
+ accent: map(brand.palette.accent),
7265
+ light: map(brand.palette.light)
7266
+ }
7267
+ };
7268
+ }
7211
7269
  function footerLogoStyle(fallbackFont, fallbackColor) {
7212
7270
  return {
7213
7271
  fontFamily: `var(--brand-font-heading, var(--font-heading, var(--font-display, ${quotedFont(fallbackFont)})))`,
@@ -7427,7 +7485,7 @@ function hexContrast(a, b) {
7427
7485
  function primaryButtonLabel(brand) {
7428
7486
  const darkC = hexContrast(brand.palette.primary, brand.palette.dark);
7429
7487
  const lightC = hexContrast(brand.palette.primary, AI_TREE_TOKENS.textPrimaryForeground);
7430
- if (darkC === null || lightC === null) return AI_TREE_TOKENS.textPrimaryForeground;
7488
+ if (darkC === null || lightC === null) return `var(--brand-on-primary, ${AI_TREE_TOKENS.textPrimaryForeground})`;
7431
7489
  return darkC > lightC ? brand.palette.dark : AI_TREE_TOKENS.textPrimaryForeground;
7432
7490
  }
7433
7491
  function accentBandContext(brand) {
@@ -7451,7 +7509,11 @@ function readableForeground(bg, brand) {
7451
7509
  return darkC > lightC ? brand.palette.dark : AI_TREE_TOKENS.textPrimaryForeground;
7452
7510
  }
7453
7511
  function hexWithAlpha(hex, alpha) {
7454
- const m = /^#([0-9a-f]{6})$/i.exec(hex.trim());
7512
+ const value = hex.trim();
7513
+ if (value.startsWith("var(") || value.startsWith("color-mix(")) {
7514
+ return `color-mix(in srgb, ${value} ${Math.round(alpha * 100)}%, transparent)`;
7515
+ }
7516
+ const m = /^#([0-9a-f]{6})$/i.exec(value);
7455
7517
  if (!m) return hex;
7456
7518
  const r2 = parseInt(m[1].slice(0, 2), 16);
7457
7519
  const g = parseInt(m[1].slice(2, 4), 16);
@@ -7750,12 +7812,10 @@ function ButtonEl({
7750
7812
  cursor: "pointer",
7751
7813
  whiteSpace: "nowrap",
7752
7814
  ...buttonShellStyle(ctx, variant, fullWidth),
7753
- ...tokenColorStyle ?? (secondary ? { border: `1px solid ${secondaryColor ?? ctx.brand.palette.dark}`, color: secondaryColor ?? ctx.brand.palette.dark } : (
7754
- // Off-band, prefer the template button's measured fill/label pair — a template may
7755
- // fill its CTAs with any token (hvac: accent bg, primary text). On an accent band
7756
- // the flipped palette keeps contrast, so the brand-driven colours stay.
7757
- !ctx.buttonLabel && ctx.buttonStyle?.background ? { background: ctx.buttonStyle.background, color: ctx.buttonStyle.color } : { background: ctx.brand.palette.primary, color: ctx.buttonLabel ?? primaryButtonLabel(ctx.brand) }
7758
- ))
7815
+ ...tokenColorStyle ?? (secondary ? { border: `1px solid ${secondaryColor ?? ctx.brand.palette.dark}`, color: secondaryColor ?? ctx.brand.palette.dark } : {
7816
+ background: ctx.brand.palette.primary,
7817
+ color: ctx.buttonLabel ?? primaryButtonLabel(ctx.brand)
7818
+ })
7759
7819
  },
7760
7820
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { ...path ? textAttrs(ctx, `${path}.label`) : {}, children: str(slots.label) })
7761
7821
  }
@@ -9600,10 +9660,10 @@ function renderNode(node, ctx, path, ambientAlign) {
9600
9660
  ...buttonShellStyle(ctx, "primary"),
9601
9661
  // Brand-styled: the template button's measured fill/label pair off-band, else
9602
9662
  // primary fill with a brand-derived label so it reads on custom palettes.
9603
- ...buttonColorStyle(ctx, "primary") ?? (!ctx.buttonLabel && ctx.buttonStyle?.background ? { background: ctx.buttonStyle.background, color: ctx.buttonStyle.color } : {
9663
+ ...buttonColorStyle(ctx, "primary") ?? {
9604
9664
  background: ctx.brand.palette.primary,
9605
9665
  color: ctx.buttonLabel ?? primaryButtonLabel(ctx.brand)
9606
- })
9666
+ }
9607
9667
  },
9608
9668
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { ...textAttrs(ctx, `${path}.c${i}.label`), children: str(cs.label) })
9609
9669
  },
@@ -9647,7 +9707,7 @@ function AiTreeRenderer({
9647
9707
  }
9648
9708
  const resolvedBrand = withThemeFonts(brand ?? AI_DEFAULT_BRAND);
9649
9709
  const band = (tree.settings ?? {}).sectionBackground === "accent" ? accentBandContext(resolvedBrand) : null;
9650
- const blockBrand = band?.brand ?? resolvedBrand;
9710
+ const blockBrand = withLivePalette(band?.brand ?? resolvedBrand, resolvedBrand);
9651
9711
  const placeholderMap = buildPlaceholderMap(tree);
9652
9712
  const hasOverlapBackground = tree.scaffold === "overlay" && tree.rows.some(
9653
9713
  (row) => (row.blocks ?? []).some((b) => b.type === "group" && str(b.slots?.layout) === "overlap")
@@ -9663,7 +9723,7 @@ function AiTreeRenderer({
9663
9723
  // standalone resolve), so generated galleries, image rows, and overlay backgrounds arrive with
9664
9724
  // photos instead of grey boxes.
9665
9725
  resolveMedia: (ref) => resolveMedia?.(ref) ?? placeholderMap[ref] ?? resolvePlaceholderRef(ref),
9666
- cardSurface: blockBrand.palette.light.toUpperCase() === AI_DEFAULT_BRAND.palette.light.toUpperCase() ? "#ECECEB" : `color-mix(in srgb, ${blockBrand.palette.light} 90%, ${blockBrand.palette.dark})`,
9726
+ cardSurface: resolvedBrand.palette.light.toUpperCase() === AI_DEFAULT_BRAND.palette.light.toUpperCase() ? "#ECECEB" : `color-mix(in srgb, ${blockBrand.palette.light} 90%, ${blockBrand.palette.dark})`,
9667
9727
  keyFor: editKeyPrefix ? (path) => `${editKeyPrefix}.${path}` : null,
9668
9728
  sectionAlignment: (tree.settings ?? {}).alignment === "center" ? "center" : "left",
9669
9729
  buttonStyle,
@@ -9680,7 +9740,7 @@ function AiTreeRenderer({
9680
9740
  const backgroundUrl = isOverlay && settings.backgroundMedia ? ctx.resolveMedia(settings.backgroundMedia) : null;
9681
9741
  const bgAttrs = ctx.keyFor && isOverlay && settings.backgroundMedia ? { "data-ohw-key": ctx.keyFor("settings.backgroundMedia"), "data-ohw-editable": "bg-image" } : {};
9682
9742
  const toneBackground = (() => {
9683
- const { dark, primary, light } = resolvedBrand.palette;
9743
+ const { dark, primary, light } = blockBrand.palette;
9684
9744
  switch (settings.sectionBackground) {
9685
9745
  case "surface":
9686
9746
  return `color-mix(in srgb, ${light} 94%, ${dark})`;
@@ -9803,14 +9863,15 @@ function AiFooterRenderer({
9803
9863
  if (!isRenderableFooterTree(tree)) {
9804
9864
  return null;
9805
9865
  }
9806
- const resolvedBrand = withThemeFonts(brand ?? AI_DEFAULT_BRAND);
9866
+ const hexBrand = withThemeFonts(brand ?? AI_DEFAULT_BRAND);
9867
+ const resolvedBrand = withLivePalette(hexBrand, brand ?? AI_DEFAULT_BRAND);
9807
9868
  const bandBg = surface?.background ?? resolvedBrand.palette.dark;
9808
9869
  const bandText = surface?.text ?? resolvedBrand.palette.light;
9809
9870
  const onDark = surface ? isDarkColor(surface.background) : true;
9810
9871
  const ctx = {
9811
9872
  brand: resolvedBrand,
9812
9873
  resolveMedia: (ref) => resolveMedia?.(ref) ?? resolvePlaceholderRef(ref),
9813
- cardSurface: resolvedBrand.palette.light.toUpperCase() === AI_DEFAULT_BRAND.palette.light.toUpperCase() ? "#ECECEB" : `color-mix(in srgb, ${resolvedBrand.palette.light} 90%, ${resolvedBrand.palette.dark})`,
9874
+ cardSurface: hexBrand.palette.light.toUpperCase() === AI_DEFAULT_BRAND.palette.light.toUpperCase() ? "#ECECEB" : `color-mix(in srgb, ${resolvedBrand.palette.light} 90%, ${resolvedBrand.palette.dark})`,
9814
9875
  keyFor: editKeyPrefix ? (path) => `${editKeyPrefix}.${path}` : null,
9815
9876
  logoUrl: logoUrl ?? null,
9816
9877
  resolvePageHref
@@ -10386,10 +10447,9 @@ function syncSoftRemovedGenerated() {
10386
10447
  }
10387
10448
  function applyAiSectionsToDom(state, options) {
10388
10449
  if (typeof document === "undefined") return;
10389
- const brandOverride = deriveBrandOverride();
10390
10450
  const templateBrand = deriveTemplateBrand();
10391
10451
  const templateButtonStyle = deriveTemplateButtonStyle();
10392
- const brandKey = brandOverride ? JSON.stringify(brandOverride) : "";
10452
+ const brandKey = JSON.stringify(getAppliedBrand() ?? deriveBrandOverride() ?? null);
10393
10453
  const pagePath = window.location.pathname;
10394
10454
  const pageSections = state.sections.filter((entry) => (entry.path ?? "/") === pagePath);
10395
10455
  const activeIds = new Set(pageSections.map((entry) => entry.id));
@@ -10433,7 +10493,7 @@ function applyAiSectionsToDom(state, options) {
10433
10493
  AiTreeRenderer,
10434
10494
  {
10435
10495
  tree: entry.tree,
10436
- brand: brandOverride ?? entry.brand ?? options?.brand ?? templateBrand ?? void 0,
10496
+ brand: brandForGenerated(deriveBrandOverride() ?? entry.brand ?? options?.brand ?? templateBrand),
10437
10497
  buttonStyle: templateButtonStyle,
10438
10498
  resolveMedia,
10439
10499
  editKeyPrefix: `ai.${entry.id}`
@@ -10599,7 +10659,7 @@ function applyAiFooterToDom(state, content) {
10599
10659
  const templateFooter = findTemplateFooter();
10600
10660
  captureTemplateFooterSurface(templateFooter);
10601
10661
  const surface = capturedSurface ?? null;
10602
- const brand = mountOptions.brand ?? deriveTemplateBrand();
10662
+ const brand = brandForGenerated(mountOptions.brand ?? deriveTemplateBrand());
10603
10663
  const logoUrl = resolveLogoFromContent(content, templateFooter);
10604
10664
  const serialized = JSON.stringify(state) + JSON.stringify(brand ?? null) + (logoUrl ?? "") + JSON.stringify(surface) + JSON.stringify(defaultLegal());
10605
10665
  if (mounted2 && mounted2.serialized === serialized && mounted2.container.isConnected) {
@@ -27325,7 +27385,7 @@ function OhhwellsBridge() {
27325
27385
  if (!subdomain) return {};
27326
27386
  return contentCache.get(subdomain) ?? {};
27327
27387
  };
27328
- const observeRoots = () => [document.querySelector("nav"), document.querySelector("footer")].filter(
27388
+ const observeRoots = () => [document.querySelector("nav"), getFooterRoot()].filter(
27329
27389
  (el) => Boolean(el)
27330
27390
  );
27331
27391
  const roots = observeRoots();
@@ -27781,7 +27841,7 @@ function OhhwellsBridge() {
27781
27841
  {
27782
27842
  const formEl = getFormElement(target);
27783
27843
  const onSuccessText = target.closest(`[${SUCCESS_TEXT_ATTR}]`);
27784
- if (formEl && formKeyOf(formEl) && !onSuccessText) {
27844
+ if (formEl && formKeyOf(formEl) && !onSuccessText && !isCtaButton(target)) {
27785
27845
  const fieldEl = getFieldWrapper(target);
27786
27846
  if (fieldEl) {
27787
27847
  const label = target.closest("label");
@@ -29690,6 +29750,7 @@ function OhhwellsBridge() {
29690
29750
  brandKitRef.current = value;
29691
29751
  applyBrandToDom(parseBrandKit(value));
29692
29752
  if (aiSectionsRef.current) applyAiSectionsToDom(parseAiSectionsState(aiSectionsRef.current));
29753
+ applyAiFooterFromContent(editContentRef.current);
29693
29754
  applyStylesToDom(parseStyleStore(stylesRef.current));
29694
29755
  postToParentRef.current({ type: "ow:change", nodes: [{ key: BRAND_KIT_KEY, text: value }] });
29695
29756
  postToParentRef.current({ type: "ow:ai-brand-applied", previous, value });