@ohhwells/bridge 0.1.14 → 0.1.16-next.8

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.js CHANGED
@@ -3396,7 +3396,9 @@ function ToggleGroupItem({
3396
3396
  Toggle,
3397
3397
  {
3398
3398
  pressed: groupValue === value,
3399
- onPressedChange: () => onValueChange(value),
3399
+ onPressedChange: (pressed) => {
3400
+ if (pressed) onValueChange(value);
3401
+ },
3400
3402
  className: cn(
3401
3403
  "text-foreground cursor-pointer hover:bg-transparent hover:text-foreground",
3402
3404
  "data-[state=on]:bg-background data-[state=on]:shadow-sm",
@@ -3431,8 +3433,30 @@ function isEditSessionActive() {
3431
3433
  return new URLSearchParams(q).get("mode") === "edit";
3432
3434
  }
3433
3435
 
3436
+ // src/ui/badge.tsx
3437
+ import { jsx as jsx3 } from "react/jsx-runtime";
3438
+ var badgeVariants = cva(
3439
+ "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
3440
+ {
3441
+ variants: {
3442
+ variant: {
3443
+ default: "border-transparent bg-primary text-primary-foreground",
3444
+ secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
3445
+ destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
3446
+ outline: "text-foreground"
3447
+ }
3448
+ },
3449
+ defaultVariants: {
3450
+ variant: "default"
3451
+ }
3452
+ }
3453
+ );
3454
+ function Badge({ className, variant, ...props }) {
3455
+ return /* @__PURE__ */ jsx3("div", { className: cn(badgeVariants({ variant }), className), ...props });
3456
+ }
3457
+
3434
3458
  // src/OhhwellsBridge.tsx
3435
- import { Fragment, jsx as jsx3, jsxs } from "react/jsx-runtime";
3459
+ import { Fragment, jsx as jsx4, jsxs } from "react/jsx-runtime";
3436
3460
  var PRIMARY = "#0885FE";
3437
3461
  var IMAGE_FADE_MS = 300;
3438
3462
  function runOpacityFade(el, onDone) {
@@ -3640,7 +3664,7 @@ var TOOLBAR_GROUPS = [
3640
3664
  ];
3641
3665
  function GlowFrame({ rect, elRef }) {
3642
3666
  const GAP = 6;
3643
- return /* @__PURE__ */ jsx3(
3667
+ return /* @__PURE__ */ jsx4(
3644
3668
  "div",
3645
3669
  {
3646
3670
  ref: elRef,
@@ -3691,7 +3715,7 @@ function FloatingToolbar({
3691
3715
  activeCommands
3692
3716
  }) {
3693
3717
  const { top, left, transform } = calcToolbarPos(rect, parentScroll);
3694
- return /* @__PURE__ */ jsx3(
3718
+ return /* @__PURE__ */ jsx4(
3695
3719
  "div",
3696
3720
  {
3697
3721
  ref: elRef,
@@ -3715,10 +3739,10 @@ function FloatingToolbar({
3715
3739
  whiteSpace: "nowrap"
3716
3740
  },
3717
3741
  children: TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ jsxs(React2.Fragment, { children: [
3718
- gi > 0 && /* @__PURE__ */ jsx3("span", { style: { display: "block", width: 1, height: 24, background: "#E7E5E4", flexShrink: 0 } }),
3742
+ gi > 0 && /* @__PURE__ */ jsx4("span", { style: { display: "block", width: 1, height: 24, background: "#E7E5E4", flexShrink: 0 } }),
3719
3743
  btns.map((btn) => {
3720
3744
  const isActive = activeCommands.has(btn.cmd);
3721
- return /* @__PURE__ */ jsx3(
3745
+ return /* @__PURE__ */ jsx4(
3722
3746
  "button",
3723
3747
  {
3724
3748
  title: btn.title,
@@ -3744,7 +3768,7 @@ function FloatingToolbar({
3744
3768
  flexShrink: 0,
3745
3769
  padding: 6
3746
3770
  },
3747
- children: /* @__PURE__ */ jsx3(
3771
+ children: /* @__PURE__ */ jsx4(
3748
3772
  "svg",
3749
3773
  {
3750
3774
  width: "16",
@@ -3777,7 +3801,7 @@ function StateToggle({
3777
3801
  states,
3778
3802
  onStateChange
3779
3803
  }) {
3780
- return /* @__PURE__ */ jsx3(
3804
+ return /* @__PURE__ */ jsx4(
3781
3805
  ToggleGroup,
3782
3806
  {
3783
3807
  "data-ohw-state-toggle": "",
@@ -3791,7 +3815,7 @@ function StateToggle({
3791
3815
  left: rect.right - 8,
3792
3816
  transform: "translateX(-100%)"
3793
3817
  },
3794
- children: states.map((state) => /* @__PURE__ */ jsx3(ToggleGroupItem, { value: state, children: state }, state))
3818
+ children: states.map((state) => /* @__PURE__ */ jsx4(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
3795
3819
  }
3796
3820
  );
3797
3821
  }
@@ -3801,20 +3825,19 @@ function OhhwellsBridge() {
3801
3825
  const router = useRouter();
3802
3826
  const searchParams = useSearchParams();
3803
3827
  const isEditMode = isEditSessionActive();
3804
- useEffect(() => {
3805
- if (!isEditMode) return;
3806
- const preconnect1 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.googleapis.com" });
3807
- const preconnect2 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.gstatic.com", crossOrigin: "" });
3808
- const stylesheet = Object.assign(document.createElement("link"), { rel: "stylesheet", href: "https://fonts.googleapis.com/css2?family=Figtree:ital,wght@0,300..900;1,300..900&display=swap" });
3809
- document.head.append(preconnect1, preconnect2, stylesheet);
3810
- return () => {
3811
- preconnect1.remove();
3812
- preconnect2.remove();
3813
- stylesheet.remove();
3814
- };
3815
- }, [isEditMode]);
3816
3828
  const [bridgeRoot, setBridgeRoot] = useState(null);
3817
3829
  useEffect(() => {
3830
+ const figtreeFontId = "ohw-figtree-font";
3831
+ if (!document.getElementById(figtreeFontId)) {
3832
+ const preconnect1 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.googleapis.com" });
3833
+ const preconnect2 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.gstatic.com", crossOrigin: "" });
3834
+ const stylesheet = Object.assign(document.createElement("link"), {
3835
+ id: figtreeFontId,
3836
+ rel: "stylesheet",
3837
+ href: "https://fonts.googleapis.com/css2?family=Figtree:ital,wght@0,300..900;1,300..900&display=swap"
3838
+ });
3839
+ document.head.append(preconnect1, preconnect2, stylesheet);
3840
+ }
3818
3841
  const el = document.createElement("div");
3819
3842
  el.setAttribute("data-ohw-bridge-root", "");
3820
3843
  el.setAttribute("data-ohw-bridge", "");
@@ -3845,6 +3868,10 @@ function OhhwellsBridge() {
3845
3868
  const toolbarElRef = useRef(null);
3846
3869
  const glowElRef = useRef(null);
3847
3870
  const hoveredImageRef = useRef(null);
3871
+ const hoveredImageHasTextOverlapRef = useRef(false);
3872
+ const hoveredGapRef = useRef(null);
3873
+ const imageUnhoverTimerRef = useRef(null);
3874
+ const imageShowTimerRef = useRef(null);
3848
3875
  const editStylesRef = useRef(null);
3849
3876
  const activateRef = useRef(() => {
3850
3877
  });
@@ -3858,6 +3885,7 @@ function OhhwellsBridge() {
3858
3885
  const [toggleState, setToggleState] = useState(null);
3859
3886
  const [maxBadge, setMaxBadge] = useState(null);
3860
3887
  const [activeCommands, setActiveCommands] = useState(/* @__PURE__ */ new Set());
3888
+ const [sectionGap, setSectionGap] = useState(null);
3861
3889
  useEffect(() => {
3862
3890
  const update = () => {
3863
3891
  const el = activeElRef.current;
@@ -3866,6 +3894,12 @@ function OhhwellsBridge() {
3866
3894
  if (!prev || !activeStateElRef.current) return prev;
3867
3895
  return { ...prev, rect: activeStateElRef.current.getBoundingClientRect() };
3868
3896
  });
3897
+ setSectionGap((prev) => {
3898
+ if (!prev) return prev;
3899
+ const anchor = document.querySelector(`[data-ohw-section="${prev.insertAfter}"]`);
3900
+ if (!anchor) return prev;
3901
+ return { ...prev, y: anchor.getBoundingClientRect().bottom };
3902
+ });
3869
3903
  };
3870
3904
  const vvp = window.visualViewport;
3871
3905
  if (!vvp) return;
@@ -3907,6 +3941,10 @@ function OhhwellsBridge() {
3907
3941
  const activate = useCallback((el) => {
3908
3942
  if (activeElRef.current === el) return;
3909
3943
  deactivate();
3944
+ if (hoveredImageRef.current) {
3945
+ hoveredImageRef.current = null;
3946
+ postToParentRef.current({ type: "ow:image-unhover" });
3947
+ }
3910
3948
  el.setAttribute("contenteditable", "true");
3911
3949
  el.removeAttribute("data-ohw-hovered");
3912
3950
  activeElRef.current = el;
@@ -3954,7 +3992,7 @@ function OhhwellsBridge() {
3954
3992
  }
3955
3993
  let cancelled = false;
3956
3994
  setFetchState("loading");
3957
- const apiUrl = globalThis.process?.env?.NEXT_PUBLIC_FLOWOPS_API_URL ?? "http://localhost:4005";
3995
+ const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
3958
3996
  fetch(`${apiUrl}/api/public/sites/${subdomain}/content`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
3959
3997
  if (cancelled) return;
3960
3998
  const content = data?.content ?? {};
@@ -4081,6 +4119,7 @@ function OhhwellsBridge() {
4081
4119
  [data-ohw-editable][contenteditable] *::selection { background: ${PRIMARY}59 !important; }
4082
4120
  [data-ohw-editable-state], [data-ohw-editable-state] * { pointer-events: none !important; }
4083
4121
  [data-ohw-editable-state][data-ohw-active-state] [data-ohw-editable] { pointer-events: auto !important; }
4122
+ [data-ohw-editable-state][data-ohw-active-state][data-ohw-editable] { pointer-events: auto !important; }
4084
4123
  `;
4085
4124
  const forceHover = document.createElement("style");
4086
4125
  forceHover.setAttribute("data-ohw-active-state-style", "");
@@ -4107,10 +4146,12 @@ function OhhwellsBridge() {
4107
4146
  if (editable) {
4108
4147
  if (editable.dataset.ohwEditable === "image" || editable.dataset.ohwEditable === "bg-image") {
4109
4148
  e.preventDefault();
4149
+ e.stopPropagation();
4110
4150
  postToParentRef.current({ type: "ow:image-pick", key: editable.dataset.ohwKey ?? "" });
4111
4151
  return;
4112
4152
  }
4113
4153
  e.preventDefault();
4154
+ e.stopPropagation();
4114
4155
  activateRef.current(editable);
4115
4156
  return;
4116
4157
  }
@@ -4173,12 +4214,15 @@ function OhhwellsBridge() {
4173
4214
  }
4174
4215
  return { top, left, width: Math.max(0, right - left), height: Math.max(0, bottom - top) };
4175
4216
  };
4176
- const postImageHover = (imgEl, isDragOver = false) => {
4217
+ const postImageHover = (imgEl, isDragOver = false, forceTextOverlap) => {
4177
4218
  const r2 = getVisibleRect(imgEl);
4219
+ const hasTextOverlap = forceTextOverlap ?? false;
4220
+ hoveredImageHasTextOverlapRef.current = hasTextOverlap;
4178
4221
  postToParentRef.current({
4179
4222
  type: "ow:image-hover",
4180
4223
  key: imgEl.dataset.ohwKey ?? "",
4181
4224
  rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height },
4225
+ hasTextOverlap,
4182
4226
  ...isDragOver ? { isDragOver: true } : {}
4183
4227
  });
4184
4228
  const track = imgEl.closest("[data-ohw-hover-pause]");
@@ -4225,7 +4269,7 @@ function OhhwellsBridge() {
4225
4269
  }
4226
4270
  return smallest;
4227
4271
  };
4228
- const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false, fromOverlay = false) => {
4272
+ const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
4229
4273
  const toggleEl = document.querySelector("[data-ohw-state-toggle]");
4230
4274
  if (toggleEl) {
4231
4275
  const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
@@ -4251,33 +4295,52 @@ function OhhwellsBridge() {
4251
4295
  }
4252
4296
  return;
4253
4297
  }
4254
- const topEditable = topEl?.closest("[data-ohw-editable]");
4255
- if (topEditable && topEditable.dataset.ohwEditable !== "image" && topEditable.dataset.ohwEditable !== "bg-image") {
4256
- if (topEditable.hasAttribute("contenteditable") && fromOverlay) return;
4257
- if (hoveredImageRef.current) {
4258
- hoveredImageRef.current = null;
4259
- resumeAnimTracks();
4260
- postToParentRef.current({ type: "ow:image-unhover" });
4261
- }
4262
- document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
4263
- topEditable.setAttribute("data-ohw-hovered", "");
4264
- return;
4298
+ const isStateCardImage = !!imgEl.closest("[data-ohw-editable-state]");
4299
+ const textEditable = Array.from(
4300
+ document.querySelectorAll('[data-ohw-editable]:not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"])')
4301
+ ).find((el) => {
4302
+ const er = el.getBoundingClientRect();
4303
+ return x >= er.left && x <= er.right && y >= er.top && y <= er.bottom;
4304
+ });
4305
+ if (imageUnhoverTimerRef.current) {
4306
+ clearTimeout(imageUnhoverTimerRef.current);
4307
+ imageUnhoverTimerRef.current = null;
4265
4308
  }
4266
- const activeHovered = document.querySelector("[data-ohw-hovered]");
4267
- if (activeHovered) {
4268
- const outlinePad = 8;
4269
- const hr = activeHovered.getBoundingClientRect();
4270
- if (x >= hr.left - outlinePad && x <= hr.right + outlinePad && y >= hr.top - outlinePad && y <= hr.bottom + outlinePad) {
4309
+ if (imageShowTimerRef.current) {
4310
+ clearTimeout(imageShowTimerRef.current);
4311
+ imageShowTimerRef.current = null;
4312
+ }
4313
+ if (textEditable) {
4314
+ if (textEditable.hasAttribute("contenteditable")) {
4271
4315
  if (hoveredImageRef.current) {
4272
4316
  hoveredImageRef.current = null;
4317
+ hoveredImageHasTextOverlapRef.current = false;
4273
4318
  resumeAnimTracks();
4274
4319
  postToParentRef.current({ type: "ow:image-unhover" });
4275
4320
  }
4276
4321
  return;
4277
4322
  }
4323
+ if (isStateCardImage) {
4324
+ if (imgEl !== hoveredImageRef.current || !hoveredImageHasTextOverlapRef.current) {
4325
+ hoveredImageRef.current = imgEl;
4326
+ postImageHover(imgEl, isDragOver, true);
4327
+ }
4328
+ document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
4329
+ textEditable.setAttribute("data-ohw-hovered", "");
4330
+ return;
4331
+ }
4332
+ if (hoveredImageRef.current) {
4333
+ hoveredImageRef.current = null;
4334
+ hoveredImageHasTextOverlapRef.current = false;
4335
+ resumeAnimTracks();
4336
+ postToParentRef.current({ type: "ow:image-unhover" });
4337
+ }
4338
+ document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
4339
+ textEditable.setAttribute("data-ohw-hovered", "");
4340
+ return;
4278
4341
  }
4279
4342
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
4280
- if (imgEl !== hoveredImageRef.current) {
4343
+ if (imgEl !== hoveredImageRef.current || hoveredImageHasTextOverlapRef.current) {
4281
4344
  hoveredImageRef.current = imgEl;
4282
4345
  postImageHover(imgEl, isDragOver);
4283
4346
  } else if (isDragOver) {
@@ -4286,9 +4349,29 @@ function OhhwellsBridge() {
4286
4349
  } else {
4287
4350
  const inIframeView = clientX >= 0 && clientY >= 0 && clientX <= window.innerWidth && clientY <= window.innerHeight;
4288
4351
  if (!inIframeView) return;
4289
- hoveredImageRef.current = null;
4290
- resumeAnimTracks();
4291
- postToParentRef.current({ type: "ow:image-unhover" });
4352
+ if (imageUnhoverTimerRef.current) {
4353
+ clearTimeout(imageUnhoverTimerRef.current);
4354
+ imageUnhoverTimerRef.current = null;
4355
+ }
4356
+ if (imageShowTimerRef.current) {
4357
+ clearTimeout(imageShowTimerRef.current);
4358
+ imageShowTimerRef.current = null;
4359
+ }
4360
+ if (hoveredImageRef.current) {
4361
+ hoveredImageRef.current = null;
4362
+ hoveredImageHasTextOverlapRef.current = false;
4363
+ resumeAnimTracks();
4364
+ postToParentRef.current({ type: "ow:image-unhover" });
4365
+ }
4366
+ const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
4367
+ const textEl = Array.from(
4368
+ document.querySelectorAll('[data-ohw-editable]:not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"])')
4369
+ ).find((el) => {
4370
+ const er = el.getBoundingClientRect();
4371
+ return x >= er.left && x <= er.right && y >= er.top && y <= er.bottom;
4372
+ });
4373
+ document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
4374
+ if (textEl && !textEl.hasAttribute("contenteditable")) textEl.setAttribute("data-ohw-hovered", "");
4292
4375
  }
4293
4376
  };
4294
4377
  const probeHoverCardsAt = (clientX, clientY, fromParentViewport = false) => {
@@ -4307,28 +4390,54 @@ function OhhwellsBridge() {
4307
4390
  }) ?? null;
4308
4391
  const currentLocked = activeStateElRef.current?.hasAttribute("data-ohw-active-state") ? activeStateElRef.current : null;
4309
4392
  const active = found ?? currentLocked;
4393
+ const prev = activeStateElRef.current;
4310
4394
  activeStateElRef.current = active;
4311
- document.querySelectorAll("[data-ohw-state-hovered]").forEach((el) => el.removeAttribute("data-ohw-state-hovered"));
4312
- if (active) {
4313
- if (active.querySelector("[data-ohw-state-view]")) active.setAttribute("data-ohw-state-hovered", "");
4314
- const states = deriveStates(active.dataset.ohwEditableState ?? "");
4315
- const activeState = active.hasAttribute("data-ohw-active-state") ? (active.getAttribute("data-ohw-active-state") ?? "Default").charAt(0).toUpperCase() + (active.getAttribute("data-ohw-active-state") ?? "").slice(1) : "Default";
4316
- setToggleState({ rect: active.getBoundingClientRect(), activeState, states });
4317
- } else {
4318
- setToggleState(null);
4395
+ if (active !== prev) {
4396
+ if (prev) prev.removeAttribute("data-ohw-state-hovered");
4397
+ if (active) {
4398
+ if (active.querySelector("[data-ohw-state-view]")) active.setAttribute("data-ohw-state-hovered", "");
4399
+ const states = deriveStates(active.dataset.ohwEditableState ?? "");
4400
+ const activeState = active.hasAttribute("data-ohw-active-state") ? (active.getAttribute("data-ohw-active-state") ?? "Default").charAt(0).toUpperCase() + (active.getAttribute("data-ohw-active-state") ?? "").slice(1) : "Default";
4401
+ setToggleState({ rect: active.getBoundingClientRect(), activeState, states });
4402
+ } else {
4403
+ setToggleState(null);
4404
+ }
4405
+ }
4406
+ };
4407
+ const probeSectionGapAt = (clientX, clientY, fromParentViewport = false) => {
4408
+ const { y } = toProbeCoords(clientX, clientY, fromParentViewport);
4409
+ const sections = Array.from(document.querySelectorAll("[data-ohw-section]")).sort((a, b) => a.getBoundingClientRect().top - b.getBoundingClientRect().top);
4410
+ const ZONE = 20;
4411
+ for (let i = 0; i < sections.length; i++) {
4412
+ const a = sections[i];
4413
+ const b = sections[i + 1] ?? null;
4414
+ const boundaryY = a.getBoundingClientRect().bottom;
4415
+ if (Math.abs(y - boundaryY) <= ZONE) {
4416
+ const insertAfter = a.dataset.ohwSection ?? "";
4417
+ const insertBefore = b?.dataset.ohwSection ?? null;
4418
+ hoveredGapRef.current = { insertAfter, insertBefore };
4419
+ setSectionGap({ insertAfter, insertBefore, y: boundaryY });
4420
+ return;
4421
+ }
4422
+ }
4423
+ if (hoveredGapRef.current) {
4424
+ hoveredGapRef.current = null;
4425
+ setSectionGap(null);
4319
4426
  }
4320
4427
  };
4321
4428
  const handleMouseMove = (e) => {
4322
4429
  const { clientX, clientY } = e;
4323
4430
  probeImageAt(clientX, clientY);
4324
4431
  probeHoverCardsAt(clientX, clientY);
4432
+ probeSectionGapAt(clientX, clientY);
4325
4433
  };
4326
4434
  const handlePointerSync = (e) => {
4327
4435
  if (e.data?.type !== "ow:pointer-sync") return;
4328
- const { clientX, clientY, fromOverlay } = e.data;
4436
+ const { clientX, clientY } = e.data;
4329
4437
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
4330
- probeImageAt(clientX, clientY, false, false, fromOverlay ?? false);
4438
+ probeImageAt(clientX, clientY);
4331
4439
  probeHoverCardsAt(clientX, clientY);
4440
+ probeSectionGapAt(clientX, clientY);
4332
4441
  };
4333
4442
  const handleDragOver = (e) => {
4334
4443
  e.preventDefault();
@@ -4340,7 +4449,8 @@ function OhhwellsBridge() {
4340
4449
  e.dataTransfer.dropEffect = "copy";
4341
4450
  if (hoveredImageRef.current !== el) {
4342
4451
  hoveredImageRef.current = el;
4343
- postImageHover(el, true);
4452
+ const isStateCard = !!el.closest("[data-ohw-editable-state]");
4453
+ postImageHover(el, true, isStateCard ? true : void 0);
4344
4454
  }
4345
4455
  };
4346
4456
  const handleDragLeave = (e) => {
@@ -4526,11 +4636,13 @@ function OhhwellsBridge() {
4526
4636
  setToggleState((prev) => prev ? { ...prev, rect } : null);
4527
4637
  }
4528
4638
  if (hoveredImageRef.current) {
4529
- const r2 = hoveredImageRef.current.getBoundingClientRect();
4639
+ const el = hoveredImageRef.current;
4640
+ const r2 = el.getBoundingClientRect();
4530
4641
  postToParentRef.current({
4531
4642
  type: "ow:image-hover",
4532
- key: hoveredImageRef.current.dataset.ohwKey ?? "",
4533
- rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height }
4643
+ key: el.dataset.ohwKey ?? "",
4644
+ rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height },
4645
+ hasTextOverlap: hoveredImageHasTextOverlapRef.current
4534
4646
  });
4535
4647
  }
4536
4648
  };
@@ -4604,12 +4716,49 @@ function OhhwellsBridge() {
4604
4716
  parentScrollRef.current = { iframeOffsetTop, headerH, canvasH };
4605
4717
  if (activeElRef.current) applyToolbarPos(activeElRef.current.getBoundingClientRect());
4606
4718
  };
4719
+ const handleClickAt = (e) => {
4720
+ if (e.data?.type !== "ow:click-at") return;
4721
+ const { clientX, clientY } = e.data;
4722
+ const allImages = Array.from(
4723
+ document.querySelectorAll('[data-ohw-editable="image"], [data-ohw-editable="bg-image"]')
4724
+ ).filter((el) => !!el.closest("[data-ohw-editable-state]"));
4725
+ const stateCardImage = allImages.find((el) => {
4726
+ const ownerCard = el.closest("[data-ohw-editable-state]");
4727
+ if (ownerCard) {
4728
+ const inHoverState = ownerCard.getAttribute("data-ohw-active-state") === "hover";
4729
+ const elState = el.dataset.ohwState;
4730
+ if (el === ownerCard && inHoverState) return false;
4731
+ if (elState === "default" && inHoverState) return false;
4732
+ if (elState === "hover" && !inHoverState) return false;
4733
+ }
4734
+ const r2 = el.getBoundingClientRect();
4735
+ return clientX >= r2.left && clientX <= r2.right && clientY >= r2.top && clientY <= r2.bottom;
4736
+ });
4737
+ if (stateCardImage) {
4738
+ postToParentRef.current({ type: "ow:image-pick", key: stateCardImage.dataset.ohwKey ?? "" });
4739
+ return;
4740
+ }
4741
+ const textEditable = Array.from(
4742
+ document.querySelectorAll(
4743
+ '[data-ohw-editable]:not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"])'
4744
+ )
4745
+ ).find((el) => {
4746
+ const r2 = el.getBoundingClientRect();
4747
+ return clientX >= r2.left && clientX <= r2.right && clientY >= r2.top && clientY <= r2.bottom;
4748
+ });
4749
+ if (textEditable) {
4750
+ activateRef.current(textEditable);
4751
+ return;
4752
+ }
4753
+ deactivateRef.current();
4754
+ };
4607
4755
  window.addEventListener("message", handleSave);
4608
4756
  window.addEventListener("message", handleImageUrl);
4609
4757
  window.addEventListener("message", handleAnimLock);
4610
4758
  window.addEventListener("message", handleCanvasHeight);
4611
4759
  window.addEventListener("message", handleParentScroll);
4612
4760
  window.addEventListener("message", handlePointerSync);
4761
+ window.addEventListener("message", handleClickAt);
4613
4762
  document.addEventListener("click", handleClick, true);
4614
4763
  document.addEventListener("paste", handlePaste, true);
4615
4764
  document.addEventListener("input", handleInput, true);
@@ -4643,10 +4792,13 @@ function OhhwellsBridge() {
4643
4792
  window.removeEventListener("message", handleCanvasHeight);
4644
4793
  window.removeEventListener("message", handleParentScroll);
4645
4794
  window.removeEventListener("message", handlePointerSync);
4795
+ window.removeEventListener("message", handleClickAt);
4646
4796
  window.removeEventListener("message", handleHydrate);
4647
4797
  window.removeEventListener("message", handleDeactivate);
4648
4798
  autoSaveTimers.current.forEach(clearTimeout);
4649
4799
  autoSaveTimers.current.clear();
4800
+ if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
4801
+ if (imageShowTimerRef.current) clearTimeout(imageShowTimerRef.current);
4650
4802
  };
4651
4803
  }, [isEditMode, refreshStateRules]);
4652
4804
  useEffect(() => {
@@ -4693,8 +4845,8 @@ function OhhwellsBridge() {
4693
4845
  return bridgeRoot ? createPortal(
4694
4846
  /* @__PURE__ */ jsxs(Fragment, { children: [
4695
4847
  toolbarRect && /* @__PURE__ */ jsxs(Fragment, { children: [
4696
- /* @__PURE__ */ jsx3(GlowFrame, { rect: toolbarRect, elRef: glowElRef }),
4697
- /* @__PURE__ */ jsx3(FloatingToolbar, { rect: toolbarRect, parentScroll: parentScrollRef.current, elRef: toolbarElRef, onCommand: handleCommand, activeCommands })
4848
+ /* @__PURE__ */ jsx4(GlowFrame, { rect: toolbarRect, elRef: glowElRef }),
4849
+ /* @__PURE__ */ jsx4(FloatingToolbar, { rect: toolbarRect, parentScroll: parentScrollRef.current, elRef: toolbarElRef, onCommand: handleCommand, activeCommands })
4698
4850
  ] }),
4699
4851
  maxBadge && /* @__PURE__ */ jsxs(
4700
4852
  "div",
@@ -4722,7 +4874,7 @@ function OhhwellsBridge() {
4722
4874
  ]
4723
4875
  }
4724
4876
  ),
4725
- toggleState && /* @__PURE__ */ jsx3(
4877
+ toggleState && /* @__PURE__ */ jsx4(
4726
4878
  StateToggle,
4727
4879
  {
4728
4880
  rect: toggleState.rect,
@@ -4730,6 +4882,31 @@ function OhhwellsBridge() {
4730
4882
  states: toggleState.states,
4731
4883
  onStateChange: handleStateChange
4732
4884
  }
4885
+ ),
4886
+ sectionGap && /* @__PURE__ */ jsxs(
4887
+ "div",
4888
+ {
4889
+ "data-ohw-section-insert-line": "",
4890
+ className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
4891
+ style: { top: sectionGap.y, transform: "translateY(-50%)" },
4892
+ children: [
4893
+ /* @__PURE__ */ jsx4("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
4894
+ /* @__PURE__ */ jsx4(
4895
+ Badge,
4896
+ {
4897
+ className: "px-8 py-1 bg-primary hover:bg-primary text-primary-foreground text-xs font-medium shrink-0 rounded-full cursor-pointer pointer-events-auto",
4898
+ onClick: () => {
4899
+ window.parent.postMessage(
4900
+ { type: "ow:add-section", insertAfter: sectionGap.insertAfter, insertBefore: sectionGap.insertBefore },
4901
+ "*"
4902
+ );
4903
+ },
4904
+ children: "Add Section"
4905
+ }
4906
+ ),
4907
+ /* @__PURE__ */ jsx4("div", { className: "flex-1 bg-primary", style: { height: 3 } })
4908
+ ]
4909
+ }
4733
4910
  )
4734
4911
  ] }),
4735
4912
  bridgeRoot