@ohhwells/bridge 0.1.132-next.358 → 0.1.132

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
@@ -2727,7 +2727,7 @@ var AI_MOBILE_CSS = [
2727
2727
  // page's first section. Top only (bottom needs no nav clearance); ~96px approximates the
2728
2728
  // template nav's own height (measured ~94px) — there's no shared `--ohw-header-h` var to read
2729
2729
  // 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-section-inner`.
2730
+ // pixel-exact fit. Horizontal spacing already comes from `data-ai-container`.
2731
2731
  '[data-ai-overlay-clearance] [data-ai-overlap-layer="overlay"]{padding-top:96px}',
2732
2732
  // A full-bleed carousel's height is pure arithmetic: it spans the whole window, so 16/9 turns
2733
2733
  // window width straight into height. Measured at 16/9 it came out ~90% of the viewport at EVERY
@@ -2743,10 +2743,8 @@ var AI_MOBILE_CSS = [
2743
2743
  "[data-ai-section]{overflow-x:hidden}",
2744
2744
  // `:not([data-ai-full-bleed])`: this `!important` gutter beat the inline `padding: 0` that a
2745
2745
  // 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. The footer's own
2747
- // container keeps the unguarded rule below — `data-ai-full-bleed` can never appear on it.
2748
- "[data-ai-section-inner]:not([data-ai-full-bleed]){padding:0 20px !important}",
2749
- "[data-ai-container]{padding:0 20px !important}",
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}",
2750
2748
  "[data-ai-row]{display:flex !important;flex-direction:column !important;align-items:stretch !important}",
2751
2749
  "[data-ai-cell]{width:100%;min-width:0}",
2752
2750
  // The rule above turns every row into a COLUMN flex container on phones, which silently breaks
@@ -2947,31 +2945,6 @@ function pricingCardEmphasis(featured, ctx, hasBg) {
2947
2945
  function textAttrs(ctx, path) {
2948
2946
  return ctx.keyFor ? { "data-ohw-key": ctx.keyFor(path), "data-ohw-editable": "text" } : {};
2949
2947
  }
2950
- var AI_RESPONSIVE_CSS = [
2951
- "@media (max-width: 960px) {",
2952
- " [data-ai-responsive] [data-ai-grid] { grid-template-columns: repeat(2, 1fr) !important; }",
2953
- ' [data-ai-responsive] [data-ai-grid="1"] { grid-template-columns: 1fr !important; }',
2954
- "}",
2955
- "@media (max-width: 640px) {",
2956
- " [data-ai-responsive] [data-ai-grid] { grid-template-columns: 1fr !important; }",
2957
- " [data-ai-responsive] [data-ai-columns] > * { grid-column: 1 / -1 !important; }",
2958
- // Group containers flatten to a column on phones; span placements come along for free.
2959
- // `:not([data-ai-group="overlap"])` for the same reason as the twin pair in AI_MOBILE_CSS — see
2960
- // the comment there. This sheet's copy is the narrower selector, so without the exemption it
2961
- // flattens an `overlap` hero below 640px even though the other rule stands down: measured a 390px
2962
- // viewport with the heading/CTA in flow *under* the image, 219px of section background where the
2963
- // layered text used to be.
2964
- ' [data-ai-responsive] [data-ai-group]:not([data-ai-group="overlap"]) { display: flex !important; flex-direction: column !important; }',
2965
- ' [data-ai-responsive] [data-ai-group]:not([data-ai-group="overlap"]) > * { grid-column: auto !important; }',
2966
- // `:not([data-ai-full-bleed])` for the same reason as the twin rule in AI_MOBILE_CSS: this one
2967
- // is narrower (0,2,0 vs 0,1,0) and also `!important`, so without the guard it wins outright and
2968
- // a full-bleed hero gets its 20px side band back below 640px only — the one width where the
2969
- // other rule had already been taught to stand down.
2970
- " [data-ai-responsive] [data-ai-section-inner]:not([data-ai-full-bleed]) { padding-left: 20px !important; padding-right: 20px !important; }",
2971
- " [data-ai-responsive] { overflow-x: hidden; }",
2972
- " [data-ai-responsive] img { max-width: 100%; }",
2973
- "}"
2974
- ].join("\n");
2975
2948
  var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
2976
2949
  function MediaBox({
2977
2950
  refValue,
@@ -2992,9 +2965,9 @@ function MediaBox({
2992
2965
  const url = refValue ? ctx.resolveMedia(refValue) : null;
2993
2966
  const isIcon = /^(lucide|simple):/.test(refValue);
2994
2967
  const aspectRatio = aspect && /^\d+:\d+$/.test(aspect) ? aspect.replace(":", " / ") : void 0;
2995
- const editAttrs = ctx.keyFor && editPath ? {
2968
+ const editAttrs = ctx.keyFor && editPath && !isIcon ? {
2996
2969
  "data-ohw-key": ctx.keyFor(editPath),
2997
- ...isIcon ? { "data-ohw-editable": "icon" } : genericImageEdit ? { "data-ohw-editable": "image" } : {}
2970
+ ...genericImageEdit ? { "data-ohw-editable": "image" } : {}
2998
2971
  } : {};
2999
2972
  if (isIcon) {
3000
2973
  const Icon = refValue.startsWith("lucide:") ? lucideByName(refValue.slice(7)) : null;
@@ -3002,7 +2975,6 @@ function MediaBox({
3002
2975
  "span",
3003
2976
  {
3004
2977
  "data-ai-icon": refValue,
3005
- ...editAttrs,
3006
2978
  style: {
3007
2979
  display: "inline-flex",
3008
2980
  width: 48,
@@ -4489,7 +4461,7 @@ function CollectionBlock({ node, ctx, path }) {
4489
4461
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
4490
4462
  "div",
4491
4463
  {
4492
- "data-ai-grid": String(itemsPerRow),
4464
+ "data-ai-grid": "",
4493
4465
  "data-ohw-pricing-collection": isPricing ? "" : void 0,
4494
4466
  style: {
4495
4467
  display: "grid",
@@ -4749,7 +4721,7 @@ function renderNode(node, ctx, path, ambientAlign) {
4749
4721
  },
4750
4722
  children: [
4751
4723
  i > 0 && ctx.heroOverlapFullBleed ? (
4752
- // The section's `data-ai-section-inner` gave up its max-width/gutter so the base
4724
+ // The section's `data-ai-container` gave up its max-width/gutter so the base
4753
4725
  // layer (this group's i===0 sibling) can span edge-to-edge — reintroduced here,
4754
4726
  // scoped to just the overlay text, so a heading/copy/CTA stack doesn't stretch
4755
4727
  // the full section width instead of reading at the usual content width.
@@ -5142,7 +5114,6 @@ function AiTreeRenderer({
5142
5114
  ...fillHeight ? { "data-ai-fill-height": "" } : {},
5143
5115
  ...needsOverlayClearance ? { "data-ai-overlay-clearance": "" } : {},
5144
5116
  ...bgAttrs,
5145
- "data-ai-responsive": "",
5146
5117
  style: {
5147
5118
  position: "relative",
5148
5119
  padding: `${padCss} 0`,
@@ -5156,13 +5127,12 @@ function AiTreeRenderer({
5156
5127
  ...fillHeight ? { display: "grid" } : {}
5157
5128
  },
5158
5129
  children: [
5159
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_RESPONSIVE_CSS }),
5160
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_MOBILE_CSS }),
5161
5130
  isOverlay && backgroundUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
5131
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_MOBILE_CSS }),
5162
5132
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
5163
5133
  "div",
5164
5134
  {
5165
- "data-ai-section-inner": "",
5135
+ "data-ai-container": "",
5166
5136
  ...fullBleedMedia ? { "data-ai-full-bleed": "" } : {},
5167
5137
  style: {
5168
5138
  position: "relative",
@@ -5179,7 +5149,7 @@ function AiTreeRenderer({
5179
5149
  children: tree.rows.map((row, r2) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
5180
5150
  "div",
5181
5151
  {
5182
- "data-ai-columns": "",
5152
+ "data-ai-row": "",
5183
5153
  style: {
5184
5154
  display: "grid",
5185
5155
  gridTemplateColumns: "repeat(12, 1fr)",
@@ -5835,10 +5805,9 @@ function applyAiSectionsToDom(state, options) {
5835
5805
  if (existing && existing.serialized === serialized && existing.container.isConnected) {
5836
5806
  continue;
5837
5807
  }
5808
+ const isNewMount = !existing || !existing.container.isConnected;
5838
5809
  let container = existing?.container;
5839
- let isNewMount = false;
5840
5810
  if (!container || !container.isConnected) {
5841
- isNewMount = true;
5842
5811
  existing?.root.unmount();
5843
5812
  container?.remove();
5844
5813
  container = document.createElement("div");
@@ -13406,9 +13375,17 @@ var Button = React7.forwardRef(
13406
13375
  );
13407
13376
  Button.displayName = "Button";
13408
13377
 
13409
- // src/ui/media-skeleton.ts
13378
+ // src/ui/MediaOverlay.tsx
13379
+ var import_jsx_runtime16 = require("react/jsx-runtime");
13410
13380
  var MEDIA_UPLOAD_FADE_MS = 300;
13411
- var MEDIA_SKELETON_CSS = `
13381
+ var VIDEO_SETTINGS_BAR_INSET = 8;
13382
+ var OVERLAY_BUTTON_STYLE = {
13383
+ pointerEvents: "auto",
13384
+ fontFamily: "Inter, sans-serif",
13385
+ fontSize: 12,
13386
+ color: "#000"
13387
+ };
13388
+ var SKELETON_CSS = `
13412
13389
  @keyframes ohw-media-shimmer {
13413
13390
  0% { transform: translateX(-100%); }
13414
13391
  100% { transform: translateX(100%); }
@@ -13425,16 +13402,6 @@ var MEDIA_SKELETON_CSS = `
13425
13402
  animation: ohw-media-shimmer 1.4s infinite;
13426
13403
  }
13427
13404
  `;
13428
-
13429
- // src/ui/MediaOverlay.tsx
13430
- var import_jsx_runtime16 = require("react/jsx-runtime");
13431
- var VIDEO_SETTINGS_BAR_INSET = 8;
13432
- var OVERLAY_BUTTON_STYLE = {
13433
- pointerEvents: "auto",
13434
- fontFamily: "Inter, sans-serif",
13435
- fontSize: 12,
13436
- color: "#000"
13437
- };
13438
13405
  var REPLACE_BUTTON_INSET = 12;
13439
13406
  function replaceButtonMode(image, button) {
13440
13407
  if (!button.fullWidth || !button.height) return "none";
@@ -13473,7 +13440,6 @@ function MediaOverlay({
13473
13440
  (prev) => prev && prev.fullWidth === width && prev.height === height ? prev : { fullWidth: width, height }
13474
13441
  );
13475
13442
  }, [isVideo]);
13476
- const replaceLabel = isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image";
13477
13443
  const replaceMode = buttonSize ? replaceButtonMode({ width: rect.width, height: rect.height }, buttonSize) : "none";
13478
13444
  const box = {
13479
13445
  position: "fixed",
@@ -13503,7 +13469,7 @@ function MediaOverlay({
13503
13469
  "data-ohw-media-skeleton": "",
13504
13470
  "aria-hidden": true,
13505
13471
  style: { ...box, pointerEvents: "none" },
13506
- children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("style", { children: MEDIA_SKELETON_CSS })
13472
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("style", { children: SKELETON_CSS })
13507
13473
  }
13508
13474
  );
13509
13475
  }
@@ -13572,10 +13538,11 @@ function MediaOverlay({
13572
13538
  className: "flex items-center justify-center cursor-pointer",
13573
13539
  style: {
13574
13540
  ...box,
13575
- // Visual only. `pointer-events: auto` on this full-bleed rect covered the navbar
13576
- // (hero bg includes the absolute TopNav) and swallowed CTA press-drag. Text overlay
13577
- // and cueOnly also need clicks to fall through — Replace / video controls opt back in.
13578
- pointerEvents: "none",
13541
+ // When text overlays this media, let clicks fall THROUGH to the text underneath. In the
13542
+ // parent this needed a whole `ow:click-at` round-trip to re-hit-test inside the iframe;
13543
+ // in-document, pointer-events does it natively. The button below opts back in, so
13544
+ // Replace still works.
13545
+ pointerEvents: hover.hasTextOverlap || cueOnly ? "none" : "auto",
13579
13546
  // Selected: a firm component ring with no wash, so the image reads as chosen rather
13580
13547
  // than hovered. Hover keeps the existing tinted preview.
13581
13548
  boxShadow: selected ? "inset 0 0 0 2px var(--color-bridge-primary)" : "inset 0 0 0 1.5px var(--color-bridge-primary)",
@@ -13602,17 +13569,17 @@ function MediaOverlay({
13602
13569
  },
13603
13570
  children: [
13604
13571
  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 }),
13605
- replaceLabel
13572
+ isVideo ? "Replace video" : "Replace image"
13606
13573
  ]
13607
13574
  }
13608
13575
  ),
13609
- showChrome && replaceMode !== "none" && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
13576
+ replaceMode === "none" || cueOnly ? null : /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
13610
13577
  Button,
13611
13578
  {
13612
13579
  "data-ohw-media-overlay": "",
13613
13580
  variant: "outline",
13614
13581
  size: "sm",
13615
- "aria-label": replaceLabel,
13582
+ "aria-label": isVideo ? "Replace video" : "Replace image",
13616
13583
  className: "gap-1.5 cursor-pointer hover:bg-background",
13617
13584
  style: {
13618
13585
  ...OVERLAY_BUTTON_STYLE,
@@ -13635,7 +13602,7 @@ function MediaOverlay({
13635
13602
  },
13636
13603
  children: [
13637
13604
  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 }),
13638
- replaceMode === "full" ? replaceLabel : null
13605
+ replaceMode === "full" ? isVideo ? "Replace video" : hover.elementType === "bg-image" ? "Replace background" : "Replace image" : null
13639
13606
  ]
13640
13607
  }
13641
13608
  )
@@ -15932,17 +15899,6 @@ function setCarouselSlideAsset(tree, index, assetId) {
15932
15899
  return true;
15933
15900
  });
15934
15901
  }
15935
- function carouselAutoplay(tree) {
15936
- return findCarouselBlock(tree)?.slots?.autoplay === true;
15937
- }
15938
- function setCarouselAutoplay(tree, on) {
15939
- return withCarousel(tree, (block) => {
15940
- const slots = { ...block.slots, autoplay: on };
15941
- if (!on) delete slots.interval;
15942
- block.slots = slots;
15943
- return true;
15944
- });
15945
- }
15946
15902
 
15947
15903
  // src/ui/ai-section/AiFadeCarouselOverlay.tsx
15948
15904
  var import_jsx_runtime22 = require("react/jsx-runtime");
@@ -16056,85 +16012,10 @@ function Pill4({ label, onClick, disabled }) {
16056
16012
  }
16057
16013
  );
16058
16014
  }
16059
- function TogglePill({
16060
- label,
16061
- on,
16062
- onToggle,
16063
- disabled
16064
- }) {
16065
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
16066
- "button",
16067
- {
16068
- type: "button",
16069
- role: "switch",
16070
- "aria-checked": on,
16071
- onClick: onToggle,
16072
- disabled,
16073
- title: on ? `${label} on \u2014 click to turn off` : `${label} off \u2014 click to turn on`,
16074
- style: {
16075
- height: 28,
16076
- padding: "0 8px 0 10px",
16077
- display: "inline-flex",
16078
- alignItems: "center",
16079
- gap: 8,
16080
- borderRadius: 999,
16081
- border: "none",
16082
- cursor: disabled ? "default" : "pointer",
16083
- background: "#ffffff",
16084
- color: OHW_BLUE_DARK4,
16085
- opacity: disabled ? 0.4 : 1,
16086
- fontSize: 12,
16087
- lineHeight: "16px",
16088
- fontFamily: "Figtree, ui-sans-serif, system-ui, sans-serif",
16089
- whiteSpace: "nowrap",
16090
- flex: "0 0 auto"
16091
- },
16092
- children: [
16093
- label,
16094
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
16095
- "span",
16096
- {
16097
- "aria-hidden": true,
16098
- style: {
16099
- position: "relative",
16100
- width: 26,
16101
- height: 15,
16102
- borderRadius: 999,
16103
- flex: "0 0 auto",
16104
- // Off is a grey track, not a washed-out blue: the pill's own white background already
16105
- // carries the brand colour in the text, and a pale blue track next to it read as
16106
- // "disabled control" rather than "switch that is off".
16107
- background: on ? OHW_BLUE_DARK4 : "#CBD5E1",
16108
- transition: "background 120ms ease"
16109
- },
16110
- children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
16111
- "span",
16112
- {
16113
- style: {
16114
- position: "absolute",
16115
- top: 2,
16116
- left: on ? 13 : 2,
16117
- width: 11,
16118
- height: 11,
16119
- borderRadius: "50%",
16120
- background: "#ffffff",
16121
- boxShadow: "0 1px 2px rgba(0,0,0,0.3)",
16122
- transition: "left 120ms ease"
16123
- }
16124
- }
16125
- )
16126
- }
16127
- )
16128
- ]
16129
- }
16130
- );
16131
- }
16132
16015
  function CarouselSlideToolbar({
16133
16016
  pending,
16134
16017
  error,
16135
- autoplay,
16136
- onChangeImage,
16137
- onToggleAutoplay
16018
+ onChangeImage
16138
16019
  }) {
16139
16020
  return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
16140
16021
  "div",
@@ -16151,18 +16032,11 @@ function CarouselSlideToolbar({
16151
16032
  padding: 8,
16152
16033
  borderRadius: 12,
16153
16034
  boxShadow: "0 8px 24px rgba(0,0,0,0.25)",
16154
- // Not dimmed while pending, only inert. The toolbar used to drop to 0.6 opacity for the
16155
- // length of the validate round-trip, which on a local backend is short enough that the
16156
- // autoplay toggle just flashed — a dim and an un-dim with nothing in between. Progress is
16157
- // reported where the change is: the switch moves at once (optimistically), and an image
16158
- // swap raises the skeleton over the slide.
16159
- pointerEvents: pending ? "none" : "auto"
16035
+ pointerEvents: pending ? "none" : "auto",
16036
+ opacity: pending ? 0.6 : 1
16160
16037
  },
16161
16038
  children: [
16162
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [
16163
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Pill4, { label: "Change Image", onClick: onChangeImage }),
16164
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TogglePill, { label: "Autoplay", on: autoplay, onToggle: onToggleAutoplay })
16165
- ] }),
16039
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Pill4, { label: "Change Image", onClick: onChangeImage }),
16166
16040
  error && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { style: { color: "#FCA5A5", fontSize: 11, maxWidth: 220, textAlign: "center" }, children: error })
16167
16041
  ]
16168
16042
  }
@@ -16178,24 +16052,8 @@ function AiFadeCarouselOverlay({
16178
16052
  const [active, setActive] = (0, import_react12.useState)(null);
16179
16053
  const [pending, setPending] = (0, import_react12.useState)(false);
16180
16054
  const [error, setError] = (0, import_react12.useState)(null);
16181
- const [autoplayOverride, setAutoplayOverride] = (0, import_react12.useState)(null);
16182
- const [swapping, setSwapping] = (0, import_react12.useState)(null);
16183
- const skeletonRef = (0, import_react12.useRef)(null);
16184
16055
  const activeRef = (0, import_react12.useRef)(null);
16185
16056
  activeRef.current = active;
16186
- (0, import_react12.useEffect)(() => {
16187
- setAutoplayOverride(null);
16188
- }, [active?.sectionId, active?.index]);
16189
- (0, import_react12.useEffect)(() => {
16190
- if (!swapping?.fadingOut || !skeletonRef.current) return;
16191
- const anim = skeletonRef.current.animate([{ opacity: 1 }, { opacity: 0 }], {
16192
- duration: MEDIA_UPLOAD_FADE_MS,
16193
- easing: "ease-out",
16194
- fill: "forwards"
16195
- });
16196
- anim.finished.then(() => setSwapping(null)).catch(() => setSwapping(null));
16197
- return () => anim.cancel();
16198
- }, [swapping?.fadingOut]);
16199
16057
  (0, import_react12.useEffect)(() => {
16200
16058
  const onMove = (e) => {
16201
16059
  const t = e.target;
@@ -16336,27 +16194,6 @@ function AiFadeCarouselOverlay({
16336
16194
  children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react11.Star, { size: 14, style: { pointerEvents: "none" } })
16337
16195
  }
16338
16196
  ),
16339
- swapping && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
16340
- "div",
16341
- {
16342
- ref: skeletonRef,
16343
- "data-ohw-bridge": "",
16344
- "data-ohw-media-skeleton": "",
16345
- "aria-hidden": true,
16346
- style: {
16347
- position: "fixed",
16348
- top: swapping.rect.top,
16349
- left: swapping.rect.left,
16350
- width: swapping.rect.width,
16351
- height: swapping.rect.height,
16352
- // Under the toolbar (2147483647) so its controls stay visible and clickable over the
16353
- // slide being swapped, and inert so it cannot eat the click that dismisses them.
16354
- zIndex: 2147483646,
16355
- pointerEvents: "none"
16356
- },
16357
- children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("style", { children: MEDIA_SKELETON_CSS })
16358
- }
16359
- ),
16360
16197
  activeItemRect && active && activeEntry && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
16361
16198
  ItemInteractionLayer,
16362
16199
  {
@@ -16367,36 +16204,14 @@ function AiFadeCarouselOverlay({
16367
16204
  {
16368
16205
  pending,
16369
16206
  error,
16370
- autoplay: autoplayOverride ?? carouselAutoplay(activeEntry.tree),
16371
- onToggleAutoplay: () => {
16372
- const next = !(autoplayOverride ?? carouselAutoplay(activeEntry.tree));
16373
- setAutoplayOverride(next);
16374
- void runMutation(active.sectionId, (tree) => setCarouselAutoplay(tree, next)).then(
16375
- (ok) => {
16376
- if (!ok) setAutoplayOverride(null);
16377
- }
16378
- );
16379
- },
16380
16207
  onChangeImage: () => {
16381
- void requestAssetPick2(postToParent2).then(async (picked) => {
16208
+ void requestAssetPick2(postToParent2).then((picked) => {
16382
16209
  if (!picked) return;
16383
- const slideRect = readRect5(active);
16384
- if (slideRect) setSwapping({ rect: slideRect, fadingOut: false });
16385
- const ok = await runMutation(
16210
+ void runMutation(
16386
16211
  active.sectionId,
16387
16212
  (tree) => setCarouselSlideAsset(tree, active.index, picked.assetId),
16388
16213
  { [picked.assetId]: picked.url }
16389
16214
  );
16390
- if (!ok) {
16391
- setSwapping(null);
16392
- return;
16393
- }
16394
- const preload = new Image();
16395
- const done = () => setSwapping((s) => s ? { ...s, fadingOut: true } : s);
16396
- preload.onload = done;
16397
- preload.onerror = done;
16398
- preload.src = picked.url;
16399
- if (preload.complete) done();
16400
16215
  });
16401
16216
  }
16402
16217
  }
@@ -19867,7 +19682,6 @@ function readLogoSizeState(content, placement) {
19867
19682
  function getLogoElement(el) {
19868
19683
  const marked = el.closest('[data-ohw-role="logo"], [data-ohw-logo]');
19869
19684
  if (marked) return marked;
19870
- if (el.closest('[data-ohw-editable="icon"]')) return null;
19871
19685
  const root = el.closest("nav, [data-ohw-nav-root], footer");
19872
19686
  if (!root) return null;
19873
19687
  const anchor = el.closest("a");
@@ -23359,7 +23173,7 @@ function getNavigationSelectionParent(el) {
23359
23173
  if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup2(el))) {
23360
23174
  return resolveFooterLinksContainerFrom(el);
23361
23175
  }
23362
- 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)) {
23176
+ if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-nav-drawer") || isFooterLinksContainer(el)) {
23363
23177
  return getNavigationRoot(el);
23364
23178
  }
23365
23179
  return null;
@@ -23581,6 +23395,7 @@ var ICONS = {
23581
23395
  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"/>',
23582
23396
  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"/>'
23583
23397
  };
23398
+ var HEIGHT_SETTLE_DELAYS = [150, 400, 800];
23584
23399
  var SELECTION_CHROME_GAP2 = 4;
23585
23400
  var TOOLBAR_STROKE_GAP2 = 4;
23586
23401
  var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
@@ -24560,6 +24375,21 @@ function OhhwellsBridge() {
24560
24375
  const [isItemDragging, setIsItemDragging] = (0, import_react21.useState)(false);
24561
24376
  const [isFooterFrameSelection, setIsFooterFrameSelection] = (0, import_react21.useState)(false);
24562
24377
  isFooterFrameSelectionRef.current = isFooterFrameSelection;
24378
+ const [floatingPanel, setFloatingPanel] = (0, import_react21.useState)(null);
24379
+ const floatingPanelOpenRef = (0, import_react21.useRef)(false);
24380
+ floatingPanelOpenRef.current = floatingPanel !== null;
24381
+ const floatingPanelRef = (0, import_react21.useRef)(null);
24382
+ floatingPanelRef.current = floatingPanel;
24383
+ const [floatingPanelPos, setFloatingPanelPos] = (0, import_react21.useState)(null);
24384
+ const buttonCollectionDragRef = (0, import_react21.useRef)(null);
24385
+ const buttonDropInsertIndexRef = (0, import_react21.useRef)(null);
24386
+ const [buttonDropSlots, setButtonDropSlots] = (0, import_react21.useState)([]);
24387
+ const [activeButtonDropIndex, setActiveButtonDropIndex] = (0, import_react21.useState)(null);
24388
+ const buttonPointerDragRef = (0, import_react21.useRef)(null);
24389
+ const [buttonStyleTick, setButtonStyleTick] = (0, import_react21.useState)(0);
24390
+ const [logoSizeDraft, setLogoSizeDraft] = (0, import_react21.useState)(null);
24391
+ const [editorViewport, setEditorViewport] = (0, import_react21.useState)("desktop");
24392
+ const [parentScrollSnap, setParentScrollSnap] = (0, import_react21.useState)(null);
24563
24393
  const [navDropdownPreviewOpen, setNavDropdownPreviewOpen] = (0, import_react21.useState)(null);
24564
24394
  const [footerHeadingVisible, setFooterHeadingVisible] = (0, import_react21.useState)(null);
24565
24395
  const footerDragRef = (0, import_react21.useRef)(null);
@@ -24585,21 +24415,6 @@ function OhhwellsBridge() {
24585
24415
  const stylesRef = (0, import_react21.useRef)("");
24586
24416
  const appliedDrawerRef = (0, import_react21.useRef)(null);
24587
24417
  const pendingDeleteUndoRef = (0, import_react21.useRef)(null);
24588
- const [floatingPanel, setFloatingPanel] = (0, import_react21.useState)(null);
24589
- const floatingPanelOpenRef = (0, import_react21.useRef)(false);
24590
- const floatingPanelRef = (0, import_react21.useRef)(null);
24591
- floatingPanelRef.current = floatingPanel;
24592
- const setFloatingPanelRef = (0, import_react21.useRef)(setFloatingPanel);
24593
- const [floatingPanelPos, setFloatingPanelPos] = (0, import_react21.useState)(null);
24594
- const buttonCollectionDragRef = (0, import_react21.useRef)(null);
24595
- const buttonDropInsertIndexRef = (0, import_react21.useRef)(null);
24596
- const [buttonDropSlots, setButtonDropSlots] = (0, import_react21.useState)([]);
24597
- const [activeButtonDropIndex, setActiveButtonDropIndex] = (0, import_react21.useState)(null);
24598
- const buttonPointerDragRef = (0, import_react21.useRef)(null);
24599
- const [buttonStyleTick, setButtonStyleTick] = (0, import_react21.useState)(0);
24600
- const [logoSizeDraft, setLogoSizeDraft] = (0, import_react21.useState)(null);
24601
- const [editorViewport, setEditorViewport] = (0, import_react21.useState)("desktop");
24602
- const [parentScrollSnap, setParentScrollSnap] = (0, import_react21.useState)(null);
24603
24418
  const [sitePages, setSitePages] = (0, import_react21.useState)([]);
24604
24419
  const [sectionsByPath, setSectionsByPath] = (0, import_react21.useState)({});
24605
24420
  const sectionsPrefetchGenRef = (0, import_react21.useRef)(0);
@@ -24608,18 +24423,7 @@ function OhhwellsBridge() {
24608
24423
  const linkPopoverOpenRef = (0, import_react21.useRef)(false);
24609
24424
  const linkPopoverGraceUntilRef = (0, import_react21.useRef)(0);
24610
24425
  setLinkPopoverRef.current = setLinkPopover;
24611
- setFloatingPanelRef.current = setFloatingPanel;
24612
24426
  linkPopoverSessionRef.current = linkPopover;
24613
- floatingPanelOpenRef.current = Boolean(floatingPanel);
24614
- (0, import_react21.useEffect)(() => {
24615
- const syncViewport = () => {
24616
- const next = window.innerWidth <= 480 ? "mobile" : "desktop";
24617
- setEditorViewport((prev) => prev === next ? prev : next);
24618
- };
24619
- syncViewport();
24620
- window.addEventListener("resize", syncViewport);
24621
- return () => window.removeEventListener("resize", syncViewport);
24622
- }, []);
24623
24427
  (0, import_react21.useEffect)(() => {
24624
24428
  setSectionOrderPersistHandler((entries) => {
24625
24429
  const currentPath = window.location.pathname;
@@ -26298,7 +26102,6 @@ function OhhwellsBridge() {
26298
26102
  applyAiFooterFromContent(content);
26299
26103
  if (typeof content[STYLE_STORE_KEY] === "string") {
26300
26104
  stylesRef.current = content[STYLE_STORE_KEY];
26301
- document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
26302
26105
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
26303
26106
  }
26304
26107
  applyBrandChrome(content);
@@ -26308,12 +26111,12 @@ function OhhwellsBridge() {
26308
26111
  if (key === "__ohw_sections") continue;
26309
26112
  if (key === AI_SECTIONS_KEY) continue;
26310
26113
  if (key === AI_FOOTER_KEY) continue;
26311
- if (key === LOGO_PLACEHOLDER_KEY) continue;
26312
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
26313
26114
  if (key === BRAND_KIT_KEY) continue;
26314
26115
  if (key === STYLE_STORE_KEY) continue;
26315
26116
  if (BRAND_CHROME_KEYS.has(key)) continue;
26117
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
26316
26118
  if (key === CUSTOM_CODE_AFTER_HEAD_KEY || key === CUSTOM_CODE_BEFORE_HEAD_KEY) continue;
26119
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
26317
26120
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
26318
26121
  continue;
26319
26122
  }
@@ -26527,18 +26330,17 @@ function OhhwellsBridge() {
26527
26330
  }
26528
26331
  applyAiFooterFromContent(content);
26529
26332
  if (typeof content[STYLE_STORE_KEY] === "string") {
26530
- document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
26531
26333
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
26532
26334
  }
26533
26335
  for (const [key, val] of Object.entries(content)) {
26534
26336
  if (key === "__ohw_sections") continue;
26535
26337
  if (key === AI_SECTIONS_KEY) continue;
26536
26338
  if (key === AI_FOOTER_KEY) continue;
26537
- if (key === LOGO_PLACEHOLDER_KEY) continue;
26538
- if (LOGO_IMAGE_KEYS.includes(key)) continue;
26539
26339
  if (key === BRAND_KIT_KEY) continue;
26540
26340
  if (key === STYLE_STORE_KEY) continue;
26541
26341
  if (BRAND_CHROME_KEYS.has(key)) continue;
26342
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
26343
+ if (LOGO_IMAGE_KEYS.includes(key)) continue;
26542
26344
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
26543
26345
  continue;
26544
26346
  }
@@ -26794,11 +26596,26 @@ function OhhwellsBridge() {
26794
26596
  const t2 = setTimeout(measure, 500);
26795
26597
  const ro = new ResizeObserver(schedule);
26796
26598
  ro.observe(document.body);
26599
+ let lastWidth = window.innerWidth;
26600
+ let resizeTimers = [];
26601
+ const clearResizeTimers = () => {
26602
+ resizeTimers.forEach(clearTimeout);
26603
+ resizeTimers = [];
26604
+ };
26605
+ const handleResize = () => {
26606
+ if (window.innerWidth === lastWidth) return;
26607
+ lastWidth = window.innerWidth;
26608
+ clearResizeTimers();
26609
+ resizeTimers = HEIGHT_SETTLE_DELAYS.map((delay) => setTimeout(measure, delay));
26610
+ };
26611
+ window.addEventListener("resize", handleResize);
26797
26612
  return () => {
26798
26613
  clearTimeout(t1);
26799
26614
  clearTimeout(t2);
26800
26615
  if (raf != null) cancelAnimationFrame(raf);
26801
26616
  ro.disconnect();
26617
+ clearResizeTimers();
26618
+ window.removeEventListener("resize", handleResize);
26802
26619
  };
26803
26620
  }, [pathname, isEditMode, postToParent2]);
26804
26621
  (0, import_react21.useEffect)(() => {
@@ -27093,13 +26910,6 @@ function OhhwellsBridge() {
27093
26910
  if (target.closest("[data-ohw-state-toggle]")) return;
27094
26911
  if (target.closest("[data-ohw-max-badge]")) return;
27095
26912
  if (isInsideLinkEditor(target)) return;
27096
- if (isInsideFloatingPanel(target)) return;
27097
- if (target.closest("[data-ohw-form-toolbar]")) return;
27098
- if (target.closest(
27099
- '[data-ohw-field-toolbar], [data-ohw-field-type-picker], [data-radix-popper-content-wrapper], [role="menu"], [data-slot="dropdown-menu-content"]'
27100
- )) {
27101
- return;
27102
- }
27103
26913
  if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
27104
26914
  clearMediaSelectionRef.current();
27105
26915
  }
@@ -27120,6 +26930,13 @@ function OhhwellsBridge() {
27120
26930
  return;
27121
26931
  }
27122
26932
  }
26933
+ if (isInsideFloatingPanel(target)) return;
26934
+ if (target.closest("[data-ohw-form-toolbar]")) return;
26935
+ if (target.closest(
26936
+ '[data-ohw-field-toolbar], [data-ohw-field-type-picker], [data-radix-popper-content-wrapper], [role="menu"], [data-slot="dropdown-menu-content"]'
26937
+ )) {
26938
+ return;
26939
+ }
27123
26940
  {
27124
26941
  const formEl = getFormElement(target);
27125
26942
  const onSuccessText = target.closest(`[${SUCCESS_TEXT_ATTR}]`);
@@ -27499,9 +27316,6 @@ function OhhwellsBridge() {
27499
27316
  setHoveredItemRect(null);
27500
27317
  hoveredNavContainerRef.current = null;
27501
27318
  setHoveredNavContainerRect(null);
27502
- siblingHintElRef.current = null;
27503
- setSiblingHintRect(null);
27504
- setSiblingHintRects([]);
27505
27319
  return;
27506
27320
  }
27507
27321
  {
@@ -27622,6 +27436,7 @@ function OhhwellsBridge() {
27622
27436
  hoveredNavContainerRef.current = null;
27623
27437
  setHoveredNavContainerRect(null);
27624
27438
  hoveredItemElRef.current = editable;
27439
+ setHoveredItemRect(isSelectedForHoverRef.current(editable) ? null : editable.getBoundingClientRect());
27625
27440
  }
27626
27441
  }
27627
27442
  }
@@ -28331,11 +28146,7 @@ function OhhwellsBridge() {
28331
28146
  };
28332
28147
  const handleMouseMove = (e) => {
28333
28148
  const { clientX, clientY } = e;
28334
- if (pointOwnedByFloatingPanel(clientX, clientY)) {
28335
- dismissImageHover();
28336
- clearImageHover();
28337
- return;
28338
- }
28149
+ if (pointOwnedByFloatingPanel(clientX, clientY)) return;
28339
28150
  if (isNavDrawerOpen() && isPointOutsideOpenDrawer(clientX, clientY)) {
28340
28151
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
28341
28152
  formHoverElRef.current = null;
@@ -28364,12 +28175,7 @@ function OhhwellsBridge() {
28364
28175
  setHoveredItemRect(null);
28365
28176
  hoveredNavContainerRef.current = null;
28366
28177
  setHoveredNavContainerRect(null);
28367
- siblingHintElRef.current = null;
28368
- setSiblingHintRect(null);
28369
- setSiblingHintRects([]);
28370
28178
  dismissImageHover();
28371
- clearImageHover();
28372
- setSectionGap(null);
28373
28179
  return;
28374
28180
  }
28375
28181
  if (probeSocialsRowAt(clientX, clientY)) return;
@@ -28381,11 +28187,7 @@ function OhhwellsBridge() {
28381
28187
  if (e.data?.type !== "ow:pointer-sync") return;
28382
28188
  const { clientX, clientY } = e.data;
28383
28189
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
28384
- if (pointOwnedByFloatingPanel(clientX, clientY)) {
28385
- dismissImageHover();
28386
- clearImageHover();
28387
- return;
28388
- }
28190
+ if (pointOwnedByFloatingPanel(clientX, clientY)) return;
28389
28191
  if (isNavDrawerOpen() && isPointOutsideOpenDrawer(clientX, clientY)) return;
28390
28192
  if (probeSocialsRowAt(clientX, clientY)) return;
28391
28193
  probeSectionGapAt(clientX, clientY);
@@ -28737,7 +28539,6 @@ function OhhwellsBridge() {
28737
28539
  applyAiFooterFromContent(content);
28738
28540
  if (typeof content[STYLE_STORE_KEY] === "string") {
28739
28541
  stylesRef.current = content[STYLE_STORE_KEY];
28740
- document.querySelectorAll('[data-ohw-editable="form"]').forEach((f) => markFormFields(f));
28741
28542
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
28742
28543
  }
28743
28544
  applyBrandChrome(content);
@@ -28751,12 +28552,12 @@ function OhhwellsBridge() {
28751
28552
  }
28752
28553
  if (key === AI_SECTIONS_KEY) continue;
28753
28554
  if (key === AI_FOOTER_KEY) continue;
28754
- if (key === LOGO_PLACEHOLDER_KEY) continue;
28755
- if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
28756
28555
  if (key === BRAND_KIT_KEY) continue;
28757
28556
  if (key === STYLE_STORE_KEY) continue;
28758
28557
  if (BRAND_CHROME_KEYS.has(key)) continue;
28558
+ if (key === LOGO_PLACEHOLDER_KEY) continue;
28759
28559
  if (key === CUSTOM_CODE_AFTER_HEAD_KEY || key === CUSTOM_CODE_BEFORE_HEAD_KEY) continue;
28560
+ if (LOGO_IMAGE_KEYS.includes(key) && !String(val ?? "").trim()) continue;
28760
28561
  if (typeof content[AI_FOOTER_KEY] === "string" && content[AI_FOOTER_KEY] && isAiFooterOwnedContentKey(key)) {
28761
28562
  continue;
28762
28563
  }
@@ -29293,12 +29094,6 @@ function OhhwellsBridge() {
29293
29094
  closeLinkPopoverRef.current();
29294
29095
  return;
29295
29096
  }
29296
- if (floatingPanelOpenRef.current) {
29297
- setFloatingPanelRef.current(null);
29298
- deselectRef.current();
29299
- deactivateRef.current();
29300
- return;
29301
- }
29302
29097
  deselectRef.current();
29303
29098
  deactivateRef.current();
29304
29099
  clearMediaSelectionRef.current();
@@ -30061,10 +29856,10 @@ function OhhwellsBridge() {
30061
29856
  window.removeEventListener("message", handleDeleteSection);
30062
29857
  window.removeEventListener("message", handleDuplicateSection);
30063
29858
  window.removeEventListener("message", handleDeactivate);
29859
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
30064
29860
  window.removeEventListener("message", handleToastAction);
30065
29861
  window.removeEventListener("message", handleFormCount);
30066
29862
  window.removeEventListener("message", handleUiEscape);
30067
- document.documentElement.removeAttribute("data-ohw-panel-dragging");
30068
29863
  autoSaveTimers.current.forEach(clearTimeout);
30069
29864
  autoSaveTimers.current.clear();
30070
29865
  if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
@@ -30358,7 +30153,7 @@ function OhhwellsBridge() {
30358
30153
  postToParent2({
30359
30154
  type: "ow:ready",
30360
30155
  version: "1",
30361
- bridgeVersion: "0.1.132",
30156
+ bridgeVersion: "0.1.130",
30362
30157
  path: pathname,
30363
30158
  nodes: collectEditableNodes(editContentRef.current),
30364
30159
  sections