@ohhwells/bridge 0.1.145 → 0.1.147

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.d.cts CHANGED
@@ -28,6 +28,14 @@ interface AiBlockNode {
28
28
  align?: 'left' | 'center' | 'right';
29
29
  slots?: Record<string, unknown>;
30
30
  children?: AiBlockNode[];
31
+ /** Stable id for ordered insertion. Omitted on trees saved before slots. */
32
+ ref?: string;
33
+ /** Fixed slot inside a split (`left`/`right`) or grid (`cell-N`). Omit to keep index order. */
34
+ slot?: string;
35
+ /** Unfilled fixed slots on a group. The published page draws none of these. */
36
+ emptySlots?: string[];
37
+ /** Stack wrapped around a slot that was filled a second time. */
38
+ promoted?: true;
31
39
  /** Shape is per-block-type, defined by the backend's `BlockDef.itemFields` (accordion's
32
40
  * question/answer, media-strip's assetId/alt/href/linkTarget, footer link-list/hours/social-row). */
33
41
  items?: Array<Record<string, unknown>>;
@@ -461,6 +469,13 @@ interface AiSectionsState {
461
469
  }
462
470
  /** Parse a persisted value defensively — malformed state renders nothing, never throws. */
463
471
  declare function parseAiSectionsState(raw: string | null | undefined): AiSectionsState;
472
+ /**
473
+ * Drop every `text-align` declaration from the inline style attributes in one content string,
474
+ * removing a style attribute that carried nothing else. Regex rather than DOMParser so the fold
475
+ * stays pure and usable outside a browser; the only authors of these attributes are the bridge's
476
+ * own `sanitizeHtml` and the model's alignment wraps, which both emit double-quoted attributes.
477
+ */
478
+ declare function stripTextAlignCss(html: string): string;
464
479
  /**
465
480
  * Fold alignment overrides that target GENERATED sections into their trees, where alignment is
466
481
  * first-class data (block.align) that persists with the rest of the tree. A node override
@@ -859,4 +874,4 @@ declare function useOhwCarousel(key: string, initial: CarouselSlide[]): {
859
874
  bind: Record<string, string>;
860
875
  };
861
876
 
862
- export { AI_DEFAULT_BRAND, AI_FOOTER_KEY, AI_TREE_SCHEMA_VERSIONS, AI_TREE_TOKENS, type AiBlockNode, type AiBrandKit, type AiFooterBottomBar, type AiFooterColumn, type AiFooterLayoutTree, AiFooterRenderer, type AiFooterRendererProps, type AiLayoutTree, AiTreeRenderer, type AiTreeRendererProps, type CarouselSlide, CustomToolbar, CustomToolbarButton, CustomToolbarDivider, DragHandle, DropIndicator, type DropIndicatorProps, DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, EmptySection, type EmptySectionProps, FOOTER_COLUMN_BLOCKS, FOOTER_COLUMN_SPANS, FOOTER_SOCIAL_PLATFORMS, ItemActionToolbar, type ItemActionToolbarProps, ItemInteractionLayer, type ItemInteractionLayerProps, type ItemInteractionState, LinkEditorPanel, type LinkModalMode, type LinkModalProps, type LinkPage, LinkPopover, type LinkSection, OhhwellsBridge, SchedulingWidget, type SectionScaffold, type SiteLegalLinks, Toggle, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, addFooterLinkColumn, appendFooterSocial, buildDefaultFooterTree, buildTarget, dropIndicatorVariants, ensureFooterTree, filterAvailablePages, foldAlignIntoTrees, getEditModeInitialState, insertFooterHours, isEditSessionActive, isRenderableFooterTree, isRenderableTree, isValidUrl, nextViableSlide, parseAiFooterState, parseAiSectionsState, parseTarget, removeFooterBlock, resolveFooterCopyright, serializeAiFooterState, stepViableSlide, toggleVariants, useOhwCarousel, validateUrlInput, viableSlides };
877
+ export { AI_DEFAULT_BRAND, AI_FOOTER_KEY, AI_TREE_SCHEMA_VERSIONS, AI_TREE_TOKENS, type AiBlockNode, type AiBrandKit, type AiFooterBottomBar, type AiFooterColumn, type AiFooterLayoutTree, AiFooterRenderer, type AiFooterRendererProps, type AiLayoutTree, AiTreeRenderer, type AiTreeRendererProps, type CarouselSlide, CustomToolbar, CustomToolbarButton, CustomToolbarDivider, DragHandle, DropIndicator, type DropIndicatorProps, DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, EmptySection, type EmptySectionProps, FOOTER_COLUMN_BLOCKS, FOOTER_COLUMN_SPANS, FOOTER_SOCIAL_PLATFORMS, ItemActionToolbar, type ItemActionToolbarProps, ItemInteractionLayer, type ItemInteractionLayerProps, type ItemInteractionState, LinkEditorPanel, type LinkModalMode, type LinkModalProps, type LinkPage, LinkPopover, type LinkSection, OhhwellsBridge, SchedulingWidget, type SectionScaffold, type SiteLegalLinks, Toggle, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, addFooterLinkColumn, appendFooterSocial, buildDefaultFooterTree, buildTarget, dropIndicatorVariants, ensureFooterTree, filterAvailablePages, foldAlignIntoTrees, getEditModeInitialState, insertFooterHours, isEditSessionActive, isRenderableFooterTree, isRenderableTree, isValidUrl, nextViableSlide, parseAiFooterState, parseAiSectionsState, parseTarget, removeFooterBlock, resolveFooterCopyright, serializeAiFooterState, stepViableSlide, stripTextAlignCss, toggleVariants, useOhwCarousel, validateUrlInput, viableSlides };
package/dist/index.d.ts CHANGED
@@ -28,6 +28,14 @@ interface AiBlockNode {
28
28
  align?: 'left' | 'center' | 'right';
29
29
  slots?: Record<string, unknown>;
30
30
  children?: AiBlockNode[];
31
+ /** Stable id for ordered insertion. Omitted on trees saved before slots. */
32
+ ref?: string;
33
+ /** Fixed slot inside a split (`left`/`right`) or grid (`cell-N`). Omit to keep index order. */
34
+ slot?: string;
35
+ /** Unfilled fixed slots on a group. The published page draws none of these. */
36
+ emptySlots?: string[];
37
+ /** Stack wrapped around a slot that was filled a second time. */
38
+ promoted?: true;
31
39
  /** Shape is per-block-type, defined by the backend's `BlockDef.itemFields` (accordion's
32
40
  * question/answer, media-strip's assetId/alt/href/linkTarget, footer link-list/hours/social-row). */
33
41
  items?: Array<Record<string, unknown>>;
@@ -461,6 +469,13 @@ interface AiSectionsState {
461
469
  }
462
470
  /** Parse a persisted value defensively — malformed state renders nothing, never throws. */
463
471
  declare function parseAiSectionsState(raw: string | null | undefined): AiSectionsState;
472
+ /**
473
+ * Drop every `text-align` declaration from the inline style attributes in one content string,
474
+ * removing a style attribute that carried nothing else. Regex rather than DOMParser so the fold
475
+ * stays pure and usable outside a browser; the only authors of these attributes are the bridge's
476
+ * own `sanitizeHtml` and the model's alignment wraps, which both emit double-quoted attributes.
477
+ */
478
+ declare function stripTextAlignCss(html: string): string;
464
479
  /**
465
480
  * Fold alignment overrides that target GENERATED sections into their trees, where alignment is
466
481
  * first-class data (block.align) that persists with the rest of the tree. A node override
@@ -859,4 +874,4 @@ declare function useOhwCarousel(key: string, initial: CarouselSlide[]): {
859
874
  bind: Record<string, string>;
860
875
  };
861
876
 
862
- export { AI_DEFAULT_BRAND, AI_FOOTER_KEY, AI_TREE_SCHEMA_VERSIONS, AI_TREE_TOKENS, type AiBlockNode, type AiBrandKit, type AiFooterBottomBar, type AiFooterColumn, type AiFooterLayoutTree, AiFooterRenderer, type AiFooterRendererProps, type AiLayoutTree, AiTreeRenderer, type AiTreeRendererProps, type CarouselSlide, CustomToolbar, CustomToolbarButton, CustomToolbarDivider, DragHandle, DropIndicator, type DropIndicatorProps, DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, EmptySection, type EmptySectionProps, FOOTER_COLUMN_BLOCKS, FOOTER_COLUMN_SPANS, FOOTER_SOCIAL_PLATFORMS, ItemActionToolbar, type ItemActionToolbarProps, ItemInteractionLayer, type ItemInteractionLayerProps, type ItemInteractionState, LinkEditorPanel, type LinkModalMode, type LinkModalProps, type LinkPage, LinkPopover, type LinkSection, OhhwellsBridge, SchedulingWidget, type SectionScaffold, type SiteLegalLinks, Toggle, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, addFooterLinkColumn, appendFooterSocial, buildDefaultFooterTree, buildTarget, dropIndicatorVariants, ensureFooterTree, filterAvailablePages, foldAlignIntoTrees, getEditModeInitialState, insertFooterHours, isEditSessionActive, isRenderableFooterTree, isRenderableTree, isValidUrl, nextViableSlide, parseAiFooterState, parseAiSectionsState, parseTarget, removeFooterBlock, resolveFooterCopyright, serializeAiFooterState, stepViableSlide, toggleVariants, useOhwCarousel, validateUrlInput, viableSlides };
877
+ export { AI_DEFAULT_BRAND, AI_FOOTER_KEY, AI_TREE_SCHEMA_VERSIONS, AI_TREE_TOKENS, type AiBlockNode, type AiBrandKit, type AiFooterBottomBar, type AiFooterColumn, type AiFooterLayoutTree, AiFooterRenderer, type AiFooterRendererProps, type AiLayoutTree, AiTreeRenderer, type AiTreeRendererProps, type CarouselSlide, CustomToolbar, CustomToolbarButton, CustomToolbarDivider, DragHandle, DropIndicator, type DropIndicatorProps, DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, EmptySection, type EmptySectionProps, FOOTER_COLUMN_BLOCKS, FOOTER_COLUMN_SPANS, FOOTER_SOCIAL_PLATFORMS, ItemActionToolbar, type ItemActionToolbarProps, ItemInteractionLayer, type ItemInteractionLayerProps, type ItemInteractionState, LinkEditorPanel, type LinkModalMode, type LinkModalProps, type LinkPage, LinkPopover, type LinkSection, OhhwellsBridge, SchedulingWidget, type SectionScaffold, type SiteLegalLinks, Toggle, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, addFooterLinkColumn, appendFooterSocial, buildDefaultFooterTree, buildTarget, dropIndicatorVariants, ensureFooterTree, filterAvailablePages, foldAlignIntoTrees, getEditModeInitialState, insertFooterHours, isEditSessionActive, isRenderableFooterTree, isRenderableTree, isValidUrl, nextViableSlide, parseAiFooterState, parseAiSectionsState, parseTarget, removeFooterBlock, resolveFooterCopyright, serializeAiFooterState, stepViableSlide, stripTextAlignCss, toggleVariants, useOhwCarousel, validateUrlInput, viableSlides };
package/dist/index.js CHANGED
@@ -6164,12 +6164,34 @@ function applyTreeToState(state, payload) {
6164
6164
  ...selfEdit ? priorEntry?.beforeSection ? { beforeSection: priorEntry.beforeSection } : {} : insertBefore && payload.targetSectionId ? { beforeSection: payload.targetSectionId } : {},
6165
6165
  ...selfEdit ? priorEntry?.replaces ? { replaces: priorEntry.replaces } : {} : payload.mode === "replace" && payload.targetSectionId ? { replaces: payload.targetSectionId } : {},
6166
6166
  tree: payload.tree,
6167
- media: payload.media ?? {},
6167
+ // A column insert rebuilds the media map from the live DOM keys. The photo
6168
+ // still points at the ref it was generated with. Keep that ref.
6169
+ media: { ...priorEntry?.media ?? {}, ...payload.media ?? {} },
6168
6170
  ...payload.brand && typeof payload.brand === "object" ? { brand: payload.brand } : {}
6169
6171
  };
6170
6172
  const others = state.sections.filter((existing) => existing.id !== entry.id);
6171
6173
  return { ...state, v: 1, sections: [...others, entry] };
6172
6174
  }
6175
+ function stripTextAlignCss(html) {
6176
+ return html.replace(/\sstyle="([^"]*)"/gi, (whole, value) => {
6177
+ if (!/text-align\s*:/i.test(value)) return whole;
6178
+ const kept = value.split(";").map((decl) => decl.trim()).filter((decl) => decl && !/^text-align\s*:/i.test(decl)).join("; ");
6179
+ return kept ? ` style="${kept}"` : "";
6180
+ });
6181
+ }
6182
+ function stripTextAlignInValue(value) {
6183
+ if (typeof value === "string") return stripTextAlignCss(value);
6184
+ if (Array.isArray(value)) return value.map(stripTextAlignInValue);
6185
+ if (value && typeof value === "object") {
6186
+ return Object.fromEntries(
6187
+ Object.entries(value).map(([key, inner]) => [
6188
+ key,
6189
+ stripTextAlignInValue(inner)
6190
+ ])
6191
+ );
6192
+ }
6193
+ return value;
6194
+ }
6173
6195
  function foldAlignIntoTrees(state, store) {
6174
6196
  const byId = new Map(state.sections.map((entry) => [entry.id, entry]));
6175
6197
  const nextTrees = /* @__PURE__ */ new Map();
@@ -6192,6 +6214,12 @@ function foldAlignIntoTrees(state, store) {
6192
6214
  return stripChildAligns(rest);
6193
6215
  })
