@ohhwells/bridge 0.1.91 → 0.1.92

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
@@ -142,6 +142,7 @@ function isRenderableTree(value) {
142
142
 
143
143
  // src/lib/ai-sections-store.ts
144
144
  var AI_SECTIONS_KEY = "__ohw_ai_sections";
145
+ var AI_SLOT_KEY_PREFIX = "ai.";
145
146
  var EMPTY_AI_SECTIONS = { v: 1, sections: [] };
146
147
  function parseAiSectionsState(raw) {
147
148
  if (!raw) return EMPTY_AI_SECTIONS;
@@ -185,6 +186,63 @@ function applyTreeToState(state, payload) {
185
186
  const others = state.sections.filter((existing) => existing.id !== entry.id);
186
187
  return { ...state, v: 1, sections: [...others, entry] };
187
188
  }
189
+ function foldAlignIntoTrees(state, store) {
190
+ const byId = new Map(state.sections.map((entry) => [entry.id, entry]));
191
+ const nextTrees = /* @__PURE__ */ new Map();
192
+ const treeFor = (id) => {
193
+ const cloned = nextTrees.get(id);
194
+ if (cloned) return cloned;
195
+ const entry = byId.get(id);
196
+ if (!entry) return void 0;
197
+ const fresh = {
198
+ ...entry.tree,
199
+ rows: entry.tree.rows.map((row) => ({ ...row, blocks: row.blocks.map((block) => ({ ...block })) }))
200
+ };
201
+ nextTrees.set(id, fresh);
202
+ return fresh;
203
+ };
204
+ const nodes = {};
205
+ for (const [key, override] of Object.entries(store.nodes)) {
206
+ const match = override.align !== void 0 && key.startsWith(AI_SLOT_KEY_PREFIX) ? key.match(/^ai\.(.+?)\.r(\d+)\.b(\d+)(?:\.|$)/) : null;
207
+ const tree = match ? treeFor(match[1]) : void 0;
208
+ const block = match && tree ? tree.rows[Number(match[2])]?.blocks[Number(match[3])] : void 0;
209
+ if (!block) {
210
+ nodes[key] = override;
211
+ continue;
212
+ }
213
+ block.align = override.align;
214
+ const rest = { ...override };
215
+ delete rest.align;
216
+ if (Object.keys(rest).length > 0) nodes[key] = rest;
217
+ }
218
+ const sections = {};
219
+ for (const [sectionId, override] of Object.entries(store.sections)) {
220
+ const tree = override.align !== void 0 ? treeFor(sectionId) : void 0;
221
+ if (!tree) {
222
+ sections[sectionId] = override;
223
+ continue;
224
+ }
225
+ for (const row of tree.rows) {
226
+ for (const block of row.blocks) block.align = override.align;
227
+ }
228
+ const rest = { ...override };
229
+ delete rest.align;
230
+ if (Object.keys(rest).length > 0) sections[sectionId] = rest;
231
+ }
232
+ if (nextTrees.size === 0) return { state, store, changed: false };
233
+ return {
234
+ state: {
235
+ ...state,
236
+ v: 1,
237
+ sections: state.sections.map((entry) => {
238
+ const tree = nextTrees.get(entry.id);
239
+ return tree ? { ...entry, tree } : entry;
240
+ })
241
+ },
242
+ store: { v: 1, sections, nodes },
243
+ changed: true
244
+ };
245
+ }
188
246
  function removeFromState(state, id) {
189
247
  return { ...state, v: 1, sections: state.sections.filter((entry) => entry.id !== id) };
190
248
  }
@@ -403,6 +461,9 @@ function styleSheetCss() {
403
461
  `[data-ohw-style-spacing="${spacing}"] { padding-top: ${px}px !important; padding-bottom: ${px}px !important; }`
404
462
  );
405
463
  }
464
+ for (const align of ["left", "center", "right"]) {
465
+ rules.push(`[data-ohw-style-align="${align}"] { text-align: ${align} !important; }`);
466
+ }
406
467
  return rules.join("\n");
407
468
  }
408
469
  var STYLE_FONT_LINK_ID = "ohw-style-fonts";
@@ -429,10 +490,24 @@ var SECTION_ATTRS = {
429
490
  textDistribution: "data-ohw-style-distribution",
430
491
  headlineScale: "data-ohw-style-headline",
431
492
  imageAspect: "data-ohw-style-aspect",
432
- spacing: "data-ohw-style-spacing"
493
+ spacing: "data-ohw-style-spacing",
494
+ align: "data-ohw-style-align"
433
495
  };
