@ohhwells/bridge 0.1.68-next.205 → 0.1.69-next.207

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
@@ -580,6 +580,9 @@ var AI_RESPONSIVE_CSS = [
580
580
  "@media (max-width: 640px) {",
581
581
  " [data-ai-responsive] [data-ai-grid] { grid-template-columns: 1fr !important; }",
582
582
  " [data-ai-responsive] [data-ai-columns] > * { grid-column: 1 / -1 !important; }",
583
+ // Group containers flatten to a column on phones; span placements come along for free.
584
+ " [data-ai-responsive] [data-ai-group] { display: flex !important; flex-direction: column !important; }",
585
+ " [data-ai-responsive] [data-ai-group] > * { grid-column: auto !important; }",
583
586
  " [data-ai-responsive] [data-ai-section-inner] { padding-left: 20px !important; padding-right: 20px !important; }",
584
587
  " [data-ai-responsive] { overflow-x: hidden; }",
585
588
  " [data-ai-responsive] img { max-width: 100%; }",
@@ -1182,7 +1185,24 @@ function CardBlock({ node, ctx, path }) {
1182
1185
  minWidth: 0
1183
1186
  },
1184
1187
  children: [
1185
- media && (horizontal ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { flex: 1, minWidth: 0, display: "flex", alignItems: "center", padding: mediaInset }, children: media }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1188
+ media && (horizontal ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1189
+ "div",
1190
+ {
1191
+ style: (
1192
+ // An icon hugs its glyph — flex:1 gave a 48px icon half the card and pushed the
1193
+ // text to the far side. Photos keep the half-and-half split. The inset has no
1194
+ // inner padding (the photo split absorbed that), so the icon carries its own gap.
1195
+ /^(lucide|simple):/.test(mediaRef) ? {
1196
+ flexShrink: 0,
1197
+ display: "flex",
1198
+ alignItems: "center",
1199
+ padding: mediaInset,
1200
+ [mediaPosition === "right" ? "marginLeft" : "marginRight"]: 20
1201
+ } : { flex: 1, minWidth: 0, display: "flex", alignItems: "center", padding: mediaInset }
1202
+ ),
1203
+ children: media
1204
+ }
1205
+ ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1186
1206
  "div",
1187
1207
  {
1188
1208
  style: /^(lucide|simple):/.test(mediaRef) ? { padding: `${AI_TREE_TOKENS.paddingBlock}px ${AI_TREE_TOKENS.paddingBlock}px 0` } : hasBg ? void 0 : { borderRadius: AI_TREE_TOKENS.radiusCard, overflow: "hidden" },
@@ -1446,6 +1466,49 @@ function renderNode(node, ctx, path) {
1446
1466
  switch (node.type) {
1447
1467
  case "text":
1448
1468
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TextBlock, { slots, ctx, path });
1469
+ // Layout container: arranges child blocks, contributes no content of its own. `grid` is a
1470
+ // nested 12-column grid the children span (a collage is 3–5 media on it, bottom-aligned so
1471
+ // mixed aspects read as a composition); `split` is exactly two children at a ratio; `stack`
1472
+ // is a column. Children render through this same dispatcher, so edit markers, media
1473
+ // resolution, and copy paths all work unchanged inside a group.
1474
+ case "group": {
1475
+ const layout = str(slots.layout);
1476
+ const gap = slots.spacing === "tight" ? AI_TREE_TOKENS.spacing3 : slots.spacing === "airy" ? AI_TREE_TOKENS.spacing8 : AI_TREE_TOKENS.spacing6;
1477
+ const kids = (node.children ?? []).map((child, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1478
+ "div",
1479
+ {
1480
+ style: layout === "grid" ? {
1481
+ gridColumn: `span ${typeof child.span === "number" ? Math.min(12, Math.max(1, child.span)) : 12}`,
1482
+ minWidth: 0
1483
+ } : { minWidth: 0 },
1484
+ children: renderNode(child, ctx, `${path}.c${i}`)
1485
+ },
1486
+ i
1487
+ ));
1488
+ if (layout === "grid") {
1489
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1490
+ "div",
1491
+ {
1492
+ "data-ai-group": "grid",
1493
+ style: { display: "grid", gridTemplateColumns: "repeat(12, 1fr)", gap, alignItems: "end" },
1494
+ children: kids
1495
+ }
1496
+ );
1497
+ }
1498
+ if (layout === "split") {
1499
+ const ratio = str(slots.ratio);
1500
+ const cols = ratio === "3:5" ? "3fr 5fr" : ratio === "5:3" ? "5fr 3fr" : "1fr 1fr";
1501
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1502
+ "div",
1503
+ {
1504
+ "data-ai-group": "split",
1505
+ style: { display: "grid", gridTemplateColumns: cols, gap, alignItems: "center" },
1506
+ children: kids
1507
+ }
1508
+ );
1509
+ }
1510
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "data-ai-group": "stack", style: { display: "flex", flexDirection: "column", gap }, children: kids });
1511
+ }
1449
1512
  case "button":
1450
1513
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ButtonEl, { slots, ctx, path });
1451
1514
  case "button-row":
