@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.cjs CHANGED
@@ -35,6 +35,7 @@ __export(index_exports, {
35
35
  CustomToolbarButton: () => CustomToolbarButton,
36
36
  CustomToolbarDivider: () => CustomToolbarDivider,
37
37
  DragHandle: () => DragHandle,
38
+ DropIndicator: () => DropIndicator,
38
39
  ItemActionToolbar: () => ItemActionToolbar,
39
40
  ItemInteractionLayer: () => ItemInteractionLayer,
40
41
  LinkEditorPanel: () => LinkEditorPanel,
@@ -49,18 +50,20 @@ __export(index_exports, {
49
50
  TooltipProvider: () => TooltipProvider,
50
51
  TooltipTrigger: () => TooltipTrigger,
51
52
  buildTarget: () => buildTarget,
53
+ dropIndicatorVariants: () => dropIndicatorVariants,
52
54
  filterAvailablePages: () => filterAvailablePages,
53
55
  getEditModeInitialState: () => getEditModeInitialState,
54
56
  isEditSessionActive: () => isEditSessionActive,
55
57
  isValidUrl: () => isValidUrl,
56
58
  parseTarget: () => parseTarget,
57
59
  toggleVariants: () => toggleVariants,
60
+ useOhwCarousel: () => useOhwCarousel,
58
61
  validateUrlInput: () => validateUrlInput
59
62
  });
60
63
  module.exports = __toCommonJS(index_exports);
61
64
 
62
65
  // src/OhhwellsBridge.tsx
63
- var import_react8 = __toESM(require("react"), 1);
66
+ var import_react9 = __toESM(require("react"), 1);
64
67
  var import_client = require("react-dom/client");
65
68
  var import_react_dom2 = require("react-dom");
66
69
 
@@ -113,6 +116,7 @@ var import_react3 = require("react");
113
116
  var import_react2 = require("react");
114
117
  var import_radix_ui = require("radix-ui");
115
118
  var import_jsx_runtime = require("react/jsx-runtime");
119
+ var isValidEmail = (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
116
120
  function Spinner() {
117
121
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
118
122
  "svg",
@@ -146,12 +150,17 @@ function EmailCaptureModal({ title, subtitle, onSubmit, onClose }) {
146
150
  const [error, setError] = (0, import_react2.useState)(null);
147
151
  const isBook = title === "Confirm your spot";
148
152
  const handleSubmit = async () => {
149
- if (!email.trim()) return;
153
+ const trimmed = email.trim();
154
+ if (!trimmed) return;
155
+ if (!isValidEmail(trimmed)) {
156
+ setError("Please enter a valid email address.");
157
+ return;
158
+ }
150
159
  setLoading(true);
151
160
  setError(null);
152
161
  try {
153
- await onSubmit(email.trim());
154
- setSubmittedEmail(email.trim());
162
+ await onSubmit(trimmed);
163
+ setSubmittedEmail(trimmed);
155
164
  setSuccess(true);
156
165
  } catch (e) {
157
166
  setError(e instanceof Error ? e.message : "Something went wrong. Please try again.");
@@ -226,7 +235,10 @@ function EmailCaptureModal({ title, subtitle, onSubmit, onClose }) {
226
235
  {
227
236
  type: "email",
228
237
  value: email,
229
- onChange: (e) => setEmail(e.target.value),
238
+ onChange: (e) => {
239
+ setEmail(e.target.value);
240
+ if (error) setError(null);
241
+ },
230
242
  onKeyDown: handleKeyDown,
231
243
  placeholder: "hello@gmail.com",
232
244
  autoFocus: true,
@@ -283,12 +295,20 @@ function formatClassTime(cls) {
283
295
  const em = endTotal % 60;
284
296
  return `${h}:${cls.startTime.minutes}-${eh}:${String(em).padStart(2, "0")}`;
285
297
  }
298
+ function formatBookingLabel(cls) {
299
+ const price = cls.price;
300
+ const isFree = price === null || price === void 0 || `${price}` === "" || `${price}` === "0";
301
+ return isFree ? "Book for free" : `Book for ${cls.currency ?? ""} ${price}`.trim();
302
+ }
286
303
  function getBookingsOnDate(cls, date) {
287
304
  if (!cls.bookings?.length) return 0;
288
- const ds = date.toISOString().split("T")[0];
305
+ const target = new Date(date);
306
+ target.setHours(0, 0, 0, 0);
289
307
  return cls.bookings.filter((b) => {
290
308
  try {
291
- return new Date(b.classDate).toISOString().split("T")[0] === ds;
309
+ const bookingDate = new Date(b.classDate);
310
+ bookingDate.setHours(0, 0, 0, 0);
311
+ return bookingDate.getTime() === target.getTime();
292
312
  } catch {
293
313
  return false;
294
314
  }
@@ -553,7 +573,7 @@ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal
553
573
  }
554
574
  )
555
575
  ] }),
556
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "sm:hidden flex flex-col gap-px", children: isFull ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
576
+ cls.showAvailability !== false && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "sm:hidden flex flex-col gap-px", children: isFull ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
557
577
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
558
578
  available,
559
579
  "/",
@@ -565,9 +585,10 @@ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal
565
585
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-1 flex-col gap-2 min-w-0 sm:contents", children: [
566
586
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex-1 flex flex-col gap-2 min-w-0", children: [
567
587
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block truncate", children: cls.name }),
568
- cls.hostName && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-normal text-(--color-dark,#200C02) opacity-80 block truncate", children: cls.hostName })
588
+ cls.hostName && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-normal text-(--color-dark,#200C02) opacity-80 block truncate", children: cls.hostName }),
589
+ cls.description && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm font-normal text-(--color-dark,#200C02) opacity-70 block truncate", children: cls.description })
569
590
  ] }),
570
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hidden sm:flex w-14 shrink-0 flex-col gap-px", children: isFull ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
591
+ cls.showAvailability !== false && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hidden sm:flex w-14 shrink-0 flex-col gap-px", children: isFull ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
571
592
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
572
593
  available,
573
594
  "/",
@@ -592,7 +613,7 @@ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal
592
613
  if (!cls.id) return;
593
614
  onOpenModal?.(isFull ? "waitlist" : "book", cls.id, selectedDate);
594
615
  },
595
- children: isFull ? "Join Waitlist" : "Book Now"
616
+ children: isFull ? "Join Waitlist" : formatBookingLabel(cls)
596
617
  }
597
618
  )
598
619
  ] })
@@ -634,6 +655,17 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
634
655
  const [isHovered, setIsHovered] = (0, import_react3.useState)(false);
635
656
  const [modalState, setModalState] = (0, import_react3.useState)(null);
636
657
  const switchScheduleIdRef = (0, import_react3.useRef)(null);
658
+ const liveScheduleIdRef = (0, import_react3.useRef)(null);
659
+ const refetchLiveSchedule = (0, import_react3.useCallback)(async () => {
660
+ const id = liveScheduleIdRef.current;
661
+ if (!id) return;
662
+ try {
663
+ const res = await fetch(`${API_URL}/api/schedule/id/${id}`);
664
+ const data = await res.json();
665
+ if (data?.id) setSchedule(data);
666
+ } catch {
667
+ }
668
+ }, []);
637
669
  const dates = (0, import_react3.useMemo)(() => {
638
670
  const today = /* @__PURE__ */ new Date();
639
671
  today.setHours(0, 0, 0, 0);
@@ -653,6 +685,18 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
653
685
  }
654
686
  }
655
687
  }, [schedule, dates]);
688
+ (0, import_react3.useEffect)(() => {
689
+ if (typeof window === "undefined") return;
690
+ const onVisible = () => {
691
+ if (!document.hidden) void refetchLiveSchedule();
692
+ };
693
+ window.addEventListener("focus", refetchLiveSchedule);
694
+ document.addEventListener("visibilitychange", onVisible);
695
+ return () => {
696
+ window.removeEventListener("focus", refetchLiveSchedule);
697
+ document.removeEventListener("visibilitychange", onVisible);
698
+ };
699
+ }, [refetchLiveSchedule]);
656
700
  (0, import_react3.useEffect)(() => {
657
701
  if (typeof window === "undefined") return;
658
702
  const isInEditor = window.self !== window.top;
@@ -714,7 +758,7 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
714
758
  setLoading(false);
715
759
  return;
716
760
  }
717
- ;
761
+ liveScheduleIdRef.current = effectiveId;
718
762
  (async () => {
719
763
  try {
720
764
  const res = await fetch(`${API_URL}/api/schedule/id/${effectiveId}`);
@@ -771,18 +815,14 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
771
815
  const data = await res.json().catch(() => ({}));
772
816
  throw new Error(data?.message ?? "Something went wrong. Please try again.");
773
817
  }
818
+ await refetchLiveSchedule();
774
819
  };
775
820
  const handleReplaceSchedule = () => {
776
821
  setIsHovered(false);
777
- if (schedule) {
778
- window.parent.postMessage({
779
- type: "ow:schedule-connected",
780
- schedule: { id: schedule.id, name: schedule.name },
781
- insertAfter
782
- }, "*");
783
- } else {
784
- window.parent.postMessage({ type: "ow:scheduling-not-connected", insertAfter }, "*");
785
- }
822
+ window.parent.postMessage(
823
+ { type: "ow:scheduling-not-connected", insertAfter, currentScheduleId: schedule?.id },
824
+ "*"
825
+ );
786
826
  };
787
827
  if (!inEditor && !loading && !schedule) return null;
788
828
  const sectionId = `scheduling-${insertAfter}`;
@@ -825,7 +865,19 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
825
865
  ),
826
866
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "max-w-[1280px] mx-auto flex flex-col items-center gap-16", children: [
827
867
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col items-center gap-4", children: [
828
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("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" }),
868
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
869
+ "h2",
870
+ {
871
+ className: "font-display text-center m-0 text-(--color-dark,#200C02)",
872
+ style: {
873
+ fontSize: "var(--fs-section-h2, clamp(40px, 4.4vw, 64px))",
874
+ fontWeight: "var(--font-weight-heading, 400)",
875
+ lineHeight: 1.05,
876
+ letterSpacing: "-0.02em"
877
+ },
878
+ children: schedule?.name ?? "Book an appointment"
879
+ }
880
+ ),
829
881
  schedule?.description && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "font-body text-body text-center m-0 text-(--color-accent,#A89B83)", children: schedule.description })
830
882
  ] }),
