@ohhwells/bridge 0.1.139 → 0.1.140

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.js CHANGED
@@ -2827,14 +2827,22 @@ function isInactiveFooterChrome(el) {
2827
2827
  if (footer && footer.style.display === "none") return true;
2828
2828
  return false;
2829
2829
  }
2830
+ function isIncidentalFooter(el) {
2831
+ return Boolean(el.closest("blockquote, article, figure"));
2832
+ }
2830
2833
  function getFooterRoot() {
2831
2834
  const ai = document.querySelector("footer[data-ai-footer]");
2832
2835
  if (ai) return ai;
2833
2836
  for (const el of Array.from(
2834
- document.querySelectorAll('footer[data-ohw-section="footer"], footer')
2837
+ document.querySelectorAll('footer[data-ohw-section="footer"]')
2835
2838
  )) {
2839
+ if (isInactiveFooterChrome(el)) continue;
2840
+ return el;
2841
+ }
2842
+ for (const el of Array.from(document.querySelectorAll("footer"))) {
2836
2843
  if (el.hasAttribute("data-ai-footer")) continue;
2837
2844
  if (isInactiveFooterChrome(el)) continue;
2845
+ if (isIncidentalFooter(el)) continue;
2838
2846
  return el;
2839
2847
  }
2840
2848
  return null;
@@ -3245,8 +3253,8 @@ function buildFooterLink(colIndex, itemIndex, href, label, template) {
3245
3253
  return anchor;
3246
3254
  }
3247
3255
  function footerColumnExistsInDom(colIndex) {
3248
- const root = getFooterRoot() ?? document;
3249
- 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;
3256
+ const scope = getFooterLinksContainer() ?? getFooterRoot() ?? document;
3257
+ 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;
3250
3258
  }
3251
3259
  function getFooterLinkTemplate() {
3252
3260
  for (const col of listFooterColumns()) {
@@ -6263,6 +6271,20 @@ function applyBrandChrome(content) {
6263
6271
 
6264
6272
  // src/lib/brand-kit.ts
6265
6273
  var BRAND_KIT_KEY = "__ohw_brand";
6274
+ var appliedPalette = null;
6275
+ var appliedFonts = null;
6276
+ function getAppliedBrand() {
6277
+ if (!appliedPalette) return null;
6278
+ return appliedFonts ? { palette: appliedPalette, fonts: appliedFonts } : { palette: appliedPalette };
6279
+ }
6280
+ function brandForGenerated(fallback) {
6281
+ const applied = getAppliedBrand();
6282
+ if (!applied) return fallback ?? void 0;
6283
+ return {
6284
+ palette: applied.palette,
6285
+ fonts: applied.fonts ?? fallback?.fonts ?? AI_DEFAULT_BRAND.fonts
6286
+ };
6287
+ }
6266
6288
  var BRAND_VAR_PREFIX = "--ohw-brand-";
6267
6289
  var BRAND_VAR_NAMES = ["primary", "accent", "light", "dark", "surface", "border", "muted"].map(
6268
6290
  (role) => `${BRAND_VAR_PREFIX}${role}`
@@ -6566,6 +6588,8 @@ function loadCustomFonts(fonts) {
6566
6588
  function applyBrandToDom(kit) {
6567
6589
  const root = document.documentElement;
6568
6590
  if (!kit) {
6591
+ appliedPalette = null;
6592
+ appliedFonts = null;
6569
6593
  for (const name of [...BRAND_VAR_NAMES, ...LEGACY_BRAND_VAR_NAMES, ...BUTTON_VAR_NAMES]) {
6570
6594
  root.style.removeProperty(name);
6571
6595
  }
@@ -6579,11 +6603,16 @@ function applyBrandToDom(kit) {
6579
6603
  const applyFonts = kit.touched ? kit.touched.fonts === true : true;
6580
6604
  if (kit.palette) {
6581
6605
  if (applyPalette) {
6606
+ appliedPalette = kit.palette;
6582
6607
  for (const [name, value] of Object.entries(brandColorVars(kit.palette))) root.style.setProperty(name, value);
6583
6608
  } else {
6609
+ appliedPalette = null;
6584
6610
  for (const name of [...BRAND_VAR_NAMES, ...LEGACY_BRAND_VAR_NAMES]) root.style.removeProperty(name);
6585
6611
  }
6586
6612
  }
6613
+ if (kit.fonts) {
6614
+ appliedFonts = applyFonts ? kit.fonts : null;
6615
+ }
6587
6616
  if (kit.buttons) {
6588
6617
  for (const name of BUTTON_VAR_NAMES) root.style.removeProperty(name);
6589
6618
  for (const [name, value] of Object.entries(buttonStyleVars({ ...kit, buttons: kit.buttons }))) root.style.setProperty(name, value);
@@ -7116,6 +7145,35 @@ function withThemeFonts(brand) {
7116
7145
  }
7117
7146
  };
7118
7147
  }
7148
+ function liveBrandColor(role, fallback) {
7149
+ if (role === "dark") {
7150
+ return `var(--brand-text, var(--ohw-brand-dark, var(--color-dark, ${fallback})))`;
7151
+ }
7152
+ if (role === "light") {
7153
+ return `var(--brand-background, var(--ohw-brand-light, var(--color-light, ${fallback})))`;
7154
+ }
7155
+ return `var(--brand-${role}, var(--ohw-brand-${role}, var(--color-${role}, ${fallback})))`;
7156
+ }
7157
+ function withLivePalette(brand, source) {
7158
+ const sourceOf = (color) => {
7159
+ const value = color.trim().toLowerCase();
7160
+ const entries = Object.entries(source.palette);
7161
+ return entries.find(([, hex]) => hex.trim().toLowerCase() === value)?.[0] ?? null;
7162
+ };
7163
+ const map = (color) => {
7164
+ const role = sourceOf(color);
7165
+ return role ? liveBrandColor(role, source.palette[role]) : color;
7166
+ };
7167
+ return {
7168
+ ...brand,
7169
+ palette: {
7170
+ dark: map(brand.palette.dark),
7171
+ primary: map(brand.palette.primary),
7172
+ accent: map(brand.palette.accent),
7173
+ light: map(brand.palette.light)
7174
+ }
7175
+ };
7176
+ }
7119
7177
  function footerLogoStyle(fallbackFont, fallbackColor) {
7120
7178
  return {
7121
7179
  fontFamily: `var(--brand-font-heading, var(--font-heading, var(--font-display, ${quotedFont(fallbackFont)})))`,
@@ -7333,7 +7391,7 @@ function hexContrast(a, b) {
7333
7391
  function primaryButtonLabel(brand) {
7334
7392
  const darkC = hexContrast(brand.palette.primary, brand.palette.dark);
7335
7393
  const lightC = hexContrast(brand.palette.primary, AI_TREE_TOKENS.textPrimaryForeground);
7336
- if (darkC === null || lightC === null) return AI_TREE_TOKENS.textPrimaryForeground;
7394
+ if (darkC === null || lightC === null) return `var(--brand-on-primary, ${AI_TREE_TOKENS.textPrimaryForeground})`;
7337
7395
  return darkC > lightC ? brand.palette.dark : AI_TREE_TOKENS.textPrimaryForeground;
7338
7396
  }
7339
7397
  function accentBandContext(brand) {
@@ -7357,7 +7415,11 @@ function readableForeground(bg, brand) {
7357
7415
  return darkC > lightC ? brand.palette.dark : AI_TREE_TOKENS.textPrimaryForeground;
7358
7416
  }
7359
7417
  function hexWithAlpha(hex, alpha) {
7360
- const m = /^#([0-9a-f]{6})$/i.exec(hex.trim());
7418
+ const value = hex.trim();
7419
+ if (value.startsWith("var(") || value.startsWith("color-mix(")) {
7420
+ return `color-mix(in srgb, ${value} ${Math.round(alpha * 100)}%, transparent)`;
7421
+ }
7422
+ const m = /^#([0-9a-f]{6})$/i.exec(value);
7361
7423
  if (!m) return hex;
7362
7424
  const r2 = parseInt(m[1].slice(0, 2), 16);
7363
7425
  const g = parseInt(m[1].slice(2, 4), 16);
@@ -7630,12 +7692,10 @@ function ButtonEl({
7630
7692
  cursor: "pointer",
7631
7693
  whiteSpace: "nowrap",
7632
7694
  ...buttonShellStyle(ctx, variant, fullWidth),
7633
- ...tokenColorStyle ?? (secondary ? { border: `1px solid ${secondaryColor ?? ctx.brand.palette.dark}`, color: secondaryColor ?? ctx.brand.palette.dark } : (
7634
- // Off-band, prefer the template button's measured fill/label pair — a template may
7635
- // fill its CTAs with any token (hvac: accent bg, primary text). On an accent band
7636
- // the flipped palette keeps contrast, so the brand-driven colours stay.
7637
- !ctx.buttonLabel && ctx.buttonStyle?.background ? { background: ctx.buttonStyle.background, color: ctx.buttonStyle.color } : { background: ctx.brand.palette.primary, color: ctx.buttonLabel ?? primaryButtonLabel(ctx.brand) }
7638
- ))
7695
+ ...tokenColorStyle ?? (secondary ? { border: `1px solid ${secondaryColor ?? ctx.brand.palette.dark}`, color: secondaryColor ?? ctx.brand.palette.dark } : {
7696
+ background: ctx.brand.palette.primary,
7697
+ color: ctx.buttonLabel ?? primaryButtonLabel(ctx.brand)
7698
+ })
7639
7699
  },
7640
7700
  children: /* @__PURE__ */ jsx("span", { ...path ? textAttrs(ctx, `${path}.label`) : {}, children: str(slots.label) })
7641
7701
  }
@@ -9480,10 +9540,10 @@ function renderNode(node, ctx, path, ambientAlign) {
9480
9540
  ...buttonShellStyle(ctx, "primary"),
9481
9541
  // Brand-styled: the template button's measured fill/label pair off-band, else
9482
9542
  // primary fill with a brand-derived label so it reads on custom palettes.
9483
- ...buttonColorStyle(ctx, "primary") ?? (!ctx.buttonLabel && ctx.buttonStyle?.background ? { background: ctx.buttonStyle.background, color: ctx.buttonStyle.color } : {
9543
+ ...buttonColorStyle(ctx, "primary") ?? {
9484
9544
  background: ctx.brand.palette.primary,
9485
9545
  color: ctx.buttonLabel ?? primaryButtonLabel(ctx.brand)
9486
- })
9546
+ }
9487
9547
  },
9488
9548
  children: /* @__PURE__ */ jsx("span", { ...textAttrs(ctx, `${path}.c${i}.label`), children: str(cs.label) })
9489
9549
  },
@@ -9527,7 +9587,7 @@ function AiTreeRenderer({
9527
9587
  }
9528
9588
  const resolvedBrand = withThemeFonts(brand ?? AI_DEFAULT_BRAND);
9529
9589
  const band = (tree.settings ?? {}).sectionBackground === "accent" ? accentBandContext(resolvedBrand) : null;
9530
- const blockBrand = band?.brand ?? resolvedBrand;
9590
+ const blockBrand = withLivePalette(band?.brand ?? resolvedBrand, resolvedBrand);
9531
9591
  const placeholderMap = buildPlaceholderMap(tree);
9532
9592
  const hasOverlapBackground = tree.scaffold === "overlay" && tree.rows.some(
9533
9593
  (row) => (row.blocks ?? []).some((b) => b.type === "group" && str(b.slots?.layout) === "overlap")
@@ -9543,7 +9603,7 @@ function AiTreeRenderer({
9543
9603
  // standalone resolve), so generated galleries, image rows, and overlay backgrounds arrive with
9544
9604
  // photos instead of grey boxes.
9545
9605
  resolveMedia: (ref) => resolveMedia?.(ref) ?? placeholderMap[ref] ?? resolvePlaceholderRef(ref),
9546
- cardSurface: blockBrand.palette.light.toUpperCase() === AI_DEFAULT_BRAND.palette.light.toUpperCase() ? "#ECECEB" : `color-mix(in srgb, ${blockBrand.palette.light} 90%, ${blockBrand.palette.dark})`,
9606
+ cardSurface: resolvedBrand.palette.light.toUpperCase() === AI_DEFAULT_BRAND.palette.light.toUpperCase() ? "#ECECEB" : `color-mix(in srgb, ${blockBrand.palette.light} 90%, ${blockBrand.palette.dark})`,
9547
9607
  keyFor: editKeyPrefix ? (path) => `${editKeyPrefix}.${path}` : null,
9548
9608
  sectionAlignment: (tree.settings ?? {}).alignment === "center" ? "center" : "left",
9549
9609
  buttonStyle,
@@ -9560,7 +9620,7 @@ function AiTreeRenderer({
9560
9620
  const backgroundUrl = isOverlay && settings.backgroundMedia ? ctx.resolveMedia(settings.backgroundMedia) : null;
9561
9621
  const bgAttrs = ctx.keyFor && isOverlay && settings.backgroundMedia ? { "data-ohw-key": ctx.keyFor("settings.backgroundMedia"), "data-ohw-editable": "bg-image" } : {};
9562
9622
  const toneBackground = (() => {
9563
- const { dark, primary, light } = resolvedBrand.palette;
9623
+ const { dark, primary, light } = blockBrand.palette;
9564
9624
  switch (settings.sectionBackground) {
9565
9625
  case "surface":
9566
9626
  return `color-mix(in srgb, ${light} 94%, ${dark})`;
@@ -9681,14 +9741,15 @@ function AiFooterRenderer({
9681
9741
  if (!isRenderableFooterTree(tree)) {
9682
9742
  return null;
9683
9743
  }
9684
- const resolvedBrand = withThemeFonts(brand ?? AI_DEFAULT_BRAND);
9744
+ const hexBrand = withThemeFonts(brand ?? AI_DEFAULT_BRAND);
9745
+ const resolvedBrand = withLivePalette(hexBrand, brand ?? AI_DEFAULT_BRAND);
9685
9746
  const bandBg = surface?.background ?? resolvedBrand.palette.dark;
9686
9747
  const bandText = surface?.text ?? resolvedBrand.palette.light;
9687
9748
  const onDark = surface ? isDarkColor(surface.background) : true;
9688
9749
  const ctx = {
9689
9750
  brand: resolvedBrand,
9690
9751
  resolveMedia: (ref) => resolveMedia?.(ref) ?? resolvePlaceholderRef(ref),
9691
- cardSurface: resolvedBrand.palette.light.toUpperCase() === AI_DEFAULT_BRAND.palette.light.toUpperCase() ? "#ECECEB" : `color-mix(in srgb, ${resolvedBrand.palette.light} 90%, ${resolvedBrand.palette.dark})`,
9752
+ cardSurface: hexBrand.palette.light.toUpperCase() === AI_DEFAULT_BRAND.palette.light.toUpperCase() ? "#ECECEB" : `color-mix(in srgb, ${resolvedBrand.palette.light} 90%, ${resolvedBrand.palette.dark})`,
9692
9753
  keyFor: editKeyPrefix ? (path) => `${editKeyPrefix}.${path}` : null,
9693
9754
  logoUrl: logoUrl ?? null,
9694
9755
  resolvePageHref
@@ -10264,10 +10325,9 @@ function syncSoftRemovedGenerated() {
10264
10325
  }
10265
10326
  function applyAiSectionsToDom(state, options) {
10266
10327
  if (typeof document === "undefined") return;
10267
- const brandOverride = deriveBrandOverride();
10268
10328
  const templateBrand = deriveTemplateBrand();
10269
10329
  const templateButtonStyle = deriveTemplateButtonStyle();
10270
- const brandKey = brandOverride ? JSON.stringify(brandOverride) : "";
10330
+ const brandKey = JSON.stringify(getAppliedBrand() ?? deriveBrandOverride() ?? null);
10271
10331
  const pagePath = window.location.pathname;
10272
10332
  const pageSections = state.sections.filter((entry) => (entry.path ?? "/") === pagePath);
10273
10333
  const activeIds = new Set(pageSections.map((entry) => entry.id));
@@ -10310,7 +10370,7 @@ function applyAiSectionsToDom(state, options) {
10310
10370
  AiTreeRenderer,
10311
10371
  {
10312
10372
  tree: entry.tree,
10313
- brand: brandOverride ?? entry.brand ?? options?.brand ?? templateBrand ?? void 0,
10373
+ brand: brandForGenerated(deriveBrandOverride() ?? entry.brand ?? options?.brand ?? templateBrand),
10314
10374
  buttonStyle: templateButtonStyle,
10315
10375
  resolveMedia,
10316
10376
  editKeyPrefix: `ai.${entry.id}`
@@ -10476,7 +10536,7 @@ function applyAiFooterToDom(state, content) {
10476
10536
  const templateFooter = findTemplateFooter();
10477
10537
  captureTemplateFooterSurface(templateFooter);
10478
10538
  const surface = capturedSurface ?? null;
10479
- const brand = mountOptions.brand ?? deriveTemplateBrand();
10539
+ const brand = brandForGenerated(mountOptions.brand ?? deriveTemplateBrand());
10480
10540
  const logoUrl = resolveLogoFromContent(content, templateFooter);
10481
10541
  const serialized = JSON.stringify(state) + JSON.stringify(brand ?? null) + (logoUrl ?? "") + JSON.stringify(surface) + JSON.stringify(defaultLegal());
10482
10542
  if (mounted2 && mounted2.serialized === serialized && mounted2.container.isConnected) {
@@ -27195,7 +27255,7 @@ function OhhwellsBridge() {
27195
27255
  if (!subdomain) return {};
27196
27256
  return contentCache.get(subdomain) ?? {};
27197
27257
  };
27198
- const observeRoots = () => [document.querySelector("nav"), document.querySelector("footer")].filter(
27258
+ const observeRoots = () => [document.querySelector("nav"), getFooterRoot()].filter(
27199
27259
  (el) => Boolean(el)
27200
27260
  );
27201
27261
  const roots = observeRoots();
@@ -27666,7 +27726,7 @@ function OhhwellsBridge() {
27666
27726
  {
27667
27727
  const formEl = getFormElement(target);
27668
27728
  const onSuccessText = target.closest(`[${SUCCESS_TEXT_ATTR}]`);
27669
- if (formEl && formKeyOf(formEl) && !onSuccessText) {
27729
+ if (formEl && formKeyOf(formEl) && !onSuccessText && !isCtaButton(target)) {
27670
27730
  const fieldEl = getFieldWrapper(target);
27671
27731
  if (fieldEl) {
27672
27732
  const label = target.closest("label");
@@ -29559,6 +29619,7 @@ function OhhwellsBridge() {
29559
29619
  brandKitRef.current = value;
29560
29620
  applyBrandToDom(parseBrandKit(value));
29561
29621
  if (aiSectionsRef.current) applyAiSectionsToDom(parseAiSectionsState(aiSectionsRef.current));
29622
+ applyAiFooterFromContent(editContentRef.current);
29562
29623
  applyStylesToDom(parseStyleStore(stylesRef.current));
29563
29624
  postToParentRef.current({ type: "ow:change", nodes: [{ key: BRAND_KIT_KEY, text: value }] });
29564
29625
  postToParentRef.current({ type: "ow:ai-brand-applied", previous, value });
@@ -30931,7 +30992,7 @@ function OhhwellsBridge() {
30931
30992
  postToParent2({
30932
30993
  type: "ow:ready",
30933
30994
  version: "1",
30934
- bridgeVersion: "0.1.138",
30995
+ bridgeVersion: "0.1.139",
30935
30996
  path: pathname,
30936
30997
  nodes: collectEditableNodes(editContentRef.current),
30937
30998
  sections