6194
6216
  } : node;
6217
+ const stripContentAligns = (node) => ({
6218
+ ...node,
6219
+ ...node.slots ? { slots: stripTextAlignInValue(node.slots) } : {},
6220
+ ...node.children ? { children: node.children.map(stripContentAligns) } : {}
6221
+ });
6222
+ const authoritative = (block) => stripChildAligns(stripContentAligns(block));
6195
6223
  const nodes = {};
6196
6224
  for (const [key, override] of Object.entries(store.nodes)) {
6197
6225
  const match = override.align !== void 0 && key.startsWith(AI_SLOT_KEY_PREFIX) ? key.match(/^ai\.(.+?)\.r(\d+)\.b(\d+)(?:\.|$)/) : null;
@@ -6203,7 +6231,7 @@ function foldAlignIntoTrees(state, store) {
6203
6231
  nodes[key] = override;
6204
6232
  continue;
6205
6233
  }
6206
- row.blocks[blockIdx] = { ...stripChildAligns(block), align: override.align };
6234
+ row.blocks[blockIdx] = { ...authoritative(block), align: override.align };
6207
6235
  const rest = { ...override };
6208
6236
  delete rest.align;
6209
6237
  if (Object.keys(rest).length > 0) nodes[key] = rest;
@@ -6216,7 +6244,7 @@ function foldAlignIntoTrees(state, store) {
6216
6244
  continue;
6217
6245
  }
6218
6246
  for (const row of tree.rows) {
6219
- row.blocks = row.blocks.map((block) => ({ ...stripChildAligns(block), align: override.align }));
6247
+ row.blocks = row.blocks.map((block) => ({ ...authoritative(block), align: override.align }));
6220
6248
  }
6221
6249
  const rest = { ...override };
6222
6250
  delete rest.align;
@@ -9665,6 +9693,46 @@ function FooterSocialRowBlock({
9665
9693
  }
9666
9694
  );
9667
9695
  }
