@ohhwells/bridge 0.1.38 → 0.1.39-next.75

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  // src/OhhwellsBridge.tsx
4
- import React9, { useCallback as useCallback2, useEffect as useEffect4, useLayoutEffect as useLayoutEffect2, useRef as useRef4, useState as useState5 } from "react";
4
+ import React10, { useCallback as useCallback4, useEffect as useEffect8, useLayoutEffect as useLayoutEffect2, useRef as useRef5, useState as useState7 } from "react";
5
5
  import { createRoot } from "react-dom/client";
6
6
  import { flushSync } from "react-dom";
7
7
 
@@ -10,6 +10,9 @@ var linkHrefStore = /* @__PURE__ */ new Map();
10
10
  function setStoredLinkHref(key, href) {
11
11
  linkHrefStore.set(key, href);
12
12
  }
13
+ function getStoredLinkHref(key) {
14
+ return linkHrefStore.get(key);
15
+ }
13
16
  function enforceLinkHrefs() {
14
17
  document.querySelectorAll("[data-ohw-href-key]").forEach((el) => {
15
18
  const key = el.getAttribute("data-ohw-href-key") ?? "";
@@ -45,12 +48,13 @@ function useLinkHrefGuardian(...deps) {
45
48
  }
46
49
 
47
50
  // src/ui/SchedulingWidget.tsx
48
- import { useEffect, useId, useMemo, useRef, useState as useState2 } from "react";
51
+ import { useCallback, useEffect, useId, useMemo, useRef, useState as useState2 } from "react";
49
52
 
50
53
  // src/ui/EmailCaptureModal.tsx
51
54
  import { useState } from "react";
52
55
  import { Dialog } from "radix-ui";
53
56
  import { jsx, jsxs } from "react/jsx-runtime";
57
+ var isValidEmail = (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
54
58
  function Spinner() {
55
59
  return /* @__PURE__ */ jsxs(
56
60
  "svg",
@@ -84,12 +88,17 @@ function EmailCaptureModal({ title, subtitle, onSubmit, onClose }) {
84
88
  const [error, setError] = useState(null);
85
89
  const isBook = title === "Confirm your spot";
86
90
  const handleSubmit = async () => {
87
- if (!email.trim()) return;
91
+ const trimmed = email.trim();
92
+ if (!trimmed) return;
93
+ if (!isValidEmail(trimmed)) {
94
+ setError("Please enter a valid email address.");
95
+ return;
96
+ }
88
97
  setLoading(true);
89
98
  setError(null);
90
99
  try {
91
- await onSubmit(email.trim());
92
- setSubmittedEmail(email.trim());
100
+ await onSubmit(trimmed);
101
+ setSubmittedEmail(trimmed);
93
102
  setSuccess(true);
94
103
  } catch (e) {
95
104
  setError(e instanceof Error ? e.message : "Something went wrong. Please try again.");
@@ -164,7 +173,10 @@ function EmailCaptureModal({ title, subtitle, onSubmit, onClose }) {
164
173
  {
165
174
  type: "email",
166
175
  value: email,
167
- onChange: (e) => setEmail(e.target.value),
176
+ onChange: (e) => {
177
+ setEmail(e.target.value);
178
+ if (error) setError(null);
179
+ },
168
180
  onKeyDown: handleKeyDown,
169
181
  placeholder: "hello@gmail.com",
170
182
  autoFocus: true,
@@ -221,12 +233,20 @@ function formatClassTime(cls) {
221
233
  const em = endTotal % 60;
222
234
  return `${h}:${cls.startTime.minutes}-${eh}:${String(em).padStart(2, "0")}`;
223
235
  }
236
+ function formatBookingLabel(cls) {
237
+ const price = cls.price;
238
+ const isFree = price === null || price === void 0 || `${price}` === "" || `${price}` === "0";
239
+ return isFree ? "Book for free" : `Book for ${cls.currency ?? ""} ${price}`.trim();
240
+ }
224
241
  function getBookingsOnDate(cls, date) {
225
242
  if (!cls.bookings?.length) return 0;
226
- const ds = date.toISOString().split("T")[0];
243
+ const target = new Date(date);
244
+ target.setHours(0, 0, 0, 0);
227
245
  return cls.bookings.filter((b) => {
228
246
  try {
229
- return new Date(b.classDate).toISOString().split("T")[0] === ds;
247
+ const bookingDate = new Date(b.classDate);
248
+ bookingDate.setHours(0, 0, 0, 0);
249
+ return bookingDate.getTime() === target.getTime();
230
250
  } catch {
231
251
  return false;
232
252
  }
@@ -491,7 +511,7 @@ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal
491
511
  }
492
512
  )
493
513
  ] }),
494
- /* @__PURE__ */ jsx2("div", { className: "sm:hidden flex flex-col gap-px", children: isFull ? /* @__PURE__ */ jsx2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
514
+ cls.showAvailability !== false && /* @__PURE__ */ jsx2("div", { className: "sm:hidden flex flex-col gap-px", children: isFull ? /* @__PURE__ */ jsx2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
495
515
  /* @__PURE__ */ jsxs2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
496
516
  available,
497
517
  "/",
@@ -503,9 +523,10 @@ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal
503
523
  /* @__PURE__ */ jsxs2("div", { className: "flex flex-1 flex-col gap-2 min-w-0 sm:contents", children: [
504
524
  /* @__PURE__ */ jsxs2("div", { className: "flex-1 flex flex-col gap-2 min-w-0", children: [
505
525
  /* @__PURE__ */ jsx2("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block truncate", children: cls.name }),
506
- cls.hostName && /* @__PURE__ */ jsx2("span", { className: "font-body text-base font-normal text-(--color-dark,#200C02) opacity-80 block truncate", children: cls.hostName })
526
+ cls.hostName && /* @__PURE__ */ jsx2("span", { className: "font-body text-base font-normal text-(--color-dark,#200C02) opacity-80 block truncate", children: cls.hostName }),
527
+ cls.description && /* @__PURE__ */ jsx2("span", { className: "font-body text-sm font-normal text-(--color-dark,#200C02) opacity-70 block truncate", children: cls.description })
507
528
  ] }),
508
- /* @__PURE__ */ jsx2("div", { className: "hidden sm:flex w-14 shrink-0 flex-col gap-px", children: isFull ? /* @__PURE__ */ jsx2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
529
+ cls.showAvailability !== false && /* @__PURE__ */ jsx2("div", { className: "hidden sm:flex w-14 shrink-0 flex-col gap-px", children: isFull ? /* @__PURE__ */ jsx2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
509
530
  /* @__PURE__ */ jsxs2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
510
531
  available,
511
532
  "/",
@@ -530,7 +551,7 @@ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal
530
551
  if (!cls.id) return;
531
552
  onOpenModal?.(isFull ? "waitlist" : "book", cls.id, selectedDate);
532
553
  },
533
- children: isFull ? "Join Waitlist" : "Book Now"
554
+ children: isFull ? "Join Waitlist" : formatBookingLabel(cls)
534
555
  }
535
556
  )
536
557
  ] })
@@ -572,6 +593,17 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
572
593
  const [isHovered, setIsHovered] = useState2(false);
573
594
  const [modalState, setModalState] = useState2(null);
574
595
  const switchScheduleIdRef = useRef(null);
596
+ const liveScheduleIdRef = useRef(null);
597
+ const refetchLiveSchedule = useCallback(async () => {
598
+ const id = liveScheduleIdRef.current;
599
+ if (!id) return;
600
+ try {
601
+ const res = await fetch(`${API_URL}/api/schedule/id/${id}`);
602
+ const data = await res.json();
603
+ if (data?.id) setSchedule(data);
604
+ } catch {
605
+ }
606
+ }, []);
575
607
  const dates = useMemo(() => {
576
608
  const today = /* @__PURE__ */ new Date();
577
609
  today.setHours(0, 0, 0, 0);
@@ -591,6 +623,18 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
591
623
  }
592
624
  }
593
625
  }, [schedule, dates]);
626
+ useEffect(() => {
627
+ if (typeof window === "undefined") return;
628
+ const onVisible = () => {
629
+ if (!document.hidden) void refetchLiveSchedule();
630
+ };
631
+ window.addEventListener("focus", refetchLiveSchedule);
632
+ document.addEventListener("visibilitychange", onVisible);
633
+ return () => {
634
+ window.removeEventListener("focus", refetchLiveSchedule);
635
+ document.removeEventListener("visibilitychange", onVisible);
636
+ };
637
+ }, [refetchLiveSchedule]);
594
638
  useEffect(() => {
595
639
  if (typeof window === "undefined") return;
596
640
  const isInEditor = window.self !== window.top;
@@ -652,7 +696,7 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
652
696
  setLoading(false);
653
697
  return;
654
698
  }
655
- ;
699
+ liveScheduleIdRef.current = effectiveId;
656
700
  (async () => {
657
701
  try {
658
702
  const res = await fetch(`${API_URL}/api/schedule/id/${effectiveId}`);
@@ -709,18 +753,14 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
709
753
  const data = await res.json().catch(() => ({}));
710
754
  throw new Error(data?.message ?? "Something went wrong. Please try again.");
711
755
  }
756
+ await refetchLiveSchedule();
712
757
  };
713
758
  const handleReplaceSchedule = () => {
714
759
  setIsHovered(false);
715
- if (schedule) {
716
- window.parent.postMessage({
717
- type: "ow:schedule-connected",
718
- schedule: { id: schedule.id, name: schedule.name },
719
- insertAfter
720
- }, "*");
721
- } else {
722
- window.parent.postMessage({ type: "ow:scheduling-not-connected", insertAfter }, "*");
723
- }
760
+ window.parent.postMessage(
761
+ { type: "ow:scheduling-not-connected", insertAfter, currentScheduleId: schedule?.id },
762
+ "*"
763
+ );
724
764
  };
725
765
  if (!inEditor && !loading && !schedule) return null;
726
766
  const sectionId = `scheduling-${insertAfter}`;
@@ -763,7 +803,19 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
763
803
  ),
764
804
  /* @__PURE__ */ jsxs2("div", { className: "max-w-[1280px] mx-auto flex flex-col items-center gap-16", children: [
765
805
  /* @__PURE__ */ jsxs2("div", { className: "flex flex-col items-center gap-4", children: [
766
- /* @__PURE__ */ jsx2("h2", { className: "font-display text-4xl sm:text-5xl font-bold text-center m-0 text-(--color-dark,#200C02)", children: schedule?.name ?? "Book an appointment" }),
806
+ /* @__PURE__ */ jsx2(
807
+ "h2",
808
+ {
809
+ className: "font-display text-center m-0 text-(--color-dark,#200C02)",
810
+ style: {
811
+ fontSize: "var(--fs-section-h2, clamp(40px, 4.4vw, 64px))",
812
+ fontWeight: "var(--font-weight-heading, 400)",
813
+ lineHeight: 1.05,
814
+ letterSpacing: "-0.02em"
815
+ },
816
+ children: schedule?.name ?? "Book an appointment"
817
+ }
818
+ ),
767
819
  schedule?.description && /* @__PURE__ */ jsx2("p", { className: "font-body text-body text-center m-0 text-(--color-accent,#A89B83)", children: schedule.description })
768
820
  ] }),
769
821
  /* @__PURE__ */ jsxs2("div", { className: "w-full flex flex-col items-end gap-3", children: [
@@ -4349,6 +4401,7 @@ function ItemActionToolbar({
4349
4401
  onEditLink,
4350
4402
  onAddItem,
4351
4403
  onMore,
4404
+ editLinkDisabled = false,
4352
4405
  addItemDisabled = true,
4353
4406
  moreDisabled = true,
4354
4407
  tooltipSide = "bottom"
@@ -4357,10 +4410,12 @@ function ItemActionToolbar({
4357
4410
  /* @__PURE__ */ jsx8(
4358
4411
  ToolbarActionTooltip,
4359
4412
  {
4360
- label: "Add link",
4413
+ label: "Edit link",
4361
4414
  side: tooltipSide,
4415
+ disabled: editLinkDisabled,
4362
4416
  buttonProps: {
4363
4417
  onMouseDown: (e) => {
4418
+ if (editLinkDisabled) return;
4364
4419
  e.preventDefault();
4365
4420
  e.stopPropagation();
4366
4421
  onEditLink?.();
@@ -4438,7 +4493,7 @@ function getChromeStyle(state) {
4438
4493
  case "dragging":
4439
4494
  return {
4440
4495
  border: `2px solid ${PRIMARY}`,
4441
- boxShadow: DRAG_SHADOW
4496
+ boxShadow: `${FOCUS_RING}, ${DRAG_SHADOW}`
4442
4497
  };
4443
4498
  default:
4444
4499
  return {};
@@ -4454,6 +4509,8 @@ function ItemInteractionLayer({
4454
4509
  dragHandleLabel = "Reorder item",
4455
4510
  onDragHandleDragStart,
4456
4511
  onDragHandleDragEnd,
4512
+ onItemPointerDown,
4513
+ onItemClick,
4457
4514
  chromeGap = 6,
4458
4515
  className
4459
4516
  }) {
@@ -4461,7 +4518,8 @@ function ItemInteractionLayer({
4461
4518
  const isActive = state === "active-top" || state === "active-bottom";
4462
4519
  const isDragging = state === "dragging";
4463
4520
  const showToolbar = isActive && toolbar;
4464
- const showDragHandle = isActive && showHandle && !isDragging;
4521
+ const showDragHandle = (isActive || isDragging) && showHandle;
4522
+ const itemDragEnabled = isActive && showHandle && !isDragging && !dragDisabled;
4465
4523
  return /* @__PURE__ */ jsxs4(
4466
4524
  "div",
4467
4525
  {
@@ -4486,11 +4544,28 @@ function ItemInteractionLayer({
4486
4544
  style: getChromeStyle(state)
4487
4545
  }
4488
4546
  ),
4547
+ itemDragEnabled && /* @__PURE__ */ jsx9(
4548
+ "div",
4549
+ {
4550
+ "data-ohw-item-drag-surface": "",
4551
+ className: "pointer-events-auto absolute inset-0 cursor-grab rounded-lg active:cursor-grabbing",
4552
+ onPointerDown: (e) => {
4553
+ if (e.button !== 0) return;
4554
+ onItemPointerDown?.(e);
4555
+ },
4556
+ onClick: (e) => {
4557
+ e.preventDefault();
4558
+ e.stopPropagation();
4559
+ onItemClick?.(e.clientX, e.clientY);
4560
+ }
4561
+ }
4562
+ ),
4489
4563
  showDragHandle && /* @__PURE__ */ jsx9(
4490
4564
  "div",
4491
4565
  {
4492
4566
  "data-ohw-drag-handle-container": "",
4493
- className: "pointer-events-auto absolute left-0 top-1/2 -translate-x-[calc(100%+7px)] -translate-y-1/2",
4567
+ className: "pointer-events-auto absolute left-0 top-1/2 z-10 -translate-x-[calc(100%+7px)] -translate-y-1/2",
4568
+ style: isDragging ? { visibility: "hidden" } : void 0,
4494
4569
  children: /* @__PURE__ */ jsx9(
4495
4570
  DragHandle,
4496
4571
  {
@@ -4713,15 +4788,12 @@ function MediaOverlay({
4713
4788
  className: "flex items-center justify-center cursor-pointer",
4714
4789
  style: {
4715
4790
  ...box,
4716
- // When text overlays this media, let clicks fall THROUGH to the text underneath. In the
4717
- // parent this needed a whole `ow:click-at` round-trip to re-hit-test inside the iframe;
4718
- // in-document, pointer-events does it natively. The button below opts back in, so
4719
- // Replace still works.
4720
- pointerEvents: hover.hasTextOverlap ? "none" : "auto",
4791
+ // Keep the full-size visual overlay click-through so carousel controls and editable
4792
+ // content underneath remain interactive. The Replace button opts back in below.
4793
+ pointerEvents: "none",
4721
4794
  boxShadow: "inset 0 0 0 1.5px var(--color-primary)",
4722
4795
  background: "color-mix(in srgb, var(--color-primary) 20%, transparent)"
4723
4796
  },
4724
- onClick: () => onReplace(hover.key),
4725
4797
  children: /* @__PURE__ */ jsxs5(
4726
4798
  Button,
4727
4799
  {
@@ -4746,9 +4818,64 @@ function MediaOverlay({
4746
4818
  ] });
4747
4819
  }
4748
4820
 
4821
+ // src/ui/CarouselOverlay.tsx
4822
+ import { GalleryHorizontal } from "lucide-react";
4823
+ import { jsx as jsx12, jsxs as jsxs6 } from "react/jsx-runtime";
4824
+ var OVERLAY_BUTTON_STYLE2 = {
4825
+ pointerEvents: "auto",
4826
+ fontFamily: "Inter, sans-serif",
4827
+ fontSize: 12,
4828
+ color: "#000"
4829
+ };
4830
+ function CarouselOverlay({
4831
+ hover,
4832
+ onEdit
4833
+ }) {
4834
+ const { rect } = hover;
4835
+ return /* @__PURE__ */ jsx12(
4836
+ "div",
4837
+ {
4838
+ "data-ohw-bridge": "",
4839
+ "data-ohw-carousel-overlay": "",
4840
+ className: "flex cursor-pointer items-center justify-center",
4841
+ style: {
4842
+ position: "fixed",
4843
+ top: rect.top,
4844
+ left: rect.left,
4845
+ width: rect.width,
4846
+ height: rect.height,
4847
+ zIndex: 2147483646,
4848
+ pointerEvents: "auto",
4849
+ boxShadow: "inset 0 0 0 1.5px var(--color-primary)",
4850
+ background: "color-mix(in srgb, var(--color-primary) 20%, transparent)"
4851
+ },
4852
+ onClick: () => onEdit(hover.key),
4853
+ children: /* @__PURE__ */ jsxs6(
4854
+ Button,
4855
+ {
4856
+ "data-ohw-carousel-overlay": "",
4857
+ variant: "outline",
4858
+ size: "sm",
4859
+ className: "cursor-pointer gap-1.5 hover:bg-background",
4860
+ style: OVERLAY_BUTTON_STYLE2,
4861
+ onMouseDown: (e) => e.preventDefault(),
4862
+ onClick: (e) => {
4863
+ e.stopPropagation();
4864
+ onEdit(hover.key);
4865
+ },
4866
+ children: [
4867
+ /* @__PURE__ */ jsx12(GalleryHorizontal, { size: 14 }),
4868
+ "Edit gallery"
4869
+ ]
4870
+ }
4871
+ )
4872
+ }
4873
+ );
4874
+ }
4875
+
4749
4876
  // src/OhhwellsBridge.tsx
4750
- import { createPortal } from "react-dom";
4751
- import { usePathname, useRouter, useSearchParams } from "next/navigation";
4877
+ import { createPortal as createPortal2 } from "react-dom";
4878
+ import { usePathname as usePathname2, useRouter as useRouter2, useSearchParams } from "next/navigation";
4752
4879
 
4753
4880
  // src/lib/session-search.ts
4754
4881
  var STORAGE_KEY = "ohw-preserved-search";
@@ -4778,6 +4905,7 @@ function parseSectionsFromRoot(root) {
4778
4905
  for (const el of root.querySelectorAll("[data-ohw-section]")) {
4779
4906
  const id = el.getAttribute("data-ohw-section") ?? "";
4780
4907
  if (!id || seen.has(id)) continue;
4908
+ if (el.parentElement?.closest("[data-ohw-section]")) continue;
4781
4909
  seen.add(id);
4782
4910
  const label = el.getAttribute("data-ohw-section-label") ?? titleCaseSectionId(id);
4783
4911
  sections.push({ id, label });
@@ -5024,54 +5152,22 @@ function scrollToHashSectionWhenReady(behavior = "smooth") {
5024
5152
  tick();
5025
5153
  }
5026
5154
 
5155
+ // src/ui/link-modal/LinkPopover.tsx
5156
+ import { useEffect as useEffect6 } from "react";
5157
+
5027
5158
  // src/ui/dialog.tsx
5028
5159
  import * as React7 from "react";
5029
5160
  import { Dialog as DialogPrimitive } from "radix-ui";
5030
-
5031
- // src/ui/icons.tsx
5032
- import { jsx as jsx12, jsxs as jsxs6 } from "react/jsx-runtime";
5033
- function IconX({ className, ...props }) {
5034
- return /* @__PURE__ */ jsxs6("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
5035
- /* @__PURE__ */ jsx12("path", { d: "M18 6 6 18" }),
5036
- /* @__PURE__ */ jsx12("path", { d: "m6 6 12 12" })
5037
- ] });
5038
- }
5039
- function IconChevronDown({ className, ...props }) {
5040
- return /* @__PURE__ */ jsx12("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: /* @__PURE__ */ jsx12("path", { d: "m6 9 6 6 6-6" }) });
5041
- }
5042
- function IconFile({ className, ...props }) {
5043
- return /* @__PURE__ */ jsxs6("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
5044
- /* @__PURE__ */ jsx12("path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }),
5045
- /* @__PURE__ */ jsx12("path", { d: "M14 2v4a2 2 0 0 0 2 2h4" })
5046
- ] });
5047
- }
5048
- function IconInfo({ className, ...props }) {
5049
- return /* @__PURE__ */ jsxs6("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
5050
- /* @__PURE__ */ jsx12("circle", { cx: "12", cy: "12", r: "10" }),
5051
- /* @__PURE__ */ jsx12("path", { d: "M12 16v-4" }),
5052
- /* @__PURE__ */ jsx12("path", { d: "M12 8h.01" })
5053
- ] });
5054
- }
5055
- function IconArrowRight({ className, ...props }) {
5056
- return /* @__PURE__ */ jsxs6("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
5057
- /* @__PURE__ */ jsx12("path", { d: "M5 12h14" }),
5058
- /* @__PURE__ */ jsx12("path", { d: "m12 5 7 7-7 7" })
5059
- ] });
5060
- }
5061
- function IconSection({ className, ...props }) {
5062
- return /* @__PURE__ */ jsxs6("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
5063
- /* @__PURE__ */ jsx12("rect", { width: "18", height: "7", x: "3", y: "3", rx: "1" }),
5064
- /* @__PURE__ */ jsx12("rect", { width: "9", height: "7", x: "3", y: "14", rx: "1" }),
5065
- /* @__PURE__ */ jsx12("rect", { width: "5", height: "7", x: "16", y: "14", rx: "1" })
5066
- ] });
5067
- }
5068
-
5069
- // src/ui/dialog.tsx
5161
+ import { X } from "lucide-react";
5070
5162
  import { jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
5071
- function Dialog2({ ...props }) {
5163
+ function Dialog2({
5164
+ ...props
5165
+ }) {
5072
5166
  return /* @__PURE__ */ jsx13(DialogPrimitive.Root, { "data-slot": "dialog", ...props });
5073
5167
  }
5074
- function DialogPortal({ ...props }) {
5168
+ function DialogPortal({
5169
+ ...props
5170
+ }) {
5075
5171
  return /* @__PURE__ */ jsx13(DialogPrimitive.Portal, { ...props });
5076
5172
  }
5077
5173
  function DialogOverlay({
@@ -5088,52 +5184,69 @@ function DialogOverlay({
5088
5184
  }
5089
5185
  );
5090
5186
  }
5091
- var DialogContent = React7.forwardRef(({ className, children, showCloseButton = true, container, ...props }, ref) => {
5092
- const positionMode = container ? "absolute" : "fixed";
5093
- return /* @__PURE__ */ jsxs7(DialogPortal, { container: container ?? void 0, children: [
5094
- /* @__PURE__ */ jsx13(DialogOverlay, { className: cn(positionMode, "inset-0") }),
5095
- /* @__PURE__ */ jsxs7(
5096
- DialogPrimitive.Content,
5097
- {
5098
- ref,
5099
- "data-slot": "dialog-content",
5100
- className: cn(
5101
- positionMode,
5102
- "left-1/2 top-1/2 z-[2147483647] flex w-full max-w-[483px] -translate-x-1/2 -translate-y-1/2 flex-col overflow-hidden",
5103
- "rounded-xl border border-border bg-background font-sans shadow-lg outline-none",
5104
- container ? "max-h-[calc(100%-32px)] max-w-[calc(100%-16px)]" : "max-h-[calc(100vh-32px)] max-w-[calc(100vw-16px)]",
5105
- className
5106
- ),
5107
- onMouseDown: (e) => e.stopPropagation(),
5108
- ...props,
5109
- children: [
5110
- children,
5111
- showCloseButton ? /* @__PURE__ */ jsx13(
5112
- DialogPrimitive.Close,
5113
- {
5114
- type: "button",
5115
- className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
5116
- "aria-label": "Close",
5117
- children: /* @__PURE__ */ jsx13(IconX, { "aria-hidden": true })
5118
- }
5119
- ) : null
5120
- ]
5121
- }
5122
- )
5123
- ] });
5124
- });
5187
+ var DialogContent = React7.forwardRef(
5188
+ ({ className, children, showCloseButton = true, container, ...props }, ref) => {
5189
+ const positionMode = container ? "absolute" : "fixed";
5190
+ return /* @__PURE__ */ jsxs7(DialogPortal, { container: container ?? void 0, children: [
5191
+ /* @__PURE__ */ jsx13(DialogOverlay, { className: cn(positionMode, "inset-0") }),
5192
+ /* @__PURE__ */ jsxs7(
5193
+ DialogPrimitive.Content,
5194
+ {
5195
+ ref,
5196
+ "data-slot": "dialog-content",
5197
+ className: cn(
5198
+ positionMode,
5199
+ "left-1/2 top-1/2 z-[2147483647] flex w-full max-w-[448px] -translate-x-1/2 -translate-y-1/2 flex-col overflow-visible",
5200
+ "rounded-xl border border-border bg-background font-sans shadow-lg outline-none",
5201
+ container ? "max-h-[calc(100%-32px)]" : "max-h-[calc(100vh-32px)]",
5202
+ className
5203
+ ),
5204
+ onMouseDown: (e) => e.stopPropagation(),
5205
+ ...props,
5206
+ children: [
5207
+ children,
5208
+ showCloseButton ? /* @__PURE__ */ jsx13(
5209
+ DialogPrimitive.Close,
5210
+ {
5211
+ type: "button",
5212
+ className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
5213
+ "aria-label": "Close",
5214
+ children: /* @__PURE__ */ jsx13(X, { size: 16, "aria-hidden": true })
5215
+ }
5216
+ ) : null
5217
+ ]
5218
+ }
5219
+ )
5220
+ ] });
5221
+ }
5222
+ );
5125
5223
  DialogContent.displayName = DialogPrimitive.Content.displayName;
5126
- function DialogHeader({ className, ...props }) {
5224
+ function DialogHeader({
5225
+ className,
5226
+ ...props
5227
+ }) {
5127
5228
  return /* @__PURE__ */ jsx13("div", { className: cn("flex flex-col gap-1.5", className), ...props });
5128
5229
  }
5129
- function DialogFooter({ className, ...props }) {
5130
- return /* @__PURE__ */ jsx13("div", { className: cn("flex items-center justify-end gap-2", className), ...props });
5230
+ function DialogFooter({
5231
+ className,
5232
+ ...props
5233
+ }) {
5234
+ return /* @__PURE__ */ jsx13(
5235
+ "div",
5236
+ {
5237
+ className: cn("flex items-center justify-end gap-2", className),
5238
+ ...props
5239
+ }
5240
+ );
5131
5241
  }
5132
5242
  var DialogTitle = React7.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx13(
5133
5243
  DialogPrimitive.Title,
5134
5244
  {
5135
5245
  ref,
5136
- className: cn("text-lg font-semibold leading-none tracking-tight text-card-foreground", className),
5246
+ className: cn(
5247
+ "text-lg font-semibold leading-none tracking-tight text-card-foreground",
5248
+ className
5249
+ ),
5137
5250
  ...props
5138
5251
  }
5139
5252
  ));
@@ -5149,19 +5262,40 @@ var DialogDescription = React7.forwardRef(({ className, ...props }, ref) => /* @
5149
5262
  DialogDescription.displayName = DialogPrimitive.Description.displayName;
5150
5263
  var DialogClose = DialogPrimitive.Close;
5151
5264
 
5265
+ // src/ui/link-modal/LinkEditorPanel.tsx
5266
+ import { Info, X as X3 } from "lucide-react";
5267
+
5152
5268
  // src/ui/link-modal/DestinationBreadcrumb.tsx
5269
+ import { ArrowRight, File, GalleryVertical } from "lucide-react";
5153
5270
  import { jsx as jsx14, jsxs as jsxs8 } from "react/jsx-runtime";
5154
- function DestinationBreadcrumb({ pageTitle, sectionLabel }) {
5271
+ function DestinationBreadcrumb({
5272
+ pageTitle,
5273
+ sectionLabel
5274
+ }) {
5155
5275
  return /* @__PURE__ */ jsxs8("div", { className: "flex w-full flex-col gap-2", children: [
5156
- /* @__PURE__ */ jsx14("p", { className: "text-sm font-medium text-foreground", children: "Destination" }),
5276
+ /* @__PURE__ */ jsx14("p", { className: "text-sm font-medium! text-foreground m-0", children: "Destination" }),
5157
5277
  /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-3", children: [
5158
5278
  /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-2", children: [
5159
- /* @__PURE__ */ jsx14(IconFile, { className: "shrink-0 text-foreground", "aria-hidden": true }),
5160
- /* @__PURE__ */ jsx14("span", { className: "text-sm font-medium leading-none text-foreground", children: pageTitle })
5279
+ /* @__PURE__ */ jsx14(File, { size: 16, className: "shrink-0 text-foreground", "aria-hidden": true }),
5280
+ /* @__PURE__ */ jsx14("span", { className: "text-sm font-medium leading-none text-foreground!", children: pageTitle })
5161
5281
  ] }),
5162
- /* @__PURE__ */ jsx14(IconArrowRight, { className: "shrink-0 text-muted-foreground", "aria-hidden": true }),
5282
+ /* @__PURE__ */ jsx14(
5283
+ ArrowRight,
5284
+ {
5285
+ size: 16,
5286
+ className: "shrink-0 text-muted-foreground",
5287
+ "aria-hidden": true
5288
+ }
5289
+ ),
5163
5290
  /* @__PURE__ */ jsxs8("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [
5164
- /* @__PURE__ */ jsx14(IconSection, { className: "shrink-0 text-foreground", "aria-hidden": true }),
5291
+ /* @__PURE__ */ jsx14(
5292
+ GalleryVertical,
5293
+ {
5294
+ size: 16,
5295
+ className: "shrink-0 text-foreground",
5296
+ "aria-hidden": true
5297
+ }
5298
+ ),
5165
5299
  /* @__PURE__ */ jsx14("span", { className: "truncate text-sm font-medium leading-none text-foreground", children: sectionLabel })
5166
5300
  ] })
5167
5301
  ] })
@@ -5169,8 +5303,13 @@ function DestinationBreadcrumb({ pageTitle, sectionLabel }) {
5169
5303
  }
5170
5304
 
5171
5305
  // src/ui/link-modal/SectionTreeItem.tsx
5306
+ import { GalleryVertical as GalleryVertical2 } from "lucide-react";
5172
5307
  import { jsx as jsx15, jsxs as jsxs9 } from "react/jsx-runtime";
5173
- function SectionTreeItem({ section, onSelect, selected }) {
5308
+ function SectionTreeItem({
5309
+ section,
5310
+ onSelect,
5311
+ selected
5312
+ }) {
5174
5313
  const interactive = Boolean(onSelect);
5175
5314
  return /* @__PURE__ */ jsxs9("div", { className: "flex h-9 w-full items-end pl-3", children: [
5176
5315
  /* @__PURE__ */ jsx15(
@@ -5198,25 +5337,23 @@ function SectionTreeItem({ section, onSelect, selected }) {
5198
5337
  interactive && selected && "border-primary"
5199
5338
  ),
5200
5339
  children: [
5201
- /* @__PURE__ */ jsx15(IconSection, { className: "shrink-0 text-foreground", "aria-hidden": true }),
5340
+ /* @__PURE__ */ jsx15(
5341
+ GalleryVertical2,
5342
+ {
5343
+ size: 16,
5344
+ className: "shrink-0 text-foreground",
5345
+ "aria-hidden": true
5346
+ }
5347
+ ),
5202
5348
  /* @__PURE__ */ jsx15("span", { className: "truncate text-sm font-normal leading-5 text-foreground", children: section.label })
5203
5349
  ]
5204
5350
  }
5205
5351
  )
5206
5352
  ] });
5207
5353
  }
5208
- function SectionPickerList({ sections, onSelect }) {
5209
- if (sections.length === 0) {
5210
- return /* @__PURE__ */ jsx15("p", { className: "text-sm text-muted-foreground", children: "No sections found on this page." });
5211
- }
5212
- return /* @__PURE__ */ jsxs9("div", { className: "flex flex-col gap-1", children: [
5213
- /* @__PURE__ */ jsx15("p", { className: "text-sm text-muted-foreground", children: "Pick a section this link should scroll to." }),
5214
- sections.map((section) => /* @__PURE__ */ jsx15(SectionTreeItem, { section, onSelect }, section.id))
5215
- ] });
5216
- }
5217
5354
 
5218
5355
  // src/ui/link-modal/UrlOrPageInput.tsx
5219
- import { useId as useId2, useRef as useRef3, useState as useState3 } from "react";
5356
+ import { useEffect as useEffect3, useId as useId2, useRef as useRef3, useState as useState3 } from "react";
5220
5357
 
5221
5358
  // src/ui/input.tsx
5222
5359
  import * as React8 from "react";
@@ -5255,8 +5392,11 @@ function Label({ className, ...props }) {
5255
5392
  }
5256
5393
 
5257
5394
  // src/ui/link-modal/UrlOrPageInput.tsx
5395
+ import { ChevronDown, File as File2, X as X2 } from "lucide-react";
5258
5396
  import { jsx as jsx18, jsxs as jsxs10 } from "react/jsx-runtime";
5259
- function FieldChevron({ onClick }) {
5397
+ function FieldChevron({
5398
+ onClick
5399
+ }) {
5260
5400
  return /* @__PURE__ */ jsx18(
5261
5401
  "button",
5262
5402
  {
@@ -5265,7 +5405,7 @@ function FieldChevron({ onClick }) {
5265
5405
  onClick,
5266
5406
  "aria-label": "Open page list",
5267
5407
  tabIndex: -1,
5268
- children: /* @__PURE__ */ jsx18(IconChevronDown, {})
5408
+ children: /* @__PURE__ */ jsx18(ChevronDown, { size: 16 })
5269
5409
  }
5270
5410
  );
5271
5411
  }
@@ -5283,7 +5423,29 @@ function UrlOrPageInput({
5283
5423
  }) {
5284
5424
  const inputId = useId2();
5285
5425
  const inputRef = useRef3(null);
5426
+ const rootRef = useRef3(null);
5286
5427
  const [isFocused, setIsFocused] = useState3(false);
5428
+ useEffect3(() => {
5429
+ if (!dropdownOpen) return;
5430
+ const isOutside = (e) => {
5431
+ const root = rootRef.current;
5432
+ if (!root) return true;
5433
+ const path = typeof e.composedPath === "function" ? e.composedPath() : [];
5434
+ if (path.includes(root)) return false;
5435
+ const target = e.target;
5436
+ return !(target instanceof Node && root.contains(target));
5437
+ };
5438
+ const closeIfOutside = (e) => {
5439
+ if (!isOutside(e)) return;
5440
+ onDropdownOpenChange(false);
5441
+ };
5442
+ document.addEventListener("pointerdown", closeIfOutside, true);
5443
+ document.addEventListener("mousedown", closeIfOutside, true);
5444
+ return () => {
5445
+ document.removeEventListener("pointerdown", closeIfOutside, true);
5446
+ document.removeEventListener("mousedown", closeIfOutside, true);
5447
+ };
5448
+ }, [dropdownOpen, onDropdownOpenChange]);
5287
5449
  const openDropdown = () => {
5288
5450
  if (readOnly || filteredPages.length === 0) return;
5289
5451
  onDropdownOpenChange(true);
@@ -5301,14 +5463,21 @@ function UrlOrPageInput({
5301
5463
  requestAnimationFrame(() => inputRef.current?.focus());
5302
5464
  };
5303
5465
  const fieldClassName = cn(
5304
- "data-ohw-link-field flex h-10 w-full items-center overflow-hidden rounded-md border bg-background pl-3 pr-3 py-2 outline-none transition-[border-color,box-shadow]",
5466
+ "data-ohw-link-field flex h-[36px] w-full items-center overflow-hidden rounded-md border bg-background pl-3 pr-3 py-2 outline-none transition-[border-color,box-shadow]",
5305
5467
  urlError ? "border-destructive shadow-[0_0_0_1px_var(--ohw-destructive)]" : isFocused ? "border-primary shadow-[0_0_0_1px_var(--ohw-primary)]" : "border-input"
5306
5468
  );
5307
5469
  return /* @__PURE__ */ jsxs10("div", { className: "flex w-full flex-col gap-2 p-0", children: [
5308
5470
  /* @__PURE__ */ jsx18(Label, { htmlFor: inputId, className: cn(urlError && "text-destructive"), children: "Destination" }),
5309
- /* @__PURE__ */ jsxs10("div", { className: "relative w-full", children: [
5471
+ /* @__PURE__ */ jsxs10("div", { ref: rootRef, className: "relative w-full", children: [
5310
5472
  /* @__PURE__ */ jsxs10("div", { "data-ohw-link-field": true, className: fieldClassName, children: [
5311
- selectedPage ? /* @__PURE__ */ jsx18("div", { className: "flex shrink-0 items-center pr-2", children: /* @__PURE__ */ jsx18(IconFile, { className: "text-foreground", "aria-hidden": true }) }) : null,
5473
+ selectedPage ? /* @__PURE__ */ jsx18("div", { className: "flex shrink-0 items-center pr-2", children: /* @__PURE__ */ jsx18(
5474
+ File2,
5475
+ {
5476
+ size: 16,
5477
+ className: "shrink-0 text-foreground",
5478
+ "aria-hidden": true
5479
+ }
5480
+ ) }) : null,
5312
5481
  readOnly ? /* @__PURE__ */ jsx18("span", { className: "min-w-0 flex-1 truncate text-sm leading-5 text-foreground", children: selectedPage?.title ?? value }) : /* @__PURE__ */ jsx18(
5313
5482
  Input,
5314
5483
  {
@@ -5320,7 +5489,14 @@ function UrlOrPageInput({
5320
5489
  setIsFocused(true);
5321
5490
  if (!selectedPage) openDropdown();
5322
5491
  },
5323
- onBlur: () => setIsFocused(false),
5492
+ onBlur: () => {
5493
+ setIsFocused(false);
5494
+ requestAnimationFrame(() => {
5495
+ if (!rootRef.current?.contains(document.activeElement)) {
5496
+ onDropdownOpenChange(false);
5497
+ }
5498
+ });
5499
+ },
5324
5500
  placeholder,
5325
5501
  className: cn(
5326
5502
  "min-w-0 flex-1 truncate border-0 bg-transparent p-0 text-sm font-normal leading-5 shadow-none outline-none ring-0 caret-foreground",
@@ -5336,7 +5512,7 @@ function UrlOrPageInput({
5336
5512
  onMouseDown: clearSelection,
5337
5513
  "aria-label": "Clear selected page",
5338
5514
  tabIndex: -1,
5339
- children: /* @__PURE__ */ jsx18(IconX, { "aria-hidden": true })
5515
+ children: /* @__PURE__ */ jsx18(X2, { size: 16, "aria-hidden": true })
5340
5516
  }
5341
5517
  ) : null,
5342
5518
  !readOnly ? /* @__PURE__ */ jsx18(FieldChevron, { onClick: toggleDropdown }) : null
@@ -5345,7 +5521,7 @@ function UrlOrPageInput({
5345
5521
  "div",
5346
5522
  {
5347
5523
  "data-ohw-link-page-dropdown": "",
5348
- className: "absolute left-0 right-0 h-20 top-[calc(100%+4px)] z-10 max-h-48 overflow-auto rounded-lg border border-border bg-popover py-1 shadow-lg",
5524
+ className: "absolute left-0 right-0 top-[calc(100%+4px)] z-50 max-h-48 overflow-auto rounded-lg border border-border bg-popover py-1 shadow-lg",
5349
5525
  onMouseDown: (e) => e.preventDefault(),
5350
5526
  children: filteredPages.map((page) => /* @__PURE__ */ jsxs10(
5351
5527
  "button",
@@ -5354,7 +5530,7 @@ function UrlOrPageInput({
5354
5530
  className: "flex h-9 w-full items-center gap-2 border-0 bg-transparent px-3 text-left text-sm leading-5 text-foreground outline-none hover:bg-muted",
5355
5531
  onClick: () => onPageSelect(page),
5356
5532
  children: [
5357
- /* @__PURE__ */ jsx18(IconFile, { className: "shrink-0", "aria-hidden": true }),
5533
+ /* @__PURE__ */ jsx18(File2, { size: 16, className: "shrink-0", "aria-hidden": true }),
5358
5534
  /* @__PURE__ */ jsx18("span", { className: "truncate", children: page.title })
5359
5535
  ]
5360
5536
  },
@@ -5367,38 +5543,466 @@ function UrlOrPageInput({
5367
5543
  ] });
5368
5544
  }
5369
5545
 
5370
- // src/ui/link-modal/useLinkModalState.ts
5371
- import { useCallback, useEffect as useEffect3, useMemo as useMemo2, useState as useState4 } from "react";
5372
- function useLinkModalState({
5373
- open,
5374
- mode,
5375
- pages,
5376
- sections: _sections,
5377
- sectionsByPath,
5378
- initialTarget,
5379
- existingTargets,
5380
- onClose,
5381
- onSubmit
5382
- }) {
5383
- const availablePages = useMemo2(
5384
- () => mode === "create" ? filterAvailablePages(pages, existingTargets) : pages,
5385
- [mode, pages, existingTargets]
5546
+ // src/ui/link-modal/LinkEditorPanel.tsx
5547
+ import { Fragment as Fragment3, jsx as jsx19, jsxs as jsxs11 } from "react/jsx-runtime";
5548
+ function LinkEditorPanel({ state, onClose }) {
5549
+ const isCancel = state.secondaryLabel === "Cancel" || state.secondaryLabel === "Back to sections";
5550
+ return /* @__PURE__ */ jsxs11(Fragment3, { children: [
5551
+ /* @__PURE__ */ jsx19(DialogClose, { asChild: true, children: /* @__PURE__ */ jsx19(
5552
+ "button",
5553
+ {
5554
+ type: "button",
5555
+ className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50 h-7",
5556
+ "aria-label": "Close",
5557
+ onClick: onClose,
5558
+ children: /* @__PURE__ */ jsx19(X3, { size: 16, "aria-hidden": true })
5559
+ }
5560
+ ) }),
5561
+ /* @__PURE__ */ jsx19(DialogHeader, { className: "w-full gap-1.5 p-6 pr-12", children: /* @__PURE__ */ jsx19(DialogTitle, { className: "m-0 w-full break-words", children: state.title }) }),
5562
+ /* @__PURE__ */ jsxs11("div", { className: "flex w-full flex-col gap-3 px-6 pb-8 pt-1", children: [
5563
+ state.showBreadcrumb && state.selectedPage && state.selectedSection ? /* @__PURE__ */ jsx19(
5564
+ DestinationBreadcrumb,
5565
+ {
5566
+ pageTitle: state.selectedPage.title,
5567
+ sectionLabel: state.selectedSection.label
5568
+ }
5569
+ ) : /* @__PURE__ */ jsx19(
5570
+ UrlOrPageInput,
5571
+ {
5572
+ value: state.searchValue,
5573
+ selectedPage: state.selectedPage,
5574
+ dropdownOpen: state.dropdownOpen,
5575
+ onDropdownOpenChange: state.setDropdownOpen,
5576
+ filteredPages: state.filteredPages,
5577
+ onInputChange: state.handleInputChange,
5578
+ onPageSelect: state.handlePageSelect,
5579
+ urlError: state.urlError
5580
+ }
5581
+ ),
5582
+ state.showChooseSection ? /* @__PURE__ */ jsxs11("div", { className: "flex flex-col justify-center gap-2", children: [
5583
+ /* @__PURE__ */ jsx19(
5584
+ Button,
5585
+ {
5586
+ type: "button",
5587
+ variant: "outline",
5588
+ className: "w-fit cursor-pointer",
5589
+ size: "sm",
5590
+ onClick: state.handleChooseSection,
5591
+ children: "Choose a section"
5592
+ }
5593
+ ),
5594
+ /* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-1 text-sm text-muted-foreground", children: [
5595
+ /* @__PURE__ */ jsx19(Info, { size: 16, className: "shrink-0", "aria-hidden": true }),
5596
+ /* @__PURE__ */ jsx19("span", { children: "Pick a section this link should scroll to." })
5597
+ ] })
5598
+ ] }) : null,
5599
+ state.showSectionRow && state.selectedSection ? /* @__PURE__ */ jsx19(SectionTreeItem, { section: state.selectedSection, selected: true }) : null
5600
+ ] }),
5601
+ /* @__PURE__ */ jsxs11(DialogFooter, { className: "w-full px-6 pb-6", children: [
5602
+ /* @__PURE__ */ jsx19(
5603
+ Button,
5604
+ {
5605
+ type: "button",
5606
+ variant: isCancel ? "outline" : "ghost",
5607
+ className: "leading-6 shadow-none cursor-pointer",
5608
+ style: isCancel ? {
5609
+ backgroundColor: "var(--ohw-background)",
5610
+ borderColor: "var(--ohw-border)",
5611
+ color: "var(--ohw-foreground)"
5612
+ } : void 0,
5613
+ onClick: state.handleSecondary,
5614
+ children: state.secondaryLabel
5615
+ }
5616
+ ),
5617
+ /* @__PURE__ */ jsx19(
5618
+ Button,
5619
+ {
5620
+ type: "button",
5621
+ className: "border-0 leading-6 shadow-none hover:opacity-90 disabled:opacity-50 cursor-pointer",
5622
+ style: {
5623
+ backgroundColor: "var(--ohw-primary)",
5624
+ color: "var(--ohw-primary-foreground)"
5625
+ },
5626
+ disabled: !state.isValid,
5627
+ onClick: state.handleSubmit,
5628
+ children: state.submitLabel
5629
+ }
5630
+ )
5631
+ ] })
5632
+ ] });
5633
+ }
5634
+
5635
+ // src/ui/link-modal/SectionPickerOverlay.tsx
5636
+ import { useCallback as useCallback2, useEffect as useEffect4, useMemo as useMemo2, useRef as useRef4, useState as useState4 } from "react";
5637
+ import { createPortal } from "react-dom";
5638
+ import { ArrowLeft, Check } from "lucide-react";
5639
+ import { usePathname, useRouter } from "next/navigation";
5640
+ import { jsx as jsx20, jsxs as jsxs12 } from "react/jsx-runtime";
5641
+ var DIM_OVERLAY = "rgba(0, 0, 0, 0.45)";
5642
+ function rectsEqual(a, b) {
5643
+ if (a.size !== b.size) return false;
5644
+ for (const [id, rect] of a) {
5645
+ const other = b.get(id);
5646
+ if (!other || rect.top !== other.top || rect.left !== other.left || rect.width !== other.width || rect.height !== other.height) {
5647
+ return false;
5648
+ }
5649
+ }
5650
+ return true;
5651
+ }
5652
+ function readSectionRects(sectionIds) {
5653
+ const next = /* @__PURE__ */ new Map();
5654
+ for (const id of sectionIds) {
5655
+ const el = document.querySelector(
5656
+ `[data-ohw-section="${CSS.escape(id)}"]`
5657
+ );
5658
+ if (el) next.set(id, el.getBoundingClientRect());
5659
+ }
5660
+ return next;
5661
+ }
5662
+ function hitTestSectionId(x, y, sectionIds, rects) {
5663
+ for (const id of sectionIds) {
5664
+ const rect = rects.get(id);
5665
+ if (!rect || rect.width <= 0 || rect.height <= 0) continue;
5666
+ if (x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom) {
5667
+ return id;
5668
+ }
5669
+ }
5670
+ return null;
5671
+ }
5672
+ function useSectionRects(sectionIdsKey, enabled) {
5673
+ const [rects, setRects] = useState4(/* @__PURE__ */ new Map());
5674
+ const sectionIds = useMemo2(
5675
+ () => sectionIdsKey ? sectionIdsKey.split("\0") : [],
5676
+ [sectionIdsKey]
5386
5677
  );
5387
- const [searchValue, setSearchValue] = useState4("");
5388
- const [selectedPage, setSelectedPage] = useState4(null);
5389
- const [selectedSection, setSelectedSection] = useState4(null);
5390
- const [step, setStep] = useState4("input");
5391
- const [dropdownOpen, setDropdownOpen] = useState4(false);
5392
- const [urlError, setUrlError] = useState4("");
5393
- const reset = useCallback(() => {
5394
- setSearchValue("");
5395
- setSelectedPage(null);
5396
- setSelectedSection(null);
5678
+ useEffect4(() => {
5679
+ if (!enabled || sectionIds.length === 0) {
5680
+ setRects((prev) => prev.size === 0 ? prev : /* @__PURE__ */ new Map());
5681
+ return;
5682
+ }
5683
+ const update = () => {
5684
+ const next = readSectionRects(sectionIds);
5685
+ setRects((prev) => rectsEqual(prev, next) ? prev : next);
5686
+ };
5687
+ update();
5688
+ const opts = { capture: true, passive: true };
5689
+ window.addEventListener("scroll", update, opts);
5690
+ window.addEventListener("resize", update);
5691
+ const vv = window.visualViewport;
5692
+ vv?.addEventListener("resize", update);
5693
+ vv?.addEventListener("scroll", update);
5694
+ const ro = new ResizeObserver(update);
5695
+ for (const id of sectionIds) {
5696
+ const el = document.querySelector(
5697
+ `[data-ohw-section="${CSS.escape(id)}"]`
5698
+ );
5699
+ if (el) ro.observe(el);
5700
+ }
5701
+ return () => {
5702
+ window.removeEventListener("scroll", update, opts);
5703
+ window.removeEventListener("resize", update);
5704
+ vv?.removeEventListener("resize", update);
5705
+ vv?.removeEventListener("scroll", update);
5706
+ ro.disconnect();
5707
+ };
5708
+ }, [sectionIds, enabled]);
5709
+ return rects;
5710
+ }
5711
+ function SectionPickerOverlay({
5712
+ pagePath,
5713
+ sections,
5714
+ onBack,
5715
+ onSelect
5716
+ }) {
5717
+ const router = useRouter();
5718
+ const pathname = usePathname();
5719
+ const [selectedId, setSelectedId] = useState4(null);
5720
+ const [hoveredId, setHoveredId] = useState4(null);
5721
+ const pointerRef = useRef4(null);
5722
+ const pointerScreenRef = useRef4(null);
5723
+ const iframeOffsetTopRef = useRef4(null);
5724
+ const sectionIdsRef = useRef4([]);
5725
+ const [chromeClip, setChromeClip] = useState4(
5726
+ () => ({
5727
+ top: 0,
5728
+ bottom: typeof window !== "undefined" ? window.innerHeight : 0
5729
+ })
5730
+ );
5731
+ const normalizedTarget = normalizePath(pagePath);
5732
+ const isOnTargetPage = normalizePath(pathname) === normalizedTarget;
5733
+ useEffect4(() => {
5734
+ if (isOnTargetPage) return;
5735
+ const search = readPreservedSearch();
5736
+ router.push(`${normalizedTarget}${search}`);
5737
+ }, [isOnTargetPage, normalizedTarget, router]);
5738
+ useEffect4(() => {
5739
+ document.documentElement.setAttribute("data-ohw-section-picking", "");
5740
+ document.querySelectorAll("[data-ohw-hovered]").forEach((el) => {
5741
+ el.removeAttribute("data-ohw-hovered");
5742
+ });
5743
+ document.querySelectorAll("[data-ohw-state-hovered]").forEach((el) => {
5744
+ el.removeAttribute("data-ohw-state-hovered");
5745
+ });
5746
+ return () => {
5747
+ document.documentElement.removeAttribute("data-ohw-section-picking");
5748
+ };
5749
+ }, []);
5750
+ const liveSections = useMemo2(() => {
5751
+ if (!isOnTargetPage) return sections;
5752
+ const live = collectSectionsFromDom();
5753
+ if (live.length === 0) return sections;
5754
+ const labels = new Map(sections.map((s) => [s.id, s.label]));
5755
+ return live.map((s) => ({ id: s.id, label: labels.get(s.id) ?? s.label }));
5756
+ }, [isOnTargetPage, sections, pathname]);
5757
+ const sectionIdsKey = useMemo2(
5758
+ () => liveSections.map((s) => s.id).join("\0"),
5759
+ [liveSections]
5760
+ );
5761
+ const sectionIds = useMemo2(
5762
+ () => liveSections.map((s) => s.id),
5763
+ [liveSections]
5764
+ );
5765
+ sectionIdsRef.current = sectionIds;
5766
+ const rects = useSectionRects(sectionIdsKey, isOnTargetPage);
5767
+ const applyHoverAt = useCallback2(
5768
+ (x, y, liveRects) => {
5769
+ const ids = sectionIdsRef.current;
5770
+ const map = liveRects ?? readSectionRects(ids);
5771
+ const next = hitTestSectionId(x, y, ids, map);
5772
+ setHoveredId((prev) => prev === next ? prev : next);
5773
+ },
5774
+ []
5775
+ );
5776
+ useEffect4(() => {
5777
+ const applyClip = (iframeOffsetTop, visibleCanvasTop, canvasH) => {
5778
+ const top = Math.max(0, visibleCanvasTop - iframeOffsetTop);
5779
+ const bottom = Math.min(
5780
+ window.innerHeight,
5781
+ visibleCanvasTop + canvasH - iframeOffsetTop
5782
+ );
5783
+ setChromeClip(
5784
+ (prev) => prev.top === top && prev.bottom === bottom ? prev : { top, bottom: Math.max(top, bottom) }
5785
+ );
5786
+ };
5787
+ const onParentScroll = (e) => {
5788
+ if (e.data?.type !== "ow:parent-scroll") return;
5789
+ const { iframeOffsetTop, headerH, canvasH } = e.data;
5790
+ applyClip(iframeOffsetTop, headerH, canvasH);
5791
+ iframeOffsetTopRef.current = iframeOffsetTop;
5792
+ if (!pointerScreenRef.current && pointerRef.current) {
5793
+ pointerScreenRef.current = {
5794
+ x: pointerRef.current.x,
5795
+ y: pointerRef.current.y + iframeOffsetTop
5796
+ };
5797
+ }
5798
+ const screen = pointerScreenRef.current;
5799
+ if (screen) {
5800
+ const next = { x: screen.x, y: screen.y - iframeOffsetTop };
5801
+ pointerRef.current = next;
5802
+ applyHoverAt(next.x, next.y);
5803
+ }
5804
+ };
5805
+ const onResize = () => {
5806
+ setChromeClip((prev) => {
5807
+ const bottom = Math.max(prev.top, window.innerHeight);
5808
+ return prev.bottom === bottom ? prev : { ...prev, bottom };
5809
+ });
5810
+ };
5811
+ window.addEventListener("message", onParentScroll);
5812
+ window.addEventListener("resize", onResize);
5813
+ return () => {
5814
+ window.removeEventListener("message", onParentScroll);
5815
+ window.removeEventListener("resize", onResize);
5816
+ };
5817
+ }, [applyHoverAt]);
5818
+ useEffect4(() => {
5819
+ const ptr = pointerRef.current;
5820
+ if (!ptr) return;
5821
+ applyHoverAt(ptr.x, ptr.y, rects);
5822
+ }, [rects, applyHoverAt]);
5823
+ useEffect4(() => {
5824
+ const rememberPointer = (clientX, clientY) => {
5825
+ pointerRef.current = { x: clientX, y: clientY };
5826
+ const top = iframeOffsetTopRef.current;
5827
+ if (top != null) {
5828
+ pointerScreenRef.current = { x: clientX, y: clientY + top };
5829
+ }
5830
+ applyHoverAt(clientX, clientY, rects);
5831
+ };
5832
+ const onPointerMove = (e) => {
5833
+ rememberPointer(e.clientX, e.clientY);
5834
+ };
5835
+ const onPointerSync = (e) => {
5836
+ if (e.data?.type !== "ow:pointer-sync") return;
5837
+ const { clientX, clientY } = e.data;
5838
+ rememberPointer(clientX, clientY);
5839
+ };
5840
+ window.addEventListener("pointermove", onPointerMove, { passive: true });
5841
+ window.addEventListener("message", onPointerSync);
5842
+ return () => {
5843
+ window.removeEventListener("pointermove", onPointerMove);
5844
+ window.removeEventListener("message", onPointerSync);
5845
+ };
5846
+ }, [rects, applyHoverAt]);
5847
+ const handleSelect = useCallback2(
5848
+ (section) => {
5849
+ if (selectedId) return;
5850
+ setSelectedId(section.id);
5851
+ onSelect(section);
5852
+ },
5853
+ [selectedId, onSelect]
5854
+ );
5855
+ useEffect4(() => {
5856
+ const onKeyDown = (e) => {
5857
+ if (e.key === "Escape") {
5858
+ e.preventDefault();
5859
+ e.stopPropagation();
5860
+ onBack();
5861
+ }
5862
+ };
5863
+ window.addEventListener("keydown", onKeyDown, true);
5864
+ return () => window.removeEventListener("keydown", onKeyDown, true);
5865
+ }, [onBack]);
5866
+ const portalRoot = typeof document !== "undefined" ? document.querySelector("[data-ohw-bridge-root]") ?? document.body : null;
5867
+ if (!portalRoot) return null;
5868
+ return createPortal(
5869
+ /* @__PURE__ */ jsxs12(
5870
+ "div",
5871
+ {
5872
+ "data-ohw-section-picker": "",
5873
+ "data-ohw-bridge": "",
5874
+ className: `pointer-events-none fixed inset-0 z-[2147483647] transition-opacity duration-200 ${"opacity-100"}`,
5875
+ "aria-modal": true,
5876
+ role: "dialog",
5877
+ "aria-label": "Choose a section",
5878
+ children: [
5879
+ /* @__PURE__ */ jsx20(
5880
+ "div",
5881
+ {
5882
+ className: "pointer-events-auto fixed left-5 z-[2]",
5883
+ style: { top: chromeClip.top + 20 },
5884
+ children: /* @__PURE__ */ jsxs12(
5885
+ Button,
5886
+ {
5887
+ type: "button",
5888
+ variant: "outline",
5889
+ size: "sm",
5890
+ className: "h-8 min-w-0 gap-1 border-border bg-background px-2 py-1.5 shadow-sm hover:bg-muted cursor-pointer",
5891
+ onClick: onBack,
5892
+ children: [
5893
+ /* @__PURE__ */ jsx20(ArrowLeft, { className: "size-4 shrink-0", "aria-hidden": true }),
5894
+ "Back"
5895
+ ]
5896
+ }
5897
+ )
5898
+ }
5899
+ ),
5900
+ /* @__PURE__ */ jsx20(
5901
+ "div",
5902
+ {
5903
+ className: "pointer-events-none fixed left-1/2 z-[2] rounded-lg px-4 py-3 text-xs leading-4 tracking-[0.18px] text-white shadow-md",
5904
+ style: {
5905
+ top: chromeClip.bottom - 20,
5906
+ transform: "translate(-50%, -100%)",
5907
+ backgroundColor: "var(--ohw-popover-foreground, #0c0a09)"
5908
+ },
5909
+ "data-ohw-section-picker-hint": "",
5910
+ children: "Click on section to select"
5911
+ }
5912
+ ),
5913
+ !isOnTargetPage ? /* @__PURE__ */ jsx20(
5914
+ "div",
5915
+ {
5916
+ className: "pointer-events-none fixed left-1/2 z-[1] -translate-x-1/2 rounded-md bg-background/90 px-3 py-2 text-sm text-muted-foreground shadow-sm",
5917
+ style: { top: chromeClip.top + 64 },
5918
+ children: "Loading page preview\u2026"
5919
+ }
5920
+ ) : null,
5921
+ isOnTargetPage && liveSections.length === 0 ? /* @__PURE__ */ jsx20("div", { className: "pointer-events-auto fixed inset-0 z-[1] flex items-center justify-center bg-muted/40", children: /* @__PURE__ */ jsx20("p", { className: "text-sm text-muted-foreground", children: "No sections found on this page." }) }) : null,
5922
+ isOnTargetPage ? liveSections.map((section) => {
5923
+ const rect = rects.get(section.id);
5924
+ if (!rect || rect.width <= 0 || rect.height <= 0) return null;
5925
+ const isSelected = selectedId === section.id;
5926
+ const isHovered = hoveredId === section.id;
5927
+ const isLit = isSelected || isHovered;
5928
+ return /* @__PURE__ */ jsxs12(
5929
+ "button",
5930
+ {
5931
+ type: "button",
5932
+ className: "pointer-events-auto fixed z-[1] cursor-pointer border-0 bg-transparent p-0 transition-[background-color] duration-150",
5933
+ style: {
5934
+ top: rect.top,
5935
+ left: rect.left,
5936
+ width: rect.width,
5937
+ height: rect.height,
5938
+ backgroundColor: isLit ? "transparent" : DIM_OVERLAY
5939
+ },
5940
+ "aria-label": `Select section ${section.label}`,
5941
+ onClick: () => handleSelect(section),
5942
+ children: [
5943
+ isLit ? /* @__PURE__ */ jsx20(
5944
+ "span",
5945
+ {
5946
+ className: "pointer-events-none absolute",
5947
+ style: {
5948
+ inset: 4,
5949
+ borderRadius: "var(--radius, 6px)",
5950
+ border: "1.5px dashed var(--primary, #0885FE)",
5951
+ boxSizing: "border-box"
5952
+ },
5953
+ "aria-hidden": true
5954
+ }
5955
+ ) : null,
5956
+ isSelected ? /* @__PURE__ */ jsx20(
5957
+ "span",
5958
+ {
5959
+ className: "absolute right-3 top-3 flex size-8 items-center justify-center rounded-full text-white",
5960
+ style: { backgroundColor: "var(--ohw-primary, #0885fe)" },
5961
+ "aria-hidden": true,
5962
+ children: /* @__PURE__ */ jsx20(Check, { className: "size-5" })
5963
+ }
5964
+ ) : null
5965
+ ]
5966
+ },
5967
+ section.id
5968
+ );
5969
+ }) : null
5970
+ ]
5971
+ }
5972
+ ),
5973
+ portalRoot
5974
+ );
5975
+ }
5976
+
5977
+ // src/ui/link-modal/useLinkModalState.ts
5978
+ import { useCallback as useCallback3, useEffect as useEffect5, useMemo as useMemo3, useState as useState5 } from "react";
5979
+ function useLinkModalState({
5980
+ open,
5981
+ mode,
5982
+ pages,
5983
+ sections: _sections,
5984
+ sectionsByPath,
5985
+ initialTarget,
5986
+ existingTargets: _existingTargets,
5987
+ onClose,
5988
+ onSubmit
5989
+ }) {
5990
+ const availablePages = useMemo3(() => pages, [pages]);
5991
+ const [searchValue, setSearchValue] = useState5("");
5992
+ const [selectedPage, setSelectedPage] = useState5(null);
5993
+ const [selectedSection, setSelectedSection] = useState5(null);
5994
+ const [step, setStep] = useState5("input");
5995
+ const [dropdownOpen, setDropdownOpen] = useState5(false);
5996
+ const [urlError, setUrlError] = useState5("");
5997
+ const reset = useCallback3(() => {
5998
+ setSearchValue("");
5999
+ setSelectedPage(null);
6000
+ setSelectedSection(null);
5397
6001
  setStep("input");
5398
6002
  setDropdownOpen(false);
5399
6003
  setUrlError("");
5400
6004
  }, []);
5401
- useEffect3(() => {
6005
+ useEffect5(() => {
5402
6006
  if (!open) return;
5403
6007
  if (mode === "edit" && initialTarget) {
5404
6008
  const { pageRoute } = parseTarget(initialTarget);
@@ -5413,12 +6017,12 @@ function useLinkModalState({
5413
6017
  }
5414
6018
  setDropdownOpen(false);
5415
6019
  setUrlError("");
5416
- }, [open, mode, initialTarget, pages, sectionsByPath, reset]);
5417
- const filteredPages = useMemo2(() => {
6020
+ }, [open, mode, initialTarget, reset]);
6021
+ const filteredPages = useMemo3(() => {
5418
6022
  if (!searchValue.trim()) return availablePages;
5419
6023
  return availablePages.filter((p) => p.title.toLowerCase().startsWith(searchValue.toLowerCase()));
5420
6024
  }, [availablePages, searchValue]);
5421
- const activeSections = useMemo2(() => {
6025
+ const activeSections = useMemo3(() => {
5422
6026
  if (!selectedPage) return [];
5423
6027
  return getSectionsForPath(sectionsByPath, selectedPage.path);
5424
6028
  }, [selectedPage, sectionsByPath]);
@@ -5460,11 +6064,14 @@ function useLinkModalState({
5460
6064
  const handleBackToSections = () => {
5461
6065
  setStep("sectionPicker");
5462
6066
  };
6067
+ const handleSectionPickerBack = () => {
6068
+ setStep("input");
6069
+ };
5463
6070
  const handleClose = () => {
5464
6071
  reset();
5465
6072
  onClose();
5466
6073
  };
5467
- const isValid = useMemo2(() => {
6074
+ const isValid = useMemo3(() => {
5468
6075
  if (urlError) return false;
5469
6076
  if (showBreadcrumb) return true;
5470
6077
  if (selectedPage) return true;
@@ -5497,7 +6104,7 @@ function useLinkModalState({
5497
6104
  onSubmit(normalizeUrl(searchValue));
5498
6105
  handleClose();
5499
6106
  };
5500
- const submitLabel = mode === "edit" ? "Save" : "Create";
6107
+ const submitLabel = showBreadcrumb ? "Save" : mode === "edit" ? "Save" : "Create";
5501
6108
  const title = mode === "edit" ? "Edit navigation item" : "Create navigation item";
5502
6109
  const secondaryLabel = showBreadcrumb ? "Back to sections" : "Cancel";
5503
6110
  const handleSecondary = showBreadcrumb ? handleBackToSections : handleClose;
@@ -5524,23 +6131,29 @@ function useLinkModalState({
5524
6131
  handleChooseSection,
5525
6132
  handleSectionSelect,
5526
6133
  handleBackToSections,
6134
+ handleSectionPickerBack,
5527
6135
  handleClose,
5528
6136
  handleSecondary,
5529
6137
  handleSubmit
5530
6138
  };
5531
6139
  }
5532
6140
 
5533
- // src/ui/link-modal/LinkEditorPanel.tsx
5534
- import { Fragment as Fragment3, jsx as jsx19, jsxs as jsxs11 } from "react/jsx-runtime";
5535
- function LinkEditorPanel({
6141
+ // src/ui/link-modal/LinkPopover.tsx
6142
+ import { Fragment as Fragment4, jsx as jsx21, jsxs as jsxs13 } from "react/jsx-runtime";
6143
+ function postToParent(data) {
6144
+ window.parent?.postMessage(data, "*");
6145
+ }
6146
+ function LinkPopover({
5536
6147
  open = true,
6148
+ panelRef,
6149
+ portalContainer,
6150
+ onClose,
5537
6151
  mode = "create",
5538
6152
  pages,
5539
6153
  sections = [],
5540
6154
  sectionsByPath,
5541
6155
  initialTarget,
5542
6156
  existingTargets = [],
5543
- onClose,
5544
6157
  onSubmit
5545
6158
  }) {
5546
6159
  const state = useLinkModalState({
@@ -5554,115 +6167,109 @@ function LinkEditorPanel({
5554
6167
  onClose,
5555
6168
  onSubmit
5556
6169
  });
5557
- const isCancel = state.secondaryLabel === "Cancel";
5558
- return /* @__PURE__ */ jsxs11(Fragment3, { children: [
5559
- /* @__PURE__ */ jsx19(DialogClose, { asChild: true, children: /* @__PURE__ */ jsx19(
5560
- "button",
6170
+ const sectionPickerActive = state.showSectionPicker && Boolean(state.selectedPage);
6171
+ useEffect6(() => {
6172
+ if (!open) return;
6173
+ if (sectionPickerActive) {
6174
+ postToParent({ type: "ui:unlock" });
6175
+ postToParent({ type: "ow:section-picker", active: true });
6176
+ document.documentElement.style.overflow = "";
6177
+ document.body.style.overflow = "";
6178
+ return () => {
6179
+ postToParent({ type: "ow:section-picker", active: false });
6180
+ };
6181
+ }
6182
+ postToParent({ type: "ow:section-picker", active: false });
6183
+ postToParent({ type: "ui:lock" });
6184
+ const html = document.documentElement;
6185
+ const body = document.body;
6186
+ const prevHtmlOverflow = html.style.overflow;
6187
+ const prevBodyOverflow = body.style.overflow;
6188
+ const prevHtmlOverscroll = html.style.overscrollBehavior;
6189
+ const prevBodyOverscroll = body.style.overscrollBehavior;
6190
+ html.style.overflow = "hidden";
6191
+ body.style.overflow = "hidden";
6192
+ html.style.overscrollBehavior = "none";
6193
+ body.style.overscrollBehavior = "none";
6194
+ const canScrollElement = (el, deltaY) => {
6195
+ const { overflowY } = getComputedStyle(el);
6196
+ if (overflowY !== "auto" && overflowY !== "scroll") return false;
6197
+ if (el.scrollHeight <= el.clientHeight + 1) return false;
6198
+ if (deltaY < 0) return el.scrollTop > 0;
6199
+ if (deltaY > 0)
6200
+ return el.scrollTop + el.clientHeight < el.scrollHeight - 1;
6201
+ return true;
6202
+ };
6203
+ const allowsInternalScroll = (e) => {
6204
+ const target = e.target;
6205
+ if (!(target instanceof Element)) return false;
6206
+ const scrollRoot = target.closest(
6207
+ "[data-ohw-link-page-dropdown], [data-ohw-allow-scroll]"
6208
+ );
6209
+ if (!scrollRoot) return false;
6210
+ const deltaY = e instanceof WheelEvent ? e.deltaY : 0;
6211
+ return canScrollElement(scrollRoot, deltaY);
6212
+ };
6213
+ const preventBackgroundScroll = (e) => {
6214
+ if (allowsInternalScroll(e)) return;
6215
+ e.preventDefault();
6216
+ };
6217
+ const scrollOpts = { passive: false, capture: true };
6218
+ document.addEventListener("wheel", preventBackgroundScroll, scrollOpts);
6219
+ document.addEventListener("touchmove", preventBackgroundScroll, scrollOpts);
6220
+ return () => {
6221
+ postToParent({ type: "ui:unlock" });
6222
+ html.style.overflow = prevHtmlOverflow;
6223
+ body.style.overflow = prevBodyOverflow;
6224
+ html.style.overscrollBehavior = prevHtmlOverscroll;
6225
+ body.style.overscrollBehavior = prevBodyOverscroll;
6226
+ document.removeEventListener(
6227
+ "wheel",
6228
+ preventBackgroundScroll,
6229
+ scrollOpts
6230
+ );
6231
+ document.removeEventListener(
6232
+ "touchmove",
6233
+ preventBackgroundScroll,
6234
+ scrollOpts
6235
+ );
6236
+ };
6237
+ }, [open, sectionPickerActive]);
6238
+ return /* @__PURE__ */ jsxs13(Fragment4, { children: [
6239
+ /* @__PURE__ */ jsx21(
6240
+ Dialog2,
5561
6241
  {
5562
- type: "button",
5563
- className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
5564
- "aria-label": "Close",
5565
- children: /* @__PURE__ */ jsx19(IconX, { "aria-hidden": true })
6242
+ open: open && !sectionPickerActive,
6243
+ onOpenChange: (next) => {
6244
+ if (!next) onClose?.();
6245
+ },
6246
+ children: /* @__PURE__ */ jsx21(
6247
+ DialogContent,
6248
+ {
6249
+ ref: panelRef,
6250
+ container: portalContainer,
6251
+ "data-ohw-link-popover-root": "",
6252
+ "data-ohw-link-modal-root": "",
6253
+ "data-ohw-bridge": "",
6254
+ showCloseButton: false,
6255
+ className: "gap-0 p-0 w-full max-w-[448px] pointer-events-auto z-[2147483646] overflow-visible",
6256
+ children: /* @__PURE__ */ jsx21(LinkEditorPanel, { state, onClose })
6257
+ }
6258
+ )
5566
6259
  }
5567
- ) }),
5568
- /* @__PURE__ */ jsx19(DialogHeader, { className: "w-full gap-1.5 p-6 pr-12", children: /* @__PURE__ */ jsx19(DialogTitle, { className: "m-0 w-full break-words", children: state.title }) }),
5569
- /* @__PURE__ */ jsxs11("div", { className: "flex w-full flex-col gap-3 px-6 pb-8 pt-1", children: [
5570
- state.showBreadcrumb && state.selectedPage && state.selectedSection ? /* @__PURE__ */ jsx19(
5571
- DestinationBreadcrumb,
5572
- {
5573
- pageTitle: state.selectedPage.title,
5574
- sectionLabel: state.selectedSection.label
5575
- }
5576
- ) : /* @__PURE__ */ jsx19(
5577
- UrlOrPageInput,
5578
- {
5579
- value: state.searchValue,
5580
- selectedPage: state.selectedPage,
5581
- dropdownOpen: state.dropdownOpen,
5582
- onDropdownOpenChange: state.setDropdownOpen,
5583
- filteredPages: state.filteredPages,
5584
- onInputChange: state.handleInputChange,
5585
- onPageSelect: state.handlePageSelect,
5586
- urlError: state.urlError
5587
- }
5588
- ),
5589
- state.showChooseSection ? /* @__PURE__ */ jsxs11("div", { className: "flex flex-col justify-center gap-2", children: [
5590
- /* @__PURE__ */ jsx19(Button, { type: "button", variant: "outline", className: "w-fit cursor-pointer", size: "sm", onClick: state.handleChooseSection, children: "Choose a section" }),
5591
- /* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-1 text-sm text-muted-foreground", children: [
5592
- /* @__PURE__ */ jsx19(IconInfo, { className: "shrink-0", "aria-hidden": true }),
5593
- /* @__PURE__ */ jsx19("span", { children: "Pick a section this link should scroll to." })
5594
- ] })
5595
- ] }) : null,
5596
- state.showSectionPicker ? /* @__PURE__ */ jsx19(SectionPickerList, { sections: state.activeSections, onSelect: state.handleSectionSelect }) : null,
5597
- state.showSectionRow && state.selectedSection ? /* @__PURE__ */ jsx19(SectionTreeItem, { section: state.selectedSection, selected: true }) : null
5598
- ] }),
5599
- /* @__PURE__ */ jsxs11(DialogFooter, { className: "w-full px-6 pb-6", children: [
5600
- /* @__PURE__ */ jsx19(
5601
- Button,
5602
- {
5603
- type: "button",
5604
- variant: isCancel ? "outline" : "ghost",
5605
- className: "leading-6 shadow-none cursor-pointer",
5606
- style: isCancel ? {
5607
- backgroundColor: "var(--ohw-background)",
5608
- borderColor: "var(--ohw-border)",
5609
- color: "var(--ohw-foreground)"
5610
- } : void 0,
5611
- onClick: state.handleSecondary,
5612
- children: state.secondaryLabel
5613
- }
5614
- ),
5615
- /* @__PURE__ */ jsx19(
5616
- Button,
5617
- {
5618
- type: "button",
5619
- className: "border-0 leading-6 shadow-none hover:opacity-90 disabled:opacity-50 cursor-pointer",
5620
- style: {
5621
- backgroundColor: "var(--ohw-primary)",
5622
- color: "var(--ohw-primary-foreground)"
5623
- },
5624
- disabled: !state.isValid,
5625
- onClick: state.handleSubmit,
5626
- children: state.submitLabel
5627
- }
5628
- )
5629
- ] })
6260
+ ),
6261
+ sectionPickerActive && state.selectedPage ? /* @__PURE__ */ jsx21(
6262
+ SectionPickerOverlay,
6263
+ {
6264
+ pagePath: state.selectedPage.path,
6265
+ sections: state.activeSections,
6266
+ onBack: state.handleSectionPickerBack,
6267
+ onSelect: state.handleSectionSelect
6268
+ }
6269
+ ) : null
5630
6270
  ] });
5631
6271
  }
5632
6272
 
5633
- // src/ui/link-modal/LinkPopover.tsx
5634
- import { jsx as jsx20 } from "react/jsx-runtime";
5635
- function LinkPopover({
5636
- open = true,
5637
- panelRef,
5638
- portalContainer,
5639
- onClose,
5640
- ...editorProps
5641
- }) {
5642
- return /* @__PURE__ */ jsx20(
5643
- Dialog2,
5644
- {
5645
- open,
5646
- onOpenChange: (next) => {
5647
- if (!next) onClose?.();
5648
- },
5649
- children: /* @__PURE__ */ jsx20(
5650
- DialogContent,
5651
- {
5652
- ref: panelRef,
5653
- container: portalContainer,
5654
- "data-ohw-link-popover-root": "",
5655
- "data-ohw-link-modal-root": "",
5656
- "data-ohw-bridge": "",
5657
- showCloseButton: false,
5658
- className: "gap-0 p-0 w-full md:w-md pointer-events-auto",
5659
- children: /* @__PURE__ */ jsx20(LinkEditorPanel, { ...editorProps, open, onClose })
5660
- }
5661
- )
5662
- }
5663
- );
5664
- }
5665
-
5666
6273
  // src/ui/link-modal/devFixtures.ts
5667
6274
  var DEV_SITE_PAGES = [
5668
6275
  { path: "/", title: "Home" },
@@ -5682,16 +6289,21 @@ var DEV_SECTIONS_BY_PATH = {
5682
6289
  { id: "founder-teaser", label: "Founder" },
5683
6290
  { id: "plan-form", label: "Plan Form" },
5684
6291
  { id: "testimonials", label: "Testimonials" },
5685
- { id: "wordmark", label: "Wordmark" }
6292
+ { id: "wordmark", label: "Wordmark" },
6293
+ { id: "footer", label: "Footer" }
5686
6294
  ],
5687
6295
  "/about": [
5688
6296
  { id: "manifesto", label: "Manifesto" },
5689
6297
  { id: "story-letter", label: "Our Story" },
5690
6298
  { id: "lagree-explainer", label: "Lagree Explainer" },
5691
6299
  { id: "waitlist-cta", label: "Waitlist" },
5692
- { id: "personal-training", label: "Personal training" }
6300
+ { id: "personal-training", label: "Personal training" },
6301
+ { id: "footer", label: "Footer" }
6302
+ ],
6303
+ "/classes": [
6304
+ { id: "class-library", label: "Class Library" },
6305
+ { id: "footer", label: "Footer" }
5693
6306
  ],
5694
- "/classes": [{ id: "class-library", label: "Class Library" }],
5695
6307
  "/pricing": [
5696
6308
  { id: "page-header", label: "Page Header" },
5697
6309
  { id: "free-class-cta", label: "Free Class" },
@@ -5699,19 +6311,25 @@ var DEV_SECTIONS_BY_PATH = {
5699
6311
  { id: "monthly-memberships", label: "Memberships" },
5700
6312
  { id: "specialty-programs", label: "Specialty Programs" },
5701
6313
  { id: "package-matcher", label: "Packages" },
5702
- { id: "wordmark", label: "Wordmark" }
6314
+ { id: "wordmark", label: "Wordmark" },
6315
+ { id: "footer", label: "Footer" }
5703
6316
  ],
5704
6317
  "/studio": [
5705
6318
  { id: "studio-intro", label: "Studio Intro" },
5706
6319
  { id: "studio-features", label: "Studio Features" },
5707
6320
  { id: "studio-gallery", label: "Studio Gallery" },
5708
- { id: "studio-visit", label: "Visit Studio" }
6321
+ { id: "studio-visit", label: "Visit Studio" },
6322
+ { id: "footer", label: "Footer" }
6323
+ ],
6324
+ "/instructors": [
6325
+ { id: "instructor-grid", label: "Instructors" },
6326
+ { id: "footer", label: "Footer" }
5709
6327
  ],
5710
- "/instructors": [{ id: "instructor-grid", label: "Instructors" }],
5711
6328
  "/contact": [
5712
6329
  { id: "hello-hero", label: "Hello" },
5713
6330
  { id: "contact-form", label: "Contact Form" },
5714
- { id: "faq", label: "FAQ" }
6331
+ { id: "faq", label: "FAQ" },
6332
+ { id: "footer", label: "Footer" }
5715
6333
  ]
5716
6334
  };
5717
6335
  function shouldUseDevFixtures() {
@@ -5721,8 +6339,716 @@ function shouldUseDevFixtures() {
5721
6339
  return new URLSearchParams(q).get("ohw-fixtures") === "1";
5722
6340
  }
5723
6341
 
6342
+ // src/lib/nav-items.ts
6343
+ var NAV_COUNT_KEY = "__ohw_nav_count";
6344
+ var NAV_ORDER_KEY = "__ohw_nav_order";
6345
+ function getLinkHref(el) {
6346
+ const key = el.getAttribute("data-ohw-href-key");
6347
+ if (key) {
6348
+ const stored = getStoredLinkHref(key);
6349
+ if (stored !== void 0) return stored;
6350
+ }
6351
+ return el.getAttribute("href") ?? "";
6352
+ }
6353
+ function isNavbarButton(el) {
6354
+ return el.hasAttribute("data-ohw-role") && el.getAttribute("data-ohw-role") === "navbar-button";
6355
+ }
6356
+ function isNavbarLinkItem(el) {
6357
+ if (!el.matches("[data-ohw-href-key]")) return false;
6358
+ if (isNavbarButton(el)) return false;
6359
+ if (!el.querySelector('[data-ohw-editable="text"]')) return false;
6360
+ return Boolean(el.closest("nav, [data-ohw-nav-container], [data-ohw-nav-drawer]"));
6361
+ }
6362
+ function getNavbarDesktopContainer() {
6363
+ return document.querySelector("[data-ohw-nav-container]");
6364
+ }
6365
+ function getNavbarDrawerContainer() {
6366
+ return document.querySelector("[data-ohw-nav-drawer]");
6367
+ }
6368
+ function listNavbarItems() {
6369
+ const desktop = getNavbarDesktopContainer();
6370
+ if (desktop) {
6371
+ return Array.from(desktop.querySelectorAll("[data-ohw-href-key]")).filter(isNavbarLinkItem);
6372
+ }
6373
+ const drawer = getNavbarDrawerContainer();
6374
+ if (drawer) {
6375
+ return Array.from(drawer.querySelectorAll("[data-ohw-href-key]")).filter(isNavbarLinkItem);
6376
+ }
6377
+ return Array.from(document.querySelectorAll("nav [data-ohw-href-key]")).filter(isNavbarLinkItem);
6378
+ }
6379
+ function parseNavIndexFromKey(key) {
6380
+ if (!key) return null;
6381
+ const match = key.match(/^nav-(\d+)-href$/);
6382
+ if (!match) return null;
6383
+ return parseInt(match[1], 10);
6384
+ }
6385
+ function getNavbarIndicesInDom() {
6386
+ const indices = /* @__PURE__ */ new Set();
6387
+ for (const item of listNavbarItems()) {
6388
+ const idx = parseNavIndexFromKey(item.getAttribute("data-ohw-href-key"));
6389
+ if (idx !== null) indices.add(idx);
6390
+ }
6391
+ return [...indices].sort((a, b) => a - b);
6392
+ }
6393
+ function getNextNavbarIndex() {
6394
+ const indices = getNavbarIndicesInDom();
6395
+ if (indices.length === 0) return 0;
6396
+ return Math.max(...indices) + 1;
6397
+ }
6398
+ function getNavbarExistingTargets() {
6399
+ return listNavbarItems().map((el) => getLinkHref(el)).filter(Boolean);
6400
+ }
6401
+ function getNavOrderFromDom() {
6402
+ return listNavbarItems().map((el) => el.getAttribute("data-ohw-href-key")).filter((key) => Boolean(key));
6403
+ }
6404
+ function parseNavOrder(content) {
6405
+ const raw = content[NAV_ORDER_KEY];
6406
+ if (!raw) return null;
6407
+ try {
6408
+ const parsed = JSON.parse(raw);
6409
+ if (!Array.isArray(parsed)) return null;
6410
+ return parsed.filter((key) => typeof key === "string" && key.length > 0);
6411
+ } catch {
6412
+ return null;
6413
+ }
6414
+ }
6415
+ function findCounterpartByHrefKey(hrefKey, root) {
6416
+ return root.querySelector(`[data-ohw-href-key="${CSS.escape(hrefKey)}"]`);
6417
+ }
6418
+ function cloneNavLinkShell(template) {
6419
+ const anchor = document.createElement("a");
6420
+ if (template) {
6421
+ anchor.style.cssText = template.style.cssText;
6422
+ if (template.className) anchor.className = template.className;
6423
+ }
6424
+ anchor.style.cursor = "pointer";
6425
+ anchor.style.textDecoration = "none";
6426
+ return anchor;
6427
+ }
6428
+ function buildNavLink(index, href, label, template) {
6429
+ const anchor = cloneNavLinkShell(template);
6430
+ anchor.href = href;
6431
+ anchor.setAttribute("data-ohw-href-key", `nav-${index}-href`);
6432
+ const span = document.createElement("span");
6433
+ span.setAttribute("data-ohw-editable", "text");
6434
+ span.setAttribute("data-ohw-key", `nav-${index}-label`);
6435
+ span.textContent = label;
6436
+ anchor.appendChild(span);
6437
+ return anchor;
6438
+ }
6439
+ function insertNavLinkInContainer(container, anchor, afterHrefKey) {
6440
+ if (!afterHrefKey) {
6441
+ container.appendChild(anchor);
6442
+ return;
6443
+ }
6444
+ const afterEl = findCounterpartByHrefKey(afterHrefKey, container);
6445
+ if (afterEl?.parentElement === container) {
6446
+ afterEl.insertAdjacentElement("afterend", anchor);
6447
+ return;
6448
+ }
6449
+ container.appendChild(anchor);
6450
+ }
6451
+ function insertNavbarItemDom(index, href, label, afterAnchor) {
6452
+ const afterHrefKey = afterAnchor?.getAttribute("data-ohw-href-key") ?? null;
6453
+ const desktopItems = getNavbarDesktopContainer()?.querySelectorAll("[data-ohw-href-key]") ?? [];
6454
+ const drawerItems = getNavbarDrawerContainer()?.querySelectorAll("[data-ohw-href-key]") ?? [];
6455
+ const desktopTemplate = Array.from(desktopItems).find(isNavbarLinkItem) ?? null;
6456
+ const drawerTemplate = Array.from(drawerItems).find(isNavbarLinkItem) ?? null;
6457
+ let primary = null;
6458
+ const desktopContainer = getNavbarDesktopContainer();
6459
+ if (desktopContainer) {
6460
+ const desktopAnchor = buildNavLink(index, href, label, desktopTemplate);
6461
+ insertNavLinkInContainer(desktopContainer, desktopAnchor, afterHrefKey);
6462
+ primary = desktopAnchor;
6463
+ }
6464
+ const drawerContainer = getNavbarDrawerContainer();
6465
+ if (drawerContainer) {
6466
+ const drawerAnchor = buildNavLink(index, href, label, drawerTemplate);
6467
+ insertNavLinkInContainer(drawerContainer, drawerAnchor, afterHrefKey);
6468
+ if (!primary) primary = drawerAnchor;
6469
+ }
6470
+ if (!primary) {
6471
+ const fallback = buildNavLink(index, href, label, listNavbarItems()[0] ?? null);
6472
+ const nav = document.querySelector("nav");
6473
+ nav?.appendChild(fallback);
6474
+ primary = fallback;
6475
+ }
6476
+ return primary;
6477
+ }
6478
+ function applyNavOrderToContainer(container, order) {
6479
+ const seen = /* @__PURE__ */ new Set();
6480
+ for (const hrefKey of order) {
6481
+ if (seen.has(hrefKey)) continue;
6482
+ seen.add(hrefKey);
6483
+ const el = findCounterpartByHrefKey(hrefKey, container);
6484
+ if (el) container.appendChild(el);
6485
+ }
6486
+ for (const el of container.querySelectorAll("[data-ohw-href-key]")) {
6487
+ if (!isNavbarLinkItem(el)) continue;
6488
+ const key = el.getAttribute("data-ohw-href-key");
6489
+ if (!key || seen.has(key)) continue;
6490
+ container.appendChild(el);
6491
+ }
6492
+ }
6493
+ function applyNavOrder(order) {
6494
+ const desktop = getNavbarDesktopContainer();
6495
+ if (desktop) applyNavOrderToContainer(desktop, order);
6496
+ const drawer = getNavbarDrawerContainer();
6497
+ if (drawer) applyNavOrderToContainer(drawer, order);
6498
+ }
6499
+ function collectNavbarIndicesFromContent(content) {
6500
+ const indices = /* @__PURE__ */ new Set();
6501
+ for (const key of Object.keys(content)) {
6502
+ const idx = parseNavIndexFromKey(key);
6503
+ if (idx !== null) indices.add(idx);
6504
+ }
6505
+ const countRaw = content[NAV_COUNT_KEY];
6506
+ if (countRaw) {
6507
+ const count = parseInt(countRaw, 10);
6508
+ if (Number.isFinite(count) && count > 0) {
6509
+ for (let i = 0; i < count; i++) indices.add(i);
6510
+ }
6511
+ }
6512
+ return [...indices].sort((a, b) => a - b);
6513
+ }
6514
+ function navbarIndexExistsInDom(index) {
6515
+ return document.querySelector(`[data-ohw-href-key="nav-${index}-href"]`) !== null;
6516
+ }
6517
+ function reconcileNavbarItemsFromContent(content) {
6518
+ const indices = collectNavbarIndicesFromContent(content);
6519
+ for (const index of indices) {
6520
+ if (navbarIndexExistsInDom(index)) continue;
6521
+ const href = content[`nav-${index}-href`];
6522
+ const label = content[`nav-${index}-label`];
6523
+ if (!href && !label) continue;
6524
+ insertNavbarItemDom(index, href ?? "/", label ?? "Untitled", null);
6525
+ }
6526
+ const order = parseNavOrder(content);
6527
+ if (order?.length) applyNavOrder(order);
6528
+ }
6529
+ function buildNavOrderAfterInsert(afterAnchor, newHrefKey) {
6530
+ const order = getNavOrderFromDom();
6531
+ if (!afterAnchor) {
6532
+ if (!order.includes(newHrefKey)) order.push(newHrefKey);
6533
+ return order;
6534
+ }
6535
+ const afterKey = afterAnchor.getAttribute("data-ohw-href-key");
6536
+ if (!afterKey) {
6537
+ if (!order.includes(newHrefKey)) order.push(newHrefKey);
6538
+ return order;
6539
+ }
6540
+ const withoutNew = order.filter((key) => key !== newHrefKey);
6541
+ const pos = withoutNew.indexOf(afterKey);
6542
+ if (pos >= 0) {
6543
+ withoutNew.splice(pos + 1, 0, newHrefKey);
6544
+ return withoutNew;
6545
+ }
6546
+ withoutNew.push(newHrefKey);
6547
+ return withoutNew;
6548
+ }
6549
+ function insertNavbarItem(href, label, afterAnchor = null) {
6550
+ const index = getNextNavbarIndex();
6551
+ const anchor = insertNavbarItemDom(index, href, label, afterAnchor);
6552
+ if (!anchor) throw new Error("Failed to insert navbar item");
6553
+ const hrefKey = `nav-${index}-href`;
6554
+ const order = buildNavOrderAfterInsert(afterAnchor, hrefKey);
6555
+ applyNavOrder(order);
6556
+ return {
6557
+ anchor,
6558
+ index,
6559
+ hrefKey,
6560
+ labelKey: `nav-${index}-label`,
6561
+ href,
6562
+ label,
6563
+ order
6564
+ };
6565
+ }
6566
+
6567
+ // src/lib/footer-items.ts
6568
+ var FOOTER_ORDER_KEY = "__ohw_footer_order";
6569
+ var FOOTER_HREF_RE = /^footer-(\d+)-(\d+)-href$/;
6570
+ function parseFooterHrefKey(key) {
6571
+ if (!key) return null;
6572
+ const match = key.match(FOOTER_HREF_RE);
6573
+ if (!match) return null;
6574
+ return { col: parseInt(match[1], 10), item: parseInt(match[2], 10) };
6575
+ }
6576
+ function isFooterHrefKey(key) {
6577
+ return parseFooterHrefKey(key) !== null;
6578
+ }
6579
+ function getFooterRoot() {
6580
+ return document.querySelector('footer[data-ohw-section="footer"], footer');
6581
+ }
6582
+ function getFooterLinksContainer() {
6583
+ return document.querySelector("[data-ohw-footer-links]") ?? document.querySelector(".rb-footer-links");
6584
+ }
6585
+ function isFooterLinkAnchor(el) {
6586
+ if (!el.matches("[data-ohw-href-key]")) return false;
6587
+ if (!isFooterHrefKey(el.getAttribute("data-ohw-href-key"))) return false;
6588
+ return Boolean(el.querySelector('[data-ohw-editable="text"]'));
6589
+ }
6590
+ function listFooterColumns() {
6591
+ const root = getFooterLinksContainer() ?? getFooterRoot()?.querySelector(".rb-footer-links") ?? null;
6592
+ if (!root) return [];
6593
+ const explicit = Array.from(root.querySelectorAll(":scope > [data-ohw-footer-col]"));
6594
+ if (explicit.length > 0) return explicit;
6595
+ return Array.from(root.children).filter((el) => {
6596
+ if (!(el instanceof HTMLElement)) return false;
6597
+ return Array.from(el.querySelectorAll(":scope > [data-ohw-href-key]")).some(
6598
+ isFooterLinkAnchor
6599
+ );
6600
+ });
6601
+ }
6602
+ function listFooterLinksInColumn(column) {
6603
+ return Array.from(column.querySelectorAll(":scope > [data-ohw-href-key]")).filter(
6604
+ isFooterLinkAnchor
6605
+ );
6606
+ }
6607
+ function findFooterColumnForLink(anchor) {
6608
+ const columns = listFooterColumns();
6609
+ return columns.find((col) => col.contains(anchor)) ?? null;
6610
+ }
6611
+ function getFooterOrderFromDom() {
6612
+ return listFooterColumns().map(
6613
+ (col) => listFooterLinksInColumn(col).map((el) => el.getAttribute("data-ohw-href-key")).filter((key) => Boolean(key))
6614
+ );
6615
+ }
6616
+ function findFooterLinkByKey(hrefKey) {
6617
+ return document.querySelector(
6618
+ `footer [data-ohw-href-key="${CSS.escape(hrefKey)}"]`
6619
+ );
6620
+ }
6621
+ function syncFooterColumnIndices(columns) {
6622
+ columns.forEach((col, index) => {
6623
+ const next = String(index);
6624
+ if (col.getAttribute("data-ohw-footer-col") !== next) {
6625
+ col.setAttribute("data-ohw-footer-col", next);
6626
+ }
6627
+ });
6628
+ }
6629
+ function appendChildIfNeeded(parent, child) {
6630
+ if (parent.lastElementChild !== child) {
6631
+ parent.appendChild(child);
6632
+ }
6633
+ }
6634
+ function insertLinkBeforeNext(col, el, nextEl) {
6635
+ if (nextEl) {
6636
+ if (el.nextElementSibling !== nextEl) {
6637
+ col.insertBefore(el, nextEl);
6638
+ }
6639
+ return;
6640
+ }
6641
+ if (col.lastElementChild !== el) {
6642
+ col.appendChild(el);
6643
+ }
6644
+ }
6645
+ function applyFooterOrder(order) {
6646
+ const container = getFooterLinksContainer();
6647
+ if (!container) return;
6648
+ const columns = listFooterColumns();
6649
+ if (columns.length === 0) return;
6650
+ const currentOrder = getFooterOrderFromDom();
6651
+ if (ordersEqual(order, currentOrder)) {
6652
+ syncFooterColumnIndices(columns);
6653
+ return;
6654
+ }
6655
+ const colCount = Math.max(columns.length, order.length);
6656
+ const working = columns.slice(0, colCount);
6657
+ for (let i = 0; i < order.length && i < working.length; i++) {
6658
+ appendChildIfNeeded(container, working[i]);
6659
+ }
6660
+ for (let i = order.length; i < working.length; i++) {
6661
+ appendChildIfNeeded(container, working[i]);
6662
+ }
6663
+ const freshColumns = listFooterColumns();
6664
+ syncFooterColumnIndices(freshColumns);
6665
+ for (let c = 0; c < order.length; c++) {
6666
+ const col = freshColumns[c];
6667
+ if (!col) continue;
6668
+ const colOrder = order[c];
6669
+ for (let j = 0; j < colOrder.length; j++) {
6670
+ const hrefKey = colOrder[j];
6671
+ const el = findFooterLinkByKey(hrefKey);
6672
+ if (!el) continue;
6673
+ if (el.parentElement !== col) {
6674
+ col.appendChild(el);
6675
+ }
6676
+ const nextKey = colOrder[j + 1];
6677
+ const nextEl = nextKey ? findFooterLinkByKey(nextKey) : null;
6678
+ insertLinkBeforeNext(col, el, nextEl?.parentElement === col ? nextEl : null);
6679
+ }
6680
+ }
6681
+ }
6682
+ function ordersEqual(a, b) {
6683
+ if (a.length !== b.length) return false;
6684
+ for (let i = 0; i < a.length; i++) {
6685
+ const left = a[i];
6686
+ const right = b[i];
6687
+ if (left.length !== right.length) return false;
6688
+ for (let j = 0; j < left.length; j++) {
6689
+ if (left[j] !== right[j]) return false;
6690
+ }
6691
+ }
6692
+ return true;
6693
+ }
6694
+ function planFooterLinkMove(hrefKey, targetColIndex, insertIndex) {
6695
+ const order = getFooterOrderFromDom();
6696
+ if (targetColIndex < 0 || targetColIndex >= order.length) return null;
6697
+ let fromCol = -1;
6698
+ let fromIdx = -1;
6699
+ for (let c = 0; c < order.length; c++) {
6700
+ const idx = order[c].indexOf(hrefKey);
6701
+ if (idx >= 0) {
6702
+ fromCol = c;
6703
+ fromIdx = idx;
6704
+ break;
6705
+ }
6706
+ }
6707
+ if (fromCol < 0) return null;
6708
+ const next = order.map((col) => [...col]);
6709
+ next[fromCol].splice(fromIdx, 1);
6710
+ let adjusted = insertIndex;
6711
+ if (fromCol === targetColIndex && fromIdx < insertIndex) adjusted -= 1;
6712
+ adjusted = Math.max(0, Math.min(adjusted, next[targetColIndex].length));
6713
+ next[targetColIndex].splice(adjusted, 0, hrefKey);
6714
+ if (ordersEqual(next, order)) return null;
6715
+ return next;
6716
+ }
6717
+ function planFooterColumnMove(fromIndex, toIndex) {
6718
+ const order = getFooterOrderFromDom();
6719
+ const columns = listFooterColumns();
6720
+ if (fromIndex < 0 || fromIndex >= columns.length || toIndex < 0 || toIndex > columns.length) {
6721
+ return null;
6722
+ }
6723
+ if (toIndex === fromIndex || toIndex === fromIndex + 1) return null;
6724
+ const nextOrder = order.map((col) => [...col]);
6725
+ const [movedOrder] = nextOrder.splice(fromIndex, 1);
6726
+ if (!movedOrder) return null;
6727
+ let adjusted = toIndex;
6728
+ if (fromIndex < toIndex) adjusted -= 1;
6729
+ adjusted = Math.max(0, Math.min(adjusted, nextOrder.length));
6730
+ nextOrder.splice(adjusted, 0, movedOrder);
6731
+ if (ordersEqual(nextOrder, order)) return null;
6732
+ return nextOrder;
6733
+ }
6734
+ function parseFooterOrder(content) {
6735
+ const raw = content[FOOTER_ORDER_KEY];
6736
+ if (!raw) return null;
6737
+ try {
6738
+ const parsed = JSON.parse(raw);
6739
+ if (!Array.isArray(parsed)) return null;
6740
+ return parsed.filter((col) => Array.isArray(col)).map((col) => col.filter((key) => typeof key === "string" && key.length > 0));
6741
+ } catch {
6742
+ return null;
6743
+ }
6744
+ }
6745
+ function reconcileFooterOrderFromContent(content) {
6746
+ const order = parseFooterOrder(content);
6747
+ if (!order?.length) return;
6748
+ const current = getFooterOrderFromDom();
6749
+ if (ordersEqual(order, current)) {
6750
+ syncFooterColumnIndices(listFooterColumns());
6751
+ return;
6752
+ }
6753
+ applyFooterOrder(order);
6754
+ }
6755
+ function buildLinkDropSlots(column, columnIndex) {
6756
+ const links = listFooterLinksInColumn(column);
6757
+ const colRect = column.getBoundingClientRect();
6758
+ const slots = [];
6759
+ const barThickness = 3;
6760
+ if (links.length === 0) {
6761
+ slots.push({
6762
+ insertIndex: 0,
6763
+ columnIndex,
6764
+ left: colRect.left,
6765
+ top: colRect.top + colRect.height / 2 - barThickness / 2,
6766
+ width: colRect.width,
6767
+ height: barThickness,
6768
+ direction: "horizontal"
6769
+ });
6770
+ return slots;
6771
+ }
6772
+ for (let i = 0; i <= links.length; i++) {
6773
+ let top;
6774
+ if (i === 0) {
6775
+ top = links[0].getBoundingClientRect().top - barThickness / 2;
6776
+ } else if (i === links.length) {
6777
+ const last = links[links.length - 1].getBoundingClientRect();
6778
+ top = last.bottom - barThickness / 2;
6779
+ } else {
6780
+ const prev = links[i - 1].getBoundingClientRect();
6781
+ const next = links[i].getBoundingClientRect();
6782
+ top = (prev.bottom + next.top) / 2 - barThickness / 2;
6783
+ }
6784
+ const widthRef = i === 0 ? links[0].getBoundingClientRect() : i === links.length ? links[links.length - 1].getBoundingClientRect() : links[i].getBoundingClientRect();
6785
+ slots.push({
6786
+ insertIndex: i,
6787
+ columnIndex,
6788
+ left: widthRef.left,
6789
+ top,
6790
+ width: Math.max(widthRef.width, colRect.width * 0.8),
6791
+ height: barThickness,
6792
+ direction: "horizontal"
6793
+ });
6794
+ }
6795
+ return slots;
6796
+ }
6797
+ function buildColumnDropSlots() {
6798
+ const columns = listFooterColumns();
6799
+ const slots = [];
6800
+ const barThickness = 3;
6801
+ if (columns.length === 0) return slots;
6802
+ for (let i = 0; i <= columns.length; i++) {
6803
+ let left;
6804
+ let height;
6805
+ let top;
6806
+ if (i === 0) {
6807
+ const first = columns[0].getBoundingClientRect();
6808
+ left = first.left - barThickness / 2;
6809
+ top = first.top;
6810
+ height = first.height;
6811
+ } else if (i === columns.length) {
6812
+ const last = columns[columns.length - 1].getBoundingClientRect();
6813
+ left = last.right - barThickness / 2;
6814
+ top = last.top;
6815
+ height = last.height;
6816
+ } else {
6817
+ const prev = columns[i - 1].getBoundingClientRect();
6818
+ const next = columns[i].getBoundingClientRect();
6819
+ left = (prev.right + next.left) / 2 - barThickness / 2;
6820
+ top = Math.min(prev.top, next.top);
6821
+ height = Math.max(prev.bottom, next.bottom) - top;
6822
+ }
6823
+ slots.push({
6824
+ insertIndex: i,
6825
+ columnIndex: i,
6826
+ left,
6827
+ top,
6828
+ width: barThickness,
6829
+ height: Math.max(height, 24),
6830
+ direction: "vertical"
6831
+ });
6832
+ }
6833
+ return slots;
6834
+ }
6835
+ function hitTestLinkDropSlot(clientX, clientY, draggedHrefKey) {
6836
+ const columns = listFooterColumns();
6837
+ let best = null;
6838
+ for (let c = 0; c < columns.length; c++) {
6839
+ const col = columns[c];
6840
+ const colRect = col.getBoundingClientRect();
6841
+ const inColX = clientX >= colRect.left - 24 && clientX <= colRect.right + 24;
6842
+ if (!inColX) continue;
6843
+ const slots = buildLinkDropSlots(col, c).filter((slot) => {
6844
+ const links = listFooterLinksInColumn(col);
6845
+ const fromIdx = links.findIndex(
6846
+ (el) => el.getAttribute("data-ohw-href-key") === draggedHrefKey
6847
+ );
6848
+ if (fromIdx < 0) return true;
6849
+ if (c === findColumnIndexForKey(draggedHrefKey) && (slot.insertIndex === fromIdx || slot.insertIndex === fromIdx + 1)) {
6850
+ return true;
6851
+ }
6852
+ return true;
6853
+ });
6854
+ for (const slot of slots) {
6855
+ const cy = slot.top + slot.height / 2;
6856
+ const dist = Math.abs(clientY - cy) + (inColX ? 0 : 1e3);
6857
+ if (!best || dist < best.dist) best = { slot, dist };
6858
+ }
6859
+ }
6860
+ return best?.slot ?? null;
6861
+ }
6862
+ function findColumnIndexForKey(hrefKey) {
6863
+ const order = getFooterOrderFromDom();
6864
+ for (let c = 0; c < order.length; c++) {
6865
+ if (order[c].includes(hrefKey)) return c;
6866
+ }
6867
+ return -1;
6868
+ }
6869
+ function hitTestColumnDropSlot(clientX, _clientY) {
6870
+ const slots = buildColumnDropSlots();
6871
+ let best = null;
6872
+ for (const slot of slots) {
6873
+ const cx2 = slot.left + slot.width / 2;
6874
+ const dist = Math.abs(clientX - cx2);
6875
+ if (!best || dist < best.dist) best = { slot, dist };
6876
+ }
6877
+ return best?.slot ?? null;
6878
+ }
6879
+
6880
+ // src/lib/carousel.ts
6881
+ import { useEffect as useEffect7, useState as useState6 } from "react";
6882
+ var CAROUSEL_ATTR = "data-ohw-carousel";
6883
+ var CAROUSEL_VALUE_ATTR = "data-ohw-carousel-value";
6884
+ var CAROUSEL_SLIDE_ATTR = "data-ohw-carousel-slide";
6885
+ var CAROUSEL_EVENT = "ohw:carousel-change";
6886
+ function listCarouselKeys() {
6887
+ const keys = /* @__PURE__ */ new Set();
6888
+ document.querySelectorAll(`[${CAROUSEL_ATTR}]`).forEach((el) => {
6889
+ const key = el.getAttribute("data-ohw-key");
6890
+ if (key) keys.add(key);
6891
+ });
6892
+ return [...keys];
6893
+ }
6894
+ function containersForKey(key) {
6895
+ return Array.from(
6896
+ document.querySelectorAll(`[${CAROUSEL_ATTR}][data-ohw-key="${CSS.escape(key)}"]`)
6897
+ );
6898
+ }
6899
+ function isCarouselKey(key) {
6900
+ return containersForKey(key).length > 0;
6901
+ }
6902
+ function parseSlides(raw) {
6903
+ if (!raw) return [];
6904
+ try {
6905
+ const parsed = JSON.parse(raw);
6906
+ if (!Array.isArray(parsed)) return [];
6907
+ return parsed.filter((s) => Boolean(s) && typeof s === "object").map((s) => ({ src: String(s.src ?? ""), alt: String(s.alt ?? "") }));
6908
+ } catch {
6909
+ return [];
6910
+ }
6911
+ }
6912
+ function readCarouselValue(key) {
6913
+ const container = containersForKey(key)[0];
6914
+ if (!container) return [];
6915
+ const fromAttr = parseSlides(container.getAttribute(CAROUSEL_VALUE_ATTR));
6916
+ if (fromAttr.length > 0) return fromAttr;
6917
+ return Array.from(container.querySelectorAll(`[${CAROUSEL_SLIDE_ATTR}]`)).filter((slide) => slide.closest(`[${CAROUSEL_ATTR}]`) === container).sort((a, b) => slideIndex(a) - slideIndex(b)).map((slide) => {
6918
+ const img = slide instanceof HTMLImageElement ? slide : slide.querySelector("img");
6919
+ return { src: img?.src ?? "", alt: img?.alt ?? "" };
6920
+ });
6921
+ }
6922
+ function slideIndex(el) {
6923
+ const raw = el.getAttribute(CAROUSEL_SLIDE_ATTR);
6924
+ const n = raw ? parseInt(raw, 10) : NaN;
6925
+ return Number.isFinite(n) ? n : 0;
6926
+ }
6927
+ function applyCarouselValue(key, slides) {
6928
+ const value = JSON.stringify(slides);
6929
+ for (const container of containersForKey(key)) {
6930
+ if (container.getAttribute(CAROUSEL_VALUE_ATTR) === value) continue;
6931
+ container.setAttribute(CAROUSEL_VALUE_ATTR, value);
6932
+ container.dispatchEvent(
6933
+ new CustomEvent(CAROUSEL_EVENT, { detail: { images: slides } })
6934
+ );
6935
+ }
6936
+ }
6937
+ function applyCarouselNode(key, val) {
6938
+ if (!isCarouselKey(key)) return false;
6939
+ applyCarouselValue(key, parseSlides(val));
6940
+ return true;
6941
+ }
6942
+ function useOhwCarousel(key, initial) {
6943
+ const [images, setImages] = useState6(initial);
6944
+ useEffect7(() => {
6945
+ const el = document.querySelector(
6946
+ `[${CAROUSEL_ATTR}][data-ohw-key="${CSS.escape(key)}"]`
6947
+ );
6948
+ if (!el) return;
6949
+ const onChange = (e) => {
6950
+ const detail = e.detail;
6951
+ if (Array.isArray(detail?.images)) setImages(detail.images);
6952
+ };
6953
+ el.addEventListener(CAROUSEL_EVENT, onChange);
6954
+ return () => el.removeEventListener(CAROUSEL_EVENT, onChange);
6955
+ }, [key]);
6956
+ const bind = {
6957
+ [CAROUSEL_ATTR]: "",
6958
+ "data-ohw-key": key,
6959
+ [CAROUSEL_VALUE_ATTR]: JSON.stringify(images)
6960
+ };
6961
+ return { images, setImages, bind };
6962
+ }
6963
+
6964
+ // src/ui/navbar-container-chrome.tsx
6965
+ import { Plus as Plus2 } from "lucide-react";
6966
+ import { jsx as jsx22 } from "react/jsx-runtime";
6967
+ function NavbarContainerChrome({
6968
+ rect,
6969
+ onAdd
6970
+ }) {
6971
+ const chromeGap = 6;
6972
+ return /* @__PURE__ */ jsx22(
6973
+ "div",
6974
+ {
6975
+ "data-ohw-navbar-container-chrome": "",
6976
+ "data-ohw-bridge": "",
6977
+ className: "pointer-events-none fixed z-[2147483647]",
6978
+ style: {
6979
+ top: rect.top - chromeGap,
6980
+ left: rect.left - chromeGap,
6981
+ width: rect.width + chromeGap * 2,
6982
+ height: rect.height + chromeGap * 2
6983
+ },
6984
+ children: /* @__PURE__ */ jsx22(
6985
+ "button",
6986
+ {
6987
+ type: "button",
6988
+ "data-ohw-navbar-add-button": "",
6989
+ className: "pointer-events-auto absolute left-1/2 flex p-0.5 rounded-[10px] size-7 -translate-x-1/2 translate-y-1/2 items-center justify-center border border-border bg-background shadow-sm transition-colors hover:bg-muted/80",
6990
+ style: { top: "70%" },
6991
+ "aria-label": "Add item",
6992
+ onMouseDown: (e) => {
6993
+ e.preventDefault();
6994
+ e.stopPropagation();
6995
+ },
6996
+ onClick: (e) => {
6997
+ e.preventDefault();
6998
+ e.stopPropagation();
6999
+ onAdd();
7000
+ },
7001
+ children: /* @__PURE__ */ jsx22(Plus2, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
7002
+ }
7003
+ )
7004
+ }
7005
+ );
7006
+ }
7007
+
7008
+ // src/ui/drop-indicator.tsx
7009
+ import * as React9 from "react";
7010
+ import { jsx as jsx23 } from "react/jsx-runtime";
7011
+ var dropIndicatorVariants = cva(
7012
+ "ov-gap-line pointer-events-none shrink-0 transition-opacity duration-150",
7013
+ {
7014
+ variants: {
7015
+ direction: {
7016
+ vertical: "h-6 w-[3px]",
7017
+ horizontal: "h-[3px] w-[200px]"
7018
+ },
7019
+ state: {
7020
+ default: "opacity-0",
7021
+ hover: "opacity-100",
7022
+ dragIdle: "opacity-40",
7023
+ dragActive: "opacity-100"
7024
+ }
7025
+ },
7026
+ defaultVariants: {
7027
+ direction: "vertical",
7028
+ state: "default"
7029
+ }
7030
+ }
7031
+ );
7032
+ var DropIndicator = React9.forwardRef(
7033
+ ({ className, direction, state, ...props }, ref) => {
7034
+ return /* @__PURE__ */ jsx23(
7035
+ "div",
7036
+ {
7037
+ ref,
7038
+ "data-slot": "drop-indicator",
7039
+ "data-direction": direction ?? "vertical",
7040
+ "data-state": state ?? "default",
7041
+ "aria-hidden": "true",
7042
+ className: cn(dropIndicatorVariants({ direction, state }), className),
7043
+ ...props
7044
+ }
7045
+ );
7046
+ }
7047
+ );
7048
+ DropIndicator.displayName = "DropIndicator";
7049
+
5724
7050
  // src/ui/badge.tsx
5725
- import { jsx as jsx21 } from "react/jsx-runtime";
7051
+ import { jsx as jsx24 } from "react/jsx-runtime";
5726
7052
  var badgeVariants = cva(
5727
7053
  "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
5728
7054
  {
@@ -5740,12 +7066,12 @@ var badgeVariants = cva(
5740
7066
  }
5741
7067
  );
5742
7068
  function Badge({ className, variant, ...props }) {
5743
- return /* @__PURE__ */ jsx21("div", { className: cn(badgeVariants({ variant }), className), ...props });
7069
+ return /* @__PURE__ */ jsx24("div", { className: cn(badgeVariants({ variant }), className), ...props });
5744
7070
  }
5745
7071
 
5746
7072
  // src/OhhwellsBridge.tsx
5747
7073
  import { Link as Link2 } from "lucide-react";
5748
- import { Fragment as Fragment4, jsx as jsx22, jsxs as jsxs12 } from "react/jsx-runtime";
7074
+ import { Fragment as Fragment5, jsx as jsx25, jsxs as jsxs14 } from "react/jsx-runtime";
5749
7075
  var PRIMARY2 = "#0885FE";
5750
7076
  var IMAGE_FADE_MS = 300;
5751
7077
  function runOpacityFade(el, onDone) {
@@ -5924,7 +7250,7 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
5924
7250
  const root = createRoot(container);
5925
7251
  flushSync(() => {
5926
7252
  root.render(
5927
- /* @__PURE__ */ jsx22(
7253
+ /* @__PURE__ */ jsx25(
5928
7254
  SchedulingWidget,
5929
7255
  {
5930
7256
  notifyOnConnect,
@@ -5964,7 +7290,7 @@ function mountSchedulingEntries(entries, notifyOnConnect = false) {
5964
7290
  }
5965
7291
  }
5966
7292
  }
5967
- function getLinkHref(el) {
7293
+ function getLinkHref2(el) {
5968
7294
  const anchor = el instanceof HTMLAnchorElement ? el : el.querySelector("a");
5969
7295
  return anchor?.getAttribute("href") ?? "";
5970
7296
  }
@@ -5986,7 +7312,7 @@ function isDragHandleDisabled(el) {
5986
7312
  return raw === "true" || raw === "";
5987
7313
  });
5988
7314
  }
5989
- function collectEditableNodes() {
7315
+ function collectEditableNodes(extraContent) {
5990
7316
  const nodes = Array.from(document.querySelectorAll("[data-ohw-editable]")).map((el) => {
5991
7317
  if (el.dataset.ohwEditable === "image") {
5992
7318
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -6001,7 +7327,7 @@ function collectEditableNodes() {
6001
7327
  return { key: el.dataset.ohwKey ?? "", type: "video", text: getVideoEl(el)?.src ?? "" };
6002
7328
  }
6003
7329
  if (el.dataset.ohwEditable === "link") {
6004
- return { key: el.dataset.ohwKey ?? "", type: "link", text: getLinkHref(el) };
7330
+ return { key: el.dataset.ohwKey ?? "", type: "link", text: getLinkHref2(el) };
6005
7331
  }
6006
7332
  return {
6007
7333
  key: el.dataset.ohwKey ?? "",
@@ -6012,8 +7338,16 @@ function collectEditableNodes() {
6012
7338
  document.querySelectorAll("[data-ohw-href-key]").forEach((el) => {
6013
7339
  const key = el.getAttribute("data-ohw-href-key") ?? "";
6014
7340
  if (!key) return;
6015
- nodes.push({ key, type: "link", text: getLinkHref(el) });
7341
+ nodes.push({ key, type: "link", text: getLinkHref2(el) });
6016
7342
  });
7343
+ if (extraContent) {
7344
+ for (const key of [NAV_ORDER_KEY, FOOTER_ORDER_KEY, NAV_COUNT_KEY]) {
7345
+ const text = extraContent[key];
7346
+ if (typeof text === "string" && text.length > 0) {
7347
+ nodes.push({ key, type: "meta", text });
7348
+ }
7349
+ }
7350
+ }
6017
7351
  document.querySelectorAll('[data-ohw-editable="video"]').forEach((el) => {
6018
7352
  const key = el.dataset.ohwKey ?? "";
6019
7353
  const video = getVideoEl(el);
@@ -6021,6 +7355,9 @@ function collectEditableNodes() {
6021
7355
  nodes.push({ key: `${key}${VIDEO_AUTOPLAY_SUFFIX}`, type: "video-setting", text: String(video.autoplay) });
6022
7356
  nodes.push({ key: `${key}${VIDEO_MUTED_SUFFIX}`, type: "video-setting", text: String(video.muted) });
6023
7357
  });
7358
+ for (const key of listCarouselKeys()) {
7359
+ nodes.push({ key, type: "carousel", text: JSON.stringify(readCarouselValue(key)) });
7360
+ }
6024
7361
  return nodes;
6025
7362
  }
6026
7363
  function isMediaEditable(el) {
@@ -6098,7 +7435,7 @@ function applyLinkByKey(key, val) {
6098
7435
  }
6099
7436
  function isInsideLinkEditor(target) {
6100
7437
  return Boolean(
6101
- target.closest("[data-ohw-link-popover-root]") || target.closest("[data-ohw-link-modal-root]") || target.closest("[data-ohw-link-page-dropdown]") || target.closest('[data-slot="popover-content"]') || target.closest('[data-slot="dialog-content"]') || target.closest('[data-slot="dialog-overlay"]')
7438
+ target.closest("[data-ohw-link-popover-root]") || target.closest("[data-ohw-link-modal-root]") || target.closest("[data-ohw-link-page-dropdown]") || target.closest("[data-ohw-section-picker]") || target.closest("[data-ohw-navbar-container-chrome]") || target.closest("[data-ohw-navbar-add-button]") || target.closest('[data-slot="popover-content"]') || target.closest('[data-slot="dialog-content"]') || target.closest('[data-slot="dialog-overlay"]')
6102
7439
  );
6103
7440
  }
6104
7441
  function getHrefKeyFromElement(el) {
@@ -6109,7 +7446,30 @@ function getHrefKeyFromElement(el) {
6109
7446
  if (!key) return null;
6110
7447
  return { anchor, key };
6111
7448
  }
6112
- function isNavbarButton(el) {
7449
+ function disableNativeHrefDrag(el) {
7450
+ if (el.draggable) el.draggable = false;
7451
+ if (el.getAttribute("draggable") !== "false") {
7452
+ el.setAttribute("draggable", "false");
7453
+ }
7454
+ }
7455
+ function clearTextSelection() {
7456
+ const sel = window.getSelection();
7457
+ if (sel && !sel.isCollapsed) sel.removeAllRanges();
7458
+ }
7459
+ function armFooterPressDrag() {
7460
+ document.documentElement.setAttribute("data-ohw-footer-press-drag", "");
7461
+ }
7462
+ function lockFooterDuringDrag() {
7463
+ document.documentElement.removeAttribute("data-ohw-footer-press-drag");
7464
+ document.documentElement.setAttribute("data-ohw-item-dragging", "");
7465
+ clearTextSelection();
7466
+ }
7467
+ function unlockFooterDragInteraction() {
7468
+ document.documentElement.removeAttribute("data-ohw-footer-press-drag");
7469
+ document.documentElement.removeAttribute("data-ohw-item-dragging");
7470
+ clearTextSelection();
7471
+ }
7472
+ function isNavbarButton2(el) {
6113
7473
  return Boolean(el.closest('[data-ohw-role="navbar-button"]'));
6114
7474
  }
6115
7475
  function getNavigationItemAnchor(el) {
@@ -6121,19 +7481,8 @@ function getNavigationItemAnchor(el) {
6121
7481
  function isNavigationItem(el) {
6122
7482
  return getNavigationItemAnchor(el) !== null;
6123
7483
  }
6124
- function getNavigationItemAddHintTarget(anchor) {
6125
- const items = listNavigationItems();
6126
- const idx = items.indexOf(anchor);
6127
- if (idx >= 0 && idx < items.length - 1) return items[idx + 1];
6128
- return anchor;
6129
- }
6130
- function listNavigationItems() {
6131
- return Array.from(
6132
- document.querySelectorAll("nav [data-ohw-href-key], footer [data-ohw-href-key]")
6133
- ).filter((el) => isNavigationItem(el));
6134
- }
6135
7484
  function getNavigationItemReorderState(anchor) {
6136
- if (isNavbarButton(anchor)) return { key: null, disabled: false };
7485
+ if (isNavbarButton2(anchor)) return { key: null, disabled: false };
6137
7486
  return getReorderHandleStateFromAnchor(anchor);
6138
7487
  }
6139
7488
  function getReorderHandleState(el) {
@@ -6155,6 +7504,120 @@ function clearHrefKeyHover(anchor) {
6155
7504
  function isInsideNavigationItem(el) {
6156
7505
  return getNavigationItemAnchor(el) !== null;
6157
7506
  }
7507
+ function getNavigationRoot(el) {
7508
+ const explicit = el.closest("[data-ohw-nav-root]");
7509
+ if (explicit) return explicit;
7510
+ return el.closest("nav, footer, aside");
7511
+ }
7512
+ function findFooterItemGroup(item) {
7513
+ const footer = item.closest("footer");
7514
+ if (!footer) return null;
7515
+ let node = item.parentElement;
7516
+ while (node && node !== footer) {
7517
+ const hasDirectNavItems = Array.from(
7518
+ node.querySelectorAll(":scope > [data-ohw-href-key]")
7519
+ ).some(isNavigationItem);
7520
+ if (hasDirectNavItems) return node;
7521
+ node = node.parentElement;
7522
+ }
7523
+ return null;
7524
+ }
7525
+ function isNavigationRoot(el) {
7526
+ return el.hasAttribute("data-ohw-nav-root") || el.matches("nav, footer, aside");
7527
+ }
7528
+ function isInferredFooterGroup(el) {
7529
+ const footer = el.closest("footer");
7530
+ if (!footer || el === footer) return false;
7531
+ return Array.from(el.querySelectorAll(":scope > [data-ohw-href-key]")).some(isNavigationItem);
7532
+ }
7533
+ function isNavigationContainer(el) {
7534
+ return el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-footer-col") || isNavigationRoot(el) || isInferredFooterGroup(el);
7535
+ }
7536
+ function isPointOverNavigation(x, y) {
7537
+ const navRoot = document.querySelector("[data-ohw-nav-root]") ?? document.querySelector("nav");
7538
+ if (!navRoot) return false;
7539
+ const r2 = navRoot.getBoundingClientRect();
7540
+ return x >= r2.left && x <= r2.right && y >= r2.top && y <= r2.bottom;
7541
+ }
7542
+ function isPointOverNavItem(container, x, y) {
7543
+ return Array.from(container.querySelectorAll("[data-ohw-href-key]")).some((el) => {
7544
+ if (!isNavigationItem(el) || isNavbarButton2(el)) return false;
7545
+ const itemRect = el.getBoundingClientRect();
7546
+ return x >= itemRect.left && x <= itemRect.right && y >= itemRect.top && y <= itemRect.bottom;
7547
+ });
7548
+ }
7549
+ function resolveNavContainerSelectionTarget(target, clientX, clientY) {
7550
+ if (getNavigationItemAnchor(target)) return null;
7551
+ if (target.closest('[data-ohw-role="navbar-button"]')) return null;
7552
+ const plainLink = target.closest("a");
7553
+ if (plainLink && !plainLink.hasAttribute("data-ohw-href-key") && !plainLink.closest("[data-ohw-nav-container]")) {
7554
+ return null;
7555
+ }
7556
+ const fromClosest = target.closest("[data-ohw-nav-container]");
7557
+ if (fromClosest && !isPointOverNavItem(fromClosest, clientX, clientY)) {
7558
+ return fromClosest;
7559
+ }
7560
+ const navRoot = target.closest("[data-ohw-nav-root]");
7561
+ if (!navRoot) return null;
7562
+ const container = navRoot.querySelector("[data-ohw-nav-container]");
7563
+ if (!container || isPointOverNavItem(container, clientX, clientY)) return null;
7564
+ if (target === navRoot || target.hasAttribute("data-ohw-nav-root")) {
7565
+ return container;
7566
+ }
7567
+ return null;
7568
+ }
7569
+ function getNavigationSelectionParent(el) {
7570
+ if (isNavigationItem(el)) {
7571
+ const explicit = el.closest("[data-ohw-nav-container]");
7572
+ if (explicit) return explicit;
7573
+ const footerGroup = findFooterItemGroup(el);
7574
+ if (footerGroup) return footerGroup;
7575
+ return getNavigationRoot(el);
7576
+ }
7577
+ if (el.hasAttribute("data-ohw-nav-container") || isInferredFooterGroup(el)) {
7578
+ return getNavigationRoot(el);
7579
+ }
7580
+ return null;
7581
+ }
7582
+ function placeCaretAtPoint(el, x, y) {
7583
+ const selection = window.getSelection();
7584
+ if (!selection) return;
7585
+ if (typeof document.caretRangeFromPoint === "function") {
7586
+ const range = document.caretRangeFromPoint(x, y);
7587
+ if (range && el.contains(range.startContainer)) {
7588
+ selection.removeAllRanges();
7589
+ selection.addRange(range);
7590
+ return;
7591
+ }
7592
+ }
7593
+ const caretPositionFromPoint = document.caretPositionFromPoint;
7594
+ if (typeof caretPositionFromPoint === "function") {
7595
+ const position = caretPositionFromPoint(x, y);
7596
+ if (position && el.contains(position.offsetNode)) {
7597
+ const range = document.createRange();
7598
+ range.setStart(position.offsetNode, position.offset);
7599
+ range.collapse(true);
7600
+ selection.removeAllRanges();
7601
+ selection.addRange(range);
7602
+ }
7603
+ }
7604
+ }
7605
+ function selectAllTextInEditable(el) {
7606
+ const selection = window.getSelection();
7607
+ if (!selection) return;
7608
+ const range = document.createRange();
7609
+ range.selectNodeContents(el);
7610
+ selection.removeAllRanges();
7611
+ selection.addRange(range);
7612
+ }
7613
+ function getNavigationLabelEditable(target) {
7614
+ const editable = target.closest('[data-ohw-editable="text"]');
7615
+ if (!editable) return null;
7616
+ const hrefCtx = getHrefKeyFromElement(editable);
7617
+ const navAnchor = hrefCtx ? getNavigationItemAnchor(hrefCtx.anchor) : null;
7618
+ if (!navAnchor) return null;
7619
+ return { editable, navAnchor };
7620
+ }
6158
7621
  function collectSections() {
6159
7622
  return collectSectionsFromDom();
6160
7623
  }
@@ -6278,6 +7741,8 @@ var ICONS = {
6278
7741
  insertUnorderedList: '<line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/>',
6279
7742
  insertOrderedList: '<line x1="10" y1="6" x2="21" y2="6"/><line x1="10" y1="12" x2="21" y2="12"/><line x1="10" y1="18" x2="21" y2="18"/><path d="M4 6h1v4"/><path d="M4 10h2"/><path d="M6 18H4c0-1 2-2 2-3s-1-1.5-2-1"/>'
6280
7743
  };
7744
+ var TOOLBAR_PILL_PADDING = 2;
7745
+ var TOOLBAR_TOGGLE_GAP = 4;
6281
7746
  var TOOLBAR_GROUPS = [
6282
7747
  [
6283
7748
  { cmd: "bold", title: "Bold" },
@@ -6302,7 +7767,7 @@ function EditGlowChrome({
6302
7767
  dragDisabled = false
6303
7768
  }) {
6304
7769
  const GAP = 6;
6305
- return /* @__PURE__ */ jsxs12(
7770
+ return /* @__PURE__ */ jsxs14(
6306
7771
  "div",
6307
7772
  {
6308
7773
  ref: elRef,
@@ -6317,7 +7782,7 @@ function EditGlowChrome({
6317
7782
  zIndex: 2147483646
6318
7783
  },
6319
7784
  children: [
6320
- /* @__PURE__ */ jsx22(
7785
+ /* @__PURE__ */ jsx25(
6321
7786
  "div",
6322
7787
  {
6323
7788
  style: {
@@ -6330,7 +7795,7 @@ function EditGlowChrome({
6330
7795
  }
6331
7796
  }
6332
7797
  ),
6333
- reorderHrefKey && /* @__PURE__ */ jsx22(
7798
+ reorderHrefKey && /* @__PURE__ */ jsx25(
6334
7799
  "div",
6335
7800
  {
6336
7801
  "data-ohw-drag-handle-container": "",
@@ -6342,7 +7807,7 @@ function EditGlowChrome({
6342
7807
  transform: "translate(calc(-100% - 7px), -50%)",
6343
7808
  pointerEvents: dragDisabled ? "none" : "auto"
6344
7809
  },
6345
- children: /* @__PURE__ */ jsx22(
7810
+ children: /* @__PURE__ */ jsx25(
6346
7811
  DragHandle,
6347
7812
  {
6348
7813
  "aria-label": `Reorder ${reorderHrefKey}`,
@@ -6446,7 +7911,7 @@ function FloatingToolbar({
6446
7911
  onEditLink
6447
7912
  }) {
6448
7913
  const { top, left, transform } = calcToolbarPos(rect, parentScroll, 330);
6449
- return /* @__PURE__ */ jsx22(
7914
+ return /* @__PURE__ */ jsx25(
6450
7915
  "div",
6451
7916
  {
6452
7917
  ref: elRef,
@@ -6456,14 +7921,23 @@ function FloatingToolbar({
6456
7921
  left,
6457
7922
  transform,
6458
7923
  zIndex: 2147483647,
7924
+ background: "#fff",
7925
+ border: "1px solid #E7E5E4",
7926
+ borderRadius: 6,
7927
+ boxShadow: "0px 2px 4px -2px rgba(0,0,0,.1),0px 4px 6px -1px rgba(0,0,0,.1)",
7928
+ display: "flex",
7929
+ alignItems: "center",
7930
+ padding: TOOLBAR_PILL_PADDING,
7931
+ gap: TOOLBAR_TOGGLE_GAP,
7932
+ fontFamily: "sans-serif",
6459
7933
  pointerEvents: "auto"
6460
7934
  },
6461
- children: /* @__PURE__ */ jsxs12(CustomToolbar, { children: [
6462
- TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ jsxs12(React9.Fragment, { children: [
6463
- gi > 0 && /* @__PURE__ */ jsx22(CustomToolbarDivider, {}),
7935
+ children: /* @__PURE__ */ jsxs14(CustomToolbar, { children: [
7936
+ TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ jsxs14(React10.Fragment, { children: [
7937
+ gi > 0 && /* @__PURE__ */ jsx25(CustomToolbarDivider, {}),
6464
7938
  btns.map((btn) => {
6465
7939
  const isActive = activeCommands.has(btn.cmd);
6466
- return /* @__PURE__ */ jsx22(
7940
+ return /* @__PURE__ */ jsx25(
6467
7941
  CustomToolbarButton,
6468
7942
  {
6469
7943
  title: btn.title,
@@ -6472,7 +7946,7 @@ function FloatingToolbar({
6472
7946
  e.preventDefault();
6473
7947
  onCommand(btn.cmd);
6474
7948
  },
6475
- children: /* @__PURE__ */ jsx22(
7949
+ children: /* @__PURE__ */ jsx25(
6476
7950
  "svg",
6477
7951
  {
6478
7952
  width: "16",
@@ -6493,7 +7967,7 @@ function FloatingToolbar({
6493
7967
  );
6494
7968
  })
6495
7969
  ] }, gi)),
6496
- showEditLink ? /* @__PURE__ */ jsx22(
7970
+ showEditLink ? /* @__PURE__ */ jsx25(
6497
7971
  CustomToolbarButton,
6498
7972
  {
6499
7973
  type: "button",
@@ -6507,7 +7981,7 @@ function FloatingToolbar({
6507
7981
  e.preventDefault();
6508
7982
  e.stopPropagation();
6509
7983
  },
6510
- children: /* @__PURE__ */ jsx22(Link2, { className: "size-4 shrink-0", "aria-hidden": true })
7984
+ children: /* @__PURE__ */ jsx25(Link2, { className: "size-4 shrink-0", "aria-hidden": true })
6511
7985
  }
6512
7986
  ) : null
6513
7987
  ] })
@@ -6524,7 +7998,7 @@ function StateToggle({
6524
7998
  states,
6525
7999
  onStateChange
6526
8000
  }) {
6527
- return /* @__PURE__ */ jsx22(
8001
+ return /* @__PURE__ */ jsx25(
6528
8002
  ToggleGroup,
6529
8003
  {
6530
8004
  "data-ohw-state-toggle": "",
@@ -6538,7 +8012,7 @@ function StateToggle({
6538
8012
  left: rect.right - 8,
6539
8013
  transform: "translateX(-100%)"
6540
8014
  },
6541
- children: states.map((state) => /* @__PURE__ */ jsx22(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
8015
+ children: states.map((state) => /* @__PURE__ */ jsx25(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
6542
8016
  }
6543
8017
  );
6544
8018
  }
@@ -6561,12 +8035,12 @@ function resolveSubdomain(subdomainFromQuery) {
6561
8035
  return "";
6562
8036
  }
6563
8037
  function OhhwellsBridge() {
6564
- const pathname = usePathname();
6565
- const router = useRouter();
8038
+ const pathname = usePathname2();
8039
+ const router = useRouter2();
6566
8040
  const searchParams = useSearchParams();
6567
8041
  const isEditMode = isEditSessionActive();
6568
- const [bridgeRoot, setBridgeRoot] = useState5(null);
6569
- useEffect4(() => {
8042
+ const [bridgeRoot, setBridgeRoot] = useState7(null);
8043
+ useEffect8(() => {
6570
8044
  const figtreeFontId = "ohw-figtree-font";
6571
8045
  if (!document.getElementById(figtreeFontId)) {
6572
8046
  const preconnect1 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.googleapis.com" });
@@ -6594,83 +8068,101 @@ function OhhwellsBridge() {
6594
8068
  const subdomainFromQuery = searchParams.get("subdomain");
6595
8069
  const subdomain = resolveSubdomain(subdomainFromQuery);
6596
8070
  useLinkHrefGuardian(pathname, subdomain, isEditMode);
6597
- const postToParent = useCallback2((data) => {
8071
+ const postToParent2 = useCallback4((data) => {
6598
8072
  if (typeof window !== "undefined" && window.parent !== window) {
6599
8073
  window.parent.postMessage(data, "*");
6600
8074
  }
6601
8075
  }, []);
6602
- const [fetchState, setFetchState] = useState5("idle");
6603
- const autoSaveTimers = useRef4(/* @__PURE__ */ new Map());
6604
- const activeElRef = useRef4(null);
6605
- const selectedElRef = useRef4(null);
6606
- const originalContentRef = useRef4(null);
6607
- const activeStateElRef = useRef4(null);
6608
- const parentScrollRef = useRef4(null);
6609
- const visibleViewportRef = useRef4(null);
6610
- const [dialogPortalContainer, setDialogPortalContainer] = useState5(null);
6611
- const attachVisibleViewport = useCallback2((node) => {
8076
+ const [fetchState, setFetchState] = useState7("idle");
8077
+ const autoSaveTimers = useRef5(/* @__PURE__ */ new Map());
8078
+ const activeElRef = useRef5(null);
8079
+ const selectedElRef = useRef5(null);
8080
+ const originalContentRef = useRef5(null);
8081
+ const activeStateElRef = useRef5(null);
8082
+ const parentScrollRef = useRef5(null);
8083
+ const visibleViewportRef = useRef5(null);
8084
+ const [dialogPortalContainer, setDialogPortalContainer] = useState7(null);
8085
+ const attachVisibleViewport = useCallback4((node) => {
6612
8086
  visibleViewportRef.current = node;
6613
8087
  setDialogPortalContainer(node);
6614
8088
  if (node) applyVisibleViewport(node, parentScrollRef.current);
6615
8089
  }, []);
6616
- const toolbarElRef = useRef4(null);
6617
- const glowElRef = useRef4(null);
6618
- const hoveredImageRef = useRef4(null);
6619
- const hoveredImageHasTextOverlapRef = useRef4(false);
6620
- const dragOverElRef = useRef4(null);
6621
- const [mediaHover, setMediaHover] = useState5(null);
6622
- const [uploadingRects, setUploadingRects] = useState5({});
6623
- const hoveredGapRef = useRef4(null);
6624
- const imageUnhoverTimerRef = useRef4(null);
6625
- const imageShowTimerRef = useRef4(null);
6626
- const editStylesRef = useRef4(null);
6627
- const activateRef = useRef4(() => {
8090
+ const toolbarElRef = useRef5(null);
8091
+ const glowElRef = useRef5(null);
8092
+ const hoveredImageRef = useRef5(null);
8093
+ const hoveredImageHasTextOverlapRef = useRef5(false);
8094
+ const dragOverElRef = useRef5(null);
8095
+ const [mediaHover, setMediaHover] = useState7(null);
8096
+ const [carouselHover, setCarouselHover] = useState7(null);
8097
+ const [uploadingRects, setUploadingRects] = useState7({});
8098
+ const pendingUploadRectRef = useRef5(null);
8099
+ const hoveredGapRef = useRef5(null);
8100
+ const imageUnhoverTimerRef = useRef5(null);
8101
+ const imageShowTimerRef = useRef5(null);
8102
+ const editStylesRef = useRef5(null);
8103
+ const activateRef = useRef5(() => {
8104
+ });
8105
+ const deactivateRef = useRef5(() => {
6628
8106
  });
6629
- const deactivateRef = useRef4(() => {
8107
+ const selectRef = useRef5(() => {
6630
8108
  });
6631
- const selectRef = useRef4(() => {
8109
+ const selectFrameRef = useRef5(() => {
6632
8110
  });
6633
- const deselectRef = useRef4(() => {
8111
+ const deselectRef = useRef5(() => {
6634
8112
  });
6635
- const reselectNavigationItemRef = useRef4(() => {
8113
+ const reselectNavigationItemRef = useRef5(() => {
6636
8114
  });
6637
- const commitNavigationTextEditRef = useRef4(() => {
8115
+ const commitNavigationTextEditRef = useRef5(() => {
6638
8116
  });
6639
- const refreshActiveCommandsRef = useRef4(() => {
8117
+ const refreshActiveCommandsRef = useRef5(() => {
6640
8118
  });
6641
- const postToParentRef = useRef4(postToParent);
6642
- postToParentRef.current = postToParent;
6643
- const sectionsLoadedRef = useRef4(false);
6644
- const pendingScheduleConfigRequests = useRef4([]);
6645
- const [toolbarRect, setToolbarRect] = useState5(null);
6646
- const [toolbarVariant, setToolbarVariant] = useState5("none");
6647
- const toolbarVariantRef = useRef4("none");
8119
+ const postToParentRef = useRef5(postToParent2);
8120
+ postToParentRef.current = postToParent2;
8121
+ const sectionsLoadedRef = useRef5(false);
8122
+ const pendingScheduleConfigRequests = useRef5([]);
8123
+ const [toolbarRect, setToolbarRect] = useState7(null);
8124
+ const [toolbarVariant, setToolbarVariant] = useState7("none");
8125
+ const toolbarVariantRef = useRef5("none");
6648
8126
  toolbarVariantRef.current = toolbarVariant;
6649
- const [reorderHrefKey, setReorderHrefKey] = useState5(null);
6650
- const [reorderDragDisabled, setReorderDragDisabled] = useState5(false);
6651
- const [toggleState, setToggleState] = useState5(null);
6652
- const [maxBadge, setMaxBadge] = useState5(null);
6653
- const [activeCommands, setActiveCommands] = useState5(/* @__PURE__ */ new Set());
6654
- const [sectionGap, setSectionGap] = useState5(null);
6655
- const [toolbarShowEditLink, setToolbarShowEditLink] = useState5(false);
6656
- const hoveredItemElRef = useRef4(null);
6657
- const [hoveredItemRect, setHoveredItemRect] = useState5(null);
6658
- const siblingHintElRef = useRef4(null);
6659
- const [siblingHintRect, setSiblingHintRect] = useState5(null);
6660
- const [isItemDragging, setIsItemDragging] = useState5(false);
6661
- const [linkPopover, setLinkPopover] = useState5(null);
6662
- const [sitePages, setSitePages] = useState5([]);
6663
- const [sectionsByPath, setSectionsByPath] = useState5({});
6664
- const sectionsPrefetchGenRef = useRef4(0);
6665
- const setLinkPopoverRef = useRef4(setLinkPopover);
6666
- const linkPopoverPanelRef = useRef4(null);
6667
- const linkPopoverOpenRef = useRef4(false);
6668
- const linkPopoverGraceUntilRef = useRef4(0);
8127
+ const [reorderHrefKey, setReorderHrefKey] = useState7(null);
8128
+ const [reorderDragDisabled, setReorderDragDisabled] = useState7(false);
8129
+ const [toggleState, setToggleState] = useState7(null);
8130
+ const [maxBadge, setMaxBadge] = useState7(null);
8131
+ const [activeCommands, setActiveCommands] = useState7(/* @__PURE__ */ new Set());
8132
+ const [sectionGap, setSectionGap] = useState7(null);
8133
+ const [toolbarShowEditLink, setToolbarShowEditLink] = useState7(false);
8134
+ const hoveredNavContainerRef = useRef5(null);
8135
+ const [hoveredNavContainerRect, setHoveredNavContainerRect] = useState7(null);
8136
+ const hoveredItemElRef = useRef5(null);
8137
+ const [hoveredItemRect, setHoveredItemRect] = useState7(null);
8138
+ const siblingHintElRef = useRef5(null);
8139
+ const [siblingHintRect, setSiblingHintRect] = useState7(null);
8140
+ const [siblingHintRects, setSiblingHintRects] = useState7([]);
8141
+ const [isItemDragging, setIsItemDragging] = useState7(false);
8142
+ const [isFooterFrameSelection, setIsFooterFrameSelection] = useState7(false);
8143
+ const footerDragRef = useRef5(null);
8144
+ const [footerDropSlots, setFooterDropSlots] = useState7([]);
8145
+ const [activeFooterDropIndex, setActiveFooterDropIndex] = useState7(null);
8146
+ const [draggedItemRect, setDraggedItemRect] = useState7(null);
8147
+ const footerPointerDragRef = useRef5(null);
8148
+ const suppressNextClickRef = useRef5(false);
8149
+ const [linkPopover, setLinkPopover] = useState7(null);
8150
+ const linkPopoverSessionRef = useRef5(null);
8151
+ const addNavAfterAnchorRef = useRef5(null);
8152
+ const editContentRef = useRef5({});
8153
+ const [sitePages, setSitePages] = useState7([]);
8154
+ const [sectionsByPath, setSectionsByPath] = useState7({});
8155
+ const sectionsPrefetchGenRef = useRef5(0);
8156
+ const setLinkPopoverRef = useRef5(setLinkPopover);
8157
+ const linkPopoverPanelRef = useRef5(null);
8158
+ const linkPopoverOpenRef = useRef5(false);
8159
+ const linkPopoverGraceUntilRef = useRef5(0);
6669
8160
  setLinkPopoverRef.current = setLinkPopover;
8161
+ linkPopoverSessionRef.current = linkPopover;
6670
8162
  const bumpLinkPopoverGrace = () => {
6671
8163
  linkPopoverGraceUntilRef.current = Date.now() + 350;
6672
8164
  };
6673
- const runSectionsPrefetch = useCallback2((pages) => {
8165
+ const runSectionsPrefetch = useCallback4((pages) => {
6674
8166
  if (!isEditMode || shouldUseDevFixtures() || pages.length === 0) return;
6675
8167
  const gen = ++sectionsPrefetchGenRef.current;
6676
8168
  const paths = pages.map((p) => p.path);
@@ -6689,43 +8181,33 @@ function OhhwellsBridge() {
6689
8181
  );
6690
8182
  });
6691
8183
  }, [isEditMode, pathname]);
6692
- const runSectionsPrefetchRef = useRef4(runSectionsPrefetch);
8184
+ const runSectionsPrefetchRef = useRef5(runSectionsPrefetch);
6693
8185
  runSectionsPrefetchRef.current = runSectionsPrefetch;
6694
- useEffect4(() => {
6695
- if (!linkPopover) return;
8186
+ useEffect8(() => {
8187
+ if (!linkPopover) {
8188
+ document.documentElement.removeAttribute("data-ohw-link-popover-open");
8189
+ return;
8190
+ }
8191
+ document.documentElement.setAttribute("data-ohw-link-popover-open", "");
6696
8192
  if (hoveredImageRef.current) {
6697
8193
  hoveredImageRef.current = null;
6698
8194
  hoveredImageHasTextOverlapRef.current = false;
6699
8195
  }
6700
8196
  hoveredGapRef.current = null;
6701
8197
  setSectionGap(null);
6702
- setMediaHover(null);
6703
- postToParent({ type: "ow:link-modal-lock", locked: true });
6704
- const html = document.documentElement;
6705
- const body = document.body;
6706
- const prevHtmlOverflow = html.style.overflow;
6707
- const prevBodyOverflow = body.style.overflow;
6708
- html.style.overflow = "hidden";
6709
- body.style.overflow = "hidden";
6710
- const preventBackgroundScroll = (e) => {
6711
- const target = e.target;
6712
- if (target instanceof Element && target.closest('[data-ohw-link-modal-root], [data-slot="dialog-overlay"]')) {
6713
- return;
6714
- }
6715
- e.preventDefault();
6716
- };
6717
- const scrollOpts = { passive: false, capture: true };
6718
- document.addEventListener("wheel", preventBackgroundScroll, scrollOpts);
6719
- document.addEventListener("touchmove", preventBackgroundScroll, scrollOpts);
8198
+ hoveredItemElRef.current = null;
8199
+ setHoveredItemRect(null);
8200
+ hoveredNavContainerRef.current = null;
8201
+ setHoveredNavContainerRect(null);
8202
+ document.querySelectorAll("[data-ohw-hovered]").forEach((el) => {
8203
+ el.removeAttribute("data-ohw-hovered");
8204
+ });
8205
+ postToParent2({ type: "ow:image-unhover" });
6720
8206
  return () => {
6721
- postToParent({ type: "ow:link-modal-lock", locked: false });
6722
- html.style.overflow = prevHtmlOverflow;
6723
- body.style.overflow = prevBodyOverflow;
6724
- document.removeEventListener("wheel", preventBackgroundScroll, scrollOpts);
6725
- document.removeEventListener("touchmove", preventBackgroundScroll, scrollOpts);
8207
+ document.documentElement.removeAttribute("data-ohw-link-popover-open");
6726
8208
  };
6727
- }, [linkPopover, postToParent]);
6728
- useEffect4(() => {
8209
+ }, [linkPopover, postToParent2]);
8210
+ useEffect8(() => {
6729
8211
  if (!isEditMode) return;
6730
8212
  const useFixtures = shouldUseDevFixtures();
6731
8213
  if (useFixtures) {
@@ -6746,17 +8228,17 @@ function OhhwellsBridge() {
6746
8228
  runSectionsPrefetchRef.current(mapped);
6747
8229
  };
6748
8230
  window.addEventListener("message", onSitePages);
6749
- if (!useFixtures) postToParent({ type: "ow:request-site-pages" });
8231
+ if (!useFixtures) postToParent2({ type: "ow:request-site-pages" });
6750
8232
  return () => window.removeEventListener("message", onSitePages);
6751
- }, [isEditMode, postToParent]);
6752
- useEffect4(() => {
8233
+ }, [isEditMode, postToParent2]);
8234
+ useEffect8(() => {
6753
8235
  if (!isEditMode || shouldUseDevFixtures()) return;
6754
8236
  void loadAllSectionsManifest().then((manifest) => {
6755
8237
  if (Object.keys(manifest).length === 0) return;
6756
8238
  setSectionsByPath((prev) => ({ ...manifest, ...prev }));
6757
8239
  });
6758
8240
  }, [isEditMode]);
6759
- useEffect4(() => {
8241
+ useEffect8(() => {
6760
8242
  const update = () => {
6761
8243
  const el = activeElRef.current ?? selectedElRef.current;
6762
8244
  if (el) setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
@@ -6780,10 +8262,10 @@ function OhhwellsBridge() {
6780
8262
  vvp.removeEventListener("resize", update);
6781
8263
  };
6782
8264
  }, []);
6783
- const refreshStateRules = useCallback2(() => {
8265
+ const refreshStateRules = useCallback4(() => {
6784
8266
  editStylesRef.current?.forceHover && (editStylesRef.current.forceHover.textContent = collectStateRules());
6785
8267
  }, []);
6786
- const processConfigRequest = useCallback2((insertAfterVal) => {
8268
+ const processConfigRequest = useCallback4((insertAfterVal) => {
6787
8269
  const tracker = getSectionsTracker();
6788
8270
  let entries = [];
6789
8271
  try {
@@ -6806,7 +8288,7 @@ function OhhwellsBridge() {
6806
8288
  }
6807
8289
  window.postMessage({ type: "ow:schedule-config", insertAfter: insertAfterVal, scheduleId: null }, "*");
6808
8290
  }, [isEditMode]);
6809
- const deactivate = useCallback2(() => {
8291
+ const deactivate = useCallback4(() => {
6810
8292
  const el = activeElRef.current;
6811
8293
  if (!el) return;
6812
8294
  const key = el.dataset.ohwKey;
@@ -6835,21 +8317,25 @@ function OhhwellsBridge() {
6835
8317
  setMaxBadge(null);
6836
8318
  setActiveCommands(/* @__PURE__ */ new Set());
6837
8319
  setToolbarShowEditLink(false);
6838
- postToParent({ type: "ow:exit-edit" });
6839
- }, [postToParent]);
6840
- const deselect = useCallback2(() => {
8320
+ postToParent2({ type: "ow:exit-edit" });
8321
+ }, [postToParent2]);
8322
+ const deselect = useCallback4(() => {
6841
8323
  selectedElRef.current = null;
6842
8324
  setReorderHrefKey(null);
6843
8325
  setReorderDragDisabled(false);
8326
+ setIsFooterFrameSelection(false);
6844
8327
  siblingHintElRef.current = null;
6845
8328
  setSiblingHintRect(null);
8329
+ setSiblingHintRects([]);
6846
8330
  setIsItemDragging(false);
8331
+ hoveredNavContainerRef.current = null;
8332
+ setHoveredNavContainerRect(null);
6847
8333
  if (!activeElRef.current) {
6848
8334
  setToolbarRect(null);
6849
8335
  setToolbarVariant("none");
6850
8336
  }
6851
8337
  }, []);
6852
- const reselectNavigationItem = useCallback2((navAnchor) => {
8338
+ const reselectNavigationItem = useCallback4((navAnchor) => {
6853
8339
  selectedElRef.current = navAnchor;
6854
8340
  const { key, disabled } = getNavigationItemReorderState(navAnchor);
6855
8341
  setReorderHrefKey(key);
@@ -6859,7 +8345,7 @@ function OhhwellsBridge() {
6859
8345
  setToolbarShowEditLink(false);
6860
8346
  setActiveCommands(/* @__PURE__ */ new Set());
6861
8347
  }, []);
6862
- const commitNavigationTextEdit = useCallback2((navAnchor) => {
8348
+ const commitNavigationTextEdit = useCallback4((navAnchor) => {
6863
8349
  const el = activeElRef.current;
6864
8350
  if (!el) return;
6865
8351
  const key = el.dataset.ohwKey;
@@ -6872,9 +8358,9 @@ function OhhwellsBridge() {
6872
8358
  const html = sanitizeHtml(el.innerHTML);
6873
8359
  const original = originalContentRef.current ?? "";
6874
8360
  if (html !== sanitizeHtml(original)) {
6875
- postToParent({ type: "ow:change", nodes: [{ key, text: html }] });
8361
+ postToParent2({ type: "ow:change", nodes: [{ key, text: html }] });
6876
8362
  const h = document.documentElement.scrollHeight;
6877
- if (h > 50) postToParent({ type: "ow:height", height: h });
8363
+ if (h > 50) postToParent2({ type: "ow:height", height: h });
6878
8364
  }
6879
8365
  }
6880
8366
  el.removeAttribute("contenteditable");
@@ -6882,79 +8368,406 @@ function OhhwellsBridge() {
6882
8368
  setMaxBadge(null);
6883
8369
  setActiveCommands(/* @__PURE__ */ new Set());
6884
8370
  setToolbarShowEditLink(false);
6885
- postToParent({ type: "ow:exit-edit" });
8371
+ postToParent2({ type: "ow:exit-edit" });
6886
8372
  reselectNavigationItem(navAnchor);
6887
- }, [postToParent, reselectNavigationItem]);
6888
- const handleAddNavigationItem = useCallback2(() => {
8373
+ }, [postToParent2, reselectNavigationItem]);
8374
+ const handleAddTopLevelNavItem = useCallback4(() => {
8375
+ const items = listNavbarItems();
8376
+ addNavAfterAnchorRef.current = items[items.length - 1] ?? null;
8377
+ deselectRef.current();
8378
+ deactivateRef.current();
8379
+ bumpLinkPopoverGrace();
8380
+ setLinkPopover({
8381
+ key: "__add-nav__",
8382
+ mode: "create",
8383
+ intent: "add-nav"
8384
+ });
8385
+ }, []);
8386
+ const clearFooterDragVisuals = useCallback4(() => {
8387
+ footerDragRef.current = null;
8388
+ setSiblingHintRects([]);
8389
+ setFooterDropSlots([]);
8390
+ setActiveFooterDropIndex(null);
8391
+ setDraggedItemRect(null);
8392
+ setIsItemDragging(false);
8393
+ unlockFooterDragInteraction();
8394
+ }, []);
8395
+ const refreshFooterDragVisuals = useCallback4((session, activeSlot, clientX, clientY) => {
8396
+ const dragged = session.draggedEl;
8397
+ setDraggedItemRect(dragged.getBoundingClientRect());
8398
+ if (typeof clientX === "number" && typeof clientY === "number") {
8399
+ session.lastClientX = clientX;
8400
+ session.lastClientY = clientY;
8401
+ }
8402
+ session.activeSlot = activeSlot;
8403
+ if (session.kind === "link") {
8404
+ const columns2 = listFooterColumns();
8405
+ const focusCols = /* @__PURE__ */ new Set([session.sourceColumnIndex]);
8406
+ if (activeSlot) focusCols.add(activeSlot.columnIndex);
8407
+ const siblingRects = [];
8408
+ columns2.forEach((col, i) => {
8409
+ if (!focusCols.has(i)) return;
8410
+ for (const link of listFooterLinksInColumn(col)) {
8411
+ if (link === dragged) continue;
8412
+ siblingRects.push(link.getBoundingClientRect());
8413
+ }
8414
+ });
8415
+ setSiblingHintRects(siblingRects);
8416
+ const slots2 = [];
8417
+ columns2.forEach((col, i) => {
8418
+ slots2.push(...buildLinkDropSlots(col, i));
8419
+ });
8420
+ setFooterDropSlots(slots2);
8421
+ const activeIdx2 = activeSlot ? slots2.findIndex((s) => s.columnIndex === activeSlot.columnIndex && s.insertIndex === activeSlot.insertIndex) : -1;
8422
+ setActiveFooterDropIndex(activeIdx2 >= 0 ? activeIdx2 : null);
8423
+ return;
8424
+ }
8425
+ const columns = listFooterColumns();
8426
+ setSiblingHintRects(columns.filter((col) => col !== dragged).map((col) => col.getBoundingClientRect()));
8427
+ const slots = buildColumnDropSlots();
8428
+ setFooterDropSlots(slots);
8429
+ const activeIdx = activeSlot ? slots.findIndex((s) => s.insertIndex === activeSlot.insertIndex) : -1;
8430
+ setActiveFooterDropIndex(activeIdx >= 0 ? activeIdx : null);
8431
+ }, []);
8432
+ const refreshFooterDragVisualsRef = useRef5(refreshFooterDragVisuals);
8433
+ refreshFooterDragVisualsRef.current = refreshFooterDragVisuals;
8434
+ const commitFooterDragRef = useRef5(() => {
8435
+ });
8436
+ const beginFooterDragRef = useRef5(() => {
8437
+ });
8438
+ const beginFooterDrag = useCallback4(
8439
+ (session) => {
8440
+ const rect = session.draggedEl.getBoundingClientRect();
8441
+ session.lastClientX = session.lastClientX || rect.left + rect.width / 2;
8442
+ session.lastClientY = session.lastClientY || rect.top + rect.height / 2;
8443
+ session.activeSlot = session.activeSlot ?? null;
8444
+ footerDragRef.current = session;
8445
+ setIsItemDragging(true);
8446
+ lockFooterDuringDrag();
8447
+ siblingHintElRef.current = null;
8448
+ setSiblingHintRect(null);
8449
+ if (session.wasSelected && selectedElRef.current === session.draggedEl) {
8450
+ setToolbarRect(rect);
8451
+ }
8452
+ const initialSlot = session.kind === "link" && session.hrefKey ? hitTestLinkDropSlot(session.lastClientX, session.lastClientY, session.hrefKey) : hitTestColumnDropSlot(session.lastClientX, session.lastClientY);
8453
+ refreshFooterDragVisuals(session, initialSlot, session.lastClientX, session.lastClientY);
8454
+ },
8455
+ [refreshFooterDragVisuals]
8456
+ );
8457
+ beginFooterDragRef.current = beginFooterDrag;
8458
+ const commitFooterDrag = useCallback4(
8459
+ (clientX, clientY) => {
8460
+ const session = footerDragRef.current;
8461
+ if (!session) {
8462
+ clearFooterDragVisuals();
8463
+ return;
8464
+ }
8465
+ const x = typeof clientX === "number" && (clientX !== 0 || clientY !== 0) ? clientX : session.lastClientX;
8466
+ const y = typeof clientY === "number" && (clientX !== 0 || clientY !== 0) ? clientY : session.lastClientY;
8467
+ let nextOrder = null;
8468
+ const slot = session.activeSlot ?? (session.kind === "link" && session.hrefKey ? hitTestLinkDropSlot(x, y, session.hrefKey) : session.kind === "column" ? hitTestColumnDropSlot(x, y) : null);
8469
+ if (session.kind === "link" && session.hrefKey && slot) {
8470
+ nextOrder = planFooterLinkMove(session.hrefKey, slot.columnIndex, slot.insertIndex);
8471
+ } else if (session.kind === "column" && slot) {
8472
+ nextOrder = planFooterColumnMove(session.sourceColumnIndex, slot.insertIndex);
8473
+ }
8474
+ const wasSelected = session.wasSelected;
8475
+ const draggedEl = session.draggedEl;
8476
+ const hrefKey = session.hrefKey;
8477
+ let movedColumnIndex = null;
8478
+ if (session.kind === "column" && slot && nextOrder) {
8479
+ let adjusted = slot.insertIndex;
8480
+ if (session.sourceColumnIndex < slot.insertIndex) adjusted -= 1;
8481
+ movedColumnIndex = Math.max(0, Math.min(adjusted, nextOrder.length - 1));
8482
+ }
8483
+ clearFooterDragVisuals();
8484
+ const applySelectionAfterDrop = () => {
8485
+ if (!wasSelected) {
8486
+ deselectRef.current();
8487
+ return;
8488
+ }
8489
+ if (hrefKey) {
8490
+ const link = document.querySelector(
8491
+ `footer [data-ohw-href-key="${CSS.escape(hrefKey)}"]`
8492
+ );
8493
+ if (link && isNavigationItem(link)) {
8494
+ selectRef.current(link);
8495
+ return;
8496
+ }
8497
+ }
8498
+ if (movedColumnIndex !== null && nextOrder) {
8499
+ const colKeys = nextOrder[movedColumnIndex] ?? [];
8500
+ let column = null;
8501
+ for (const key of colKeys) {
8502
+ const link = document.querySelector(
8503
+ `footer [data-ohw-href-key="${CSS.escape(key)}"]`
8504
+ );
8505
+ if (link) {
8506
+ column = findFooterColumnForLink(link);
8507
+ if (column) break;
8508
+ }
8509
+ }
8510
+ if (!column) column = listFooterColumns()[movedColumnIndex] ?? null;
8511
+ if (column && isNavigationContainer(column)) {
8512
+ selectFrameRef.current(column);
8513
+ return;
8514
+ }
8515
+ }
8516
+ if (document.body.contains(draggedEl)) {
8517
+ if (isNavigationItem(draggedEl)) {
8518
+ selectRef.current(draggedEl);
8519
+ return;
8520
+ }
8521
+ if (isNavigationContainer(draggedEl)) {
8522
+ selectFrameRef.current(draggedEl);
8523
+ return;
8524
+ }
8525
+ }
8526
+ deselectRef.current();
8527
+ };
8528
+ if (!wasSelected) {
8529
+ deselectRef.current();
8530
+ }
8531
+ if (nextOrder) {
8532
+ const orderJson = JSON.stringify(nextOrder);
8533
+ editContentRef.current = {
8534
+ ...editContentRef.current,
8535
+ [FOOTER_ORDER_KEY]: orderJson
8536
+ };
8537
+ applyFooterOrder(nextOrder);
8538
+ document.querySelectorAll("footer [data-ohw-href-key]").forEach(disableNativeHrefDrag);
8539
+ postToParentRef.current({
8540
+ type: "ow:change",
8541
+ nodes: [{ key: FOOTER_ORDER_KEY, text: orderJson }]
8542
+ });
8543
+ requestAnimationFrame(() => {
8544
+ if (editContentRef.current[FOOTER_ORDER_KEY] === orderJson) {
8545
+ applyFooterOrder(nextOrder);
8546
+ document.querySelectorAll("footer [data-ohw-href-key]").forEach(disableNativeHrefDrag);
8547
+ }
8548
+ requestAnimationFrame(applySelectionAfterDrop);
8549
+ });
8550
+ return;
8551
+ }
8552
+ applySelectionAfterDrop();
8553
+ },
8554
+ [clearFooterDragVisuals]
8555
+ );
8556
+ commitFooterDragRef.current = commitFooterDrag;
8557
+ const startFooterLinkDrag = useCallback4(
8558
+ (anchor, clientX, clientY, wasSelected) => {
8559
+ const hrefKey = anchor.getAttribute("data-ohw-href-key");
8560
+ if (!hrefKey || !isFooterHrefKey(hrefKey)) return false;
8561
+ const column = findFooterColumnForLink(anchor);
8562
+ const columns = listFooterColumns();
8563
+ beginFooterDrag({
8564
+ kind: "link",
8565
+ hrefKey,
8566
+ columnEl: column,
8567
+ sourceColumnIndex: column ? columns.indexOf(column) : 0,
8568
+ wasSelected,
8569
+ draggedEl: anchor,
8570
+ lastClientX: clientX,
8571
+ lastClientY: clientY,
8572
+ activeSlot: null
8573
+ });
8574
+ return true;
8575
+ },
8576
+ [beginFooterDrag]
8577
+ );
8578
+ const startFooterColumnDrag = useCallback4(
8579
+ (columnEl, clientX, clientY, wasSelected) => {
8580
+ const columns = listFooterColumns();
8581
+ const idx = columns.indexOf(columnEl);
8582
+ if (idx < 0) return false;
8583
+ beginFooterDrag({
8584
+ kind: "column",
8585
+ hrefKey: null,
8586
+ columnEl,
8587
+ sourceColumnIndex: idx,
8588
+ wasSelected,
8589
+ draggedEl: columnEl,
8590
+ lastClientX: clientX,
8591
+ lastClientY: clientY,
8592
+ activeSlot: null
8593
+ });
8594
+ return true;
8595
+ },
8596
+ [beginFooterDrag]
8597
+ );
8598
+ const handleItemDragStart = useCallback4(
8599
+ (e) => {
8600
+ const selected = selectedElRef.current;
8601
+ if (!selected) {
8602
+ setIsItemDragging(true);
8603
+ return;
8604
+ }
8605
+ const clientX = e?.clientX ?? selected.getBoundingClientRect().left + 8;
8606
+ const clientY = e?.clientY ?? selected.getBoundingClientRect().top + 8;
8607
+ if (startFooterLinkDrag(selected, clientX, clientY, true)) return;
8608
+ if (selected.hasAttribute("data-ohw-footer-col") || isInferredFooterGroup(selected) && selected.closest("footer")) {
8609
+ if (startFooterColumnDrag(selected, clientX, clientY, true)) return;
8610
+ }
8611
+ siblingHintElRef.current = null;
8612
+ setSiblingHintRect(null);
8613
+ setIsItemDragging(true);
8614
+ },
8615
+ [startFooterColumnDrag, startFooterLinkDrag]
8616
+ );
8617
+ const handleItemDragEnd = useCallback4(
8618
+ (e) => {
8619
+ if (footerDragRef.current) {
8620
+ const x = e?.clientX;
8621
+ const y = e?.clientY;
8622
+ if (typeof x === "number" && typeof y === "number" && (x !== 0 || y !== 0)) {
8623
+ commitFooterDrag(x, y);
8624
+ } else {
8625
+ commitFooterDrag();
8626
+ }
8627
+ return;
8628
+ }
8629
+ setIsItemDragging(false);
8630
+ },
8631
+ [commitFooterDrag]
8632
+ );
8633
+ const handleItemChromePointerDown = useCallback4((e) => {
8634
+ if (e.button !== 0) return;
6889
8635
  const selected = selectedElRef.current;
6890
8636
  if (!selected) return;
6891
- const target = getNavigationItemAddHintTarget(selected);
6892
- siblingHintElRef.current = target;
6893
- setSiblingHintRect(target.getBoundingClientRect());
6894
- }, []);
6895
- const handleItemDragStart = useCallback2(() => {
6896
- siblingHintElRef.current = null;
6897
- setSiblingHintRect(null);
6898
- setIsItemDragging(true);
8637
+ armFooterPressDrag();
8638
+ const hrefKey = selected.getAttribute("data-ohw-href-key");
8639
+ if (hrefKey && isFooterHrefKey(hrefKey)) {
8640
+ footerPointerDragRef.current = {
8641
+ el: selected,
8642
+ kind: "link",
8643
+ startX: e.clientX,
8644
+ startY: e.clientY,
8645
+ pointerId: e.pointerId,
8646
+ wasSelected: true,
8647
+ started: false
8648
+ };
8649
+ return;
8650
+ }
8651
+ if (selected.hasAttribute("data-ohw-footer-col") || isInferredFooterGroup(selected) && selected.closest("footer")) {
8652
+ footerPointerDragRef.current = {
8653
+ el: selected,
8654
+ kind: "column",
8655
+ startX: e.clientX,
8656
+ startY: e.clientY,
8657
+ pointerId: e.pointerId,
8658
+ wasSelected: true,
8659
+ started: false
8660
+ };
8661
+ }
6899
8662
  }, []);
6900
- const handleItemDragEnd = useCallback2(() => {
6901
- setIsItemDragging(false);
8663
+ const handleItemChromeClick = useCallback4((clientX, clientY) => {
8664
+ if (suppressNextClickRef.current) {
8665
+ suppressNextClickRef.current = false;
8666
+ return;
8667
+ }
8668
+ const selected = selectedElRef.current;
8669
+ if (!selected || !isNavigationItem(selected)) return;
8670
+ const editable = selected.querySelector('[data-ohw-editable="text"]') ?? selected.querySelector("[data-ohw-editable]");
8671
+ if (!editable) return;
8672
+ activateRef.current(editable, { caretX: clientX, caretY: clientY });
6902
8673
  }, []);
6903
8674
  reselectNavigationItemRef.current = reselectNavigationItem;
6904
8675
  commitNavigationTextEditRef.current = commitNavigationTextEdit;
6905
- const select = useCallback2((anchor) => {
6906
- if (!isNavigationItem(anchor)) return;
6907
- if (activeElRef.current) deactivate();
6908
- selectedElRef.current = anchor;
6909
- clearHrefKeyHover(anchor);
6910
- setHoveredItemRect(null);
6911
- hoveredItemElRef.current = null;
6912
- siblingHintElRef.current = null;
6913
- setSiblingHintRect(null);
6914
- setIsItemDragging(false);
6915
- const { key, disabled } = getNavigationItemReorderState(anchor);
6916
- setReorderHrefKey(key);
6917
- setReorderDragDisabled(disabled);
6918
- setToolbarVariant("link-action");
6919
- setToolbarRect(anchor.getBoundingClientRect());
6920
- setToolbarShowEditLink(false);
6921
- setActiveCommands(/* @__PURE__ */ new Set());
6922
- }, [deactivate]);
6923
- const activate = useCallback2((el) => {
6924
- if (activeElRef.current === el) return;
6925
- selectedElRef.current = null;
6926
- deactivate();
6927
- if (hoveredImageRef.current) {
6928
- hoveredImageRef.current = null;
6929
- setMediaHover(null);
6930
- }
6931
- setToolbarVariant("rich-text");
6932
- siblingHintElRef.current = null;
6933
- setSiblingHintRect(null);
6934
- setIsItemDragging(false);
6935
- el.setAttribute("contenteditable", "true");
6936
- el.removeAttribute("data-ohw-hovered");
6937
- el.closest("[data-ohw-href-key]")?.removeAttribute("data-ohw-hovered");
6938
- activeElRef.current = el;
6939
- originalContentRef.current = el.innerHTML;
6940
- el.focus();
6941
- setToolbarShowEditLink(Boolean(getHrefKeyFromElement(el)));
6942
- const navAnchor = getNavigationItemAnchor(el);
6943
- if (navAnchor) {
8676
+ const select = useCallback4(
8677
+ (anchor) => {
8678
+ if (!isNavigationItem(anchor)) return;
8679
+ if (activeElRef.current) deactivate();
8680
+ selectedElRef.current = anchor;
8681
+ clearHrefKeyHover(anchor);
8682
+ hoveredNavContainerRef.current = null;
8683
+ setHoveredNavContainerRect(null);
8684
+ setHoveredItemRect(null);
8685
+ hoveredItemElRef.current = null;
8686
+ siblingHintElRef.current = null;
8687
+ setSiblingHintRect(null);
8688
+ setSiblingHintRects([]);
8689
+ setIsItemDragging(false);
8690
+ const { key, disabled } = getNavigationItemReorderState(anchor);
8691
+ setReorderHrefKey(key);
8692
+ setReorderDragDisabled(disabled);
8693
+ setIsFooterFrameSelection(false);
8694
+ setToolbarVariant("link-action");
8695
+ setToolbarRect(anchor.getBoundingClientRect());
8696
+ setToolbarShowEditLink(false);
8697
+ setActiveCommands(/* @__PURE__ */ new Set());
8698
+ },
8699
+ [deactivate]
8700
+ );
8701
+ const selectFrame = useCallback4(
8702
+ (el) => {
8703
+ if (!isNavigationContainer(el)) return;
8704
+ if (activeElRef.current) deactivate();
8705
+ const isFooterColumn = el.hasAttribute("data-ohw-footer-col") || Boolean(el.closest("footer") && isInferredFooterGroup(el));
8706
+ selectedElRef.current = el;
8707
+ clearHrefKeyHover(el);
8708
+ hoveredNavContainerRef.current = null;
8709
+ setHoveredNavContainerRect(null);
8710
+ setHoveredItemRect(null);
8711
+ hoveredItemElRef.current = null;
8712
+ siblingHintElRef.current = null;
8713
+ setSiblingHintRect(null);
8714
+ setSiblingHintRects(
8715
+ isFooterColumn ? listFooterColumns().filter((column) => column !== el).map((column) => column.getBoundingClientRect()) : []
8716
+ );
8717
+ setIsItemDragging(false);
6944
8718
  setReorderHrefKey(null);
6945
8719
  setReorderDragDisabled(false);
6946
- } else {
6947
- const { key: reorderKey, disabled: reorderDisabled } = getReorderHandleState(el);
6948
- setReorderHrefKey(reorderKey);
6949
- setReorderDragDisabled(reorderDisabled);
6950
- }
6951
- setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
6952
- postToParent({ type: "ow:enter-edit", key: el.dataset.ohwKey });
6953
- requestAnimationFrame(() => refreshActiveCommandsRef.current());
6954
- }, [deactivate, postToParent]);
8720
+ setIsFooterFrameSelection(isFooterColumn);
8721
+ setToolbarVariant("select-frame");
8722
+ setToolbarRect(el.getBoundingClientRect());
8723
+ setToolbarShowEditLink(false);
8724
+ setActiveCommands(/* @__PURE__ */ new Set());
8725
+ },
8726
+ [deactivate]
8727
+ );
8728
+ const activate = useCallback4(
8729
+ (el, options) => {
8730
+ if (activeElRef.current === el) return;
8731
+ selectedElRef.current = null;
8732
+ deactivate();
8733
+ if (hoveredImageRef.current) {
8734
+ hoveredImageRef.current = null;
8735
+ postToParentRef.current({ type: "ow:image-unhover" });
8736
+ }
8737
+ setToolbarVariant("rich-text");
8738
+ siblingHintElRef.current = null;
8739
+ setSiblingHintRect(null);
8740
+ setSiblingHintRects([]);
8741
+ setIsItemDragging(false);
8742
+ el.setAttribute("contenteditable", "true");
8743
+ el.removeAttribute("data-ohw-hovered");
8744
+ el.closest("[data-ohw-href-key]")?.removeAttribute("data-ohw-hovered");
8745
+ activeElRef.current = el;
8746
+ originalContentRef.current = el.innerHTML;
8747
+ el.focus();
8748
+ if (options?.caretX !== void 0 && options?.caretY !== void 0) {
8749
+ placeCaretAtPoint(el, options.caretX, options.caretY);
8750
+ }
8751
+ setToolbarShowEditLink(Boolean(getHrefKeyFromElement(el)));
8752
+ const navAnchor = getNavigationItemAnchor(el);
8753
+ if (navAnchor) {
8754
+ setReorderHrefKey(null);
8755
+ setReorderDragDisabled(false);
8756
+ } else {
8757
+ const { key: reorderKey, disabled: reorderDisabled } = getReorderHandleState(el);
8758
+ setReorderHrefKey(reorderKey);
8759
+ setReorderDragDisabled(reorderDisabled);
8760
+ }
8761
+ setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
8762
+ postToParent2({ type: "ow:enter-edit", key: el.dataset.ohwKey });
8763
+ requestAnimationFrame(() => refreshActiveCommandsRef.current());
8764
+ },
8765
+ [deactivate, postToParent2]
8766
+ );
6955
8767
  activateRef.current = activate;
6956
8768
  deactivateRef.current = deactivate;
6957
8769
  selectRef.current = select;
8770
+ selectFrameRef.current = selectFrame;
6958
8771
  deselectRef.current = deselect;
6959
8772
  useLayoutEffect2(() => {
6960
8773
  if (!subdomain || isEditMode) {
@@ -6966,6 +8779,7 @@ function OhhwellsBridge() {
6966
8779
  for (const [key, val] of Object.entries(content)) {
6967
8780
  if (key === "__ohw_sections") continue;
6968
8781
  if (applyVideoSettingNode(key, val)) continue;
8782
+ if (applyCarouselNode(key, val)) continue;
6969
8783
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
6970
8784
  if (el.dataset.ohwEditable === "image") {
6971
8785
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -6996,6 +8810,8 @@ function OhhwellsBridge() {
6996
8810
  });
6997
8811
  applyLinkByKey(key, val);
6998
8812
  }
8813
+ reconcileNavbarItemsFromContent(content);
8814
+ reconcileFooterOrderFromContent(content);
6999
8815
  enforceLinkHrefs();
7000
8816
  initSectionsFromContent(content, true);
7001
8817
  sectionsLoadedRef.current = true;
@@ -7024,7 +8840,7 @@ function OhhwellsBridge() {
7024
8840
  cancelled = true;
7025
8841
  };
7026
8842
  }, [subdomain, isEditMode]);
7027
- useEffect4(() => {
8843
+ useEffect8(() => {
7028
8844
  if (!subdomain || isEditMode) return;
7029
8845
  let debounceTimer = null;
7030
8846
  let observer = null;
@@ -7037,6 +8853,7 @@ function OhhwellsBridge() {
7037
8853
  for (const [key, val] of Object.entries(content)) {
7038
8854
  if (key === "__ohw_sections") continue;
7039
8855
  if (applyVideoSettingNode(key, val)) continue;
8856
+ if (applyCarouselNode(key, val)) continue;
7040
8857
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
7041
8858
  if (el.dataset.ohwEditable === "image") {
7042
8859
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -7055,6 +8872,8 @@ function OhhwellsBridge() {
7055
8872
  });
7056
8873
  applyLinkByKey(key, val);
7057
8874
  }
8875
+ reconcileNavbarItemsFromContent(content);
8876
+ reconcileFooterOrderFromContent(content);
7058
8877
  } finally {
7059
8878
  observer?.observe(document.body, { childList: true, subtree: true });
7060
8879
  }
@@ -7078,20 +8897,65 @@ function OhhwellsBridge() {
7078
8897
  const visible = Boolean(subdomain) && fetchState !== "done";
7079
8898
  el.style.display = visible ? "flex" : "none";
7080
8899
  }, [subdomain, fetchState]);
7081
- useEffect4(() => {
7082
- postToParent({ type: "ow:navigation", path: pathname });
7083
- }, [pathname, postToParent]);
7084
- useEffect4(() => {
8900
+ useEffect8(() => {
8901
+ postToParent2({ type: "ow:navigation", path: pathname });
8902
+ }, [pathname, postToParent2]);
8903
+ useEffect8(() => {
7085
8904
  if (!isEditMode) return;
8905
+ if (linkPopoverSessionRef.current?.intent === "add-nav") return;
8906
+ if (document.querySelector("[data-ohw-section-picker]")) return;
7086
8907
  setLinkPopover(null);
7087
8908
  deselectRef.current();
7088
8909
  deactivateRef.current();
7089
8910
  }, [pathname, isEditMode]);
7090
- useEffect4(() => {
8911
+ useEffect8(() => {
8912
+ const contentForNav = () => {
8913
+ if (isEditMode) return editContentRef.current;
8914
+ if (!subdomain) return {};
8915
+ return contentCache.get(subdomain) ?? {};
8916
+ };
8917
+ let applying = false;
8918
+ let rafId = null;
8919
+ const run = () => {
8920
+ if (footerDragRef.current || applying) return;
8921
+ applying = true;
8922
+ try {
8923
+ const content = contentForNav();
8924
+ reconcileNavbarItemsFromContent(content);
8925
+ reconcileFooterOrderFromContent(content);
8926
+ document.querySelectorAll("footer [data-ohw-href-key]").forEach((el) => {
8927
+ if (isFooterHrefKey(el.getAttribute("data-ohw-href-key"))) {
8928
+ disableNativeHrefDrag(el);
8929
+ }
8930
+ });
8931
+ } finally {
8932
+ applying = false;
8933
+ }
8934
+ };
8935
+ const scheduleRun = () => {
8936
+ if (rafId !== null) cancelAnimationFrame(rafId);
8937
+ rafId = requestAnimationFrame(() => {
8938
+ rafId = null;
8939
+ run();
8940
+ });
8941
+ };
8942
+ run();
8943
+ const nav = document.querySelector("nav");
8944
+ const footer = document.querySelector("footer");
8945
+ const observer = new MutationObserver(scheduleRun);
8946
+ if (nav) observer.observe(nav, { childList: true, subtree: true });
8947
+ if (footer) observer.observe(footer, { childList: true, subtree: true });
8948
+ if (!nav && !footer) return;
8949
+ return () => {
8950
+ observer.disconnect();
8951
+ if (rafId !== null) cancelAnimationFrame(rafId);
8952
+ };
8953
+ }, [isEditMode, pathname, subdomain, fetchState]);
8954
+ useEffect8(() => {
7091
8955
  if (!isEditMode) return;
7092
8956
  const measure = () => {
7093
8957
  const h = document.body.scrollHeight;
7094
- if (h > 50) postToParent({ type: "ow:height", height: h });
8958
+ if (h > 50) postToParent2({ type: "ow:height", height: h });
7095
8959
  };
7096
8960
  const t1 = setTimeout(measure, 50);
7097
8961
  const t2 = setTimeout(measure, 500);
@@ -7110,8 +8974,8 @@ function OhhwellsBridge() {
7110
8974
  if (resizeTimer) clearTimeout(resizeTimer);
7111
8975
  window.removeEventListener("resize", handleResize);
7112
8976
  };
7113
- }, [pathname, isEditMode, postToParent]);
7114
- useEffect4(() => {
8977
+ }, [pathname, isEditMode, postToParent2]);
8978
+ useEffect8(() => {
7115
8979
  if (!subdomainFromQuery || isEditMode) return;
7116
8980
  const handleClick = (e) => {
7117
8981
  const anchor = e.target.closest("a");
@@ -7127,7 +8991,7 @@ function OhhwellsBridge() {
7127
8991
  document.addEventListener("click", handleClick, true);
7128
8992
  return () => document.removeEventListener("click", handleClick, true);
7129
8993
  }, [subdomainFromQuery, isEditMode, router]);
7130
- useEffect4(() => {
8994
+ useEffect8(() => {
7131
8995
  if (!isEditMode) {
7132
8996
  editStylesRef.current?.base.remove();
7133
8997
  editStylesRef.current?.forceHover.remove();
@@ -7150,6 +9014,32 @@ function OhhwellsBridge() {
7150
9014
  [data-ohw-editable] {
7151
9015
  display: block;
7152
9016
  }
9017
+ /* Native <a> drag steals clicks \u2014 disable for edit links; reorder via press-to-drag / handle. */
9018
+ footer [data-ohw-href-key] {
9019
+ -webkit-user-drag: none !important;
9020
+ }
9021
+ footer [data-ohw-footer-col]:hover {
9022
+ outline: 1.5px dashed ${PRIMARY2} !important;
9023
+ outline-offset: 6px;
9024
+ border-radius: 8px;
9025
+ }
9026
+ html[data-ohw-link-popover-open] footer [data-ohw-footer-col]:hover {
9027
+ outline: none !important;
9028
+ }
9029
+ html[data-ohw-footer-press-drag] footer,
9030
+ html[data-ohw-footer-press-drag] footer * {
9031
+ user-select: none !important;
9032
+ -webkit-user-select: none !important;
9033
+ }
9034
+ html[data-ohw-item-dragging] footer,
9035
+ html[data-ohw-item-dragging] footer * {
9036
+ user-select: none !important;
9037
+ -webkit-user-select: none !important;
9038
+ pointer-events: none !important;
9039
+ }
9040
+ html[data-ohw-item-dragging] {
9041
+ cursor: grabbing !important;
9042
+ }
7153
9043
  [data-ohw-editable]:not([contenteditable]):not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"]):not([data-ohw-editable="video"]) { cursor: text !important; }
7154
9044
  [data-ohw-editable="image"], [data-ohw-editable="image"] *,
7155
9045
  [data-ohw-editable="video"], [data-ohw-editable="video"] *,
@@ -7195,12 +9085,46 @@ function OhhwellsBridge() {
7195
9085
  }
7196
9086
  refreshStateRules();
7197
9087
  const handleClick = (e) => {
9088
+ if (suppressNextClickRef.current) {
9089
+ suppressNextClickRef.current = false;
9090
+ e.preventDefault();
9091
+ e.stopPropagation();
9092
+ return;
9093
+ }
7198
9094
  const target = e.target;
7199
9095
  if (target.closest("[data-ohw-toolbar]")) return;
7200
9096
  if (target.closest("[data-ohw-state-toggle]")) return;
7201
9097
  if (target.closest("[data-ohw-max-badge]")) return;
7202
9098
  if (isInsideLinkEditor(target)) return;
7203
9099
  if (target.closest('[data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
9100
+ if (target.closest("[data-ohw-item-drag-surface]")) {
9101
+ e.preventDefault();
9102
+ e.stopPropagation();
9103
+ const selected = selectedElRef.current;
9104
+ if (selected && isNavigationItem(selected)) {
9105
+ const editable2 = selected.querySelector('[data-ohw-editable="text"]') ?? selected.querySelector("[data-ohw-editable]");
9106
+ if (editable2) {
9107
+ activateRef.current(editable2, {
9108
+ caretX: e.clientX,
9109
+ caretY: e.clientY
9110
+ });
9111
+ }
9112
+ } else if (selected?.hasAttribute("data-ohw-footer-col")) {
9113
+ const link = listFooterLinksInColumn(selected).find((el) => {
9114
+ const r2 = el.getBoundingClientRect();
9115
+ return e.clientX >= r2.left && e.clientX <= r2.right && e.clientY >= r2.top && e.clientY <= r2.bottom;
9116
+ });
9117
+ if (link) selectRef.current(link);
9118
+ }
9119
+ return;
9120
+ }
9121
+ const navFromChrome = resolveNavContainerSelectionTarget(target, e.clientX, e.clientY);
9122
+ if (navFromChrome) {
9123
+ e.preventDefault();
9124
+ e.stopPropagation();
9125
+ selectFrameRef.current(navFromChrome);
9126
+ return;
9127
+ }
7204
9128
  e.preventDefault();
7205
9129
  e.stopPropagation();
7206
9130
  return;
@@ -7215,14 +9139,23 @@ function OhhwellsBridge() {
7215
9139
  bumpLinkPopoverGrace();
7216
9140
  setLinkPopoverRef.current({
7217
9141
  key: editable.dataset.ohwKey ?? "",
7218
- target: getLinkHref(editable)
9142
+ mode: "edit",
9143
+ target: getLinkHref2(editable)
7219
9144
  });
7220
9145
  return;
7221
9146
  }
7222
9147
  if (isMediaEditable(editable)) {
7223
9148
  e.preventDefault();
7224
9149
  e.stopPropagation();
7225
- postToParentRef.current({ type: "ow:image-pick", key: editable.dataset.ohwKey ?? "", elementType: editable.dataset.ohwEditable ?? "image" });
9150
+ const key = editable.dataset.ohwKey ?? "";
9151
+ const r2 = editable.getBoundingClientRect();
9152
+ if (key && r2.width > 1 && r2.height > 1) {
9153
+ pendingUploadRectRef.current = {
9154
+ key,
9155
+ rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height }
9156
+ };
9157
+ }
9158
+ postToParentRef.current({ type: "ow:image-pick", key, elementType: editable.dataset.ohwEditable ?? "image" });
7226
9159
  return;
7227
9160
  }
7228
9161
  const hrefCtx = getHrefKeyFromElement(editable);
@@ -7231,7 +9164,8 @@ function OhhwellsBridge() {
7231
9164
  e.preventDefault();
7232
9165
  e.stopPropagation();
7233
9166
  if (selectedElRef.current === navAnchor) {
7234
- activateRef.current(editable);
9167
+ if (e.detail >= 2) return;
9168
+ activateRef.current(editable, { caretX: e.clientX, caretY: e.clientY });
7235
9169
  return;
7236
9170
  }
7237
9171
  selectRef.current(navAnchor);
@@ -7246,10 +9180,42 @@ function OhhwellsBridge() {
7246
9180
  if (hrefAnchor) {
7247
9181
  e.preventDefault();
7248
9182
  e.stopPropagation();
7249
- if (selectedElRef.current === hrefAnchor) return;
9183
+ if (selectedElRef.current === hrefAnchor) {
9184
+ const textEditable = hrefAnchor.querySelector('[data-ohw-editable="text"]') ?? hrefAnchor.querySelector("[data-ohw-editable]");
9185
+ if (textEditable) {
9186
+ activateRef.current(textEditable, {
9187
+ caretX: e.clientX,
9188
+ caretY: e.clientY
9189
+ });
9190
+ }
9191
+ return;
9192
+ }
7250
9193
  selectRef.current(hrefAnchor);
7251
9194
  return;
7252
9195
  }
9196
+ const footerColClick = target.closest("[data-ohw-footer-col]");
9197
+ if (footerColClick) {
9198
+ e.preventDefault();
9199
+ e.stopPropagation();
9200
+ selectFrameRef.current(footerColClick);
9201
+ return;
9202
+ }
9203
+ const navContainerToSelect = resolveNavContainerSelectionTarget(target, e.clientX, e.clientY);
9204
+ if (navContainerToSelect) {
9205
+ e.preventDefault();
9206
+ e.stopPropagation();
9207
+ selectFrameRef.current(navContainerToSelect);
9208
+ return;
9209
+ }
9210
+ const selectedContainer = selectedElRef.current;
9211
+ if (selectedContainer && isNavigationContainer(selectedContainer)) {
9212
+ const navItem = getNavigationItemAnchor(target);
9213
+ if (!navItem && (selectedContainer === target || selectedContainer.contains(target))) {
9214
+ e.preventDefault();
9215
+ e.stopPropagation();
9216
+ return;
9217
+ }
9218
+ }
7253
9219
  if (activeElRef.current) {
7254
9220
  const sel = window.getSelection();
7255
9221
  if (sel && !sel.isCollapsed) {
@@ -7275,10 +9241,65 @@ function OhhwellsBridge() {
7275
9241
  deselectRef.current();
7276
9242
  deactivateRef.current();
7277
9243
  };
9244
+ const handleDblClick = (e) => {
9245
+ const target = e.target;
9246
+ if (target.closest("[data-ohw-toolbar]")) return;
9247
+ if (target.closest("[data-ohw-state-toggle]")) return;
9248
+ if (target.closest("[data-ohw-max-badge]")) return;
9249
+ if (isInsideLinkEditor(target)) return;
9250
+ if (target.closest('[data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
9251
+ return;
9252
+ }
9253
+ const navLabel = getNavigationLabelEditable(target);
9254
+ if (!navLabel) return;
9255
+ const { editable } = navLabel;
9256
+ e.preventDefault();
9257
+ e.stopPropagation();
9258
+ if (activeElRef.current !== editable) {
9259
+ activateRef.current(editable);
9260
+ }
9261
+ requestAnimationFrame(() => {
9262
+ selectAllTextInEditable(editable);
9263
+ refreshActiveCommandsRef.current();
9264
+ });
9265
+ };
7278
9266
  const handleMouseOver = (e) => {
9267
+ if (document.documentElement.hasAttribute("data-ohw-section-picking")) return;
7279
9268
  const target = e.target;
9269
+ if (linkPopoverOpenRef.current) {
9270
+ hoveredItemElRef.current = null;
9271
+ setHoveredItemRect(null);
9272
+ hoveredNavContainerRef.current = null;
9273
+ setHoveredNavContainerRect(null);
9274
+ return;
9275
+ }
9276
+ if (toolbarVariantRef.current !== "select-frame") {
9277
+ const navContainer = target.closest("[data-ohw-nav-container]");
9278
+ if (navContainer && !getNavigationItemAnchor(target)) {
9279
+ hoveredNavContainerRef.current = navContainer;
9280
+ setHoveredNavContainerRect(navContainer.getBoundingClientRect());
9281
+ hoveredItemElRef.current = null;
9282
+ setHoveredItemRect(null);
9283
+ return;
9284
+ }
9285
+ if (!target.closest("[data-ohw-nav-container]")) {
9286
+ hoveredNavContainerRef.current = null;
9287
+ setHoveredNavContainerRect(null);
9288
+ }
9289
+ }
9290
+ const footerCol = target.closest("[data-ohw-footer-col]");
9291
+ if (footerCol) {
9292
+ hoveredNavContainerRef.current = null;
9293
+ setHoveredNavContainerRect(null);
9294
+ if (selectedElRef.current === footerCol) return;
9295
+ hoveredItemElRef.current = footerCol;
9296
+ setHoveredItemRect(footerCol.getBoundingClientRect());
9297
+ return;
9298
+ }
7280
9299
  const navAnchor = getNavigationItemAnchor(target);
7281
9300
  if (navAnchor) {
9301
+ hoveredNavContainerRef.current = null;
9302
+ setHoveredNavContainerRect(null);
7282
9303
  const selected2 = selectedElRef.current;
7283
9304
  if (selected2 === navAnchor || selected2?.contains(navAnchor)) return;
7284
9305
  clearHrefKeyHover(navAnchor);
@@ -7303,6 +9324,23 @@ function OhhwellsBridge() {
7303
9324
  };
7304
9325
  const handleMouseOut = (e) => {
7305
9326
  const target = e.target;
9327
+ if (target.closest("[data-ohw-nav-container]")) {
9328
+ const related2 = e.relatedTarget instanceof Element ? e.relatedTarget : null;
9329
+ if (related2?.closest("[data-ohw-nav-container], [data-ohw-navbar-container-chrome], [data-ohw-navbar-add-button]")) {
9330
+ return;
9331
+ }
9332
+ hoveredNavContainerRef.current = null;
9333
+ setHoveredNavContainerRect(null);
9334
+ }
9335
+ const footerCol = target.closest("[data-ohw-footer-col]");
9336
+ if (footerCol && hoveredItemElRef.current === footerCol) {
9337
+ const related2 = e.relatedTarget instanceof Element ? e.relatedTarget : null;
9338
+ if (related2?.closest("[data-ohw-drag-handle-container], [data-ohw-item-interaction]")) return;
9339
+ if (related2?.closest("[data-ohw-footer-col]") === footerCol) return;
9340
+ hoveredItemElRef.current = null;
9341
+ setHoveredItemRect(null);
9342
+ return;
9343
+ }
7306
9344
  const navAnchor = getNavigationItemAnchor(target);
7307
9345
  if (navAnchor) {
7308
9346
  const related2 = e.relatedTarget instanceof Element ? e.relatedTarget : null;
@@ -7424,44 +9462,87 @@ function OhhwellsBridge() {
7424
9462
  }
7425
9463
  return smallest;
7426
9464
  };
9465
+ const dismissImageHover = () => {
9466
+ if (hoveredImageRef.current) {
9467
+ hoveredImageRef.current = null;
9468
+ hoveredImageHasTextOverlapRef.current = false;
9469
+ resumeAnimTracks();
9470
+ postToParentRef.current({ type: "ow:image-unhover" });
9471
+ }
9472
+ clearImageHover();
9473
+ };
9474
+ const probeNavigationHoverAt = (x, y) => {
9475
+ if (toolbarVariantRef.current === "select-frame") {
9476
+ hoveredNavContainerRef.current = null;
9477
+ setHoveredNavContainerRect(null);
9478
+ return;
9479
+ }
9480
+ const navContainer = document.querySelector("[data-ohw-nav-container]");
9481
+ if (!navContainer) {
9482
+ hoveredNavContainerRef.current = null;
9483
+ setHoveredNavContainerRect(null);
9484
+ return;
9485
+ }
9486
+ const containerRect = navContainer.getBoundingClientRect();
9487
+ const overContainer = x >= containerRect.left && x <= containerRect.right && y >= containerRect.top && y <= containerRect.bottom;
9488
+ if (!overContainer) {
9489
+ hoveredNavContainerRef.current = null;
9490
+ setHoveredNavContainerRect(null);
9491
+ return;
9492
+ }
9493
+ const navItemHit = Array.from(
9494
+ navContainer.querySelectorAll("[data-ohw-href-key]")
9495
+ ).find((el) => {
9496
+ if (!isNavigationItem(el) || isNavbarButton2(el)) return false;
9497
+ const itemRect = el.getBoundingClientRect();
9498
+ return x >= itemRect.left && x <= itemRect.right && y >= itemRect.top && y <= itemRect.bottom;
9499
+ });
9500
+ if (navItemHit) {
9501
+ hoveredNavContainerRef.current = null;
9502
+ setHoveredNavContainerRect(null);
9503
+ const selected = selectedElRef.current;
9504
+ if (selected !== navItemHit && !selected?.contains(navItemHit)) {
9505
+ clearHrefKeyHover(navItemHit);
9506
+ hoveredItemElRef.current = navItemHit;
9507
+ setHoveredItemRect(navItemHit.getBoundingClientRect());
9508
+ }
9509
+ return;
9510
+ }
9511
+ hoveredNavContainerRef.current = navContainer;
9512
+ setHoveredNavContainerRect(containerRect);
9513
+ hoveredItemElRef.current = null;
9514
+ setHoveredItemRect(null);
9515
+ };
7427
9516
  const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
7428
9517
  if (linkPopoverOpenRef.current) {
7429
- if (hoveredImageRef.current) {
7430
- hoveredImageRef.current = null;
7431
- hoveredImageHasTextOverlapRef.current = false;
7432
- resumeAnimTracks();
7433
- clearImageHover();
7434
- }
9518
+ dismissImageHover();
9519
+ return;
9520
+ }
9521
+ const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
9522
+ if (isPointOverNavigation(x, y)) {
9523
+ dismissImageHover();
9524
+ probeNavigationHoverAt(x, y);
7435
9525
  return;
7436
9526
  }
9527
+ hoveredNavContainerRef.current = null;
9528
+ setHoveredNavContainerRect(null);
7437
9529
  const toggleEl = document.querySelector("[data-ohw-state-toggle]");
7438
9530
  if (toggleEl) {
7439
- const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
7440
9531
  const tr = toggleEl.getBoundingClientRect();
7441
9532
  if (x >= tr.left && x <= tr.right && y >= tr.top && y <= tr.bottom) {
7442
- if (hoveredImageRef.current) {
7443
- hoveredImageRef.current = null;
7444
- resumeAnimTracks();
7445
- clearImageHover();
7446
- }
9533
+ dismissImageHover();
7447
9534
  return;
7448
9535
  }
7449
9536
  }
7450
9537
  const imgEl = findImageAtPoint(clientX, clientY, fromParentViewport);
7451
9538
  const activeEl = activeElRef.current;
7452
9539
  if (activeEl && (!imgEl || imgEl.contains(activeEl))) {
7453
- if (hoveredImageRef.current) {
7454
- hoveredImageRef.current = null;
7455
- hoveredImageHasTextOverlapRef.current = false;
7456
- resumeAnimTracks();
7457
- clearImageHover();
7458
- }
9540
+ dismissImageHover();
7459
9541
  return;
7460
9542
  }
7461
9543
  if (imgEl) {
7462
- const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
7463
9544
  const topEl = document.elementFromPoint(x, y);
7464
- if (topEl?.closest('[data-ohw-toolbar], [data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
9545
+ 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]')) {
7465
9546
  if (hoveredImageRef.current) {
7466
9547
  hoveredImageRef.current = null;
7467
9548
  resumeAnimTracks();
@@ -7559,12 +9640,12 @@ function OhhwellsBridge() {
7559
9640
  resumeAnimTracks();
7560
9641
  clearImageHover();
7561
9642
  }
7562
- const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
9643
+ const { x: x2, y: y2 } = toProbeCoords(clientX, clientY, fromParentViewport);
7563
9644
  const textEl = Array.from(
7564
9645
  document.querySelectorAll(NON_MEDIA_SELECTOR)
7565
9646
  ).find((el) => {
7566
9647
  const er = el.getBoundingClientRect();
7567
- return x >= er.left && x <= er.right && y >= er.top && y <= er.bottom;
9648
+ return x2 >= er.left && x2 <= er.right && y2 >= er.top && y2 <= er.bottom;
7568
9649
  });
7569
9650
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
7570
9651
  if (textEl && !textEl.hasAttribute("contenteditable")) {
@@ -7588,6 +9669,14 @@ function OhhwellsBridge() {
7588
9669
  }
7589
9670
  };
7590
9671
  const probeHoverCardsAt = (clientX, clientY, fromParentViewport = false) => {
9672
+ if (linkPopoverOpenRef.current || document.documentElement.hasAttribute("data-ohw-section-picking")) {
9673
+ if (activeStateElRef.current) {
9674
+ activeStateElRef.current.removeAttribute("data-ohw-state-hovered");
9675
+ activeStateElRef.current = null;
9676
+ setToggleState(null);
9677
+ }
9678
+ return;
9679
+ }
7591
9680
  const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
7592
9681
  const toggleEl = document.querySelector("[data-ohw-state-toggle]");
7593
9682
  if (toggleEl) {
@@ -7660,6 +9749,14 @@ function OhhwellsBridge() {
7660
9749
  probeHoverCardsAt(clientX, clientY);
7661
9750
  };
7662
9751
  const handleDragOver = (e) => {
9752
+ const footerSession = footerDragRef.current;
9753
+ if (footerSession) {
9754
+ e.preventDefault();
9755
+ if (e.dataTransfer) e.dataTransfer.dropEffect = "move";
9756
+ const slot = footerSession.kind === "link" && footerSession.hrefKey ? hitTestLinkDropSlot(e.clientX, e.clientY, footerSession.hrefKey) : hitTestColumnDropSlot(e.clientX, e.clientY);
9757
+ refreshFooterDragVisualsRef.current(footerSession, slot, e.clientX, e.clientY);
9758
+ return;
9759
+ }
7663
9760
  e.preventDefault();
7664
9761
  const el = findImageAtPoint(e.clientX, e.clientY, false);
7665
9762
  if (!el) {
@@ -7728,6 +9825,7 @@ function OhhwellsBridge() {
7728
9825
  if (!entry || entry.fadingOut) return prev;
7729
9826
  return { ...prev, [key]: { ...entry, fadingOut: true } };
7730
9827
  });
9828
+ if (pendingUploadRectRef.current?.key === key) pendingUploadRectRef.current = null;
7731
9829
  };
7732
9830
  let found = false;
7733
9831
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -7842,6 +9940,7 @@ function OhhwellsBridge() {
7842
9940
  continue;
7843
9941
  }
7844
9942
  if (applyVideoSettingNode(key, val)) continue;
9943
+ if (applyCarouselNode(key, val)) continue;
7845
9944
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
7846
9945
  if (el.dataset.ohwEditable === "image") {
7847
9946
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -7864,6 +9963,9 @@ function OhhwellsBridge() {
7864
9963
  sectionsLoadedRef.current = true;
7865
9964
  pendingScheduleConfigRequests.current.splice(0).forEach(processConfigRequest);
7866
9965
  }
9966
+ editContentRef.current = { ...editContentRef.current, ...content };
9967
+ reconcileNavbarItemsFromContent(editContentRef.current);
9968
+ reconcileFooterOrderFromContent(editContentRef.current);
7867
9969
  enforceLinkHrefs();
7868
9970
  postToParentRef.current({ type: "ow:hydrate-done" });
7869
9971
  };
@@ -7871,6 +9973,7 @@ function OhhwellsBridge() {
7871
9973
  const handleDeactivate = (e) => {
7872
9974
  if (e.data?.type !== "ow:deactivate") return;
7873
9975
  if (Date.now() < linkPopoverGraceUntilRef.current) return;
9976
+ if (document.querySelector("[data-ohw-section-picker]")) return;
7874
9977
  if (linkPopoverOpenRef.current) {
7875
9978
  setLinkPopoverRef.current(null);
7876
9979
  return;
@@ -7879,13 +9982,40 @@ function OhhwellsBridge() {
7879
9982
  deactivateRef.current();
7880
9983
  };
7881
9984
  window.addEventListener("message", handleDeactivate);
9985
+ const handleUiEscape = (e) => {
9986
+ if (e.data?.type !== "ui:escape") return;
9987
+ if (linkPopoverOpenRef.current) {
9988
+ setLinkPopoverRef.current(null);
9989
+ }
9990
+ };
9991
+ window.addEventListener("message", handleUiEscape);
7882
9992
  const handleKeyDown = (e) => {
9993
+ if (e.key === "Escape" && document.querySelector("[data-ohw-section-picker]")) return;
9994
+ if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "a" && activeElRef.current) {
9995
+ const navAnchor = getNavigationItemAnchor(activeElRef.current);
9996
+ if (navAnchor) {
9997
+ e.preventDefault();
9998
+ selectAllTextInEditable(activeElRef.current);
9999
+ refreshActiveCommandsRef.current();
10000
+ return;
10001
+ }
10002
+ }
7883
10003
  if (e.key === "Escape" && linkPopoverOpenRef.current) {
7884
10004
  setLinkPopoverRef.current(null);
7885
10005
  return;
7886
10006
  }
7887
10007
  if (e.key === "Escape" && selectedElRef.current && !activeElRef.current) {
7888
- deselectRef.current();
10008
+ if (toolbarVariantRef.current === "select-frame") {
10009
+ deselectRef.current();
10010
+ return;
10011
+ }
10012
+ const parent = getNavigationSelectionParent(selectedElRef.current);
10013
+ if (parent) {
10014
+ e.preventDefault();
10015
+ selectFrameRef.current(parent);
10016
+ } else {
10017
+ deselectRef.current();
10018
+ }
7889
10019
  return;
7890
10020
  }
7891
10021
  if (e.key === "Escape" && activeElRef.current) {
@@ -7943,9 +10073,23 @@ function OhhwellsBridge() {
7943
10073
  if (hoveredItemElRef.current) {
7944
10074
  setHoveredItemRect(hoveredItemElRef.current.getBoundingClientRect());
7945
10075
  }
10076
+ if (hoveredNavContainerRef.current) {
10077
+ setHoveredNavContainerRect(hoveredNavContainerRef.current.getBoundingClientRect());
10078
+ }
7946
10079
  if (siblingHintElRef.current) {
7947
10080
  setSiblingHintRect(siblingHintElRef.current.getBoundingClientRect());
7948
10081
  }
10082
+ const selected = selectedElRef.current;
10083
+ if (selected && !footerDragRef.current && (selected.hasAttribute("data-ohw-footer-col") || Boolean(selected.closest("footer") && isInferredFooterGroup(selected)))) {
10084
+ setSiblingHintRects(
10085
+ listFooterColumns().filter((column) => column !== selected).map((column) => column.getBoundingClientRect())
10086
+ );
10087
+ }
10088
+ if (footerDragRef.current) {
10089
+ const session = footerDragRef.current;
10090
+ const slot = session.kind === "link" && session.hrefKey ? hitTestLinkDropSlot(session.lastClientX, session.lastClientY, session.hrefKey) : hitTestColumnDropSlot(session.lastClientX, session.lastClientY);
10091
+ refreshFooterDragVisualsRef.current(session, slot, session.lastClientX, session.lastClientY);
10092
+ }
7949
10093
  if (hoveredImageRef.current) {
7950
10094
  const el = hoveredImageRef.current;
7951
10095
  const r2 = el.getBoundingClientRect();
@@ -7956,7 +10100,7 @@ function OhhwellsBridge() {
7956
10100
  };
7957
10101
  const handleSave = (e) => {
7958
10102
  if (e.data?.type !== "ow:save") return;
7959
- const nodes = collectEditableNodes();
10103
+ const nodes = collectEditableNodes(editContentRef.current);
7960
10104
  const tracker = document.querySelector("[data-ohw-sections-tracker]");
7961
10105
  if (tracker?.textContent) nodes.push({ key: "__ohw_sections", type: "sections", text: tracker.textContent });
7962
10106
  postToParentRef.current({ type: "ow:save-result", nodes });
@@ -8041,28 +10185,49 @@ function OhhwellsBridge() {
8041
10185
  const handleDocMouseLeave = () => {
8042
10186
  hoveredImageRef.current = null;
8043
10187
  setMediaHover(null);
10188
+ setCarouselHover(null);
8044
10189
  document.querySelectorAll("[data-ohw-state-hovered]").forEach((el) => el.removeAttribute("data-ohw-state-hovered"));
8045
10190
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
8046
10191
  activeStateElRef.current = null;
8047
10192
  setToggleState(null);
8048
10193
  };
10194
+ const resolveUploadRects = (key) => {
10195
+ const matches = Array.from(document.querySelectorAll(`[data-ohw-key="${key}"]`));
10196
+ const rects = matches.map((el) => {
10197
+ const r2 = el.getBoundingClientRect();
10198
+ return { top: r2.top, left: r2.left, width: r2.width, height: r2.height };
10199
+ }).filter((r2) => r2.width > 1 && r2.height > 1);
10200
+ if (rects.length > 0) return rects;
10201
+ const pending = pendingUploadRectRef.current;
10202
+ if (pending?.key === key && pending.rect.width > 1 && pending.rect.height > 1) {
10203
+ return [pending.rect];
10204
+ }
10205
+ const hovered = hoveredImageRef.current;
10206
+ if (hovered?.dataset.ohwKey === key) {
10207
+ const r2 = hovered.getBoundingClientRect();
10208
+ if (r2.width > 1 && r2.height > 1) {
10209
+ return [{ top: r2.top, left: r2.left, width: r2.width, height: r2.height }];
10210
+ }
10211
+ }
10212
+ return [];
10213
+ };
8049
10214
  const handleImageUploading = (e) => {
8050
- if (e.data?.type !== "ow:image-uploading") return;
8051
- const { key, uploading } = e.data;
10215
+ const type = e.data?.type;
10216
+ if (type !== "ow:image-uploading" && type !== "ow:anim-lock") return;
10217
+ const key = e.data.key;
10218
+ if (!key) return;
10219
+ const uploading = type === "ow:anim-lock" ? true : !!e.data.uploading;
8052
10220
  setUploadingRects((prev) => {
8053
10221
  if (!uploading) {
10222
+ if (pendingUploadRectRef.current?.key === key) pendingUploadRectRef.current = null;
8054
10223
  if (!(key in prev)) return prev;
8055
10224
  const next = { ...prev };
8056
10225
  delete next[key];
8057
10226
  return next;
8058
10227
  }
8059
- const el = document.querySelector(`[data-ohw-key="${key}"]`);
8060
- if (!el) return prev;
8061
- const r2 = getVisibleRect(el);
8062
- return {
8063
- ...prev,
8064
- [key]: { rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height } }
8065
- };
10228
+ const rects = resolveUploadRects(key);
10229
+ if (rects.length === 0) return prev;
10230
+ return { ...prev, [key]: { rects } };
8066
10231
  });
8067
10232
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
8068
10233
  const track = el.closest("[data-ohw-hover-pause]");
@@ -8080,6 +10245,48 @@ function OhhwellsBridge() {
8080
10245
  if (e.data?.type !== "ow:canvas-height" || typeof e.data.height !== "number") return;
8081
10246
  document.documentElement.style.setProperty("--ohw-canvas-h", `${e.data.height}px`);
8082
10247
  };
10248
+ const handleCarouselChange = (e) => {
10249
+ if (e.data?.type !== "ow:carousel-change") return;
10250
+ const { key, images } = e.data;
10251
+ if (!key || !Array.isArray(images)) return;
10252
+ const text = JSON.stringify(images);
10253
+ applyCarouselValue(key, images);
10254
+ editContentRef.current = { ...editContentRef.current, [key]: text };
10255
+ postToParentRef.current({ type: "ow:change", nodes: [{ key, text }] });
10256
+ setCarouselHover(null);
10257
+ };
10258
+ const findCarouselAtPoint = (clientX, clientY) => {
10259
+ const containers = Array.from(document.querySelectorAll(`[${CAROUSEL_ATTR}]`));
10260
+ for (let i = containers.length - 1; i >= 0; i--) {
10261
+ const r2 = containers[i].getBoundingClientRect();
10262
+ if (clientX >= r2.left && clientX <= r2.right && clientY >= r2.top && clientY <= r2.bottom) {
10263
+ return containers[i];
10264
+ }
10265
+ }
10266
+ return null;
10267
+ };
10268
+ const isPointOverEditable = (scope, clientX, clientY) => {
10269
+ const editables = scope.querySelectorAll("[data-ohw-editable], [data-ohw-href-key]");
10270
+ for (const el of editables) {
10271
+ const r2 = el.getBoundingClientRect();
10272
+ if (clientX >= r2.left && clientX <= r2.right && clientY >= r2.top && clientY <= r2.bottom) return true;
10273
+ }
10274
+ return false;
10275
+ };
10276
+ const handleCarouselHover = (e) => {
10277
+ const container = findCarouselAtPoint(e.clientX, e.clientY);
10278
+ const scope = container?.closest("[data-ohw-section]") ?? container?.parentElement ?? null;
10279
+ if (!container || !scope || isPointOverEditable(scope, e.clientX, e.clientY)) {
10280
+ setCarouselHover((prev) => prev ? null : prev);
10281
+ return;
10282
+ }
10283
+ const key = container.getAttribute("data-ohw-key") ?? "";
10284
+ if (!key) return;
10285
+ const r2 = getVisibleRect(container);
10286
+ setCarouselHover(
10287
+ (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 } }
10288
+ );
10289
+ };
8083
10290
  const applyToolbarPos = (rect) => {
8084
10291
  const ps = parentScrollRef.current;
8085
10292
  const approxW = 330;
@@ -8139,9 +10346,45 @@ function OhhwellsBridge() {
8139
10346
  return clientX >= r2.left && clientX <= r2.right && clientY >= r2.top && clientY <= r2.bottom;
8140
10347
  });
8141
10348
  if (textEditable) {
8142
- activateRef.current(textEditable);
10349
+ const hrefCtx = getHrefKeyFromElement(textEditable);
10350
+ const navAnchor = hrefCtx ? getNavigationItemAnchor(hrefCtx.anchor) : null;
10351
+ if (navAnchor) {
10352
+ if (selectedElRef.current === navAnchor) {
10353
+ activateRef.current(textEditable, { caretX: clientX, caretY: clientY });
10354
+ } else {
10355
+ selectRef.current(navAnchor);
10356
+ }
10357
+ return;
10358
+ }
10359
+ activateRef.current(textEditable, { caretX: clientX, caretY: clientY });
8143
10360
  return;
8144
10361
  }
10362
+ const navContainer = document.querySelector("[data-ohw-nav-container]");
10363
+ if (navContainer) {
10364
+ const r2 = navContainer.getBoundingClientRect();
10365
+ const pad = 8;
10366
+ const over = clientX >= r2.left - pad && clientX <= r2.right + pad && clientY >= r2.top - pad && clientY <= r2.bottom + pad;
10367
+ if (over && !isPointOverNavItem(navContainer, clientX, clientY)) {
10368
+ selectFrameRef.current(navContainer);
10369
+ return;
10370
+ }
10371
+ }
10372
+ const navRoot = document.querySelector("[data-ohw-nav-root]");
10373
+ if (navRoot && navContainer) {
10374
+ const rr = navRoot.getBoundingClientRect();
10375
+ if (clientX >= rr.left && clientX <= rr.right && clientY >= rr.top && clientY <= rr.bottom && !isPointOverNavItem(navContainer, clientX, clientY)) {
10376
+ const book = navRoot.querySelector('[data-ohw-role="navbar-button"]');
10377
+ if (book) {
10378
+ const br = book.getBoundingClientRect();
10379
+ if (clientX >= br.left && clientX <= br.right && clientY >= br.top && clientY <= br.bottom) {
10380
+ deactivateRef.current();
10381
+ return;
10382
+ }
10383
+ }
10384
+ selectFrameRef.current(navContainer);
10385
+ return;
10386
+ }
10387
+ }
8145
10388
  deactivateRef.current();
8146
10389
  };
8147
10390
  window.addEventListener("message", handleSave);
@@ -8152,6 +10395,7 @@ function OhhwellsBridge() {
8152
10395
  window.addEventListener("message", handleRemoveSchedulingSection);
8153
10396
  window.addEventListener("message", handleImageUrl);
8154
10397
  window.addEventListener("message", handleImageUploading);
10398
+ window.addEventListener("message", handleCarouselChange);
8155
10399
  window.addEventListener("message", handleCanvasHeight);
8156
10400
  window.addEventListener("message", handleParentScroll);
8157
10401
  window.addEventListener("message", handlePointerSync);
@@ -8163,11 +10407,13 @@ function OhhwellsBridge() {
8163
10407
  };
8164
10408
  window.addEventListener("resize", handleViewportResize, { passive: true });
8165
10409
  document.addEventListener("click", handleClick, true);
10410
+ document.addEventListener("dblclick", handleDblClick, true);
8166
10411
  document.addEventListener("paste", handlePaste, true);
8167
10412
  document.addEventListener("input", handleInput, true);
8168
10413
  document.addEventListener("mouseover", handleMouseOver, true);
8169
10414
  document.addEventListener("mouseout", handleMouseOut, true);
8170
10415
  document.addEventListener("mousemove", handleMouseMove, true);
10416
+ document.addEventListener("mousemove", handleCarouselHover, true);
8171
10417
  document.addEventListener("mouseleave", handleDocMouseLeave);
8172
10418
  document.addEventListener("dragover", handleDragOver, true);
8173
10419
  document.addEventListener("dragleave", handleDragLeave, true);
@@ -8177,11 +10423,13 @@ function OhhwellsBridge() {
8177
10423
  window.addEventListener("scroll", handleScroll, true);
8178
10424
  return () => {
8179
10425
  document.removeEventListener("click", handleClick, true);
10426
+ document.removeEventListener("dblclick", handleDblClick, true);
8180
10427
  document.removeEventListener("paste", handlePaste, true);
8181
10428
  document.removeEventListener("input", handleInput, true);
8182
10429
  document.removeEventListener("mouseover", handleMouseOver, true);
8183
10430
  document.removeEventListener("mouseout", handleMouseOut, true);
8184
10431
  document.removeEventListener("mousemove", handleMouseMove, true);
10432
+ document.removeEventListener("mousemove", handleCarouselHover, true);
8185
10433
  document.removeEventListener("mouseleave", handleDocMouseLeave);
8186
10434
  document.removeEventListener("dragover", handleDragOver, true);
8187
10435
  document.removeEventListener("dragleave", handleDragLeave, true);
@@ -8197,6 +10445,7 @@ function OhhwellsBridge() {
8197
10445
  window.removeEventListener("message", handleRemoveSchedulingSection);
8198
10446
  window.removeEventListener("message", handleImageUrl);
8199
10447
  window.removeEventListener("message", handleImageUploading);
10448
+ window.removeEventListener("message", handleCarouselChange);
8200
10449
  window.removeEventListener("message", handleCanvasHeight);
8201
10450
  window.removeEventListener("message", handleParentScroll);
8202
10451
  window.removeEventListener("resize", handleViewportResize);
@@ -8204,13 +10453,165 @@ function OhhwellsBridge() {
8204
10453
  window.removeEventListener("message", handleClickAt);
8205
10454
  window.removeEventListener("message", handleHydrate);
8206
10455
  window.removeEventListener("message", handleDeactivate);
10456
+ window.removeEventListener("message", handleUiEscape);
8207
10457
  autoSaveTimers.current.forEach(clearTimeout);
8208
10458
  autoSaveTimers.current.clear();
8209
10459
  if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
8210
10460
  if (imageShowTimerRef.current) clearTimeout(imageShowTimerRef.current);
8211
10461
  };
8212
10462
  }, [isEditMode, refreshStateRules]);
8213
- useEffect4(() => {
10463
+ useEffect8(() => {
10464
+ if (!isEditMode) return;
10465
+ const THRESHOLD = 10;
10466
+ const resolveWasSelected = (el) => {
10467
+ if (selectedElRef.current === el) return true;
10468
+ const activeAnchor = activeElRef.current ? getNavigationItemAnchor(activeElRef.current) : null;
10469
+ return activeAnchor === el;
10470
+ };
10471
+ const onPointerDown = (e) => {
10472
+ if (e.button !== 0) return;
10473
+ if (suppressNextClickRef.current && !footerDragRef.current && !footerPointerDragRef.current?.started) {
10474
+ suppressNextClickRef.current = false;
10475
+ }
10476
+ if (footerDragRef.current) return;
10477
+ const target = e.target;
10478
+ if (!target) return;
10479
+ 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]')) {
10480
+ return;
10481
+ }
10482
+ if (target.closest("[data-ohw-item-drag-surface]")) return;
10483
+ const anchor = getNavigationItemAnchor(target);
10484
+ const hrefKey = anchor?.getAttribute("data-ohw-href-key") ?? null;
10485
+ if (anchor && isFooterHrefKey(hrefKey)) {
10486
+ armFooterPressDrag();
10487
+ footerPointerDragRef.current = {
10488
+ el: anchor,
10489
+ kind: "link",
10490
+ startX: e.clientX,
10491
+ startY: e.clientY,
10492
+ pointerId: e.pointerId,
10493
+ wasSelected: resolveWasSelected(anchor),
10494
+ started: false
10495
+ };
10496
+ return;
10497
+ }
10498
+ const col = target.closest("[data-ohw-footer-col]");
10499
+ if (col && !getNavigationItemAnchor(target)) {
10500
+ armFooterPressDrag();
10501
+ footerPointerDragRef.current = {
10502
+ el: col,
10503
+ kind: "column",
10504
+ startX: e.clientX,
10505
+ startY: e.clientY,
10506
+ pointerId: e.pointerId,
10507
+ wasSelected: resolveWasSelected(col),
10508
+ started: false
10509
+ };
10510
+ }
10511
+ };
10512
+ const onPointerMove = (e) => {
10513
+ const pending = footerPointerDragRef.current;
10514
+ if (!pending) return;
10515
+ if (pending.started) {
10516
+ e.preventDefault();
10517
+ clearTextSelection();
10518
+ const session = footerDragRef.current;
10519
+ if (!session) return;
10520
+ const slot = session.kind === "link" && session.hrefKey ? hitTestLinkDropSlot(e.clientX, e.clientY, session.hrefKey) : hitTestColumnDropSlot(e.clientX, e.clientY);
10521
+ refreshFooterDragVisualsRef.current(session, slot, e.clientX, e.clientY);
10522
+ return;
10523
+ }
10524
+ const dx = e.clientX - pending.startX;
10525
+ const dy = e.clientY - pending.startY;
10526
+ if (dx * dx + dy * dy < THRESHOLD * THRESHOLD) return;
10527
+ e.preventDefault();
10528
+ pending.started = true;
10529
+ clearTextSelection();
10530
+ try {
10531
+ document.body.setPointerCapture(pending.pointerId);
10532
+ } catch {
10533
+ }
10534
+ if (linkPopoverOpenRef.current) {
10535
+ setLinkPopoverRef.current(null);
10536
+ }
10537
+ if (activeElRef.current) {
10538
+ deactivateRef.current();
10539
+ }
10540
+ if (pending.kind === "link") {
10541
+ const key = pending.el.getAttribute("data-ohw-href-key");
10542
+ if (!key) return;
10543
+ const column = findFooterColumnForLink(pending.el);
10544
+ const columns2 = listFooterColumns();
10545
+ beginFooterDragRef.current({
10546
+ kind: "link",
10547
+ hrefKey: key,
10548
+ columnEl: column,
10549
+ sourceColumnIndex: column ? columns2.indexOf(column) : 0,
10550
+ wasSelected: pending.wasSelected,
10551
+ draggedEl: pending.el,
10552
+ lastClientX: e.clientX,
10553
+ lastClientY: e.clientY,
10554
+ activeSlot: null
10555
+ });
10556
+ return;
10557
+ }
10558
+ const columns = listFooterColumns();
10559
+ const idx = columns.indexOf(pending.el);
10560
+ if (idx < 0) return;
10561
+ beginFooterDragRef.current({
10562
+ kind: "column",
10563
+ hrefKey: null,
10564
+ columnEl: pending.el,
10565
+ sourceColumnIndex: idx,
10566
+ wasSelected: pending.wasSelected,
10567
+ draggedEl: pending.el,
10568
+ lastClientX: e.clientX,
10569
+ lastClientY: e.clientY,
10570
+ activeSlot: null
10571
+ });
10572
+ };
10573
+ const endPointerDrag = (e) => {
10574
+ const pending = footerPointerDragRef.current;
10575
+ footerPointerDragRef.current = null;
10576
+ try {
10577
+ if (document.body.hasPointerCapture(e.pointerId)) {
10578
+ document.body.releasePointerCapture(e.pointerId);
10579
+ }
10580
+ } catch {
10581
+ }
10582
+ if (!pending?.started) {
10583
+ unlockFooterDragInteraction();
10584
+ return;
10585
+ }
10586
+ suppressNextClickRef.current = true;
10587
+ commitFooterDragRef.current(e.clientX, e.clientY);
10588
+ };
10589
+ const blockSelectStart = (e) => {
10590
+ if (footerDragRef.current || footerPointerDragRef.current?.started || document.documentElement.hasAttribute("data-ohw-footer-press-drag")) {
10591
+ e.preventDefault();
10592
+ }
10593
+ };
10594
+ const onDragEnd = (_e) => {
10595
+ if (!footerDragRef.current) return;
10596
+ commitFooterDragRef.current();
10597
+ };
10598
+ document.addEventListener("pointerdown", onPointerDown, true);
10599
+ document.addEventListener("pointermove", onPointerMove, true);
10600
+ document.addEventListener("pointerup", endPointerDrag, true);
10601
+ document.addEventListener("pointercancel", endPointerDrag, true);
10602
+ document.addEventListener("selectstart", blockSelectStart, true);
10603
+ document.addEventListener("dragend", onDragEnd, true);
10604
+ return () => {
10605
+ document.removeEventListener("pointerdown", onPointerDown, true);
10606
+ document.removeEventListener("pointermove", onPointerMove, true);
10607
+ document.removeEventListener("pointerup", endPointerDrag, true);
10608
+ document.removeEventListener("pointercancel", endPointerDrag, true);
10609
+ document.removeEventListener("selectstart", blockSelectStart, true);
10610
+ document.removeEventListener("dragend", onDragEnd, true);
10611
+ unlockFooterDragInteraction();
10612
+ };
10613
+ }, [isEditMode]);
10614
+ useEffect8(() => {
8214
10615
  const handler = (e) => {
8215
10616
  if (e.data?.type !== "ow:request-schedule-config") return;
8216
10617
  const insertAfterVal = e.data.insertAfter;
@@ -8226,7 +10627,7 @@ function OhhwellsBridge() {
8226
10627
  window.addEventListener("message", handler);
8227
10628
  return () => window.removeEventListener("message", handler);
8228
10629
  }, [processConfigRequest]);
8229
- useEffect4(() => {
10630
+ useEffect8(() => {
8230
10631
  if (!isEditMode) return;
8231
10632
  document.querySelectorAll("[data-ohw-active-state]").forEach((el) => {
8232
10633
  el.removeAttribute("data-ohw-active-state");
@@ -8247,27 +10648,33 @@ function OhhwellsBridge() {
8247
10648
  const next = { ...prev, [pathKey]: sections };
8248
10649
  return shouldUseDevFixtures() ? { ...DEV_SECTIONS_BY_PATH, ...next } : next;
8249
10650
  });
8250
- postToParent({ type: "ow:ready", version: "1", path: pathname, nodes: collectEditableNodes(), sections });
8251
- postToParent({ type: "ow:request-site-pages" });
10651
+ postToParent2({
10652
+ type: "ow:ready",
10653
+ version: "1",
10654
+ path: pathname,
10655
+ nodes: collectEditableNodes(editContentRef.current),
10656
+ sections
10657
+ });
10658
+ postToParent2({ type: "ow:request-site-pages" });
8252
10659
  }, 150);
8253
10660
  return () => {
8254
10661
  cancelAnimationFrame(raf);
8255
10662
  clearTimeout(timer);
8256
10663
  };
8257
- }, [pathname, isEditMode, refreshStateRules, postToParent]);
8258
- useEffect4(() => {
10664
+ }, [pathname, isEditMode, refreshStateRules, postToParent2]);
10665
+ useEffect8(() => {
8259
10666
  scrollToHashSectionWhenReady();
8260
10667
  const onHashChange = () => scrollToHashSectionWhenReady();
8261
10668
  window.addEventListener("hashchange", onHashChange);
8262
10669
  return () => window.removeEventListener("hashchange", onHashChange);
8263
10670
  }, [pathname]);
8264
- const handleCommand = useCallback2((cmd) => {
10671
+ const handleCommand = useCallback4((cmd) => {
8265
10672
  document.execCommand(cmd, false);
8266
10673
  activeElRef.current?.focus();
8267
10674
  if (activeElRef.current) setToolbarRect(getEditMeasureEl(activeElRef.current).getBoundingClientRect());
8268
10675
  refreshActiveCommandsRef.current();
8269
10676
  }, []);
8270
- const handleStateChange = useCallback2((state) => {
10677
+ const handleStateChange = useCallback4((state) => {
8271
10678
  if (!activeStateElRef.current) return;
8272
10679
  const el = activeStateElRef.current;
8273
10680
  if (state === "Default") {
@@ -8280,18 +10687,22 @@ function OhhwellsBridge() {
8280
10687
  }
8281
10688
  setToggleState((prev) => prev ? { ...prev, activeState: state } : null);
8282
10689
  }, [deactivate]);
8283
- const closeLinkPopover = useCallback2(() => setLinkPopover(null), []);
8284
- const openLinkPopoverForActive = useCallback2(() => {
10690
+ const closeLinkPopover = useCallback4(() => {
10691
+ addNavAfterAnchorRef.current = null;
10692
+ setLinkPopover(null);
10693
+ }, []);
10694
+ const openLinkPopoverForActive = useCallback4(() => {
8285
10695
  const hrefCtx = getHrefKeyFromElement(activeElRef.current);
8286
10696
  if (!hrefCtx) return;
8287
10697
  bumpLinkPopoverGrace();
8288
10698
  setLinkPopover({
8289
10699
  key: hrefCtx.key,
8290
- target: getLinkHref(hrefCtx.anchor)
10700
+ mode: "edit",
10701
+ target: getLinkHref2(hrefCtx.anchor)
8291
10702
  });
8292
10703
  deactivate();
8293
10704
  }, [deactivate]);
8294
- const openLinkPopoverForSelected = useCallback2(() => {
10705
+ const openLinkPopoverForSelected = useCallback4(() => {
8295
10706
  const anchor = selectedElRef.current;
8296
10707
  if (!anchor) return;
8297
10708
  const key = anchor.getAttribute("data-ohw-href-key");
@@ -8299,30 +10710,83 @@ function OhhwellsBridge() {
8299
10710
  bumpLinkPopoverGrace();
8300
10711
  setLinkPopover({
8301
10712
  key,
8302
- target: getLinkHref(anchor)
10713
+ mode: "edit",
10714
+ target: getLinkHref2(anchor)
8303
10715
  });
8304
10716
  deselect();
8305
10717
  }, [deselect]);
8306
- const handleLinkPopoverSubmit = useCallback2(
10718
+ const handleLinkPopoverSubmit = useCallback4(
8307
10719
  (target) => {
8308
- if (!linkPopover) return;
8309
- const { key } = linkPopover;
10720
+ const session = linkPopoverSessionRef.current;
10721
+ if (!session) return;
10722
+ if (session.intent === "add-nav") {
10723
+ const trimmed = target.trim();
10724
+ let href = trimmed;
10725
+ let label = "Untitled";
10726
+ if (trimmed) {
10727
+ const { pageRoute, sectionId } = parseTarget(trimmed);
10728
+ const page = resolvePage(pageRoute, sitePages);
10729
+ const pageSections = getSectionsForPath(sectionsByPath, pageRoute);
10730
+ const section = sectionId ? pageSections.find((s) => s.id === sectionId) : void 0;
10731
+ label = section?.label ?? page.title;
10732
+ href = trimmed;
10733
+ }
10734
+ const afterAnchor = addNavAfterAnchorRef.current;
10735
+ const { anchor, hrefKey, labelKey, index, order } = insertNavbarItem(href, label, afterAnchor);
10736
+ applyLinkByKey(hrefKey, href);
10737
+ document.querySelectorAll(`[data-ohw-key="${labelKey}"]`).forEach((el) => {
10738
+ el.textContent = label;
10739
+ });
10740
+ const navCount = String(index + 1);
10741
+ const orderJson = JSON.stringify(order);
10742
+ editContentRef.current = {
10743
+ ...editContentRef.current,
10744
+ [hrefKey]: href,
10745
+ [labelKey]: label,
10746
+ [NAV_COUNT_KEY]: navCount,
10747
+ [NAV_ORDER_KEY]: orderJson
10748
+ };
10749
+ postToParent2({
10750
+ type: "ow:change",
10751
+ nodes: [
10752
+ { key: hrefKey, text: href },
10753
+ { key: labelKey, text: label },
10754
+ { key: NAV_COUNT_KEY, text: navCount },
10755
+ { key: NAV_ORDER_KEY, text: orderJson }
10756
+ ]
10757
+ });
10758
+ postToParent2({ type: "ow:toast", title: "Link added", toastType: "success" });
10759
+ addNavAfterAnchorRef.current = null;
10760
+ setLinkPopover(null);
10761
+ enforceLinkHrefs();
10762
+ requestAnimationFrame(() => {
10763
+ selectRef.current(anchor);
10764
+ });
10765
+ return;
10766
+ }
10767
+ const { key } = session;
8310
10768
  applyLinkByKey(key, target);
8311
- postToParent({ type: "ow:change", nodes: [{ key, text: target }] });
10769
+ postToParent2({ type: "ow:change", nodes: [{ key, text: target }] });
8312
10770
  setLinkPopover(null);
8313
10771
  },
8314
- [linkPopover, postToParent]
10772
+ [postToParent2, sitePages, sectionsByPath]
8315
10773
  );
8316
- const showEditLink = toolbarShowEditLink;
8317
- const currentSections = sectionsByPath[pathname] ?? [];
8318
- linkPopoverOpenRef.current = linkPopover !== null;
8319
- const handleMediaReplace = useCallback2(
10774
+ const handleMediaReplace = useCallback4(
10775
+ (key) => {
10776
+ if (mediaHover?.key === key) {
10777
+ pendingUploadRectRef.current = { key, rect: mediaHover.rect };
10778
+ }
10779
+ postToParent2({ type: "ow:image-pick", key, elementType: mediaHover?.elementType ?? "image" });
10780
+ },
10781
+ [postToParent2, mediaHover]
10782
+ );
10783
+ const handleEditCarousel = useCallback4(
8320
10784
  (key) => {
8321
- postToParent({ type: "ow:image-pick", key, elementType: mediaHover?.elementType ?? "image" });
10785
+ postToParent2({ type: "ow:carousel-open", key, images: readCarouselValue(key) });
8322
10786
  },
8323
- [postToParent, mediaHover?.elementType]
10787
+ [postToParent2]
8324
10788
  );
8325
- const handleMediaFadeOutComplete = useCallback2((key) => {
10789
+ const handleMediaFadeOutComplete = useCallback4((key) => {
8326
10790
  setUploadingRects((prev) => {
8327
10791
  if (!(key in prev)) return prev;
8328
10792
  const next = { ...prev };
@@ -8330,7 +10794,7 @@ function OhhwellsBridge() {
8330
10794
  return next;
8331
10795
  });
8332
10796
  }, []);
8333
- const handleVideoSettingsChange = useCallback2(
10797
+ const handleVideoSettingsChange = useCallback4(
8334
10798
  (key, settings) => {
8335
10799
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
8336
10800
  const video = getVideoEl(el);
@@ -8338,7 +10802,7 @@ function OhhwellsBridge() {
8338
10802
  if (typeof settings.autoplay === "boolean") video.autoplay = settings.autoplay;
8339
10803
  if (typeof settings.muted === "boolean") video.muted = settings.muted;
8340
10804
  syncVideoPlayback(video);
8341
- postToParent({
10805
+ postToParent2({
8342
10806
  type: "ow:change",
8343
10807
  nodes: [
8344
10808
  { key: `${key}${VIDEO_AUTOPLAY_SUFFIX}`, text: String(video.autoplay) },
@@ -8350,23 +10814,28 @@ function OhhwellsBridge() {
8350
10814
  );
8351
10815
  });
8352
10816
  },
8353
- [postToParent]
10817
+ [postToParent2]
8354
10818
  );
8355
- return bridgeRoot ? createPortal(
8356
- /* @__PURE__ */ jsxs12(Fragment4, { children: [
8357
- /* @__PURE__ */ jsx22("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
8358
- Object.entries(uploadingRects).map(([key, { rect, fadingOut }]) => /* @__PURE__ */ jsx22(
8359
- MediaOverlay,
8360
- {
8361
- hover: { key, rect, elementType: "image", isDragOver: false, hasTextOverlap: false },
8362
- isUploading: true,
8363
- fadingOut,
8364
- onFadeOutComplete: handleMediaFadeOutComplete,
8365
- onReplace: handleMediaReplace
8366
- },
8367
- `uploading-${key}`
8368
- )),
8369
- mediaHover && !(mediaHover.key in uploadingRects) && /* @__PURE__ */ jsx22(
10819
+ const showEditLink = toolbarShowEditLink;
10820
+ const currentSections = sectionsByPath[pathname] ?? [];
10821
+ linkPopoverOpenRef.current = linkPopover !== null;
10822
+ return bridgeRoot ? createPortal2(
10823
+ /* @__PURE__ */ jsxs14(Fragment5, { children: [
10824
+ /* @__PURE__ */ jsx25("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
10825
+ Object.entries(uploadingRects).flatMap(
10826
+ ([key, { rects, fadingOut }]) => rects.map((rect, i) => /* @__PURE__ */ jsx25(
10827
+ MediaOverlay,
10828
+ {
10829
+ hover: { key, rect, elementType: "image", isDragOver: false, hasTextOverlap: false },
10830
+ isUploading: true,
10831
+ fadingOut,
10832
+ onFadeOutComplete: handleMediaFadeOutComplete,
10833
+ onReplace: handleMediaReplace
10834
+ },
10835
+ `uploading-${key}-${i}`
10836
+ ))
10837
+ ),
10838
+ mediaHover && !(mediaHover.key in uploadingRects) && /* @__PURE__ */ jsx25(
8370
10839
  MediaOverlay,
8371
10840
  {
8372
10841
  hover: mediaHover,
@@ -8375,53 +10844,33 @@ function OhhwellsBridge() {
8375
10844
  onVideoSettingsChange: handleVideoSettingsChange
8376
10845
  }
8377
10846
  ),
8378
- siblingHintRect && !isItemDragging && /* @__PURE__ */ jsx22(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
8379
- hoveredItemRect && !siblingHintRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ jsx22(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
8380
- toolbarRect && toolbarVariant === "link-action" && /* @__PURE__ */ jsx22(
8381
- ItemInteractionLayer,
10847
+ carouselHover && /* @__PURE__ */ jsx25(CarouselOverlay, { hover: carouselHover, onEdit: handleEditCarousel }),
10848
+ siblingHintRect && !isItemDragging && siblingHintRects.length === 0 && /* @__PURE__ */ jsx25(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
10849
+ siblingHintRects.map((rect, i) => /* @__PURE__ */ jsx25(ItemInteractionLayer, { rect, state: "sibling-hint" }, `sibling-hint-${i}`)),
10850
+ isItemDragging && draggedItemRect && selectedElRef.current !== footerDragRef.current?.draggedEl && /* @__PURE__ */ jsx25(ItemInteractionLayer, { rect: draggedItemRect, state: "dragging" }),
10851
+ isItemDragging && footerDropSlots.map((slot, i) => /* @__PURE__ */ jsx25(
10852
+ "div",
8382
10853
  {
8383
- rect: toolbarRect,
8384
- elRef: glowElRef,
8385
- state: isItemDragging ? "dragging" : resolveItemInteractionState(toolbarRect, parentScrollRef.current),
8386
- showHandle: Boolean(reorderHrefKey),
8387
- dragDisabled: reorderDragDisabled,
8388
- dragHandleLabel: reorderHrefKey ? `Reorder ${reorderHrefKey}` : "Reorder item",
8389
- onDragHandleDragStart: handleItemDragStart,
8390
- onDragHandleDragEnd: handleItemDragEnd,
8391
- toolbar: isItemDragging ? void 0 : /* @__PURE__ */ jsx22(
8392
- ItemActionToolbar,
8393
- {
8394
- onEditLink: openLinkPopoverForSelected,
8395
- onAddItem: handleAddNavigationItem,
8396
- addItemDisabled: false
8397
- }
8398
- )
8399
- }
8400
- ),
8401
- toolbarRect && toolbarVariant === "rich-text" && /* @__PURE__ */ jsxs12(Fragment4, { children: [
8402
- /* @__PURE__ */ jsx22(
8403
- EditGlowChrome,
8404
- {
8405
- rect: toolbarRect,
8406
- elRef: glowElRef,
8407
- reorderHrefKey,
8408
- dragDisabled: reorderDragDisabled
8409
- }
8410
- ),
8411
- /* @__PURE__ */ jsx22(
8412
- FloatingToolbar,
8413
- {
8414
- rect: toolbarRect,
8415
- parentScroll: parentScrollRef.current,
8416
- elRef: toolbarElRef,
8417
- onCommand: handleCommand,
8418
- activeCommands,
8419
- showEditLink,
8420
- onEditLink: openLinkPopoverForActive
8421
- }
8422
- )
10854
+ className: "pointer-events-none fixed z-2147483646",
10855
+ style: {
10856
+ left: slot.left,
10857
+ top: slot.top,
10858
+ width: slot.width,
10859
+ height: slot.height
10860
+ },
10861
+ children: /* @__PURE__ */ jsx25(DropIndicator, { direction: slot.direction, state: activeFooterDropIndex === i ? "dragActive" : "dragIdle", className: slot.direction === "horizontal" ? "!h-full !w-full" : "!h-full !w-full" })
10862
+ },
10863
+ `footer-drop-${slot.direction}-${slot.columnIndex}-${slot.insertIndex}-${i}`
10864
+ )),
10865
+ hoveredNavContainerRect && toolbarVariant !== "select-frame" && !linkPopover && !isItemDragging && !siblingHintRect && siblingHintRects.length === 0 && /* @__PURE__ */ jsx25(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
10866
+ toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && /* @__PURE__ */ jsx25(NavbarContainerChrome, { rect: toolbarRect, onAdd: handleAddTopLevelNavItem }),
10867
+ hoveredItemRect && !linkPopover && !hoveredNavContainerRect && !siblingHintRect && siblingHintRects.length === 0 && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ jsx25(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
10868
+ toolbarRect && (toolbarVariant === "link-action" || toolbarVariant === "select-frame") && /* @__PURE__ */ jsx25(ItemInteractionLayer, { rect: isItemDragging && draggedItemRect && footerDragRef.current?.wasSelected ? draggedItemRect : toolbarRect, elRef: glowElRef, state: isItemDragging ? "dragging" : resolveItemInteractionState(toolbarRect, parentScrollRef.current), showHandle: toolbarVariant === "link-action" && Boolean(reorderHrefKey) || toolbarVariant === "select-frame" && isFooterFrameSelection, dragDisabled: reorderDragDisabled, dragHandleLabel: reorderHrefKey ? `Reorder ${reorderHrefKey}` : "Reorder item", onDragHandleDragStart: handleItemDragStart, onDragHandleDragEnd: handleItemDragEnd, onItemPointerDown: handleItemChromePointerDown, onItemClick: handleItemChromeClick, toolbar: toolbarVariant === "link-action" && !isItemDragging ? /* @__PURE__ */ jsx25(ItemActionToolbar, { onEditLink: openLinkPopoverForSelected, addItemDisabled: true, editLinkDisabled: false, moreDisabled: true }) : void 0 }),
10869
+ toolbarRect && toolbarVariant === "rich-text" && /* @__PURE__ */ jsxs14(Fragment5, { children: [
10870
+ /* @__PURE__ */ jsx25(EditGlowChrome, { rect: toolbarRect, elRef: glowElRef, reorderHrefKey, dragDisabled: reorderDragDisabled }),
10871
+ /* @__PURE__ */ jsx25(FloatingToolbar, { rect: toolbarRect, parentScroll: parentScrollRef.current, elRef: toolbarElRef, onCommand: handleCommand, activeCommands, showEditLink, onEditLink: openLinkPopoverForActive })
8423
10872
  ] }),
8424
- maxBadge && /* @__PURE__ */ jsxs12(
10873
+ maxBadge && /* @__PURE__ */ jsxs14(
8425
10874
  "div",
8426
10875
  {
8427
10876
  "data-ohw-max-badge": "",
@@ -8447,56 +10896,29 @@ function OhhwellsBridge() {
8447
10896
  ]
8448
10897
  }
8449
10898
  ),
8450
- toggleState && /* @__PURE__ */ jsx22(
8451
- StateToggle,
8452
- {
8453
- rect: toggleState.rect,
8454
- activeState: toggleState.activeState,
8455
- states: toggleState.states,
8456
- onStateChange: handleStateChange
8457
- }
8458
- ),
8459
- sectionGap && /* @__PURE__ */ jsxs12(
8460
- "div",
8461
- {
8462
- "data-ohw-section-insert-line": "",
8463
- className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
8464
- style: { top: sectionGap.y, transform: "translateY(-50%)" },
8465
- children: [
8466
- /* @__PURE__ */ jsx22("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
8467
- /* @__PURE__ */ jsx22(
8468
- Badge,
8469
- {
8470
- 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",
8471
- onClick: () => {
8472
- window.parent.postMessage(
8473
- { type: "ow:add-section", insertAfter: sectionGap.insertAfter, insertBefore: sectionGap.insertBefore },
8474
- "*"
8475
- );
10899
+ toggleState && !linkPopover && /* @__PURE__ */ jsx25(StateToggle, { rect: toggleState.rect, activeState: toggleState.activeState, states: toggleState.states, onStateChange: handleStateChange }),
10900
+ sectionGap && !linkPopover && /* @__PURE__ */ jsxs14("div", { "data-ohw-section-insert-line": "", className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none", style: { top: sectionGap.y, transform: "translateY(-50%)" }, children: [
10901
+ /* @__PURE__ */ jsx25("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
10902
+ /* @__PURE__ */ jsx25(
10903
+ Badge,
10904
+ {
10905
+ 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",
10906
+ onClick: () => {
10907
+ window.parent.postMessage(
10908
+ {
10909
+ type: "ow:add-section",
10910
+ insertAfter: sectionGap.insertAfter,
10911
+ insertBefore: sectionGap.insertBefore
8476
10912
  },
8477
- children: "Add Section"
8478
- }
8479
- ),
8480
- /* @__PURE__ */ jsx22("div", { className: "flex-1 bg-primary", style: { height: 3 } })
8481
- ]
8482
- }
8483
- ),
8484
- linkPopover && dialogPortalContainer ? /* @__PURE__ */ jsx22(
8485
- LinkPopover,
8486
- {
8487
- panelRef: linkPopoverPanelRef,
8488
- portalContainer: dialogPortalContainer,
8489
- open: true,
8490
- mode: "edit",
8491
- pages: sitePages,
8492
- sections: currentSections,
8493
- sectionsByPath,
8494
- initialTarget: linkPopover.target,
8495
- onClose: closeLinkPopover,
8496
- onSubmit: handleLinkPopoverSubmit
8497
- },
8498
- `${linkPopover.key}-${pathname}`
8499
- ) : null
10913
+ "*"
10914
+ );
10915
+ },
10916
+ children: "Add Section"
10917
+ }
10918
+ ),
10919
+ /* @__PURE__ */ jsx25("div", { className: "flex-1 bg-primary", style: { height: 3 } })
10920
+ ] }),
10921
+ linkPopover && dialogPortalContainer ? /* @__PURE__ */ jsx25(LinkPopover, { panelRef: linkPopoverPanelRef, portalContainer: dialogPortalContainer, open: true, mode: linkPopover.mode ?? "edit", pages: sitePages, sections: currentSections, sectionsByPath, initialTarget: linkPopover.target, existingTargets: linkPopover.intent === "add-nav" ? getNavbarExistingTargets() : [], onClose: closeLinkPopover, onSubmit: handleLinkPopoverSubmit }, linkPopover.key) : null
8500
10922
  ] }),
8501
10923
  bridgeRoot
8502
10924
  ) : null;
@@ -8506,6 +10928,7 @@ export {
8506
10928
  CustomToolbarButton,
8507
10929
  CustomToolbarDivider,
8508
10930
  DragHandle,
10931
+ DropIndicator,
8509
10932
  ItemActionToolbar,
8510
10933
  ItemInteractionLayer,
8511
10934
  LinkEditorPanel,
@@ -8520,12 +10943,14 @@ export {
8520
10943
  TooltipProvider,
8521
10944
  TooltipTrigger,
8522
10945
  buildTarget,
10946
+ dropIndicatorVariants,
8523
10947
  filterAvailablePages,
8524
10948
  getEditModeInitialState,
8525
10949
  isEditSessionActive,
8526
10950
  isValidUrl,
8527
10951
  parseTarget,
8528
10952
  toggleVariants,
10953
+ useOhwCarousel,
8529
10954
  validateUrlInput
8530
10955
  };
8531
10956
  //# sourceMappingURL=index.js.map