@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.cjs CHANGED
@@ -3435,7 +3435,9 @@ function ToggleGroupItem({
3435
3435
  Toggle,
3436
3436
  {
3437
3437
  pressed: groupValue === value,
3438
- onPressedChange: () => onValueChange(value),
3438
+ onPressedChange: (pressed) => {
3439
+ if (pressed) onValueChange(value);
3440
+ },
3439
3441
  className: cn(
3440
3442
  "text-foreground cursor-pointer hover:bg-transparent hover:text-foreground",
3441
3443
  "data-[state=on]:bg-background data-[state=on]:shadow-sm",
@@ -3470,8 +3472,30 @@ function isEditSessionActive() {
3470
3472
  return new URLSearchParams(q).get("mode") === "edit";
3471
3473
  }
3472
3474
 
3473
- // src/OhhwellsBridge.tsx
3475
+ // src/ui/badge.tsx
3474
3476
  var import_jsx_runtime3 = require("react/jsx-runtime");
3477
+ var badgeVariants = cva(
3478
+ "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",
3479
+ {
3480
+ variants: {
3481
+ variant: {
3482
+ default: "border-transparent bg-primary text-primary-foreground",
3483
+ secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
3484
+ destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
3485
+ outline: "text-foreground"
3486
+ }
3487
+ },
3488
+ defaultVariants: {
3489
+ variant: "default"
3490
+ }
3491
+ }
3492
+ );
3493
+ function Badge({ className, variant, ...props }) {
3494
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: cn(badgeVariants({ variant }), className), ...props });
3495
+ }
3496
+
3497
+ // src/OhhwellsBridge.tsx
3498
+ var import_jsx_runtime4 = require("react/jsx-runtime");
3475
3499
  var PRIMARY = "#0885FE";
3476
3500
  var IMAGE_FADE_MS = 300;
3477
3501
  function runOpacityFade(el, onDone) {
@@ -3679,7 +3703,7 @@ var TOOLBAR_GROUPS = [
3679
3703
  ];
3680
3704
  function GlowFrame({ rect, elRef }) {
3681
3705
  const GAP = 6;
3682
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3706
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3683
3707
  "div",
3684
3708
  {
3685
3709
  ref: elRef,
@@ -3730,7 +3754,7 @@ function FloatingToolbar({
3730
3754
  activeCommands
3731
3755
  }) {
3732
3756
  const { top, left, transform } = calcToolbarPos(rect, parentScroll);
3733
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3757
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3734
3758
  "div",
3735
3759
  {
3736
3760
  ref: elRef,
@@ -3753,11 +3777,11 @@ function FloatingToolbar({
3753
3777
  pointerEvents: "auto",
3754
3778
  whiteSpace: "nowrap"
3755
3779
  },
3756
- children: TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react.default.Fragment, { children: [
3757
- gi > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { display: "block", width: 1, height: 24, background: "#E7E5E4", flexShrink: 0 } }),
3780
+ children: TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react.default.Fragment, { children: [
3781
+ gi > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: { display: "block", width: 1, height: 24, background: "#E7E5E4", flexShrink: 0 } }),
3758
3782
  btns.map((btn) => {
3759
3783
  const isActive = activeCommands.has(btn.cmd);
3760
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3784
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3761
3785
  "button",
3762
3786
  {
3763
3787
  title: btn.title,
@@ -3783,7 +3807,7 @@ function FloatingToolbar({
3783
3807
  flexShrink: 0,
3784
3808
  padding: 6
3785
3809
  },
3786
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3810
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3787
3811
  "svg",
3788
3812
  {
3789
3813
  width: "16",
@@ -3816,7 +3840,7 @@ function StateToggle({
3816
3840
  states,
3817
3841
  onStateChange
3818
3842
  }) {
3819
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3843
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3820
3844
  ToggleGroup,
3821
3845
  {
3822
3846
  "data-ohw-state-toggle": "",
@@ -3830,7 +3854,7 @@ function StateToggle({
3830
3854
  left: rect.right - 8,
3831
3855
  transform: "translateX(-100%)"
3832
3856
  },
3833
- children: states.map((state) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ToggleGroupItem, { value: state, children: state }, state))
3857
+ children: states.map((state) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
3834
3858
  }
3835
3859
  );
3836
3860
  }
@@ -3840,20 +3864,19 @@ function OhhwellsBridge() {
3840
3864
  const router = (0, import_navigation.useRouter)();
3841
3865
  const searchParams = (0, import_navigation.useSearchParams)();
3842
3866
  const isEditMode = isEditSessionActive();
3843
- (0, import_react.useEffect)(() => {
3844
- if (!isEditMode) return;
3845
- const preconnect1 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.googleapis.com" });
3846
- const preconnect2 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.gstatic.com", crossOrigin: "" });
3847
- 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" });
3848
- document.head.append(preconnect1, preconnect2, stylesheet);
3849
- return () => {
3850
- preconnect1.remove();
3851
- preconnect2.remove();
3852
- stylesheet.remove();
3853
- };
3854
- }, [isEditMode]);
3855
3867
  const [bridgeRoot, setBridgeRoot] = (0, import_react.useState)(null);
3856
3868
  (0, import_react.useEffect)(() => {
3869
+ const figtreeFontId = "ohw-figtree-font";
3870
+ if (!document.getElementById(figtreeFontId)) {
3871
+ const preconnect1 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.googleapis.com" });
3872
+ const preconnect2 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.gstatic.com", crossOrigin: "" });
3873
+ const stylesheet = Object.assign(document.createElement("link"), {
3874
+ id: figtreeFontId,
3875
+ rel: "stylesheet",
3876
+ href: "https://fonts.googleapis.com/css2?family=Figtree:ital,wght@0,300..900;1,300..900&display=swap"
3877
+ });
3878
+ document.head.append(preconnect1, preconnect2, stylesheet);
3879
+ }
3857
3880
  const el = document.createElement("div");
3858
3881
  el.setAttribute("data-ohw-bridge-root", "");
3859
3882
  el.setAttribute("data-ohw-bridge", "");
@@ -3884,6 +3907,10 @@ function OhhwellsBridge() {
3884
3907
  const toolbarElRef = (0, import_react.useRef)(null);
3885
3908
  const glowElRef = (0, import_react.useRef)(null);
3886
3909
  const hoveredImageRef = (0, import_react.useRef)(null);
3910
+ const hoveredImageHasTextOverlapRef = (0, import_react.useRef)(false);
3911
+ const hoveredGapRef = (0, import_react.useRef)(null);
3912
+ const imageUnhoverTimerRef = (0, import_react.useRef)(null);
3913
+ const imageShowTimerRef = (0, import_react.useRef)(null);
3887
3914
  const editStylesRef = (0, import_react.useRef)(null);
3888
3915
  const activateRef = (0, import_react.useRef)(() => {
3889
3916
  });
@@ -3897,6 +3924,7 @@ function OhhwellsBridge() {
3897
3924
  const [toggleState, setToggleState] = (0, import_react.useState)(null);
3898
3925
  const [maxBadge, setMaxBadge] = (0, import_react.useState)(null);
3899
3926
  const [activeCommands, setActiveCommands] = (0, import_react.useState)(/* @__PURE__ */ new Set());
3927
+ const [sectionGap, setSectionGap] = (0, import_react.useState)(null);
3900
3928
  (0, import_react.useEffect)(() => {
3901
3929
  const update = () => {
3902
3930
  const el = activeElRef.current;
@@ -3905,6 +3933,12 @@ function OhhwellsBridge() {
3905
3933
  if (!prev || !activeStateElRef.current) return prev;
3906
3934
  return { ...prev, rect: activeStateElRef.current.getBoundingClientRect() };
3907
3935
  });
3936
+ setSectionGap((prev) => {
3937
+ if (!prev) return prev;
3938
+ const anchor = document.querySelector(`[data-ohw-section="${prev.insertAfter}"]`);
3939
+ if (!anchor) return prev;
3940
+ return { ...prev, y: anchor.getBoundingClientRect().bottom };
3941
+ });
3908
3942
  };
3909
3943
  const vvp = window.visualViewport;
3910
3944
  if (!vvp) return;
@@ -3946,6 +3980,10 @@ function OhhwellsBridge() {
3946
3980
  const activate = (0, import_react.useCallback)((el) => {
3947
3981
  if (activeElRef.current === el) return;
3948
3982
  deactivate();
3983
+ if (hoveredImageRef.current) {
3984
+ hoveredImageRef.current = null;
3985
+ postToParentRef.current({ type: "ow:image-unhover" });
3986
+ }
3949
3987
  el.setAttribute("contenteditable", "true");
3950
3988
  el.removeAttribute("data-ohw-hovered");
3951
3989
  activeElRef.current = el;
@@ -3993,7 +4031,7 @@ function OhhwellsBridge() {
3993
4031
  }
3994
4032
  let cancelled = false;
3995
4033
  setFetchState("loading");
3996
- const apiUrl = globalThis.process?.env?.NEXT_PUBLIC_FLOWOPS_API_URL ?? "http://localhost:4005";
4034
+ const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
3997
4035
  fetch(`${apiUrl}/api/public/sites/${subdomain}/content`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
3998
4036
  if (cancelled) return;
3999
4037
  const content = data?.content ?? {};
@@ -4120,6 +4158,7 @@ function OhhwellsBridge() {
4120
4158
  [data-ohw-editable][contenteditable] *::selection { background: ${PRIMARY}59 !important; }
4121
4159
  [data-ohw-editable-state], [data-ohw-editable-state] * { pointer-events: none !important; }
4122
4160
  [data-ohw-editable-state][data-ohw-active-state] [data-ohw-editable] { pointer-events: auto !important; }
4161
+ [data-ohw-editable-state][data-ohw-active-state][data-ohw-editable] { pointer-events: auto !important; }
4123
4162
  `;
4124
4163
  const forceHover = document.createElement("style");
4125
4164
  forceHover.setAttribute("data-ohw-active-state-style", "");
@@ -4146,10 +4185,12 @@ function OhhwellsBridge() {
4146
4185
  if (editable) {
4147
4186
  if (editable.dataset.ohwEditable === "image" || editable.dataset.ohwEditable === "bg-image") {
4148
4187
  e.preventDefault();
4188
+ e.stopPropagation();
4149
4189
  postToParentRef.current({ type: "ow:image-pick", key: editable.dataset.ohwKey ?? "" });
4150
4190
  return;
4151
4191
  }
4152
4192
  e.preventDefault();
4193
+ e.stopPropagation();
4153
4194
  activateRef.current(editable);
4154
4195
  return;
4155
4196
  }
@@ -4212,12 +4253,15 @@ function OhhwellsBridge() {
4212
4253
  }
4213
4254
  return { top, left, width: Math.max(0, right - left), height: Math.max(0, bottom - top) };
4214
4255
  };
4215
- const postImageHover = (imgEl, isDragOver = false) => {
4256
+ const postImageHover = (imgEl, isDragOver = false, forceTextOverlap) => {
4216
4257
  const r2 = getVisibleRect(imgEl);
4258
+ const hasTextOverlap = forceTextOverlap ?? false;
4259
+ hoveredImageHasTextOverlapRef.current = hasTextOverlap;
4217
4260
  postToParentRef.current({
4218
4261
  type: "ow:image-hover",
4219
4262
  key: imgEl.dataset.ohwKey ?? "",
4220
4263
  rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height },
4264
+ hasTextOverlap,
4221
4265
  ...isDragOver ? { isDragOver: true } : {}
4222
4266
  });
4223
4267
  const track = imgEl.closest("[data-ohw-hover-pause]");
@@ -4264,7 +4308,7 @@ function OhhwellsBridge() {
4264
4308
  }
4265
4309
  return smallest;
4266
4310
  };
4267
- const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false, fromOverlay = false) => {
4311
+ const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
4268
4312
  const toggleEl = document.querySelector("[data-ohw-state-toggle]");
4269
4313
  if (toggleEl) {
4270
4314
  const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
@@ -4290,33 +4334,52 @@ function OhhwellsBridge() {
4290
4334
  }
4291
4335
  return;
4292
4336
  }
4293
- const topEditable = topEl?.closest("[data-ohw-editable]");
4294
- if (topEditable && topEditable.dataset.ohwEditable !== "image" && topEditable.dataset.ohwEditable !== "bg-image") {
4295
- if (topEditable.hasAttribute("contenteditable") && fromOverlay) return;
4296
- if (hoveredImageRef.current) {
4297
- hoveredImageRef.current = null;
4298
- resumeAnimTracks();
4299
- postToParentRef.current({ type: "ow:image-unhover" });
4300
- }
4301
- document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
4302
- topEditable.setAttribute("data-ohw-hovered", "");
4303
- return;
4337
+ const isStateCardImage = !!imgEl.closest("[data-ohw-editable-state]");
4338
+ const textEditable = Array.from(
4339
+ document.querySelectorAll('[data-ohw-editable]:not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"])')
4340
+ ).find((el) => {
4341
+ const er = el.getBoundingClientRect();
4342
+ return x >= er.left && x <= er.right && y >= er.top && y <= er.bottom;
4343
+ });
4344
+ if (imageUnhoverTimerRef.current) {
4345
+ clearTimeout(imageUnhoverTimerRef.current);
4346
+ imageUnhoverTimerRef.current = null;
4347
+ }
4348
+ if (imageShowTimerRef.current) {
4349
+ clearTimeout(imageShowTimerRef.current);
4350
+ imageShowTimerRef.current = null;
4304
4351
  }
4305
- const activeHovered = document.querySelector("[data-ohw-hovered]");
4306
- if (activeHovered) {
4307
- const outlinePad = 8;
4308
- const hr = activeHovered.getBoundingClientRect();
4309
- if (x >= hr.left - outlinePad && x <= hr.right + outlinePad && y >= hr.top - outlinePad && y <= hr.bottom + outlinePad) {
4352
+ if (textEditable) {
4353
+ if (textEditable.hasAttribute("contenteditable")) {
4310
4354
  if (hoveredImageRef.current) {
4311
4355
  hoveredImageRef.current = null;
4356
+ hoveredImageHasTextOverlapRef.current = false;
4312
4357
  resumeAnimTracks();
4313
4358
  postToParentRef.current({ type: "ow:image-unhover" });
4314
4359
  }
4315
4360
  return;
4316
4361
  }
4362
+ if (isStateCardImage) {
4363
+ if (imgEl !== hoveredImageRef.current || !hoveredImageHasTextOverlapRef.current) {
4364
+ hoveredImageRef.current = imgEl;
4365
+ postImageHover(imgEl, isDragOver, true);
4366
+ }
4367
+ document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
4368
+ textEditable.setAttribute("data-ohw-hovered", "");
4369
+ return;
4370
+ }
4371
+ if (hoveredImageRef.current) {
4372
+ hoveredImageRef.current = null;
4373
+ hoveredImageHasTextOverlapRef.current = false;
4374
+ resumeAnimTracks();
4375
+ postToParentRef.current({ type: "ow:image-unhover" });
4376
+ }
4377
+ document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
4378
+ textEditable.setAttribute("data-ohw-hovered", "");
4379
+ return;
4317
4380
  }
4318
4381
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
4319
- if (imgEl !== hoveredImageRef.current) {
4382
+ if (imgEl !== hoveredImageRef.current || hoveredImageHasTextOverlapRef.current) {
4320
4383
  hoveredImageRef.current = imgEl;
4321
4384
  postImageHover(imgEl, isDragOver);
4322
4385
  } else if (isDragOver) {
@@ -4325,9 +4388,29 @@ function OhhwellsBridge() {
4325
4388
  } else {
4326
4389
  const inIframeView = clientX >= 0 && clientY >= 0 && clientX <= window.innerWidth && clientY <= window.innerHeight;
4327
4390
  if (!inIframeView) return;
4328
- hoveredImageRef.current = null;
4329
- resumeAnimTracks();
4330
- postToParentRef.current({ type: "ow:image-unhover" });
4391
+ if (imageUnhoverTimerRef.current) {
4392
+ clearTimeout(imageUnhoverTimerRef.current);
4393
+ imageUnhoverTimerRef.current = null;
4394
+ }
4395
+ if (imageShowTimerRef.current) {
4396
+ clearTimeout(imageShowTimerRef.current);
4397
+ imageShowTimerRef.current = null;
4398
+ }
4399
+ if (hoveredImageRef.current) {
4400
+ hoveredImageRef.current = null;
4401
+ hoveredImageHasTextOverlapRef.current = false;
4402
+ resumeAnimTracks();
4403
+ postToParentRef.current({ type: "ow:image-unhover" });
4404
+ }
4405
+ const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
4406
+ const textEl = Array.from(
4407
+ document.querySelectorAll('[data-ohw-editable]:not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"])')
4408
+ ).find((el) => {
4409
+ const er = el.getBoundingClientRect();
4410
+ return x >= er.left && x <= er.right && y >= er.top && y <= er.bottom;
4411
+ });
4412
+ document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
4413
+ if (textEl && !textEl.hasAttribute("contenteditable")) textEl.setAttribute("data-ohw-hovered", "");
4331
4414
  }
4332
4415
  };
4333
4416
  const probeHoverCardsAt = (clientX, clientY, fromParentViewport = false) => {
@@ -4346,28 +4429,54 @@ function OhhwellsBridge() {
4346
4429
  }) ?? null;
4347
4430
  const currentLocked = activeStateElRef.current?.hasAttribute("data-ohw-active-state") ? activeStateElRef.current : null;
4348
4431
  const active = found ?? currentLocked;
4432
+ const prev = activeStateElRef.current;
4349
4433
  activeStateElRef.current = active;
4350
- document.querySelectorAll("[data-ohw-state-hovered]").forEach((el) => el.removeAttribute("data-ohw-state-hovered"));
4351
- if (active) {
4352
- if (active.querySelector("[data-ohw-state-view]")) active.setAttribute("data-ohw-state-hovered", "");
4353
- const states = deriveStates(active.dataset.ohwEditableState ?? "");
4354
- 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";
4355
- setToggleState({ rect: active.getBoundingClientRect(), activeState, states });
4356
- } else {
4357
- setToggleState(null);
4434
+ if (active !== prev) {
4435
+ if (prev) prev.removeAttribute("data-ohw-state-hovered");
4436
+ if (active) {
4437
+ if (active.querySelector("[data-ohw-state-view]")) active.setAttribute("data-ohw-state-hovered", "");
4438
+ const states = deriveStates(active.dataset.ohwEditableState ?? "");
4439
+ 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";
4440
+ setToggleState({ rect: active.getBoundingClientRect(), activeState, states });
4441
+ } else {
4442
+ setToggleState(null);
4443
+ }
4444
+ }
4445
+ };
4446
+ const probeSectionGapAt = (clientX, clientY, fromParentViewport = false) => {
4447
+ const { y } = toProbeCoords(clientX, clientY, fromParentViewport);
4448
+ const sections = Array.from(document.querySelectorAll("[data-ohw-section]")).sort((a, b) => a.getBoundingClientRect().top - b.getBoundingClientRect().top);
4449
+ const ZONE = 20;
4450
+ for (let i = 0; i < sections.length; i++) {
4451
+ const a = sections[i];
4452
+ const b = sections[i + 1] ?? null;
4453
+ const boundaryY = a.getBoundingClientRect().bottom;
4454
+ if (Math.abs(y - boundaryY) <= ZONE) {
4455
+ const insertAfter = a.dataset.ohwSection ?? "";
4456
+ const insertBefore = b?.dataset.ohwSection ?? null;
4457
+ hoveredGapRef.current = { insertAfter, insertBefore };
4458
+ setSectionGap({ insertAfter, insertBefore, y: boundaryY });
4459
+ return;
4460
+ }
4461
+ }
4462
+ if (hoveredGapRef.current) {
4463
+ hoveredGapRef.current = null;
4464
+ setSectionGap(null);
4358
4465
  }
4359
4466
  };
4360
4467
  const handleMouseMove = (e) => {
4361
4468
  const { clientX, clientY } = e;
4362
4469
  probeImageAt(clientX, clientY);
4363
4470
  probeHoverCardsAt(clientX, clientY);
4471
+ probeSectionGapAt(clientX, clientY);
4364
4472
  };
4365
4473
  const handlePointerSync = (e) => {
4366
4474
  if (e.data?.type !== "ow:pointer-sync") return;
4367
- const { clientX, clientY, fromOverlay } = e.data;
4475
+ const { clientX, clientY } = e.data;
4368
4476
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
4369
- probeImageAt(clientX, clientY, false, false, fromOverlay ?? false);
4477
+ probeImageAt(clientX, clientY);
4370
4478
  probeHoverCardsAt(clientX, clientY);
4479
+ probeSectionGapAt(clientX, clientY);
4371
4480
  };
4372
4481
  const handleDragOver = (e) => {
4373
4482
  e.preventDefault();
@@ -4379,7 +4488,8 @@ function OhhwellsBridge() {
4379
4488
  e.dataTransfer.dropEffect = "copy";
4380
4489
  if (hoveredImageRef.current !== el) {
4381
4490
  hoveredImageRef.current = el;
4382
- postImageHover(el, true);
4491
+ const isStateCard = !!el.closest("[data-ohw-editable-state]");
4492
+ postImageHover(el, true, isStateCard ? true : void 0);
4383
4493
  }
4384
4494
  };
4385
4495
  const handleDragLeave = (e) => {
@@ -4565,11 +4675,13 @@ function OhhwellsBridge() {
4565
4675
  setToggleState((prev) => prev ? { ...prev, rect } : null);
4566
4676
  }
4567
4677
  if (hoveredImageRef.current) {
4568
- const r2 = hoveredImageRef.current.getBoundingClientRect();
4678
+ const el = hoveredImageRef.current;
4679
+ const r2 = el.getBoundingClientRect();
4569
4680
  postToParentRef.current({
4570
4681
  type: "ow:image-hover",
4571
- key: hoveredImageRef.current.dataset.ohwKey ?? "",
4572
- rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height }
4682
+ key: el.dataset.ohwKey ?? "",
4683
+ rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height },
4684
+ hasTextOverlap: hoveredImageHasTextOverlapRef.current
4573
4685
  });
4574
4686
  }
4575
4687
  };
@@ -4643,12 +4755,49 @@ function OhhwellsBridge() {
4643
4755
  parentScrollRef.current = { iframeOffsetTop, headerH, canvasH };
4644
4756
  if (activeElRef.current) applyToolbarPos(activeElRef.current.getBoundingClientRect());
4645
4757
  };
4758
+ const handleClickAt = (e) => {
4759
+ if (e.data?.type !== "ow:click-at") return;
4760
+ const { clientX, clientY } = e.data;
4761
+ const allImages = Array.from(
4762
+ document.querySelectorAll('[data-ohw-editable="image"], [data-ohw-editable="bg-image"]')
4763
+ ).filter((el) => !!el.closest("[data-ohw-editable-state]"));
4764
+ const stateCardImage = allImages.find((el) => {
4765
+ const ownerCard = el.closest("[data-ohw-editable-state]");
4766
+ if (ownerCard) {
4767
+ const inHoverState = ownerCard.getAttribute("data-ohw-active-state") === "hover";
4768
+ const elState = el.dataset.ohwState;
4769
+ if (el === ownerCard && inHoverState) return false;
4770
+ if (elState === "default" && inHoverState) return false;
4771
+ if (elState === "hover" && !inHoverState) return false;
4772
+ }
4773
+ const r2 = el.getBoundingClientRect();
4774
+ return clientX >= r2.left && clientX <= r2.right && clientY >= r2.top && clientY <= r2.bottom;
4775
+ });
4776
+ if (stateCardImage) {
4777
+ postToParentRef.current({ type: "ow:image-pick", key: stateCardImage.dataset.ohwKey ?? "" });
4778
+ return;
4779
+ }
4780
+ const textEditable = Array.from(
4781
+ document.querySelectorAll(
4782
+ '[data-ohw-editable]:not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"])'
4783
+ )
4784
+ ).find((el) => {
4785
+ const r2 = el.getBoundingClientRect();
4786
+ return clientX >= r2.left && clientX <= r2.right && clientY >= r2.top && clientY <= r2.bottom;
4787
+ });
4788
+ if (textEditable) {
4789
+ activateRef.current(textEditable);
4790
+ return;
4791
+ }
4792
+ deactivateRef.current();
4793
+ };
4646
4794
  window.addEventListener("message", handleSave);
4647
4795
  window.addEventListener("message", handleImageUrl);
4648
4796
  window.addEventListener("message", handleAnimLock);
4649
4797
  window.addEventListener("message", handleCanvasHeight);
4650
4798
  window.addEventListener("message", handleParentScroll);
4651
4799
  window.addEventListener("message", handlePointerSync);
4800
+ window.addEventListener("message", handleClickAt);
4652
4801
  document.addEventListener("click", handleClick, true);
4653
4802
  document.addEventListener("paste", handlePaste, true);
4654
4803
  document.addEventListener("input", handleInput, true);
@@ -4682,10 +4831,13 @@ function OhhwellsBridge() {
4682
4831
  window.removeEventListener("message", handleCanvasHeight);
4683
4832
  window.removeEventListener("message", handleParentScroll);
4684
4833
  window.removeEventListener("message", handlePointerSync);
4834
+ window.removeEventListener("message", handleClickAt);
4685
4835
  window.removeEventListener("message", handleHydrate);
4686
4836
  window.removeEventListener("message", handleDeactivate);
4687
4837
  autoSaveTimers.current.forEach(clearTimeout);
4688
4838
  autoSaveTimers.current.clear();
4839
+ if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
4840
+ if (imageShowTimerRef.current) clearTimeout(imageShowTimerRef.current);
4689
4841
  };
4690
4842
  }, [isEditMode, refreshStateRules]);
4691
4843
  (0, import_react.useEffect)(() => {
@@ -4730,12 +4882,12 @@ function OhhwellsBridge() {
4730
4882
  setToggleState((prev) => prev ? { ...prev, activeState: state } : null);
4731
4883
  }, [deactivate]);
4732
4884
  return bridgeRoot ? (0, import_react_dom.createPortal)(
4733
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
4734
- toolbarRect && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
4735
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(GlowFrame, { rect: toolbarRect, elRef: glowElRef }),
4736
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(FloatingToolbar, { rect: toolbarRect, parentScroll: parentScrollRef.current, elRef: toolbarElRef, onCommand: handleCommand, activeCommands })
4885
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
4886
+ toolbarRect && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
4887
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(GlowFrame, { rect: toolbarRect, elRef: glowElRef }),
4888
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(FloatingToolbar, { rect: toolbarRect, parentScroll: parentScrollRef.current, elRef: toolbarElRef, onCommand: handleCommand, activeCommands })
4737
4889
  ] }),
4738
- maxBadge && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
4890
+ maxBadge && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
4739
4891
  "div",
4740
4892
  {
4741
4893
  "data-ohw-max-badge": "",
@@ -4761,7 +4913,7 @@ function OhhwellsBridge() {
4761
4913
  ]
4762
4914
  }
4763
4915
  ),
4764
- toggleState && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
4916
+ toggleState && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
4765
4917
  StateToggle,
4766
4918
  {
4767
4919
  rect: toggleState.rect,
@@ -4769,6 +4921,31 @@ function OhhwellsBridge() {
4769
4921
  states: toggleState.states,
4770
4922
  onStateChange: handleStateChange
4771
4923
  }
4924
+ ),
4925
+ sectionGap && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
4926
+ "div",
4927
+ {
4928
+ "data-ohw-section-insert-line": "",
4929
+ className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
4930
+ style: { top: sectionGap.y, transform: "translateY(-50%)" },
4931
+ children: [
4932
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
4933
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
4934
+ Badge,
4935
+ {
4936
+ 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",
4937
+ onClick: () => {
4938
+ window.parent.postMessage(
4939
+ { type: "ow:add-section", insertAfter: sectionGap.insertAfter, insertBefore: sectionGap.insertBefore },
4940
+ "*"
4941
+ );
4942
+ },
4943
+ children: "Add Section"
4944
+ }
4945
+ ),
4946
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
4947
+ ]
4948
+ }
4772
4949
  )
4773
4950
  ] }),
4774
4951
  bridgeRoot