@ohhwells/bridge 0.1.133 → 0.1.134-next.362

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
@@ -2656,6 +2656,19 @@ function quotedFont(font) {
2656
2656
  const cleaned = font.replace(/["']/g, "").trim();
2657
2657
  return cleaned ? `"${cleaned}"` : "sans-serif";
2658
2658
  }
2659
+ function themeFont(role, fallback) {
2660
+ const quoted = quotedFont(fallback);
2661
+ return role === "heading" ? `var(--brand-font-heading, var(--font-heading, var(--font-display, ${quoted})))` : `var(--brand-font-body, var(--font-body, ${quoted}))`;
2662
+ }
2663
+ function withThemeFonts(brand) {
2664
+ return {
2665
+ ...brand,
2666
+ fonts: {
2667
+ heading: themeFont("heading", brand.fonts.heading),
2668
+ body: themeFont("body", brand.fonts.body)
2669
+ }
2670
+ };
2671
+ }
2659
2672
  function footerLogoStyle(fallbackFont, fallbackColor) {
2660
2673
  return {
2661
2674
  fontFamily: `var(--brand-font-heading, var(--font-heading, var(--font-display, ${quotedFont(fallbackFont)})))`,
@@ -2727,7 +2740,7 @@ var AI_MOBILE_CSS = [
2727
2740
  // page's first section. Top only (bottom needs no nav clearance); ~96px approximates the
2728
2741
  // template nav's own height (measured ~94px) — there's no shared `--ohw-header-h` var to read
2729
2742
  // an exact figure from (no live measurement exists), so this is a safe approximation, not a
2730
- // pixel-exact fit. Horizontal spacing already comes from `data-ai-container`.
2743
+ // pixel-exact fit. Horizontal spacing already comes from `data-ai-section-inner`.
2731
2744
  '[data-ai-overlay-clearance] [data-ai-overlap-layer="overlay"]{padding-top:96px}',
2732
2745
  // A full-bleed carousel's height is pure arithmetic: it spans the whole window, so 16/9 turns
2733
2746
  // window width straight into height. Measured at 16/9 it came out ~90% of the viewport at EVERY
@@ -2743,8 +2756,10 @@ var AI_MOBILE_CSS = [
2743
2756
  "[data-ai-section]{overflow-x:hidden}",
2744
2757
  // `:not([data-ai-full-bleed])`: this `!important` gutter beat the inline `padding: 0` that a
2745
2758
  // full-bleed section's container sets, so a carousel/media hero that is edge-to-edge on desktop
2746
- // still got a 20px band of section background down both sides on a phone.
2747
- "[data-ai-container]:not([data-ai-full-bleed]){padding:0 20px !important}",
2759
+ // still got a 20px band of section background down both sides on a phone. The footer's own
2760
+ // container keeps the unguarded rule below — `data-ai-full-bleed` can never appear on it.
2761
+ "[data-ai-section-inner]:not([data-ai-full-bleed]){padding:0 20px !important}",
2762
+ "[data-ai-container]{padding:0 20px !important}",
2748
2763
  "[data-ai-row]{display:flex !important;flex-direction:column !important;align-items:stretch !important}",
2749
2764
  "[data-ai-cell]{width:100%;min-width:0}",
2750
2765
  // The rule above turns every row into a COLUMN flex container on phones, which silently breaks
@@ -2945,6 +2960,31 @@ function pricingCardEmphasis(featured, ctx, hasBg) {
2945
2960
  function textAttrs(ctx, path) {
2946
2961
  return ctx.keyFor ? { "data-ohw-key": ctx.keyFor(path), "data-ohw-editable": "text" } : {};
2947
2962
  }
2963
+ var AI_RESPONSIVE_CSS = [
2964
+ "@media (max-width: 960px) {",
2965
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: repeat(2, 1fr) !important; }",
2966
+ ' [data-ai-responsive] [data-ai-grid="1"] { grid-template-columns: 1fr !important; }',
2967
+ "}",
2968
+ "@media (max-width: 640px) {",
2969
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: 1fr !important; }",
2970
+ " [data-ai-responsive] [data-ai-columns] > * { grid-column: 1 / -1 !important; }",
2971
+ // Group containers flatten to a column on phones; span placements come along for free.
2972
+ // `:not([data-ai-group="overlap"])` for the same reason as the twin pair in AI_MOBILE_CSS — see
2973
+ // the comment there. This sheet's copy is the narrower selector, so without the exemption it
2974
+ // flattens an `overlap` hero below 640px even though the other rule stands down: measured a 390px
2975
+ // viewport with the heading/CTA in flow *under* the image, 219px of section background where the
2976
+ // layered text used to be.
2977
+ ' [data-ai-responsive] [data-ai-group]:not([data-ai-group="overlap"]) { display: flex !important; flex-direction: column !important; }',
2978
+ ' [data-ai-responsive] [data-ai-group]:not([data-ai-group="overlap"]) > * { grid-column: auto !important; }',
2979
+ // `:not([data-ai-full-bleed])` for the same reason as the twin rule in AI_MOBILE_CSS: this one
2980
+ // is narrower (0,2,0 vs 0,1,0) and also `!important`, so without the guard it wins outright and
2981
+ // a full-bleed hero gets its 20px side band back below 640px only — the one width where the
2982
+ // other rule had already been taught to stand down.
2983
+ " [data-ai-responsive] [data-ai-section-inner]:not([data-ai-full-bleed]) { padding-left: 20px !important; padding-right: 20px !important; }",
2984
+ " [data-ai-responsive] { overflow-x: hidden; }",
2985
+ " [data-ai-responsive] img { max-width: 100%; }",
2986
+ "}"
2987
+ ].join("\n");
2948
2988
  var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
2949
2989
  function MediaBox({
2950
2990
  refValue,
@@ -2965,9 +3005,9 @@ function MediaBox({
2965
3005
  const url = refValue ? ctx.resolveMedia(refValue) : null;
2966
3006
  const isIcon = /^(lucide|simple):/.test(refValue);
2967
3007
  const aspectRatio = aspect && /^\d+:\d+$/.test(aspect) ? aspect.replace(":", " / ") : void 0;
2968
- const editAttrs = ctx.keyFor && editPath && !isIcon ? {
3008
+ const editAttrs = ctx.keyFor && editPath ? {
2969
3009
  "data-ohw-key": ctx.keyFor(editPath),
2970
- ...genericImageEdit ? { "data-ohw-editable": "image" } : {}
3010
+ ...isIcon ? { "data-ohw-editable": "icon" } : genericImageEdit ? { "data-ohw-editable": "image" } : {}
2971
3011
  } : {};
2972
3012
  if (isIcon) {
2973
3013
  const Icon = refValue.startsWith("lucide:") ? lucideByName(refValue.slice(7)) : null;
@@ -2975,6 +3015,7 @@ function MediaBox({
2975
3015
  "span",
2976
3016
  {
2977
3017
  "data-ai-icon": refValue,
3018
+ ...editAttrs,
2978
3019
  style: {
2979
3020
  display: "inline-flex",
2980
3021
  width: 48,
@@ -4468,7 +4509,7 @@ function CollectionBlock({ node, ctx, path }) {
4468
4509
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
4469
4510
  "div",
4470
4511
  {
4471
- "data-ai-grid": "",
4512
+ "data-ai-grid": String(itemsPerRow),
4472
4513
  "data-ohw-pricing-collection": isPricing ? "" : void 0,
4473
4514
  style: {
4474
4515
  display: "grid",
@@ -4728,7 +4769,7 @@ function renderNode(node, ctx, path, ambientAlign) {
4728
4769
  },
4729
4770
  children: [
4730
4771
  i > 0 && ctx.heroOverlapFullBleed ? (
4731
- // The section's `data-ai-container` gave up its max-width/gutter so the base
4772
+ // The section's `data-ai-section-inner` gave up its max-width/gutter so the base
4732
4773
  // layer (this group's i===0 sibling) can span edge-to-edge — reintroduced here,
4733
4774
  // scoped to just the overlay text, so a heading/copy/CTA stack doesn't stretch
4734
4775
  // the full section width instead of reading at the usual content width.
@@ -5051,7 +5092,7 @@ function AiTreeRenderer({
5051
5092
  if (!isRenderableTree(tree)) {
5052
5093
  return null;
5053
5094
  }
5054
- const resolvedBrand = brand ?? AI_DEFAULT_BRAND;
5095
+ const resolvedBrand = withThemeFonts(brand ?? AI_DEFAULT_BRAND);
5055
5096
  const band = (tree.settings ?? {}).sectionBackground === "accent" ? accentBandContext(resolvedBrand) : null;
5056
5097
  const blockBrand = band?.brand ?? resolvedBrand;
5057
5098
  const placeholderMap = buildPlaceholderMap(tree);
@@ -5121,6 +5162,7 @@ function AiTreeRenderer({
5121
5162
  ...fillHeight ? { "data-ai-fill-height": "" } : {},
5122
5163
  ...needsOverlayClearance ? { "data-ai-overlay-clearance": "" } : {},
5123
5164
  ...bgAttrs,
5165
+ "data-ai-responsive": "",
5124
5166
  style: {
5125
5167
  position: "relative",
5126
5168
  padding: `${padCss} 0`,
@@ -5134,12 +5176,13 @@ function AiTreeRenderer({
5134
5176
  ...fillHeight ? { display: "grid" } : {}
5135
5177
  },
5136
5178
  children: [
5137
- isOverlay && backgroundUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
5179
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_RESPONSIVE_CSS }),
5138
5180
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_MOBILE_CSS }),
5181
+ isOverlay && backgroundUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
5139
5182
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
5140
5183
  "div",
5141
5184
  {
5142
- "data-ai-container": "",
5185
+ "data-ai-section-inner": "",
5143
5186
  ...fullBleedMedia ? { "data-ai-full-bleed": "" } : {},
5144
5187
  style: {
5145
5188
  position: "relative",
@@ -5156,7 +5199,7 @@ function AiTreeRenderer({
5156
5199
  children: tree.rows.map((row, r2) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
5157
5200
  "div",
5158
5201
  {
5159
- "data-ai-row": "",
5202
+ "data-ai-columns": "",
5160
5203
  style: {
5161
5204
  display: "grid",
5162
5205
  gridTemplateColumns: "repeat(12, 1fr)",
@@ -5207,7 +5250,7 @@ function AiFooterRenderer({
5207
5250
  if (!isRenderableFooterTree(tree)) {
5208
5251
  return null;
5209
5252
  }
5210
- const resolvedBrand = brand ?? AI_DEFAULT_BRAND;
5253
+ const resolvedBrand = withThemeFonts(brand ?? AI_DEFAULT_BRAND);
5211
5254
  const bandBg = surface?.background ?? resolvedBrand.palette.dark;
5212
5255
  const bandText = surface?.text ?? resolvedBrand.palette.light;
5213
5256
  const onDark = surface ? isDarkColor(surface.background) : true;
@@ -5812,9 +5855,10 @@ function applyAiSectionsToDom(state, options) {
5812
5855
  if (existing && existing.serialized === serialized && existing.container.isConnected) {
5813
5856
  continue;
5814
5857
  }
5815
- const isNewMount = !existing || !existing.container.isConnected;
5816
5858
  let container = existing?.container;
5859
+ let isNewMount = false;
5817
5860
  if (!container || !container.isConnected) {
5861
+ isNewMount = true;
5818
5862
  existing?.root.unmount();
5819
5863
  container?.remove();
5820
5864
  container = document.createElement("div");
@@ -9030,7 +9074,7 @@ var isLabelWeight = (label) => label === "number" || label === "weight";
9030
9074
  var isLabelShadow = (label) => label === "shadow";
9031
9075
  var getDefaultConfig = () => {
9032
9076
  const themeColor = fromTheme("color");
9033
- const themeFont = fromTheme("font");
9077
+ const themeFont2 = fromTheme("font");
9034
9078
  const themeText = fromTheme("text");
9035
9079
  const themeFontWeight = fromTheme("font-weight");
9036
9080
  const themeTracking = fromTheme("tracking");
@@ -9952,7 +9996,7 @@ var getDefaultConfig = () => {
9952
9996
  * @see https://tailwindcss.com/docs/font-family
9953
9997
  */
9954
9998
  "font-family": [{
9955
- font: [isArbitraryVariableFamilyName, isArbitraryFamilyName, themeFont]
9999
+ font: [isArbitraryVariableFamilyName, isArbitraryFamilyName, themeFont2]
9956
10000
  }],
9957
10001
  /**
9958
10002
  * Font Feature Settings
@@ -13449,6 +13493,7 @@ function MediaOverlay({
13449
13493
  (prev) => prev && prev.fullWidth === width && prev.height === height ? prev : { fullWidth: width, height }
13450
13494
  );
13451
13495
  }, [isVideo]);
13496
+ const replaceLabel = isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image";
13452
13497
  const replaceMode = buttonSize ? replaceButtonMode({ width: rect.width, height: rect.height }, buttonSize) : "none";
13453
13498
  const box = {
13454
13499
  position: "fixed",
@@ -13547,11 +13592,10 @@ function MediaOverlay({
13547
13592
  className: "flex items-center justify-center cursor-pointer",
13548
13593
  style: {
13549
13594
  ...box,
13550
- // When text overlays this media, let clicks fall THROUGH to the text underneath. In the
13551
- // parent this needed a whole `ow:click-at` round-trip to re-hit-test inside the iframe;
13552
- // in-document, pointer-events does it natively. The button below opts back in, so
13553
- // Replace still works.
13554
- pointerEvents: hover.hasTextOverlap || cueOnly ? "none" : "auto",
13595
+ // Visual only. `pointer-events: auto` on this full-bleed rect covered the navbar
13596
+ // (hero bg includes the absolute TopNav) and swallowed CTA press-drag. Text overlay
13597
+ // and cueOnly also need clicks to fall through — Replace / video controls opt back in.
13598
+ pointerEvents: "none",
13555
13599
  // Selected: a firm component ring with no wash, so the image reads as chosen rather
13556
13600
  // than hovered. Hover keeps the existing tinted preview.
13557
13601
  boxShadow: selected ? "inset 0 0 0 2px var(--color-bridge-primary)" : "inset 0 0 0 1.5px var(--color-bridge-primary)",
@@ -13578,17 +13622,17 @@ function MediaOverlay({
13578
13622
  },
13579
13623
  children: [
13580
13624
  isVideo ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react5.Film, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react5.ImageIcon, { size: 14 }),
13581
- isVideo ? "Replace video" : "Replace image"
13625
+ replaceLabel
13582
13626
  ]
13583
13627
  }
13584
13628
  ),
13585
- replaceMode === "none" || cueOnly ? null : /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
13629
+ showChrome && replaceMode !== "none" && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
13586
13630
  Button,
13587
13631
  {
13588
13632
  "data-ohw-media-overlay": "",
13589
13633
  variant: "outline",
13590
13634
  size: "sm",
13591
- "aria-label": isVideo ? "Replace video" : "Replace image",
13635
+ "aria-label": replaceLabel,
13592
13636
  className: "gap-1.5 cursor-pointer hover:bg-background",
13593
13637
  style: {
13594
13638
  ...OVERLAY_BUTTON_STYLE,
@@ -13611,7 +13655,7 @@ function MediaOverlay({
13611
13655
  },
13612
13656
  children: [
13613
13657
  isVideo ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react5.Film, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react5.ImageIcon, { size: 14 }),
13614
- replaceMode === "full" ? isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image" : null
13658
+ replaceMode === "full" ? replaceLabel : null
13615
13659
  ]
13616
13660
  }
13617
13661
  )
@@ -19843,6 +19887,7 @@ function readLogoSizeState(content, placement) {
19843
19887
  function getLogoElement(el) {
19844
19888
  const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
19845
19889
  if (marked) return marked;
19890
+ if (el.closest('[data-ohw-editable="icon"]')) return null;
19846
19891
  const root = el.closest("nav, [data-ohw-nav-root], footer");
19847
19892
  if (!root) return null;
19848
19893
  const anchor = el.closest("a");
@@ -23334,7 +23379,7 @@ function getNavigationSelectionParent(el) {
23334
23379
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
23335
23380
  return resolveFooterLinksContainerFrom(el);
23336
23381
  }
23337
- if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || isFooterLinksContainer(el)) {
23382
+ if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isFooterLinksContainer(el) || isInferredFooterGroup2(el)) {
23338
23383
  return getNavigationRoot(el);
23339
23384
  }
23340
23385
  return null;
@@ -23556,7 +23601,6 @@ var ICONS = {
23556
23601
  insertUnorderedList: '<line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/>',
23557
23602
  insertOrderedList: '<line x1="10" y1="6" x2="21" y2="6"/><line x1="10" y1="12" x2="21" y2="12"/><line x1="10" y1="18" x2="21" y2="18"/><path d="M4 6h1v4"/><path d="M4 10h2"/><path d="M6 18H4c0-1 2-2 2-3s-1-1.5-2-1"/>'
23558
23603
  };
23559
- var HEIGHT_SETTLE_DELAYS = [150, 400, 800];
23560
23604
  var SELECTION_CHROME_GAP2 = 4;
23561
23605
  var TOOLBAR_STROKE_GAP2 = 4;
23562
23606
  var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
@@ -24536,21 +24580,6 @@ function OhhwellsBridge() {
24536
24580
  const [isItemDragging, setIsItemDragging] = (0, import_react21.useState)(false);
24537
24581
  const [isFooterFrameSelection, setIsFooterFrameSelection] = (0, import_react21.useState)(false);
24538
24582
  isFooterFrameSelectionRef.current = isFooterFrameSelection;
24539
- const [floatingPanel, setFloatingPanel] = (0, import_react21.useState)(null);
24540
- const floatingPanelOpenRef = (0, import_react21.useRef)(false);
24541
- floatingPanelOpenRef.current = floatingPanel !== null;
24542
- const floatingPanelRef = (0, import_react21.useRef)(null);
24543
- floatingPanelRef.current = floatingPanel;
24544
- const [floatingPanelPos, setFloatingPanelPos] = (0, import_react21.useState)(null);
24545
- const buttonCollectionDragRef = (0, import_react21.useRef)(null);
24546
- const buttonDropInsertIndexRef = (0, import_react21.useRef)(null);
24547
- const [buttonDropSlots, setButtonDropSlots] = (0, import_react21.useState)([]);
24548
- const [activeButtonDropIndex, setActiveButtonDropIndex] = (0, import_react21.useState)(null);
24549
- const buttonPointerDragRef = (0, import_react21.useRef)(null);
24550
- const [buttonStyleTick, setButtonStyleTick] = (0, import_react21.useState)(0);
24551
- const [logoSizeDraft, setLogoSizeDraft] = (0, import_react21.useState)(null);
24552
- const [editorViewport, setEditorViewport] = (0, import_react21.useState)("desktop");
24553
- const [parentScrollSnap, setParentScrollSnap] = (0, import_react21.useState)(null);
24554
24583
  const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = (0, import_react21.useState)(null);
24555
24584
  const [footerHeadingVisible, setFooterHeadingVisible] = (0, import_react21.useState)(null);
24556
24585
  const footerDragRef = (0, import_react21.useRef)(null);
@@ -24576,6 +24605,21 @@ function OhhwellsBridge() {
24576
24605
  const stylesRef = (0, import_react21.useRef)("");
24577
24606
  const appliedDrawerRef = (0, import_react21.useRef)(null);
24578
24607
  const pendingDeleteUndoRef = (0, import_react21.useRef)(null);
24608
+ const [floatingPanel, setFloatingPanel] = (0, import_react21.useState)(null);
24609
+ const floatingPanelOpenRef = (0, import_react21.useRef)(false);
24610
+ const floatingPanelRef = (0, import_react21.useRef)(null);
24611
+ floatingPanelRef.current = floatingPanel;
24612
+ const setFloatingPanelRef = (0, import_react21.useRef)(setFloatingPanel);
24613
+ const [floatingPanelPos, setFloatingPanelPos] = (0, import_react21.useState)(null);
24614
+ const buttonCollectionDragRef = (0, import_react21.useRef)(null);
24615
+ const buttonDropInsertIndexRef = (0, import_react21.useRef)(null);
24616
+ const [buttonDropSlots, setButtonDropSlots] = (0, import_react21.useState)([]);
24617
+ const [activeButtonDropIndex, setActiveButtonDropIndex] = (0, import_react21.useState)(null);
24618
+ const buttonPointerDragRef = (0, import_react21.useRef)(null);
24619
+ const [buttonStyleTick, setButtonStyleTick] = (0, import_react21.useState)(0);
24620
+ const [logoSizeDraft, setLogoSizeDraft] = (0, import_react21.useState)(null);
24621
+ const [editorViewport, setEditorViewport] = (0, import_react21.useState)("desktop");
24622
+ const [parentScrollSnap, setParentScrollSnap] = (0, import_react21.useState)(null);
24579
24623
  const [sitePages, setSitePages] = (0, import_react21.useState)([]);
24580
24624
  const [sectionsByPath, setSectionsByPath] = (0, import_react21.useState)({});
24581
24625
  const sectionsPrefetchGenRef = (0, import_react21.useRef)(0);
@@ -24584,7 +24628,18 @@ function OhhwellsBridge() {
24584
24628
  const linkPopoverOpenRef = (0, import_react21.useRef)(false);
24585
24629
  const linkPopoverGraceUntilRef = (0, import_react21.useRef)(0);
24586
24630
  setLinkPopoverRef.current = setLinkPopover;
24631
+ setFloatingPanelRef.current = setFloatingPanel;
24587
24632
  linkPopoverSessionRef.current = linkPopover;
24633
+ floatingPanelOpenRef.current = Boolean(floatingPanel);
24634
+ (0, import_react21.useEffect)(() => {
24635
+ const syncViewport = () => {
24636
+ const next = window.innerWidth <= 480 ? "mobile" : "desktop";
24637
+ setEditorViewport((prev) => prev === next ? prev : next);
24638
+ };
24639
+ syncViewport();
24640
+ window.addEventListener("resize", syncViewport);
24641
+ return () => window.removeEventListener("resize", syncViewport);
24642
+ }, []);
24588
24643
  (0, import_react21.useEffect)(() => {
24589
24644
  setSectionOrderPersistHandler((entries) => {
24590
24645
  const currentPath = window.location.pathname;
@@ -26263,6 +26318,7 @@ function OhhwellsBridge() {
26263
26318
  applyAiFooterFromContent(content);
26264
26319
  if (typeof content[STYLE_STORE_KEY] === "string") {
26265
26320
  stylesRef.current = content[STYLE_STORE_KEY];
26321
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
26266
26322
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
26267
26323
  }
26268
26324
  applyBrandChrome(content);
@@ -26272,12 +26328,12 @@ function OhhwellsBridge() {
26272
26328
  if (key === "__ohw_sections") continue;
26273
26329
  if (key === AI_SECTIONS_KEY) continue;
26274
26330
  if (key === AI_FOOTER_KEY) continue;
26331
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
26332
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
26275
26333
  if (key === BRAND_KIT_KEY) continue;
26276
26334
  if (key === STYLE_STORE_KEY) continue;
26277
26335
  if (BRAND_CHROME_KEYS.has(key)) continue;
26278
- if (key === LOGO_PLACEHOLDER_KEY) continue;
26279
26336
  if (key === CUSTOM_CODE_AFTER_HEAD_KEY || key === CUSTOM_CODE_BEFORE_HEAD_KEY) continue;
26280
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
26281
26337
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
26282
26338
  continue;
26283
26339
  }
@@ -26491,17 +26547,18 @@ function OhhwellsBridge() {
26491
26547
  }
26492
26548
  applyAiFooterFromContent(content);
26493
26549
  if (typeof content[STYLE_STORE_KEY] === "string") {
26550
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
26494
26551
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
26495
26552
  }
26496
26553
  for (const [key, val] of Object.entries(content)) {
26497
26554
  if (key === "__ohw_sections") continue;
26498
26555
  if (key === AI_SECTIONS_KEY) continue;
26499
26556
  if (key === AI_FOOTER_KEY) continue;
26557
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
26558
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
26500
26559
  if (key === BRAND_KIT_KEY) continue;
26501
26560
  if (key === STYLE_STORE_KEY) continue;
26502
26561
  if (BRAND_CHROME_KEYS.has(key)) continue;
26503
- if (key === LOGO_PLACEHOLDER_KEY) continue;
26504
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
26505
26562
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
26506
26563
  continue;
26507
26564
  }
@@ -26757,26 +26814,11 @@ function OhhwellsBridge() {
26757
26814
  const t2 = setTimeout(measure, 500);
26758
26815
  const ro = new ResizeObserver(schedule);
26759
26816
  ro.observe(document.body);
26760
- let lastWidth = window.innerWidth;
26761
- let resizeTimers = [];
26762
- const clearResizeTimers = () => {
26763
- resizeTimers.forEach(clearTimeout);
26764
- resizeTimers = [];
26765
- };
26766
- const handleResize = () => {
26767
- if (window.innerWidth === lastWidth) return;
26768
- lastWidth = window.innerWidth;
26769
- clearResizeTimers();
26770
- resizeTimers = HEIGHT_SETTLE_DELAYS.map((delay) => setTimeout(measure, delay));
26771
- };
26772
- window.addEventListener("resize", handleResize);
26773
26817
  return () => {
26774
26818
  clearTimeout(t1);
26775
26819
  clearTimeout(t2);
26776
26820
  if (raf != null) cancelAnimationFrame(raf);
26777
26821
  ro.disconnect();
26778
- clearResizeTimers();
26779
- window.removeEventListener("resize", handleResize);
26780
26822
  };
26781
26823
  }, [pathname, isEditMode, postToParent2]);
26782
26824
  (0, import_react21.useEffect)(() => {
@@ -27071,6 +27113,13 @@ function OhhwellsBridge() {
27071
27113
  if (target.closest("[data-ohw-state-toggle]")) return;
27072
27114
  if (target.closest("[data-ohw-max-badge]")) return;
27073
27115
  if (isInsideLinkEditor(target)) return;
27116
+ if (isInsideFloatingPanel(target)) return;
27117
+ if (target.closest("[data-ohw-form-toolbar]")) return;
27118
+ if (target.closest(
27119
+ '[data-ohw-field-toolbar], [data-ohw-field-type-picker], [data-radix-popper-content-wrapper], [role="menu"], [data-slot="dropdown-menu-content"]'
27120
+ )) {
27121
+ return;
27122
+ }
27074
27123
  if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
27075
27124
  clearMediaSelectionRef.current();
27076
27125
  }
@@ -27091,13 +27140,6 @@ function OhhwellsBridge() {
27091
27140
  return;
27092
27141
  }
27093
27142
  }
27094
- if (isInsideFloatingPanel(target)) return;
27095
- if (target.closest("[data-ohw-form-toolbar]")) return;
27096
- if (target.closest(
27097
- '[data-ohw-field-toolbar], [data-ohw-field-type-picker], [data-radix-popper-content-wrapper], [role="menu"], [data-slot="dropdown-menu-content"]'
27098
- )) {
27099
- return;
27100
- }
27101
27143
  {
27102
27144
  const formEl = getFormElement(target);
27103
27145
  const onSuccessText = target.closest(`[${SUCCESS_TEXT_ATTR}]`);
@@ -27477,6 +27519,9 @@ function OhhwellsBridge() {
27477
27519
  setHoveredItemRect(null);
27478
27520
  hoveredNavContainerRef.current = null;
27479
27521
  setHoveredNavContainerRect(null);
27522
+ siblingHintElRef.current = null;
27523
+ setSiblingHintRect(null);
27524
+ setSiblingHintRects([]);
27480
27525
  return;
27481
27526
  }
27482
27527
  {
@@ -27597,7 +27642,6 @@ function OhhwellsBridge() {
27597
27642
  hoveredNavContainerRef.current = null;
27598
27643
  setHoveredNavContainerRect(null);
27599
27644
  hoveredItemElRef.current = editable;
27600
- setHoveredItemRect(isSelectedForHoverRef.current(editable) ? null : editable.getBoundingClientRect());
27601
27645
  }
27602
27646
  }
27603
27647
  }
@@ -28307,7 +28351,11 @@ function OhhwellsBridge() {
28307
28351
  };
28308
28352
  const handleMouseMove = (e) => {
28309
28353
  const { clientX, clientY } = e;
28310
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
28354
+ if (pointOwnedByFloatingPanel(clientX, clientY)) {
28355
+ dismissImageHover();
28356
+ clearImageHover();
28357
+ return;
28358
+ }
28311
28359
  if (isNavDrawerOpen() && isPointOutsideOpenDrawer(clientX, clientY)) {
28312
28360
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
28313
28361
  formHoverElRef.current = null;
@@ -28336,7 +28384,12 @@ function OhhwellsBridge() {
28336
28384
  setHoveredItemRect(null);
28337
28385
  hoveredNavContainerRef.current = null;
28338
28386
  setHoveredNavContainerRect(null);
28387
+ siblingHintElRef.current = null;
28388
+ setSiblingHintRect(null);
28389
+ setSiblingHintRects([]);
28339
28390
  dismissImageHover();
28391
+ clearImageHover();
28392
+ setSectionGap(null);
28340
28393
  return;
28341
28394
  }
28342
28395
  if (probeSocialsRowAt(clientX, clientY)) return;
@@ -28348,7 +28401,11 @@ function OhhwellsBridge() {
28348
28401
  if (e.data?.type !== "ow:pointer-sync") return;
28349
28402
  const { clientX, clientY } = e.data;
28350
28403
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
28351
- if (pointOwnedByFloatingPanel(clientX, clientY)) return;
28404
+ if (pointOwnedByFloatingPanel(clientX, clientY)) {
28405
+ dismissImageHover();
28406
+ clearImageHover();
28407
+ return;
28408
+ }
28352
28409
  if (isNavDrawerOpen() && isPointOutsideOpenDrawer(clientX, clientY)) return;
28353
28410
  if (probeSocialsRowAt(clientX, clientY)) return;
28354
28411
  probeSectionGapAt(clientX, clientY);
@@ -28700,6 +28757,7 @@ function OhhwellsBridge() {
28700
28757
  applyAiFooterFromContent(content);
28701
28758
  if (typeof content[STYLE_STORE_KEY] === "string") {
28702
28759
  stylesRef.current = content[STYLE_STORE_KEY];
28760
+ document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
28703
28761
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
28704
28762
  }
28705
28763
  applyBrandChrome(content);
@@ -28713,12 +28771,12 @@ function OhhwellsBridge() {
28713
28771
  }
28714
28772
  if (key === AI_SECTIONS_KEY) continue;
28715
28773
  if (key === AI_FOOTER_KEY) continue;
28774
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
28775
+ if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
28716
28776
  if (key === BRAND_KIT_KEY) continue;
28717
28777
  if (key === STYLE_STORE_KEY) continue;
28718
28778
  if (BRAND_CHROME_KEYS.has(key)) continue;
28719
- if (key === LOGO_PLACEHOLDER_KEY) continue;
28720
28779
  if (key === CUSTOM_CODE_AFTER_HEAD_KEY || key === CUSTOM_CODE_BEFORE_HEAD_KEY) continue;
28721
- if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
28722
28780
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
28723
28781
  continue;
28724
28782
  }
@@ -29255,6 +29313,12 @@ function OhhwellsBridge() {
29255
29313
  closeLinkPopoverRef.current();
29256
29314
  return;
29257
29315
  }
29316
+ if (floatingPanelOpenRef.current) {
29317
+ setFloatingPanelRef.current(null);
29318
+ deselectRef.current();
29319
+ deactivateRef.current();
29320
+ return;
29321
+ }
29258
29322
  deselectRef.current();
29259
29323
  deactivateRef.current();
29260
29324
  clearMediaSelectionRef.current();
@@ -30017,10 +30081,10 @@ function OhhwellsBridge() {
30017
30081
  window.removeEventListener("message", handleDeleteSection);
30018
30082
  window.removeEventListener("message", handleDuplicateSection);
30019
30083
  window.removeEventListener("message", handleDeactivate);
30020
- document.documentElement.removeAttribute("data-ohw-panel-dragging");
30021
30084
  window.removeEventListener("message", handleToastAction);
30022
30085
  window.removeEventListener("message", handleFormCount);
30023
30086
  window.removeEventListener("message", handleUiEscape);
30087
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
30024
30088
  autoSaveTimers.current.forEach(clearTimeout);
30025
30089
  autoSaveTimers.current.clear();
30026
30090
  if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
@@ -30314,7 +30378,7 @@ function OhhwellsBridge() {
30314
30378
  postToParent2({
30315
30379
  type: "ow:ready",
30316
30380
  version: "1",
30317
- bridgeVersion: "0.1.132",
30381
+ bridgeVersion: "0.1.134",
30318
30382
  path: pathname,
30319
30383
  nodes: collectEditableNodes(editContentRef.current),
30320
30384
  sections