831
883
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "w-full flex flex-col items-end gap-3", children: [
@@ -4503,7 +4555,7 @@ function getChromeStyle(state) {
4503
4555
  case "dragging":
4504
4556
  return {
4505
4557
  border: `2px solid ${PRIMARY}`,
4506
- boxShadow: DRAG_SHADOW
4558
+ boxShadow: `${FOCUS_RING}, ${DRAG_SHADOW}`
4507
4559
  };
4508
4560
  default:
4509
4561
  return {};
@@ -4519,6 +4571,8 @@ function ItemInteractionLayer({
4519
4571
  dragHandleLabel = "Reorder item",
4520
4572
  onDragHandleDragStart,
4521
4573
  onDragHandleDragEnd,
4574
+ onItemPointerDown,
4575
+ onItemClick,
4522
4576
  chromeGap = 6,
4523
4577
  className
4524
4578
  }) {
@@ -4526,7 +4580,8 @@ function ItemInteractionLayer({
4526
4580
  const isActive = state === "active-top" || state === "active-bottom";
4527
4581
  const isDragging = state === "dragging";
4528
4582
  const showToolbar = isActive && toolbar;
4529
- const showDragHandle = isActive && showHandle && !isDragging;
4583
+ const showDragHandle = (isActive || isDragging) && showHandle;
4584
+ const itemDragEnabled = isActive && showHandle && !isDragging && !dragDisabled;
4530
4585
  return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
4531
4586
  "div",
4532
4587
  {
@@ -4551,11 +4606,28 @@ function ItemInteractionLayer({
4551
4606
  style: getChromeStyle(state)
4552
4607
  }
4553
4608
  ),
4609
+ itemDragEnabled && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4610
+ "div",
4611
+ {
4612
+ "data-ohw-item-drag-surface": "",
4613
+ className: "pointer-events-auto absolute inset-0 cursor-grab rounded-lg active:cursor-grabbing",
4614
+ onPointerDown: (e) => {
4615
+ if (e.button !== 0) return;
4616
+ onItemPointerDown?.(e);
4617
+ },
4618
+ onClick: (e) => {
4619
+ e.preventDefault();
4620
+ e.stopPropagation();
4621
+ onItemClick?.(e.clientX, e.clientY);
4622
+ }
4623
+ }
4624
+ ),
4554
4625
  showDragHandle && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4555
4626
  "div",
4556
4627
  {
4557
4628
  "data-ohw-drag-handle-container": "",
4558
- className: "pointer-events-auto absolute left-0 top-1/2 -translate-x-[calc(100%+7px)] -translate-y-1/2",
4629
+ className: "pointer-events-auto absolute left-0 top-1/2 z-10 -translate-x-[calc(100%+7px)] -translate-y-1/2",
4630
+ style: isDragging ? { visibility: "hidden" } : void 0,
4559
4631
  children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4560
4632
  DragHandle,
4561
4633
  {
@@ -4778,15 +4850,12 @@ function MediaOverlay({
4778
4850
  className: "flex items-center justify-center cursor-pointer",
4779
4851
  style: {
4780
4852
  ...box,
4781
- // When text overlays this media, let clicks fall THROUGH to the text underneath. In the
4782
- // parent this needed a whole `ow:click-at` round-trip to re-hit-test inside the iframe;
4783
- // in-document, pointer-events does it natively. The button below opts back in, so
4784
- // Replace still works.
4785
- pointerEvents: hover.hasTextOverlap ? "none" : "auto",
4853
+ // Keep the full-size visual overlay click-through so carousel controls and editable
4854
+ // content underneath remain interactive. The Replace button opts back in below.
4855
+ pointerEvents: "none",
4786
4856
  boxShadow: "inset 0 0 0 1.5px var(--color-primary)",
4787
4857
  background: "color-mix(in srgb, var(--color-primary) 20%, transparent)"
4788
4858
  },
4789
- onClick: () => onReplace(hover.key),
4790
4859
  children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
4791
4860
  Button,
4792
4861
  {
@@ -4811,6 +4880,61 @@ function MediaOverlay({
4811
4880
  ] });
4812
4881
  }
4813
4882
 
4883
+ // src/ui/CarouselOverlay.tsx
4884
+ var import_lucide_react4 = require("lucide-react");
4885
+ var import_jsx_runtime12 = require("react/jsx-runtime");
4886
+ var OVERLAY_BUTTON_STYLE2 = {
4887
+ pointerEvents: "auto",
4888
+ fontFamily: "Inter, sans-serif",
4889
+ fontSize: 12,
4890
+ color: "#000"
4891
+ };
4892
+ function CarouselOverlay({
4893
+ hover,
4894
+ onEdit
4895
+ }) {
4896
+ const { rect } = hover;
4897
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
4898
+ "div",
4899
+ {
4900
+ "data-ohw-bridge": "",
4901
+ "data-ohw-carousel-overlay": "",
4902
+ className: "flex cursor-pointer items-center justify-center",
4903
+ style: {
4904
+ position: "fixed",
4905
+ top: rect.top,
4906
+ left: rect.left,
4907
+ width: rect.width,
4908
+ height: rect.height,
4909
+ zIndex: 2147483646,
4910
+ pointerEvents: "auto",
4911
+ boxShadow: "inset 0 0 0 1.5px var(--color-primary)",
4912
+ background: "color-mix(in srgb, var(--color-primary) 20%, transparent)"
4913
+ },
4914
+ onClick: () => onEdit(hover.key),
4915
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
4916
+ Button,
4917
+ {
4918
+ "data-ohw-carousel-overlay": "",
4919
+ variant: "outline",
4920
+ size: "sm",
4921
+ className: "cursor-pointer gap-1.5 hover:bg-background",
4922
+ style: OVERLAY_BUTTON_STYLE2,
4923
+ onMouseDown: (e) => e.preventDefault(),
4924
+ onClick: (e) => {
4925
+ e.stopPropagation();
4926
+ onEdit(hover.key);
4927
+ },
4928
+ children: [
4929
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react4.GalleryHorizontal, { size: 14 }),
4930
+ "Edit gallery"
4931
+ ]
4932
+ }
4933
+ )
4934
+ }
4935
+ );
4936
+ }
4937
+
4814
4938
  // src/OhhwellsBridge.tsx
4815
4939
  var import_react_dom3 = require("react-dom");
4816
4940
  var import_navigation2 = require("next/navigation");
@@ -5096,23 +5220,23 @@ var import_react7 = require("react");
5096
5220
  // src/ui/dialog.tsx
5097
5221
  var React7 = __toESM(require("react"), 1);
5098
5222
  var import_radix_ui5 = require("radix-ui");
5099
- var import_lucide_react4 = require("lucide-react");
5100
- var import_jsx_runtime12 = require("react/jsx-runtime");
5223
+ var import_lucide_react5 = require("lucide-react");
5224
+ var import_jsx_runtime13 = require("react/jsx-runtime");
5101
5225
  function Dialog2({
5102
5226
  ...props
5103
5227
  }) {
5104
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_radix_ui5.Dialog.Root, { "data-slot": "dialog", ...props });
5228
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_radix_ui5.Dialog.Root, { "data-slot": "dialog", ...props });
5105
5229
  }
5106
5230
  function DialogPortal({
5107
5231
  ...props
5108
5232
  }) {
5109
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_radix_ui5.Dialog.Portal, { ...props });
5233
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_radix_ui5.Dialog.Portal, { ...props });
5110
5234
  }
5111
5235
  function DialogOverlay({
5112
5236
  className,
5113
5237
  ...props
5114
5238
  }) {
5115
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
5239
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5116
5240
  import_radix_ui5.Dialog.Overlay,
5117
5241
  {
5118
5242
  "data-slot": "dialog-overlay",
@@ -5125,9 +5249,9 @@ function DialogOverlay({
5125
5249
  var DialogContent = React7.forwardRef(
5126
5250
  ({ className, children, showCloseButton = true, container, ...props }, ref) => {
5127
5251
  const positionMode = container ? "absolute" : "fixed";
5128
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(DialogPortal, { container: container ?? void 0, children: [
5129
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(DialogOverlay, { className: cn(positionMode, "inset-0") }),
5130
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
5252
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(DialogPortal, { container: container ?? void 0, children: [
5253
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(DialogOverlay, { className: cn(positionMode, "inset-0") }),
5254
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
5131
5255
  import_radix_ui5.Dialog.Content,
5132
5256
  {
5133
5257
  ref,
@@ -5143,13 +5267,13 @@ var DialogContent = React7.forwardRef(
5143
5267
  ...props,
5144
5268
  children: [
5145
5269
  children,
5146
- showCloseButton ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
5270
+ showCloseButton ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5147
5271
  import_radix_ui5.Dialog.Close,
5148
5272
  {
5149
5273
  type: "button",
5150
5274
  className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
5151
5275
  "aria-label": "Close",
5152
- children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react4.X, { size: 16, "aria-hidden": true })
5276
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react5.X, { size: 16, "aria-hidden": true })
5153
5277
  }
5154
5278
  ) : null
5155
5279
  ]
@@ -5163,13 +5287,13 @@ function DialogHeader({
5163
5287
  className,
5164
5288
  ...props
5165
5289
  }) {
5166
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: cn("flex flex-col gap-1.5", className), ...props });
5290
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: cn("flex flex-col gap-1.5", className), ...props });
5167
5291
  }
5168
5292
  function DialogFooter({
5169
5293
  className,
5170
5294
  ...props
5171
5295
  }) {
5172
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
5296
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5173
5297
  "div",
5174
5298
  {
5175
5299
  className: cn("flex items-center justify-end gap-2", className),
@@ -5177,7 +5301,7 @@ function DialogFooter({
5177
5301
  }
5178
5302
  );
5179
5303
  }
5180
- var DialogTitle = React7.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
5304
+ var DialogTitle = React7.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5181
5305
  import_radix_ui5.Dialog.Title,
5182
5306
  {
5183
5307
  ref,
@@ -5189,7 +5313,7 @@ var DialogTitle = React7.forwardRef(({ className, ...props }, ref) => /* @__PURE
5189
5313
  }
5190
5314
  ));
5191
5315
  DialogTitle.displayName = import_radix_ui5.Dialog.Title.displayName;
5192
- var DialogDescription = React7.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
5316
+ var DialogDescription = React7.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5193
5317
  import_radix_ui5.Dialog.Description,
5194
5318
  {
5195
5319
  ref,
@@ -5201,63 +5325,63 @@ DialogDescription.displayName = import_radix_ui5.Dialog.Description.displayName;
5201
5325
  var DialogClose = import_radix_ui5.Dialog.Close;
5202
5326
 
5203
5327
  // src/ui/link-modal/LinkEditorPanel.tsx
5204
- var import_lucide_react8 = require("lucide-react");
5328
+ var import_lucide_react9 = require("lucide-react");
5205
5329
 
5206
5330
  // src/ui/link-modal/DestinationBreadcrumb.tsx
5207
- var import_lucide_react5 = require("lucide-react");
5208
- var import_jsx_runtime13 = require("react/jsx-runtime");
5331
+ var import_lucide_react6 = require("lucide-react");
5332
+ var import_jsx_runtime14 = require("react/jsx-runtime");
5209
5333
  function DestinationBreadcrumb({
5210
5334
  pageTitle,
5211
5335
  sectionLabel
5212
5336
  }) {
5213
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex w-full flex-col gap-2", children: [
5214
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { className: "text-sm font-medium! text-foreground m-0", children: "Destination" }),
5215
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-center gap-3", children: [
5216
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-center gap-2", children: [
5217
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react5.File, { size: 16, className: "shrink-0 text-foreground", "aria-hidden": true }),
5218
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "text-sm font-medium leading-none text-foreground!", children: pageTitle })
5337
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex w-full flex-col gap-2", children: [
5338
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { className: "text-sm font-medium! text-foreground m-0", children: "Destination" }),
5339
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex items-center gap-3", children: [
5340
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex items-center gap-2", children: [
5341
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_lucide_react6.File, { size: 16, className: "shrink-0 text-foreground", "aria-hidden": true }),
5342
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "text-sm font-medium leading-none text-foreground!", children: pageTitle })
5219
5343
  ] }),
5220
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5221
- import_lucide_react5.ArrowRight,
5344
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
5345
+ import_lucide_react6.ArrowRight,
5222
5346
  {
5223
5347
  size: 16,
5224
5348
  className: "shrink-0 text-muted-foreground",
5225
5349
  "aria-hidden": true
5226
5350
  }
5227
5351
  ),
5228
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [
5229
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5230
- import_lucide_react5.GalleryVertical,
5352
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [
5353
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
5354
+ import_lucide_react6.GalleryVertical,
5231
5355
  {
5232
5356
  size: 16,
5233
5357
  className: "shrink-0 text-foreground",
5234
5358
  "aria-hidden": true
5235
5359
  }
5236
5360
  ),
5237
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "truncate text-sm font-medium leading-none text-foreground", children: sectionLabel })
5361
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "truncate text-sm font-medium leading-none text-foreground", children: sectionLabel })
5238
5362
  ] })
5239
5363
  ] })
5240
5364
  ] });
5241
5365
  }
5242
5366
 
5243
5367
  // src/ui/link-modal/SectionTreeItem.tsx
5244
- var import_lucide_react6 = require("lucide-react");
5245
- var import_jsx_runtime14 = require("react/jsx-runtime");
5368
+ var import_lucide_react7 = require("lucide-react");
5369
+ var import_jsx_runtime15 = require("react/jsx-runtime");
5246
5370
  function SectionTreeItem({
5247
5371
  section,
5248
5372
  onSelect,
5249
5373
  selected
5250
5374
  }) {
5251
5375
  const interactive = Boolean(onSelect);
5252
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex h-9 w-full items-end pl-3", children: [
5253
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
5376
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex h-9 w-full items-end pl-3", children: [
5377
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
5254
5378
  "div",
5255
5379
  {
5256
5380
  className: "mr-[-1px] h-9 w-2 shrink-0 rounded-bl-sm border-b border-l border-border mb-4",
5257
5381
  "aria-hidden": true
5258
5382
  }
5259
5383
  ),
5260
- /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
5384
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
5261
5385
  "div",
5262
5386
  {
5263
5387
  role: interactive ? "button" : void 0,
@@ -5275,15 +5399,15 @@ function SectionTreeItem({
5275
5399
  interactive && selected && "border-primary"
5276
5400
  ),
5277
5401
  children: [
5278
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
5279
- import_lucide_react6.GalleryVertical,
5402
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
5403
+ import_lucide_react7.GalleryVertical,
5280
5404
  {
5281
5405
  size: 16,
5282
5406
  className: "shrink-0 text-foreground",
5283
5407
  "aria-hidden": true
5284
5408
  }
5285
5409
  ),
5286
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "truncate text-sm font-normal leading-5 text-foreground", children: section.label })
5410
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "truncate text-sm font-normal leading-5 text-foreground", children: section.label })
5287
5411
  ]
5288
5412
  }
5289
5413
  )
@@ -5295,10 +5419,10 @@ var import_react4 = require("react");
5295
5419
 
5296
5420
  // src/ui/input.tsx
5297
5421
  var React8 = __toESM(require("react"), 1);
5298
- var import_jsx_runtime15 = require("react/jsx-runtime");
5422
+ var import_jsx_runtime16 = require("react/jsx-runtime");
5299
5423
  var Input = React8.forwardRef(
5300
5424
  ({ className, type, ...props }, ref) => {
5301
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
5425
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
5302
5426
  "input",
5303
5427
  {
5304
5428
  type,
@@ -5317,9 +5441,9 @@ Input.displayName = "Input";
5317
5441
 
5318
5442
  // src/ui/label.tsx
5319
5443
  var import_radix_ui6 = require("radix-ui");
5320
- var import_jsx_runtime16 = require("react/jsx-runtime");
5444
+ var import_jsx_runtime17 = require("react/jsx-runtime");
5321
5445
  function Label({ className, ...props }) {
5322
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
5446
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5323
5447
  import_radix_ui6.Label.Root,
5324
5448
  {
5325
5449
  "data-slot": "label",
@@ -5330,12 +5454,12 @@ function Label({ className, ...props }) {
5330
5454
  }
5331
5455
 
5332
5456
  // src/ui/link-modal/UrlOrPageInput.tsx
5333
- var import_lucide_react7 = require("lucide-react");
5334
- var import_jsx_runtime17 = require("react/jsx-runtime");
5457
+ var import_lucide_react8 = require("lucide-react");
5458
+ var import_jsx_runtime18 = require("react/jsx-runtime");
5335
5459
  function FieldChevron({
5336
5460
  onClick
5337
5461
  }) {
5338
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5462
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5339
5463
  "button",
5340
5464
  {
5341
5465
  type: "button",
@@ -5343,7 +5467,7 @@ function FieldChevron({
5343
5467
  onClick,
5344
5468
  "aria-label": "Open page list",
5345
5469
  tabIndex: -1,
5346
- children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react7.ChevronDown, { size: 16 })
5470
+ children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_lucide_react8.ChevronDown, { size: 16 })
5347
5471
  }
5348
5472
  );
5349
5473
  }
@@ -5404,19 +5528,19 @@ function UrlOrPageInput({
5404
5528
  "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]",
5405
5529
  urlError ? "border-destructive shadow-[0_0_0_1px_var(--ohw-destructive)]" : isFocused ? "border-primary shadow-[0_0_0_1px_var(--ohw-primary)]" : "border-input"
5406
5530
  );
5407
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex w-full flex-col gap-2 p-0", children: [
5408
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Label, { htmlFor: inputId, className: cn(urlError && "text-destructive"), children: "Destination" }),
5409
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { ref: rootRef, className: "relative w-full", children: [
5410
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { "data-ohw-link-field": true, className: fieldClassName, children: [
5411
- selectedPage ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "flex shrink-0 items-center pr-2", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5412
- import_lucide_react7.File,
5531
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex w-full flex-col gap-2 p-0", children: [
5532
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Label, { htmlFor: inputId, className: cn(urlError && "text-destructive"), children: "Destination" }),
5533
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { ref: rootRef, className: "relative w-full", children: [
5534
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { "data-ohw-link-field": true, className: fieldClassName, children: [
5535
+ selectedPage ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "flex shrink-0 items-center pr-2", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5536
+ import_lucide_react8.File,
5413
5537
  {
5414
5538
  size: 16,
5415
5539
  className: "shrink-0 text-foreground",
5416
5540
  "aria-hidden": true
5417
5541
  }
5418
5542
  ) }) : null,
5419
- readOnly ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "min-w-0 flex-1 truncate text-sm leading-5 text-foreground", children: selectedPage?.title ?? value }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5543
+ readOnly ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "min-w-0 flex-1 truncate text-sm leading-5 text-foreground", children: selectedPage?.title ?? value }) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5420
5544
  Input,
5421
5545
  {
5422
5546
  ref: inputRef,
@@ -5442,7 +5566,7 @@ function UrlOrPageInput({
5442
5566
  )
5443
5567
  }
5444
5568
  ),
5445
- selectedPage && !readOnly ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5569
+ selectedPage && !readOnly ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5446
5570
  "button",
5447
5571
  {
5448
5572
  type: "button",
@@ -5450,26 +5574,26 @@ function UrlOrPageInput({
5450
5574
  onMouseDown: clearSelection,
5451
5575
  "aria-label": "Clear selected page",
5452
5576
  tabIndex: -1,
5453
- children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react7.X, { size: 16, "aria-hidden": true })
5577
+ children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_lucide_react8.X, { size: 16, "aria-hidden": true })
5454
5578
  }
5455
5579
  ) : null,
5456
- !readOnly ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(FieldChevron, { onClick: toggleDropdown }) : null
5580
+ !readOnly ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(FieldChevron, { onClick: toggleDropdown }) : null
5457
5581
  ] }),
5458
- dropdownOpen && !readOnly && filteredPages.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5582
+ dropdownOpen && !readOnly && filteredPages.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5459
5583
  "div",
5460
5584
  {
5461
5585
  "data-ohw-link-page-dropdown": "",
5462
5586
  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",
5463
5587
  onMouseDown: (e) => e.preventDefault(),
5464
- children: filteredPages.map((page) => /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
5588
+ children: filteredPages.map((page) => /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
5465
5589
  "button",
5466
5590
  {
5467
5591
  type: "button",
5468
5592
  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",
5469
5593
  onClick: () => onPageSelect(page),
5470
5594
  children: [
5471
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react7.File, { size: 16, className: "shrink-0", "aria-hidden": true }),
5472
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "truncate", children: page.title })
5595
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_lucide_react8.File, { size: 16, className: "shrink-0", "aria-hidden": true }),
5596
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "truncate", children: page.title })
5473
5597
  ]
5474
5598
  },
5475
5599
  page.path
@@ -5477,34 +5601,34 @@ function UrlOrPageInput({
5477
5601
  }
5478
5602
  ) : null
5479
5603
  ] }),
5480
- urlError ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("p", { className: "text-sm font-medium text-destructive", children: urlError }) : null
5604
+ urlError ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { className: "text-sm font-medium text-destructive", children: urlError }) : null
5481
5605
  ] });
5482
5606
  }
5483
5607
 
5484
5608
  // src/ui/link-modal/LinkEditorPanel.tsx
5485
- var import_jsx_runtime18 = require("react/jsx-runtime");
5609
+ var import_jsx_runtime19 = require("react/jsx-runtime");
5486
5610
  function LinkEditorPanel({ state, onClose }) {
5487
5611
  const isCancel = state.secondaryLabel === "Cancel" || state.secondaryLabel === "Back to sections";
5488
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [
5489
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(DialogClose, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5612
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_jsx_runtime19.Fragment, { children: [
5613
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(DialogClose, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5490
5614
  "button",
5491
5615
  {
5492
5616
  type: "button",
5493
5617
  className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50 h-7",
5494
5618
  "aria-label": "Close",
5495
5619
  onClick: onClose,
5496
- children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_lucide_react8.X, { size: 16, "aria-hidden": true })
5620
+ children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react9.X, { size: 16, "aria-hidden": true })
5497
5621
  }
5498
5622
  ) }),
5499
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(DialogHeader, { className: "w-full gap-1.5 p-6 pr-12", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(DialogTitle, { className: "m-0 w-full break-words", children: state.title }) }),
5500
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex w-full flex-col gap-3 px-6 pb-8 pt-1", children: [
5501
- state.showBreadcrumb && state.selectedPage && state.selectedSection ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5623
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(DialogHeader, { className: "w-full gap-1.5 p-6 pr-12", children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(DialogTitle, { className: "m-0 w-full break-words", children: state.title }) }),
5624
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex w-full flex-col gap-3 px-6 pb-8 pt-1", children: [
5625
+ state.showBreadcrumb && state.selectedPage && state.selectedSection ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5502
5626
  DestinationBreadcrumb,
5503
5627
  {
5504
5628
  pageTitle: state.selectedPage.title,
5505
5629
  sectionLabel: state.selectedSection.label
5506
5630
  }
5507
- ) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5631
+ ) : /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5508
5632
  UrlOrPageInput,
5509
5633
  {
5510
5634
  value: state.searchValue,
@@ -5517,8 +5641,8 @@ function LinkEditorPanel({ state, onClose }) {
5517
5641
  urlError: state.urlError
5518
5642
  }
5519
5643
  ),
5520
- state.showChooseSection ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex flex-col justify-center gap-2", children: [
5521
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5644
+ state.showChooseSection ? /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex flex-col justify-center gap-2", children: [
5645
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5522
5646
  Button,
5523
5647
  {
5524
5648
  type: "button",
@@ -5529,15 +5653,15 @@ function LinkEditorPanel({ state, onClose }) {
5529
5653
  children: "Choose a section"
5530
5654
  }
5531
5655
  ),
5532
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex items-center gap-1 text-sm text-muted-foreground", children: [
5533
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_lucide_react8.Info, { size: 16, className: "shrink-0", "aria-hidden": true }),
5534
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { children: "Pick a section this link should scroll to." })
5656
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex items-center gap-1 text-sm text-muted-foreground", children: [
5657
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react9.Info, { size: 16, className: "shrink-0", "aria-hidden": true }),
5658
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { children: "Pick a section this link should scroll to." })
5535
5659
  ] })
5536
5660
  ] }) : null,
5537
- state.showSectionRow && state.selectedSection ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(SectionTreeItem, { section: state.selectedSection, selected: true }) : null
5661
+ state.showSectionRow && state.selectedSection ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(SectionTreeItem, { section: state.selectedSection, selected: true }) : null
5538
5662
  ] }),
5539
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(DialogFooter, { className: "w-full px-6 pb-6", children: [
5540
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5663
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(DialogFooter, { className: "w-full px-6 pb-6", children: [
5664
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5541
5665
  Button,
5542
5666
  {
5543
5667
  type: "button",
@@ -5552,7 +5676,7 @@ function LinkEditorPanel({ state, onClose }) {
5552
5676
  children: state.secondaryLabel
5553
5677
  }
5554
5678
  ),
5555
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5679
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5556
5680
  Button,
5557
5681
  {
5558
5682
  type: "button",
@@ -5573,9 +5697,9 @@ function LinkEditorPanel({ state, onClose }) {
5573
5697
  // src/ui/link-modal/SectionPickerOverlay.tsx
5574
5698
  var import_react5 = require("react");
5575
5699
  var import_react_dom = require("react-dom");
5576
- var import_lucide_react9 = require("lucide-react");
5700
+ var import_lucide_react10 = require("lucide-react");
5577
5701
  var import_navigation = require("next/navigation");
5578
- var import_jsx_runtime19 = require("react/jsx-runtime");
5702
+ var import_jsx_runtime20 = require("react/jsx-runtime");
5579
5703
  var DIM_OVERLAY = "rgba(0, 0, 0, 0.45)";
5580
5704
  function rectsEqual(a, b) {
5581
5705
  if (a.size !== b.size) return false;
@@ -5804,7 +5928,7 @@ function SectionPickerOverlay({
5804
5928
  const portalRoot = typeof document !== "undefined" ? document.querySelector("[data-ohw-bridge-root]") ?? document.body : null;
5805
5929
  if (!portalRoot) return null;
5806
5930
  return (0, import_react_dom.createPortal)(
5807
- /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
5931
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
5808
5932
  "div",
5809
5933
  {
5810
5934
  "data-ohw-section-picker": "",
@@ -5814,12 +5938,12 @@ function SectionPickerOverlay({
5814
5938
  role: "dialog",
5815
5939
  "aria-label": "Choose a section",
5816
5940
  children: [
5817
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5941
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
5818
5942
  "div",
5819
5943
  {
5820
5944
  className: "pointer-events-auto fixed left-5 z-[2]",
5821
5945
  style: { top: chromeClip.top + 20 },
5822
- children: /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
5946
+ children: /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
5823
5947
  Button,
5824
5948
  {
5825
5949
  type: "button",
@@ -5828,14 +5952,14 @@ function SectionPickerOverlay({
5828
5952
  className: "h-8 min-w-0 gap-1 border-border bg-background px-2 py-1.5 shadow-sm hover:bg-muted cursor-pointer",
5829
5953
  onClick: onBack,
5830
5954
  children: [
5831
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react9.ArrowLeft, { className: "size-4 shrink-0", "aria-hidden": true }),
5955
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react10.ArrowLeft, { className: "size-4 shrink-0", "aria-hidden": true }),
5832
5956
  "Back"
5833
5957
  ]
5834
5958
  }
5835
5959
  )
5836
5960
  }
5837
5961
  ),
5838
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5962
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
5839
5963
  "div",
5840
5964
  {
5841
5965
  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",
@@ -5848,7 +5972,7 @@ function SectionPickerOverlay({
5848
5972
  children: "Click on section to select"
5849
5973
  }
5850
5974
  ),
5851
- !isOnTargetPage ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5975
+ !isOnTargetPage ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
5852
5976
  "div",
5853
5977
  {
5854
5978
  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",
@@ -5856,14 +5980,14 @@ function SectionPickerOverlay({
5856
5980
  children: "Loading page preview\u2026"
5857
5981
  }
5858
5982
  ) : null,
5859
- isOnTargetPage && liveSections.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "pointer-events-auto fixed inset-0 z-[1] flex items-center justify-center bg-muted/40", children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("p", { className: "text-sm text-muted-foreground", children: "No sections found on this page." }) }) : null,
5983
+ isOnTargetPage && liveSections.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "pointer-events-auto fixed inset-0 z-[1] flex items-center justify-center bg-muted/40", children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("p", { className: "text-sm text-muted-foreground", children: "No sections found on this page." }) }) : null,
5860
5984
  isOnTargetPage ? liveSections.map((section) => {
5861
5985
  const rect = rects.get(section.id);
5862
5986
  if (!rect || rect.width <= 0 || rect.height <= 0) return null;
5863
5987
  const isSelected = selectedId === section.id;
5864
5988
  const isHovered = hoveredId === section.id;
5865
5989
  const isLit = isSelected || isHovered;
5866
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
5990
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
5867
5991
  "button",
5868
5992
  {
5869
5993
  type: "button",
@@ -5878,7 +6002,7 @@ function SectionPickerOverlay({
5878
6002
  "aria-label": `Select section ${section.label}`,
5879
6003
  onClick: () => handleSelect(section),
5880
6004
  children: [
5881
- isLit ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
6005
+ isLit ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
5882
6006
  "span",
5883
6007
  {
5884
6008
  className: "pointer-events-none absolute",
@@ -5891,13 +6015,13 @@ function SectionPickerOverlay({
5891
6015
  "aria-hidden": true
5892
6016
  }
5893
6017
  ) : null,
5894
- isSelected ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
6018
+ isSelected ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
5895
6019
  "span",
5896
6020
  {
5897
6021
  className: "absolute right-3 top-3 flex size-8 items-center justify-center rounded-full text-white",
5898
6022
  style: { backgroundColor: "var(--ohw-primary, #0885fe)" },
5899
6023
  "aria-hidden": true,
5900
- children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react9.Check, { className: "size-5" })
6024
+ children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react10.Check, { className: "size-5" })
5901
6025
  }
5902
6026
  ) : null
5903
6027
  ]
@@ -6077,7 +6201,7 @@ function useLinkModalState({
6077
6201
  }
6078
6202
 
6079
6203
  // src/ui/link-modal/LinkPopover.tsx
6080
- var import_jsx_runtime20 = require("react/jsx-runtime");
6204
+ var import_jsx_runtime21 = require("react/jsx-runtime");
6081
6205
  function postToParent(data) {
6082
6206
  window.parent?.postMessage(data, "*");
6083
6207
  }
@@ -6173,15 +6297,15 @@ function LinkPopover({
6173
6297
  );
6174
6298
  };
6175
6299
  }, [open, sectionPickerActive]);
6176
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_jsx_runtime20.Fragment, { children: [
6177
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
6300
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
6301
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
6178
6302
  Dialog2,
6179
6303
  {
6180
6304
  open: open && !sectionPickerActive,
6181
6305
  onOpenChange: (next) => {
6182
6306
  if (!next) onClose?.();
6183
6307
  },
6184
- children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
6308
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
6185
6309
  DialogContent,
6186
6310
  {
6187
6311
  ref: panelRef,
@@ -6191,12 +6315,12 @@ function LinkPopover({
6191
6315
  "data-ohw-bridge": "",
6192
6316
  showCloseButton: false,
6193
6317
  className: "gap-0 p-0 w-full max-w-[448px] pointer-events-auto z-[2147483646] overflow-visible",
6194
- children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(LinkEditorPanel, { state, onClose })
6318
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(LinkEditorPanel, { state, onClose })
6195
6319
  }
6196
6320
  )
6197
6321
  }
6198
6322
  ),
6199
- sectionPickerActive && state.selectedPage ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
6323
+ sectionPickerActive && state.selectedPage ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
6200
6324
  SectionPickerOverlay,
6201
6325
  {
6202
6326
  pagePath: state.selectedPage.path,
@@ -6502,15 +6626,412 @@ function insertNavbarItem(href, label, afterAnchor = null) {
6502
6626
  };
6503
6627
  }
6504
6628
 
6629
+ // src/lib/footer-items.ts
6630
+ var FOOTER_ORDER_KEY = "__ohw_footer_order";
6631
+ var FOOTER_HREF_RE = /^footer-(\d+)-(\d+)-href$/;
6632
+ function parseFooterHrefKey(key) {
6633
+ if (!key) return null;
6634
+ const match = key.match(FOOTER_HREF_RE);
6635
+ if (!match) return null;
6636
+ return { col: parseInt(match[1], 10), item: parseInt(match[2], 10) };
6637
+ }
6638
+ function isFooterHrefKey(key) {
6639
+ return parseFooterHrefKey(key) !== null;
6640
+ }
6641
+ function getFooterRoot() {
6642
+ return document.querySelector('footer[data-ohw-section="footer"], footer');
6643
+ }
6644
+ function getFooterLinksContainer() {
6645
+ return document.querySelector("[data-ohw-footer-links]") ?? document.querySelector(".rb-footer-links");
6646
+ }
6647
+ function isFooterLinkAnchor(el) {
6648
+ if (!el.matches("[data-ohw-href-key]")) return false;
6649
+ if (!isFooterHrefKey(el.getAttribute("data-ohw-href-key"))) return false;
6650
+ return Boolean(el.querySelector('[data-ohw-editable="text"]'));
6651
+ }
6652
+ function listFooterColumns() {
6653
+ const root = getFooterLinksContainer() ?? getFooterRoot()?.querySelector(".rb-footer-links") ?? null;
6654
+ if (!root) return [];
6655
+ const explicit = Array.from(root.querySelectorAll(":scope > [data-ohw-footer-col]"));
6656
+ if (explicit.length > 0) return explicit;
6657
+ return Array.from(root.children).filter((el) => {
6658
+ if (!(el instanceof HTMLElement)) return false;
6659
+ return Array.from(el.querySelectorAll(":scope > [data-ohw-href-key]")).some(
6660
+ isFooterLinkAnchor
6661
+ );
6662
+ });
6663
+ }
6664
+ function listFooterLinksInColumn(column) {
6665
+ return Array.from(column.querySelectorAll(":scope > [data-ohw-href-key]")).filter(
6666
+ isFooterLinkAnchor
6667
+ );
6668
+ }
6669
+ function findFooterColumnForLink(anchor) {
6670
+ const columns = listFooterColumns();
6671
+ return columns.find((col) => col.contains(anchor)) ?? null;
6672
+ }
6673
+ function getFooterOrderFromDom() {
6674
+ return listFooterColumns().map(
6675
+ (col) => listFooterLinksInColumn(col).map((el) => el.getAttribute("data-ohw-href-key")).filter((key) => Boolean(key))
6676
+ );
6677
+ }
6678
+ function findFooterLinkByKey(hrefKey) {
6679
+ return document.querySelector(
6680
+ `footer [data-ohw-href-key="${CSS.escape(hrefKey)}"]`
6681
+ );
6682
+ }
6683
+ function syncFooterColumnIndices(columns) {
6684
+ columns.forEach((col, index) => {
6685
+ const next = String(index);
6686
+ if (col.getAttribute("data-ohw-footer-col") !== next) {
6687
+ col.setAttribute("data-ohw-footer-col", next);
6688
+ }
6689
+ });
6690
+ }
6691
+ function appendChildIfNeeded(parent, child) {
6692
+ if (parent.lastElementChild !== child) {
6693
+ parent.appendChild(child);
6694
+ }
6695
+ }
6696
+ function insertLinkBeforeNext(col, el, nextEl) {
6697
+ if (nextEl) {
6698
+ if (el.nextElementSibling !== nextEl) {
6699
+ col.insertBefore(el, nextEl);
6700
+ }
6701
+ return;
6702
+ }
6703
+ if (col.lastElementChild !== el) {
6704
+ col.appendChild(el);
6705
+ }
6706
+ }
6707
+ function applyFooterOrder(order) {
6708
+ const container = getFooterLinksContainer();
6709
+ if (!container) return;
6710
+ const columns = listFooterColumns();
6711
+ if (columns.length === 0) return;
6712
+ const currentOrder = getFooterOrderFromDom();
6713
+ if (ordersEqual(order, currentOrder)) {
6714
+ syncFooterColumnIndices(columns);
6715
+ return;
6716
+ }
6717
+ const colCount = Math.max(columns.length, order.length);
6718
+ const working = columns.slice(0, colCount);
6719
+ for (let i = 0; i < order.length && i < working.length; i++) {
6720
+ appendChildIfNeeded(container, working[i]);
6721
+ }
6722
+ for (let i = order.length; i < working.length; i++) {
6723
+ appendChildIfNeeded(container, working[i]);
6724
+ }
6725
+ const freshColumns = listFooterColumns();
6726
+ syncFooterColumnIndices(freshColumns);
6727
+ for (let c = 0; c < order.length; c++) {
6728
+ const col = freshColumns[c];
6729
+ if (!col) continue;
6730
+ const colOrder = order[c];
6731
+ for (let j = 0; j < colOrder.length; j++) {
6732
+ const hrefKey = colOrder[j];
6733
+ const el = findFooterLinkByKey(hrefKey);
6734
+ if (!el) continue;
6735
+ if (el.parentElement !== col) {
6736
+ col.appendChild(el);
6737
+ }
6738
+ const nextKey = colOrder[j + 1];
6739
+ const nextEl = nextKey ? findFooterLinkByKey(nextKey) : null;
6740
+ insertLinkBeforeNext(col, el, nextEl?.parentElement === col ? nextEl : null);
6741
+ }
6742
+ }
6743
+ }
6744
+ function ordersEqual(a, b) {
6745
+ if (a.length !== b.length) return false;
6746
+ for (let i = 0; i < a.length; i++) {
6747
+ const left = a[i];
6748
+ const right = b[i];
6749
+ if (left.length !== right.length) return false;
6750
+ for (let j = 0; j < left.length; j++) {
6751
+ if (left[j] !== right[j]) return false;
6752
+ }
6753
+ }
6754
+ return true;
6755
+ }
6756
+ function planFooterLinkMove(hrefKey, targetColIndex, insertIndex) {
6757
+ const order = getFooterOrderFromDom();
6758
+ if (targetColIndex < 0 || targetColIndex >= order.length) return null;
6759
+ let fromCol = -1;
6760
+ let fromIdx = -1;
6761
+ for (let c = 0; c < order.length; c++) {
6762
+ const idx = order[c].indexOf(hrefKey);
6763
+ if (idx >= 0) {
6764
+ fromCol = c;
6765
+ fromIdx = idx;
6766
+ break;
6767
+ }
6768
+ }
6769
+ if (fromCol < 0) return null;
6770
+ const next = order.map((col) => [...col]);
6771
+ next[fromCol].splice(fromIdx, 1);
6772
+ let adjusted = insertIndex;
6773
+ if (fromCol === targetColIndex && fromIdx < insertIndex) adjusted -= 1;
6774
+ adjusted = Math.max(0, Math.min(adjusted, next[targetColIndex].length));
6775
+ next[targetColIndex].splice(adjusted, 0, hrefKey);
6776
+ if (ordersEqual(next, order)) return null;
6777
+ return next;
6778
+ }
6779
+ function planFooterColumnMove(fromIndex, toIndex) {
6780
+ const order = getFooterOrderFromDom();
6781
+ const columns = listFooterColumns();
6782
+ if (fromIndex < 0 || fromIndex >= columns.length || toIndex < 0 || toIndex > columns.length) {
6783
+ return null;
6784
+ }
6785
+ if (toIndex === fromIndex || toIndex === fromIndex + 1) return null;
6786
+ const nextOrder = order.map((col) => [...col]);
6787
+ const [movedOrder] = nextOrder.splice(fromIndex, 1);
6788
+ if (!movedOrder) return null;
6789
+ let adjusted = toIndex;
6790
+ if (fromIndex < toIndex) adjusted -= 1;
6791
+ adjusted = Math.max(0, Math.min(adjusted, nextOrder.length));
6792
+ nextOrder.splice(adjusted, 0, movedOrder);
6793
+ if (ordersEqual(nextOrder, order)) return null;
6794
+ return nextOrder;
6795
+ }
6796
+ function parseFooterOrder(content) {
6797
+ const raw = content[FOOTER_ORDER_KEY];
6798
+ if (!raw) return null;
6799
+ try {
6800
+ const parsed = JSON.parse(raw);
6801
+ if (!Array.isArray(parsed)) return null;
6802
+ return parsed.filter((col) => Array.isArray(col)).map((col) => col.filter((key) => typeof key === "string" && key.length > 0));
6803
+ } catch {
6804
+ return null;
6805
+ }
6806
+ }
6807
+ function reconcileFooterOrderFromContent(content) {
6808
+ const order = parseFooterOrder(content);
6809
+ if (!order?.length) return;
6810
+ const current = getFooterOrderFromDom();
6811
+ if (ordersEqual(order, current)) {
6812
+ syncFooterColumnIndices(listFooterColumns());
6813
+ return;
6814
+ }
6815
+ applyFooterOrder(order);
6816
+ }
6817
+ function buildLinkDropSlots(column, columnIndex) {
6818
+ const links = listFooterLinksInColumn(column);
6819
+ const colRect = column.getBoundingClientRect();
6820
+ const slots = [];
6821
+ const barThickness = 3;
6822
+ if (links.length === 0) {
6823
+ slots.push({
6824
+ insertIndex: 0,
6825
+ columnIndex,
6826
+ left: colRect.left,
6827
+ top: colRect.top + colRect.height / 2 - barThickness / 2,
6828
+ width: colRect.width,
6829
+ height: barThickness,
6830
+ direction: "horizontal"
6831
+ });
6832
+ return slots;
6833
+ }
6834
+ for (let i = 0; i <= links.length; i++) {
6835
+ let top;
6836
+ if (i === 0) {
6837
+ top = links[0].getBoundingClientRect().top - barThickness / 2;
6838
+ } else if (i === links.length) {
6839
+ const last = links[links.length - 1].getBoundingClientRect();
6840
+ top = last.bottom - barThickness / 2;
6841
+ } else {
6842
+ const prev = links[i - 1].getBoundingClientRect();
6843
+ const next = links[i].getBoundingClientRect();
6844
+ top = (prev.bottom + next.top) / 2 - barThickness / 2;
6845
+ }
6846
+ const widthRef = i === 0 ? links[0].getBoundingClientRect() : i === links.length ? links[links.length - 1].getBoundingClientRect() : links[i].getBoundingClientRect();
6847
+ slots.push({
6848
+ insertIndex: i,
6849
+ columnIndex,
6850
+ left: widthRef.left,
6851
+ top,
6852
+ width: Math.max(widthRef.width, colRect.width * 0.8),
6853
+ height: barThickness,
6854
+ direction: "horizontal"
6855
+ });
6856
+ }
6857
+ return slots;
6858
+ }
6859
+ function buildColumnDropSlots() {
6860
+ const columns = listFooterColumns();
6861
+ const slots = [];
6862
+ const barThickness = 3;
6863
+ if (columns.length === 0) return slots;
6864
+ for (let i = 0; i <= columns.length; i++) {
6865
+ let left;
6866
+ let height;
6867
+ let top;
6868
+ if (i === 0) {
6869
+ const first = columns[0].getBoundingClientRect();
6870
+ left = first.left - barThickness / 2;
6871
+ top = first.top;
6872
+ height = first.height;
6873
+ } else if (i === columns.length) {
6874
+ const last = columns[columns.length - 1].getBoundingClientRect();
6875
+ left = last.right - barThickness / 2;
6876
+ top = last.top;
6877
+ height = last.height;
6878
+ } else {
6879
+ const prev = columns[i - 1].getBoundingClientRect();
6880
+ const next = columns[i].getBoundingClientRect();
6881
+ left = (prev.right + next.left) / 2 - barThickness / 2;
6882
+ top = Math.min(prev.top, next.top);
6883
+ height = Math.max(prev.bottom, next.bottom) - top;
6884
+ }
6885
+ slots.push({
6886
+ insertIndex: i,
6887
+ columnIndex: i,
6888
+ left,
6889
+ top,
6890
+ width: barThickness,
6891
+ height: Math.max(height, 24),
6892
+ direction: "vertical"
6893
+ });
6894
+ }
6895
+ return slots;
6896
+ }
6897
+ function hitTestLinkDropSlot(clientX, clientY, draggedHrefKey) {
6898
+ const columns = listFooterColumns();
6899
+ let best = null;
6900
+ for (let c = 0; c < columns.length; c++) {
6901
+ const col = columns[c];
6902
+ const colRect = col.getBoundingClientRect();
6903
+ const inColX = clientX >= colRect.left - 24 && clientX <= colRect.right + 24;
6904
+ if (!inColX) continue;
6905
+ const slots = buildLinkDropSlots(col, c).filter((slot) => {
6906
+ const links = listFooterLinksInColumn(col);
6907
+ const fromIdx = links.findIndex(
6908
+ (el) => el.getAttribute("data-ohw-href-key") === draggedHrefKey
6909
+ );
6910
+ if (fromIdx < 0) return true;
6911
+ if (c === findColumnIndexForKey(draggedHrefKey) && (slot.insertIndex === fromIdx || slot.insertIndex === fromIdx + 1)) {
6912
+ return true;
6913
+ }
6914
+ return true;
6915
+ });
6916
+ for (const slot of slots) {
6917
+ const cy = slot.top + slot.height / 2;
6918
+ const dist = Math.abs(clientY - cy) + (inColX ? 0 : 1e3);
6919
+ if (!best || dist < best.dist) best = { slot, dist };
6920
+ }
6921
+ }
6922
+ return best?.slot ?? null;
6923
+ }
6924
+ function findColumnIndexForKey(hrefKey) {
6925
+ const order = getFooterOrderFromDom();
6926
+ for (let c = 0; c < order.length; c++) {
6927
+ if (order[c].includes(hrefKey)) return c;
6928
+ }
6929
+ return -1;
6930
+ }
6931
+ function hitTestColumnDropSlot(clientX, _clientY) {
6932
+ const slots = buildColumnDropSlots();
6933
+ let best = null;
6934
+ for (const slot of slots) {
6935
+ const cx2 = slot.left + slot.width / 2;
6936
+ const dist = Math.abs(clientX - cx2);
6937
+ if (!best || dist < best.dist) best = { slot, dist };
6938
+ }
6939
+ return best?.slot ?? null;
6940
+ }
6941
+
6942
+ // src/lib/carousel.ts
6943
+ var import_react8 = require("react");
6944
+ var CAROUSEL_ATTR = "data-ohw-carousel";
6945
+ var CAROUSEL_VALUE_ATTR = "data-ohw-carousel-value";
6946
+ var CAROUSEL_SLIDE_ATTR = "data-ohw-carousel-slide";
6947
+ var CAROUSEL_EVENT = "ohw:carousel-change";
6948
+ function listCarouselKeys() {
6949
+ const keys = /* @__PURE__ */ new Set();
6950
+ document.querySelectorAll(`[${CAROUSEL_ATTR}]`).forEach((el) => {
6951
+ const key = el.getAttribute("data-ohw-key");
6952
+ if (key) keys.add(key);
6953
+ });
6954
+ return [...keys];
6955
+ }
6956
+ function containersForKey(key) {
6957
+ return Array.from(
6958
+ document.querySelectorAll(`[${CAROUSEL_ATTR}][data-ohw-key="${CSS.escape(key)}"]`)
6959
+ );
6960
+ }
6961
+ function isCarouselKey(key) {
6962
+ return containersForKey(key).length > 0;
6963
+ }
6964
+ function parseSlides(raw) {
6965
+ if (!raw) return [];
6966
+ try {
6967
+ const parsed = JSON.parse(raw);
6968
+ if (!Array.isArray(parsed)) return [];
6969
+ return parsed.filter((s) => Boolean(s) && typeof s === "object").map((s) => ({ src: String(s.src ?? ""), alt: String(s.alt ?? "") }));
6970
+ } catch {
6971
+ return [];
6972
+ }
6973
+ }
6974
+ function readCarouselValue(key) {
6975
+ const container = containersForKey(key)[0];
6976
+ if (!container) return [];
6977
+ const fromAttr = parseSlides(container.getAttribute(CAROUSEL_VALUE_ATTR));
6978
+ if (fromAttr.length > 0) return fromAttr;
6979
+ return Array.from(container.querySelectorAll(`[${CAROUSEL_SLIDE_ATTR}]`)).filter((slide) => slide.closest(`[${CAROUSEL_ATTR}]`) === container).sort((a, b) => slideIndex(a) - slideIndex(b)).map((slide) => {
6980
+ const img = slide instanceof HTMLImageElement ? slide : slide.querySelector("img");
6981
+ return { src: img?.src ?? "", alt: img?.alt ?? "" };
6982
+ });
6983
+ }
6984
+ function slideIndex(el) {
6985
+ const raw = el.getAttribute(CAROUSEL_SLIDE_ATTR);
6986
+ const n = raw ? parseInt(raw, 10) : NaN;
6987
+ return Number.isFinite(n) ? n : 0;
6988
+ }
6989
+ function applyCarouselValue(key, slides) {
6990
+ const value = JSON.stringify(slides);
6991
+ for (const container of containersForKey(key)) {
6992
+ if (container.getAttribute(CAROUSEL_VALUE_ATTR) === value) continue;
6993
+ container.setAttribute(CAROUSEL_VALUE_ATTR, value);
6994
+ container.dispatchEvent(
6995
+ new CustomEvent(CAROUSEL_EVENT, { detail: { images: slides } })
6996
+ );
6997
+ }
6998
+ }
6999
+ function applyCarouselNode(key, val) {
7000
+ if (!isCarouselKey(key)) return false;
7001
+ applyCarouselValue(key, parseSlides(val));
7002
+ return true;
7003
+ }
7004
+ function useOhwCarousel(key, initial) {
7005
+ const [images, setImages] = (0, import_react8.useState)(initial);
7006
+ (0, import_react8.useEffect)(() => {
7007
+ const el = document.querySelector(
7008
+ `[${CAROUSEL_ATTR}][data-ohw-key="${CSS.escape(key)}"]`
7009
+ );
7010
+ if (!el) return;
7011
+ const onChange = (e) => {
7012
+ const detail = e.detail;
7013
+ if (Array.isArray(detail?.images)) setImages(detail.images);
7014
+ };
7015
+ el.addEventListener(CAROUSEL_EVENT, onChange);
7016
+ return () => el.removeEventListener(CAROUSEL_EVENT, onChange);
7017
+ }, [key]);
7018
+ const bind = {
7019
+ [CAROUSEL_ATTR]: "",
7020
+ "data-ohw-key": key,
7021
+ [CAROUSEL_VALUE_ATTR]: JSON.stringify(images)
7022
+ };
7023
+ return { images, setImages, bind };
7024
+ }
7025
+
6505
7026
  // src/ui/navbar-container-chrome.tsx
6506
- var import_lucide_react10 = require("lucide-react");
6507
- var import_jsx_runtime21 = require("react/jsx-runtime");
7027
+ var import_lucide_react11 = require("lucide-react");
7028
+ var import_jsx_runtime22 = require("react/jsx-runtime");
6508
7029
  function NavbarContainerChrome({
6509
7030
  rect,
6510
7031
  onAdd
6511
7032
  }) {
6512
7033
  const chromeGap = 6;
6513
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7034
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
6514
7035
  "div",
6515
7036
  {
6516
7037
  "data-ohw-navbar-container-chrome": "",
@@ -6522,7 +7043,7 @@ function NavbarContainerChrome({
6522
7043
  width: rect.width + chromeGap * 2,
6523
7044
  height: rect.height + chromeGap * 2
6524
7045
  },
6525
- children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7046
+ children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
6526
7047
  "button",
6527
7048
  {
6528
7049
  type: "button",
@@ -6539,15 +7060,57 @@ function NavbarContainerChrome({
6539
7060
  e.stopPropagation();
6540
7061
  onAdd();
6541
7062
  },
6542
- children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react10.Plus, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
7063
+ children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react11.Plus, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
6543
7064
  }
6544
7065
  )
6545
7066
  }
6546
7067
  );
6547
7068
  }
6548
7069
 
7070
+ // src/ui/drop-indicator.tsx
7071
+ var React9 = __toESM(require("react"), 1);
7072
+ var import_jsx_runtime23 = require("react/jsx-runtime");
7073
+ var dropIndicatorVariants = cva(
7074
+ "ov-gap-line pointer-events-none shrink-0 transition-opacity duration-150",
7075
+ {
7076
+ variants: {
7077
+ direction: {
7078
+ vertical: "h-6 w-[3px]",
7079
+ horizontal: "h-[3px] w-[200px]"
7080
+ },
7081
+ state: {
7082
+ default: "opacity-0",
7083
+ hover: "opacity-100",
7084
+ dragIdle: "opacity-40",
7085
+ dragActive: "opacity-100"
7086
+ }
7087
+ },
7088
+ defaultVariants: {
7089
+ direction: "vertical",
7090
+ state: "default"
7091
+ }
7092
+ }
7093
+ );
7094
+ var DropIndicator = React9.forwardRef(
7095
+ ({ className, direction, state, ...props }, ref) => {
7096
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
7097
+ "div",
7098
+ {
7099
+ ref,
7100
+ "data-slot": "drop-indicator",
7101
+ "data-direction": direction ?? "vertical",
7102
+ "data-state": state ?? "default",
7103
+ "aria-hidden": "true",
7104
+ className: cn(dropIndicatorVariants({ direction, state }), className),
7105
+ ...props
7106
+ }
7107
+ );
7108
+ }
7109
+ );
7110
+ DropIndicator.displayName = "DropIndicator";
7111
+
6549
7112
  // src/ui/badge.tsx
6550
- var import_jsx_runtime22 = require("react/jsx-runtime");
7113
+ var import_jsx_runtime24 = require("react/jsx-runtime");
6551
7114
  var badgeVariants = cva(
6552
7115
  "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",
6553
7116
  {
@@ -6565,12 +7128,12 @@ var badgeVariants = cva(
6565
7128
  }
6566
7129
  );
6567
7130
  function Badge({ className, variant, ...props }) {
6568
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: cn(badgeVariants({ variant }), className), ...props });
7131
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: cn(badgeVariants({ variant }), className), ...props });
6569
7132
  }
6570
7133
 
6571
7134
  // src/OhhwellsBridge.tsx
6572
- var import_lucide_react11 = require("lucide-react");
6573
- var import_jsx_runtime23 = require("react/jsx-runtime");
7135
+ var import_lucide_react12 = require("lucide-react");
7136
+ var import_jsx_runtime25 = require("react/jsx-runtime");
6574
7137
  var PRIMARY2 = "#0885FE";
6575
7138
  var IMAGE_FADE_MS = 300;
6576
7139
  function runOpacityFade(el, onDone) {
@@ -6749,7 +7312,7 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
6749
7312
  const root = (0, import_client.createRoot)(container);
6750
7313
  (0, import_react_dom2.flushSync)(() => {
6751
7314
  root.render(
6752
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
7315
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
6753
7316
  SchedulingWidget,
6754
7317
  {
6755
7318
  notifyOnConnect,
@@ -6811,7 +7374,7 @@ function isDragHandleDisabled(el) {
6811
7374
  return raw === "true" || raw === "";
6812
7375
  });
6813
7376
  }
6814
- function collectEditableNodes() {
7377
+ function collectEditableNodes(extraContent) {
6815
7378
  const nodes = Array.from(document.querySelectorAll("[data-ohw-editable]")).map((el) => {
6816
7379
  if (el.dataset.ohwEditable === "image") {
6817
7380
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -6839,6 +7402,14 @@ function collectEditableNodes() {
6839
7402
  if (!key) return;
6840
7403
  nodes.push({ key, type: "link", text: getLinkHref2(el) });
6841
7404
  });
7405
+ if (extraContent) {
7406
+ for (const key of [NAV_ORDER_KEY, FOOTER_ORDER_KEY, NAV_COUNT_KEY]) {
7407
+ const text = extraContent[key];
7408
+ if (typeof text === "string" && text.length > 0) {
7409
+ nodes.push({ key, type: "meta", text });
7410
+ }
7411
+ }
7412
+ }
6842
7413
  document.querySelectorAll('[data-ohw-editable="video"]').forEach((el) => {
6843
7414
  const key = el.dataset.ohwKey ?? "";
6844
7415
  const video = getVideoEl(el);
@@ -6846,13 +7417,10 @@ function collectEditableNodes() {
6846
7417
  nodes.push({ key: `${key}${VIDEO_AUTOPLAY_SUFFIX}`, type: "video-setting", text: String(video.autoplay) });
6847
7418
  nodes.push({ key: `${key}${VIDEO_MUTED_SUFFIX}`, type: "video-setting", text: String(video.muted) });
6848
7419
  });
6849
- const seen = /* @__PURE__ */ new Set();
6850
- return nodes.filter((node) => {
6851
- if (!node.key) return true;
6852
- if (seen.has(node.key)) return false;
6853
- seen.add(node.key);
6854
- return true;
6855
- });
7420
+ for (const key of listCarouselKeys()) {
7421
+ nodes.push({ key, type: "carousel", text: JSON.stringify(readCarouselValue(key)) });
7422
+ }
7423
+ return nodes;
6856
7424
  }
6857
7425
  function isMediaEditable(el) {
6858
7426
  const t = el.dataset.ohwEditable;
@@ -6940,6 +7508,29 @@ function getHrefKeyFromElement(el) {
6940
7508
  if (!key) return null;
6941
7509
  return { anchor, key };
6942
7510
  }
7511
+ function disableNativeHrefDrag(el) {
7512
+ if (el.draggable) el.draggable = false;
7513
+ if (el.getAttribute("draggable") !== "false") {
7514
+ el.setAttribute("draggable", "false");
7515
+ }
7516
+ }
7517
+ function clearTextSelection() {
7518
+ const sel = window.getSelection();
7519
+ if (sel && !sel.isCollapsed) sel.removeAllRanges();
7520
+ }
7521
+ function armFooterPressDrag() {
7522
+ document.documentElement.setAttribute("data-ohw-footer-press-drag", "");
7523
+ }
7524
+ function lockFooterDuringDrag() {
7525
+ document.documentElement.removeAttribute("data-ohw-footer-press-drag");
7526
+ document.documentElement.setAttribute("data-ohw-item-dragging", "");
7527
+ clearTextSelection();
7528
+ }
7529
+ function unlockFooterDragInteraction() {
7530
+ document.documentElement.removeAttribute("data-ohw-footer-press-drag");
7531
+ document.documentElement.removeAttribute("data-ohw-item-dragging");
7532
+ clearTextSelection();
7533
+ }
6943
7534
  function isNavbarButton2(el) {
6944
7535
  return Boolean(el.closest('[data-ohw-role="navbar-button"]'));
6945
7536
  }
@@ -7002,7 +7593,7 @@ function isInferredFooterGroup(el) {
7002
7593
  return Array.from(el.querySelectorAll(":scope > [data-ohw-href-key]")).some(isNavigationItem);
7003
7594
  }
7004
7595
  function isNavigationContainer(el) {
7005
- return el.hasAttribute("data-ohw-nav-container") || isNavigationRoot(el) || isInferredFooterGroup(el);
7596
+ return el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-footer-col") || isNavigationRoot(el) || isInferredFooterGroup(el);
7006
7597
  }
7007
7598
  function isPointOverNavigation(x, y) {
7008
7599
  const navRoot = document.querySelector("[data-ohw-nav-root]") ?? document.querySelector("nav");
@@ -7073,6 +7664,22 @@ function placeCaretAtPoint(el, x, y) {
7073
7664
  }
7074
7665
  }
7075
7666
  }
7667
+ function selectAllTextInEditable(el) {
7668
+ const selection = window.getSelection();
7669
+ if (!selection) return;
7670
+ const range = document.createRange();
7671
+ range.selectNodeContents(el);
7672
+ selection.removeAllRanges();
7673
+ selection.addRange(range);
7674
+ }
7675
+ function getNavigationLabelEditable(target) {
7676
+ const editable = target.closest('[data-ohw-editable="text"]');
7677
+ if (!editable) return null;
7678
+ const hrefCtx = getHrefKeyFromElement(editable);
7679
+ const navAnchor = hrefCtx ? getNavigationItemAnchor(hrefCtx.anchor) : null;
7680
+ if (!navAnchor) return null;
7681
+ return { editable, navAnchor };
7682
+ }
7076
7683
  function collectSections() {
7077
7684
  return collectSectionsFromDom();
7078
7685
  }
@@ -7196,6 +7803,8 @@ var ICONS = {
7196
7803
  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"/>',
7197
7804
  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"/>'
7198
7805
  };
7806
+ var TOOLBAR_PILL_PADDING = 2;
7807
+ var TOOLBAR_TOGGLE_GAP = 4;
7199
7808
  var TOOLBAR_GROUPS = [
7200
7809
  [
7201
7810
  { cmd: "bold", title: "Bold" },
@@ -7220,7 +7829,7 @@ function EditGlowChrome({
7220
7829
  dragDisabled = false
7221
7830
  }) {
7222
7831
  const GAP = 6;
7223
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
7832
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
7224
7833
  "div",
7225
7834
  {
7226
7835
  ref: elRef,
@@ -7235,7 +7844,7 @@ function EditGlowChrome({
7235
7844
  zIndex: 2147483646
7236
7845
  },
7237
7846
  children: [
7238
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
7847
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
7239
7848
  "div",
7240
7849
  {
7241
7850
  style: {
@@ -7248,7 +7857,7 @@ function EditGlowChrome({
7248
7857
  }
7249
7858
  }
7250
7859
  ),
7251
- reorderHrefKey && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
7860
+ reorderHrefKey && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
7252
7861
  "div",
7253
7862
  {
7254
7863
  "data-ohw-drag-handle-container": "",
@@ -7260,7 +7869,7 @@ function EditGlowChrome({
7260
7869
  transform: "translate(calc(-100% - 7px), -50%)",
7261
7870
  pointerEvents: dragDisabled ? "none" : "auto"
7262
7871
  },
7263
- children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
7872
+ children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
7264
7873
  DragHandle,
7265
7874
  {
7266
7875
  "aria-label": `Reorder ${reorderHrefKey}`,
@@ -7364,7 +7973,7 @@ function FloatingToolbar({
7364
7973
  onEditLink
7365
7974
  }) {
7366
7975
  const { top, left, transform } = calcToolbarPos(rect, parentScroll, 330);
7367
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
7976
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
7368
7977
  "div",
7369
7978
  {
7370
7979
  ref: elRef,
@@ -7374,14 +7983,23 @@ function FloatingToolbar({
7374
7983
  left,
7375
7984
  transform,
7376
7985
  zIndex: 2147483647,
7986
+ background: "#fff",
7987
+ border: "1px solid #E7E5E4",
7988
+ borderRadius: 6,
7989
+ boxShadow: "0px 2px 4px -2px rgba(0,0,0,.1),0px 4px 6px -1px rgba(0,0,0,.1)",
7990
+ display: "flex",
7991
+ alignItems: "center",
7992
+ padding: TOOLBAR_PILL_PADDING,
7993
+ gap: TOOLBAR_TOGGLE_GAP,
7994
+ fontFamily: "sans-serif",
7377
7995
  pointerEvents: "auto"
7378
7996
  },
7379
- children: /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(CustomToolbar, { children: [
7380
- TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_react8.default.Fragment, { children: [
7381
- gi > 0 && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(CustomToolbarDivider, {}),
7997
+ children: /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(CustomToolbar, { children: [
7998
+ TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(import_react9.default.Fragment, { children: [
7999
+ gi > 0 && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(CustomToolbarDivider, {}),
7382
8000
  btns.map((btn) => {
7383
8001
  const isActive = activeCommands.has(btn.cmd);
7384
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8002
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
7385
8003
  CustomToolbarButton,
7386
8004
  {
7387
8005
  title: btn.title,
@@ -7390,7 +8008,7 @@ function FloatingToolbar({
7390
8008
  e.preventDefault();
7391
8009
  onCommand(btn.cmd);
7392
8010
  },
7393
- children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8011
+ children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
7394
8012
  "svg",
7395
8013
  {
7396
8014
  width: "16",
@@ -7411,7 +8029,7 @@ function FloatingToolbar({
7411
8029
  );
7412
8030
  })
7413
8031
  ] }, gi)),
7414
- showEditLink ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8032
+ showEditLink ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
7415
8033
  CustomToolbarButton,
7416
8034
  {
7417
8035
  type: "button",
@@ -7425,7 +8043,7 @@ function FloatingToolbar({
7425
8043
  e.preventDefault();
7426
8044
  e.stopPropagation();
7427
8045
  },
7428
- children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_lucide_react11.Link, { className: "size-4 shrink-0", "aria-hidden": true })
8046
+ children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_lucide_react12.Link, { className: "size-4 shrink-0", "aria-hidden": true })
7429
8047
  }
7430
8048
  ) : null
7431
8049
  ] })
@@ -7442,7 +8060,7 @@ function StateToggle({
7442
8060
  states,
7443
8061
  onStateChange
7444
8062
  }) {
7445
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8063
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
7446
8064
  ToggleGroup,
7447
8065
  {
7448
8066
  "data-ohw-state-toggle": "",
@@ -7456,7 +8074,7 @@ function StateToggle({
7456
8074
  left: rect.right - 8,
7457
8075
  transform: "translateX(-100%)"
7458
8076
  },
7459
- children: states.map((state) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
8077
+ children: states.map((state) => /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
7460
8078
  }
7461
8079
  );
7462
8080
  }
@@ -7483,8 +8101,8 @@ function OhhwellsBridge() {
7483
8101
  const router = (0, import_navigation2.useRouter)();
7484
8102
  const searchParams = (0, import_navigation2.useSearchParams)();
7485
8103
  const isEditMode = isEditSessionActive();
7486
- const [bridgeRoot, setBridgeRoot] = (0, import_react8.useState)(null);
7487
- (0, import_react8.useEffect)(() => {
8104
+ const [bridgeRoot, setBridgeRoot] = (0, import_react9.useState)(null);
8105
+ (0, import_react9.useEffect)(() => {
7488
8106
  const figtreeFontId = "ohw-figtree-font";
7489
8107
  if (!document.getElementById(figtreeFontId)) {
7490
8108
  const preconnect1 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.googleapis.com" });
@@ -7512,91 +8130,101 @@ function OhhwellsBridge() {
7512
8130
  const subdomainFromQuery = searchParams.get("subdomain");
7513
8131
  const subdomain = resolveSubdomain(subdomainFromQuery);
7514
8132
  useLinkHrefGuardian(pathname, subdomain, isEditMode);
7515
- const postToParent2 = (0, import_react8.useCallback)((data) => {
8133
+ const postToParent2 = (0, import_react9.useCallback)((data) => {
7516
8134
  if (typeof window !== "undefined" && window.parent !== window) {
7517
8135
  window.parent.postMessage(data, "*");
7518
8136
  }
7519
8137
  }, []);
7520
- const [fetchState, setFetchState] = (0, import_react8.useState)("idle");
7521
- const autoSaveTimers = (0, import_react8.useRef)(/* @__PURE__ */ new Map());
7522
- const activeElRef = (0, import_react8.useRef)(null);
7523
- const selectedElRef = (0, import_react8.useRef)(null);
7524
- const originalContentRef = (0, import_react8.useRef)(null);
7525
- const activeStateElRef = (0, import_react8.useRef)(null);
7526
- const parentScrollRef = (0, import_react8.useRef)(null);
7527
- const visibleViewportRef = (0, import_react8.useRef)(null);
7528
- const [dialogPortalContainer, setDialogPortalContainer] = (0, import_react8.useState)(null);
7529
- const attachVisibleViewport = (0, import_react8.useCallback)((node) => {
8138
+ const [fetchState, setFetchState] = (0, import_react9.useState)("idle");
8139
+ const autoSaveTimers = (0, import_react9.useRef)(/* @__PURE__ */ new Map());
8140
+ const activeElRef = (0, import_react9.useRef)(null);
8141
+ const selectedElRef = (0, import_react9.useRef)(null);
8142
+ const originalContentRef = (0, import_react9.useRef)(null);
8143
+ const activeStateElRef = (0, import_react9.useRef)(null);
8144
+ const parentScrollRef = (0, import_react9.useRef)(null);
8145
+ const visibleViewportRef = (0, import_react9.useRef)(null);
8146
+ const [dialogPortalContainer, setDialogPortalContainer] = (0, import_react9.useState)(null);
8147
+ const attachVisibleViewport = (0, import_react9.useCallback)((node) => {
7530
8148
  visibleViewportRef.current = node;
7531
8149
  setDialogPortalContainer(node);
7532
8150
  if (node) applyVisibleViewport(node, parentScrollRef.current);
7533
8151
  }, []);
7534
- const toolbarElRef = (0, import_react8.useRef)(null);
7535
- const glowElRef = (0, import_react8.useRef)(null);
7536
- const hoveredImageRef = (0, import_react8.useRef)(null);
7537
- const hoveredImageHasTextOverlapRef = (0, import_react8.useRef)(false);
7538
- const dragOverElRef = (0, import_react8.useRef)(null);
7539
- const [mediaHover, setMediaHover] = (0, import_react8.useState)(null);
7540
- const [uploadingRects, setUploadingRects] = (0, import_react8.useState)({});
7541
- const hoveredGapRef = (0, import_react8.useRef)(null);
7542
- const imageUnhoverTimerRef = (0, import_react8.useRef)(null);
7543
- const imageShowTimerRef = (0, import_react8.useRef)(null);
7544
- const editStylesRef = (0, import_react8.useRef)(null);
7545
- const activateRef = (0, import_react8.useRef)(() => {
8152
+ const toolbarElRef = (0, import_react9.useRef)(null);
8153
+ const glowElRef = (0, import_react9.useRef)(null);
8154
+ const hoveredImageRef = (0, import_react9.useRef)(null);
8155
+ const hoveredImageHasTextOverlapRef = (0, import_react9.useRef)(false);
8156
+ const dragOverElRef = (0, import_react9.useRef)(null);
8157
+ const [mediaHover, setMediaHover] = (0, import_react9.useState)(null);
8158
+ const [carouselHover, setCarouselHover] = (0, import_react9.useState)(null);
8159
+ const [uploadingRects, setUploadingRects] = (0, import_react9.useState)({});
8160
+ const pendingUploadRectRef = (0, import_react9.useRef)(null);
8161
+ const hoveredGapRef = (0, import_react9.useRef)(null);
8162
+ const imageUnhoverTimerRef = (0, import_react9.useRef)(null);
8163
+ const imageShowTimerRef = (0, import_react9.useRef)(null);
8164
+ const editStylesRef = (0, import_react9.useRef)(null);
8165
+ const activateRef = (0, import_react9.useRef)(() => {
7546
8166
  });
7547
- const deactivateRef = (0, import_react8.useRef)(() => {
8167
+ const deactivateRef = (0, import_react9.useRef)(() => {
7548
8168
  });
7549
- const selectRef = (0, import_react8.useRef)(() => {
8169
+ const selectRef = (0, import_react9.useRef)(() => {
7550
8170
  });
7551
- const selectFrameRef = (0, import_react8.useRef)(() => {
8171
+ const selectFrameRef = (0, import_react9.useRef)(() => {
7552
8172
  });
7553
- const deselectRef = (0, import_react8.useRef)(() => {
8173
+ const deselectRef = (0, import_react9.useRef)(() => {
7554
8174
  });
7555
- const reselectNavigationItemRef = (0, import_react8.useRef)(() => {
8175
+ const reselectNavigationItemRef = (0, import_react9.useRef)(() => {
7556
8176
  });
7557
- const commitNavigationTextEditRef = (0, import_react8.useRef)(() => {
8177
+ const commitNavigationTextEditRef = (0, import_react9.useRef)(() => {
7558
8178
  });
7559
- const refreshActiveCommandsRef = (0, import_react8.useRef)(() => {
8179
+ const refreshActiveCommandsRef = (0, import_react9.useRef)(() => {
7560
8180
  });
7561
- const postToParentRef = (0, import_react8.useRef)(postToParent2);
8181
+ const postToParentRef = (0, import_react9.useRef)(postToParent2);
7562
8182
  postToParentRef.current = postToParent2;
7563
- const sectionsLoadedRef = (0, import_react8.useRef)(false);
7564
- const pendingScheduleConfigRequests = (0, import_react8.useRef)([]);
7565
- const [toolbarRect, setToolbarRect] = (0, import_react8.useState)(null);
7566
- const [toolbarVariant, setToolbarVariant] = (0, import_react8.useState)("none");
7567
- const toolbarVariantRef = (0, import_react8.useRef)("none");
8183
+ const sectionsLoadedRef = (0, import_react9.useRef)(false);
8184
+ const pendingScheduleConfigRequests = (0, import_react9.useRef)([]);
8185
+ const [toolbarRect, setToolbarRect] = (0, import_react9.useState)(null);
8186
+ const [toolbarVariant, setToolbarVariant] = (0, import_react9.useState)("none");
8187
+ const toolbarVariantRef = (0, import_react9.useRef)("none");
7568
8188
  toolbarVariantRef.current = toolbarVariant;
7569
- const [reorderHrefKey, setReorderHrefKey] = (0, import_react8.useState)(null);
7570
- const [reorderDragDisabled, setReorderDragDisabled] = (0, import_react8.useState)(false);
7571
- const [toggleState, setToggleState] = (0, import_react8.useState)(null);
7572
- const [maxBadge, setMaxBadge] = (0, import_react8.useState)(null);
7573
- const [activeCommands, setActiveCommands] = (0, import_react8.useState)(/* @__PURE__ */ new Set());
7574
- const [sectionGap, setSectionGap] = (0, import_react8.useState)(null);
7575
- const [toolbarShowEditLink, setToolbarShowEditLink] = (0, import_react8.useState)(false);
7576
- const hoveredNavContainerRef = (0, import_react8.useRef)(null);
7577
- const [hoveredNavContainerRect, setHoveredNavContainerRect] = (0, import_react8.useState)(null);
7578
- const hoveredItemElRef = (0, import_react8.useRef)(null);
7579
- const [hoveredItemRect, setHoveredItemRect] = (0, import_react8.useState)(null);
7580
- const siblingHintElRef = (0, import_react8.useRef)(null);
7581
- const [siblingHintRect, setSiblingHintRect] = (0, import_react8.useState)(null);
7582
- const [isItemDragging, setIsItemDragging] = (0, import_react8.useState)(false);
7583
- const [linkPopover, setLinkPopover] = (0, import_react8.useState)(null);
7584
- const linkPopoverSessionRef = (0, import_react8.useRef)(null);
7585
- const addNavAfterAnchorRef = (0, import_react8.useRef)(null);
7586
- const editContentRef = (0, import_react8.useRef)({});
7587
- const [sitePages, setSitePages] = (0, import_react8.useState)([]);
7588
- const [sectionsByPath, setSectionsByPath] = (0, import_react8.useState)({});
7589
- const sectionsPrefetchGenRef = (0, import_react8.useRef)(0);
7590
- const setLinkPopoverRef = (0, import_react8.useRef)(setLinkPopover);
7591
- const linkPopoverPanelRef = (0, import_react8.useRef)(null);
7592
- const linkPopoverOpenRef = (0, import_react8.useRef)(false);
7593
- const linkPopoverGraceUntilRef = (0, import_react8.useRef)(0);
8189
+ const [reorderHrefKey, setReorderHrefKey] = (0, import_react9.useState)(null);
8190
+ const [reorderDragDisabled, setReorderDragDisabled] = (0, import_react9.useState)(false);
8191
+ const [toggleState, setToggleState] = (0, import_react9.useState)(null);
8192
+ const [maxBadge, setMaxBadge] = (0, import_react9.useState)(null);
8193
+ const [activeCommands, setActiveCommands] = (0, import_react9.useState)(/* @__PURE__ */ new Set());
8194
+ const [sectionGap, setSectionGap] = (0, import_react9.useState)(null);
8195
+ const [toolbarShowEditLink, setToolbarShowEditLink] = (0, import_react9.useState)(false);
8196
+ const hoveredNavContainerRef = (0, import_react9.useRef)(null);
8197
+ const [hoveredNavContainerRect, setHoveredNavContainerRect] = (0, import_react9.useState)(null);
8198
+ const hoveredItemElRef = (0, import_react9.useRef)(null);
8199
+ const [hoveredItemRect, setHoveredItemRect] = (0, import_react9.useState)(null);
8200
+ const siblingHintElRef = (0, import_react9.useRef)(null);
8201
+ const [siblingHintRect, setSiblingHintRect] = (0, import_react9.useState)(null);
8202
+ const [siblingHintRects, setSiblingHintRects] = (0, import_react9.useState)([]);
8203
+ const [isItemDragging, setIsItemDragging] = (0, import_react9.useState)(false);
8204
+ const [isFooterFrameSelection, setIsFooterFrameSelection] = (0, import_react9.useState)(false);
8205
+ const footerDragRef = (0, import_react9.useRef)(null);
8206
+ const [footerDropSlots, setFooterDropSlots] = (0, import_react9.useState)([]);
8207
+ const [activeFooterDropIndex, setActiveFooterDropIndex] = (0, import_react9.useState)(null);
8208
+ const [draggedItemRect, setDraggedItemRect] = (0, import_react9.useState)(null);
8209
+ const footerPointerDragRef = (0, import_react9.useRef)(null);
8210
+ const suppressNextClickRef = (0, import_react9.useRef)(false);
8211
+ const [linkPopover, setLinkPopover] = (0, import_react9.useState)(null);
8212
+ const linkPopoverSessionRef = (0, import_react9.useRef)(null);
8213
+ const addNavAfterAnchorRef = (0, import_react9.useRef)(null);
8214
+ const editContentRef = (0, import_react9.useRef)({});
8215
+ const [sitePages, setSitePages] = (0, import_react9.useState)([]);
8216
+ const [sectionsByPath, setSectionsByPath] = (0, import_react9.useState)({});
8217
+ const sectionsPrefetchGenRef = (0, import_react9.useRef)(0);
8218
+ const setLinkPopoverRef = (0, import_react9.useRef)(setLinkPopover);
8219
+ const linkPopoverPanelRef = (0, import_react9.useRef)(null);
8220
+ const linkPopoverOpenRef = (0, import_react9.useRef)(false);
8221
+ const linkPopoverGraceUntilRef = (0, import_react9.useRef)(0);
7594
8222
  setLinkPopoverRef.current = setLinkPopover;
7595
8223
  linkPopoverSessionRef.current = linkPopover;
7596
8224
  const bumpLinkPopoverGrace = () => {
7597
8225
  linkPopoverGraceUntilRef.current = Date.now() + 350;
7598
8226
  };
7599
- const runSectionsPrefetch = (0, import_react8.useCallback)((pages) => {
8227
+ const runSectionsPrefetch = (0, import_react9.useCallback)((pages) => {
7600
8228
  if (!isEditMode || shouldUseDevFixtures() || pages.length === 0) return;
7601
8229
  const gen = ++sectionsPrefetchGenRef.current;
7602
8230
  const paths = pages.map((p) => p.path);
@@ -7615,10 +8243,14 @@ function OhhwellsBridge() {
7615
8243
  );
7616
8244
  });
7617
8245
  }, [isEditMode, pathname]);
7618
- const runSectionsPrefetchRef = (0, import_react8.useRef)(runSectionsPrefetch);
8246
+ const runSectionsPrefetchRef = (0, import_react9.useRef)(runSectionsPrefetch);
7619
8247
  runSectionsPrefetchRef.current = runSectionsPrefetch;
7620
- (0, import_react8.useEffect)(() => {
7621
- if (!linkPopover) return;
8248
+ (0, import_react9.useEffect)(() => {
8249
+ if (!linkPopover) {
8250
+ document.documentElement.removeAttribute("data-ohw-link-popover-open");
8251
+ return;
8252
+ }
8253
+ document.documentElement.setAttribute("data-ohw-link-popover-open", "");
7622
8254
  if (hoveredImageRef.current) {
7623
8255
  hoveredImageRef.current = null;
7624
8256
  hoveredImageHasTextOverlapRef.current = false;
@@ -7645,14 +8277,10 @@ function OhhwellsBridge() {
7645
8277
  document.addEventListener("touchmove", preventBackgroundScroll, scrollOpts);
7646
8278
  postToParent2({ type: "ow:image-unhover" });
7647
8279
  return () => {
7648
- postToParent2({ type: "ow:link-modal-lock", locked: false });
7649
- html.style.overflow = prevHtmlOverflow;
7650
- body.style.overflow = prevBodyOverflow;
7651
- document.removeEventListener("wheel", preventBackgroundScroll, scrollOpts);
7652
- document.removeEventListener("touchmove", preventBackgroundScroll, scrollOpts);
8280
+ document.documentElement.removeAttribute("data-ohw-link-popover-open");
7653
8281
  };
7654
8282
  }, [linkPopover, postToParent2]);
7655
- (0, import_react8.useEffect)(() => {
8283
+ (0, import_react9.useEffect)(() => {
7656
8284
  if (!isEditMode) return;
7657
8285
  const useFixtures = shouldUseDevFixtures();
7658
8286
  if (useFixtures) {
@@ -7676,14 +8304,14 @@ function OhhwellsBridge() {
7676
8304
  if (!useFixtures) postToParent2({ type: "ow:request-site-pages" });
7677
8305
  return () => window.removeEventListener("message", onSitePages);
7678
8306
  }, [isEditMode, postToParent2]);
7679
- (0, import_react8.useEffect)(() => {
8307
+ (0, import_react9.useEffect)(() => {
7680
8308
  if (!isEditMode || shouldUseDevFixtures()) return;
7681
8309
  void loadAllSectionsManifest().then((manifest) => {
7682
8310
  if (Object.keys(manifest).length === 0) return;
7683
8311
  setSectionsByPath((prev) => ({ ...manifest, ...prev }));
7684
8312
  });
7685
8313
  }, [isEditMode]);
7686
- (0, import_react8.useEffect)(() => {
8314
+ (0, import_react9.useEffect)(() => {
7687
8315
  const update = () => {
7688
8316
  const el = activeElRef.current ?? selectedElRef.current;
7689
8317
  if (el) setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
@@ -7707,10 +8335,10 @@ function OhhwellsBridge() {
7707
8335
  vvp.removeEventListener("resize", update);
7708
8336
  };
7709
8337
  }, []);
7710
- const refreshStateRules = (0, import_react8.useCallback)(() => {
8338
+ const refreshStateRules = (0, import_react9.useCallback)(() => {
7711
8339
  editStylesRef.current?.forceHover && (editStylesRef.current.forceHover.textContent = collectStateRules());
7712
8340
  }, []);
7713
- const processConfigRequest = (0, import_react8.useCallback)((insertAfterVal) => {
8341
+ const processConfigRequest = (0, import_react9.useCallback)((insertAfterVal) => {
7714
8342
  const tracker = getSectionsTracker();
7715
8343
  let entries = [];
7716
8344
  try {
@@ -7733,7 +8361,7 @@ function OhhwellsBridge() {
7733
8361
  }
7734
8362
  window.postMessage({ type: "ow:schedule-config", insertAfter: insertAfterVal, scheduleId: null }, "*");
7735
8363
  }, [isEditMode]);
7736
- const deactivate = (0, import_react8.useCallback)(() => {
8364
+ const deactivate = (0, import_react9.useCallback)(() => {
7737
8365
  const el = activeElRef.current;
7738
8366
  if (!el) return;
7739
8367
  const key = el.dataset.ohwKey;
@@ -7764,12 +8392,14 @@ function OhhwellsBridge() {
7764
8392
  setToolbarShowEditLink(false);
7765
8393
  postToParent2({ type: "ow:exit-edit" });
7766
8394
  }, [postToParent2]);
7767
- const deselect = (0, import_react8.useCallback)(() => {
8395
+ const deselect = (0, import_react9.useCallback)(() => {
7768
8396
  selectedElRef.current = null;
7769
8397
  setReorderHrefKey(null);
7770
8398
  setReorderDragDisabled(false);
8399
+ setIsFooterFrameSelection(false);
7771
8400
  siblingHintElRef.current = null;
7772
8401
  setSiblingHintRect(null);
8402
+ setSiblingHintRects([]);
7773
8403
  setIsItemDragging(false);
7774
8404
  hoveredNavContainerRef.current = null;
7775
8405
  setHoveredNavContainerRect(null);
@@ -7778,7 +8408,7 @@ function OhhwellsBridge() {
7778
8408
  setToolbarVariant("none");
7779
8409
  }
7780
8410
  }, []);
7781
- const reselectNavigationItem = (0, import_react8.useCallback)((navAnchor) => {
8411
+ const reselectNavigationItem = (0, import_react9.useCallback)((navAnchor) => {
7782
8412
  selectedElRef.current = navAnchor;
7783
8413
  const { key, disabled } = getNavigationItemReorderState(navAnchor);
7784
8414
  setReorderHrefKey(key);
@@ -7788,7 +8418,7 @@ function OhhwellsBridge() {
7788
8418
  setToolbarShowEditLink(false);
7789
8419
  setActiveCommands(/* @__PURE__ */ new Set());
7790
8420
  }, []);
7791
- const commitNavigationTextEdit = (0, import_react8.useCallback)((navAnchor) => {
8421
+ const commitNavigationTextEdit = (0, import_react9.useCallback)((navAnchor) => {
7792
8422
  const el = activeElRef.current;
7793
8423
  if (!el) return;
7794
8424
  const key = el.dataset.ohwKey;
@@ -7814,7 +8444,7 @@ function OhhwellsBridge() {
7814
8444
  postToParent2({ type: "ow:exit-edit" });
7815
8445
  reselectNavigationItem(navAnchor);
7816
8446
  }, [postToParent2, reselectNavigationItem]);
7817
- const handleAddTopLevelNavItem = (0, import_react8.useCallback)(() => {
8447
+ const handleAddTopLevelNavItem = (0, import_react9.useCallback)(() => {
7818
8448
  const items = listNavbarItems();
7819
8449
  addNavAfterAnchorRef.current = items[items.length - 1] ?? null;
7820
8450
  deselectRef.current();
@@ -7826,96 +8456,393 @@ function OhhwellsBridge() {
7826
8456
  intent: "add-nav"
7827
8457
  });
7828
8458
  }, []);
7829
- const handleItemDragStart = (0, import_react8.useCallback)(() => {
7830
- siblingHintElRef.current = null;
7831
- setSiblingHintRect(null);
7832
- setIsItemDragging(true);
7833
- }, []);
7834
- const handleItemDragEnd = (0, import_react8.useCallback)(() => {
8459
+ const clearFooterDragVisuals = (0, import_react9.useCallback)(() => {
8460
+ footerDragRef.current = null;
8461
+ setSiblingHintRects([]);
8462
+ setFooterDropSlots([]);
8463
+ setActiveFooterDropIndex(null);
8464
+ setDraggedItemRect(null);
7835
8465
  setIsItemDragging(false);
8466
+ unlockFooterDragInteraction();
7836
8467
  }, []);
7837
- reselectNavigationItemRef.current = reselectNavigationItem;
7838
- commitNavigationTextEditRef.current = commitNavigationTextEdit;
7839
- const select = (0, import_react8.useCallback)((anchor) => {
7840
- if (!isNavigationItem(anchor)) return;
7841
- if (activeElRef.current) deactivate();
7842
- selectedElRef.current = anchor;
7843
- clearHrefKeyHover(anchor);
7844
- hoveredNavContainerRef.current = null;
7845
- setHoveredNavContainerRect(null);
7846
- setHoveredItemRect(null);
7847
- hoveredItemElRef.current = null;
7848
- siblingHintElRef.current = null;
7849
- setSiblingHintRect(null);
7850
- setIsItemDragging(false);
7851
- const { key, disabled } = getNavigationItemReorderState(anchor);
7852
- setReorderHrefKey(key);
7853
- setReorderDragDisabled(disabled);
7854
- setToolbarVariant("link-action");
7855
- setToolbarRect(anchor.getBoundingClientRect());
7856
- setToolbarShowEditLink(false);
7857
- setActiveCommands(/* @__PURE__ */ new Set());
7858
- }, [deactivate]);
7859
- const selectFrame = (0, import_react8.useCallback)((el) => {
7860
- if (!isNavigationContainer(el)) return;
7861
- if (activeElRef.current) deactivate();
7862
- selectedElRef.current = el;
7863
- clearHrefKeyHover(el);
7864
- hoveredNavContainerRef.current = null;
7865
- setHoveredNavContainerRect(null);
7866
- setHoveredItemRect(null);
7867
- hoveredItemElRef.current = null;
7868
- siblingHintElRef.current = null;
7869
- setSiblingHintRect(null);
7870
- setIsItemDragging(false);
7871
- setReorderHrefKey(null);
7872
- setReorderDragDisabled(false);
7873
- setToolbarVariant("select-frame");
7874
- setToolbarRect(el.getBoundingClientRect());
7875
- setToolbarShowEditLink(false);
7876
- setActiveCommands(/* @__PURE__ */ new Set());
7877
- }, [deactivate]);
7878
- const activate = (0, import_react8.useCallback)((el, options) => {
7879
- if (activeElRef.current === el) return;
7880
- selectedElRef.current = null;
7881
- deactivate();
7882
- if (hoveredImageRef.current) {
7883
- hoveredImageRef.current = null;
7884
- setMediaHover(null);
8468
+ const refreshFooterDragVisuals = (0, import_react9.useCallback)((session, activeSlot, clientX, clientY) => {
8469
+ const dragged = session.draggedEl;
8470
+ setDraggedItemRect(dragged.getBoundingClientRect());
8471
+ if (typeof clientX === "number" && typeof clientY === "number") {
8472
+ session.lastClientX = clientX;
8473
+ session.lastClientY = clientY;
7885
8474
  }
7886
- setToolbarVariant("rich-text");
7887
- siblingHintElRef.current = null;
7888
- setSiblingHintRect(null);
7889
- setIsItemDragging(false);
7890
- el.setAttribute("contenteditable", "true");
7891
- el.removeAttribute("data-ohw-hovered");
7892
- el.closest("[data-ohw-href-key]")?.removeAttribute("data-ohw-hovered");
7893
- activeElRef.current = el;
7894
- originalContentRef.current = el.innerHTML;
7895
- el.focus();
7896
- if (options?.caretX !== void 0 && options?.caretY !== void 0) {
7897
- placeCaretAtPoint(el, options.caretX, options.caretY);
8475
+ session.activeSlot = activeSlot;
8476
+ if (session.kind === "link") {
8477
+ const columns2 = listFooterColumns();
8478
+ const focusCols = /* @__PURE__ */ new Set([session.sourceColumnIndex]);
8479
+ if (activeSlot) focusCols.add(activeSlot.columnIndex);
8480
+ const siblingRects = [];
8481
+ columns2.forEach((col, i) => {
8482
+ if (!focusCols.has(i)) return;
8483
+ for (const link of listFooterLinksInColumn(col)) {
8484
+ if (link === dragged) continue;
8485
+ siblingRects.push(link.getBoundingClientRect());
8486
+ }
8487
+ });
8488
+ setSiblingHintRects(siblingRects);
8489
+ const slots2 = [];
8490
+ columns2.forEach((col, i) => {
8491
+ slots2.push(...buildLinkDropSlots(col, i));
8492
+ });
8493
+ setFooterDropSlots(slots2);
8494
+ const activeIdx2 = activeSlot ? slots2.findIndex((s) => s.columnIndex === activeSlot.columnIndex && s.insertIndex === activeSlot.insertIndex) : -1;
8495
+ setActiveFooterDropIndex(activeIdx2 >= 0 ? activeIdx2 : null);
8496
+ return;
7898
8497
  }
7899
- setToolbarShowEditLink(Boolean(getHrefKeyFromElement(el)));
7900
- const navAnchor = getNavigationItemAnchor(el);
7901
- if (navAnchor) {
8498
+ const columns = listFooterColumns();
8499
+ setSiblingHintRects(columns.filter((col) => col !== dragged).map((col) => col.getBoundingClientRect()));
8500
+ const slots = buildColumnDropSlots();
8501
+ setFooterDropSlots(slots);
8502
+ const activeIdx = activeSlot ? slots.findIndex((s) => s.insertIndex === activeSlot.insertIndex) : -1;
8503
+ setActiveFooterDropIndex(activeIdx >= 0 ? activeIdx : null);
8504
+ }, []);
8505
+ const refreshFooterDragVisualsRef = (0, import_react9.useRef)(refreshFooterDragVisuals);
8506
+ refreshFooterDragVisualsRef.current = refreshFooterDragVisuals;
8507
+ const commitFooterDragRef = (0, import_react9.useRef)(() => {
8508
+ });
8509
+ const beginFooterDragRef = (0, import_react9.useRef)(() => {
8510
+ });
8511
+ const beginFooterDrag = (0, import_react9.useCallback)(
8512
+ (session) => {
8513
+ const rect = session.draggedEl.getBoundingClientRect();
8514
+ session.lastClientX = session.lastClientX || rect.left + rect.width / 2;
8515
+ session.lastClientY = session.lastClientY || rect.top + rect.height / 2;
8516
+ session.activeSlot = session.activeSlot ?? null;
8517
+ footerDragRef.current = session;
8518
+ setIsItemDragging(true);
8519
+ lockFooterDuringDrag();
8520
+ siblingHintElRef.current = null;
8521
+ setSiblingHintRect(null);
8522
+ if (session.wasSelected && selectedElRef.current === session.draggedEl) {
8523
+ setToolbarRect(rect);
8524
+ }
8525
+ const initialSlot = session.kind === "link" && session.hrefKey ? hitTestLinkDropSlot(session.lastClientX, session.lastClientY, session.hrefKey) : hitTestColumnDropSlot(session.lastClientX, session.lastClientY);
8526
+ refreshFooterDragVisuals(session, initialSlot, session.lastClientX, session.lastClientY);
8527
+ },
8528
+ [refreshFooterDragVisuals]
8529
+ );
8530
+ beginFooterDragRef.current = beginFooterDrag;
8531
+ const commitFooterDrag = (0, import_react9.useCallback)(
8532
+ (clientX, clientY) => {
8533
+ const session = footerDragRef.current;
8534
+ if (!session) {
8535
+ clearFooterDragVisuals();
8536
+ return;
8537
+ }
8538
+ const x = typeof clientX === "number" && (clientX !== 0 || clientY !== 0) ? clientX : session.lastClientX;
8539
+ const y = typeof clientY === "number" && (clientX !== 0 || clientY !== 0) ? clientY : session.lastClientY;
8540
+ let nextOrder = null;
8541
+ const slot = session.activeSlot ?? (session.kind === "link" && session.hrefKey ? hitTestLinkDropSlot(x, y, session.hrefKey) : session.kind === "column" ? hitTestColumnDropSlot(x, y) : null);
8542
+ if (session.kind === "link" && session.hrefKey && slot) {
8543
+ nextOrder = planFooterLinkMove(session.hrefKey, slot.columnIndex, slot.insertIndex);
8544
+ } else if (session.kind === "column" && slot) {
8545
+ nextOrder = planFooterColumnMove(session.sourceColumnIndex, slot.insertIndex);
8546
+ }
8547
+ const wasSelected = session.wasSelected;
8548
+ const draggedEl = session.draggedEl;
8549
+ const hrefKey = session.hrefKey;
8550
+ let movedColumnIndex = null;
8551
+ if (session.kind === "column" && slot && nextOrder) {
8552
+ let adjusted = slot.insertIndex;
8553
+ if (session.sourceColumnIndex < slot.insertIndex) adjusted -= 1;
8554
+ movedColumnIndex = Math.max(0, Math.min(adjusted, nextOrder.length - 1));
8555
+ }
8556
+ clearFooterDragVisuals();
8557
+ const applySelectionAfterDrop = () => {
8558
+ if (!wasSelected) {
8559
+ deselectRef.current();
8560
+ return;
8561
+ }
8562
+ if (hrefKey) {
8563
+ const link = document.querySelector(
8564
+ `footer [data-ohw-href-key="${CSS.escape(hrefKey)}"]`
8565
+ );
8566
+ if (link && isNavigationItem(link)) {
8567
+ selectRef.current(link);
8568
+ return;
8569
+ }
8570
+ }
8571
+ if (movedColumnIndex !== null && nextOrder) {
8572
+ const colKeys = nextOrder[movedColumnIndex] ?? [];
8573
+ let column = null;
8574
+ for (const key of colKeys) {
8575
+ const link = document.querySelector(
8576
+ `footer [data-ohw-href-key="${CSS.escape(key)}"]`
8577
+ );
8578
+ if (link) {
8579
+ column = findFooterColumnForLink(link);
8580
+ if (column) break;
8581
+ }
8582
+ }
8583
+ if (!column) column = listFooterColumns()[movedColumnIndex] ?? null;
8584
+ if (column && isNavigationContainer(column)) {
8585
+ selectFrameRef.current(column);
8586
+ return;
8587
+ }
8588
+ }
8589
+ if (document.body.contains(draggedEl)) {
8590
+ if (isNavigationItem(draggedEl)) {
8591
+ selectRef.current(draggedEl);
8592
+ return;
8593
+ }
8594
+ if (isNavigationContainer(draggedEl)) {
8595
+ selectFrameRef.current(draggedEl);
8596
+ return;
8597
+ }
8598
+ }
8599
+ deselectRef.current();
8600
+ };
8601
+ if (!wasSelected) {
8602
+ deselectRef.current();
8603
+ }
8604
+ if (nextOrder) {
8605
+ const orderJson = JSON.stringify(nextOrder);
8606
+ editContentRef.current = {
8607
+ ...editContentRef.current,
8608
+ [FOOTER_ORDER_KEY]: orderJson
8609
+ };
8610
+ applyFooterOrder(nextOrder);
8611
+ document.querySelectorAll("footer [data-ohw-href-key]").forEach(disableNativeHrefDrag);
8612
+ postToParentRef.current({
8613
+ type: "ow:change",
8614
+ nodes: [{ key: FOOTER_ORDER_KEY, text: orderJson }]
8615
+ });
8616
+ requestAnimationFrame(() => {
8617
+ if (editContentRef.current[FOOTER_ORDER_KEY] === orderJson) {
8618
+ applyFooterOrder(nextOrder);
8619
+ document.querySelectorAll("footer [data-ohw-href-key]").forEach(disableNativeHrefDrag);
8620
+ }
8621
+ requestAnimationFrame(applySelectionAfterDrop);
8622
+ });
8623
+ return;
8624
+ }
8625
+ applySelectionAfterDrop();
8626
+ },
8627
+ [clearFooterDragVisuals]
8628
+ );
8629
+ commitFooterDragRef.current = commitFooterDrag;
8630
+ const startFooterLinkDrag = (0, import_react9.useCallback)(
8631
+ (anchor, clientX, clientY, wasSelected) => {
8632
+ const hrefKey = anchor.getAttribute("data-ohw-href-key");
8633
+ if (!hrefKey || !isFooterHrefKey(hrefKey)) return false;
8634
+ const column = findFooterColumnForLink(anchor);
8635
+ const columns = listFooterColumns();
8636
+ beginFooterDrag({
8637
+ kind: "link",
8638
+ hrefKey,
8639
+ columnEl: column,
8640
+ sourceColumnIndex: column ? columns.indexOf(column) : 0,
8641
+ wasSelected,
8642
+ draggedEl: anchor,
8643
+ lastClientX: clientX,
8644
+ lastClientY: clientY,
8645
+ activeSlot: null
8646
+ });
8647
+ return true;
8648
+ },
8649
+ [beginFooterDrag]
8650
+ );
8651
+ const startFooterColumnDrag = (0, import_react9.useCallback)(
8652
+ (columnEl, clientX, clientY, wasSelected) => {
8653
+ const columns = listFooterColumns();
8654
+ const idx = columns.indexOf(columnEl);
8655
+ if (idx < 0) return false;
8656
+ beginFooterDrag({
8657
+ kind: "column",
8658
+ hrefKey: null,
8659
+ columnEl,
8660
+ sourceColumnIndex: idx,
8661
+ wasSelected,
8662
+ draggedEl: columnEl,
8663
+ lastClientX: clientX,
8664
+ lastClientY: clientY,
8665
+ activeSlot: null
8666
+ });
8667
+ return true;
8668
+ },
8669
+ [beginFooterDrag]
8670
+ );
8671
+ const handleItemDragStart = (0, import_react9.useCallback)(
8672
+ (e) => {
8673
+ const selected = selectedElRef.current;
8674
+ if (!selected) {
8675
+ setIsItemDragging(true);
8676
+ return;
8677
+ }
8678
+ const clientX = e?.clientX ?? selected.getBoundingClientRect().left + 8;
8679
+ const clientY = e?.clientY ?? selected.getBoundingClientRect().top + 8;
8680
+ if (startFooterLinkDrag(selected, clientX, clientY, true)) return;
8681
+ if (selected.hasAttribute("data-ohw-footer-col") || isInferredFooterGroup(selected) && selected.closest("footer")) {
8682
+ if (startFooterColumnDrag(selected, clientX, clientY, true)) return;
8683
+ }
8684
+ siblingHintElRef.current = null;
8685
+ setSiblingHintRect(null);
8686
+ setIsItemDragging(true);
8687
+ },
8688
+ [startFooterColumnDrag, startFooterLinkDrag]
8689
+ );
8690
+ const handleItemDragEnd = (0, import_react9.useCallback)(
8691
+ (e) => {
8692
+ if (footerDragRef.current) {
8693
+ const x = e?.clientX;
8694
+ const y = e?.clientY;
8695
+ if (typeof x === "number" && typeof y === "number" && (x !== 0 || y !== 0)) {
8696
+ commitFooterDrag(x, y);
8697
+ } else {
8698
+ commitFooterDrag();
8699
+ }
8700
+ return;
8701
+ }
8702
+ setIsItemDragging(false);
8703
+ },
8704
+ [commitFooterDrag]
8705
+ );
8706
+ const handleItemChromePointerDown = (0, import_react9.useCallback)((e) => {
8707
+ if (e.button !== 0) return;
8708
+ const selected = selectedElRef.current;
8709
+ if (!selected) return;
8710
+ armFooterPressDrag();
8711
+ const hrefKey = selected.getAttribute("data-ohw-href-key");
8712
+ if (hrefKey && isFooterHrefKey(hrefKey)) {
8713
+ footerPointerDragRef.current = {
8714
+ el: selected,
8715
+ kind: "link",
8716
+ startX: e.clientX,
8717
+ startY: e.clientY,
8718
+ pointerId: e.pointerId,
8719
+ wasSelected: true,
8720
+ started: false
8721
+ };
8722
+ return;
8723
+ }
8724
+ if (selected.hasAttribute("data-ohw-footer-col") || isInferredFooterGroup(selected) && selected.closest("footer")) {
8725
+ footerPointerDragRef.current = {
8726
+ el: selected,
8727
+ kind: "column",
8728
+ startX: e.clientX,
8729
+ startY: e.clientY,
8730
+ pointerId: e.pointerId,
8731
+ wasSelected: true,
8732
+ started: false
8733
+ };
8734
+ }
8735
+ }, []);
8736
+ const handleItemChromeClick = (0, import_react9.useCallback)((clientX, clientY) => {
8737
+ if (suppressNextClickRef.current) {
8738
+ suppressNextClickRef.current = false;
8739
+ return;
8740
+ }
8741
+ const selected = selectedElRef.current;
8742
+ if (!selected || !isNavigationItem(selected)) return;
8743
+ const editable = selected.querySelector('[data-ohw-editable="text"]') ?? selected.querySelector("[data-ohw-editable]");
8744
+ if (!editable) return;
8745
+ activateRef.current(editable, { caretX: clientX, caretY: clientY });
8746
+ }, []);
8747
+ reselectNavigationItemRef.current = reselectNavigationItem;
8748
+ commitNavigationTextEditRef.current = commitNavigationTextEdit;
8749
+ const select = (0, import_react9.useCallback)(
8750
+ (anchor) => {
8751
+ if (!isNavigationItem(anchor)) return;
8752
+ if (activeElRef.current) deactivate();
8753
+ selectedElRef.current = anchor;
8754
+ clearHrefKeyHover(anchor);
8755
+ hoveredNavContainerRef.current = null;
8756
+ setHoveredNavContainerRect(null);
8757
+ setHoveredItemRect(null);
8758
+ hoveredItemElRef.current = null;
8759
+ siblingHintElRef.current = null;
8760
+ setSiblingHintRect(null);
8761
+ setSiblingHintRects([]);
8762
+ setIsItemDragging(false);
8763
+ const { key, disabled } = getNavigationItemReorderState(anchor);
8764
+ setReorderHrefKey(key);
8765
+ setReorderDragDisabled(disabled);
8766
+ setIsFooterFrameSelection(false);
8767
+ setToolbarVariant("link-action");
8768
+ setToolbarRect(anchor.getBoundingClientRect());
8769
+ setToolbarShowEditLink(false);
8770
+ setActiveCommands(/* @__PURE__ */ new Set());
8771
+ },
8772
+ [deactivate]
8773
+ );
8774
+ const selectFrame = (0, import_react9.useCallback)(
8775
+ (el) => {
8776
+ if (!isNavigationContainer(el)) return;
8777
+ if (activeElRef.current) deactivate();
8778
+ const isFooterColumn = el.hasAttribute("data-ohw-footer-col") || Boolean(el.closest("footer") && isInferredFooterGroup(el));
8779
+ selectedElRef.current = el;
8780
+ clearHrefKeyHover(el);
8781
+ hoveredNavContainerRef.current = null;
8782
+ setHoveredNavContainerRect(null);
8783
+ setHoveredItemRect(null);
8784
+ hoveredItemElRef.current = null;
8785
+ siblingHintElRef.current = null;
8786
+ setSiblingHintRect(null);
8787
+ setSiblingHintRects(
8788
+ isFooterColumn ? listFooterColumns().filter((column) => column !== el).map((column) => column.getBoundingClientRect()) : []
8789
+ );
8790
+ setIsItemDragging(false);
7902
8791
  setReorderHrefKey(null);
7903
8792
  setReorderDragDisabled(false);
7904
- } else {
7905
- const { key: reorderKey, disabled: reorderDisabled } = getReorderHandleState(el);
7906
- setReorderHrefKey(reorderKey);
7907
- setReorderDragDisabled(reorderDisabled);
7908
- }
7909
- setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
7910
- postToParent2({ type: "ow:enter-edit", key: el.dataset.ohwKey });
7911
- requestAnimationFrame(() => refreshActiveCommandsRef.current());
7912
- }, [deactivate, postToParent2]);
8793
+ setIsFooterFrameSelection(isFooterColumn);
8794
+ setToolbarVariant("select-frame");
8795
+ setToolbarRect(el.getBoundingClientRect());
8796
+ setToolbarShowEditLink(false);
8797
+ setActiveCommands(/* @__PURE__ */ new Set());
8798
+ },
8799
+ [deactivate]
8800
+ );
8801
+ const activate = (0, import_react9.useCallback)(
8802
+ (el, options) => {
8803
+ if (activeElRef.current === el) return;
8804
+ selectedElRef.current = null;
8805
+ deactivate();
8806
+ if (hoveredImageRef.current) {
8807
+ hoveredImageRef.current = null;
8808
+ postToParentRef.current({ type: "ow:image-unhover" });
8809
+ }
8810
+ setToolbarVariant("rich-text");
8811
+ siblingHintElRef.current = null;
8812
+ setSiblingHintRect(null);
8813
+ setSiblingHintRects([]);
8814
+ setIsItemDragging(false);
8815
+ el.setAttribute("contenteditable", "true");
8816
+ el.removeAttribute("data-ohw-hovered");
8817
+ el.closest("[data-ohw-href-key]")?.removeAttribute("data-ohw-hovered");
8818
+ activeElRef.current = el;
8819
+ originalContentRef.current = el.innerHTML;
8820
+ el.focus();
8821
+ if (options?.caretX !== void 0 && options?.caretY !== void 0) {
8822
+ placeCaretAtPoint(el, options.caretX, options.caretY);
8823
+ }
8824
+ setToolbarShowEditLink(Boolean(getHrefKeyFromElement(el)));
8825
+ const navAnchor = getNavigationItemAnchor(el);
8826
+ if (navAnchor) {
8827
+ setReorderHrefKey(null);
8828
+ setReorderDragDisabled(false);
8829
+ } else {
8830
+ const { key: reorderKey, disabled: reorderDisabled } = getReorderHandleState(el);
8831
+ setReorderHrefKey(reorderKey);
8832
+ setReorderDragDisabled(reorderDisabled);
8833
+ }
8834
+ setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
8835
+ postToParent2({ type: "ow:enter-edit", key: el.dataset.ohwKey });
8836
+ requestAnimationFrame(() => refreshActiveCommandsRef.current());
8837
+ },
8838
+ [deactivate, postToParent2]
8839
+ );
7913
8840
  activateRef.current = activate;
7914
8841
  deactivateRef.current = deactivate;
7915
8842
  selectRef.current = select;
7916
8843
  selectFrameRef.current = selectFrame;
7917
8844
  deselectRef.current = deselect;
7918
- (0, import_react8.useLayoutEffect)(() => {
8845
+ (0, import_react9.useLayoutEffect)(() => {
7919
8846
  if (!subdomain || isEditMode) {
7920
8847
  setFetchState("done");
7921
8848
  return;
@@ -7925,6 +8852,7 @@ function OhhwellsBridge() {
7925
8852
  for (const [key, val] of Object.entries(content)) {
7926
8853
  if (key === "__ohw_sections") continue;
7927
8854
  if (applyVideoSettingNode(key, val)) continue;
8855
+ if (applyCarouselNode(key, val)) continue;
7928
8856
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
7929
8857
  if (el.dataset.ohwEditable === "image") {
7930
8858
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -7956,6 +8884,7 @@ function OhhwellsBridge() {
7956
8884
  applyLinkByKey(key, val);
7957
8885
  }
7958
8886
  reconcileNavbarItemsFromContent(content);
8887
+ reconcileFooterOrderFromContent(content);
7959
8888
  enforceLinkHrefs();
7960
8889
  initSectionsFromContent(content, true);
7961
8890
  sectionsLoadedRef.current = true;
@@ -7984,7 +8913,7 @@ function OhhwellsBridge() {
7984
8913
  cancelled = true;
7985
8914
  };
7986
8915
  }, [subdomain, isEditMode]);
7987
- (0, import_react8.useEffect)(() => {
8916
+ (0, import_react9.useEffect)(() => {
7988
8917
  if (!subdomain || isEditMode) return;
7989
8918
  let debounceTimer = null;
7990
8919
  let observer = null;
@@ -7997,6 +8926,7 @@ function OhhwellsBridge() {
7997
8926
  for (const [key, val] of Object.entries(content)) {
7998
8927
  if (key === "__ohw_sections") continue;
7999
8928
  if (applyVideoSettingNode(key, val)) continue;
8929
+ if (applyCarouselNode(key, val)) continue;
8000
8930
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
8001
8931
  if (el.dataset.ohwEditable === "image") {
8002
8932
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -8016,6 +8946,7 @@ function OhhwellsBridge() {
8016
8946
  applyLinkByKey(key, val);
8017
8947
  }
8018
8948
  reconcileNavbarItemsFromContent(content);
8949
+ reconcileFooterOrderFromContent(content);
8019
8950
  } finally {
8020
8951
  observer?.observe(document.body, { childList: true, subtree: true });
8021
8952
  }
@@ -8033,16 +8964,16 @@ function OhhwellsBridge() {
8033
8964
  if (debounceTimer) clearTimeout(debounceTimer);
8034
8965
  };
8035
8966
  }, [subdomain, isEditMode, pathname]);
8036
- (0, import_react8.useLayoutEffect)(() => {
8967
+ (0, import_react9.useLayoutEffect)(() => {
8037
8968
  const el = document.getElementById("ohw-loader");
8038
8969
  if (!el) return;
8039
8970
  const visible = Boolean(subdomain) && fetchState !== "done";
8040
8971
  el.style.display = visible ? "flex" : "none";
8041
8972
  }, [subdomain, fetchState]);
8042
- (0, import_react8.useEffect)(() => {
8973
+ (0, import_react9.useEffect)(() => {
8043
8974
  postToParent2({ type: "ow:navigation", path: pathname });
8044
8975
  }, [pathname, postToParent2]);
8045
- (0, import_react8.useEffect)(() => {
8976
+ (0, import_react9.useEffect)(() => {
8046
8977
  if (!isEditMode) return;
8047
8978
  if (linkPopoverSessionRef.current?.intent === "add-nav") return;
8048
8979
  if (document.querySelector("[data-ohw-section-picker]")) return;
@@ -8050,23 +8981,50 @@ function OhhwellsBridge() {
8050
8981
  deselectRef.current();
8051
8982
  deactivateRef.current();
8052
8983
  }, [pathname, isEditMode]);
8053
- (0, import_react8.useEffect)(() => {
8984
+ (0, import_react9.useEffect)(() => {
8054
8985
  const contentForNav = () => {
8055
8986
  if (isEditMode) return editContentRef.current;
8056
8987
  if (!subdomain) return {};
8057
8988
  return contentCache.get(subdomain) ?? {};
8058
8989
  };
8059
- const run = () => reconcileNavbarItemsFromContent(contentForNav());
8990
+ let applying = false;
8991
+ let rafId = null;
8992
+ const run = () => {
8993
+ if (footerDragRef.current || applying) return;
8994
+ applying = true;
8995
+ try {
8996
+ const content = contentForNav();
8997
+ reconcileNavbarItemsFromContent(content);
8998
+ reconcileFooterOrderFromContent(content);
8999
+ document.querySelectorAll("footer [data-ohw-href-key]").forEach((el) => {
9000
+ if (isFooterHrefKey(el.getAttribute("data-ohw-href-key"))) {
9001
+ disableNativeHrefDrag(el);
9002
+ }
9003
+ });
9004
+ } finally {
9005
+ applying = false;
9006
+ }
9007
+ };
9008
+ const scheduleRun = () => {
9009
+ if (rafId !== null) cancelAnimationFrame(rafId);
9010
+ rafId = requestAnimationFrame(() => {
9011
+ rafId = null;
9012
+ run();
9013
+ });
9014
+ };
8060
9015
  run();
8061
9016
  const nav = document.querySelector("nav");
8062
- if (!nav) return;
8063
- const observer = new MutationObserver(() => {
8064
- requestAnimationFrame(run);
8065
- });
8066
- observer.observe(nav, { childList: true, subtree: true });
8067
- return () => observer.disconnect();
9017
+ const footer = document.querySelector("footer");
9018
+ const observer = new MutationObserver(scheduleRun);
9019
+ if (nav) observer.observe(nav, { childList: true, subtree: true });
9020
+ if (footer) observer.observe(footer, { childList: true, subtree: true });
9021
+ if (!nav && !footer) return;
9022
+ return () => {
9023
+ observer.disconnect();
9024
+ if (rafId !== null) cancelAnimationFrame(rafId);
9025
+ };
8068
9026
  }, [isEditMode, pathname, subdomain, fetchState]);
8069
- (0, import_react8.useEffect)(() => {
9027
+ (0, import_react9.useEffect)(() => {
8070
9028
  if (!isEditMode) return;
8071
9029
  const measure = () => {
8072
9030
  const h = document.body.scrollHeight;
@@ -8090,7 +9048,7 @@ function OhhwellsBridge() {
8090
9048
  window.removeEventListener("resize", handleResize);
8091
9049
  };
8092
9050
  }, [pathname, isEditMode, postToParent2]);
8093
- (0, import_react8.useEffect)(() => {
9051
+ (0, import_react9.useEffect)(() => {
8094
9052
  if (!subdomainFromQuery || isEditMode) return;
8095
9053
  const handleClick = (e) => {
8096
9054
  const anchor = e.target.closest("a");
@@ -8106,7 +9064,7 @@ function OhhwellsBridge() {
8106
9064
  document.addEventListener("click", handleClick, true);
8107
9065
  return () => document.removeEventListener("click", handleClick, true);
8108
9066
  }, [subdomainFromQuery, isEditMode, router]);
8109
- (0, import_react8.useEffect)(() => {
9067
+ (0, import_react9.useEffect)(() => {
8110
9068
  if (!isEditMode) {
8111
9069
  editStylesRef.current?.base.remove();
8112
9070
  editStylesRef.current?.forceHover.remove();
@@ -8129,6 +9087,32 @@ function OhhwellsBridge() {
8129
9087
  [data-ohw-editable] {
8130
9088
  display: block;
8131
9089
  }
9090
+ /* Native <a> drag steals clicks \u2014 disable for edit links; reorder via press-to-drag / handle. */
9091
+ footer [data-ohw-href-key] {
9092
+ -webkit-user-drag: none !important;
9093
+ }
9094
+ footer [data-ohw-footer-col]:hover {
9095
+ outline: 1.5px dashed ${PRIMARY2} !important;
9096
+ outline-offset: 6px;
9097
+ border-radius: 8px;
9098
+ }
9099
+ html[data-ohw-link-popover-open] footer [data-ohw-footer-col]:hover {
9100
+ outline: none !important;
9101
+ }
9102
+ html[data-ohw-footer-press-drag] footer,
9103
+ html[data-ohw-footer-press-drag] footer * {
9104
+ user-select: none !important;
9105
+ -webkit-user-select: none !important;
9106
+ }
9107
+ html[data-ohw-item-dragging] footer,
9108
+ html[data-ohw-item-dragging] footer * {
9109
+ user-select: none !important;
9110
+ -webkit-user-select: none !important;
9111
+ pointer-events: none !important;
9112
+ }
9113
+ html[data-ohw-item-dragging] {
9114
+ cursor: grabbing !important;
9115
+ }
8132
9116
  [data-ohw-editable]:not([contenteditable]):not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"]):not([data-ohw-editable="video"]) { cursor: text !important; }
8133
9117
  [data-ohw-editable="image"], [data-ohw-editable="image"] *,
8134
9118
  [data-ohw-editable="video"], [data-ohw-editable="video"] *,
@@ -8173,12 +9157,39 @@ function OhhwellsBridge() {
8173
9157
  }
8174
9158
  refreshStateRules();
8175
9159
  const handleClick = (e) => {
9160
+ if (suppressNextClickRef.current) {
9161
+ suppressNextClickRef.current = false;
9162
+ e.preventDefault();
9163
+ e.stopPropagation();
9164
+ return;
9165
+ }
8176
9166
  const target = e.target;
8177
9167
  if (target.closest("[data-ohw-toolbar]")) return;
8178
9168
  if (target.closest("[data-ohw-state-toggle]")) return;
8179
9169
  if (target.closest("[data-ohw-max-badge]")) return;
8180
9170
  if (isInsideLinkEditor(target)) return;
8181
9171
  if (target.closest('[data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
9172
+ if (target.closest("[data-ohw-item-drag-surface]")) {
9173
+ e.preventDefault();
9174
+ e.stopPropagation();
9175
+ const selected = selectedElRef.current;
9176
+ if (selected && isNavigationItem(selected)) {
9177
+ const editable2 = selected.querySelector('[data-ohw-editable="text"]') ?? selected.querySelector("[data-ohw-editable]");
9178
+ if (editable2) {
9179
+ activateRef.current(editable2, {
9180
+ caretX: e.clientX,
9181
+ caretY: e.clientY
9182
+ });
9183
+ }
9184
+ } else if (selected?.hasAttribute("data-ohw-footer-col")) {
9185
+ const link = listFooterLinksInColumn(selected).find((el) => {
9186
+ const r2 = el.getBoundingClientRect();
9187
+ return e.clientX >= r2.left && e.clientX <= r2.right && e.clientY >= r2.top && e.clientY <= r2.bottom;
9188
+ });
9189
+ if (link) selectRef.current(link);
9190
+ }
9191
+ return;
9192
+ }
8182
9193
  const navFromChrome = resolveNavContainerSelectionTarget(target, e.clientX, e.clientY);
8183
9194
  if (navFromChrome) {
8184
9195
  e.preventDefault();
@@ -8208,7 +9219,15 @@ function OhhwellsBridge() {
8208
9219
  if (isMediaEditable(editable)) {
8209
9220
  e.preventDefault();
8210
9221
  e.stopPropagation();
8211
- postToParentRef.current({ type: "ow:image-pick", key: editable.dataset.ohwKey ?? "", elementType: editable.dataset.ohwEditable ?? "image" });
9222
+ const key = editable.dataset.ohwKey ?? "";
9223
+ const r2 = editable.getBoundingClientRect();
9224
+ if (key && r2.width > 1 && r2.height > 1) {
9225
+ pendingUploadRectRef.current = {
9226
+ key,
9227
+ rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height }
9228
+ };
9229
+ }
9230
+ postToParentRef.current({ type: "ow:image-pick", key, elementType: editable.dataset.ohwEditable ?? "image" });
8212
9231
  return;
8213
9232
  }
8214
9233
  const hrefCtx = getHrefKeyFromElement(editable);
@@ -8217,6 +9236,7 @@ function OhhwellsBridge() {
8217
9236
  e.preventDefault();
8218
9237
  e.stopPropagation();
8219
9238
  if (selectedElRef.current === navAnchor) {
9239
+ if (e.detail >= 2) return;
8220
9240
  activateRef.current(editable, { caretX: e.clientX, caretY: e.clientY });
8221
9241
  return;
8222
9242
  }
@@ -8232,10 +9252,26 @@ function OhhwellsBridge() {
8232
9252
  if (hrefAnchor) {
8233
9253
  e.preventDefault();
8234
9254
  e.stopPropagation();
8235
- if (selectedElRef.current === hrefAnchor) return;
9255
+ if (selectedElRef.current === hrefAnchor) {
9256
+ const textEditable = hrefAnchor.querySelector('[data-ohw-editable="text"]') ?? hrefAnchor.querySelector("[data-ohw-editable]");
9257
+ if (textEditable) {
9258
+ activateRef.current(textEditable, {
9259
+ caretX: e.clientX,
9260
+ caretY: e.clientY
9261
+ });
9262
+ }
9263
+ return;
9264
+ }
8236
9265
  selectRef.current(hrefAnchor);
8237
9266
  return;
8238
9267
  }
9268
+ const footerColClick = target.closest("[data-ohw-footer-col]");
9269
+ if (footerColClick) {
9270
+ e.preventDefault();
9271
+ e.stopPropagation();
9272
+ selectFrameRef.current(footerColClick);
9273
+ return;
9274
+ }
8239
9275
  const navContainerToSelect = resolveNavContainerSelectionTarget(target, e.clientX, e.clientY);
8240
9276
  if (navContainerToSelect) {
8241
9277
  e.preventDefault();
@@ -8277,9 +9313,38 @@ function OhhwellsBridge() {
8277
9313
  deselectRef.current();
8278
9314
  deactivateRef.current();
8279
9315
  };
9316
+ const handleDblClick = (e) => {
9317
+ const target = e.target;
9318
+ if (target.closest("[data-ohw-toolbar]")) return;
9319
+ if (target.closest("[data-ohw-state-toggle]")) return;
9320
+ if (target.closest("[data-ohw-max-badge]")) return;
9321
+ if (isInsideLinkEditor(target)) return;
9322
+ if (target.closest('[data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
9323
+ return;
9324
+ }
9325
+ const navLabel = getNavigationLabelEditable(target);
9326
+ if (!navLabel) return;
9327
+ const { editable } = navLabel;
9328
+ e.preventDefault();
9329
+ e.stopPropagation();
9330
+ if (activeElRef.current !== editable) {
9331
+ activateRef.current(editable);
9332
+ }
9333
+ requestAnimationFrame(() => {
9334
+ selectAllTextInEditable(editable);
9335
+ refreshActiveCommandsRef.current();
9336
+ });
9337
+ };
8280
9338
  const handleMouseOver = (e) => {
8281
9339
  if (document.documentElement.hasAttribute("data-ohw-section-picking")) return;
8282
9340
  const target = e.target;
9341
+ if (linkPopoverOpenRef.current) {
9342
+ hoveredItemElRef.current = null;
9343
+ setHoveredItemRect(null);
9344
+ hoveredNavContainerRef.current = null;
9345
+ setHoveredNavContainerRect(null);
9346
+ return;
9347
+ }
8283
9348
  if (toolbarVariantRef.current !== "select-frame") {
8284
9349
  const navContainer = target.closest("[data-ohw-nav-container]");
8285
9350
  if (navContainer && !getNavigationItemAnchor(target)) {
@@ -8294,6 +9359,15 @@ function OhhwellsBridge() {
8294
9359
  setHoveredNavContainerRect(null);
8295
9360
  }
8296
9361
  }
9362
+ const footerCol = target.closest("[data-ohw-footer-col]");
9363
+ if (footerCol) {
9364
+ hoveredNavContainerRef.current = null;
9365
+ setHoveredNavContainerRect(null);
9366
+ if (selectedElRef.current === footerCol) return;
9367
+ hoveredItemElRef.current = footerCol;
9368
+ setHoveredItemRect(footerCol.getBoundingClientRect());
9369
+ return;
9370
+ }
8297
9371
  const navAnchor = getNavigationItemAnchor(target);
8298
9372
  if (navAnchor) {
8299
9373
  hoveredNavContainerRef.current = null;
@@ -8330,6 +9404,15 @@ function OhhwellsBridge() {
8330
9404
  hoveredNavContainerRef.current = null;
8331
9405
  setHoveredNavContainerRect(null);
8332
9406
  }
9407
+ const footerCol = target.closest("[data-ohw-footer-col]");
9408
+ if (footerCol && hoveredItemElRef.current === footerCol) {
9409
+ const related2 = e.relatedTarget instanceof Element ? e.relatedTarget : null;
9410
+ if (related2?.closest("[data-ohw-drag-handle-container], [data-ohw-item-interaction]")) return;
9411
+ if (related2?.closest("[data-ohw-footer-col]") === footerCol) return;
9412
+ hoveredItemElRef.current = null;
9413
+ setHoveredItemRect(null);
9414
+ return;
9415
+ }
8333
9416
  const navAnchor = getNavigationItemAnchor(target);
8334
9417
  if (navAnchor) {
8335
9418
  const related2 = e.relatedTarget instanceof Element ? e.relatedTarget : null;
@@ -8441,12 +9524,12 @@ function OhhwellsBridge() {
8441
9524
  const stickyPad = 48;
8442
9525
  const withinPad = x >= pr.left - stickyPad && x <= pr.left + pr.width + stickyPad && y >= pr.top - stickyPad && y <= pr.top + pr.height + stickyPad;
8443
9526
  if (withinPad) {
8444
- if (smallest !== pinned && candidatePool.includes(smallest)) {
9527
+ const yieldToSmaller = smallest !== pinned && candidatePool.includes(smallest) && (() => {
8445
9528
  const sr = getVisibleRect(smallest);
8446
- const overOther = x >= sr.left && x <= sr.left + sr.width && y >= sr.top && y <= sr.top + sr.height;
8447
- if (overOther) return smallest;
8448
- }
8449
- return pinned;
9529
+ const pr2 = getVisibleRect(pinned);
9530
+ return sr.width * sr.height < pr2.width * pr2.height;
9531
+ })();
9532
+ if (!yieldToSmaller) return pinned;
8450
9533
  }
8451
9534
  }
8452
9535
  return smallest;
@@ -8458,6 +9541,7 @@ function OhhwellsBridge() {
8458
9541
  resumeAnimTracks();
8459
9542
  postToParentRef.current({ type: "ow:image-unhover" });
8460
9543
  }
9544
+ clearImageHover();
8461
9545
  };
8462
9546
  const probeNavigationHoverAt = (x, y) => {
8463
9547
  if (toolbarVariantRef.current === "select-frame") {
@@ -8560,7 +9644,7 @@ function OhhwellsBridge() {
8560
9644
  return;
8561
9645
  }
8562
9646
  const topEl = document.elementFromPoint(x2, y2);
8563
- if (topEl?.closest('[data-ohw-toolbar], [data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
9647
+ 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]')) {
8564
9648
  if (hoveredImageRef.current) {
8565
9649
  hoveredImageRef.current = null;
8566
9650
  resumeAnimTracks();
@@ -8767,6 +9851,14 @@ function OhhwellsBridge() {
8767
9851
  probeHoverCardsAt(clientX, clientY);
8768
9852
  };
8769
9853
  const handleDragOver = (e) => {
9854
+ const footerSession = footerDragRef.current;
9855
+ if (footerSession) {
9856
+ e.preventDefault();
9857
+ if (e.dataTransfer) e.dataTransfer.dropEffect = "move";
9858
+ const slot = footerSession.kind === "link" && footerSession.hrefKey ? hitTestLinkDropSlot(e.clientX, e.clientY, footerSession.hrefKey) : hitTestColumnDropSlot(e.clientX, e.clientY);
9859
+ refreshFooterDragVisualsRef.current(footerSession, slot, e.clientX, e.clientY);
9860
+ return;
9861
+ }
8770
9862
  e.preventDefault();
8771
9863
  const el = findImageAtPoint(e.clientX, e.clientY, false);
8772
9864
  if (!el) {
@@ -8835,6 +9927,7 @@ function OhhwellsBridge() {
8835
9927
  if (!entry || entry.fadingOut) return prev;
8836
9928
  return { ...prev, [key]: { ...entry, fadingOut: true } };
8837
9929
  });
9930
+ if (pendingUploadRectRef.current?.key === key) pendingUploadRectRef.current = null;
8838
9931
  };
8839
9932
  let found = false;
8840
9933
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -8949,6 +10042,7 @@ function OhhwellsBridge() {
8949
10042
  continue;
8950
10043
  }
8951
10044
  if (applyVideoSettingNode(key, val)) continue;
10045
+ if (applyCarouselNode(key, val)) continue;
8952
10046
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
8953
10047
  if (el.dataset.ohwEditable === "image") {
8954
10048
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -8973,6 +10067,7 @@ function OhhwellsBridge() {
8973
10067
  }
8974
10068
  editContentRef.current = { ...editContentRef.current, ...content };
8975
10069
  reconcileNavbarItemsFromContent(editContentRef.current);
10070
+ reconcileFooterOrderFromContent(editContentRef.current);
8976
10071
  enforceLinkHrefs();
8977
10072
  postToParentRef.current({ type: "ow:hydrate-done" });
8978
10073
  };
@@ -8998,6 +10093,15 @@ function OhhwellsBridge() {
8998
10093
  window.addEventListener("message", handleUiEscape);
8999
10094
  const handleKeyDown = (e) => {
9000
10095
  if (e.key === "Escape" && document.querySelector("[data-ohw-section-picker]")) return;
10096
+ if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "a" && activeElRef.current) {
10097
+ const navAnchor = getNavigationItemAnchor(activeElRef.current);
10098
+ if (navAnchor) {
10099
+ e.preventDefault();
10100
+ selectAllTextInEditable(activeElRef.current);
10101
+ refreshActiveCommandsRef.current();
10102
+ return;
10103
+ }
10104
+ }
9001
10105
  if (e.key === "Escape" && linkPopoverOpenRef.current) {
9002
10106
  setLinkPopoverRef.current(null);
9003
10107
  return;
@@ -9077,6 +10181,17 @@ function OhhwellsBridge() {
9077
10181
  if (siblingHintElRef.current) {
9078
10182
  setSiblingHintRect(siblingHintElRef.current.getBoundingClientRect());
9079
10183
  }
10184
+ const selected = selectedElRef.current;
10185
+ if (selected && !footerDragRef.current && (selected.hasAttribute("data-ohw-footer-col") || Boolean(selected.closest("footer") && isInferredFooterGroup(selected)))) {
10186
+ setSiblingHintRects(
10187
+ listFooterColumns().filter((column) => column !== selected).map((column) => column.getBoundingClientRect())
10188
+ );
10189
+ }
10190
+ if (footerDragRef.current) {
10191
+ const session = footerDragRef.current;
10192
+ const slot = session.kind === "link" && session.hrefKey ? hitTestLinkDropSlot(session.lastClientX, session.lastClientY, session.hrefKey) : hitTestColumnDropSlot(session.lastClientX, session.lastClientY);
10193
+ refreshFooterDragVisualsRef.current(session, slot, session.lastClientX, session.lastClientY);
10194
+ }
9080
10195
  if (hoveredImageRef.current) {
9081
10196
  const el = hoveredImageRef.current;
9082
10197
  const r2 = el.getBoundingClientRect();
@@ -9087,7 +10202,7 @@ function OhhwellsBridge() {
9087
10202
  };
9088
10203
  const handleSave = (e) => {
9089
10204
  if (e.data?.type !== "ow:save") return;
9090
- const nodes = collectEditableNodes();
10205
+ const nodes = collectEditableNodes(editContentRef.current);
9091
10206
  const tracker = document.querySelector("[data-ohw-sections-tracker]");
9092
10207
  if (tracker?.textContent) nodes.push({ key: "__ohw_sections", type: "sections", text: tracker.textContent });
9093
10208
  postToParentRef.current({ type: "ow:save-result", nodes });
@@ -9172,28 +10287,49 @@ function OhhwellsBridge() {
9172
10287
  const handleDocMouseLeave = () => {
9173
10288
  hoveredImageRef.current = null;
9174
10289
  setMediaHover(null);
10290
+ setCarouselHover(null);
9175
10291
  document.querySelectorAll("[data-ohw-state-hovered]").forEach((el) => el.removeAttribute("data-ohw-state-hovered"));
9176
10292
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
9177
10293
  activeStateElRef.current = null;
9178
10294
  setToggleState(null);
9179
10295
  };
10296
+ const resolveUploadRects = (key) => {
10297
+ const matches = Array.from(document.querySelectorAll(`[data-ohw-key="${key}"]`));
10298
+ const rects = matches.map((el) => {
10299
+ const r2 = el.getBoundingClientRect();
10300
+ return { top: r2.top, left: r2.left, width: r2.width, height: r2.height };
10301
+ }).filter((r2) => r2.width > 1 && r2.height > 1);
10302
+ if (rects.length > 0) return rects;
10303
+ const pending = pendingUploadRectRef.current;
10304
+ if (pending?.key === key && pending.rect.width > 1 && pending.rect.height > 1) {
10305
+ return [pending.rect];
10306
+ }
10307
+ const hovered = hoveredImageRef.current;
10308
+ if (hovered?.dataset.ohwKey === key) {
10309
+ const r2 = hovered.getBoundingClientRect();
10310
+ if (r2.width > 1 && r2.height > 1) {
10311
+ return [{ top: r2.top, left: r2.left, width: r2.width, height: r2.height }];
10312
+ }
10313
+ }
10314
+ return [];
10315
+ };
9180
10316
  const handleImageUploading = (e) => {
9181
- if (e.data?.type !== "ow:image-uploading") return;
9182
- const { key, uploading } = e.data;
10317
+ const type = e.data?.type;
10318
+ if (type !== "ow:image-uploading" && type !== "ow:anim-lock") return;
10319
+ const key = e.data.key;
10320
+ if (!key) return;
10321
+ const uploading = type === "ow:anim-lock" ? true : !!e.data.uploading;
9183
10322
  setUploadingRects((prev) => {
9184
10323
  if (!uploading) {
10324
+ if (pendingUploadRectRef.current?.key === key) pendingUploadRectRef.current = null;
9185
10325
  if (!(key in prev)) return prev;
9186
10326
  const next = { ...prev };
9187
10327
  delete next[key];
9188
10328
  return next;
9189
10329
  }
9190
- const el = document.querySelector(`[data-ohw-key="${key}"]`);
9191
- if (!el) return prev;
9192
- const r2 = getVisibleRect(el);
9193
- return {
9194
- ...prev,
9195
- [key]: { rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height } }
9196
- };
10330
+ const rects = resolveUploadRects(key);
10331
+ if (rects.length === 0) return prev;
10332
+ return { ...prev, [key]: { rects } };
9197
10333
  });
9198
10334
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
9199
10335
  const track = el.closest("[data-ohw-hover-pause]");
@@ -9211,6 +10347,48 @@ function OhhwellsBridge() {
9211
10347
  if (e.data?.type !== "ow:canvas-height" || typeof e.data.height !== "number") return;
9212
10348
  document.documentElement.style.setProperty("--ohw-canvas-h", `${e.data.height}px`);
9213
10349
  };
10350
+ const handleCarouselChange = (e) => {
10351
+ if (e.data?.type !== "ow:carousel-change") return;
10352
+ const { key, images } = e.data;
10353
+ if (!key || !Array.isArray(images)) return;
10354
+ const text = JSON.stringify(images);
10355
+ applyCarouselValue(key, images);
10356
+ editContentRef.current = { ...editContentRef.current, [key]: text };
10357
+ postToParentRef.current({ type: "ow:change", nodes: [{ key, text }] });
10358
+ setCarouselHover(null);
10359
+ };
10360
+ const findCarouselAtPoint = (clientX, clientY) => {
10361
+ const containers = Array.from(document.querySelectorAll(`[${CAROUSEL_ATTR}]`));
10362
+ for (let i = containers.length - 1; i >= 0; i--) {
10363
+ const r2 = containers[i].getBoundingClientRect();
10364
+ if (clientX >= r2.left && clientX <= r2.right && clientY >= r2.top && clientY <= r2.bottom) {
10365
+ return containers[i];
10366
+ }
10367
+ }
10368
+ return null;
10369
+ };
10370
+ const isPointOverEditable = (scope, clientX, clientY) => {
10371
+ const editables = scope.querySelectorAll("[data-ohw-editable], [data-ohw-href-key]");
10372
+ for (const el of editables) {
10373
+ const r2 = el.getBoundingClientRect();
10374
+ if (clientX >= r2.left && clientX <= r2.right && clientY >= r2.top && clientY <= r2.bottom) return true;
10375
+ }
10376
+ return false;
10377
+ };
10378
+ const handleCarouselHover = (e) => {
10379
+ const container = findCarouselAtPoint(e.clientX, e.clientY);
10380
+ const scope = container?.closest("[data-ohw-section]") ?? container?.parentElement ?? null;
10381
+ if (!container || !scope || isPointOverEditable(scope, e.clientX, e.clientY)) {
10382
+ setCarouselHover((prev) => prev ? null : prev);
10383
+ return;
10384
+ }
10385
+ const key = container.getAttribute("data-ohw-key") ?? "";
10386
+ if (!key) return;
10387
+ const r2 = getVisibleRect(container);
10388
+ setCarouselHover(
10389
+ (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 } }
10390
+ );
10391
+ };
9214
10392
  const applyToolbarPos = (rect) => {
9215
10393
  const ps = parentScrollRef.current;
9216
10394
  const approxW = 330;
@@ -9319,6 +10497,7 @@ function OhhwellsBridge() {
9319
10497
  window.addEventListener("message", handleRemoveSchedulingSection);
9320
10498
  window.addEventListener("message", handleImageUrl);
9321
10499
  window.addEventListener("message", handleImageUploading);
10500
+ window.addEventListener("message", handleCarouselChange);
9322
10501
  window.addEventListener("message", handleCanvasHeight);
9323
10502
  window.addEventListener("message", handleParentScroll);
9324
10503
  window.addEventListener("message", handlePointerSync);
@@ -9330,11 +10509,13 @@ function OhhwellsBridge() {
9330
10509
  };
9331
10510
  window.addEventListener("resize", handleViewportResize, { passive: true });
9332
10511
  document.addEventListener("click", handleClick, true);
10512
+ document.addEventListener("dblclick", handleDblClick, true);
9333
10513
  document.addEventListener("paste", handlePaste, true);
9334
10514
  document.addEventListener("input", handleInput, true);
9335
10515
  document.addEventListener("mouseover", handleMouseOver, true);
9336
10516
  document.addEventListener("mouseout", handleMouseOut, true);
9337
10517
  document.addEventListener("mousemove", handleMouseMove, true);
10518
+ document.addEventListener("mousemove", handleCarouselHover, true);
9338
10519
  document.addEventListener("mouseleave", handleDocMouseLeave);
9339
10520
  document.addEventListener("dragover", handleDragOver, true);
9340
10521
  document.addEventListener("dragleave", handleDragLeave, true);
@@ -9344,11 +10525,13 @@ function OhhwellsBridge() {
9344
10525
  window.addEventListener("scroll", handleScroll, true);
9345
10526
  return () => {
9346
10527
  document.removeEventListener("click", handleClick, true);
10528
+ document.removeEventListener("dblclick", handleDblClick, true);
9347
10529
  document.removeEventListener("paste", handlePaste, true);
9348
10530
  document.removeEventListener("input", handleInput, true);
9349
10531
  document.removeEventListener("mouseover", handleMouseOver, true);
9350
10532
  document.removeEventListener("mouseout", handleMouseOut, true);
9351
10533
  document.removeEventListener("mousemove", handleMouseMove, true);
10534
+ document.removeEventListener("mousemove", handleCarouselHover, true);
9352
10535
  document.removeEventListener("mouseleave", handleDocMouseLeave);
9353
10536
  document.removeEventListener("dragover", handleDragOver, true);
9354
10537
  document.removeEventListener("dragleave", handleDragLeave, true);
@@ -9364,6 +10547,7 @@ function OhhwellsBridge() {
9364
10547
  window.removeEventListener("message", handleRemoveSchedulingSection);
9365
10548
  window.removeEventListener("message", handleImageUrl);
9366
10549
  window.removeEventListener("message", handleImageUploading);
10550
+ window.removeEventListener("message", handleCarouselChange);
9367
10551
  window.removeEventListener("message", handleCanvasHeight);
9368
10552
  window.removeEventListener("message", handleParentScroll);
9369
10553
  window.removeEventListener("resize", handleViewportResize);
@@ -9378,7 +10562,158 @@ function OhhwellsBridge() {
9378
10562
  if (imageShowTimerRef.current) clearTimeout(imageShowTimerRef.current);
9379
10563
  };
9380
10564
  }, [isEditMode, refreshStateRules]);
9381
- (0, import_react8.useEffect)(() => {
10565
+ (0, import_react9.useEffect)(() => {
10566
+ if (!isEditMode) return;
10567
+ const THRESHOLD = 10;
10568
+ const resolveWasSelected = (el) => {
10569
+ if (selectedElRef.current === el) return true;
10570
+ const activeAnchor = activeElRef.current ? getNavigationItemAnchor(activeElRef.current) : null;
10571
+ return activeAnchor === el;
10572
+ };
10573
+ const onPointerDown = (e) => {
10574
+ if (e.button !== 0) return;
10575
+ if (suppressNextClickRef.current && !footerDragRef.current && !footerPointerDragRef.current?.started) {
10576
+ suppressNextClickRef.current = false;
10577
+ }
10578
+ if (footerDragRef.current) return;
10579
+ const target = e.target;
10580
+ if (!target) return;
10581
+ 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]')) {
10582
+ return;
10583
+ }
10584
+ if (target.closest("[data-ohw-item-drag-surface]")) return;
10585
+ const anchor = getNavigationItemAnchor(target);
10586
+ const hrefKey = anchor?.getAttribute("data-ohw-href-key") ?? null;
10587
+ if (anchor && isFooterHrefKey(hrefKey)) {
10588
+ armFooterPressDrag();
10589
+ footerPointerDragRef.current = {
10590
+ el: anchor,
10591
+ kind: "link",
10592
+ startX: e.clientX,
10593
+ startY: e.clientY,
10594
+ pointerId: e.pointerId,
10595
+ wasSelected: resolveWasSelected(anchor),
10596
+ started: false
10597
+ };
10598
+ return;
10599
+ }
10600
+ const col = target.closest("[data-ohw-footer-col]");
10601
+ if (col && !getNavigationItemAnchor(target)) {
10602
+ armFooterPressDrag();
10603
+ footerPointerDragRef.current = {
10604
+ el: col,
10605
+ kind: "column",
10606
+ startX: e.clientX,
10607
+ startY: e.clientY,
10608
+ pointerId: e.pointerId,
10609
+ wasSelected: resolveWasSelected(col),
10610
+ started: false
10611
+ };
10612
+ }
10613
+ };
10614
+ const onPointerMove = (e) => {
10615
+ const pending = footerPointerDragRef.current;
10616
+ if (!pending) return;
10617
+ if (pending.started) {
10618
+ e.preventDefault();
10619
+ clearTextSelection();
10620
+ const session = footerDragRef.current;
10621
+ if (!session) return;
10622
+ const slot = session.kind === "link" && session.hrefKey ? hitTestLinkDropSlot(e.clientX, e.clientY, session.hrefKey) : hitTestColumnDropSlot(e.clientX, e.clientY);
10623
+ refreshFooterDragVisualsRef.current(session, slot, e.clientX, e.clientY);
10624
+ return;
10625
+ }
10626
+ const dx = e.clientX - pending.startX;
10627
+ const dy = e.clientY - pending.startY;
10628
+ if (dx * dx + dy * dy < THRESHOLD * THRESHOLD) return;
10629
+ e.preventDefault();
10630
+ pending.started = true;
10631
+ clearTextSelection();
10632
+ try {
10633
+ document.body.setPointerCapture(pending.pointerId);
10634
+ } catch {
10635
+ }
10636
+ if (linkPopoverOpenRef.current) {
10637
+ setLinkPopoverRef.current(null);
10638
+ }
10639
+ if (activeElRef.current) {
10640
+ deactivateRef.current();
10641
+ }
10642
+ if (pending.kind === "link") {
10643
+ const key = pending.el.getAttribute("data-ohw-href-key");
10644
+ if (!key) return;
10645
+ const column = findFooterColumnForLink(pending.el);
10646
+ const columns2 = listFooterColumns();
10647
+ beginFooterDragRef.current({
10648
+ kind: "link",
10649
+ hrefKey: key,
10650
+ columnEl: column,
10651
+ sourceColumnIndex: column ? columns2.indexOf(column) : 0,
10652
+ wasSelected: pending.wasSelected,
10653
+ draggedEl: pending.el,
10654
+ lastClientX: e.clientX,
10655
+ lastClientY: e.clientY,
10656
+ activeSlot: null
10657
+ });
10658
+ return;
10659
+ }
10660
+ const columns = listFooterColumns();
10661
+ const idx = columns.indexOf(pending.el);
10662
+ if (idx < 0) return;
10663
+ beginFooterDragRef.current({
10664
+ kind: "column",
10665
+ hrefKey: null,
10666
+ columnEl: pending.el,
10667
+ sourceColumnIndex: idx,
10668
+ wasSelected: pending.wasSelected,
10669
+ draggedEl: pending.el,
10670
+ lastClientX: e.clientX,
10671
+ lastClientY: e.clientY,
10672
+ activeSlot: null
10673
+ });
10674
+ };
10675
+ const endPointerDrag = (e) => {
10676
+ const pending = footerPointerDragRef.current;
10677
+ footerPointerDragRef.current = null;
10678
+ try {
10679
+ if (document.body.hasPointerCapture(e.pointerId)) {
10680
+ document.body.releasePointerCapture(e.pointerId);
10681
+ }
10682
+ } catch {
10683
+ }
10684
+ if (!pending?.started) {
10685
+ unlockFooterDragInteraction();
10686
+ return;
10687
+ }
10688
+ suppressNextClickRef.current = true;
10689
+ commitFooterDragRef.current(e.clientX, e.clientY);
10690
+ };
10691
+ const blockSelectStart = (e) => {
10692
+ if (footerDragRef.current || footerPointerDragRef.current?.started || document.documentElement.hasAttribute("data-ohw-footer-press-drag")) {
10693
+ e.preventDefault();
10694
+ }
10695
+ };
10696
+ const onDragEnd = (_e) => {
10697
+ if (!footerDragRef.current) return;
10698
+ commitFooterDragRef.current();
10699
+ };
10700
+ document.addEventListener("pointerdown", onPointerDown, true);
10701
+ document.addEventListener("pointermove", onPointerMove, true);
10702
+ document.addEventListener("pointerup", endPointerDrag, true);
10703
+ document.addEventListener("pointercancel", endPointerDrag, true);
10704
+ document.addEventListener("selectstart", blockSelectStart, true);
10705
+ document.addEventListener("dragend", onDragEnd, true);
10706
+ return () => {
10707
+ document.removeEventListener("pointerdown", onPointerDown, true);
10708
+ document.removeEventListener("pointermove", onPointerMove, true);
10709
+ document.removeEventListener("pointerup", endPointerDrag, true);
10710
+ document.removeEventListener("pointercancel", endPointerDrag, true);
10711
+ document.removeEventListener("selectstart", blockSelectStart, true);
10712
+ document.removeEventListener("dragend", onDragEnd, true);
10713
+ unlockFooterDragInteraction();
10714
+ };
10715
+ }, [isEditMode]);
10716
+ (0, import_react9.useEffect)(() => {
9382
10717
  const handler = (e) => {
9383
10718
  if (e.data?.type !== "ow:request-schedule-config") return;
9384
10719
  const insertAfterVal = e.data.insertAfter;
@@ -9394,7 +10729,7 @@ function OhhwellsBridge() {
9394
10729
  window.addEventListener("message", handler);
9395
10730
  return () => window.removeEventListener("message", handler);
9396
10731
  }, [processConfigRequest]);
9397
- (0, import_react8.useEffect)(() => {
10732
+ (0, import_react9.useEffect)(() => {
9398
10733
  if (!isEditMode) return;
9399
10734
  document.querySelectorAll("[data-ohw-active-state]").forEach((el) => {
9400
10735
  el.removeAttribute("data-ohw-active-state");
@@ -9415,7 +10750,13 @@ function OhhwellsBridge() {
9415
10750
  const next = { ...prev, [pathKey]: sections };
9416
10751
  return shouldUseDevFixtures() ? { ...DEV_SECTIONS_BY_PATH, ...next } : next;
9417
10752
  });
9418
- postToParent2({ type: "ow:ready", version: "1", path: pathname, nodes: collectEditableNodes(), sections });
10753
+ postToParent2({
10754
+ type: "ow:ready",
10755
+ version: "1",
10756
+ path: pathname,
10757
+ nodes: collectEditableNodes(editContentRef.current),
10758
+ sections
10759
+ });
9419
10760
  postToParent2({ type: "ow:request-site-pages" });
9420
10761
  }, 150);
9421
10762
  return () => {
@@ -9423,19 +10764,19 @@ function OhhwellsBridge() {
9423
10764
  clearTimeout(timer);
9424
10765
  };
9425
10766
  }, [pathname, isEditMode, refreshStateRules, postToParent2]);
9426
- (0, import_react8.useEffect)(() => {
10767
+ (0, import_react9.useEffect)(() => {
9427
10768
  scrollToHashSectionWhenReady();
9428
10769
  const onHashChange = () => scrollToHashSectionWhenReady();
9429
10770
  window.addEventListener("hashchange", onHashChange);
9430
10771
  return () => window.removeEventListener("hashchange", onHashChange);
9431
10772
  }, [pathname]);
9432
- const handleCommand = (0, import_react8.useCallback)((cmd) => {
10773
+ const handleCommand = (0, import_react9.useCallback)((cmd) => {
9433
10774
  document.execCommand(cmd, false);
9434
10775
  activeElRef.current?.focus();
9435
10776
  if (activeElRef.current) setToolbarRect(getEditMeasureEl(activeElRef.current).getBoundingClientRect());
9436
10777
  refreshActiveCommandsRef.current();
9437
10778
  }, []);
9438
- const handleStateChange = (0, import_react8.useCallback)((state) => {
10779
+ const handleStateChange = (0, import_react9.useCallback)((state) => {
9439
10780
  if (!activeStateElRef.current) return;
9440
10781
  const el = activeStateElRef.current;
9441
10782
  if (state === "Default") {
@@ -9448,11 +10789,11 @@ function OhhwellsBridge() {
9448
10789
  }
9449
10790
  setToggleState((prev) => prev ? { ...prev, activeState: state } : null);
9450
10791
  }, [deactivate]);
9451
- const closeLinkPopover = (0, import_react8.useCallback)(() => {
10792
+ const closeLinkPopover = (0, import_react9.useCallback)(() => {
9452
10793
  addNavAfterAnchorRef.current = null;
9453
10794
  setLinkPopover(null);
9454
10795
  }, []);
9455
- const openLinkPopoverForActive = (0, import_react8.useCallback)(() => {
10796
+ const openLinkPopoverForActive = (0, import_react9.useCallback)(() => {
9456
10797
  const hrefCtx = getHrefKeyFromElement(activeElRef.current);
9457
10798
  if (!hrefCtx) return;
9458
10799
  bumpLinkPopoverGrace();
@@ -9463,7 +10804,7 @@ function OhhwellsBridge() {
9463
10804
  });
9464
10805
  deactivate();
9465
10806
  }, [deactivate]);
9466
- const openLinkPopoverForSelected = (0, import_react8.useCallback)(() => {
10807
+ const openLinkPopoverForSelected = (0, import_react9.useCallback)(() => {
9467
10808
  const anchor = selectedElRef.current;
9468
10809
  if (!anchor) return;
9469
10810
  const key = anchor.getAttribute("data-ohw-href-key");
@@ -9476,7 +10817,7 @@ function OhhwellsBridge() {
9476
10817
  });
9477
10818
  deselect();
9478
10819
  }, [deselect]);
9479
- const handleLinkPopoverSubmit = (0, import_react8.useCallback)(
10820
+ const handleLinkPopoverSubmit = (0, import_react9.useCallback)(
9480
10821
  (target) => {
9481
10822
  const session = linkPopoverSessionRef.current;
9482
10823
  if (!session) return;
@@ -9532,16 +10873,22 @@ function OhhwellsBridge() {
9532
10873
  },
9533
10874
  [postToParent2, sitePages, sectionsByPath]
9534
10875
  );
9535
- const showEditLink = toolbarShowEditLink;
9536
- const currentSections = sectionsByPath[pathname] ?? [];
9537
- linkPopoverOpenRef.current = linkPopover !== null;
9538
- const handleMediaReplace = (0, import_react8.useCallback)(
10876
+ const handleMediaReplace = (0, import_react9.useCallback)(
9539
10877
  (key) => {
10878
+ if (mediaHover?.key === key) {
10879
+ pendingUploadRectRef.current = { key, rect: mediaHover.rect };
10880
+ }
9540
10881
  postToParent2({ type: "ow:image-pick", key, elementType: mediaHover?.elementType ?? "image" });
9541
10882
  },
9542
- [postToParent2, mediaHover?.elementType]
10883
+ [postToParent2, mediaHover]
9543
10884
  );
9544
- const handleMediaFadeOutComplete = (0, import_react8.useCallback)((key) => {
10885
+ const handleEditCarousel = (0, import_react9.useCallback)(
10886
+ (key) => {
10887
+ postToParent2({ type: "ow:carousel-open", key, images: readCarouselValue(key) });
10888
+ },
10889
+ [postToParent2]
10890
+ );
10891
+ const handleMediaFadeOutComplete = (0, import_react9.useCallback)((key) => {
9545
10892
  setUploadingRects((prev) => {
9546
10893
  if (!(key in prev)) return prev;
9547
10894
  const next = { ...prev };
@@ -9549,7 +10896,7 @@ function OhhwellsBridge() {
9549
10896
  return next;
9550
10897
  });
9551
10898
  }, []);
9552
- const handleVideoSettingsChange = (0, import_react8.useCallback)(
10899
+ const handleVideoSettingsChange = (0, import_react9.useCallback)(
9553
10900
  (key, settings) => {
9554
10901
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
9555
10902
  const video = getVideoEl(el);
@@ -9571,21 +10918,26 @@ function OhhwellsBridge() {
9571
10918
  },
9572
10919
  [postToParent2]
9573
10920
  );
10921
+ const showEditLink = toolbarShowEditLink;
10922
+ const currentSections = sectionsByPath[pathname] ?? [];
10923
+ linkPopoverOpenRef.current = linkPopover !== null;
9574
10924
  return bridgeRoot ? (0, import_react_dom3.createPortal)(
9575
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
9576
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
9577
- Object.entries(uploadingRects).map(([key, { rect, fadingOut }]) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
9578
- MediaOverlay,
9579
- {
9580
- hover: { key, rect, elementType: "image", isDragOver: false, hasTextOverlap: false },
9581
- isUploading: true,
9582
- fadingOut,
9583
- onFadeOutComplete: handleMediaFadeOutComplete,
9584
- onReplace: handleMediaReplace
9585
- },
9586
- `uploading-${key}`
9587
- )),
9588
- mediaHover && !(mediaHover.key in uploadingRects) && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
10925
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(import_jsx_runtime25.Fragment, { children: [
10926
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
10927
+ Object.entries(uploadingRects).flatMap(
10928
+ ([key, { rects, fadingOut }]) => rects.map((rect, i) => /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
10929
+ MediaOverlay,
10930
+ {
10931
+ hover: { key, rect, elementType: "image", isDragOver: false, hasTextOverlap: false },
10932
+ isUploading: true,
10933
+ fadingOut,
10934
+ onFadeOutComplete: handleMediaFadeOutComplete,
10935
+ onReplace: handleMediaReplace
10936
+ },
10937
+ `uploading-${key}-${i}`
10938
+ ))
10939
+ ),
10940
+ mediaHover && !(mediaHover.key in uploadingRects) && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
9589
10941
  MediaOverlay,
9590
10942
  {
9591
10943
  hover: mediaHover,
@@ -9594,62 +10946,33 @@ function OhhwellsBridge() {
9594
10946
  onVideoSettingsChange: handleVideoSettingsChange
9595
10947
  }
9596
10948
  ),
9597
- siblingHintRect && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
9598
- hoveredNavContainerRect && toolbarVariant !== "select-frame" && !linkPopover && !isItemDragging && !siblingHintRect && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
9599
- toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
9600
- NavbarContainerChrome,
9601
- {
9602
- rect: toolbarRect,
9603
- onAdd: handleAddTopLevelNavItem
9604
- }
9605
- ),
9606
- hoveredItemRect && !hoveredNavContainerRect && !siblingHintRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
9607
- toolbarRect && (toolbarVariant === "link-action" || toolbarVariant === "select-frame") && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
9608
- ItemInteractionLayer,
10949
+ carouselHover && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(CarouselOverlay, { hover: carouselHover, onEdit: handleEditCarousel }),
10950
+ siblingHintRect && !isItemDragging && siblingHintRects.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
10951
+ siblingHintRects.map((rect, i) => /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ItemInteractionLayer, { rect, state: "sibling-hint" }, `sibling-hint-${i}`)),
10952
+ isItemDragging && draggedItemRect && selectedElRef.current !== footerDragRef.current?.draggedEl && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ItemInteractionLayer, { rect: draggedItemRect, state: "dragging" }),
10953
+ isItemDragging && footerDropSlots.map((slot, i) => /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
10954
+ "div",
9609
10955
  {
9610
- rect: toolbarRect,
9611
- elRef: glowElRef,
9612
- state: isItemDragging ? "dragging" : resolveItemInteractionState(toolbarRect, parentScrollRef.current),
9613
- showHandle: toolbarVariant === "link-action" && Boolean(reorderHrefKey),
9614
- dragDisabled: reorderDragDisabled,
9615
- dragHandleLabel: reorderHrefKey ? `Reorder ${reorderHrefKey}` : "Reorder item",
9616
- onDragHandleDragStart: handleItemDragStart,
9617
- onDragHandleDragEnd: handleItemDragEnd,
9618
- toolbar: toolbarVariant === "link-action" && !isItemDragging ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
9619
- ItemActionToolbar,
9620
- {
9621
- onEditLink: openLinkPopoverForSelected,
9622
- addItemDisabled: true,
9623
- editLinkDisabled: false,
9624
- moreDisabled: true
9625
- }
9626
- ) : void 0
9627
- }
9628
- ),
9629
- toolbarRect && toolbarVariant === "rich-text" && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
9630
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
9631
- EditGlowChrome,
9632
- {
9633
- rect: toolbarRect,
9634
- elRef: glowElRef,
9635
- reorderHrefKey,
9636
- dragDisabled: reorderDragDisabled
9637
- }
9638
- ),
9639
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
9640
- FloatingToolbar,
9641
- {
9642
- rect: toolbarRect,
9643
- parentScroll: parentScrollRef.current,
9644
- elRef: toolbarElRef,
9645
- onCommand: handleCommand,
9646
- activeCommands,
9647
- showEditLink,
9648
- onEditLink: openLinkPopoverForActive
9649
- }
9650
- )
10956
+ className: "pointer-events-none fixed z-2147483646",
10957
+ style: {
10958
+ left: slot.left,
10959
+ top: slot.top,
10960
+ width: slot.width,
10961
+ height: slot.height
10962
+ },
10963
+ children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(DropIndicator, { direction: slot.direction, state: activeFooterDropIndex === i ? "dragActive" : "dragIdle", className: slot.direction === "horizontal" ? "!h-full !w-full" : "!h-full !w-full" })
10964
+ },
10965
+ `footer-drop-${slot.direction}-${slot.columnIndex}-${slot.insertIndex}-${i}`
10966
+ )),
10967
+ hoveredNavContainerRect && toolbarVariant !== "select-frame" && !linkPopover && !isItemDragging && !siblingHintRect && siblingHintRects.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
10968
+ toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(NavbarContainerChrome, { rect: toolbarRect, onAdd: handleAddTopLevelNavItem }),
10969
+ hoveredItemRect && !linkPopover && !hoveredNavContainerRect && !siblingHintRect && siblingHintRects.length === 0 && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
10970
+ toolbarRect && (toolbarVariant === "link-action" || toolbarVariant === "select-frame") && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(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__ */ (0, import_jsx_runtime25.jsx)(ItemActionToolbar, { onEditLink: openLinkPopoverForSelected, addItemDisabled: true, editLinkDisabled: false, moreDisabled: true }) : void 0 }),
10971
+ toolbarRect && toolbarVariant === "rich-text" && /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(import_jsx_runtime25.Fragment, { children: [
10972
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(EditGlowChrome, { rect: toolbarRect, elRef: glowElRef, reorderHrefKey, dragDisabled: reorderDragDisabled }),
10973
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(FloatingToolbar, { rect: toolbarRect, parentScroll: parentScrollRef.current, elRef: toolbarElRef, onCommand: handleCommand, activeCommands, showEditLink, onEditLink: openLinkPopoverForActive })
9651
10974
  ] }),
9652
- maxBadge && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
10975
+ maxBadge && /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
9653
10976
  "div",
9654
10977
  {
9655
10978
  "data-ohw-max-badge": "",
@@ -9675,57 +10998,29 @@ function OhhwellsBridge() {
9675
10998
  ]
9676
10999
  }
9677
11000
  ),
9678
- toggleState && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
9679
- StateToggle,
9680
- {
9681
- rect: toggleState.rect,
9682
- activeState: toggleState.activeState,
9683
- states: toggleState.states,
9684
- onStateChange: handleStateChange
9685
- }
9686
- ),
9687
- sectionGap && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
9688
- "div",
9689
- {
9690
- "data-ohw-section-insert-line": "",
9691
- className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
9692
- style: { top: sectionGap.y, transform: "translateY(-50%)" },
9693
- children: [
9694
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
9695
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
9696
- Badge,
9697
- {
9698
- 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",
9699
- onClick: () => {
9700
- window.parent.postMessage(
9701
- { type: "ow:add-section", insertAfter: sectionGap.insertAfter, insertBefore: sectionGap.insertBefore },
9702
- "*"
9703
- );
11001
+ toggleState && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(StateToggle, { rect: toggleState.rect, activeState: toggleState.activeState, states: toggleState.states, onStateChange: handleStateChange }),
11002
+ sectionGap && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("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: [
11003
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
11004
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
11005
+ Badge,
11006
+ {
11007
+ 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",
11008
+ onClick: () => {
11009
+ window.parent.postMessage(
11010
+ {
11011
+ type: "ow:add-section",
11012
+ insertAfter: sectionGap.insertAfter,
11013
+ insertBefore: sectionGap.insertBefore
9704
11014
  },
9705
- children: "Add Section"
9706
- }
9707
- ),
9708
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
9709
- ]
9710
- }
9711
- ),
9712
- linkPopover && dialogPortalContainer ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
9713
- LinkPopover,
9714
- {
9715
- panelRef: linkPopoverPanelRef,
9716
- portalContainer: dialogPortalContainer,
9717
- open: true,
9718
- mode: linkPopover.mode ?? "edit",
9719
- pages: sitePages,
9720
- sections: currentSections,
9721
- sectionsByPath,
9722
- initialTarget: linkPopover.target,
9723
- existingTargets: linkPopover.intent === "add-nav" ? getNavbarExistingTargets() : [],
9724
- onClose: closeLinkPopover,
9725
- onSubmit: handleLinkPopoverSubmit
9726
- },
9727
- linkPopover.key
9728
- ) : null
11015
+ "*"
11016
+ );
11017
+ },
11018
+ children: "Add Section"
11019
+ }
11020
+ ),
11021
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
11022
+ ] }),
11023
+ linkPopover && dialogPortalContainer ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(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
9729
11024
  ] }),
9730
11025
  bridgeRoot
9731
11026
  ) : null;
@@ -9736,6 +11031,7 @@ function OhhwellsBridge() {
9736
11031
  CustomToolbarButton,
9737
11032
  CustomToolbarDivider,
9738
11033
  DragHandle,
11034
+ DropIndicator,
9739
11035
  ItemActionToolbar,
9740
11036
  ItemInteractionLayer,
9741
11037
  LinkEditorPanel,
@@ -9750,12 +11046,14 @@ function OhhwellsBridge() {
9750
11046
  TooltipProvider,
9751
11047
  TooltipTrigger,
9752
11048
  buildTarget,
11049
+ dropIndicatorVariants,
9753
11050
  filterAvailablePages,
9754
11051
  getEditModeInitialState,
9755
11052
  isEditSessionActive,
9756
11053
  isValidUrl,
9757
11054
  parseTarget,
9758
11055
  toggleVariants,
11056
+ useOhwCarousel,
9759
11057
  validateUrlInput
9760
11058
  });
9761
11059
  //# sourceMappingURL=index.cjs.map