9696
+ function EmptySlotPlaceholder({ name }) {
9697
+ return /* @__PURE__ */ jsx(
9698
+ "div",
9699
+ {
9700
+ "data-ai-slot": name,
9701
+ "data-ai-slot-empty": "",
9702
+ "aria-hidden": "true",
9703
+ style: {
9704
+ minHeight: 88,
9705
+ borderRadius: 12,
9706
+ border: "1px dashed #D6D3D1",
9707
+ display: "flex",
9708
+ alignItems: "center",
9709
+ justifyContent: "center",
9710
+ padding: 16,
9711
+ color: "#A8A29E",
9712
+ fontSize: 13,
9713
+ lineHeight: 1.4,
9714
+ textAlign: "center",
9715
+ pointerEvents: "none"
9716
+ },
9717
+ children: "Ask AI to add something here"
9718
+ }
9719
+ );
9720
+ }
9721
+ function splitSides(node) {
9722
+ const named = /* @__PURE__ */ new Map();
9723
+ const unnamed = [];
9724
+ (node.children ?? []).forEach((child, index) => {
9725
+ if (child.slot === "left" || child.slot === "right") {
9726
+ named.set(child.slot, { node: child, index });
9727
+ } else {
9728
+ unnamed.push({ node: child, index });
9729
+ }
9730
+ });
9731
+ return {
9732
+ left: named.get("left") ?? unnamed.shift() ?? null,
9733
+ right: named.get("right") ?? unnamed.shift() ?? null
9734
+ };
9735
+ }
9668
9736
  function renderNode(node, ctx, path, ambientAlign) {
9669
9737
  const slots = node.slots ?? {};
9670
9738
  switch (node.type) {
@@ -9672,9 +9740,11 @@ function renderNode(node, ctx, path, ambientAlign) {
9672
9740
  return /* @__PURE__ */ jsx(TextBlock, { slots, ctx, path, align: node.align ?? ambientAlign });
9673
9741
  // Layout container: arranges child blocks, contributes no content of its own. `grid` is a
9674
9742
  // nested 12-column grid the children span (a collage is 3–5 media on it, bottom-aligned so
9675
- // mixed aspects read as a composition); `split` is exactly two children at a ratio; `stack`
9676
- // is a column. Children render through this same dispatcher, so edit markers, media
9677
- // resolution, and copy paths all work unchanged inside a group.
9743
+ // mixed aspects read as a composition); `split` is left and right (index order when `slot`
9744
+ // is omitted); `stack` is a column. A missing split side is a canvas placeholder only when
9745
+ // `emptySlots` names it. A one-child split used to keep a row at span 12 (a testimonial
9746
+ // under a heading) sits centered at half the row width. Children render through this same
9747
+ // dispatcher, so edit markers, media resolution, and copy paths all work unchanged.
9678
9748
  case "group": {
9679
9749
  const layout = str(slots.layout);
9680
9750
  const gap = slots.spacing === "tight" ? AI_TREE_TOKENS.spacing3 : slots.spacing === "airy" ? AI_TREE_TOKENS.spacing8 : AI_TREE_TOKENS.spacing6;
@@ -9772,24 +9842,38 @@ function renderNode(node, ctx, path, ambientAlign) {
9772
9842
  i
9773
9843
  ));
9774
9844
  if (layout === "grid") {
9775
- return /* @__PURE__ */ jsx(
9845
+ const holes = ctx.keyFor ? node.emptySlots ?? [] : [];
9846
+ return /* @__PURE__ */ jsxs(
9776
9847
  "div",
9777
9848
  {
9778
9849
  "data-ai-group": "grid",
9779
9850
  style: { display: "grid", gridTemplateColumns: "repeat(12, 1fr)", gap, alignItems: "end" },
9780
- children: kids
9851
+ children: [
9852
+ kids,
9853
+ holes.map((name) => /* @__PURE__ */ jsx("div", { style: { gridColumn: "span 6", minWidth: 0 }, children: /* @__PURE__ */ jsx(EmptySlotPlaceholder, { name }) }, name))
9854
+ ]
9781
9855
  }
9782
9856
  );
9783
9857
  }
9784
9858
  if (layout === "split") {
9859
+ const { left, right } = splitSides(node);
9860
+ const open = new Set(ctx.keyFor ? node.emptySlots ?? [] : []);
9861
+ const side = (entry, name) => entry ? /* @__PURE__ */ jsx("div", { style: { minWidth: 0 }, children: renderNode(entry.node, ctx, `${path}.c${entry.index}`) }, name) : open.has(name) ? /* @__PURE__ */ jsx(EmptySlotPlaceholder, { name }, name) : null;
9862
+ if (!left && !open.has("left") || !right && !open.has("right")) {
9863
+ const only = left ?? right;
9864
+ return only ? /* @__PURE__ */ jsx("div", { "data-ai-group": "split", style: { display: "flex", justifyContent: "center" }, children: /* @__PURE__ */ jsx("div", { style: { width: "50%", minWidth: 0 }, children: renderNode(only.node, ctx, `${path}.c${only.index}`) }) }) : null;
9865
+ }
9785
9866
  const ratio = str(slots.ratio);
9786
9867
  const cols = ratio === "3:5" ? "3fr 5fr" : ratio === "5:3" ? "5fr 3fr" : "1fr 1fr";
9787
- return /* @__PURE__ */ jsx(
9868
+ return /* @__PURE__ */ jsxs(
9788
9869
  "div",
9789
9870
  {
9790
9871
  "data-ai-group": "split",
9791
9872
  style: { display: "grid", gridTemplateColumns: cols, gap, alignItems: "center" },
9792
- children: kids
9873
+ children: [
9874
+ side(left, "left"),
9875
+ side(right, "right")
9876
+ ]
9793
9877
  }
9794
9878
  );
9795
9879
  }
@@ -30066,14 +30150,73 @@ function OhhwellsBridge() {
30066
30150
  value = JSON.stringify(foldedHeight.store);
30067
30151
  }
30068
30152
  }
30153
+ const restoreNodes = e.data.restoreNodes;
30154
+ const restored = [];
30155
+ if (restoreNodes && typeof restoreNodes === "object") {
30156
+ for (const [key, html] of Object.entries(restoreNodes)) {
30157
+ if (typeof html !== "string") continue;
30158
+ const els = document.querySelectorAll(`[data-ohw-key="${CSS.escape(key)}"]`);
30159
+ if (els.length === 0) continue;
30160
+ for (const el of Array.from(els)) el.innerHTML = html;
30161
+ editContentRef.current = { ...editContentRef.current, [key]: html };
30162
+ restored.push({ key, text: html });
30163
+ }
30164
+ }
30165
+ const previousNodes = {};
30166
+ const strippedNodes = [];
30167
+ if (!restoreNodes) {
30168
+ const aligned = parseStyleStore(value);
30169
+ const prior = parseStyleStore(previous);
30170
+ const keys = /* @__PURE__ */ new Set();
30171
+ if (aligned) {
30172
+ for (const [key, override] of Object.entries(aligned.nodes)) {
30173
+ if (override.align === void 0) continue;
30174
+ if (prior?.nodes[key]?.align === override.align) continue;
30175
+ keys.add(key);
30176
+ }
30177
+ for (const [sectionId, override] of Object.entries(aligned.sections)) {
30178
+ if (override.align === void 0) continue;
30179
+ if (prior?.sections[sectionId]?.align === override.align) continue;
30180
+ const escaped = CSS.escape(sectionId);
30181
+ const markers = document.querySelectorAll(
30182
+ `[data-ohw-instance="${escaped}"], [data-ohw-section="${escaped}"]`
30183
+ );
30184
+ for (const marker of Array.from(markers)) {
30185
+ const editables = marker.querySelectorAll(
30186
+ '[data-ohw-key][data-ohw-editable="text"], [data-ohw-key][data-ohw-editable="plain"]'
30187
+ );
30188
+ for (const el of Array.from(editables)) {
30189
+ const key = el.getAttribute("data-ohw-key");
30190
+ if (key) keys.add(key);
30191
+ }
30192
+ }
30193
+ }
30194
+ }
30195
+ for (const key of keys) {
30196
+ if (key.startsWith(AI_SLOT_KEY_PREFIX) || key.startsWith("__ohw_")) continue;
30197
+ const els = document.querySelectorAll(`[data-ohw-key="${CSS.escape(key)}"]`);
30198
+ if (els.length === 0) continue;
30199
+ const before = sanitizeHtml(els[0].innerHTML);
30200
+ const after = stripTextAlignCss(before);
30201
+ if (after === before) continue;
30202
+ for (const el of Array.from(els)) el.innerHTML = after;
30203
+ previousNodes[key] = before;
30204
+ editContentRef.current = { ...editContentRef.current, [key]: after };
30205
+ strippedNodes.push({ key, text: after });
30206
+ }
30207
+ }
30069
30208
  stylesRef.current = value;
30070
30209
  applyStylesToDom(parseStyleStore(value));
30071
- postToParentRef.current({ type: "ow:change", nodes: [{ key: STYLE_STORE_KEY, text: value }] });
30210
+ postToParentRef.current({
30211
+ type: "ow:change",
30212
+ nodes: [{ key: STYLE_STORE_KEY, text: value }, ...strippedNodes, ...restored]
30213
+ });
30072
30214
  postToParentRef.current({
30073
30215
  type: "ow:ai-styles-applied",
30074
30216
  previous,
30075
30217
  value,
30076
- ...previousSections !== void 0 ? { previousSections } : {}
30218
+ ...previousSections !== void 0 ? { previousSections } : {},
30219
+ ...Object.keys(previousNodes).length > 0 ? { previousNodes } : {}
30077
30220
  });
30078
30221
  };
30079
30222
  window.addEventListener("message", handleAiSetStyles);
@@ -31418,7 +31561,7 @@ function OhhwellsBridge() {
31418
31561
  postToParent2({
31419
31562
  type: "ow:ready",
31420
31563
  version: "1",
31421
- bridgeVersion: "0.1.144",
31564
+ bridgeVersion: "0.1.146",
31422
31565
  path: pathname,
31423
31566
  nodes: collectEditableNodes(editContentRef.current),
31424
31567
  sections
@@ -32648,6 +32791,7 @@ export {
32648
32791
  resolveFooterCopyright,
32649
32792
  serializeAiFooterState,
32650
32793
  stepViableSlide,
32794
+ stripTextAlignCss,
32651
32795
  toggleVariants,
32652
32796
  useOhwCarousel,
32653
32797
  validateUrlInput,