434
496
  var NODE_WROTE_ATTR = "data-ohw-style-node";
435
- var NODE_PROPS = ["color", "font-family", "font-size", "background"];
497
+ var NODE_PROPS = [
498
+ "color",
499
+ "font-family",
500
+ "font-size",
501
+ "background",
502
+ "text-align",
503
+ "justify-content",
504
+ "align-items"
505
+ ];
506
+ var ALIGN_JUSTIFY = {
507
+ left: "flex-start",
508
+ center: "center",
509
+ right: "flex-end"
510
+ };
436
511
  function saveInline(el, prop) {
437
512
  const attr = `data-ohw-style-prev-${prop}`;
438
513
  if (el.hasAttribute(attr)) return;
@@ -476,6 +551,10 @@ function clearNodeProps(root) {
476
551
  function buttonSurfaceOf(el) {
477
552
  return el.closest("a, button") ?? el;
478
553
  }
554
+ function alignSubjectOf(el) {
555
+ const button = el.closest('[data-ohw-role="button"]');
556
+ return button?.parentElement ?? el;
557
+ }
479
558
  function applyStylesToDom(store) {
480
559
  ensureStyleSheet();
481
560
  clearSectionAttrs(document);
@@ -521,6 +600,18 @@ function applyStylesToDom(store) {
521
600
  el.style.setProperty("font-size", `${override.fontSize}px`, "important");
522
601
  el.setAttribute(NODE_WROTE_ATTR, "");
523
602
  }
603
+ if (override.align !== void 0) {
604
+ const subject = alignSubjectOf(el);
605
+ saveInline(subject, "text-align");
606
+ saveInline(subject, "justify-content");
607
+ subject.style.setProperty("text-align", override.align, "important");
608
+ subject.style.setProperty(
609
+ "justify-content",
610
+ ALIGN_JUSTIFY[override.align] ?? "flex-start",
611
+ "important"
612
+ );
613
+ subject.setAttribute(NODE_WROTE_ATTR, "");
614
+ }
524
615
  if (override.buttonBackground !== void 0 || override.buttonText !== void 0) {
525
616
  const surface = buttonSurfaceOf(el);
526
617
  if (override.buttonBackground !== void 0) {
@@ -729,7 +820,7 @@ function TextBlock({ slots, ctx, path }) {
729
820
  }
730
821
  function SectionHeaderBlock({ node, ctx, path }) {
731
822
  const slots = node.slots ?? {};
732
- const align = slots.alignment === "center" ? "center" : "left";
823
+ const align = node.align ?? (slots.alignment === "center" ? "center" : "left");
733
824
  const children = node.children ?? [];
734
825
  const buttonRowIdx = children.findIndex((c) => c.type === "button-row");
735
826
  const buttonRow = buttonRowIdx >= 0 ? children[buttonRowIdx] : void 0;
@@ -773,7 +864,7 @@ function SectionHeaderBlock({ node, ctx, path }) {
773
864
  display: "flex",
774
865
  gap: AI_TREE_TOKENS.spacing6,
775
866
  marginTop: AI_TREE_TOKENS.spacing8,
776
- justifyContent: align === "center" ? "center" : "flex-start"
867
+ justifyContent: align === "center" ? "center" : align === "right" ? "flex-end" : "flex-start"
777
868
  },
778
869
  children: (buttonRow.children ?? []).map((button, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
779
870
  ButtonEl,
@@ -1106,7 +1197,7 @@ function CardBlock({ node, ctx, path }) {
1106
1197
  editPath: `${path}.media`
1107
1198
  }
1108
1199
  ) : null;
1109
- const centered = slots.alignment === "center";
1200
+ const centered = (node.align ?? slots.alignment) === "center";
1110
1201
  const content = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1111
1202
  "div",
1112
1203
  {
@@ -1825,6 +1916,19 @@ function AiTreeRenderer({
1825
1916
  }
1826
1917
  })();
1827
1918
  const distributed = !isOverlay && settings.textDistribution;
1919
+ const rowAlignItems = (rowAlign) => {
1920
+ if (rowAlign === "top") return "start";
1921
+ if (rowAlign === "bottom") return "end";
1922
+ if (rowAlign === "center" || rowAlign === "stretch") return rowAlign;
1923
+ if (distributed === "space-between") return "stretch";
1924
+ return (distributed ?? settings.verticalPosition) === "top" ? "start" : "center";
1925
+ };
1926
+ const cellAlignStyle = (blockAlign) => blockAlign ? {
1927
+ display: "flex",
1928
+ flexDirection: "column",
1929
+ alignItems: blockAlign === "left" ? "flex-start" : blockAlign === "right" ? "flex-end" : "center",
1930
+ textAlign: blockAlign
1931
+ } : {};
1828
1932
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1829
1933
  "section",
1830
1934
  {
@@ -1861,7 +1965,7 @@ function AiTreeRenderer({
1861
1965
  display: "grid",
1862
1966
  gridTemplateColumns: "repeat(12, 1fr)",
1863
1967
  gap: AI_TREE_TOKENS.spacing6,
1864
- alignItems: distributed === "space-between" ? "stretch" : (distributed ?? settings.verticalPosition) === "top" ? "start" : "center",
1968
+ alignItems: rowAlignItems(row.align),
1865
1969
  marginTop: r2 > 0 ? AI_TREE_TOKENS.spacing8 : 0
1866
1970
  },
1867
1971
  children: (row.blocks ?? []).map((block, b) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -1871,6 +1975,8 @@ function AiTreeRenderer({
1871
1975
  style: {
1872
1976
  gridColumn: `span ${Math.min(12, Math.max(1, block.span))}`,
1873
1977
  minWidth: 0,
1978
+ // Horizontal placement of the block's content within its column.
1979
+ ...cellAlignStyle(block.align),
1874
1980
  // space-between: each column becomes a flex column whose content spreads over
1875
1981
  // the full row height instead of clumping at the top.
1876
1982
  ...distributed === "space-between" ? { display: "flex", flexDirection: "column", justifyContent: "space-between" } : {}
@@ -20324,12 +20430,35 @@ function OhhwellsBridge() {
20324
20430
  window.addEventListener("message", handleAiSetBrand);
20325
20431
  const handleAiSetStyles = (e) => {
20326
20432
  if (e.data?.type !== "ow:ai-set-styles") return;
20327
- const value = typeof e.data.value === "string" ? e.data.value : "";
20433
+ let value = typeof e.data.value === "string" ? e.data.value : "";
20328
20434
  const previous = stylesRef.current;
20435
+ let previousSections;
20436
+ const store = parseStyleStore(value);
20437
+ if (store) {
20438
+ const folded = foldAlignIntoTrees(parseAiSectionsState(aiSectionsRef.current), store);
20439
+ if (folded.changed) {
20440
+ const nextSections = serializeAiSectionsState(folded.state);
20441
+ if (nextSections !== aiSectionsRef.current) {
20442
+ previousSections = aiSectionsRef.current;
20443
+ aiSectionsRef.current = nextSections;
20444
+ applyAiSectionsToDom(folded.state);
20445
+ postToParentRef.current({
20446
+ type: "ow:change",
20447
+ nodes: [{ key: AI_SECTIONS_KEY, text: nextSections }]
20448
+ });
20449
+ }
20450
+ value = JSON.stringify(folded.store);
20451
+ }
20452
+ }
20329
20453
  stylesRef.current = value;
20330
20454
  applyStylesToDom(parseStyleStore(value));
20331
20455
  postToParentRef.current({ type: "ow:change", nodes: [{ key: STYLE_STORE_KEY, text: value }] });
20332
- postToParentRef.current({ type: "ow:ai-styles-applied", previous, value });
20456
+ postToParentRef.current({
20457
+ type: "ow:ai-styles-applied",
20458
+ previous,
20459
+ value,
20460
+ ...previousSections !== void 0 ? { previousSections } : {}
20461
+ });
20333
20462
  };
20334
20463
  window.addEventListener("message", handleAiSetStyles);
20335
20464
  const handleGetBrand = (e) => {
@@ -21350,7 +21479,7 @@ function OhhwellsBridge() {
21350
21479
  postToParent2({
21351
21480
  type: "ow:ready",
21352
21481
  version: "1",
21353
- bridgeVersion: "0.1.90",
21482
+ bridgeVersion: "0.1.91",
21354
21483
  path: pathname,
21355
21484
  nodes: collectEditableNodes(editContentRef.current),
21356
21485
  sections