@ohhwells/bridge 0.1.59-next.170 → 0.1.60-next.171

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
@@ -170,6 +170,7 @@ function applyTreeToState(state, payload) {
170
170
  const entry = {
171
171
  id: payload.id,
172
172
  label: payload.label ?? "Generated section",
173
+ ...typeof payload.path === "string" && payload.path ? { path: payload.path } : {},
173
174
  afterSection: payload.mode === "insert" && !insertBefore ? payload.targetSectionId ?? null : null,
174
175
  ...insertBefore && payload.targetSectionId ? { beforeSection: payload.targetSectionId } : {},
175
176
  ...payload.mode === "replace" && payload.targetSectionId ? { replaces: payload.targetSectionId } : {},
@@ -315,6 +316,149 @@ function applyBrandToDom(kit) {
315
316
  loadBrandFonts([familyOf(kit.fonts.heading), familyOf(kit.fonts.body)]);
316
317
  }
317
318
 
319
+ // src/lib/section-styles.ts
320
+ var STYLE_STORE_KEY = "__ohw_styles";
321
+ var STYLE_SHEET_ID = "ohw-section-styles";
322
+ function parseStyleStore(raw) {
323
+ if (!raw) return null;
324
+ try {
325
+ const parsed = JSON.parse(raw);
326
+ if (parsed?.v !== 1) return null;
327
+ return {
328
+ v: 1,
329
+ sections: typeof parsed.sections === "object" && parsed.sections ? parsed.sections : {},
330
+ nodes: typeof parsed.nodes === "object" && parsed.nodes ? parsed.nodes : {}
331
+ };
332
+ } catch {
333
+ return null;
334
+ }
335
+ }
336
+ var BG_VALUES = {
337
+ surface: "color-mix(in srgb, var(--ohw-brand-light, var(--color-light, #ECECEB)) 94%, var(--ohw-brand-dark, var(--color-dark, #0C0A09)))",
338
+ accent: "var(--ohw-brand-primary, var(--color-primary, #0078E5))",
339
+ "accent-soft": "color-mix(in srgb, var(--ohw-brand-primary, var(--color-primary, #0078E5)) 12%, var(--ohw-brand-light, var(--color-light, #FFFFFF)))"
340
+ };
341
+ var HEADLINE_SIZES = { md: 24, lg: 32, xl: 40, display: 56 };
342
+ function styleSheetCss() {
343
+ const rules = [];
344
+ for (const [tone, value] of Object.entries(BG_VALUES)) {
345
+ rules.push(`[data-ohw-style-bg="${tone}"] { background: ${value} !important; }`);
346
+ }
347
+ rules.push(
348
+ `[data-ohw-style-bg="accent"] { color: var(--ohw-brand-light, var(--color-light, #FFFFFF)) !important; }`
349
+ );
350
+ rules.push(
351
+ `[data-ohw-style-distribution="space-between"] { display: flex !important; flex-direction: column; justify-content: space-between; }`,
352
+ `[data-ohw-style-distribution="center"] { display: flex !important; flex-direction: column; justify-content: center; }`
353
+ );
354
+ for (const [scale, size] of Object.entries(HEADLINE_SIZES)) {
355
+ rules.push(
356
+ `[data-ohw-style-headline="${scale}"] :is(h1, h2, h3) { font-size: ${size}px !important; line-height: 1.15 !important; }`
357
+ );
358
+ }
359
+ rules.push(
360
+ `[data-ohw-style-aspect="fill-height"] img { height: 100% !important; aspect-ratio: auto !important; object-fit: cover; }`
361
+ );
362
+ for (const aspect of ["1:1", "4:5", "3:4", "3:2", "16:9", "2:1", "3:1"]) {
363
+ rules.push(
364
+ `[data-ohw-style-aspect="${aspect.replace(":", "-")}"] img { aspect-ratio: ${aspect.replace(":", " / ")} !important; height: auto !important; object-fit: cover; }`
365
+ );
366
+ }
367
+ const pad = { tight: 40, balanced: 64, airy: 96 };
368
+ for (const [spacing, px] of Object.entries(pad)) {
369
+ rules.push(
370
+ `[data-ohw-style-spacing="${spacing}"] { padding-top: ${px}px !important; padding-bottom: ${px}px !important; }`
371
+ );
372
+ }
373
+ return rules.join("\n");
374
+ }
375
+ var SECTION_ATTRS = {
376
+ sectionBackground: "data-ohw-style-bg",
377
+ textDistribution: "data-ohw-style-distribution",
378
+ headlineScale: "data-ohw-style-headline",
379
+ imageAspect: "data-ohw-style-aspect",
380
+ spacing: "data-ohw-style-spacing"
381
+ };
382
+ var NODE_WROTE_ATTR = "data-ohw-style-node";
383
+ function ensureStyleSheet() {
384
+ let el = document.getElementById(STYLE_SHEET_ID);
385
+ if (!el) {
386
+ el = document.createElement("style");
387
+ el.id = STYLE_SHEET_ID;
388
+ document.head.appendChild(el);
389
+ }
390
+ const css = styleSheetCss();
391
+ if (el.textContent !== css) el.textContent = css;
392
+ }
393
+ function clearSectionAttrs(root) {
394
+ for (const attr of Object.values(SECTION_ATTRS)) {
395
+ for (const el of Array.from(root.querySelectorAll(`[${attr}]`))) el.removeAttribute(attr);
396
+ }
397
+ for (const el of Array.from(root.querySelectorAll("[data-ohw-style-bgcolor]"))) {
398
+ ;
399
+ el.style.removeProperty("background");
400
+ el.removeAttribute("data-ohw-style-bgcolor");
401
+ }
402
+ }
403
+ function clearNodeProps(root) {
404
+ for (const el of Array.from(root.querySelectorAll(`[${NODE_WROTE_ATTR}]`))) {
405
+ const h = el;
406
+ h.style.removeProperty("color");
407
+ h.style.removeProperty("font-size");
408
+ h.style.removeProperty("background");
409
+ h.removeAttribute(NODE_WROTE_ATTR);
410
+ }
411
+ }
412
+ function buttonSurfaceOf(el) {
413
+ return el.closest("a, button") ?? el;
414
+ }
415
+ function applyStylesToDom(store) {
416
+ ensureStyleSheet();
417
+ clearSectionAttrs(document);
418
+ clearNodeProps(document);
419
+ if (!store) return;
420
+ for (const [sectionId, override] of Object.entries(store.sections)) {
421
+ const sections = document.querySelectorAll(
422
+ `[data-ohw-section="${CSS.escape(sectionId)}"]`
423
+ );
424
+ for (const section of Array.from(sections)) {
425
+ for (const [prop, attr] of Object.entries(SECTION_ATTRS)) {
426
+ const value = override[prop];
427
+ if (value === void 0) continue;
428
+ if (prop === "sectionBackground" && override.sectionBackgroundColor !== void 0) continue;
429
+ section.setAttribute(attr, String(value).replace(":", "-"));
430
+ }
431
+ if (override.sectionBackgroundColor !== void 0) {
432
+ section.style.setProperty("background", override.sectionBackgroundColor, "important");
433
+ section.setAttribute("data-ohw-style-bgcolor", "");
434
+ }
435
+ }
436
+ }
437
+ for (const [key, override] of Object.entries(store.nodes)) {
438
+ const nodes = document.querySelectorAll(`[data-ohw-key="${CSS.escape(key)}"]`);
439
+ for (const el of Array.from(nodes)) {
440
+ if (override.color !== void 0) {
441
+ el.style.setProperty("color", override.color, "important");
442
+ el.setAttribute(NODE_WROTE_ATTR, "");
443
+ }
444
+ if (override.fontSize !== void 0) {
445
+ el.style.setProperty("font-size", `${override.fontSize}px`, "important");
446
+ el.setAttribute(NODE_WROTE_ATTR, "");
447
+ }
448
+ if (override.buttonBackground !== void 0 || override.buttonText !== void 0) {
449
+ const surface = buttonSurfaceOf(el);
450
+ if (override.buttonBackground !== void 0) {
451
+ surface.style.setProperty("background", override.buttonBackground, "important");
452
+ }
453
+ if (override.buttonText !== void 0) {
454
+ surface.style.setProperty("color", override.buttonText, "important");
455
+ }
456
+ surface.setAttribute(NODE_WROTE_ATTR, "");
457
+ }
458
+ }
459
+ }
460
+ }
461
+
318
462
  // src/ui/ai-tree/aiSectionsManager.tsx
319
463
  var import_react_dom = require("react-dom");
320
464
  var import_client = require("react-dom/client");
@@ -329,7 +473,8 @@ function lucideByName(name) {
329
473
  }
330
474
  var typeStyle = (spec, font) => ({
331
475
  fontFamily: font,
332
- fontSize: spec.size,
476
+ // Headings shrink with the viewport (reaching full size around ~900px wide); body copy stays put.
477
+ fontSize: spec.size >= 24 ? `clamp(${Math.max(18, Math.round(spec.size * 0.6))}px, ${(spec.size / 9).toFixed(2)}vw, ${spec.size}px)` : spec.size,
333
478
  lineHeight: spec.line,
334
479
  fontWeight: spec.weight
335
480
  });
@@ -356,6 +501,8 @@ var AI_RESPONSIVE_CSS = [
356
501
  " [data-ai-responsive] [data-ai-grid] { grid-template-columns: 1fr !important; }",
357
502
  " [data-ai-responsive] [data-ai-columns] > * { grid-column: 1 / -1 !important; }",
358
503
  " [data-ai-responsive] [data-ai-section-inner] { padding-left: 20px !important; padding-right: 20px !important; }",
504
+ " [data-ai-responsive] { overflow-x: hidden; }",
505
+ " [data-ai-responsive] img { max-width: 100%; }",
359
506
  "}"
360
507
  ].join("\n");
361
508
  var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
@@ -1363,6 +1510,20 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1363
1510
  const overlayAlpha = { low: 0.25, medium: 0.45, high: 0.65 }[settings.overlayIntensity ?? "medium"];
1364
1511
  const backgroundUrl = isOverlay && settings.backgroundMedia ? ctx.resolveMedia(settings.backgroundMedia) : null;
1365
1512
  const bgAttrs = ctx.keyFor && isOverlay && settings.backgroundMedia ? { "data-ohw-key": ctx.keyFor("settings.backgroundMedia"), "data-ohw-editable": "bg-image" } : {};
1513
+ const toneBackground = (() => {
1514
+ const { dark, primary, light } = resolvedBrand.palette;
1515
+ switch (settings.sectionBackground) {
1516
+ case "surface":
1517
+ return `color-mix(in srgb, ${light} 94%, ${dark})`;
1518
+ case "accent":
1519
+ return primary;
1520
+ case "accent-soft":
1521
+ return `color-mix(in srgb, ${primary} 12%, ${light})`;
1522
+ default:
1523
+ return void 0;
1524
+ }
1525
+ })();
1526
+ const distributed = !isOverlay && settings.textDistribution;
1366
1527
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1367
1528
  "section",
1368
1529
  {
@@ -1372,10 +1533,11 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1372
1533
  style: {
1373
1534
  position: "relative",
1374
1535
  padding: `${pad}px 0`,
1375
- background: isOverlay && !backgroundUrl ? "#DBEAFE" : void 0,
1536
+ background: toneBackground ?? (isOverlay && !backgroundUrl ? "#DBEAFE" : void 0),
1376
1537
  backgroundImage: backgroundUrl ? `url(${backgroundUrl})` : void 0,
1377
1538
  backgroundSize: "cover",
1378
- backgroundPosition: "center"
1539
+ backgroundPosition: "center",
1540
+ color: settings.sectionBackground === "accent" ? resolvedBrand.palette.light : void 0
1379
1541
  },
1380
1542
  children: [
1381
1543
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_RESPONSIVE_CSS }),
@@ -1399,10 +1561,24 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1399
1561
  display: "grid",
1400
1562
  gridTemplateColumns: "repeat(12, 1fr)",
1401
1563
  gap: AI_TREE_TOKENS.spacing6,
1402
- alignItems: settings.verticalPosition === "top" ? "start" : "center",
1564
+ alignItems: distributed === "space-between" ? "stretch" : (distributed ?? settings.verticalPosition) === "top" ? "start" : "center",
1403
1565
  marginTop: r2 > 0 ? AI_TREE_TOKENS.spacing8 : 0
1404
1566
  },
1405
- children: (row.blocks ?? []).map((block, b) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { gridColumn: `span ${Math.min(12, Math.max(1, block.span))}`, minWidth: 0 }, children: renderNode(block, ctx, `r${r2}.b${b}`) }, b))
1567
+ children: (row.blocks ?? []).map((block, b) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1568
+ "div",
1569
+ {
1570
+ "data-ai-cell": "",
1571
+ style: {
1572
+ gridColumn: `span ${Math.min(12, Math.max(1, block.span))}`,
1573
+ minWidth: 0,
1574
+ // space-between: each column becomes a flex column whose content spreads over
1575
+ // the full row height instead of clumping at the top.
1576
+ ...distributed === "space-between" ? { display: "flex", flexDirection: "column", justifyContent: "space-between" } : {}
1577
+ },
1578
+ children: renderNode(block, ctx, `r${r2}.b${b}`)
1579
+ },
1580
+ b
1581
+ ))
1406
1582
  },