@@ -1820,7 +1883,7 @@ function applyAiSectionsToDom(state, options) {
1820
1883
  const templateBrand = deriveTemplateBrand();
1821
1884
  const brandKey = brandOverride ? JSON.stringify(brandOverride) : "";
1822
1885
  const pagePath = window.location.pathname;
1823
- const pageSections = state.sections.filter((entry) => !entry.path || entry.path === pagePath);
1886
+ const pageSections = state.sections.filter((entry) => (entry.path ?? "/") === pagePath);
1824
1887
  const activeIds = new Set(pageSections.map((entry) => entry.id));
1825
1888
  for (const [id, section] of mounted) {
1826
1889
  if (!activeIds.has(id)) {
@@ -7698,7 +7761,7 @@ function MediaOverlay({
7698
7761
  },
7699
7762
  children: [
7700
7763
  isVideo ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react5.Film, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react5.ImageIcon, { size: 14 }),
7701
- isVideo ? "Replace video" : "Replace image"
7764
+ isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image"
7702
7765
  ]
7703
7766
  }
7704
7767
  )
@@ -10777,8 +10840,13 @@ var GLYPH_SELECTOR = "svg, img";
10777
10840
  function referenceBox(slot) {
10778
10841
  const row = slot.closest("[data-ohw-socials-row]") ?? slot.closest("a")?.parentElement ?? null;
10779
10842
  const neighbour = row ? Array.from(row.querySelectorAll('[data-ohw-editable="icon"]')).find((el) => el !== slot) : null;
10780
- const source = (neighbour ?? slot).querySelector(GLYPH_SELECTOR);
10781
- const box = source?.getBoundingClientRect() ?? null;
10843
+ if (neighbour) {
10844
+ const box2 = neighbour.querySelector(GLYPH_SELECTOR)?.getBoundingClientRect();
10845
+ if (box2?.width && box2.height) return box2;
10846
+ }
10847
+ const own = slot.getBoundingClientRect();
10848
+ if (own.width && own.height) return own;
10849
+ const box = slot.querySelector(GLYPH_SELECTOR)?.getBoundingClientRect() ?? null;
10782
10850
  return box?.width && box.height ? box : null;
10783
10851
  }
10784
10852
  function iconMarkupSizedFor(slot, markup) {
@@ -15603,7 +15671,7 @@ function OhhwellsBridge() {
15603
15671
  key: el.dataset.ohwKey ?? null,
15604
15672
  // Display name for the pill — the raw key prettifies into fragments ("Img"); the
15605
15673
  // bridge knows what the node IS, so it names it.
15606
- keyLabel: el.dataset.ohwEditable === "video" ? "Video" : "Image"
15674
+ keyLabel: el.dataset.ohwEditable === "video" ? "Video" : el.dataset.ohwEditable === "bg-image" ? "Background" : "Image"
15607
15675
  });
15608
15676
  }, []);
15609
15677
  const selectMediaElementRef = (0, import_react17.useRef)(selectMediaElement);
@@ -19200,6 +19268,8 @@ function OhhwellsBridge() {
19200
19268
  if (video && video.src !== val) applyVideoSrc(video, val);
19201
19269
  } else if (el.dataset.ohwEditable === "link") {
19202
19270
  applyLinkHref(el, val);
19271
+ } else if (el.dataset.ohwEditable === "icon") {
19272
+ applyIconMarkup(el, val);
19203
19273
  } else {
19204
19274
  el.innerHTML = val;
19205
19275
  }
@@ -19408,6 +19478,7 @@ function OhhwellsBridge() {
19408
19478
  }
19409
19479
  deselectRef.current();
19410
19480
  deactivateRef.current();
19481
+ clearMediaSelectionRef.current();
19411
19482
  };
19412
19483
  window.addEventListener("message", handleDeactivate);
19413
19484
  const handleToastAction = (e) => {
@@ -20283,7 +20354,7 @@ function OhhwellsBridge() {
20283
20354
  postToParent2({
20284
20355
  type: "ow:ready",
20285
20356
  version: "1",
20286
- bridgeVersion: "0.1.68",
20357
+ bridgeVersion: "0.1.69",
20287
20358
  path: pathname,
20288
20359
  nodes: collectEditableNodes(editContentRef.current),
20289
20360
  sections