@ohhwells/bridge 0.1.40 → 0.1.42-next.81

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
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  // src/OhhwellsBridge.tsx
4
- import React9, { useCallback as useCallback3, useEffect as useEffect7, useLayoutEffect as useLayoutEffect2, useRef as useRef5, useState as useState6 } from "react";
4
+ import React10, { useCallback as useCallback4, useEffect as useEffect8, useLayoutEffect as useLayoutEffect2, useRef as useRef5, useState as useState7 } from "react";
5
5
  import { createRoot } from "react-dom/client";
6
6
  import { flushSync } from "react-dom";
7
7
 
@@ -48,12 +48,13 @@ function useLinkHrefGuardian(...deps) {
48
48
  }
49
49
 
50
50
  // src/ui/SchedulingWidget.tsx
51
- import { useEffect, useId, useMemo, useRef, useState as useState2 } from "react";
51
+ import { useCallback, useEffect, useId, useMemo, useRef, useState as useState2 } from "react";
52
52
 
53
53
  // src/ui/EmailCaptureModal.tsx
54
54
  import { useState } from "react";
55
55
  import { Dialog } from "radix-ui";
56
56
  import { jsx, jsxs } from "react/jsx-runtime";
57
+ var isValidEmail = (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
57
58
  function Spinner() {
58
59
  return /* @__PURE__ */ jsxs(
59
60
  "svg",
@@ -87,12 +88,17 @@ function EmailCaptureModal({ title, subtitle, onSubmit, onClose }) {
87
88
  const [error, setError] = useState(null);
88
89
  const isBook = title === "Confirm your spot";
89
90
  const handleSubmit = async () => {
90
- if (!email.trim()) return;
91
+ const trimmed = email.trim();
92
+ if (!trimmed) return;
93
+ if (!isValidEmail(trimmed)) {
94
+ setError("Please enter a valid email address.");
95
+ return;
96
+ }
91
97
  setLoading(true);
92
98
  setError(null);
93
99
  try {
94
- await onSubmit(email.trim());
95
- setSubmittedEmail(email.trim());
100
+ await onSubmit(trimmed);
101
+ setSubmittedEmail(trimmed);
96
102
  setSuccess(true);
97
103
  } catch (e) {
98
104
  setError(e instanceof Error ? e.message : "Something went wrong. Please try again.");
@@ -167,7 +173,10 @@ function EmailCaptureModal({ title, subtitle, onSubmit, onClose }) {
167
173
  {
168
174
  type: "email",
169
175
  value: email,
170
- onChange: (e) => setEmail(e.target.value),
176
+ onChange: (e) => {
177
+ setEmail(e.target.value);
178
+ if (error) setError(null);
179
+ },
171
180
  onKeyDown: handleKeyDown,
172
181
  placeholder: "hello@gmail.com",
173
182
  autoFocus: true,
@@ -224,12 +233,20 @@ function formatClassTime(cls) {
224
233
  const em = endTotal % 60;
225
234
  return `${h}:${cls.startTime.minutes}-${eh}:${String(em).padStart(2, "0")}`;
226
235
  }
236
+ function formatBookingLabel(cls) {
237
+ const price = cls.price;
238
+ const isFree = price === null || price === void 0 || `${price}` === "" || `${price}` === "0";
239
+ return isFree ? "Book for free" : `Book for ${cls.currency ?? ""} ${price}`.trim();
240
+ }
227
241
  function getBookingsOnDate(cls, date) {
228
242
  if (!cls.bookings?.length) return 0;
229
- const ds = date.toISOString().split("T")[0];
243
+ const target = new Date(date);
244
+ target.setHours(0, 0, 0, 0);
230
245
  return cls.bookings.filter((b) => {
231
246
  try {
232
- return new Date(b.classDate).toISOString().split("T")[0] === ds;
247
+ const bookingDate = new Date(b.classDate);
248
+ bookingDate.setHours(0, 0, 0, 0);
249
+ return bookingDate.getTime() === target.getTime();
233
250
  } catch {
234
251
  return false;
235
252
  }
@@ -494,7 +511,7 @@ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal
494
511
  }
495
512
  )
496
513
  ] }),
497
- /* @__PURE__ */ jsx2("div", { className: "sm:hidden flex flex-col gap-px", children: isFull ? /* @__PURE__ */ jsx2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
514
+ cls.showAvailability !== false && /* @__PURE__ */ jsx2("div", { className: "sm:hidden flex flex-col gap-px", children: isFull ? /* @__PURE__ */ jsx2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
498
515
  /* @__PURE__ */ jsxs2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
499
516
  available,
500
517
  "/",
@@ -506,9 +523,10 @@ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal
506
523
  /* @__PURE__ */ jsxs2("div", { className: "flex flex-1 flex-col gap-2 min-w-0 sm:contents", children: [
507
524
  /* @__PURE__ */ jsxs2("div", { className: "flex-1 flex flex-col gap-2 min-w-0", children: [
508
525
  /* @__PURE__ */ jsx2("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block truncate", children: cls.name }),
509
- cls.hostName && /* @__PURE__ */ jsx2("span", { className: "font-body text-base font-normal text-(--color-dark,#200C02) opacity-80 block truncate", children: cls.hostName })
526
+ cls.hostName && /* @__PURE__ */ jsx2("span", { className: "font-body text-base font-normal text-(--color-dark,#200C02) opacity-80 block truncate", children: cls.hostName }),
527
+ cls.description && /* @__PURE__ */ jsx2("span", { className: "font-body text-sm font-normal text-(--color-dark,#200C02) opacity-70 block truncate", children: cls.description })
510
528
  ] }),
511
- /* @__PURE__ */ jsx2("div", { className: "hidden sm:flex w-14 shrink-0 flex-col gap-px", children: isFull ? /* @__PURE__ */ jsx2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
529
+ cls.showAvailability !== false && /* @__PURE__ */ jsx2("div", { className: "hidden sm:flex w-14 shrink-0 flex-col gap-px", children: isFull ? /* @__PURE__ */ jsx2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
512
530
  /* @__PURE__ */ jsxs2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
513
531
  available,
514
532
  "/",
@@ -533,7 +551,7 @@ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal
533
551
  if (!cls.id) return;
534
552
  onOpenModal?.(isFull ? "waitlist" : "book", cls.id, selectedDate);
535
553
  },
536
- children: isFull ? "Join Waitlist" : "Book Now"
554
+ children: isFull ? "Join Waitlist" : formatBookingLabel(cls)
537
555
  }
538
556
  )
539
557
  ] })
@@ -575,6 +593,17 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
575
593
  const [isHovered, setIsHovered] = useState2(false);
576
594
  const [modalState, setModalState] = useState2(null);
577
595
  const switchScheduleIdRef = useRef(null);
596
+ const liveScheduleIdRef = useRef(null);
597
+ const refetchLiveSchedule = useCallback(async () => {
598
+ const id = liveScheduleIdRef.current;
599
+ if (!id) return;
600
+ try {
601
+ const res = await fetch(`${API_URL}/api/schedule/id/${id}`);
602
+ const data = await res.json();
603
+ if (data?.id) setSchedule(data);
604
+ } catch {
605
+ }
606
+ }, []);
578
607
  const dates = useMemo(() => {
579
608
  const today = /* @__PURE__ */ new Date();
580
609
  today.setHours(0, 0, 0, 0);
@@ -594,6 +623,18 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
594
623
  }
595
624
  }
596
625
  }, [schedule, dates]);
626
+ useEffect(() => {
627
+ if (typeof window === "undefined") return;
628
+ const onVisible = () => {
629
+ if (!document.hidden) void refetchLiveSchedule();
630
+ };
631
+ window.addEventListener("focus", refetchLiveSchedule);
632
+ document.addEventListener("visibilitychange", onVisible);
633
+ return () => {
634
+ window.removeEventListener("focus", refetchLiveSchedule);
635
+ document.removeEventListener("visibilitychange", onVisible);
636
+ };
637
+ }, [refetchLiveSchedule]);
597
638
  useEffect(() => {
598
639
  if (typeof window === "undefined") return;
599
640
  const isInEditor = window.self !== window.top;
@@ -655,7 +696,7 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
655
696
  setLoading(false);
656
697
  return;
657
698
  }
658
- ;
699
+ liveScheduleIdRef.current = effectiveId;
659
700
  (async () => {
660
701
  try {
661
702
  const res = await fetch(`${API_URL}/api/schedule/id/${effectiveId}`);
@@ -712,18 +753,14 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
712
753
  const data = await res.json().catch(() => ({}));
713
754
  throw new Error(data?.message ?? "Something went wrong. Please try again.");
714
755
  }
756
+ await refetchLiveSchedule();
715
757
  };
716
758
  const handleReplaceSchedule = () => {
717
759
  setIsHovered(false);
718
- if (schedule) {
719
- window.parent.postMessage({
720
- type: "ow:schedule-connected",
721
- schedule: { id: schedule.id, name: schedule.name },
722
- insertAfter
723
- }, "*");
724
- } else {
725
- window.parent.postMessage({ type: "ow:scheduling-not-connected", insertAfter }, "*");
726
- }
760
+ window.parent.postMessage(
761
+ { type: "ow:scheduling-not-connected", insertAfter, currentScheduleId: schedule?.id },
762
+ "*"
763
+ );
727
764
  };
728
765
  if (!inEditor && !loading && !schedule) return null;
729
766
  const sectionId = `scheduling-${insertAfter}`;
@@ -766,7 +803,19 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
766
803
  ),
767
804
  /* @__PURE__ */ jsxs2("div", { className: "max-w-[1280px] mx-auto flex flex-col items-center gap-16", children: [
768
805
  /* @__PURE__ */ jsxs2("div", { className: "flex flex-col items-center gap-4", children: [
769
- /* @__PURE__ */ jsx2("h2", { className: "font-display text-4xl sm:text-5xl font-bold text-center m-0 text-(--color-dark,#200C02)", children: schedule?.name ?? "Book an appointment" }),
806
+ /* @__PURE__ */ jsx2(
807
+ "h2",
808
+ {
809
+ className: "font-display text-center m-0 text-(--color-dark,#200C02)",
810
+ style: {
811
+ fontSize: "var(--fs-section-h2, clamp(40px, 4.4vw, 64px))",
812
+ fontWeight: "var(--font-weight-heading, 400)",
813
+ lineHeight: 1.05,
814
+ letterSpacing: "-0.02em"
815
+ },
816
+ children: schedule?.name ?? "Book an appointment"
817
+ }
818
+ ),
770
819
  schedule?.description && /* @__PURE__ */ jsx2("p", { className: "font-body text-body text-center m-0 text-(--color-accent,#A89B83)", children: schedule.description })
771
820
  ] }),
772
821
  /* @__PURE__ */ jsxs2("div", { className: "w-full flex flex-col items-end gap-3", children: [
@@ -4444,7 +4493,7 @@ function getChromeStyle(state) {
4444
4493
  case "dragging":
4445
4494
  return {
4446
4495
  border: `2px solid ${PRIMARY}`,
4447
- boxShadow: DRAG_SHADOW
4496
+ boxShadow: `${FOCUS_RING}, ${DRAG_SHADOW}`
4448
4497
  };
4449
4498
  default:
4450
4499
  return {};
@@ -4460,6 +4509,8 @@ function ItemInteractionLayer({
4460
4509
  dragHandleLabel = "Reorder item",
4461
4510
  onDragHandleDragStart,
4462
4511
  onDragHandleDragEnd,
4512
+ onItemPointerDown,
4513
+ onItemClick,
4463
4514
  chromeGap = 6,
4464
4515
  className
4465
4516
  }) {
@@ -4467,7 +4518,8 @@ function ItemInteractionLayer({
4467
4518
  const isActive = state === "active-top" || state === "active-bottom";
4468
4519
  const isDragging = state === "dragging";
4469
4520
  const showToolbar = isActive && toolbar;
4470
- const showDragHandle = isActive && showHandle && !isDragging;
4521
+ const showDragHandle = (isActive || isDragging) && showHandle;
4522
+ const itemDragEnabled = isActive && showHandle && !isDragging && !dragDisabled;
4471
4523
  return /* @__PURE__ */ jsxs4(
4472
4524
  "div",
4473
4525
  {
@@ -4492,11 +4544,28 @@ function ItemInteractionLayer({
4492
4544
  style: getChromeStyle(state)
4493
4545
  }
4494
4546
  ),
4547
+ itemDragEnabled && /* @__PURE__ */ jsx9(
4548
+ "div",
4549
+ {
4550
+ "data-ohw-item-drag-surface": "",
4551
+ className: "pointer-events-auto absolute inset-0 cursor-grab rounded-lg active:cursor-grabbing",
4552
+ onPointerDown: (e) => {
4553
+ if (e.button !== 0) return;
4554
+ onItemPointerDown?.(e);
4555
+ },
4556
+ onClick: (e) => {
4557
+ e.preventDefault();
4558
+ e.stopPropagation();
4559
+ onItemClick?.(e.clientX, e.clientY);
4560
+ }
4561
+ }
4562
+ ),
4495
4563
  showDragHandle && /* @__PURE__ */ jsx9(
4496
4564
  "div",
4497
4565
  {
4498
4566
  "data-ohw-drag-handle-container": "",
4499
- className: "pointer-events-auto absolute left-0 top-1/2 -translate-x-[calc(100%+7px)] -translate-y-1/2",
4567
+ className: "pointer-events-auto absolute left-0 top-1/2 z-10 -translate-x-[calc(100%+7px)] -translate-y-1/2",
4568
+ style: isDragging ? { visibility: "hidden" } : void 0,
4500
4569
  children: /* @__PURE__ */ jsx9(
4501
4570
  DragHandle,
4502
4571
  {
@@ -4719,15 +4788,12 @@ function MediaOverlay({
4719
4788
  className: "flex items-center justify-center cursor-pointer",
4720
4789
  style: {
4721
4790
  ...box,
4722
- // When text overlays this media, let clicks fall THROUGH to the text underneath. In the
4723
- // parent this needed a whole `ow:click-at` round-trip to re-hit-test inside the iframe;
4724
- // in-document, pointer-events does it natively. The button below opts back in, so
4725
- // Replace still works.
4726
- pointerEvents: hover.hasTextOverlap ? "none" : "auto",
4791
+ // Keep the full-size visual overlay click-through so carousel controls and editable
4792
+ // content underneath remain interactive. The Replace button opts back in below.
4793
+ pointerEvents: "none",
4727
4794
  boxShadow: "inset 0 0 0 1.5px var(--color-primary)",
4728
4795
  background: "color-mix(in srgb, var(--color-primary) 20%, transparent)"
4729
4796
  },
4730
- onClick: () => onReplace(hover.key),
4731
4797
  children: /* @__PURE__ */ jsxs5(
4732
4798
  Button,
4733
4799
  {
@@ -4752,6 +4818,61 @@ function MediaOverlay({
4752
4818
  ] });
4753
4819
  }
4754
4820
 
4821
+ // src/ui/CarouselOverlay.tsx
4822
+ import { GalleryHorizontal } from "lucide-react";
4823
+ import { jsx as jsx12, jsxs as jsxs6 } from "react/jsx-runtime";
4824
+ var OVERLAY_BUTTON_STYLE2 = {
4825
+ pointerEvents: "auto",
4826
+ fontFamily: "Inter, sans-serif",
4827
+ fontSize: 12,
4828
+ color: "#000"
4829
+ };
4830
+ function CarouselOverlay({
4831
+ hover,
4832
+ onEdit
4833
+ }) {
4834
+ const { rect } = hover;
4835
+ return /* @__PURE__ */ jsx12(
4836
+ "div",
4837
+ {
4838
+ "data-ohw-bridge": "",
4839
+ "data-ohw-carousel-overlay": "",
4840
+ className: "flex cursor-pointer items-center justify-center",
4841
+ style: {
4842
+ position: "fixed",
4843
+ top: rect.top,
4844
+ left: rect.left,
4845
+ width: rect.width,
4846
+ height: rect.height,
4847
+ zIndex: 2147483646,
4848
+ pointerEvents: "auto",
4849
+ boxShadow: "inset 0 0 0 1.5px var(--color-primary)",
4850
+ background: "color-mix(in srgb, var(--color-primary) 20%, transparent)"
4851
+ },
4852
+ onClick: () => onEdit(hover.key),
4853
+ children: /* @__PURE__ */ jsxs6(
4854
+ Button,
4855
+ {
4856
+ "data-ohw-carousel-overlay": "",
4857
+ variant: "outline",
4858
+ size: "sm",
4859
+ className: "cursor-pointer gap-1.5 hover:bg-background",
4860
+ style: OVERLAY_BUTTON_STYLE2,
4861
+ onMouseDown: (e) => e.preventDefault(),
4862
+ onClick: (e) => {
4863
+ e.stopPropagation();
4864
+ onEdit(hover.key);
4865
+ },
4866
+ children: [
4867
+ /* @__PURE__ */ jsx12(GalleryHorizontal, { size: 14 }),
4868
+ "Edit gallery"
4869
+ ]
4870
+ }
4871
+ )
4872
+ }
4873
+ );
4874
+ }
4875
+
4755
4876
  // src/OhhwellsBridge.tsx
4756
4877
  import { createPortal as createPortal2 } from "react-dom";
4757
4878
  import { usePathname as usePathname2, useRouter as useRouter2, useSearchParams } from "next/navigation";
@@ -5038,22 +5159,22 @@ import { useEffect as useEffect6 } from "react";
5038
5159
  import * as React7 from "react";
5039
5160
  import { Dialog as DialogPrimitive } from "radix-ui";
5040
5161
  import { X } from "lucide-react";
5041
- import { jsx as jsx12, jsxs as jsxs6 } from "react/jsx-runtime";
5162
+ import { jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
5042
5163
  function Dialog2({
5043
5164
  ...props
5044
5165
  }) {
5045
- return /* @__PURE__ */ jsx12(DialogPrimitive.Root, { "data-slot": "dialog", ...props });
5166
+ return /* @__PURE__ */ jsx13(DialogPrimitive.Root, { "data-slot": "dialog", ...props });
5046
5167
  }
5047
5168
  function DialogPortal({
5048
5169
  ...props
5049
5170
  }) {
5050
- return /* @__PURE__ */ jsx12(DialogPrimitive.Portal, { ...props });
5171
+ return /* @__PURE__ */ jsx13(DialogPrimitive.Portal, { ...props });
5051
5172
  }
5052
5173
  function DialogOverlay({
5053
5174
  className,
5054
5175
  ...props
5055
5176
  }) {
5056
- return /* @__PURE__ */ jsx12(
5177
+ return /* @__PURE__ */ jsx13(
5057
5178
  DialogPrimitive.Overlay,
5058
5179
  {
5059
5180
  "data-slot": "dialog-overlay",
@@ -5066,9 +5187,9 @@ function DialogOverlay({
5066
5187
  var DialogContent = React7.forwardRef(
5067
5188
  ({ className, children, showCloseButton = true, container, ...props }, ref) => {
5068
5189
  const positionMode = container ? "absolute" : "fixed";
5069
- return /* @__PURE__ */ jsxs6(DialogPortal, { container: container ?? void 0, children: [
5070
- /* @__PURE__ */ jsx12(DialogOverlay, { className: cn(positionMode, "inset-0") }),
5071
- /* @__PURE__ */ jsxs6(
5190
+ return /* @__PURE__ */ jsxs7(DialogPortal, { container: container ?? void 0, children: [
5191
+ /* @__PURE__ */ jsx13(DialogOverlay, { className: cn(positionMode, "inset-0") }),
5192
+ /* @__PURE__ */ jsxs7(
5072
5193
  DialogPrimitive.Content,
5073
5194
  {
5074
5195
  ref,
@@ -5084,13 +5205,13 @@ var DialogContent = React7.forwardRef(
5084
5205
  ...props,
5085
5206
  children: [
5086
5207
  children,
5087
- showCloseButton ? /* @__PURE__ */ jsx12(
5208
+ showCloseButton ? /* @__PURE__ */ jsx13(
5088
5209
  DialogPrimitive.Close,
5089
5210
  {
5090
5211
  type: "button",
5091
5212
  className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
5092
5213
  "aria-label": "Close",
5093
- children: /* @__PURE__ */ jsx12(X, { size: 16, "aria-hidden": true })
5214
+ children: /* @__PURE__ */ jsx13(X, { size: 16, "aria-hidden": true })
5094
5215
  }
5095
5216
  ) : null
5096
5217
  ]
@@ -5104,13 +5225,13 @@ function DialogHeader({
5104
5225
  className,
5105
5226
  ...props
5106
5227
  }) {
5107
- return /* @__PURE__ */ jsx12("div", { className: cn("flex flex-col gap-1.5", className), ...props });
5228
+ return /* @__PURE__ */ jsx13("div", { className: cn("flex flex-col gap-1.5", className), ...props });
5108
5229
  }
5109
5230
  function DialogFooter({
5110
5231
  className,
5111
5232
  ...props
5112
5233
  }) {
5113
- return /* @__PURE__ */ jsx12(
5234
+ return /* @__PURE__ */ jsx13(
5114
5235
  "div",
5115
5236
  {
5116
5237
  className: cn("flex items-center justify-end gap-2", className),
@@ -5118,7 +5239,7 @@ function DialogFooter({
5118
5239
  }
5119
5240
  );
5120
5241
  }
5121
- var DialogTitle = React7.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx12(
5242
+ var DialogTitle = React7.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx13(
5122
5243
  DialogPrimitive.Title,
5123
5244
  {
5124
5245
  ref,
@@ -5130,7 +5251,7 @@ var DialogTitle = React7.forwardRef(({ className, ...props }, ref) => /* @__PURE
5130
5251
  }
5131
5252
  ));
5132
5253
  DialogTitle.displayName = DialogPrimitive.Title.displayName;
5133
- var DialogDescription = React7.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx12(
5254
+ var DialogDescription = React7.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx13(
5134
5255
  DialogPrimitive.Description,
5135
5256
  {
5136
5257
  ref,
@@ -5146,19 +5267,19 @@ import { Info, X as X3 } from "lucide-react";
5146
5267
 
5147
5268
  // src/ui/link-modal/DestinationBreadcrumb.tsx
5148
5269
  import { ArrowRight, File, GalleryVertical } from "lucide-react";
5149
- import { jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
5270
+ import { jsx as jsx14, jsxs as jsxs8 } from "react/jsx-runtime";
5150
5271
  function DestinationBreadcrumb({
5151
5272
  pageTitle,
5152
5273
  sectionLabel
5153
5274
  }) {
5154
- return /* @__PURE__ */ jsxs7("div", { className: "flex w-full flex-col gap-2", children: [
5155
- /* @__PURE__ */ jsx13("p", { className: "text-sm font-medium! text-foreground m-0", children: "Destination" }),
5156
- /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-3", children: [
5157
- /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-2", children: [
5158
- /* @__PURE__ */ jsx13(File, { size: 16, className: "shrink-0 text-foreground", "aria-hidden": true }),
5159
- /* @__PURE__ */ jsx13("span", { className: "text-sm font-medium leading-none text-foreground!", children: pageTitle })
5275
+ return /* @__PURE__ */ jsxs8("div", { className: "flex w-full flex-col gap-2", children: [
5276
+ /* @__PURE__ */ jsx14("p", { className: "text-sm font-medium! text-foreground m-0", children: "Destination" }),
5277
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-3", children: [
5278
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-2", children: [
5279
+ /* @__PURE__ */ jsx14(File, { size: 16, className: "shrink-0 text-foreground", "aria-hidden": true }),
5280
+ /* @__PURE__ */ jsx14("span", { className: "text-sm font-medium leading-none text-foreground!", children: pageTitle })
5160
5281
  ] }),
5161
- /* @__PURE__ */ jsx13(
5282
+ /* @__PURE__ */ jsx14(
5162
5283
  ArrowRight,
5163
5284
  {
5164
5285
  size: 16,
@@ -5166,8 +5287,8 @@ function DestinationBreadcrumb({
5166
5287
  "aria-hidden": true
5167
5288
  }
5168
5289
  ),
5169
- /* @__PURE__ */ jsxs7("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [
5170
- /* @__PURE__ */ jsx13(
5290
+ /* @__PURE__ */ jsxs8("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [
5291
+ /* @__PURE__ */ jsx14(
5171
5292
  GalleryVertical,
5172
5293
  {
5173
5294
  size: 16,
@@ -5175,7 +5296,7 @@ function DestinationBreadcrumb({
5175
5296
  "aria-hidden": true
5176
5297
  }
5177
5298
  ),
5178
- /* @__PURE__ */ jsx13("span", { className: "truncate text-sm font-medium leading-none text-foreground", children: sectionLabel })
5299
+ /* @__PURE__ */ jsx14("span", { className: "truncate text-sm font-medium leading-none text-foreground", children: sectionLabel })
5179
5300
  ] })
5180
5301
  ] })
5181
5302
  ] });
@@ -5183,22 +5304,22 @@ function DestinationBreadcrumb({
5183
5304
 
5184
5305
  // src/ui/link-modal/SectionTreeItem.tsx
5185
5306
  import { GalleryVertical as GalleryVertical2 } from "lucide-react";
5186
- import { jsx as jsx14, jsxs as jsxs8 } from "react/jsx-runtime";
5307
+ import { jsx as jsx15, jsxs as jsxs9 } from "react/jsx-runtime";
5187
5308
  function SectionTreeItem({
5188
5309
  section,
5189
5310
  onSelect,
5190
5311
  selected
5191
5312
  }) {
5192
5313
  const interactive = Boolean(onSelect);
5193
- return /* @__PURE__ */ jsxs8("div", { className: "flex h-9 w-full items-end pl-3", children: [
5194
- /* @__PURE__ */ jsx14(
5314
+ return /* @__PURE__ */ jsxs9("div", { className: "flex h-9 w-full items-end pl-3", children: [
5315
+ /* @__PURE__ */ jsx15(
5195
5316
  "div",
5196
5317
  {
5197
5318
  className: "mr-[-1px] h-9 w-2 shrink-0 rounded-bl-sm border-b border-l border-border mb-4",
5198
5319
  "aria-hidden": true
5199
5320
  }
5200
5321
  ),
5201
- /* @__PURE__ */ jsxs8(
5322
+ /* @__PURE__ */ jsxs9(
5202
5323
  "div",
5203
5324
  {
5204
5325
  role: interactive ? "button" : void 0,
@@ -5216,7 +5337,7 @@ function SectionTreeItem({
5216
5337
  interactive && selected && "border-primary"
5217
5338
  ),
5218
5339
  children: [
5219
- /* @__PURE__ */ jsx14(
5340
+ /* @__PURE__ */ jsx15(
5220
5341
  GalleryVertical2,
5221
5342
  {
5222
5343
  size: 16,
@@ -5224,7 +5345,7 @@ function SectionTreeItem({
5224
5345
  "aria-hidden": true
5225
5346
  }
5226
5347
  ),
5227
- /* @__PURE__ */ jsx14("span", { className: "truncate text-sm font-normal leading-5 text-foreground", children: section.label })
5348
+ /* @__PURE__ */ jsx15("span", { className: "truncate text-sm font-normal leading-5 text-foreground", children: section.label })
5228
5349
  ]
5229
5350
  }
5230
5351
  )
@@ -5236,10 +5357,10 @@ import { useEffect as useEffect3, useId as useId2, useRef as useRef3, useState a
5236
5357
 
5237
5358
  // src/ui/input.tsx
5238
5359
  import * as React8 from "react";
5239
- import { jsx as jsx15 } from "react/jsx-runtime";
5360
+ import { jsx as jsx16 } from "react/jsx-runtime";
5240
5361
  var Input = React8.forwardRef(
5241
5362
  ({ className, type, ...props }, ref) => {
5242
- return /* @__PURE__ */ jsx15(
5363
+ return /* @__PURE__ */ jsx16(
5243
5364
  "input",
5244
5365
  {
5245
5366
  type,
@@ -5258,9 +5379,9 @@ Input.displayName = "Input";
5258
5379
 
5259
5380
  // src/ui/label.tsx
5260
5381
  import { Label as LabelPrimitive } from "radix-ui";
5261
- import { jsx as jsx16 } from "react/jsx-runtime";
5382
+ import { jsx as jsx17 } from "react/jsx-runtime";
5262
5383
  function Label({ className, ...props }) {
5263
- return /* @__PURE__ */ jsx16(
5384
+ return /* @__PURE__ */ jsx17(
5264
5385
  LabelPrimitive.Root,
5265
5386
  {
5266
5387
  "data-slot": "label",
@@ -5272,11 +5393,11 @@ function Label({ className, ...props }) {
5272
5393
 
5273
5394
  // src/ui/link-modal/UrlOrPageInput.tsx
5274
5395
  import { ChevronDown, File as File2, X as X2 } from "lucide-react";
5275
- import { jsx as jsx17, jsxs as jsxs9 } from "react/jsx-runtime";
5396
+ import { jsx as jsx18, jsxs as jsxs10 } from "react/jsx-runtime";
5276
5397
  function FieldChevron({
5277
5398
  onClick
5278
5399
  }) {
5279
- return /* @__PURE__ */ jsx17(
5400
+ return /* @__PURE__ */ jsx18(
5280
5401
  "button",
5281
5402
  {
5282
5403
  type: "button",
@@ -5284,7 +5405,7 @@ function FieldChevron({
5284
5405
  onClick,
5285
5406
  "aria-label": "Open page list",
5286
5407
  tabIndex: -1,
5287
- children: /* @__PURE__ */ jsx17(ChevronDown, { size: 16 })
5408
+ children: /* @__PURE__ */ jsx18(ChevronDown, { size: 16 })
5288
5409
  }
5289
5410
  );
5290
5411
  }
@@ -5345,11 +5466,11 @@ function UrlOrPageInput({
5345
5466
  "data-ohw-link-field flex h-[36px] w-full items-center overflow-hidden rounded-md border bg-background pl-3 pr-3 py-2 outline-none transition-[border-color,box-shadow]",
5346
5467
  urlError ? "border-destructive shadow-[0_0_0_1px_var(--ohw-destructive)]" : isFocused ? "border-primary shadow-[0_0_0_1px_var(--ohw-primary)]" : "border-input"
5347
5468
  );
5348
- return /* @__PURE__ */ jsxs9("div", { className: "flex w-full flex-col gap-2 p-0", children: [
5349
- /* @__PURE__ */ jsx17(Label, { htmlFor: inputId, className: cn(urlError && "text-destructive"), children: "Destination" }),
5350
- /* @__PURE__ */ jsxs9("div", { ref: rootRef, className: "relative w-full", children: [
5351
- /* @__PURE__ */ jsxs9("div", { "data-ohw-link-field": true, className: fieldClassName, children: [
5352
- selectedPage ? /* @__PURE__ */ jsx17("div", { className: "flex shrink-0 items-center pr-2", children: /* @__PURE__ */ jsx17(
5469
+ return /* @__PURE__ */ jsxs10("div", { className: "flex w-full flex-col gap-2 p-0", children: [
5470
+ /* @__PURE__ */ jsx18(Label, { htmlFor: inputId, className: cn(urlError && "text-destructive"), children: "Destination" }),
5471
+ /* @__PURE__ */ jsxs10("div", { ref: rootRef, className: "relative w-full", children: [
5472
+ /* @__PURE__ */ jsxs10("div", { "data-ohw-link-field": true, className: fieldClassName, children: [
5473
+ selectedPage ? /* @__PURE__ */ jsx18("div", { className: "flex shrink-0 items-center pr-2", children: /* @__PURE__ */ jsx18(
5353
5474
  File2,
5354
5475
  {
5355
5476
  size: 16,
@@ -5357,7 +5478,7 @@ function UrlOrPageInput({
5357
5478
  "aria-hidden": true
5358
5479
  }
5359
5480
  ) }) : null,
5360
- readOnly ? /* @__PURE__ */ jsx17("span", { className: "min-w-0 flex-1 truncate text-sm leading-5 text-foreground", children: selectedPage?.title ?? value }) : /* @__PURE__ */ jsx17(
5481
+ readOnly ? /* @__PURE__ */ jsx18("span", { className: "min-w-0 flex-1 truncate text-sm leading-5 text-foreground", children: selectedPage?.title ?? value }) : /* @__PURE__ */ jsx18(
5361
5482
  Input,
5362
5483
  {
5363
5484
  ref: inputRef,
@@ -5383,7 +5504,7 @@ function UrlOrPageInput({
5383
5504
  )
5384
5505
  }
5385
5506
  ),
5386
- selectedPage && !readOnly ? /* @__PURE__ */ jsx17(
5507
+ selectedPage && !readOnly ? /* @__PURE__ */ jsx18(
5387
5508
  "button",
5388
5509
  {
5389
5510
  type: "button",
@@ -5391,26 +5512,26 @@ function UrlOrPageInput({
5391
5512
  onMouseDown: clearSelection,
5392
5513
  "aria-label": "Clear selected page",
5393
5514
  tabIndex: -1,
5394
- children: /* @__PURE__ */ jsx17(X2, { size: 16, "aria-hidden": true })
5515
+ children: /* @__PURE__ */ jsx18(X2, { size: 16, "aria-hidden": true })
5395
5516
  }
5396
5517
  ) : null,
5397
- !readOnly ? /* @__PURE__ */ jsx17(FieldChevron, { onClick: toggleDropdown }) : null
5518
+ !readOnly ? /* @__PURE__ */ jsx18(FieldChevron, { onClick: toggleDropdown }) : null
5398
5519
  ] }),
5399
- dropdownOpen && !readOnly && filteredPages.length > 0 ? /* @__PURE__ */ jsx17(
5520
+ dropdownOpen && !readOnly && filteredPages.length > 0 ? /* @__PURE__ */ jsx18(
5400
5521
  "div",
5401
5522
  {
5402
5523
  "data-ohw-link-page-dropdown": "",
5403
5524
  className: "absolute left-0 right-0 top-[calc(100%+4px)] z-50 max-h-48 overflow-auto rounded-lg border border-border bg-popover py-1 shadow-lg",
5404
5525
  onMouseDown: (e) => e.preventDefault(),
5405
- children: filteredPages.map((page) => /* @__PURE__ */ jsxs9(
5526
+ children: filteredPages.map((page) => /* @__PURE__ */ jsxs10(
5406
5527
  "button",
5407
5528
  {
5408
5529
  type: "button",
5409
5530
  className: "flex h-9 w-full items-center gap-2 border-0 bg-transparent px-3 text-left text-sm leading-5 text-foreground outline-none hover:bg-muted",
5410
5531
  onClick: () => onPageSelect(page),
5411
5532
  children: [
5412
- /* @__PURE__ */ jsx17(File2, { size: 16, className: "shrink-0", "aria-hidden": true }),
5413
- /* @__PURE__ */ jsx17("span", { className: "truncate", children: page.title })
5533
+ /* @__PURE__ */ jsx18(File2, { size: 16, className: "shrink-0", "aria-hidden": true }),
5534
+ /* @__PURE__ */ jsx18("span", { className: "truncate", children: page.title })
5414
5535
  ]
5415
5536
  },
5416
5537
  page.path
@@ -5418,34 +5539,34 @@ function UrlOrPageInput({
5418
5539
  }
5419
5540
  ) : null
5420
5541
  ] }),
5421
- urlError ? /* @__PURE__ */ jsx17("p", { className: "text-sm font-medium text-destructive", children: urlError }) : null
5542
+ urlError ? /* @__PURE__ */ jsx18("p", { className: "text-sm font-medium text-destructive", children: urlError }) : null
5422
5543
  ] });
5423
5544
  }
5424
5545
 
5425
5546
  // src/ui/link-modal/LinkEditorPanel.tsx
5426
- import { Fragment as Fragment3, jsx as jsx18, jsxs as jsxs10 } from "react/jsx-runtime";
5547
+ import { Fragment as Fragment3, jsx as jsx19, jsxs as jsxs11 } from "react/jsx-runtime";
5427
5548
  function LinkEditorPanel({ state, onClose }) {
5428
5549
  const isCancel = state.secondaryLabel === "Cancel" || state.secondaryLabel === "Back to sections";
5429
- return /* @__PURE__ */ jsxs10(Fragment3, { children: [
5430
- /* @__PURE__ */ jsx18(DialogClose, { asChild: true, children: /* @__PURE__ */ jsx18(
5550
+ return /* @__PURE__ */ jsxs11(Fragment3, { children: [
5551
+ /* @__PURE__ */ jsx19(DialogClose, { asChild: true, children: /* @__PURE__ */ jsx19(
5431
5552
  "button",
5432
5553
  {
5433
5554
  type: "button",
5434
5555
  className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50 h-7",
5435
5556
  "aria-label": "Close",
5436
5557
  onClick: onClose,
5437
- children: /* @__PURE__ */ jsx18(X3, { size: 16, "aria-hidden": true })
5558
+ children: /* @__PURE__ */ jsx19(X3, { size: 16, "aria-hidden": true })
5438
5559
  }
5439
5560
  ) }),
5440
- /* @__PURE__ */ jsx18(DialogHeader, { className: "w-full gap-1.5 p-6 pr-12", children: /* @__PURE__ */ jsx18(DialogTitle, { className: "m-0 w-full break-words", children: state.title }) }),
5441
- /* @__PURE__ */ jsxs10("div", { className: "flex w-full flex-col gap-3 px-6 pb-8 pt-1", children: [
5442
- state.showBreadcrumb && state.selectedPage && state.selectedSection ? /* @__PURE__ */ jsx18(
5561
+ /* @__PURE__ */ jsx19(DialogHeader, { className: "w-full gap-1.5 p-6 pr-12", children: /* @__PURE__ */ jsx19(DialogTitle, { className: "m-0 w-full break-words", children: state.title }) }),
5562
+ /* @__PURE__ */ jsxs11("div", { className: "flex w-full flex-col gap-3 px-6 pb-8 pt-1", children: [
5563
+ state.showBreadcrumb && state.selectedPage && state.selectedSection ? /* @__PURE__ */ jsx19(
5443
5564
  DestinationBreadcrumb,
5444
5565
  {
5445
5566
  pageTitle: state.selectedPage.title,
5446
5567
  sectionLabel: state.selectedSection.label
5447
5568
  }
5448
- ) : /* @__PURE__ */ jsx18(
5569
+ ) : /* @__PURE__ */ jsx19(
5449
5570
  UrlOrPageInput,
5450
5571
  {
5451
5572
  value: state.searchValue,
@@ -5458,8 +5579,8 @@ function LinkEditorPanel({ state, onClose }) {
5458
5579
  urlError: state.urlError
5459
5580
  }
5460
5581
  ),
5461
- state.showChooseSection ? /* @__PURE__ */ jsxs10("div", { className: "flex flex-col justify-center gap-2", children: [
5462
- /* @__PURE__ */ jsx18(
5582
+ state.showChooseSection ? /* @__PURE__ */ jsxs11("div", { className: "flex flex-col justify-center gap-2", children: [
5583
+ /* @__PURE__ */ jsx19(
5463
5584
  Button,
5464
5585
  {
5465
5586
  type: "button",
@@ -5470,15 +5591,15 @@ function LinkEditorPanel({ state, onClose }) {
5470
5591
  children: "Choose a section"
5471
5592
  }
5472
5593
  ),
5473
- /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-1 text-sm text-muted-foreground", children: [
5474
- /* @__PURE__ */ jsx18(Info, { size: 16, className: "shrink-0", "aria-hidden": true }),
5475
- /* @__PURE__ */ jsx18("span", { children: "Pick a section this link should scroll to." })
5594
+ /* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-1 text-sm text-muted-foreground", children: [
5595
+ /* @__PURE__ */ jsx19(Info, { size: 16, className: "shrink-0", "aria-hidden": true }),
5596
+ /* @__PURE__ */ jsx19("span", { children: "Pick a section this link should scroll to." })
5476
5597
  ] })
5477
5598
  ] }) : null,
5478
- state.showSectionRow && state.selectedSection ? /* @__PURE__ */ jsx18(SectionTreeItem, { section: state.selectedSection, selected: true }) : null
5599
+ state.showSectionRow && state.selectedSection ? /* @__PURE__ */ jsx19(SectionTreeItem, { section: state.selectedSection, selected: true }) : null
5479
5600
  ] }),
5480
- /* @__PURE__ */ jsxs10(DialogFooter, { className: "w-full px-6 pb-6", children: [
5481
- /* @__PURE__ */ jsx18(
5601
+ /* @__PURE__ */ jsxs11(DialogFooter, { className: "w-full px-6 pb-6", children: [
5602
+ /* @__PURE__ */ jsx19(
5482
5603
  Button,
5483
5604
  {
5484
5605
  type: "button",
@@ -5493,7 +5614,7 @@ function LinkEditorPanel({ state, onClose }) {
5493
5614
  children: state.secondaryLabel
5494
5615
  }
5495
5616
  ),
5496
- /* @__PURE__ */ jsx18(
5617
+ /* @__PURE__ */ jsx19(
5497
5618
  Button,
5498
5619
  {
5499
5620
  type: "button",
@@ -5512,11 +5633,11 @@ function LinkEditorPanel({ state, onClose }) {
5512
5633
  }
5513
5634
 
5514
5635
  // src/ui/link-modal/SectionPickerOverlay.tsx
5515
- import { useCallback, useEffect as useEffect4, useMemo as useMemo2, useRef as useRef4, useState as useState4 } from "react";
5636
+ import { useCallback as useCallback2, useEffect as useEffect4, useMemo as useMemo2, useRef as useRef4, useState as useState4 } from "react";
5516
5637
  import { createPortal } from "react-dom";
5517
5638
  import { ArrowLeft, Check } from "lucide-react";
5518
5639
  import { usePathname, useRouter } from "next/navigation";
5519
- import { jsx as jsx19, jsxs as jsxs11 } from "react/jsx-runtime";
5640
+ import { jsx as jsx20, jsxs as jsxs12 } from "react/jsx-runtime";
5520
5641
  var DIM_OVERLAY = "rgba(0, 0, 0, 0.45)";
5521
5642
  function rectsEqual(a, b) {
5522
5643
  if (a.size !== b.size) return false;
@@ -5643,7 +5764,7 @@ function SectionPickerOverlay({
5643
5764
  );
5644
5765
  sectionIdsRef.current = sectionIds;
5645
5766
  const rects = useSectionRects(sectionIdsKey, isOnTargetPage);
5646
- const applyHoverAt = useCallback(
5767
+ const applyHoverAt = useCallback2(
5647
5768
  (x, y, liveRects) => {
5648
5769
  const ids = sectionIdsRef.current;
5649
5770
  const map = liveRects ?? readSectionRects(ids);
@@ -5723,7 +5844,7 @@ function SectionPickerOverlay({
5723
5844
  window.removeEventListener("message", onPointerSync);
5724
5845
  };
5725
5846
  }, [rects, applyHoverAt]);
5726
- const handleSelect = useCallback(
5847
+ const handleSelect = useCallback2(
5727
5848
  (section) => {
5728
5849
  if (selectedId) return;
5729
5850
  setSelectedId(section.id);
@@ -5745,7 +5866,7 @@ function SectionPickerOverlay({
5745
5866
  const portalRoot = typeof document !== "undefined" ? document.querySelector("[data-ohw-bridge-root]") ?? document.body : null;
5746
5867
  if (!portalRoot) return null;
5747
5868
  return createPortal(
5748
- /* @__PURE__ */ jsxs11(
5869
+ /* @__PURE__ */ jsxs12(
5749
5870
  "div",
5750
5871
  {
5751
5872
  "data-ohw-section-picker": "",
@@ -5755,12 +5876,12 @@ function SectionPickerOverlay({
5755
5876
  role: "dialog",
5756
5877
  "aria-label": "Choose a section",
5757
5878
  children: [
5758
- /* @__PURE__ */ jsx19(
5879
+ /* @__PURE__ */ jsx20(
5759
5880
  "div",
5760
5881
  {
5761
5882
  className: "pointer-events-auto fixed left-5 z-[2]",
5762
5883
  style: { top: chromeClip.top + 20 },
5763
- children: /* @__PURE__ */ jsxs11(
5884
+ children: /* @__PURE__ */ jsxs12(
5764
5885
  Button,
5765
5886
  {
5766
5887
  type: "button",
@@ -5769,14 +5890,14 @@ function SectionPickerOverlay({
5769
5890
  className: "h-8 min-w-0 gap-1 border-border bg-background px-2 py-1.5 shadow-sm hover:bg-muted cursor-pointer",
5770
5891
  onClick: onBack,
5771
5892
  children: [
5772
- /* @__PURE__ */ jsx19(ArrowLeft, { className: "size-4 shrink-0", "aria-hidden": true }),
5893
+ /* @__PURE__ */ jsx20(ArrowLeft, { className: "size-4 shrink-0", "aria-hidden": true }),
5773
5894
  "Back"
5774
5895
  ]
5775
5896
  }
5776
5897
  )
5777
5898
  }
5778
5899
  ),
5779
- /* @__PURE__ */ jsx19(
5900
+ /* @__PURE__ */ jsx20(
5780
5901
  "div",
5781
5902
  {
5782
5903
  className: "pointer-events-none fixed left-1/2 z-[2] rounded-lg px-4 py-3 text-xs leading-4 tracking-[0.18px] text-white shadow-md",
@@ -5789,7 +5910,7 @@ function SectionPickerOverlay({
5789
5910
  children: "Click on section to select"
5790
5911
  }
5791
5912
  ),
5792
- !isOnTargetPage ? /* @__PURE__ */ jsx19(
5913
+ !isOnTargetPage ? /* @__PURE__ */ jsx20(
5793
5914
  "div",
5794
5915
  {
5795
5916
  className: "pointer-events-none fixed left-1/2 z-[1] -translate-x-1/2 rounded-md bg-background/90 px-3 py-2 text-sm text-muted-foreground shadow-sm",
@@ -5797,14 +5918,14 @@ function SectionPickerOverlay({
5797
5918
  children: "Loading page preview\u2026"
5798
5919
  }
5799
5920
  ) : null,
5800
- isOnTargetPage && liveSections.length === 0 ? /* @__PURE__ */ jsx19("div", { className: "pointer-events-auto fixed inset-0 z-[1] flex items-center justify-center bg-muted/40", children: /* @__PURE__ */ jsx19("p", { className: "text-sm text-muted-foreground", children: "No sections found on this page." }) }) : null,
5921
+ isOnTargetPage && liveSections.length === 0 ? /* @__PURE__ */ jsx20("div", { className: "pointer-events-auto fixed inset-0 z-[1] flex items-center justify-center bg-muted/40", children: /* @__PURE__ */ jsx20("p", { className: "text-sm text-muted-foreground", children: "No sections found on this page." }) }) : null,
5801
5922
  isOnTargetPage ? liveSections.map((section) => {
5802
5923
  const rect = rects.get(section.id);
5803
5924
  if (!rect || rect.width <= 0 || rect.height <= 0) return null;
5804
5925
  const isSelected = selectedId === section.id;
5805
5926
  const isHovered = hoveredId === section.id;
5806
5927
  const isLit = isSelected || isHovered;
5807
- return /* @__PURE__ */ jsxs11(
5928
+ return /* @__PURE__ */ jsxs12(
5808
5929
  "button",
5809
5930
  {
5810
5931
  type: "button",
@@ -5819,7 +5940,7 @@ function SectionPickerOverlay({
5819
5940
  "aria-label": `Select section ${section.label}`,
5820
5941
  onClick: () => handleSelect(section),
5821
5942
  children: [
5822
- isLit ? /* @__PURE__ */ jsx19(
5943
+ isLit ? /* @__PURE__ */ jsx20(
5823
5944
  "span",
5824
5945
  {
5825
5946
  className: "pointer-events-none absolute",
@@ -5832,13 +5953,13 @@ function SectionPickerOverlay({
5832
5953
  "aria-hidden": true
5833
5954
  }
5834
5955
  ) : null,
5835
- isSelected ? /* @__PURE__ */ jsx19(
5956
+ isSelected ? /* @__PURE__ */ jsx20(
5836
5957
  "span",
5837
5958
  {
5838
5959
  className: "absolute right-3 top-3 flex size-8 items-center justify-center rounded-full text-white",
5839
5960
  style: { backgroundColor: "var(--ohw-primary, #0885fe)" },
5840
5961
  "aria-hidden": true,
5841
- children: /* @__PURE__ */ jsx19(Check, { className: "size-5" })
5962
+ children: /* @__PURE__ */ jsx20(Check, { className: "size-5" })
5842
5963
  }
5843
5964
  ) : null
5844
5965
  ]
@@ -5854,7 +5975,7 @@ function SectionPickerOverlay({
5854
5975
  }
5855
5976
 
5856
5977
  // src/ui/link-modal/useLinkModalState.ts
5857
- import { useCallback as useCallback2, useEffect as useEffect5, useMemo as useMemo3, useState as useState5 } from "react";
5978
+ import { useCallback as useCallback3, useEffect as useEffect5, useMemo as useMemo3, useState as useState5 } from "react";
5858
5979
  function useLinkModalState({
5859
5980
  open,
5860
5981
  mode,
@@ -5873,7 +5994,7 @@ function useLinkModalState({
5873
5994
  const [step, setStep] = useState5("input");
5874
5995
  const [dropdownOpen, setDropdownOpen] = useState5(false);
5875
5996
  const [urlError, setUrlError] = useState5("");
5876
- const reset = useCallback2(() => {
5997
+ const reset = useCallback3(() => {
5877
5998
  setSearchValue("");
5878
5999
  setSelectedPage(null);
5879
6000
  setSelectedSection(null);
@@ -6018,7 +6139,7 @@ function useLinkModalState({
6018
6139
  }
6019
6140
 
6020
6141
  // src/ui/link-modal/LinkPopover.tsx
6021
- import { Fragment as Fragment4, jsx as jsx20, jsxs as jsxs12 } from "react/jsx-runtime";
6142
+ import { Fragment as Fragment4, jsx as jsx21, jsxs as jsxs13 } from "react/jsx-runtime";
6022
6143
  function postToParent(data) {
6023
6144
  window.parent?.postMessage(data, "*");
6024
6145
  }
@@ -6114,15 +6235,15 @@ function LinkPopover({
6114
6235
  );
6115
6236
  };
6116
6237
  }, [open, sectionPickerActive]);
6117
- return /* @__PURE__ */ jsxs12(Fragment4, { children: [
6118
- /* @__PURE__ */ jsx20(
6238
+ return /* @__PURE__ */ jsxs13(Fragment4, { children: [
6239
+ /* @__PURE__ */ jsx21(
6119
6240
  Dialog2,
6120
6241
  {
6121
6242
  open: open && !sectionPickerActive,
6122
6243
  onOpenChange: (next) => {
6123
6244
  if (!next) onClose?.();
6124
6245
  },
6125
- children: /* @__PURE__ */ jsx20(
6246
+ children: /* @__PURE__ */ jsx21(
6126
6247
  DialogContent,
6127
6248
  {
6128
6249
  ref: panelRef,
@@ -6132,12 +6253,12 @@ function LinkPopover({
6132
6253
  "data-ohw-bridge": "",
6133
6254
  showCloseButton: false,
6134
6255
  className: "gap-0 p-0 w-full max-w-[448px] pointer-events-auto z-[2147483646] overflow-visible",
6135
- children: /* @__PURE__ */ jsx20(LinkEditorPanel, { state, onClose })
6256
+ children: /* @__PURE__ */ jsx21(LinkEditorPanel, { state, onClose })
6136
6257
  }
6137
6258
  )
6138
6259
  }
6139
6260
  ),
6140
- sectionPickerActive && state.selectedPage ? /* @__PURE__ */ jsx20(
6261
+ sectionPickerActive && state.selectedPage ? /* @__PURE__ */ jsx21(
6141
6262
  SectionPickerOverlay,
6142
6263
  {
6143
6264
  pagePath: state.selectedPage.path,
@@ -6443,15 +6564,412 @@ function insertNavbarItem(href, label, afterAnchor = null) {
6443
6564
  };
6444
6565
  }
6445
6566
 
6567
+ // src/lib/footer-items.ts
6568
+ var FOOTER_ORDER_KEY = "__ohw_footer_order";
6569
+ var FOOTER_HREF_RE = /^footer-(\d+)-(\d+)-href$/;
6570
+ function parseFooterHrefKey(key) {
6571
+ if (!key) return null;
6572
+ const match = key.match(FOOTER_HREF_RE);
6573
+ if (!match) return null;
6574
+ return { col: parseInt(match[1], 10), item: parseInt(match[2], 10) };
6575
+ }
6576
+ function isFooterHrefKey(key) {
6577
+ return parseFooterHrefKey(key) !== null;
6578
+ }
6579
+ function getFooterRoot() {
6580
+ return document.querySelector('footer[data-ohw-section="footer"], footer');
6581
+ }
6582
+ function getFooterLinksContainer() {
6583
+ return document.querySelector("[data-ohw-footer-links]") ?? document.querySelector(".rb-footer-links");
6584
+ }
6585
+ function isFooterLinkAnchor(el) {
6586
+ if (!el.matches("[data-ohw-href-key]")) return false;
6587
+ if (!isFooterHrefKey(el.getAttribute("data-ohw-href-key"))) return false;
6588
+ return Boolean(el.querySelector('[data-ohw-editable="text"]'));
6589
+ }
6590
+ function listFooterColumns() {
6591
+ const root = getFooterLinksContainer() ?? getFooterRoot()?.querySelector(".rb-footer-links") ?? null;
6592
+ if (!root) return [];
6593
+ const explicit = Array.from(root.querySelectorAll(":scope > [data-ohw-footer-col]"));
6594
+ if (explicit.length > 0) return explicit;
6595
+ return Array.from(root.children).filter((el) => {
6596
+ if (!(el instanceof HTMLElement)) return false;
6597
+ return Array.from(el.querySelectorAll(":scope > [data-ohw-href-key]")).some(
6598
+ isFooterLinkAnchor
6599
+ );
6600
+ });
6601
+ }
6602
+ function listFooterLinksInColumn(column) {
6603
+ return Array.from(column.querySelectorAll(":scope > [data-ohw-href-key]")).filter(
6604
+ isFooterLinkAnchor
6605
+ );
6606
+ }
6607
+ function findFooterColumnForLink(anchor) {
6608
+ const columns = listFooterColumns();
6609
+ return columns.find((col) => col.contains(anchor)) ?? null;
6610
+ }
6611
+ function getFooterOrderFromDom() {
6612
+ return listFooterColumns().map(
6613
+ (col) => listFooterLinksInColumn(col).map((el) => el.getAttribute("data-ohw-href-key")).filter((key) => Boolean(key))
6614
+ );
6615
+ }
6616
+ function findFooterLinkByKey(hrefKey) {
6617
+ return document.querySelector(
6618
+ `footer [data-ohw-href-key="${CSS.escape(hrefKey)}"]`
6619
+ );
6620
+ }
6621
+ function syncFooterColumnIndices(columns) {
6622
+ columns.forEach((col, index) => {
6623
+ const next = String(index);
6624
+ if (col.getAttribute("data-ohw-footer-col") !== next) {
6625
+ col.setAttribute("data-ohw-footer-col", next);
6626
+ }
6627
+ });
6628
+ }
6629
+ function appendChildIfNeeded(parent, child) {
6630
+ if (parent.lastElementChild !== child) {
6631
+ parent.appendChild(child);
6632
+ }
6633
+ }
6634
+ function insertLinkBeforeNext(col, el, nextEl) {
6635
+ if (nextEl) {
6636
+ if (el.nextElementSibling !== nextEl) {
6637
+ col.insertBefore(el, nextEl);
6638
+ }
6639
+ return;
6640
+ }
6641
+ if (col.lastElementChild !== el) {
6642
+ col.appendChild(el);
6643
+ }
6644
+ }
6645
+ function applyFooterOrder(order) {
6646
+ const container = getFooterLinksContainer();
6647
+ if (!container) return;
6648
+ const columns = listFooterColumns();
6649
+ if (columns.length === 0) return;
6650
+ const currentOrder = getFooterOrderFromDom();
6651
+ if (ordersEqual(order, currentOrder)) {
6652
+ syncFooterColumnIndices(columns);
6653
+ return;
6654
+ }
6655
+ const colCount = Math.max(columns.length, order.length);
6656
+ const working = columns.slice(0, colCount);
6657
+ for (let i = 0; i < order.length && i < working.length; i++) {
6658
+ appendChildIfNeeded(container, working[i]);
6659
+ }
6660
+ for (let i = order.length; i < working.length; i++) {
6661
+ appendChildIfNeeded(container, working[i]);
6662
+ }
6663
+ const freshColumns = listFooterColumns();
6664
+ syncFooterColumnIndices(freshColumns);
6665
+ for (let c = 0; c < order.length; c++) {
6666
+ const col = freshColumns[c];
6667
+ if (!col) continue;
6668
+ const colOrder = order[c];
6669
+ for (let j = 0; j < colOrder.length; j++) {
6670
+ const hrefKey = colOrder[j];
6671
+ const el = findFooterLinkByKey(hrefKey);
6672
+ if (!el) continue;
6673
+ if (el.parentElement !== col) {
6674
+ col.appendChild(el);
6675
+ }
6676
+ const nextKey = colOrder[j + 1];
6677
+ const nextEl = nextKey ? findFooterLinkByKey(nextKey) : null;
6678
+ insertLinkBeforeNext(col, el, nextEl?.parentElement === col ? nextEl : null);
6679
+ }
6680
+ }
6681
+ }
6682
+ function ordersEqual(a, b) {
6683
+ if (a.length !== b.length) return false;
6684
+ for (let i = 0; i < a.length; i++) {
6685
+ const left = a[i];
6686
+ const right = b[i];
6687
+ if (left.length !== right.length) return false;
6688
+ for (let j = 0; j < left.length; j++) {
6689
+ if (left[j] !== right[j]) return false;
6690
+ }
6691
+ }
6692
+ return true;
6693
+ }
6694
+ function planFooterLinkMove(hrefKey, targetColIndex, insertIndex) {
6695
+ const order = getFooterOrderFromDom();
6696
+ if (targetColIndex < 0 || targetColIndex >= order.length) return null;
6697
+ let fromCol = -1;
6698
+ let fromIdx = -1;
6699
+ for (let c = 0; c < order.length; c++) {
6700
+ const idx = order[c].indexOf(hrefKey);
6701
+ if (idx >= 0) {
6702
+ fromCol = c;
6703
+ fromIdx = idx;
6704
+ break;
6705
+ }
6706
+ }
6707
+ if (fromCol < 0) return null;
6708
+ const next = order.map((col) => [...col]);
6709
+ next[fromCol].splice(fromIdx, 1);
6710
+ let adjusted = insertIndex;
6711
+ if (fromCol === targetColIndex && fromIdx < insertIndex) adjusted -= 1;
6712
+ adjusted = Math.max(0, Math.min(adjusted, next[targetColIndex].length));
6713
+ next[targetColIndex].splice(adjusted, 0, hrefKey);
6714
+ if (ordersEqual(next, order)) return null;
6715
+ return next;
6716
+ }
6717
+ function planFooterColumnMove(fromIndex, toIndex) {
6718
+ const order = getFooterOrderFromDom();
6719
+ const columns = listFooterColumns();
6720
+ if (fromIndex < 0 || fromIndex >= columns.length || toIndex < 0 || toIndex > columns.length) {
6721
+ return null;
6722
+ }
6723
+ if (toIndex === fromIndex || toIndex === fromIndex + 1) return null;
6724
+ const nextOrder = order.map((col) => [...col]);
6725
+ const [movedOrder] = nextOrder.splice(fromIndex, 1);
6726
+ if (!movedOrder) return null;
6727
+ let adjusted = toIndex;
6728
+ if (fromIndex < toIndex) adjusted -= 1;
6729
+ adjusted = Math.max(0, Math.min(adjusted, nextOrder.length));
6730
+ nextOrder.splice(adjusted, 0, movedOrder);
6731
+ if (ordersEqual(nextOrder, order)) return null;
6732
+ return nextOrder;
6733
+ }
6734
+ function parseFooterOrder(content) {
6735
+ const raw = content[FOOTER_ORDER_KEY];
6736
+ if (!raw) return null;
6737
+ try {
6738
+ const parsed = JSON.parse(raw);
6739
+ if (!Array.isArray(parsed)) return null;
6740
+ return parsed.filter((col) => Array.isArray(col)).map((col) => col.filter((key) => typeof key === "string" && key.length > 0));
6741
+ } catch {
6742
+ return null;
6743
+ }
6744
+ }
6745
+ function reconcileFooterOrderFromContent(content) {
6746
+ const order = parseFooterOrder(content);
6747
+ if (!order?.length) return;
6748
+ const current = getFooterOrderFromDom();
6749
+ if (ordersEqual(order, current)) {
6750
+ syncFooterColumnIndices(listFooterColumns());
6751
+ return;
6752
+ }
6753
+ applyFooterOrder(order);
6754
+ }
6755
+ function buildLinkDropSlots(column, columnIndex) {
6756
+ const links = listFooterLinksInColumn(column);
6757
+ const colRect = column.getBoundingClientRect();
6758
+ const slots = [];
6759
+ const barThickness = 3;
6760
+ if (links.length === 0) {
6761
+ slots.push({
6762
+ insertIndex: 0,
6763
+ columnIndex,
6764
+ left: colRect.left,
6765
+ top: colRect.top + colRect.height / 2 - barThickness / 2,
6766
+ width: colRect.width,
6767
+ height: barThickness,
6768
+ direction: "horizontal"
6769
+ });
6770
+ return slots;
6771
+ }
6772
+ for (let i = 0; i <= links.length; i++) {
6773
+ let top;
6774
+ if (i === 0) {
6775
+ top = links[0].getBoundingClientRect().top - barThickness / 2;
6776
+ } else if (i === links.length) {
6777
+ const last = links[links.length - 1].getBoundingClientRect();
6778
+ top = last.bottom - barThickness / 2;
6779
+ } else {
6780
+ const prev = links[i - 1].getBoundingClientRect();
6781
+ const next = links[i].getBoundingClientRect();
6782
+ top = (prev.bottom + next.top) / 2 - barThickness / 2;
6783
+ }
6784
+ const widthRef = i === 0 ? links[0].getBoundingClientRect() : i === links.length ? links[links.length - 1].getBoundingClientRect() : links[i].getBoundingClientRect();
6785
+ slots.push({
6786
+ insertIndex: i,
6787
+ columnIndex,
6788
+ left: widthRef.left,
6789
+ top,
6790
+ width: Math.max(widthRef.width, colRect.width * 0.8),
6791
+ height: barThickness,
6792
+ direction: "horizontal"
6793
+ });
6794
+ }
6795
+ return slots;
6796
+ }
6797
+ function buildColumnDropSlots() {
6798
+ const columns = listFooterColumns();
6799
+ const slots = [];
6800
+ const barThickness = 3;
6801
+ if (columns.length === 0) return slots;
6802
+ for (let i = 0; i <= columns.length; i++) {
6803
+ let left;
6804
+ let height;
6805
+ let top;
6806
+ if (i === 0) {
6807
+ const first = columns[0].getBoundingClientRect();
6808
+ left = first.left - barThickness / 2;
6809
+ top = first.top;
6810
+ height = first.height;
6811
+ } else if (i === columns.length) {
6812
+ const last = columns[columns.length - 1].getBoundingClientRect();
6813
+ left = last.right - barThickness / 2;
6814
+ top = last.top;
6815
+ height = last.height;
6816
+ } else {
6817
+ const prev = columns[i - 1].getBoundingClientRect();
6818
+ const next = columns[i].getBoundingClientRect();
6819
+ left = (prev.right + next.left) / 2 - barThickness / 2;
6820
+ top = Math.min(prev.top, next.top);
6821
+ height = Math.max(prev.bottom, next.bottom) - top;
6822
+ }
6823
+ slots.push({
6824
+ insertIndex: i,
6825
+ columnIndex: i,
6826
+ left,
6827
+ top,
6828
+ width: barThickness,
6829
+ height: Math.max(height, 24),
6830
+ direction: "vertical"
6831
+ });
6832
+ }
6833
+ return slots;
6834
+ }
6835
+ function hitTestLinkDropSlot(clientX, clientY, draggedHrefKey) {
6836
+ const columns = listFooterColumns();
6837
+ let best = null;
6838
+ for (let c = 0; c < columns.length; c++) {
6839
+ const col = columns[c];
6840
+ const colRect = col.getBoundingClientRect();
6841
+ const inColX = clientX >= colRect.left - 24 && clientX <= colRect.right + 24;
6842
+ if (!inColX) continue;
6843
+ const slots = buildLinkDropSlots(col, c).filter((slot) => {
6844
+ const links = listFooterLinksInColumn(col);
6845
+ const fromIdx = links.findIndex(
6846
+ (el) => el.getAttribute("data-ohw-href-key") === draggedHrefKey
6847
+ );
6848
+ if (fromIdx < 0) return true;
6849
+ if (c === findColumnIndexForKey(draggedHrefKey) && (slot.insertIndex === fromIdx || slot.insertIndex === fromIdx + 1)) {
6850
+ return true;
6851
+ }
6852
+ return true;
6853
+ });
6854
+ for (const slot of slots) {
6855
+ const cy = slot.top + slot.height / 2;
6856
+ const dist = Math.abs(clientY - cy) + (inColX ? 0 : 1e3);
6857
+ if (!best || dist < best.dist) best = { slot, dist };
6858
+ }
6859
+ }
6860
+ return best?.slot ?? null;
6861
+ }
6862
+ function findColumnIndexForKey(hrefKey) {
6863
+ const order = getFooterOrderFromDom();
6864
+ for (let c = 0; c < order.length; c++) {
6865
+ if (order[c].includes(hrefKey)) return c;
6866
+ }
6867
+ return -1;
6868
+ }
6869
+ function hitTestColumnDropSlot(clientX, _clientY) {
6870
+ const slots = buildColumnDropSlots();
6871
+ let best = null;
6872
+ for (const slot of slots) {
6873
+ const cx2 = slot.left + slot.width / 2;
6874
+ const dist = Math.abs(clientX - cx2);
6875
+ if (!best || dist < best.dist) best = { slot, dist };
6876
+ }
6877
+ return best?.slot ?? null;
6878
+ }
6879
+
6880
+ // src/lib/carousel.ts
6881
+ import { useEffect as useEffect7, useState as useState6 } from "react";
6882
+ var CAROUSEL_ATTR = "data-ohw-carousel";
6883
+ var CAROUSEL_VALUE_ATTR = "data-ohw-carousel-value";
6884
+ var CAROUSEL_SLIDE_ATTR = "data-ohw-carousel-slide";
6885
+ var CAROUSEL_EVENT = "ohw:carousel-change";
6886
+ function listCarouselKeys() {
6887
+ const keys = /* @__PURE__ */ new Set();
6888
+ document.querySelectorAll(`[${CAROUSEL_ATTR}]`).forEach((el) => {
6889
+ const key = el.getAttribute("data-ohw-key");
6890
+ if (key) keys.add(key);
6891
+ });
6892
+ return [...keys];
6893
+ }
6894
+ function containersForKey(key) {
6895
+ return Array.from(
6896
+ document.querySelectorAll(`[${CAROUSEL_ATTR}][data-ohw-key="${CSS.escape(key)}"]`)
6897
+ );
6898
+ }
6899
+ function isCarouselKey(key) {
6900
+ return containersForKey(key).length > 0;
6901
+ }
6902
+ function parseSlides(raw) {
6903
+ if (!raw) return [];
6904
+ try {
6905
+ const parsed = JSON.parse(raw);
6906
+ if (!Array.isArray(parsed)) return [];
6907
+ return parsed.filter((s) => Boolean(s) && typeof s === "object").map((s) => ({ src: String(s.src ?? ""), alt: String(s.alt ?? "") }));
6908
+ } catch {
6909
+ return [];
6910
+ }
6911
+ }
6912
+ function readCarouselValue(key) {
6913
+ const container = containersForKey(key)[0];
6914
+ if (!container) return [];
6915
+ const fromAttr = parseSlides(container.getAttribute(CAROUSEL_VALUE_ATTR));
6916
+ if (fromAttr.length > 0) return fromAttr;
6917
+ return Array.from(container.querySelectorAll(`[${CAROUSEL_SLIDE_ATTR}]`)).filter((slide) => slide.closest(`[${CAROUSEL_ATTR}]`) === container).sort((a, b) => slideIndex(a) - slideIndex(b)).map((slide) => {
6918
+ const img = slide instanceof HTMLImageElement ? slide : slide.querySelector("img");
6919
+ return { src: img?.src ?? "", alt: img?.alt ?? "" };
6920
+ });
6921
+ }
6922
+ function slideIndex(el) {
6923
+ const raw = el.getAttribute(CAROUSEL_SLIDE_ATTR);
6924
+ const n = raw ? parseInt(raw, 10) : NaN;
6925
+ return Number.isFinite(n) ? n : 0;
6926
+ }
6927
+ function applyCarouselValue(key, slides) {
6928
+ const value = JSON.stringify(slides);
6929
+ for (const container of containersForKey(key)) {
6930
+ if (container.getAttribute(CAROUSEL_VALUE_ATTR) === value) continue;
6931
+ container.setAttribute(CAROUSEL_VALUE_ATTR, value);
6932
+ container.dispatchEvent(
6933
+ new CustomEvent(CAROUSEL_EVENT, { detail: { images: slides } })
6934
+ );
6935
+ }
6936
+ }
6937
+ function applyCarouselNode(key, val) {
6938
+ if (!isCarouselKey(key)) return false;
6939
+ applyCarouselValue(key, parseSlides(val));
6940
+ return true;
6941
+ }
6942
+ function useOhwCarousel(key, initial) {
6943
+ const [images, setImages] = useState6(initial);
6944
+ useEffect7(() => {
6945
+ const el = document.querySelector(
6946
+ `[${CAROUSEL_ATTR}][data-ohw-key="${CSS.escape(key)}"]`
6947
+ );
6948
+ if (!el) return;
6949
+ const onChange = (e) => {
6950
+ const detail = e.detail;
6951
+ if (Array.isArray(detail?.images)) setImages(detail.images);
6952
+ };
6953
+ el.addEventListener(CAROUSEL_EVENT, onChange);
6954
+ return () => el.removeEventListener(CAROUSEL_EVENT, onChange);
6955
+ }, [key]);
6956
+ const bind = {
6957
+ [CAROUSEL_ATTR]: "",
6958
+ "data-ohw-key": key,
6959
+ [CAROUSEL_VALUE_ATTR]: JSON.stringify(images)
6960
+ };
6961
+ return { images, setImages, bind };
6962
+ }
6963
+
6446
6964
  // src/ui/navbar-container-chrome.tsx
6447
6965
  import { Plus as Plus2 } from "lucide-react";
6448
- import { jsx as jsx21 } from "react/jsx-runtime";
6966
+ import { jsx as jsx22 } from "react/jsx-runtime";
6449
6967
  function NavbarContainerChrome({
6450
6968
  rect,
6451
6969
  onAdd
6452
6970
  }) {
6453
6971
  const chromeGap = 6;
6454
- return /* @__PURE__ */ jsx21(
6972
+ return /* @__PURE__ */ jsx22(
6455
6973
  "div",
6456
6974
  {
6457
6975
  "data-ohw-navbar-container-chrome": "",
@@ -6463,7 +6981,7 @@ function NavbarContainerChrome({
6463
6981
  width: rect.width + chromeGap * 2,
6464
6982
  height: rect.height + chromeGap * 2
6465
6983
  },
6466
- children: /* @__PURE__ */ jsx21(
6984
+ children: /* @__PURE__ */ jsx22(
6467
6985
  "button",
6468
6986
  {
6469
6987
  type: "button",
@@ -6480,15 +6998,57 @@ function NavbarContainerChrome({
6480
6998
  e.stopPropagation();
6481
6999
  onAdd();
6482
7000
  },
6483
- children: /* @__PURE__ */ jsx21(Plus2, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
7001
+ children: /* @__PURE__ */ jsx22(Plus2, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
6484
7002
  }
6485
7003
  )
6486
7004
  }
6487
7005
  );
6488
7006
  }
6489
7007
 
7008
+ // src/ui/drop-indicator.tsx
7009
+ import * as React9 from "react";
7010
+ import { jsx as jsx23 } from "react/jsx-runtime";
7011
+ var dropIndicatorVariants = cva(
7012
+ "ov-gap-line pointer-events-none shrink-0 transition-opacity duration-150",
7013
+ {
7014
+ variants: {
7015
+ direction: {
7016
+ vertical: "h-6 w-[3px]",
7017
+ horizontal: "h-[3px] w-[200px]"
7018
+ },
7019
+ state: {
7020
+ default: "opacity-0",
7021
+ hover: "opacity-100",
7022
+ dragIdle: "opacity-40",
7023
+ dragActive: "opacity-100"
7024
+ }
7025
+ },
7026
+ defaultVariants: {
7027
+ direction: "vertical",
7028
+ state: "default"
7029
+ }
7030
+ }
7031
+ );
7032
+ var DropIndicator = React9.forwardRef(
7033
+ ({ className, direction, state, ...props }, ref) => {
7034
+ return /* @__PURE__ */ jsx23(
7035
+ "div",
7036
+ {
7037
+ ref,
7038
+ "data-slot": "drop-indicator",
7039
+ "data-direction": direction ?? "vertical",
7040
+ "data-state": state ?? "default",
7041
+ "aria-hidden": "true",
7042
+ className: cn(dropIndicatorVariants({ direction, state }), className),
7043
+ ...props
7044
+ }
7045
+ );
7046
+ }
7047
+ );
7048
+ DropIndicator.displayName = "DropIndicator";
7049
+
6490
7050
  // src/ui/badge.tsx
6491
- import { jsx as jsx22 } from "react/jsx-runtime";
7051
+ import { jsx as jsx24 } from "react/jsx-runtime";
6492
7052
  var badgeVariants = cva(
6493
7053
  "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",
6494
7054
  {
@@ -6506,12 +7066,12 @@ var badgeVariants = cva(
6506
7066
  }
6507
7067
  );
6508
7068
  function Badge({ className, variant, ...props }) {
6509
- return /* @__PURE__ */ jsx22("div", { className: cn(badgeVariants({ variant }), className), ...props });
7069
+ return /* @__PURE__ */ jsx24("div", { className: cn(badgeVariants({ variant }), className), ...props });
6510
7070
  }
6511
7071
 
6512
7072
  // src/OhhwellsBridge.tsx
6513
7073
  import { Link as Link2 } from "lucide-react";
6514
- import { Fragment as Fragment5, jsx as jsx23, jsxs as jsxs13 } from "react/jsx-runtime";
7074
+ import { Fragment as Fragment5, jsx as jsx25, jsxs as jsxs14 } from "react/jsx-runtime";
6515
7075
  var PRIMARY2 = "#0885FE";
6516
7076
  var IMAGE_FADE_MS = 300;
6517
7077
  function runOpacityFade(el, onDone) {
@@ -6690,7 +7250,7 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
6690
7250
  const root = createRoot(container);
6691
7251
  flushSync(() => {
6692
7252
  root.render(
6693
- /* @__PURE__ */ jsx23(
7253
+ /* @__PURE__ */ jsx25(
6694
7254
  SchedulingWidget,
6695
7255
  {
6696
7256
  notifyOnConnect,
@@ -6752,7 +7312,7 @@ function isDragHandleDisabled(el) {
6752
7312
  return raw === "true" || raw === "";
6753
7313
  });
6754
7314
  }
6755
- function collectEditableNodes() {
7315
+ function collectEditableNodes(extraContent) {
6756
7316
  const nodes = Array.from(document.querySelectorAll("[data-ohw-editable]")).map((el) => {
6757
7317
  if (el.dataset.ohwEditable === "image") {
6758
7318
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -6780,6 +7340,14 @@ function collectEditableNodes() {
6780
7340
  if (!key) return;
6781
7341
  nodes.push({ key, type: "link", text: getLinkHref2(el) });
6782
7342
  });
7343
+ if (extraContent) {
7344
+ for (const key of [NAV_ORDER_KEY, FOOTER_ORDER_KEY, NAV_COUNT_KEY]) {
7345
+ const text = extraContent[key];
7346
+ if (typeof text === "string" && text.length > 0) {
7347
+ nodes.push({ key, type: "meta", text });
7348
+ }
7349
+ }
7350
+ }
6783
7351
  document.querySelectorAll('[data-ohw-editable="video"]').forEach((el) => {
6784
7352
  const key = el.dataset.ohwKey ?? "";
6785
7353
  const video = getVideoEl(el);
@@ -6787,13 +7355,10 @@ function collectEditableNodes() {
6787
7355
  nodes.push({ key: `${key}${VIDEO_AUTOPLAY_SUFFIX}`, type: "video-setting", text: String(video.autoplay) });
6788
7356
  nodes.push({ key: `${key}${VIDEO_MUTED_SUFFIX}`, type: "video-setting", text: String(video.muted) });
6789
7357
  });
6790
- const seen = /* @__PURE__ */ new Set();
6791
- return nodes.filter((node) => {
6792
- if (!node.key) return true;
6793
- if (seen.has(node.key)) return false;
6794
- seen.add(node.key);
6795
- return true;
6796
- });
7358
+ for (const key of listCarouselKeys()) {
7359
+ nodes.push({ key, type: "carousel", text: JSON.stringify(readCarouselValue(key)) });
7360
+ }
7361
+ return nodes;
6797
7362
  }
6798
7363
  function isMediaEditable(el) {
6799
7364
  const t = el.dataset.ohwEditable;
@@ -6881,6 +7446,29 @@ function getHrefKeyFromElement(el) {
6881
7446
  if (!key) return null;
6882
7447
  return { anchor, key };
6883
7448
  }
7449
+ function disableNativeHrefDrag(el) {
7450
+ if (el.draggable) el.draggable = false;
7451
+ if (el.getAttribute("draggable") !== "false") {
7452
+ el.setAttribute("draggable", "false");
7453
+ }
7454
+ }
7455
+ function clearTextSelection() {
7456
+ const sel = window.getSelection();
7457
+ if (sel && !sel.isCollapsed) sel.removeAllRanges();
7458
+ }
7459
+ function armFooterPressDrag() {
7460
+ document.documentElement.setAttribute("data-ohw-footer-press-drag", "");
7461
+ }
7462
+ function lockFooterDuringDrag() {
7463
+ document.documentElement.removeAttribute("data-ohw-footer-press-drag");
7464
+ document.documentElement.setAttribute("data-ohw-item-dragging", "");
7465
+ clearTextSelection();
7466
+ }
7467
+ function unlockFooterDragInteraction() {
7468
+ document.documentElement.removeAttribute("data-ohw-footer-press-drag");
7469
+ document.documentElement.removeAttribute("data-ohw-item-dragging");
7470
+ clearTextSelection();
7471
+ }
6884
7472
  function isNavbarButton2(el) {
6885
7473
  return Boolean(el.closest('[data-ohw-role="navbar-button"]'));
6886
7474
  }
@@ -6943,7 +7531,7 @@ function isInferredFooterGroup(el) {
6943
7531
  return Array.from(el.querySelectorAll(":scope > [data-ohw-href-key]")).some(isNavigationItem);
6944
7532
  }
6945
7533
  function isNavigationContainer(el) {
6946
- return el.hasAttribute("data-ohw-nav-container") || isNavigationRoot(el) || isInferredFooterGroup(el);
7534
+ return el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-footer-col") || isNavigationRoot(el) || isInferredFooterGroup(el);
6947
7535
  }
6948
7536
  function isPointOverNavigation(x, y) {
6949
7537
  const navRoot = document.querySelector("[data-ohw-nav-root]") ?? document.querySelector("nav");
@@ -7014,6 +7602,22 @@ function placeCaretAtPoint(el, x, y) {
7014
7602
  }
7015
7603
  }
7016
7604
  }
7605
+ function selectAllTextInEditable(el) {
7606
+ const selection = window.getSelection();
7607
+ if (!selection) return;
7608
+ const range = document.createRange();
7609
+ range.selectNodeContents(el);
7610
+ selection.removeAllRanges();
7611
+ selection.addRange(range);
7612
+ }
7613
+ function getNavigationLabelEditable(target) {
7614
+ const editable = target.closest('[data-ohw-editable="text"]');
7615
+ if (!editable) return null;
7616
+ const hrefCtx = getHrefKeyFromElement(editable);
7617
+ const navAnchor = hrefCtx ? getNavigationItemAnchor(hrefCtx.anchor) : null;
7618
+ if (!navAnchor) return null;
7619
+ return { editable, navAnchor };
7620
+ }
7017
7621
  function collectSections() {
7018
7622
  return collectSectionsFromDom();
7019
7623
  }
@@ -7137,6 +7741,8 @@ var ICONS = {
7137
7741
  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"/>',
7138
7742
  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"/>'
7139
7743
  };
7744
+ var TOOLBAR_PILL_PADDING = 2;
7745
+ var TOOLBAR_TOGGLE_GAP = 4;
7140
7746
  var TOOLBAR_GROUPS = [
7141
7747
  [
7142
7748
  { cmd: "bold", title: "Bold" },
@@ -7161,7 +7767,7 @@ function EditGlowChrome({
7161
7767
  dragDisabled = false
7162
7768
  }) {
7163
7769
  const GAP = 6;
7164
- return /* @__PURE__ */ jsxs13(
7770
+ return /* @__PURE__ */ jsxs14(
7165
7771
  "div",
7166
7772
  {
7167
7773
  ref: elRef,
@@ -7176,7 +7782,7 @@ function EditGlowChrome({
7176
7782
  zIndex: 2147483646
7177
7783
  },
7178
7784
  children: [
7179
- /* @__PURE__ */ jsx23(
7785
+ /* @__PURE__ */ jsx25(
7180
7786
  "div",
7181
7787
  {
7182
7788
  style: {
@@ -7189,7 +7795,7 @@ function EditGlowChrome({
7189
7795
  }
7190
7796
  }
7191
7797
  ),
7192
- reorderHrefKey && /* @__PURE__ */ jsx23(
7798
+ reorderHrefKey && /* @__PURE__ */ jsx25(
7193
7799
  "div",
7194
7800
  {
7195
7801
  "data-ohw-drag-handle-container": "",
@@ -7201,7 +7807,7 @@ function EditGlowChrome({
7201
7807
  transform: "translate(calc(-100% - 7px), -50%)",
7202
7808
  pointerEvents: dragDisabled ? "none" : "auto"
7203
7809
  },
7204
- children: /* @__PURE__ */ jsx23(
7810
+ children: /* @__PURE__ */ jsx25(
7205
7811
  DragHandle,
7206
7812
  {
7207
7813
  "aria-label": `Reorder ${reorderHrefKey}`,
@@ -7305,7 +7911,7 @@ function FloatingToolbar({
7305
7911
  onEditLink
7306
7912
  }) {
7307
7913
  const { top, left, transform } = calcToolbarPos(rect, parentScroll, 330);
7308
- return /* @__PURE__ */ jsx23(
7914
+ return /* @__PURE__ */ jsx25(
7309
7915
  "div",
7310
7916
  {
7311
7917
  ref: elRef,
@@ -7315,14 +7921,23 @@ function FloatingToolbar({
7315
7921
  left,
7316
7922
  transform,
7317
7923
  zIndex: 2147483647,
7924
+ background: "#fff",
7925
+ border: "1px solid #E7E5E4",
7926
+ borderRadius: 6,
7927
+ boxShadow: "0px 2px 4px -2px rgba(0,0,0,.1),0px 4px 6px -1px rgba(0,0,0,.1)",
7928
+ display: "flex",
7929
+ alignItems: "center",
7930
+ padding: TOOLBAR_PILL_PADDING,
7931
+ gap: TOOLBAR_TOGGLE_GAP,
7932
+ fontFamily: "sans-serif",
7318
7933
  pointerEvents: "auto"
7319
7934
  },
7320
- children: /* @__PURE__ */ jsxs13(CustomToolbar, { children: [
7321
- TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ jsxs13(React9.Fragment, { children: [
7322
- gi > 0 && /* @__PURE__ */ jsx23(CustomToolbarDivider, {}),
7935
+ children: /* @__PURE__ */ jsxs14(CustomToolbar, { children: [
7936
+ TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ jsxs14(React10.Fragment, { children: [
7937
+ gi > 0 && /* @__PURE__ */ jsx25(CustomToolbarDivider, {}),
7323
7938
  btns.map((btn) => {
7324
7939
  const isActive = activeCommands.has(btn.cmd);
7325
- return /* @__PURE__ */ jsx23(
7940
+ return /* @__PURE__ */ jsx25(
7326
7941
  CustomToolbarButton,
7327
7942
  {
7328
7943
  title: btn.title,
@@ -7331,7 +7946,7 @@ function FloatingToolbar({
7331
7946
  e.preventDefault();
7332
7947
  onCommand(btn.cmd);
7333
7948
  },
7334
- children: /* @__PURE__ */ jsx23(
7949
+ children: /* @__PURE__ */ jsx25(
7335
7950
  "svg",
7336
7951
  {
7337
7952
  width: "16",
@@ -7352,7 +7967,7 @@ function FloatingToolbar({
7352
7967
  );
7353
7968
  })
7354
7969
  ] }, gi)),
7355
- showEditLink ? /* @__PURE__ */ jsx23(
7970
+ showEditLink ? /* @__PURE__ */ jsx25(
7356
7971
  CustomToolbarButton,
7357
7972
  {
7358
7973
  type: "button",
@@ -7366,7 +7981,7 @@ function FloatingToolbar({
7366
7981
  e.preventDefault();
7367
7982
  e.stopPropagation();
7368
7983
  },
7369
- children: /* @__PURE__ */ jsx23(Link2, { className: "size-4 shrink-0", "aria-hidden": true })
7984
+ children: /* @__PURE__ */ jsx25(Link2, { className: "size-4 shrink-0", "aria-hidden": true })
7370
7985
  }
7371
7986
  ) : null
7372
7987
  ] })
@@ -7383,7 +7998,7 @@ function StateToggle({
7383
7998
  states,
7384
7999
  onStateChange
7385
8000
  }) {
7386
- return /* @__PURE__ */ jsx23(
8001
+ return /* @__PURE__ */ jsx25(
7387
8002
  ToggleGroup,
7388
8003
  {
7389
8004
  "data-ohw-state-toggle": "",
@@ -7397,7 +8012,7 @@ function StateToggle({
7397
8012
  left: rect.right - 8,
7398
8013
  transform: "translateX(-100%)"
7399
8014
  },
7400
- children: states.map((state) => /* @__PURE__ */ jsx23(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
8015
+ children: states.map((state) => /* @__PURE__ */ jsx25(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
7401
8016
  }
7402
8017
  );
7403
8018
  }
@@ -7424,8 +8039,8 @@ function OhhwellsBridge() {
7424
8039
  const router = useRouter2();
7425
8040
  const searchParams = useSearchParams();
7426
8041
  const isEditMode = isEditSessionActive();
7427
- const [bridgeRoot, setBridgeRoot] = useState6(null);
7428
- useEffect7(() => {
8042
+ const [bridgeRoot, setBridgeRoot] = useState7(null);
8043
+ useEffect8(() => {
7429
8044
  const figtreeFontId = "ohw-figtree-font";
7430
8045
  if (!document.getElementById(figtreeFontId)) {
7431
8046
  const preconnect1 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.googleapis.com" });
@@ -7453,12 +8068,12 @@ function OhhwellsBridge() {
7453
8068
  const subdomainFromQuery = searchParams.get("subdomain");
7454
8069
  const subdomain = resolveSubdomain(subdomainFromQuery);
7455
8070
  useLinkHrefGuardian(pathname, subdomain, isEditMode);
7456
- const postToParent2 = useCallback3((data) => {
8071
+ const postToParent2 = useCallback4((data) => {
7457
8072
  if (typeof window !== "undefined" && window.parent !== window) {
7458
8073
  window.parent.postMessage(data, "*");
7459
8074
  }
7460
8075
  }, []);
7461
- const [fetchState, setFetchState] = useState6("idle");
8076
+ const [fetchState, setFetchState] = useState7("idle");
7462
8077
  const autoSaveTimers = useRef5(/* @__PURE__ */ new Map());
7463
8078
  const activeElRef = useRef5(null);
7464
8079
  const selectedElRef = useRef5(null);
@@ -7466,8 +8081,8 @@ function OhhwellsBridge() {
7466
8081
  const activeStateElRef = useRef5(null);
7467
8082
  const parentScrollRef = useRef5(null);
7468
8083
  const visibleViewportRef = useRef5(null);
7469
- const [dialogPortalContainer, setDialogPortalContainer] = useState6(null);
7470
- const attachVisibleViewport = useCallback3((node) => {
8084
+ const [dialogPortalContainer, setDialogPortalContainer] = useState7(null);
8085
+ const attachVisibleViewport = useCallback4((node) => {
7471
8086
  visibleViewportRef.current = node;
7472
8087
  setDialogPortalContainer(node);
7473
8088
  if (node) applyVisibleViewport(node, parentScrollRef.current);
@@ -7477,8 +8092,10 @@ function OhhwellsBridge() {
7477
8092
  const hoveredImageRef = useRef5(null);
7478
8093
  const hoveredImageHasTextOverlapRef = useRef5(false);
7479
8094
  const dragOverElRef = useRef5(null);
7480
- const [mediaHover, setMediaHover] = useState6(null);
7481
- const [uploadingRects, setUploadingRects] = useState6({});
8095
+ const [mediaHover, setMediaHover] = useState7(null);
8096
+ const [carouselHover, setCarouselHover] = useState7(null);
8097
+ const [uploadingRects, setUploadingRects] = useState7({});
8098
+ const pendingUploadRectRef = useRef5(null);
7482
8099
  const hoveredGapRef = useRef5(null);
7483
8100
  const imageUnhoverTimerRef = useRef5(null);
7484
8101
  const imageShowTimerRef = useRef5(null);
@@ -7503,30 +8120,38 @@ function OhhwellsBridge() {
7503
8120
  postToParentRef.current = postToParent2;
7504
8121
  const sectionsLoadedRef = useRef5(false);
7505
8122
  const pendingScheduleConfigRequests = useRef5([]);
7506
- const [toolbarRect, setToolbarRect] = useState6(null);
7507
- const [toolbarVariant, setToolbarVariant] = useState6("none");
8123
+ const [toolbarRect, setToolbarRect] = useState7(null);
8124
+ const [toolbarVariant, setToolbarVariant] = useState7("none");
7508
8125
  const toolbarVariantRef = useRef5("none");
7509
8126
  toolbarVariantRef.current = toolbarVariant;
7510
- const [reorderHrefKey, setReorderHrefKey] = useState6(null);
7511
- const [reorderDragDisabled, setReorderDragDisabled] = useState6(false);
7512
- const [toggleState, setToggleState] = useState6(null);
7513
- const [maxBadge, setMaxBadge] = useState6(null);
7514
- const [activeCommands, setActiveCommands] = useState6(/* @__PURE__ */ new Set());
7515
- const [sectionGap, setSectionGap] = useState6(null);
7516
- const [toolbarShowEditLink, setToolbarShowEditLink] = useState6(false);
8127
+ const [reorderHrefKey, setReorderHrefKey] = useState7(null);
8128
+ const [reorderDragDisabled, setReorderDragDisabled] = useState7(false);
8129
+ const [toggleState, setToggleState] = useState7(null);
8130
+ const [maxBadge, setMaxBadge] = useState7(null);
8131
+ const [activeCommands, setActiveCommands] = useState7(/* @__PURE__ */ new Set());
8132
+ const [sectionGap, setSectionGap] = useState7(null);
8133
+ const [toolbarShowEditLink, setToolbarShowEditLink] = useState7(false);
7517
8134
  const hoveredNavContainerRef = useRef5(null);
7518
- const [hoveredNavContainerRect, setHoveredNavContainerRect] = useState6(null);
8135
+ const [hoveredNavContainerRect, setHoveredNavContainerRect] = useState7(null);
7519
8136
  const hoveredItemElRef = useRef5(null);
7520
- const [hoveredItemRect, setHoveredItemRect] = useState6(null);
8137
+ const [hoveredItemRect, setHoveredItemRect] = useState7(null);
7521
8138
  const siblingHintElRef = useRef5(null);
7522
- const [siblingHintRect, setSiblingHintRect] = useState6(null);
7523
- const [isItemDragging, setIsItemDragging] = useState6(false);
7524
- const [linkPopover, setLinkPopover] = useState6(null);
8139
+ const [siblingHintRect, setSiblingHintRect] = useState7(null);
8140
+ const [siblingHintRects, setSiblingHintRects] = useState7([]);
8141
+ const [isItemDragging, setIsItemDragging] = useState7(false);
8142
+ const [isFooterFrameSelection, setIsFooterFrameSelection] = useState7(false);
8143
+ const footerDragRef = useRef5(null);
8144
+ const [footerDropSlots, setFooterDropSlots] = useState7([]);
8145
+ const [activeFooterDropIndex, setActiveFooterDropIndex] = useState7(null);
8146
+ const [draggedItemRect, setDraggedItemRect] = useState7(null);
8147
+ const footerPointerDragRef = useRef5(null);
8148
+ const suppressNextClickRef = useRef5(false);
8149
+ const [linkPopover, setLinkPopover] = useState7(null);
7525
8150
  const linkPopoverSessionRef = useRef5(null);
7526
8151
  const addNavAfterAnchorRef = useRef5(null);
7527
8152
  const editContentRef = useRef5({});
7528
- const [sitePages, setSitePages] = useState6([]);
7529
- const [sectionsByPath, setSectionsByPath] = useState6({});
8153
+ const [sitePages, setSitePages] = useState7([]);
8154
+ const [sectionsByPath, setSectionsByPath] = useState7({});
7530
8155
  const sectionsPrefetchGenRef = useRef5(0);
7531
8156
  const setLinkPopoverRef = useRef5(setLinkPopover);
7532
8157
  const linkPopoverPanelRef = useRef5(null);
@@ -7537,7 +8162,7 @@ function OhhwellsBridge() {
7537
8162
  const bumpLinkPopoverGrace = () => {
7538
8163
  linkPopoverGraceUntilRef.current = Date.now() + 350;
7539
8164
  };
7540
- const runSectionsPrefetch = useCallback3((pages) => {
8165
+ const runSectionsPrefetch = useCallback4((pages) => {
7541
8166
  if (!isEditMode || shouldUseDevFixtures() || pages.length === 0) return;
7542
8167
  const gen = ++sectionsPrefetchGenRef.current;
7543
8168
  const paths = pages.map((p) => p.path);
@@ -7558,8 +8183,12 @@ function OhhwellsBridge() {
7558
8183
  }, [isEditMode, pathname]);
7559
8184
  const runSectionsPrefetchRef = useRef5(runSectionsPrefetch);
7560
8185
  runSectionsPrefetchRef.current = runSectionsPrefetch;
7561
- useEffect7(() => {
7562
- if (!linkPopover) return;
8186
+ useEffect8(() => {
8187
+ if (!linkPopover) {
8188
+ document.documentElement.removeAttribute("data-ohw-link-popover-open");
8189
+ return;
8190
+ }
8191
+ document.documentElement.setAttribute("data-ohw-link-popover-open", "");
7563
8192
  if (hoveredImageRef.current) {
7564
8193
  hoveredImageRef.current = null;
7565
8194
  hoveredImageHasTextOverlapRef.current = false;
@@ -7586,14 +8215,10 @@ function OhhwellsBridge() {
7586
8215
  document.addEventListener("touchmove", preventBackgroundScroll, scrollOpts);
7587
8216
  postToParent2({ type: "ow:image-unhover" });
7588
8217
  return () => {
7589
- postToParent2({ type: "ow:link-modal-lock", locked: false });
7590
- html.style.overflow = prevHtmlOverflow;
7591
- body.style.overflow = prevBodyOverflow;
7592
- document.removeEventListener("wheel", preventBackgroundScroll, scrollOpts);
7593
- document.removeEventListener("touchmove", preventBackgroundScroll, scrollOpts);
8218
+ document.documentElement.removeAttribute("data-ohw-link-popover-open");
7594
8219
  };
7595
8220
  }, [linkPopover, postToParent2]);
7596
- useEffect7(() => {
8221
+ useEffect8(() => {
7597
8222
  if (!isEditMode) return;
7598
8223
  const useFixtures = shouldUseDevFixtures();
7599
8224
  if (useFixtures) {
@@ -7617,14 +8242,14 @@ function OhhwellsBridge() {
7617
8242
  if (!useFixtures) postToParent2({ type: "ow:request-site-pages" });
7618
8243
  return () => window.removeEventListener("message", onSitePages);
7619
8244
  }, [isEditMode, postToParent2]);
7620
- useEffect7(() => {
8245
+ useEffect8(() => {
7621
8246
  if (!isEditMode || shouldUseDevFixtures()) return;
7622
8247
  void loadAllSectionsManifest().then((manifest) => {
7623
8248
  if (Object.keys(manifest).length === 0) return;
7624
8249
  setSectionsByPath((prev) => ({ ...manifest, ...prev }));
7625
8250
  });
7626
8251
  }, [isEditMode]);
7627
- useEffect7(() => {
8252
+ useEffect8(() => {
7628
8253
  const update = () => {
7629
8254
  const el = activeElRef.current ?? selectedElRef.current;
7630
8255
  if (el) setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
@@ -7648,10 +8273,10 @@ function OhhwellsBridge() {
7648
8273
  vvp.removeEventListener("resize", update);
7649
8274
  };
7650
8275
  }, []);
7651
- const refreshStateRules = useCallback3(() => {
8276
+ const refreshStateRules = useCallback4(() => {
7652
8277
  editStylesRef.current?.forceHover && (editStylesRef.current.forceHover.textContent = collectStateRules());
7653
8278
  }, []);
7654
- const processConfigRequest = useCallback3((insertAfterVal) => {
8279
+ const processConfigRequest = useCallback4((insertAfterVal) => {
7655
8280
  const tracker = getSectionsTracker();
7656
8281
  let entries = [];
7657
8282
  try {
@@ -7674,7 +8299,7 @@ function OhhwellsBridge() {
7674
8299
  }
7675
8300
  window.postMessage({ type: "ow:schedule-config", insertAfter: insertAfterVal, scheduleId: null }, "*");
7676
8301
  }, [isEditMode]);
7677
- const deactivate = useCallback3(() => {
8302
+ const deactivate = useCallback4(() => {
7678
8303
  const el = activeElRef.current;
7679
8304
  if (!el) return;
7680
8305
  const key = el.dataset.ohwKey;
@@ -7705,12 +8330,14 @@ function OhhwellsBridge() {
7705
8330
  setToolbarShowEditLink(false);
7706
8331
  postToParent2({ type: "ow:exit-edit" });
7707
8332
  }, [postToParent2]);
7708
- const deselect = useCallback3(() => {
8333
+ const deselect = useCallback4(() => {
7709
8334
  selectedElRef.current = null;
7710
8335
  setReorderHrefKey(null);
7711
8336
  setReorderDragDisabled(false);
8337
+ setIsFooterFrameSelection(false);
7712
8338
  siblingHintElRef.current = null;
7713
8339
  setSiblingHintRect(null);
8340
+ setSiblingHintRects([]);
7714
8341
  setIsItemDragging(false);
7715
8342
  hoveredNavContainerRef.current = null;
7716
8343
  setHoveredNavContainerRect(null);
@@ -7719,7 +8346,7 @@ function OhhwellsBridge() {
7719
8346
  setToolbarVariant("none");
7720
8347
  }
7721
8348
  }, []);
7722
- const reselectNavigationItem = useCallback3((navAnchor) => {
8349
+ const reselectNavigationItem = useCallback4((navAnchor) => {
7723
8350
  selectedElRef.current = navAnchor;
7724
8351
  const { key, disabled } = getNavigationItemReorderState(navAnchor);
7725
8352
  setReorderHrefKey(key);
@@ -7729,7 +8356,7 @@ function OhhwellsBridge() {
7729
8356
  setToolbarShowEditLink(false);
7730
8357
  setActiveCommands(/* @__PURE__ */ new Set());
7731
8358
  }, []);
7732
- const commitNavigationTextEdit = useCallback3((navAnchor) => {
8359
+ const commitNavigationTextEdit = useCallback4((navAnchor) => {
7733
8360
  const el = activeElRef.current;
7734
8361
  if (!el) return;
7735
8362
  const key = el.dataset.ohwKey;
@@ -7755,7 +8382,7 @@ function OhhwellsBridge() {
7755
8382
  postToParent2({ type: "ow:exit-edit" });
7756
8383
  reselectNavigationItem(navAnchor);
7757
8384
  }, [postToParent2, reselectNavigationItem]);
7758
- const handleAddTopLevelNavItem = useCallback3(() => {
8385
+ const handleAddTopLevelNavItem = useCallback4(() => {
7759
8386
  const items = listNavbarItems();
7760
8387
  addNavAfterAnchorRef.current = items[items.length - 1] ?? null;
7761
8388
  deselectRef.current();
@@ -7767,90 +8394,387 @@ function OhhwellsBridge() {
7767
8394
  intent: "add-nav"
7768
8395
  });
7769
8396
  }, []);
7770
- const handleItemDragStart = useCallback3(() => {
7771
- siblingHintElRef.current = null;
7772
- setSiblingHintRect(null);
7773
- setIsItemDragging(true);
7774
- }, []);
7775
- const handleItemDragEnd = useCallback3(() => {
8397
+ const clearFooterDragVisuals = useCallback4(() => {
8398
+ footerDragRef.current = null;
8399
+ setSiblingHintRects([]);
8400
+ setFooterDropSlots([]);
8401
+ setActiveFooterDropIndex(null);
8402
+ setDraggedItemRect(null);
7776
8403
  setIsItemDragging(false);
8404
+ unlockFooterDragInteraction();
7777
8405
  }, []);
7778
- reselectNavigationItemRef.current = reselectNavigationItem;
7779
- commitNavigationTextEditRef.current = commitNavigationTextEdit;
7780
- const select = useCallback3((anchor) => {
7781
- if (!isNavigationItem(anchor)) return;
7782
- if (activeElRef.current) deactivate();
7783
- selectedElRef.current = anchor;
7784
- clearHrefKeyHover(anchor);
7785
- hoveredNavContainerRef.current = null;
7786
- setHoveredNavContainerRect(null);
7787
- setHoveredItemRect(null);
7788
- hoveredItemElRef.current = null;
7789
- siblingHintElRef.current = null;
7790
- setSiblingHintRect(null);
7791
- setIsItemDragging(false);
7792
- const { key, disabled } = getNavigationItemReorderState(anchor);
7793
- setReorderHrefKey(key);
7794
- setReorderDragDisabled(disabled);
7795
- setToolbarVariant("link-action");
7796
- setToolbarRect(anchor.getBoundingClientRect());
7797
- setToolbarShowEditLink(false);
7798
- setActiveCommands(/* @__PURE__ */ new Set());
7799
- }, [deactivate]);
7800
- const selectFrame = useCallback3((el) => {
7801
- if (!isNavigationContainer(el)) return;
7802
- if (activeElRef.current) deactivate();
7803
- selectedElRef.current = el;
7804
- clearHrefKeyHover(el);
7805
- hoveredNavContainerRef.current = null;
7806
- setHoveredNavContainerRect(null);
7807
- setHoveredItemRect(null);
7808
- hoveredItemElRef.current = null;
7809
- siblingHintElRef.current = null;
7810
- setSiblingHintRect(null);
7811
- setIsItemDragging(false);
7812
- setReorderHrefKey(null);
7813
- setReorderDragDisabled(false);
7814
- setToolbarVariant("select-frame");
7815
- setToolbarRect(el.getBoundingClientRect());
7816
- setToolbarShowEditLink(false);
7817
- setActiveCommands(/* @__PURE__ */ new Set());
7818
- }, [deactivate]);
7819
- const activate = useCallback3((el, options) => {
7820
- if (activeElRef.current === el) return;
7821
- selectedElRef.current = null;
7822
- deactivate();
7823
- if (hoveredImageRef.current) {
7824
- hoveredImageRef.current = null;
7825
- setMediaHover(null);
8406
+ const refreshFooterDragVisuals = useCallback4((session, activeSlot, clientX, clientY) => {
8407
+ const dragged = session.draggedEl;
8408
+ setDraggedItemRect(dragged.getBoundingClientRect());
8409
+ if (typeof clientX === "number" && typeof clientY === "number") {
8410
+ session.lastClientX = clientX;
8411
+ session.lastClientY = clientY;
7826
8412
  }
7827
- setToolbarVariant("rich-text");
7828
- siblingHintElRef.current = null;
7829
- setSiblingHintRect(null);
7830
- setIsItemDragging(false);
7831
- el.setAttribute("contenteditable", "true");
7832
- el.removeAttribute("data-ohw-hovered");
7833
- el.closest("[data-ohw-href-key]")?.removeAttribute("data-ohw-hovered");
7834
- activeElRef.current = el;
7835
- originalContentRef.current = el.innerHTML;
7836
- el.focus();
7837
- if (options?.caretX !== void 0 && options?.caretY !== void 0) {
7838
- placeCaretAtPoint(el, options.caretX, options.caretY);
8413
+ session.activeSlot = activeSlot;
8414
+ if (session.kind === "link") {
8415
+ const columns2 = listFooterColumns();
8416
+ const focusCols = /* @__PURE__ */ new Set([session.sourceColumnIndex]);
8417
+ if (activeSlot) focusCols.add(activeSlot.columnIndex);
8418
+ const siblingRects = [];
8419
+ columns2.forEach((col, i) => {
8420
+ if (!focusCols.has(i)) return;
8421
+ for (const link of listFooterLinksInColumn(col)) {
8422
+ if (link === dragged) continue;
8423
+ siblingRects.push(link.getBoundingClientRect());
8424
+ }
8425
+ });
8426
+ setSiblingHintRects(siblingRects);
8427
+ const slots2 = [];
8428
+ columns2.forEach((col, i) => {
8429
+ slots2.push(...buildLinkDropSlots(col, i));
8430
+ });
8431
+ setFooterDropSlots(slots2);
8432
+ const activeIdx2 = activeSlot ? slots2.findIndex((s) => s.columnIndex === activeSlot.columnIndex && s.insertIndex === activeSlot.insertIndex) : -1;
8433
+ setActiveFooterDropIndex(activeIdx2 >= 0 ? activeIdx2 : null);
8434
+ return;
8435
+ }
8436
+ const columns = listFooterColumns();
8437
+ setSiblingHintRects(columns.filter((col) => col !== dragged).map((col) => col.getBoundingClientRect()));
8438
+ const slots = buildColumnDropSlots();
8439
+ setFooterDropSlots(slots);
8440
+ const activeIdx = activeSlot ? slots.findIndex((s) => s.insertIndex === activeSlot.insertIndex) : -1;
8441
+ setActiveFooterDropIndex(activeIdx >= 0 ? activeIdx : null);
8442
+ }, []);
8443
+ const refreshFooterDragVisualsRef = useRef5(refreshFooterDragVisuals);
8444
+ refreshFooterDragVisualsRef.current = refreshFooterDragVisuals;
8445
+ const commitFooterDragRef = useRef5(() => {
8446
+ });
8447
+ const beginFooterDragRef = useRef5(() => {
8448
+ });
8449
+ const beginFooterDrag = useCallback4(
8450
+ (session) => {
8451
+ const rect = session.draggedEl.getBoundingClientRect();
8452
+ session.lastClientX = session.lastClientX || rect.left + rect.width / 2;
8453
+ session.lastClientY = session.lastClientY || rect.top + rect.height / 2;
8454
+ session.activeSlot = session.activeSlot ?? null;
8455
+ footerDragRef.current = session;
8456
+ setIsItemDragging(true);
8457
+ lockFooterDuringDrag();
8458
+ siblingHintElRef.current = null;
8459
+ setSiblingHintRect(null);
8460
+ if (session.wasSelected && selectedElRef.current === session.draggedEl) {
8461
+ setToolbarRect(rect);
8462
+ }
8463
+ const initialSlot = session.kind === "link" && session.hrefKey ? hitTestLinkDropSlot(session.lastClientX, session.lastClientY, session.hrefKey) : hitTestColumnDropSlot(session.lastClientX, session.lastClientY);
8464
+ refreshFooterDragVisuals(session, initialSlot, session.lastClientX, session.lastClientY);
8465
+ },
8466
+ [refreshFooterDragVisuals]
8467
+ );
8468
+ beginFooterDragRef.current = beginFooterDrag;
8469
+ const commitFooterDrag = useCallback4(
8470
+ (clientX, clientY) => {
8471
+ const session = footerDragRef.current;
8472
+ if (!session) {
8473
+ clearFooterDragVisuals();
8474
+ return;
8475
+ }
8476
+ const x = typeof clientX === "number" && (clientX !== 0 || clientY !== 0) ? clientX : session.lastClientX;
8477
+ const y = typeof clientY === "number" && (clientX !== 0 || clientY !== 0) ? clientY : session.lastClientY;
8478
+ let nextOrder = null;
8479
+ const slot = session.activeSlot ?? (session.kind === "link" && session.hrefKey ? hitTestLinkDropSlot(x, y, session.hrefKey) : session.kind === "column" ? hitTestColumnDropSlot(x, y) : null);
8480
+ if (session.kind === "link" && session.hrefKey && slot) {
8481
+ nextOrder = planFooterLinkMove(session.hrefKey, slot.columnIndex, slot.insertIndex);
8482
+ } else if (session.kind === "column" && slot) {
8483
+ nextOrder = planFooterColumnMove(session.sourceColumnIndex, slot.insertIndex);
8484
+ }
8485
+ const wasSelected = session.wasSelected;
8486
+ const draggedEl = session.draggedEl;
8487
+ const hrefKey = session.hrefKey;
8488
+ let movedColumnIndex = null;
8489
+ if (session.kind === "column" && slot && nextOrder) {
8490
+ let adjusted = slot.insertIndex;
8491
+ if (session.sourceColumnIndex < slot.insertIndex) adjusted -= 1;
8492
+ movedColumnIndex = Math.max(0, Math.min(adjusted, nextOrder.length - 1));
8493
+ }
8494
+ clearFooterDragVisuals();
8495
+ const applySelectionAfterDrop = () => {
8496
+ if (!wasSelected) {
8497
+ deselectRef.current();
8498
+ return;
8499
+ }
8500
+ if (hrefKey) {
8501
+ const link = document.querySelector(
8502
+ `footer [data-ohw-href-key="${CSS.escape(hrefKey)}"]`
8503
+ );
8504
+ if (link && isNavigationItem(link)) {
8505
+ selectRef.current(link);
8506
+ return;
8507
+ }
8508
+ }
8509
+ if (movedColumnIndex !== null && nextOrder) {
8510
+ const colKeys = nextOrder[movedColumnIndex] ?? [];
8511
+ let column = null;
8512
+ for (const key of colKeys) {
8513
+ const link = document.querySelector(
8514
+ `footer [data-ohw-href-key="${CSS.escape(key)}"]`
8515
+ );
8516
+ if (link) {
8517
+ column = findFooterColumnForLink(link);
8518
+ if (column) break;
8519
+ }
8520
+ }
8521
+ if (!column) column = listFooterColumns()[movedColumnIndex] ?? null;
8522
+ if (column && isNavigationContainer(column)) {
8523
+ selectFrameRef.current(column);
8524
+ return;
8525
+ }
8526
+ }
8527
+ if (document.body.contains(draggedEl)) {
8528
+ if (isNavigationItem(draggedEl)) {
8529
+ selectRef.current(draggedEl);
8530
+ return;
8531
+ }
8532
+ if (isNavigationContainer(draggedEl)) {
8533
+ selectFrameRef.current(draggedEl);
8534
+ return;
8535
+ }
8536
+ }
8537
+ deselectRef.current();
8538
+ };
8539
+ if (!wasSelected) {
8540
+ deselectRef.current();
8541
+ }
8542
+ if (nextOrder) {
8543
+ const orderJson = JSON.stringify(nextOrder);
8544
+ editContentRef.current = {
8545
+ ...editContentRef.current,
8546
+ [FOOTER_ORDER_KEY]: orderJson
8547
+ };
8548
+ applyFooterOrder(nextOrder);
8549
+ document.querySelectorAll("footer [data-ohw-href-key]").forEach(disableNativeHrefDrag);
8550
+ postToParentRef.current({
8551
+ type: "ow:change",
8552
+ nodes: [{ key: FOOTER_ORDER_KEY, text: orderJson }]
8553
+ });
8554
+ requestAnimationFrame(() => {
8555
+ if (editContentRef.current[FOOTER_ORDER_KEY] === orderJson) {
8556
+ applyFooterOrder(nextOrder);
8557
+ document.querySelectorAll("footer [data-ohw-href-key]").forEach(disableNativeHrefDrag);
8558
+ }
8559
+ requestAnimationFrame(applySelectionAfterDrop);
8560
+ });
8561
+ return;
8562
+ }
8563
+ applySelectionAfterDrop();
8564
+ },
8565
+ [clearFooterDragVisuals]
8566
+ );
8567
+ commitFooterDragRef.current = commitFooterDrag;
8568
+ const startFooterLinkDrag = useCallback4(
8569
+ (anchor, clientX, clientY, wasSelected) => {
8570
+ const hrefKey = anchor.getAttribute("data-ohw-href-key");
8571
+ if (!hrefKey || !isFooterHrefKey(hrefKey)) return false;
8572
+ const column = findFooterColumnForLink(anchor);
8573
+ const columns = listFooterColumns();
8574
+ beginFooterDrag({
8575
+ kind: "link",
8576
+ hrefKey,
8577
+ columnEl: column,
8578
+ sourceColumnIndex: column ? columns.indexOf(column) : 0,
8579
+ wasSelected,
8580
+ draggedEl: anchor,
8581
+ lastClientX: clientX,
8582
+ lastClientY: clientY,
8583
+ activeSlot: null
8584
+ });
8585
+ return true;
8586
+ },
8587
+ [beginFooterDrag]
8588
+ );
8589
+ const startFooterColumnDrag = useCallback4(
8590
+ (columnEl, clientX, clientY, wasSelected) => {
8591
+ const columns = listFooterColumns();
8592
+ const idx = columns.indexOf(columnEl);
8593
+ if (idx < 0) return false;
8594
+ beginFooterDrag({
8595
+ kind: "column",
8596
+ hrefKey: null,
8597
+ columnEl,
8598
+ sourceColumnIndex: idx,
8599
+ wasSelected,
8600
+ draggedEl: columnEl,
8601
+ lastClientX: clientX,
8602
+ lastClientY: clientY,
8603
+ activeSlot: null
8604
+ });
8605
+ return true;
8606
+ },
8607
+ [beginFooterDrag]
8608
+ );
8609
+ const handleItemDragStart = useCallback4(
8610
+ (e) => {
8611
+ const selected = selectedElRef.current;
8612
+ if (!selected) {
8613
+ setIsItemDragging(true);
8614
+ return;
8615
+ }
8616
+ const clientX = e?.clientX ?? selected.getBoundingClientRect().left + 8;
8617
+ const clientY = e?.clientY ?? selected.getBoundingClientRect().top + 8;
8618
+ if (startFooterLinkDrag(selected, clientX, clientY, true)) return;
8619
+ if (selected.hasAttribute("data-ohw-footer-col") || isInferredFooterGroup(selected) && selected.closest("footer")) {
8620
+ if (startFooterColumnDrag(selected, clientX, clientY, true)) return;
8621
+ }
8622
+ siblingHintElRef.current = null;
8623
+ setSiblingHintRect(null);
8624
+ setIsItemDragging(true);
8625
+ },
8626
+ [startFooterColumnDrag, startFooterLinkDrag]
8627
+ );
8628
+ const handleItemDragEnd = useCallback4(
8629
+ (e) => {
8630
+ if (footerDragRef.current) {
8631
+ const x = e?.clientX;
8632
+ const y = e?.clientY;
8633
+ if (typeof x === "number" && typeof y === "number" && (x !== 0 || y !== 0)) {
8634
+ commitFooterDrag(x, y);
8635
+ } else {
8636
+ commitFooterDrag();
8637
+ }
8638
+ return;
8639
+ }
8640
+ setIsItemDragging(false);
8641
+ },
8642
+ [commitFooterDrag]
8643
+ );
8644
+ const handleItemChromePointerDown = useCallback4((e) => {
8645
+ if (e.button !== 0) return;
8646
+ const selected = selectedElRef.current;
8647
+ if (!selected) return;
8648
+ armFooterPressDrag();
8649
+ const hrefKey = selected.getAttribute("data-ohw-href-key");
8650
+ if (hrefKey && isFooterHrefKey(hrefKey)) {
8651
+ footerPointerDragRef.current = {
8652
+ el: selected,
8653
+ kind: "link",
8654
+ startX: e.clientX,
8655
+ startY: e.clientY,
8656
+ pointerId: e.pointerId,
8657
+ wasSelected: true,
8658
+ started: false
8659
+ };
8660
+ return;
8661
+ }
8662
+ if (selected.hasAttribute("data-ohw-footer-col") || isInferredFooterGroup(selected) && selected.closest("footer")) {
8663
+ footerPointerDragRef.current = {
8664
+ el: selected,
8665
+ kind: "column",
8666
+ startX: e.clientX,
8667
+ startY: e.clientY,
8668
+ pointerId: e.pointerId,
8669
+ wasSelected: true,
8670
+ started: false
8671
+ };
7839
8672
  }
7840
- setToolbarShowEditLink(Boolean(getHrefKeyFromElement(el)));
7841
- const navAnchor = getNavigationItemAnchor(el);
7842
- if (navAnchor) {
8673
+ }, []);
8674
+ const handleItemChromeClick = useCallback4((clientX, clientY) => {
8675
+ if (suppressNextClickRef.current) {
8676
+ suppressNextClickRef.current = false;
8677
+ return;
8678
+ }
8679
+ const selected = selectedElRef.current;
8680
+ if (!selected || !isNavigationItem(selected)) return;
8681
+ const editable = selected.querySelector('[data-ohw-editable="text"]') ?? selected.querySelector("[data-ohw-editable]");
8682
+ if (!editable) return;
8683
+ activateRef.current(editable, { caretX: clientX, caretY: clientY });
8684
+ }, []);
8685
+ reselectNavigationItemRef.current = reselectNavigationItem;
8686
+ commitNavigationTextEditRef.current = commitNavigationTextEdit;
8687
+ const select = useCallback4(
8688
+ (anchor) => {
8689
+ if (!isNavigationItem(anchor)) return;
8690
+ if (activeElRef.current) deactivate();
8691
+ selectedElRef.current = anchor;
8692
+ clearHrefKeyHover(anchor);
8693
+ hoveredNavContainerRef.current = null;
8694
+ setHoveredNavContainerRect(null);
8695
+ setHoveredItemRect(null);
8696
+ hoveredItemElRef.current = null;
8697
+ siblingHintElRef.current = null;
8698
+ setSiblingHintRect(null);
8699
+ setSiblingHintRects([]);
8700
+ setIsItemDragging(false);
8701
+ const { key, disabled } = getNavigationItemReorderState(anchor);
8702
+ setReorderHrefKey(key);
8703
+ setReorderDragDisabled(disabled);
8704
+ setIsFooterFrameSelection(false);
8705
+ setToolbarVariant("link-action");
8706
+ setToolbarRect(anchor.getBoundingClientRect());
8707
+ setToolbarShowEditLink(false);
8708
+ setActiveCommands(/* @__PURE__ */ new Set());
8709
+ },
8710
+ [deactivate]
8711
+ );
8712
+ const selectFrame = useCallback4(
8713
+ (el) => {
8714
+ if (!isNavigationContainer(el)) return;
8715
+ if (activeElRef.current) deactivate();
8716
+ const isFooterColumn = el.hasAttribute("data-ohw-footer-col") || Boolean(el.closest("footer") && isInferredFooterGroup(el));
8717
+ selectedElRef.current = el;
8718
+ clearHrefKeyHover(el);
8719
+ hoveredNavContainerRef.current = null;
8720
+ setHoveredNavContainerRect(null);
8721
+ setHoveredItemRect(null);
8722
+ hoveredItemElRef.current = null;
8723
+ siblingHintElRef.current = null;
8724
+ setSiblingHintRect(null);
8725
+ setSiblingHintRects(
8726
+ isFooterColumn ? listFooterColumns().filter((column) => column !== el).map((column) => column.getBoundingClientRect()) : []
8727
+ );
8728
+ setIsItemDragging(false);
7843
8729
  setReorderHrefKey(null);
7844
8730
  setReorderDragDisabled(false);
7845
- } else {
7846
- const { key: reorderKey, disabled: reorderDisabled } = getReorderHandleState(el);
7847
- setReorderHrefKey(reorderKey);
7848
- setReorderDragDisabled(reorderDisabled);
7849
- }
7850
- setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
7851
- postToParent2({ type: "ow:enter-edit", key: el.dataset.ohwKey });
7852
- requestAnimationFrame(() => refreshActiveCommandsRef.current());
7853
- }, [deactivate, postToParent2]);
8731
+ setIsFooterFrameSelection(isFooterColumn);
8732
+ setToolbarVariant("select-frame");
8733
+ setToolbarRect(el.getBoundingClientRect());
8734
+ setToolbarShowEditLink(false);
8735
+ setActiveCommands(/* @__PURE__ */ new Set());
8736
+ },
8737
+ [deactivate]
8738
+ );
8739
+ const activate = useCallback4(
8740
+ (el, options) => {
8741
+ if (activeElRef.current === el) return;
8742
+ selectedElRef.current = null;
8743
+ deactivate();
8744
+ if (hoveredImageRef.current) {
8745
+ hoveredImageRef.current = null;
8746
+ postToParentRef.current({ type: "ow:image-unhover" });
8747
+ }
8748
+ setToolbarVariant("rich-text");
8749
+ siblingHintElRef.current = null;
8750
+ setSiblingHintRect(null);
8751
+ setSiblingHintRects([]);
8752
+ setIsItemDragging(false);
8753
+ el.setAttribute("contenteditable", "true");
8754
+ el.removeAttribute("data-ohw-hovered");
8755
+ el.closest("[data-ohw-href-key]")?.removeAttribute("data-ohw-hovered");
8756
+ activeElRef.current = el;
8757
+ originalContentRef.current = el.innerHTML;
8758
+ el.focus();
8759
+ if (options?.caretX !== void 0 && options?.caretY !== void 0) {
8760
+ placeCaretAtPoint(el, options.caretX, options.caretY);
8761
+ }
8762
+ setToolbarShowEditLink(Boolean(getHrefKeyFromElement(el)));
8763
+ const navAnchor = getNavigationItemAnchor(el);
8764
+ if (navAnchor) {
8765
+ setReorderHrefKey(null);
8766
+ setReorderDragDisabled(false);
8767
+ } else {
8768
+ const { key: reorderKey, disabled: reorderDisabled } = getReorderHandleState(el);
8769
+ setReorderHrefKey(reorderKey);
8770
+ setReorderDragDisabled(reorderDisabled);
8771
+ }
8772
+ setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
8773
+ postToParent2({ type: "ow:enter-edit", key: el.dataset.ohwKey });
8774
+ requestAnimationFrame(() => refreshActiveCommandsRef.current());
8775
+ },
8776
+ [deactivate, postToParent2]
8777
+ );
7854
8778
  activateRef.current = activate;
7855
8779
  deactivateRef.current = deactivate;
7856
8780
  selectRef.current = select;
@@ -7866,6 +8790,7 @@ function OhhwellsBridge() {
7866
8790
  for (const [key, val] of Object.entries(content)) {
7867
8791
  if (key === "__ohw_sections") continue;
7868
8792
  if (applyVideoSettingNode(key, val)) continue;
8793
+ if (applyCarouselNode(key, val)) continue;
7869
8794
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
7870
8795
  if (el.dataset.ohwEditable === "image") {
7871
8796
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -7897,6 +8822,7 @@ function OhhwellsBridge() {
7897
8822
  applyLinkByKey(key, val);
7898
8823
  }
7899
8824
  reconcileNavbarItemsFromContent(content);
8825
+ reconcileFooterOrderFromContent(content);
7900
8826
  enforceLinkHrefs();
7901
8827
  initSectionsFromContent(content, true);
7902
8828
  sectionsLoadedRef.current = true;
@@ -7925,7 +8851,7 @@ function OhhwellsBridge() {
7925
8851
  cancelled = true;
7926
8852
  };
7927
8853
  }, [subdomain, isEditMode]);
7928
- useEffect7(() => {
8854
+ useEffect8(() => {
7929
8855
  if (!subdomain || isEditMode) return;
7930
8856
  let debounceTimer = null;
7931
8857
  let observer = null;
@@ -7938,6 +8864,7 @@ function OhhwellsBridge() {
7938
8864
  for (const [key, val] of Object.entries(content)) {
7939
8865
  if (key === "__ohw_sections") continue;
7940
8866
  if (applyVideoSettingNode(key, val)) continue;
8867
+ if (applyCarouselNode(key, val)) continue;
7941
8868
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
7942
8869
  if (el.dataset.ohwEditable === "image") {
7943
8870
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -7957,6 +8884,7 @@ function OhhwellsBridge() {
7957
8884
  applyLinkByKey(key, val);
7958
8885
  }
7959
8886
  reconcileNavbarItemsFromContent(content);
8887
+ reconcileFooterOrderFromContent(content);
7960
8888
  } finally {
7961
8889
  observer?.observe(document.body, { childList: true, subtree: true });
7962
8890
  }
@@ -7980,10 +8908,10 @@ function OhhwellsBridge() {
7980
8908
  const visible = Boolean(subdomain) && fetchState !== "done";
7981
8909
  el.style.display = visible ? "flex" : "none";
7982
8910
  }, [subdomain, fetchState]);
7983
- useEffect7(() => {
8911
+ useEffect8(() => {
7984
8912
  postToParent2({ type: "ow:navigation", path: pathname });
7985
8913
  }, [pathname, postToParent2]);
7986
- useEffect7(() => {
8914
+ useEffect8(() => {
7987
8915
  if (!isEditMode) return;
7988
8916
  if (linkPopoverSessionRef.current?.intent === "add-nav") return;
7989
8917
  if (document.querySelector("[data-ohw-section-picker]")) return;
@@ -7991,23 +8919,50 @@ function OhhwellsBridge() {
7991
8919
  deselectRef.current();
7992
8920
  deactivateRef.current();
7993
8921
  }, [pathname, isEditMode]);
7994
- useEffect7(() => {
8922
+ useEffect8(() => {
7995
8923
  const contentForNav = () => {
7996
8924
  if (isEditMode) return editContentRef.current;
7997
8925
  if (!subdomain) return {};
7998
8926
  return contentCache.get(subdomain) ?? {};
7999
8927
  };
8000
- const run = () => reconcileNavbarItemsFromContent(contentForNav());
8928
+ let applying = false;
8929
+ let rafId = null;
8930
+ const run = () => {
8931
+ if (footerDragRef.current || applying) return;
8932
+ applying = true;
8933
+ try {
8934
+ const content = contentForNav();
8935
+ reconcileNavbarItemsFromContent(content);
8936
+ reconcileFooterOrderFromContent(content);
8937
+ document.querySelectorAll("footer [data-ohw-href-key]").forEach((el) => {
8938
+ if (isFooterHrefKey(el.getAttribute("data-ohw-href-key"))) {
8939
+ disableNativeHrefDrag(el);
8940
+ }
8941
+ });
8942
+ } finally {
8943
+ applying = false;
8944
+ }
8945
+ };
8946
+ const scheduleRun = () => {
8947
+ if (rafId !== null) cancelAnimationFrame(rafId);
8948
+ rafId = requestAnimationFrame(() => {
8949
+ rafId = null;
8950
+ run();
8951
+ });
8952
+ };
8001
8953
  run();
8002
8954
  const nav = document.querySelector("nav");
8003
- if (!nav) return;
8004
- const observer = new MutationObserver(() => {
8005
- requestAnimationFrame(run);
8006
- });
8007
- observer.observe(nav, { childList: true, subtree: true });
8008
- return () => observer.disconnect();
8955
+ const footer = document.querySelector("footer");
8956
+ const observer = new MutationObserver(scheduleRun);
8957
+ if (nav) observer.observe(nav, { childList: true, subtree: true });
8958
+ if (footer) observer.observe(footer, { childList: true, subtree: true });
8959
+ if (!nav && !footer) return;
8960
+ return () => {
8961
+ observer.disconnect();
8962
+ if (rafId !== null) cancelAnimationFrame(rafId);
8963
+ };
8009
8964
  }, [isEditMode, pathname, subdomain, fetchState]);
8010
- useEffect7(() => {
8965
+ useEffect8(() => {
8011
8966
  if (!isEditMode) return;
8012
8967
  const measure = () => {
8013
8968
  const h = document.body.scrollHeight;
@@ -8031,7 +8986,7 @@ function OhhwellsBridge() {
8031
8986
  window.removeEventListener("resize", handleResize);
8032
8987
  };
8033
8988
  }, [pathname, isEditMode, postToParent2]);
8034
- useEffect7(() => {
8989
+ useEffect8(() => {
8035
8990
  if (!subdomainFromQuery || isEditMode) return;
8036
8991
  const handleClick = (e) => {
8037
8992
  const anchor = e.target.closest("a");
@@ -8047,7 +9002,7 @@ function OhhwellsBridge() {
8047
9002
  document.addEventListener("click", handleClick, true);
8048
9003
  return () => document.removeEventListener("click", handleClick, true);
8049
9004
  }, [subdomainFromQuery, isEditMode, router]);
8050
- useEffect7(() => {
9005
+ useEffect8(() => {
8051
9006
  if (!isEditMode) {
8052
9007
  editStylesRef.current?.base.remove();
8053
9008
  editStylesRef.current?.forceHover.remove();
@@ -8070,6 +9025,32 @@ function OhhwellsBridge() {
8070
9025
  [data-ohw-editable] {
8071
9026
  display: block;
8072
9027
  }
9028
+ /* Native <a> drag steals clicks \u2014 disable for edit links; reorder via press-to-drag / handle. */
9029
+ footer [data-ohw-href-key] {
9030
+ -webkit-user-drag: none !important;
9031
+ }
9032
+ footer [data-ohw-footer-col]:hover {
9033
+ outline: 1.5px dashed ${PRIMARY2} !important;
9034
+ outline-offset: 6px;
9035
+ border-radius: 8px;
9036
+ }
9037
+ html[data-ohw-link-popover-open] footer [data-ohw-footer-col]:hover {
9038
+ outline: none !important;
9039
+ }
9040
+ html[data-ohw-footer-press-drag] footer,
9041
+ html[data-ohw-footer-press-drag] footer * {
9042
+ user-select: none !important;
9043
+ -webkit-user-select: none !important;
9044
+ }
9045
+ html[data-ohw-item-dragging] footer,
9046
+ html[data-ohw-item-dragging] footer * {
9047
+ user-select: none !important;
9048
+ -webkit-user-select: none !important;
9049
+ pointer-events: none !important;
9050
+ }
9051
+ html[data-ohw-item-dragging] {
9052
+ cursor: grabbing !important;
9053
+ }
8073
9054
  [data-ohw-editable]:not([contenteditable]):not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"]):not([data-ohw-editable="video"]) { cursor: text !important; }
8074
9055
  [data-ohw-editable="image"], [data-ohw-editable="image"] *,
8075
9056
  [data-ohw-editable="video"], [data-ohw-editable="video"] *,
@@ -8114,12 +9095,39 @@ function OhhwellsBridge() {
8114
9095
  }
8115
9096
  refreshStateRules();
8116
9097
  const handleClick = (e) => {
9098
+ if (suppressNextClickRef.current) {
9099
+ suppressNextClickRef.current = false;
9100
+ e.preventDefault();
9101
+ e.stopPropagation();
9102
+ return;
9103
+ }
8117
9104
  const target = e.target;
8118
9105
  if (target.closest("[data-ohw-toolbar]")) return;
8119
9106
  if (target.closest("[data-ohw-state-toggle]")) return;
8120
9107
  if (target.closest("[data-ohw-max-badge]")) return;
8121
9108
  if (isInsideLinkEditor(target)) return;
8122
9109
  if (target.closest('[data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
9110
+ if (target.closest("[data-ohw-item-drag-surface]")) {
9111
+ e.preventDefault();
9112
+ e.stopPropagation();
9113
+ const selected = selectedElRef.current;
9114
+ if (selected && isNavigationItem(selected)) {
9115
+ const editable2 = selected.querySelector('[data-ohw-editable="text"]') ?? selected.querySelector("[data-ohw-editable]");
9116
+ if (editable2) {
9117
+ activateRef.current(editable2, {
9118
+ caretX: e.clientX,
9119
+ caretY: e.clientY
9120
+ });
9121
+ }
9122
+ } else if (selected?.hasAttribute("data-ohw-footer-col")) {
9123
+ const link = listFooterLinksInColumn(selected).find((el) => {
9124
+ const r2 = el.getBoundingClientRect();
9125
+ return e.clientX >= r2.left && e.clientX <= r2.right && e.clientY >= r2.top && e.clientY <= r2.bottom;
9126
+ });
9127
+ if (link) selectRef.current(link);
9128
+ }
9129
+ return;
9130
+ }
8123
9131
  const navFromChrome = resolveNavContainerSelectionTarget(target, e.clientX, e.clientY);
8124
9132
  if (navFromChrome) {
8125
9133
  e.preventDefault();
@@ -8149,7 +9157,15 @@ function OhhwellsBridge() {
8149
9157
  if (isMediaEditable(editable)) {
8150
9158
  e.preventDefault();
8151
9159
  e.stopPropagation();
8152
- postToParentRef.current({ type: "ow:image-pick", key: editable.dataset.ohwKey ?? "", elementType: editable.dataset.ohwEditable ?? "image" });
9160
+ const key = editable.dataset.ohwKey ?? "";
9161
+ const r2 = editable.getBoundingClientRect();
9162
+ if (key && r2.width > 1 && r2.height > 1) {
9163
+ pendingUploadRectRef.current = {
9164
+ key,
9165
+ rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height }
9166
+ };
9167
+ }
9168
+ postToParentRef.current({ type: "ow:image-pick", key, elementType: editable.dataset.ohwEditable ?? "image" });
8153
9169
  return;
8154
9170
  }
8155
9171
  const hrefCtx = getHrefKeyFromElement(editable);
@@ -8158,6 +9174,7 @@ function OhhwellsBridge() {
8158
9174
  e.preventDefault();
8159
9175
  e.stopPropagation();
8160
9176
  if (selectedElRef.current === navAnchor) {
9177
+ if (e.detail >= 2) return;
8161
9178
  activateRef.current(editable, { caretX: e.clientX, caretY: e.clientY });
8162
9179
  return;
8163
9180
  }
@@ -8173,10 +9190,26 @@ function OhhwellsBridge() {
8173
9190
  if (hrefAnchor) {
8174
9191
  e.preventDefault();
8175
9192
  e.stopPropagation();
8176
- if (selectedElRef.current === hrefAnchor) return;
9193
+ if (selectedElRef.current === hrefAnchor) {
9194
+ const textEditable = hrefAnchor.querySelector('[data-ohw-editable="text"]') ?? hrefAnchor.querySelector("[data-ohw-editable]");
9195
+ if (textEditable) {
9196
+ activateRef.current(textEditable, {
9197
+ caretX: e.clientX,
9198
+ caretY: e.clientY
9199
+ });
9200
+ }
9201
+ return;
9202
+ }
8177
9203
  selectRef.current(hrefAnchor);
8178
9204
  return;
8179
9205
  }
9206
+ const footerColClick = target.closest("[data-ohw-footer-col]");
9207
+ if (footerColClick) {
9208
+ e.preventDefault();
9209
+ e.stopPropagation();
9210
+ selectFrameRef.current(footerColClick);
9211
+ return;
9212
+ }
8180
9213
  const navContainerToSelect = resolveNavContainerSelectionTarget(target, e.clientX, e.clientY);
8181
9214
  if (navContainerToSelect) {
8182
9215
  e.preventDefault();
@@ -8218,9 +9251,38 @@ function OhhwellsBridge() {
8218
9251
  deselectRef.current();
8219
9252
  deactivateRef.current();
8220
9253
  };
9254
+ const handleDblClick = (e) => {
9255
+ const target = e.target;
9256
+ if (target.closest("[data-ohw-toolbar]")) return;
9257
+ if (target.closest("[data-ohw-state-toggle]")) return;
9258
+ if (target.closest("[data-ohw-max-badge]")) return;
9259
+ if (isInsideLinkEditor(target)) return;
9260
+ if (target.closest('[data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
9261
+ return;
9262
+ }
9263
+ const navLabel = getNavigationLabelEditable(target);
9264
+ if (!navLabel) return;
9265
+ const { editable } = navLabel;
9266
+ e.preventDefault();
9267
+ e.stopPropagation();
9268
+ if (activeElRef.current !== editable) {
9269
+ activateRef.current(editable);
9270
+ }
9271
+ requestAnimationFrame(() => {
9272
+ selectAllTextInEditable(editable);
9273
+ refreshActiveCommandsRef.current();
9274
+ });
9275
+ };
8221
9276
  const handleMouseOver = (e) => {
8222
9277
  if (document.documentElement.hasAttribute("data-ohw-section-picking")) return;
8223
9278
  const target = e.target;
9279
+ if (linkPopoverOpenRef.current) {
9280
+ hoveredItemElRef.current = null;
9281
+ setHoveredItemRect(null);
9282
+ hoveredNavContainerRef.current = null;
9283
+ setHoveredNavContainerRect(null);
9284
+ return;
9285
+ }
8224
9286
  if (toolbarVariantRef.current !== "select-frame") {
8225
9287
  const navContainer = target.closest("[data-ohw-nav-container]");
8226
9288
  if (navContainer && !getNavigationItemAnchor(target)) {
@@ -8235,6 +9297,15 @@ function OhhwellsBridge() {
8235
9297
  setHoveredNavContainerRect(null);
8236
9298
  }
8237
9299
  }
9300
+ const footerCol = target.closest("[data-ohw-footer-col]");
9301
+ if (footerCol) {
9302
+ hoveredNavContainerRef.current = null;
9303
+ setHoveredNavContainerRect(null);
9304
+ if (selectedElRef.current === footerCol) return;
9305
+ hoveredItemElRef.current = footerCol;
9306
+ setHoveredItemRect(footerCol.getBoundingClientRect());
9307
+ return;
9308
+ }
8238
9309
  const navAnchor = getNavigationItemAnchor(target);
8239
9310
  if (navAnchor) {
8240
9311
  hoveredNavContainerRef.current = null;
@@ -8271,6 +9342,15 @@ function OhhwellsBridge() {
8271
9342
  hoveredNavContainerRef.current = null;
8272
9343
  setHoveredNavContainerRect(null);
8273
9344
  }
9345
+ const footerCol = target.closest("[data-ohw-footer-col]");
9346
+ if (footerCol && hoveredItemElRef.current === footerCol) {
9347
+ const related2 = e.relatedTarget instanceof Element ? e.relatedTarget : null;
9348
+ if (related2?.closest("[data-ohw-drag-handle-container], [data-ohw-item-interaction]")) return;
9349
+ if (related2?.closest("[data-ohw-footer-col]") === footerCol) return;
9350
+ hoveredItemElRef.current = null;
9351
+ setHoveredItemRect(null);
9352
+ return;
9353
+ }
8274
9354
  const navAnchor = getNavigationItemAnchor(target);
8275
9355
  if (navAnchor) {
8276
9356
  const related2 = e.relatedTarget instanceof Element ? e.relatedTarget : null;
@@ -8382,12 +9462,12 @@ function OhhwellsBridge() {
8382
9462
  const stickyPad = 48;
8383
9463
  const withinPad = x >= pr.left - stickyPad && x <= pr.left + pr.width + stickyPad && y >= pr.top - stickyPad && y <= pr.top + pr.height + stickyPad;
8384
9464
  if (withinPad) {
8385
- if (smallest !== pinned && candidatePool.includes(smallest)) {
9465
+ const yieldToSmaller = smallest !== pinned && candidatePool.includes(smallest) && (() => {
8386
9466
  const sr = getVisibleRect(smallest);
8387
- const overOther = x >= sr.left && x <= sr.left + sr.width && y >= sr.top && y <= sr.top + sr.height;
8388
- if (overOther) return smallest;
8389
- }
8390
- return pinned;
9467
+ const pr2 = getVisibleRect(pinned);
9468
+ return sr.width * sr.height < pr2.width * pr2.height;
9469
+ })();
9470
+ if (!yieldToSmaller) return pinned;
8391
9471
  }
8392
9472
  }
8393
9473
  return smallest;
@@ -8399,6 +9479,7 @@ function OhhwellsBridge() {
8399
9479
  resumeAnimTracks();
8400
9480
  postToParentRef.current({ type: "ow:image-unhover" });
8401
9481
  }
9482
+ clearImageHover();
8402
9483
  };
8403
9484
  const probeNavigationHoverAt = (x, y) => {
8404
9485
  if (toolbarVariantRef.current === "select-frame") {
@@ -8501,7 +9582,7 @@ function OhhwellsBridge() {
8501
9582
  return;
8502
9583
  }
8503
9584
  const topEl = document.elementFromPoint(x2, y2);
8504
- if (topEl?.closest('[data-ohw-toolbar], [data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
9585
+ if (topEl?.closest('[data-ohw-toolbar], [data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"], [data-ohw-media-chrome]')) {
8505
9586
  if (hoveredImageRef.current) {
8506
9587
  hoveredImageRef.current = null;
8507
9588
  resumeAnimTracks();
@@ -8708,6 +9789,14 @@ function OhhwellsBridge() {
8708
9789
  probeHoverCardsAt(clientX, clientY);
8709
9790
  };
8710
9791
  const handleDragOver = (e) => {
9792
+ const footerSession = footerDragRef.current;
9793
+ if (footerSession) {
9794
+ e.preventDefault();
9795
+ if (e.dataTransfer) e.dataTransfer.dropEffect = "move";
9796
+ const slot = footerSession.kind === "link" && footerSession.hrefKey ? hitTestLinkDropSlot(e.clientX, e.clientY, footerSession.hrefKey) : hitTestColumnDropSlot(e.clientX, e.clientY);
9797
+ refreshFooterDragVisualsRef.current(footerSession, slot, e.clientX, e.clientY);
9798
+ return;
9799
+ }
8711
9800
  e.preventDefault();
8712
9801
  const el = findImageAtPoint(e.clientX, e.clientY, false);
8713
9802
  if (!el) {
@@ -8776,6 +9865,7 @@ function OhhwellsBridge() {
8776
9865
  if (!entry || entry.fadingOut) return prev;
8777
9866
  return { ...prev, [key]: { ...entry, fadingOut: true } };
8778
9867
  });
9868
+ if (pendingUploadRectRef.current?.key === key) pendingUploadRectRef.current = null;
8779
9869
  };
8780
9870
  let found = false;
8781
9871
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -8890,6 +9980,7 @@ function OhhwellsBridge() {
8890
9980
  continue;
8891
9981
  }
8892
9982
  if (applyVideoSettingNode(key, val)) continue;
9983
+ if (applyCarouselNode(key, val)) continue;
8893
9984
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
8894
9985
  if (el.dataset.ohwEditable === "image") {
8895
9986
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -8914,6 +10005,7 @@ function OhhwellsBridge() {
8914
10005
  }
8915
10006
  editContentRef.current = { ...editContentRef.current, ...content };
8916
10007
  reconcileNavbarItemsFromContent(editContentRef.current);
10008
+ reconcileFooterOrderFromContent(editContentRef.current);
8917
10009
  enforceLinkHrefs();
8918
10010
  postToParentRef.current({ type: "ow:hydrate-done" });
8919
10011
  };
@@ -8939,6 +10031,15 @@ function OhhwellsBridge() {
8939
10031
  window.addEventListener("message", handleUiEscape);
8940
10032
  const handleKeyDown = (e) => {
8941
10033
  if (e.key === "Escape" && document.querySelector("[data-ohw-section-picker]")) return;
10034
+ if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "a" && activeElRef.current) {
10035
+ const navAnchor = getNavigationItemAnchor(activeElRef.current);
10036
+ if (navAnchor) {
10037
+ e.preventDefault();
10038
+ selectAllTextInEditable(activeElRef.current);
10039
+ refreshActiveCommandsRef.current();
10040
+ return;
10041
+ }
10042
+ }
8942
10043
  if (e.key === "Escape" && linkPopoverOpenRef.current) {
8943
10044
  setLinkPopoverRef.current(null);
8944
10045
  return;
@@ -9018,6 +10119,17 @@ function OhhwellsBridge() {
9018
10119
  if (siblingHintElRef.current) {
9019
10120
  setSiblingHintRect(siblingHintElRef.current.getBoundingClientRect());
9020
10121
  }
10122
+ const selected = selectedElRef.current;
10123
+ if (selected && !footerDragRef.current && (selected.hasAttribute("data-ohw-footer-col") || Boolean(selected.closest("footer") && isInferredFooterGroup(selected)))) {
10124
+ setSiblingHintRects(
10125
+ listFooterColumns().filter((column) => column !== selected).map((column) => column.getBoundingClientRect())
10126
+ );
10127
+ }
10128
+ if (footerDragRef.current) {
10129
+ const session = footerDragRef.current;
10130
+ const slot = session.kind === "link" && session.hrefKey ? hitTestLinkDropSlot(session.lastClientX, session.lastClientY, session.hrefKey) : hitTestColumnDropSlot(session.lastClientX, session.lastClientY);
10131
+ refreshFooterDragVisualsRef.current(session, slot, session.lastClientX, session.lastClientY);
10132
+ }
9021
10133
  if (hoveredImageRef.current) {
9022
10134
  const el = hoveredImageRef.current;
9023
10135
  const r2 = el.getBoundingClientRect();
@@ -9028,7 +10140,7 @@ function OhhwellsBridge() {
9028
10140
  };
9029
10141
  const handleSave = (e) => {
9030
10142
  if (e.data?.type !== "ow:save") return;
9031
- const nodes = collectEditableNodes();
10143
+ const nodes = collectEditableNodes(editContentRef.current);
9032
10144
  const tracker = document.querySelector("[data-ohw-sections-tracker]");
9033
10145
  if (tracker?.textContent) nodes.push({ key: "__ohw_sections", type: "sections", text: tracker.textContent });
9034
10146
  postToParentRef.current({ type: "ow:save-result", nodes });
@@ -9113,28 +10225,49 @@ function OhhwellsBridge() {
9113
10225
  const handleDocMouseLeave = () => {
9114
10226
  hoveredImageRef.current = null;
9115
10227
  setMediaHover(null);
10228
+ setCarouselHover(null);
9116
10229
  document.querySelectorAll("[data-ohw-state-hovered]").forEach((el) => el.removeAttribute("data-ohw-state-hovered"));
9117
10230
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
9118
10231
  activeStateElRef.current = null;
9119
10232
  setToggleState(null);
9120
10233
  };
10234
+ const resolveUploadRects = (key) => {
10235
+ const matches = Array.from(document.querySelectorAll(`[data-ohw-key="${key}"]`));
10236
+ const rects = matches.map((el) => {
10237
+ const r2 = el.getBoundingClientRect();
10238
+ return { top: r2.top, left: r2.left, width: r2.width, height: r2.height };
10239
+ }).filter((r2) => r2.width > 1 && r2.height > 1);
10240
+ if (rects.length > 0) return rects;
10241
+ const pending = pendingUploadRectRef.current;
10242
+ if (pending?.key === key && pending.rect.width > 1 && pending.rect.height > 1) {
10243
+ return [pending.rect];
10244
+ }
10245
+ const hovered = hoveredImageRef.current;
10246
+ if (hovered?.dataset.ohwKey === key) {
10247
+ const r2 = hovered.getBoundingClientRect();
10248
+ if (r2.width > 1 && r2.height > 1) {
10249
+ return [{ top: r2.top, left: r2.left, width: r2.width, height: r2.height }];
10250
+ }
10251
+ }
10252
+ return [];
10253
+ };
9121
10254
  const handleImageUploading = (e) => {
9122
- if (e.data?.type !== "ow:image-uploading") return;
9123
- const { key, uploading } = e.data;
10255
+ const type = e.data?.type;
10256
+ if (type !== "ow:image-uploading" && type !== "ow:anim-lock") return;
10257
+ const key = e.data.key;
10258
+ if (!key) return;
10259
+ const uploading = type === "ow:anim-lock" ? true : !!e.data.uploading;
9124
10260
  setUploadingRects((prev) => {
9125
10261
  if (!uploading) {
10262
+ if (pendingUploadRectRef.current?.key === key) pendingUploadRectRef.current = null;
9126
10263
  if (!(key in prev)) return prev;
9127
10264
  const next = { ...prev };
9128
10265
  delete next[key];
9129
10266
  return next;
9130
10267
  }
9131
- const el = document.querySelector(`[data-ohw-key="${key}"]`);
9132
- if (!el) return prev;
9133
- const r2 = getVisibleRect(el);
9134
- return {
9135
- ...prev,
9136
- [key]: { rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height } }
9137
- };
10268
+ const rects = resolveUploadRects(key);
10269
+ if (rects.length === 0) return prev;
10270
+ return { ...prev, [key]: { rects } };
9138
10271
  });
9139
10272
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
9140
10273
  const track = el.closest("[data-ohw-hover-pause]");
@@ -9152,6 +10285,48 @@ function OhhwellsBridge() {
9152
10285
  if (e.data?.type !== "ow:canvas-height" || typeof e.data.height !== "number") return;
9153
10286
  document.documentElement.style.setProperty("--ohw-canvas-h", `${e.data.height}px`);
9154
10287
  };
10288
+ const handleCarouselChange = (e) => {
10289
+ if (e.data?.type !== "ow:carousel-change") return;
10290
+ const { key, images } = e.data;
10291
+ if (!key || !Array.isArray(images)) return;
10292
+ const text = JSON.stringify(images);
10293
+ applyCarouselValue(key, images);
10294
+ editContentRef.current = { ...editContentRef.current, [key]: text };
10295
+ postToParentRef.current({ type: "ow:change", nodes: [{ key, text }] });
10296
+ setCarouselHover(null);
10297
+ };
10298
+ const findCarouselAtPoint = (clientX, clientY) => {
10299
+ const containers = Array.from(document.querySelectorAll(`[${CAROUSEL_ATTR}]`));
10300
+ for (let i = containers.length - 1; i >= 0; i--) {
10301
+ const r2 = containers[i].getBoundingClientRect();
10302
+ if (clientX >= r2.left && clientX <= r2.right && clientY >= r2.top && clientY <= r2.bottom) {
10303
+ return containers[i];
10304
+ }
10305
+ }
10306
+ return null;
10307
+ };
10308
+ const isPointOverEditable = (scope, clientX, clientY) => {
10309
+ const editables = scope.querySelectorAll("[data-ohw-editable], [data-ohw-href-key]");
10310
+ for (const el of editables) {
10311
+ const r2 = el.getBoundingClientRect();
10312
+ if (clientX >= r2.left && clientX <= r2.right && clientY >= r2.top && clientY <= r2.bottom) return true;
10313
+ }
10314
+ return false;
10315
+ };
10316
+ const handleCarouselHover = (e) => {
10317
+ const container = findCarouselAtPoint(e.clientX, e.clientY);
10318
+ const scope = container?.closest("[data-ohw-section]") ?? container?.parentElement ?? null;
10319
+ if (!container || !scope || isPointOverEditable(scope, e.clientX, e.clientY)) {
10320
+ setCarouselHover((prev) => prev ? null : prev);
10321
+ return;
10322
+ }
10323
+ const key = container.getAttribute("data-ohw-key") ?? "";
10324
+ if (!key) return;
10325
+ const r2 = getVisibleRect(container);
10326
+ setCarouselHover(
10327
+ (prev) => prev && prev.key === key && prev.rect.top === r2.top && prev.rect.left === r2.left && prev.rect.width === r2.width && prev.rect.height === r2.height ? prev : { key, rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height } }
10328
+ );
10329
+ };
9155
10330
  const applyToolbarPos = (rect) => {
9156
10331
  const ps = parentScrollRef.current;
9157
10332
  const approxW = 330;
@@ -9260,6 +10435,7 @@ function OhhwellsBridge() {
9260
10435
  window.addEventListener("message", handleRemoveSchedulingSection);
9261
10436
  window.addEventListener("message", handleImageUrl);
9262
10437
  window.addEventListener("message", handleImageUploading);
10438
+ window.addEventListener("message", handleCarouselChange);
9263
10439
  window.addEventListener("message", handleCanvasHeight);
9264
10440
  window.addEventListener("message", handleParentScroll);
9265
10441
  window.addEventListener("message", handlePointerSync);
@@ -9271,11 +10447,13 @@ function OhhwellsBridge() {
9271
10447
  };
9272
10448
  window.addEventListener("resize", handleViewportResize, { passive: true });
9273
10449
  document.addEventListener("click", handleClick, true);
10450
+ document.addEventListener("dblclick", handleDblClick, true);
9274
10451
  document.addEventListener("paste", handlePaste, true);
9275
10452
  document.addEventListener("input", handleInput, true);
9276
10453
  document.addEventListener("mouseover", handleMouseOver, true);
9277
10454
  document.addEventListener("mouseout", handleMouseOut, true);
9278
10455
  document.addEventListener("mousemove", handleMouseMove, true);
10456
+ document.addEventListener("mousemove", handleCarouselHover, true);
9279
10457
  document.addEventListener("mouseleave", handleDocMouseLeave);
9280
10458
  document.addEventListener("dragover", handleDragOver, true);
9281
10459
  document.addEventListener("dragleave", handleDragLeave, true);
@@ -9285,11 +10463,13 @@ function OhhwellsBridge() {
9285
10463
  window.addEventListener("scroll", handleScroll, true);
9286
10464
  return () => {
9287
10465
  document.removeEventListener("click", handleClick, true);
10466
+ document.removeEventListener("dblclick", handleDblClick, true);
9288
10467
  document.removeEventListener("paste", handlePaste, true);
9289
10468
  document.removeEventListener("input", handleInput, true);
9290
10469
  document.removeEventListener("mouseover", handleMouseOver, true);
9291
10470
  document.removeEventListener("mouseout", handleMouseOut, true);
9292
10471
  document.removeEventListener("mousemove", handleMouseMove, true);
10472
+ document.removeEventListener("mousemove", handleCarouselHover, true);
9293
10473
  document.removeEventListener("mouseleave", handleDocMouseLeave);
9294
10474
  document.removeEventListener("dragover", handleDragOver, true);
9295
10475
  document.removeEventListener("dragleave", handleDragLeave, true);
@@ -9305,6 +10485,7 @@ function OhhwellsBridge() {
9305
10485
  window.removeEventListener("message", handleRemoveSchedulingSection);
9306
10486
  window.removeEventListener("message", handleImageUrl);
9307
10487
  window.removeEventListener("message", handleImageUploading);
10488
+ window.removeEventListener("message", handleCarouselChange);
9308
10489
  window.removeEventListener("message", handleCanvasHeight);
9309
10490
  window.removeEventListener("message", handleParentScroll);
9310
10491
  window.removeEventListener("resize", handleViewportResize);
@@ -9319,7 +10500,158 @@ function OhhwellsBridge() {
9319
10500
  if (imageShowTimerRef.current) clearTimeout(imageShowTimerRef.current);
9320
10501
  };
9321
10502
  }, [isEditMode, refreshStateRules]);
9322
- useEffect7(() => {
10503
+ useEffect8(() => {
10504
+ if (!isEditMode) return;
10505
+ const THRESHOLD = 10;
10506
+ const resolveWasSelected = (el) => {
10507
+ if (selectedElRef.current === el) return true;
10508
+ const activeAnchor = activeElRef.current ? getNavigationItemAnchor(activeElRef.current) : null;
10509
+ return activeAnchor === el;
10510
+ };
10511
+ const onPointerDown = (e) => {
10512
+ if (e.button !== 0) return;
10513
+ if (suppressNextClickRef.current && !footerDragRef.current && !footerPointerDragRef.current?.started) {
10514
+ suppressNextClickRef.current = false;
10515
+ }
10516
+ if (footerDragRef.current) return;
10517
+ const target = e.target;
10518
+ if (!target) return;
10519
+ if (target.closest('[data-ohw-drag-handle-container], [data-slot="drag-handle"], [data-ohw-toolbar], [data-ohw-item-toolbar-anchor], [data-ohw-link-popover-root]')) {
10520
+ return;
10521
+ }
10522
+ if (target.closest("[data-ohw-item-drag-surface]")) return;
10523
+ const anchor = getNavigationItemAnchor(target);
10524
+ const hrefKey = anchor?.getAttribute("data-ohw-href-key") ?? null;
10525
+ if (anchor && isFooterHrefKey(hrefKey)) {
10526
+ armFooterPressDrag();
10527
+ footerPointerDragRef.current = {
10528
+ el: anchor,
10529
+ kind: "link",
10530
+ startX: e.clientX,
10531
+ startY: e.clientY,
10532
+ pointerId: e.pointerId,
10533
+ wasSelected: resolveWasSelected(anchor),
10534
+ started: false
10535
+ };
10536
+ return;
10537
+ }
10538
+ const col = target.closest("[data-ohw-footer-col]");
10539
+ if (col && !getNavigationItemAnchor(target)) {
10540
+ armFooterPressDrag();
10541
+ footerPointerDragRef.current = {
10542
+ el: col,
10543
+ kind: "column",
10544
+ startX: e.clientX,
10545
+ startY: e.clientY,
10546
+ pointerId: e.pointerId,
10547
+ wasSelected: resolveWasSelected(col),
10548
+ started: false
10549
+ };
10550
+ }
10551
+ };
10552
+ const onPointerMove = (e) => {
10553
+ const pending = footerPointerDragRef.current;
10554
+ if (!pending) return;
10555
+ if (pending.started) {
10556
+ e.preventDefault();
10557
+ clearTextSelection();
10558
+ const session = footerDragRef.current;
10559
+ if (!session) return;
10560
+ const slot = session.kind === "link" && session.hrefKey ? hitTestLinkDropSlot(e.clientX, e.clientY, session.hrefKey) : hitTestColumnDropSlot(e.clientX, e.clientY);
10561
+ refreshFooterDragVisualsRef.current(session, slot, e.clientX, e.clientY);
10562
+ return;
10563
+ }
10564
+ const dx = e.clientX - pending.startX;
10565
+ const dy = e.clientY - pending.startY;
10566
+ if (dx * dx + dy * dy < THRESHOLD * THRESHOLD) return;
10567
+ e.preventDefault();
10568
+ pending.started = true;
10569
+ clearTextSelection();
10570
+ try {
10571
+ document.body.setPointerCapture(pending.pointerId);
10572
+ } catch {
10573
+ }
10574
+ if (linkPopoverOpenRef.current) {
10575
+ setLinkPopoverRef.current(null);
10576
+ }
10577
+ if (activeElRef.current) {
10578
+ deactivateRef.current();
10579
+ }
10580
+ if (pending.kind === "link") {
10581
+ const key = pending.el.getAttribute("data-ohw-href-key");
10582
+ if (!key) return;
10583
+ const column = findFooterColumnForLink(pending.el);
10584
+ const columns2 = listFooterColumns();
10585
+ beginFooterDragRef.current({
10586
+ kind: "link",
10587
+ hrefKey: key,
10588
+ columnEl: column,
10589
+ sourceColumnIndex: column ? columns2.indexOf(column) : 0,
10590
+ wasSelected: pending.wasSelected,
10591
+ draggedEl: pending.el,
10592
+ lastClientX: e.clientX,
10593
+ lastClientY: e.clientY,
10594
+ activeSlot: null
10595
+ });
10596
+ return;
10597
+ }
10598
+ const columns = listFooterColumns();
10599
+ const idx = columns.indexOf(pending.el);
10600
+ if (idx < 0) return;
10601
+ beginFooterDragRef.current({
10602
+ kind: "column",
10603
+ hrefKey: null,
10604
+ columnEl: pending.el,
10605
+ sourceColumnIndex: idx,
10606
+ wasSelected: pending.wasSelected,
10607
+ draggedEl: pending.el,
10608
+ lastClientX: e.clientX,
10609
+ lastClientY: e.clientY,
10610
+ activeSlot: null
10611
+ });
10612
+ };
10613
+ const endPointerDrag = (e) => {
10614
+ const pending = footerPointerDragRef.current;
10615
+ footerPointerDragRef.current = null;
10616
+ try {
10617
+ if (document.body.hasPointerCapture(e.pointerId)) {
10618
+ document.body.releasePointerCapture(e.pointerId);
10619
+ }
10620
+ } catch {
10621
+ }
10622
+ if (!pending?.started) {
10623
+ unlockFooterDragInteraction();
10624
+ return;
10625
+ }
10626
+ suppressNextClickRef.current = true;
10627
+ commitFooterDragRef.current(e.clientX, e.clientY);
10628
+ };
10629
+ const blockSelectStart = (e) => {
10630
+ if (footerDragRef.current || footerPointerDragRef.current?.started || document.documentElement.hasAttribute("data-ohw-footer-press-drag")) {
10631
+ e.preventDefault();
10632
+ }
10633
+ };
10634
+ const onDragEnd = (_e) => {
10635
+ if (!footerDragRef.current) return;
10636
+ commitFooterDragRef.current();
10637
+ };
10638
+ document.addEventListener("pointerdown", onPointerDown, true);
10639
+ document.addEventListener("pointermove", onPointerMove, true);
10640
+ document.addEventListener("pointerup", endPointerDrag, true);
10641
+ document.addEventListener("pointercancel", endPointerDrag, true);
10642
+ document.addEventListener("selectstart", blockSelectStart, true);
10643
+ document.addEventListener("dragend", onDragEnd, true);
10644
+ return () => {
10645
+ document.removeEventListener("pointerdown", onPointerDown, true);
10646
+ document.removeEventListener("pointermove", onPointerMove, true);
10647
+ document.removeEventListener("pointerup", endPointerDrag, true);
10648
+ document.removeEventListener("pointercancel", endPointerDrag, true);
10649
+ document.removeEventListener("selectstart", blockSelectStart, true);
10650
+ document.removeEventListener("dragend", onDragEnd, true);
10651
+ unlockFooterDragInteraction();
10652
+ };
10653
+ }, [isEditMode]);
10654
+ useEffect8(() => {
9323
10655
  const handler = (e) => {
9324
10656
  if (e.data?.type !== "ow:request-schedule-config") return;
9325
10657
  const insertAfterVal = e.data.insertAfter;
@@ -9335,7 +10667,7 @@ function OhhwellsBridge() {
9335
10667
  window.addEventListener("message", handler);
9336
10668
  return () => window.removeEventListener("message", handler);
9337
10669
  }, [processConfigRequest]);
9338
- useEffect7(() => {
10670
+ useEffect8(() => {
9339
10671
  if (!isEditMode) return;
9340
10672
  document.querySelectorAll("[data-ohw-active-state]").forEach((el) => {
9341
10673
  el.removeAttribute("data-ohw-active-state");
@@ -9356,7 +10688,13 @@ function OhhwellsBridge() {
9356
10688
  const next = { ...prev, [pathKey]: sections };
9357
10689
  return shouldUseDevFixtures() ? { ...DEV_SECTIONS_BY_PATH, ...next } : next;
9358
10690
  });
9359
- postToParent2({ type: "ow:ready", version: "1", path: pathname, nodes: collectEditableNodes(), sections });
10691
+ postToParent2({
10692
+ type: "ow:ready",
10693
+ version: "1",
10694
+ path: pathname,
10695
+ nodes: collectEditableNodes(editContentRef.current),
10696
+ sections
10697
+ });
9360
10698
  postToParent2({ type: "ow:request-site-pages" });
9361
10699
  }, 150);
9362
10700
  return () => {
@@ -9364,19 +10702,19 @@ function OhhwellsBridge() {
9364
10702
  clearTimeout(timer);
9365
10703
  };
9366
10704
  }, [pathname, isEditMode, refreshStateRules, postToParent2]);
9367
- useEffect7(() => {
10705
+ useEffect8(() => {
9368
10706
  scrollToHashSectionWhenReady();
9369
10707
  const onHashChange = () => scrollToHashSectionWhenReady();
9370
10708
  window.addEventListener("hashchange", onHashChange);
9371
10709
  return () => window.removeEventListener("hashchange", onHashChange);
9372
10710
  }, [pathname]);
9373
- const handleCommand = useCallback3((cmd) => {
10711
+ const handleCommand = useCallback4((cmd) => {
9374
10712
  document.execCommand(cmd, false);
9375
10713
  activeElRef.current?.focus();
9376
10714
  if (activeElRef.current) setToolbarRect(getEditMeasureEl(activeElRef.current).getBoundingClientRect());
9377
10715
  refreshActiveCommandsRef.current();
9378
10716
  }, []);
9379
- const handleStateChange = useCallback3((state) => {
10717
+ const handleStateChange = useCallback4((state) => {
9380
10718
  if (!activeStateElRef.current) return;
9381
10719
  const el = activeStateElRef.current;
9382
10720
  if (state === "Default") {
@@ -9389,11 +10727,11 @@ function OhhwellsBridge() {
9389
10727
  }
9390
10728
  setToggleState((prev) => prev ? { ...prev, activeState: state } : null);
9391
10729
  }, [deactivate]);
9392
- const closeLinkPopover = useCallback3(() => {
10730
+ const closeLinkPopover = useCallback4(() => {
9393
10731
  addNavAfterAnchorRef.current = null;
9394
10732
  setLinkPopover(null);
9395
10733
  }, []);
9396
- const openLinkPopoverForActive = useCallback3(() => {
10734
+ const openLinkPopoverForActive = useCallback4(() => {
9397
10735
  const hrefCtx = getHrefKeyFromElement(activeElRef.current);
9398
10736
  if (!hrefCtx) return;
9399
10737
  bumpLinkPopoverGrace();
@@ -9404,7 +10742,7 @@ function OhhwellsBridge() {
9404
10742
  });
9405
10743
  deactivate();
9406
10744
  }, [deactivate]);
9407
- const openLinkPopoverForSelected = useCallback3(() => {
10745
+ const openLinkPopoverForSelected = useCallback4(() => {
9408
10746
  const anchor = selectedElRef.current;
9409
10747
  if (!anchor) return;
9410
10748
  const key = anchor.getAttribute("data-ohw-href-key");
@@ -9417,7 +10755,7 @@ function OhhwellsBridge() {
9417
10755
  });
9418
10756
  deselect();
9419
10757
  }, [deselect]);
9420
- const handleLinkPopoverSubmit = useCallback3(
10758
+ const handleLinkPopoverSubmit = useCallback4(
9421
10759
  (target) => {
9422
10760
  const session = linkPopoverSessionRef.current;
9423
10761
  if (!session) return;
@@ -9473,16 +10811,22 @@ function OhhwellsBridge() {
9473
10811
  },
9474
10812
  [postToParent2, sitePages, sectionsByPath]
9475
10813
  );
9476
- const showEditLink = toolbarShowEditLink;
9477
- const currentSections = sectionsByPath[pathname] ?? [];
9478
- linkPopoverOpenRef.current = linkPopover !== null;
9479
- const handleMediaReplace = useCallback3(
10814
+ const handleMediaReplace = useCallback4(
9480
10815
  (key) => {
10816
+ if (mediaHover?.key === key) {
10817
+ pendingUploadRectRef.current = { key, rect: mediaHover.rect };
10818
+ }
9481
10819
  postToParent2({ type: "ow:image-pick", key, elementType: mediaHover?.elementType ?? "image" });
9482
10820
  },
9483
- [postToParent2, mediaHover?.elementType]
10821
+ [postToParent2, mediaHover]
10822
+ );
10823
+ const handleEditCarousel = useCallback4(
10824
+ (key) => {
10825
+ postToParent2({ type: "ow:carousel-open", key, images: readCarouselValue(key) });
10826
+ },
10827
+ [postToParent2]
9484
10828
  );
9485
- const handleMediaFadeOutComplete = useCallback3((key) => {
10829
+ const handleMediaFadeOutComplete = useCallback4((key) => {
9486
10830
  setUploadingRects((prev) => {
9487
10831
  if (!(key in prev)) return prev;
9488
10832
  const next = { ...prev };
@@ -9490,7 +10834,7 @@ function OhhwellsBridge() {
9490
10834
  return next;
9491
10835
  });
9492
10836
  }, []);
9493
- const handleVideoSettingsChange = useCallback3(
10837
+ const handleVideoSettingsChange = useCallback4(
9494
10838
  (key, settings) => {
9495
10839
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
9496
10840
  const video = getVideoEl(el);
@@ -9512,21 +10856,26 @@ function OhhwellsBridge() {
9512
10856
  },
9513
10857
  [postToParent2]
9514
10858
  );
10859
+ const showEditLink = toolbarShowEditLink;
10860
+ const currentSections = sectionsByPath[pathname] ?? [];
10861
+ linkPopoverOpenRef.current = linkPopover !== null;
9515
10862
  return bridgeRoot ? createPortal2(
9516
- /* @__PURE__ */ jsxs13(Fragment5, { children: [
9517
- /* @__PURE__ */ jsx23("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
9518
- Object.entries(uploadingRects).map(([key, { rect, fadingOut }]) => /* @__PURE__ */ jsx23(
9519
- MediaOverlay,
9520
- {
9521
- hover: { key, rect, elementType: "image", isDragOver: false, hasTextOverlap: false },
9522
- isUploading: true,
9523
- fadingOut,
9524
- onFadeOutComplete: handleMediaFadeOutComplete,
9525
- onReplace: handleMediaReplace
9526
- },
9527
- `uploading-${key}`
9528
- )),
9529
- mediaHover && !(mediaHover.key in uploadingRects) && /* @__PURE__ */ jsx23(
10863
+ /* @__PURE__ */ jsxs14(Fragment5, { children: [
10864
+ /* @__PURE__ */ jsx25("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
10865
+ Object.entries(uploadingRects).flatMap(
10866
+ ([key, { rects, fadingOut }]) => rects.map((rect, i) => /* @__PURE__ */ jsx25(
10867
+ MediaOverlay,
10868
+ {
10869
+ hover: { key, rect, elementType: "image", isDragOver: false, hasTextOverlap: false },
10870
+ isUploading: true,
10871
+ fadingOut,
10872
+ onFadeOutComplete: handleMediaFadeOutComplete,
10873
+ onReplace: handleMediaReplace
10874
+ },
10875
+ `uploading-${key}-${i}`
10876
+ ))
10877
+ ),
10878
+ mediaHover && !(mediaHover.key in uploadingRects) && /* @__PURE__ */ jsx25(
9530
10879
  MediaOverlay,
9531
10880
  {
9532
10881
  hover: mediaHover,
@@ -9535,62 +10884,33 @@ function OhhwellsBridge() {
9535
10884
  onVideoSettingsChange: handleVideoSettingsChange
9536
10885
  }
9537
10886
  ),
9538
- siblingHintRect && !isItemDragging && /* @__PURE__ */ jsx23(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
9539
- hoveredNavContainerRect && toolbarVariant !== "select-frame" && !linkPopover && !isItemDragging && !siblingHintRect && /* @__PURE__ */ jsx23(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
9540
- toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && /* @__PURE__ */ jsx23(
9541
- NavbarContainerChrome,
9542
- {
9543
- rect: toolbarRect,
9544
- onAdd: handleAddTopLevelNavItem
9545
- }
9546
- ),
9547
- hoveredItemRect && !hoveredNavContainerRect && !siblingHintRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ jsx23(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
9548
- toolbarRect && (toolbarVariant === "link-action" || toolbarVariant === "select-frame") && /* @__PURE__ */ jsx23(
9549
- ItemInteractionLayer,
10887
+ carouselHover && /* @__PURE__ */ jsx25(CarouselOverlay, { hover: carouselHover, onEdit: handleEditCarousel }),
10888
+ siblingHintRect && !isItemDragging && siblingHintRects.length === 0 && /* @__PURE__ */ jsx25(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
10889
+ siblingHintRects.map((rect, i) => /* @__PURE__ */ jsx25(ItemInteractionLayer, { rect, state: "sibling-hint" }, `sibling-hint-${i}`)),
10890
+ isItemDragging && draggedItemRect && selectedElRef.current !== footerDragRef.current?.draggedEl && /* @__PURE__ */ jsx25(ItemInteractionLayer, { rect: draggedItemRect, state: "dragging" }),
10891
+ isItemDragging && footerDropSlots.map((slot, i) => /* @__PURE__ */ jsx25(
10892
+ "div",
9550
10893
  {
9551
- rect: toolbarRect,
9552
- elRef: glowElRef,
9553
- state: isItemDragging ? "dragging" : resolveItemInteractionState(toolbarRect, parentScrollRef.current),
9554
- showHandle: toolbarVariant === "link-action" && Boolean(reorderHrefKey),
9555
- dragDisabled: reorderDragDisabled,
9556
- dragHandleLabel: reorderHrefKey ? `Reorder ${reorderHrefKey}` : "Reorder item",
9557
- onDragHandleDragStart: handleItemDragStart,
9558
- onDragHandleDragEnd: handleItemDragEnd,
9559
- toolbar: toolbarVariant === "link-action" && !isItemDragging ? /* @__PURE__ */ jsx23(
9560
- ItemActionToolbar,
9561
- {
9562
- onEditLink: openLinkPopoverForSelected,
9563
- addItemDisabled: true,
9564
- editLinkDisabled: false,
9565
- moreDisabled: true
9566
- }
9567
- ) : void 0
9568
- }
9569
- ),
9570
- toolbarRect && toolbarVariant === "rich-text" && /* @__PURE__ */ jsxs13(Fragment5, { children: [
9571
- /* @__PURE__ */ jsx23(
9572
- EditGlowChrome,
9573
- {
9574
- rect: toolbarRect,
9575
- elRef: glowElRef,
9576
- reorderHrefKey,
9577
- dragDisabled: reorderDragDisabled
9578
- }
9579
- ),
9580
- /* @__PURE__ */ jsx23(
9581
- FloatingToolbar,
9582
- {
9583
- rect: toolbarRect,
9584
- parentScroll: parentScrollRef.current,
9585
- elRef: toolbarElRef,
9586
- onCommand: handleCommand,
9587
- activeCommands,
9588
- showEditLink,
9589
- onEditLink: openLinkPopoverForActive
9590
- }
9591
- )
10894
+ className: "pointer-events-none fixed z-2147483646",
10895
+ style: {
10896
+ left: slot.left,
10897
+ top: slot.top,
10898
+ width: slot.width,
10899
+ height: slot.height
10900
+ },
10901
+ children: /* @__PURE__ */ jsx25(DropIndicator, { direction: slot.direction, state: activeFooterDropIndex === i ? "dragActive" : "dragIdle", className: slot.direction === "horizontal" ? "!h-full !w-full" : "!h-full !w-full" })
10902
+ },
10903
+ `footer-drop-${slot.direction}-${slot.columnIndex}-${slot.insertIndex}-${i}`
10904
+ )),
10905
+ hoveredNavContainerRect && toolbarVariant !== "select-frame" && !linkPopover && !isItemDragging && !siblingHintRect && siblingHintRects.length === 0 && /* @__PURE__ */ jsx25(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
10906
+ toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && /* @__PURE__ */ jsx25(NavbarContainerChrome, { rect: toolbarRect, onAdd: handleAddTopLevelNavItem }),
10907
+ hoveredItemRect && !linkPopover && !hoveredNavContainerRect && !siblingHintRect && siblingHintRects.length === 0 && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ jsx25(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
10908
+ toolbarRect && (toolbarVariant === "link-action" || toolbarVariant === "select-frame") && /* @__PURE__ */ jsx25(ItemInteractionLayer, { rect: isItemDragging && draggedItemRect && footerDragRef.current?.wasSelected ? draggedItemRect : toolbarRect, elRef: glowElRef, state: isItemDragging ? "dragging" : resolveItemInteractionState(toolbarRect, parentScrollRef.current), showHandle: toolbarVariant === "link-action" && Boolean(reorderHrefKey) || toolbarVariant === "select-frame" && isFooterFrameSelection, dragDisabled: reorderDragDisabled, dragHandleLabel: reorderHrefKey ? `Reorder ${reorderHrefKey}` : "Reorder item", onDragHandleDragStart: handleItemDragStart, onDragHandleDragEnd: handleItemDragEnd, onItemPointerDown: handleItemChromePointerDown, onItemClick: handleItemChromeClick, toolbar: toolbarVariant === "link-action" && !isItemDragging ? /* @__PURE__ */ jsx25(ItemActionToolbar, { onEditLink: openLinkPopoverForSelected, addItemDisabled: true, editLinkDisabled: false, moreDisabled: true }) : void 0 }),
10909
+ toolbarRect && toolbarVariant === "rich-text" && /* @__PURE__ */ jsxs14(Fragment5, { children: [
10910
+ /* @__PURE__ */ jsx25(EditGlowChrome, { rect: toolbarRect, elRef: glowElRef, reorderHrefKey, dragDisabled: reorderDragDisabled }),
10911
+ /* @__PURE__ */ jsx25(FloatingToolbar, { rect: toolbarRect, parentScroll: parentScrollRef.current, elRef: toolbarElRef, onCommand: handleCommand, activeCommands, showEditLink, onEditLink: openLinkPopoverForActive })
9592
10912
  ] }),
9593
- maxBadge && /* @__PURE__ */ jsxs13(
10913
+ maxBadge && /* @__PURE__ */ jsxs14(
9594
10914
  "div",
9595
10915
  {
9596
10916
  "data-ohw-max-badge": "",
@@ -9616,57 +10936,29 @@ function OhhwellsBridge() {
9616
10936
  ]
9617
10937
  }
9618
10938
  ),
9619
- toggleState && !linkPopover && /* @__PURE__ */ jsx23(
9620
- StateToggle,
9621
- {
9622
- rect: toggleState.rect,
9623
- activeState: toggleState.activeState,
9624
- states: toggleState.states,
9625
- onStateChange: handleStateChange
9626
- }
9627
- ),
9628
- sectionGap && !linkPopover && /* @__PURE__ */ jsxs13(
9629
- "div",
9630
- {
9631
- "data-ohw-section-insert-line": "",
9632
- className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
9633
- style: { top: sectionGap.y, transform: "translateY(-50%)" },
9634
- children: [
9635
- /* @__PURE__ */ jsx23("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
9636
- /* @__PURE__ */ jsx23(
9637
- Badge,
9638
- {
9639
- 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",
9640
- onClick: () => {
9641
- window.parent.postMessage(
9642
- { type: "ow:add-section", insertAfter: sectionGap.insertAfter, insertBefore: sectionGap.insertBefore },
9643
- "*"
9644
- );
10939
+ toggleState && !linkPopover && /* @__PURE__ */ jsx25(StateToggle, { rect: toggleState.rect, activeState: toggleState.activeState, states: toggleState.states, onStateChange: handleStateChange }),
10940
+ sectionGap && !linkPopover && /* @__PURE__ */ jsxs14("div", { "data-ohw-section-insert-line": "", className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none", style: { top: sectionGap.y, transform: "translateY(-50%)" }, children: [
10941
+ /* @__PURE__ */ jsx25("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
10942
+ /* @__PURE__ */ jsx25(
10943
+ Badge,
10944
+ {
10945
+ 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",
10946
+ onClick: () => {
10947
+ window.parent.postMessage(
10948
+ {
10949
+ type: "ow:add-section",
10950
+ insertAfter: sectionGap.insertAfter,
10951
+ insertBefore: sectionGap.insertBefore
9645
10952
  },
9646
- children: "Add Section"
9647
- }
9648
- ),
9649
- /* @__PURE__ */ jsx23("div", { className: "flex-1 bg-primary", style: { height: 3 } })
9650
- ]
9651
- }
9652
- ),
9653
- linkPopover && dialogPortalContainer ? /* @__PURE__ */ jsx23(
9654
- LinkPopover,
9655
- {
9656
- panelRef: linkPopoverPanelRef,
9657
- portalContainer: dialogPortalContainer,
9658
- open: true,
9659
- mode: linkPopover.mode ?? "edit",
9660
- pages: sitePages,
9661
- sections: currentSections,
9662
- sectionsByPath,
9663
- initialTarget: linkPopover.target,
9664
- existingTargets: linkPopover.intent === "add-nav" ? getNavbarExistingTargets() : [],
9665
- onClose: closeLinkPopover,
9666
- onSubmit: handleLinkPopoverSubmit
9667
- },
9668
- linkPopover.key
9669
- ) : null
10953
+ "*"
10954
+ );
10955
+ },
10956
+ children: "Add Section"
10957
+ }
10958
+ ),
10959
+ /* @__PURE__ */ jsx25("div", { className: "flex-1 bg-primary", style: { height: 3 } })
10960
+ ] }),
10961
+ linkPopover && dialogPortalContainer ? /* @__PURE__ */ jsx25(LinkPopover, { panelRef: linkPopoverPanelRef, portalContainer: dialogPortalContainer, open: true, mode: linkPopover.mode ?? "edit", pages: sitePages, sections: currentSections, sectionsByPath, initialTarget: linkPopover.target, existingTargets: linkPopover.intent === "add-nav" ? getNavbarExistingTargets() : [], onClose: closeLinkPopover, onSubmit: handleLinkPopoverSubmit }, linkPopover.key) : null
9670
10962
  ] }),
9671
10963
  bridgeRoot
9672
10964
  ) : null;
@@ -9676,6 +10968,7 @@ export {
9676
10968
  CustomToolbarButton,
9677
10969
  CustomToolbarDivider,
9678
10970
  DragHandle,
10971
+ DropIndicator,
9679
10972
  ItemActionToolbar,
9680
10973
  ItemInteractionLayer,
9681
10974
  LinkEditorPanel,
@@ -9690,12 +10983,14 @@ export {
9690
10983
  TooltipProvider,
9691
10984
  TooltipTrigger,
9692
10985
  buildTarget,
10986
+ dropIndicatorVariants,
9693
10987
  filterAvailablePages,
9694
10988
  getEditModeInitialState,
9695
10989
  isEditSessionActive,
9696
10990
  isValidUrl,
9697
10991
  parseTarget,
9698
10992
  toggleVariants,
10993
+ useOhwCarousel,
9699
10994
  validateUrlInput
9700
10995
  };
9701
10996
  //# sourceMappingURL=index.js.map