1407
1583
  r2
1408
1584
  ))
@@ -1540,7 +1716,9 @@ function applyAiSectionsToDom(state, options) {
1540
1716
  const brandOverride = deriveBrandOverride();
1541
1717
  const templateBrand = deriveTemplateBrand();
1542
1718
  const brandKey = brandOverride ? JSON.stringify(brandOverride) : "";
1543
- const activeIds = new Set(state.sections.map((entry) => entry.id));
1719
+ const pagePath = window.location.pathname;
1720
+ const pageSections = state.sections.filter((entry) => !entry.path || entry.path === pagePath);
1721
+ const activeIds = new Set(pageSections.map((entry) => entry.id));
1544
1722
  for (const [id, section] of mounted) {
1545
1723
  if (!activeIds.has(id)) {
1546
1724
  section.root.unmount();
@@ -1548,7 +1726,7 @@ function applyAiSectionsToDom(state, options) {
1548
1726
  mounted.delete(id);
1549
1727
  }
1550
1728
  }
1551
- for (const entry of state.sections) {
1729
+ for (const entry of pageSections) {
1552
1730
  const serialized = JSON.stringify(entry) + brandKey;
1553
1731
  const existing = mounted.get(entry.id);
1554
1732
  if (existing && existing.serialized === serialized && existing.container.isConnected) {
@@ -14351,6 +14529,7 @@ function OhhwellsBridge() {
14351
14529
  const editContentRef = (0, import_react16.useRef)({});
14352
14530
  const aiSectionsRef = (0, import_react16.useRef)("");
14353
14531
  const brandKitRef = (0, import_react16.useRef)("");
14532
+ const stylesRef = (0, import_react16.useRef)("");
14354
14533
  const pendingDeleteUndoRef = (0, import_react16.useRef)(null);
14355
14534
  const [floatingPanel, setFloatingPanel] = (0, import_react16.useState)(null);
14356
14535
  const floatingPanelOpenRef = (0, import_react16.useRef)(false);
@@ -15614,6 +15793,10 @@ function OhhwellsBridge() {
15614
15793
  brandKitRef.current = content[BRAND_KIT_KEY];
15615
15794
  applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
15616
15795
  }
15796
+ if (typeof content[STYLE_STORE_KEY] === "string") {
15797
+ stylesRef.current = content[STYLE_STORE_KEY];
15798
+ applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
15799
+ }
15617
15800
  applyBrandChrome(content);
15618
15801
  for (const [key, val] of Object.entries(content)) {
15619
15802
  if (key === "__ohw_sections") continue;
@@ -15621,6 +15804,7 @@ function OhhwellsBridge() {
15621
15804
  if (key === LOGO_PLACEHOLDER_KEY) continue;
15622
15805
  if (LOGO_IMAGE_KEYS.includes(key)) continue;
15623
15806
  if (key === BRAND_KIT_KEY) continue;
15807
+ if (key === STYLE_STORE_KEY) continue;
15624
15808
  if (BRAND_CHROME_KEYS.has(key)) continue;
15625
15809
  if (applyVideoSettingNode(key, val)) continue;
15626
15810
  if (applyCarouselNode(key, val)) continue;
@@ -15708,11 +15892,16 @@ function OhhwellsBridge() {
15708
15892
  if (typeof content[BRAND_KIT_KEY] === "string") {
15709
15893
  applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
15710
15894
  }
15895
+ if (typeof content[STYLE_STORE_KEY] === "string") {
15896
+ applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
15897
+ }
15711
15898
  for (const [key, val] of Object.entries(content)) {
15712
15899
  if (key === "__ohw_sections") continue;
15713
15900
  if (key === LOGO_PLACEHOLDER_KEY) continue;
15714
15901
  if (LOGO_IMAGE_KEYS.includes(key)) continue;
15715
15902
  if (key === BRAND_KIT_KEY) continue;
15903
+ if (key === STYLE_STORE_KEY) continue;
15904
+ if (key === STYLE_STORE_KEY) continue;
15716
15905
  if (BRAND_CHROME_KEYS.has(key)) continue;
15717
15906
  if (applyVideoSettingNode(key, val)) continue;
15718
15907
  if (applyCarouselNode(key, val)) continue;
@@ -17289,6 +17478,10 @@ function OhhwellsBridge() {
17289
17478
  brandKitRef.current = content[BRAND_KIT_KEY];
17290
17479
  applyBrandToDom(parseBrandKit(content[BRAND_KIT_KEY]));
17291
17480
  }
17481
+ if (typeof content[STYLE_STORE_KEY] === "string") {
17482
+ stylesRef.current = content[STYLE_STORE_KEY];
17483
+ applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
17484
+ }
17292
17485
  applyBrandChrome(content);
17293
17486
  let sectionsJson = null;
17294
17487
  for (const [key, val] of Object.entries(content)) {
@@ -17300,6 +17493,7 @@ function OhhwellsBridge() {
17300
17493
  if (key === LOGO_PLACEHOLDER_KEY) continue;
17301
17494
  if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
17302
17495
  if (key === BRAND_KIT_KEY) continue;
17496
+ if (key === STYLE_STORE_KEY) continue;
17303
17497
  if (BRAND_CHROME_KEYS.has(key)) continue;
17304
17498
  if (applyVideoSettingNode(key, val)) continue;
17305
17499
  if (applyCarouselNode(key, val)) continue;
@@ -17402,7 +17596,10 @@ function OhhwellsBridge() {
17402
17596
  const payload = e.data.payload;
17403
17597
  if (!payload || typeof payload.id !== "string" || !isRenderableTree(payload.tree)) return;
17404
17598
  const previous = aiSectionsRef.current;
17405
- const nextState = applyTreeToState(parseAiSectionsState(previous), payload);
17599
+ const nextState = applyTreeToState(parseAiSectionsState(previous), {
17600
+ ...payload,
17601
+ path: payload.path ?? window.location.pathname
17602
+ });
17406
17603
  const nextValue = serializeAiSectionsState(nextState);
17407
17604
  aiSectionsRef.current = nextValue;
17408
17605
  applyAiSectionsToDom(nextState);
@@ -17439,6 +17636,7 @@ function OhhwellsBridge() {
17439
17636
  const value = typeof e.data.value === "string" ? e.data.value : "";
17440
17637
  aiSectionsRef.current = value;
17441
17638
  applyAiSectionsToDom(parseAiSectionsState(value));
17639
+ applyStylesToDom(parseStyleStore(stylesRef.current));
17442
17640
  const restoredHeight = document.documentElement.scrollHeight;
17443
17641
  if (restoredHeight > 50) postToParentRef.current({ type: "ow:height", height: restoredHeight });
17444
17642
  postToParentRef.current({ type: "ow:change", nodes: [{ key: AI_SECTIONS_KEY, text: value }] });
@@ -17452,10 +17650,28 @@ function OhhwellsBridge() {
17452
17650
  brandKitRef.current = value;
17453
17651
  applyBrandToDom(parseBrandKit(value));
17454
17652
  if (aiSectionsRef.current) applyAiSectionsToDom(parseAiSectionsState(aiSectionsRef.current));
17653
+ applyStylesToDom(parseStyleStore(stylesRef.current));
17455
17654
  postToParentRef.current({ type: "ow:change", nodes: [{ key: BRAND_KIT_KEY, text: value }] });
17456
17655
  postToParentRef.current({ type: "ow:ai-brand-applied", previous, value });
17457
17656
  };
17458
17657
  window.addEventListener("message", handleAiSetBrand);
17658
+ const handleAiSetStyles = (e) => {
17659
+ if (e.data?.type !== "ow:ai-set-styles") return;
17660
+ const value = typeof e.data.value === "string" ? e.data.value : "";
17661
+ const previous = stylesRef.current;
17662
+ stylesRef.current = value;
17663
+ applyStylesToDom(parseStyleStore(value));
17664
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: STYLE_STORE_KEY, text: value }] });
17665
+ postToParentRef.current({ type: "ow:ai-styles-applied", previous, value });
17666
+ };
17667
+ window.addEventListener("message", handleAiSetStyles);
17668
+ const handleGetBrand = (e) => {
17669
+ if (e.data?.type !== "ow:get-brand") return;
17670
+ const template = deriveTemplateBrand();
17671
+ const value = brandKitRef.current || (template ? JSON.stringify(template) : "");
17672
+ postToParentRef.current({ type: "ow:brand-value", value });
17673
+ };
17674
+ window.addEventListener("message", handleGetBrand);
17459
17675
  const handleDeactivate = (e) => {
17460
17676
  if (e.data?.type !== "ow:deactivate") return;
17461
17677
  if (Date.now() < linkPopoverGraceUntilRef.current) return;
@@ -17704,6 +17920,9 @@ function OhhwellsBridge() {
17704
17920
  if (aiSectionsRef.current) {
17705
17921
  nodes.push({ key: AI_SECTIONS_KEY, type: "sections", text: aiSectionsRef.current });
17706
17922
  }
17923
+ if (stylesRef.current) {
17924
+ nodes.push({ key: STYLE_STORE_KEY, type: "styles", text: stylesRef.current });
17925
+ }
17707
17926
  if (brandKitRef.current) {
17708
17927
  nodes.push({ key: BRAND_KIT_KEY, type: "brand", text: brandKitRef.current });
17709
17928
  }
@@ -18110,6 +18329,8 @@ function OhhwellsBridge() {
18110
18329
  window.removeEventListener("message", handleAiDeleteSection);
18111
18330
  window.removeEventListener("message", handleAiSetSections);
18112
18331
  window.removeEventListener("message", handleAiSetBrand);
18332
+ window.removeEventListener("message", handleAiSetStyles);
18333
+ window.removeEventListener("message", handleGetBrand);
18113
18334
  window.removeEventListener("message", handleDeactivate);
18114
18335
  window.removeEventListener("message", handleToastAction);
18115
18336
  window.removeEventListener("message", handleUiEscape);
@@ -18313,7 +18534,7 @@ function OhhwellsBridge() {
18313
18534
  postToParent2({
18314
18535
  type: "ow:ready",
18315
18536
  version: "1",
18316
- bridgeVersion: "0.1.59",
18537
+ bridgeVersion: "0.1.60",
18317
18538
  path: pathname,
18318
18539
  nodes: collectEditableNodes(editContentRef.current),
18319
18540
  sections