@ohhwells/bridge 0.1.38 → 0.1.39-next.72

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,100 @@ 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 hoveredGapRef = useRef5(null);
8099
+ const imageUnhoverTimerRef = useRef5(null);
8100
+ const imageShowTimerRef = useRef5(null);
8101
+ const editStylesRef = useRef5(null);
8102
+ const activateRef = useRef5(() => {
8103
+ });
8104
+ const deactivateRef = useRef5(() => {
6628
8105
  });
6629
- const deactivateRef = useRef4(() => {
8106
+ const selectRef = useRef5(() => {
6630
8107
  });
6631
- const selectRef = useRef4(() => {
8108
+ const selectFrameRef = useRef5(() => {
6632
8109
  });
6633
- const deselectRef = useRef4(() => {
8110
+ const deselectRef = useRef5(() => {
6634
8111
  });
6635
- const reselectNavigationItemRef = useRef4(() => {
8112
+ const reselectNavigationItemRef = useRef5(() => {
6636
8113
  });
6637
- const commitNavigationTextEditRef = useRef4(() => {
8114
+ const commitNavigationTextEditRef = useRef5(() => {
6638
8115
  });
6639
- const refreshActiveCommandsRef = useRef4(() => {
8116
+ const refreshActiveCommandsRef = useRef5(() => {
6640
8117
  });
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");
8118
+ const postToParentRef = useRef5(postToParent2);
8119
+ postToParentRef.current = postToParent2;
8120
+ const sectionsLoadedRef = useRef5(false);
8121
+ const pendingScheduleConfigRequests = useRef5([]);
8122
+ const [toolbarRect, setToolbarRect] = useState7(null);
8123
+ const [toolbarVariant, setToolbarVariant] = useState7("none");
8124
+ const toolbarVariantRef = useRef5("none");
6648
8125
  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);
8126
+ const [reorderHrefKey, setReorderHrefKey] = useState7(null);
8127
+ const [reorderDragDisabled, setReorderDragDisabled] = useState7(false);
8128
+ const [toggleState, setToggleState] = useState7(null);
8129
+ const [maxBadge, setMaxBadge] = useState7(null);
8130
+ const [activeCommands, setActiveCommands] = useState7(/* @__PURE__ */ new Set());
8131
+ const [sectionGap, setSectionGap] = useState7(null);
8132
+ const [toolbarShowEditLink, setToolbarShowEditLink] = useState7(false);
8133
+ const hoveredNavContainerRef = useRef5(null);
8134
+ const [hoveredNavContainerRect, setHoveredNavContainerRect] = useState7(null);
8135
+ const hoveredItemElRef = useRef5(null);
8136
+ const [hoveredItemRect, setHoveredItemRect] = useState7(null);
8137
+ const siblingHintElRef = useRef5(null);
8138
+ const [siblingHintRect, setSiblingHintRect] = useState7(null);
8139
+ const [siblingHintRects, setSiblingHintRects] = useState7([]);
8140
+ const [isItemDragging, setIsItemDragging] = useState7(false);
8141
+ const [isFooterFrameSelection, setIsFooterFrameSelection] = useState7(false);
8142
+ const footerDragRef = useRef5(null);
8143
+ const [footerDropSlots, setFooterDropSlots] = useState7([]);
8144
+ const [activeFooterDropIndex, setActiveFooterDropIndex] = useState7(null);
8145
+ const [draggedItemRect, setDraggedItemRect] = useState7(null);
8146
+ const footerPointerDragRef = useRef5(null);
8147
+ const suppressNextClickRef = useRef5(false);
8148
+ const [linkPopover, setLinkPopover] = useState7(null);
8149
+ const linkPopoverSessionRef = useRef5(null);
8150
+ const addNavAfterAnchorRef = useRef5(null);
8151
+ const editContentRef = useRef5({});
8152
+ const [sitePages, setSitePages] = useState7([]);
8153
+ const [sectionsByPath, setSectionsByPath] = useState7({});
8154
+ const sectionsPrefetchGenRef = useRef5(0);
8155
+ const setLinkPopoverRef = useRef5(setLinkPopover);
8156
+ const linkPopoverPanelRef = useRef5(null);
8157
+ const linkPopoverOpenRef = useRef5(false);
8158
+ const linkPopoverGraceUntilRef = useRef5(0);
6669
8159
  setLinkPopoverRef.current = setLinkPopover;
8160
+ linkPopoverSessionRef.current = linkPopover;
6670
8161
  const bumpLinkPopoverGrace = () => {
6671
8162
  linkPopoverGraceUntilRef.current = Date.now() + 350;
6672
8163
  };
6673
- const runSectionsPrefetch = useCallback2((pages) => {
8164
+ const runSectionsPrefetch = useCallback4((pages) => {
6674
8165
  if (!isEditMode || shouldUseDevFixtures() || pages.length === 0) return;
6675
8166
  const gen = ++sectionsPrefetchGenRef.current;
6676
8167
  const paths = pages.map((p) => p.path);
@@ -6689,43 +8180,33 @@ function OhhwellsBridge() {
6689
8180
  );
6690
8181
  });
6691
8182
  }, [isEditMode, pathname]);
6692
- const runSectionsPrefetchRef = useRef4(runSectionsPrefetch);
8183
+ const runSectionsPrefetchRef = useRef5(runSectionsPrefetch);
6693
8184
  runSectionsPrefetchRef.current = runSectionsPrefetch;
6694
- useEffect4(() => {
6695
- if (!linkPopover) return;
8185
+ useEffect8(() => {
8186
+ if (!linkPopover) {
8187
+ document.documentElement.removeAttribute("data-ohw-link-popover-open");
8188
+ return;
8189
+ }
8190
+ document.documentElement.setAttribute("data-ohw-link-popover-open", "");
6696
8191
  if (hoveredImageRef.current) {
6697
8192
  hoveredImageRef.current = null;
6698
8193
  hoveredImageHasTextOverlapRef.current = false;
6699
8194
  }
6700
8195
  hoveredGapRef.current = null;
6701
8196
  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);
8197
+ hoveredItemElRef.current = null;
8198
+ setHoveredItemRect(null);
8199
+ hoveredNavContainerRef.current = null;
8200
+ setHoveredNavContainerRect(null);
8201
+ document.querySelectorAll("[data-ohw-hovered]").forEach((el) => {
8202
+ el.removeAttribute("data-ohw-hovered");
8203
+ });
8204
+ postToParent2({ type: "ow:image-unhover" });
6720
8205
  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);
8206
+ document.documentElement.removeAttribute("data-ohw-link-popover-open");
6726
8207
  };
6727
- }, [linkPopover, postToParent]);
6728
- useEffect4(() => {
8208
+ }, [linkPopover, postToParent2]);
8209
+ useEffect8(() => {
6729
8210
  if (!isEditMode) return;
6730
8211
  const useFixtures = shouldUseDevFixtures();
6731
8212
  if (useFixtures) {
@@ -6746,17 +8227,17 @@ function OhhwellsBridge() {
6746
8227
  runSectionsPrefetchRef.current(mapped);
6747
8228
  };
6748
8229
  window.addEventListener("message", onSitePages);
6749
- if (!useFixtures) postToParent({ type: "ow:request-site-pages" });
8230
+ if (!useFixtures) postToParent2({ type: "ow:request-site-pages" });
6750
8231
  return () => window.removeEventListener("message", onSitePages);
6751
- }, [isEditMode, postToParent]);
6752
- useEffect4(() => {
8232
+ }, [isEditMode, postToParent2]);
8233
+ useEffect8(() => {
6753
8234
  if (!isEditMode || shouldUseDevFixtures()) return;
6754
8235
  void loadAllSectionsManifest().then((manifest) => {
6755
8236
  if (Object.keys(manifest).length === 0) return;
6756
8237
  setSectionsByPath((prev) => ({ ...manifest, ...prev }));
6757
8238
  });
6758
8239
  }, [isEditMode]);
6759
- useEffect4(() => {
8240
+ useEffect8(() => {
6760
8241
  const update = () => {
6761
8242
  const el = activeElRef.current ?? selectedElRef.current;
6762
8243
  if (el) setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
@@ -6780,10 +8261,10 @@ function OhhwellsBridge() {
6780
8261
  vvp.removeEventListener("resize", update);
6781
8262
  };
6782
8263
  }, []);
6783
- const refreshStateRules = useCallback2(() => {
8264
+ const refreshStateRules = useCallback4(() => {
6784
8265
  editStylesRef.current?.forceHover && (editStylesRef.current.forceHover.textContent = collectStateRules());
6785
8266
  }, []);
6786
- const processConfigRequest = useCallback2((insertAfterVal) => {
8267
+ const processConfigRequest = useCallback4((insertAfterVal) => {
6787
8268
  const tracker = getSectionsTracker();
6788
8269
  let entries = [];
6789
8270
  try {
@@ -6806,7 +8287,7 @@ function OhhwellsBridge() {
6806
8287
  }
6807
8288
  window.postMessage({ type: "ow:schedule-config", insertAfter: insertAfterVal, scheduleId: null }, "*");
6808
8289
  }, [isEditMode]);
6809
- const deactivate = useCallback2(() => {
8290
+ const deactivate = useCallback4(() => {
6810
8291
  const el = activeElRef.current;
6811
8292
  if (!el) return;
6812
8293
  const key = el.dataset.ohwKey;
@@ -6835,21 +8316,25 @@ function OhhwellsBridge() {
6835
8316
  setMaxBadge(null);
6836
8317
  setActiveCommands(/* @__PURE__ */ new Set());
6837
8318
  setToolbarShowEditLink(false);
6838
- postToParent({ type: "ow:exit-edit" });
6839
- }, [postToParent]);
6840
- const deselect = useCallback2(() => {
8319
+ postToParent2({ type: "ow:exit-edit" });
8320
+ }, [postToParent2]);
8321
+ const deselect = useCallback4(() => {
6841
8322
  selectedElRef.current = null;
6842
8323
  setReorderHrefKey(null);
6843
8324
  setReorderDragDisabled(false);
8325
+ setIsFooterFrameSelection(false);
6844
8326
  siblingHintElRef.current = null;
6845
8327
  setSiblingHintRect(null);
8328
+ setSiblingHintRects([]);
6846
8329
  setIsItemDragging(false);
8330
+ hoveredNavContainerRef.current = null;
8331
+ setHoveredNavContainerRect(null);
6847
8332
  if (!activeElRef.current) {
6848
8333
  setToolbarRect(null);
6849
8334
  setToolbarVariant("none");
6850
8335
  }
6851
8336
  }, []);
6852
- const reselectNavigationItem = useCallback2((navAnchor) => {
8337
+ const reselectNavigationItem = useCallback4((navAnchor) => {
6853
8338
  selectedElRef.current = navAnchor;
6854
8339
  const { key, disabled } = getNavigationItemReorderState(navAnchor);
6855
8340
  setReorderHrefKey(key);
@@ -6859,7 +8344,7 @@ function OhhwellsBridge() {
6859
8344
  setToolbarShowEditLink(false);
6860
8345
  setActiveCommands(/* @__PURE__ */ new Set());
6861
8346
  }, []);
6862
- const commitNavigationTextEdit = useCallback2((navAnchor) => {
8347
+ const commitNavigationTextEdit = useCallback4((navAnchor) => {
6863
8348
  const el = activeElRef.current;
6864
8349
  if (!el) return;
6865
8350
  const key = el.dataset.ohwKey;
@@ -6872,9 +8357,9 @@ function OhhwellsBridge() {
6872
8357
  const html = sanitizeHtml(el.innerHTML);
6873
8358
  const original = originalContentRef.current ?? "";
6874
8359
  if (html !== sanitizeHtml(original)) {
6875
- postToParent({ type: "ow:change", nodes: [{ key, text: html }] });
8360
+ postToParent2({ type: "ow:change", nodes: [{ key, text: html }] });
6876
8361
  const h = document.documentElement.scrollHeight;
6877
- if (h > 50) postToParent({ type: "ow:height", height: h });
8362
+ if (h > 50) postToParent2({ type: "ow:height", height: h });
6878
8363
  }
6879
8364
  }
6880
8365
  el.removeAttribute("contenteditable");
@@ -6882,79 +8367,406 @@ function OhhwellsBridge() {
6882
8367
  setMaxBadge(null);
6883
8368
  setActiveCommands(/* @__PURE__ */ new Set());
6884
8369
  setToolbarShowEditLink(false);
6885
- postToParent({ type: "ow:exit-edit" });
8370
+ postToParent2({ type: "ow:exit-edit" });
6886
8371
  reselectNavigationItem(navAnchor);
6887
- }, [postToParent, reselectNavigationItem]);
6888
- const handleAddNavigationItem = useCallback2(() => {
8372
+ }, [postToParent2, reselectNavigationItem]);
8373
+ const handleAddTopLevelNavItem = useCallback4(() => {
8374
+ const items = listNavbarItems();
8375
+ addNavAfterAnchorRef.current = items[items.length - 1] ?? null;
8376
+ deselectRef.current();
8377
+ deactivateRef.current();
8378
+ bumpLinkPopoverGrace();
8379
+ setLinkPopover({
8380
+ key: "__add-nav__",
8381
+ mode: "create",
8382
+ intent: "add-nav"
8383
+ });
8384
+ }, []);
8385
+ const clearFooterDragVisuals = useCallback4(() => {
8386
+ footerDragRef.current = null;
8387
+ setSiblingHintRects([]);
8388
+ setFooterDropSlots([]);
8389
+ setActiveFooterDropIndex(null);
8390
+ setDraggedItemRect(null);
8391
+ setIsItemDragging(false);
8392
+ unlockFooterDragInteraction();
8393
+ }, []);
8394
+ const refreshFooterDragVisuals = useCallback4((session, activeSlot, clientX, clientY) => {
8395
+ const dragged = session.draggedEl;
8396
+ setDraggedItemRect(dragged.getBoundingClientRect());
8397
+ if (typeof clientX === "number" && typeof clientY === "number") {
8398
+ session.lastClientX = clientX;
8399
+ session.lastClientY = clientY;
8400
+ }
8401
+ session.activeSlot = activeSlot;
8402
+ if (session.kind === "link") {
8403
+ const columns2 = listFooterColumns();
8404
+ const focusCols = /* @__PURE__ */ new Set([session.sourceColumnIndex]);
8405
+ if (activeSlot) focusCols.add(activeSlot.columnIndex);
8406
+ const siblingRects = [];
8407
+ columns2.forEach((col, i) => {
8408
+ if (!focusCols.has(i)) return;
8409
+ for (const link of listFooterLinksInColumn(col)) {
8410
+ if (link === dragged) continue;
8411
+ siblingRects.push(link.getBoundingClientRect());
8412
+ }
8413
+ });
8414
+ setSiblingHintRects(siblingRects);
8415
+ const slots2 = [];
8416
+ columns2.forEach((col, i) => {
8417
+ slots2.push(...buildLinkDropSlots(col, i));
8418
+ });
8419
+ setFooterDropSlots(slots2);
8420
+ const activeIdx2 = activeSlot ? slots2.findIndex((s) => s.columnIndex === activeSlot.columnIndex && s.insertIndex === activeSlot.insertIndex) : -1;
8421
+ setActiveFooterDropIndex(activeIdx2 >= 0 ? activeIdx2 : null);
8422
+ return;
8423
+ }
8424
+ const columns = listFooterColumns();
8425
+ setSiblingHintRects(columns.filter((col) => col !== dragged).map((col) => col.getBoundingClientRect()));
8426
+ const slots = buildColumnDropSlots();
8427
+ setFooterDropSlots(slots);
8428
+ const activeIdx = activeSlot ? slots.findIndex((s) => s.insertIndex === activeSlot.insertIndex) : -1;
8429
+ setActiveFooterDropIndex(activeIdx >= 0 ? activeIdx : null);
8430
+ }, []);
8431
+ const refreshFooterDragVisualsRef = useRef5(refreshFooterDragVisuals);
8432
+ refreshFooterDragVisualsRef.current = refreshFooterDragVisuals;
8433
+ const commitFooterDragRef = useRef5(() => {
8434
+ });
8435
+ const beginFooterDragRef = useRef5(() => {
8436
+ });
8437
+ const beginFooterDrag = useCallback4(
8438
+ (session) => {
8439
+ const rect = session.draggedEl.getBoundingClientRect();
8440
+ session.lastClientX = session.lastClientX || rect.left + rect.width / 2;
8441
+ session.lastClientY = session.lastClientY || rect.top + rect.height / 2;
8442
+ session.activeSlot = session.activeSlot ?? null;
8443
+ footerDragRef.current = session;
8444
+ setIsItemDragging(true);
8445
+ lockFooterDuringDrag();
8446
+ siblingHintElRef.current = null;
8447
+ setSiblingHintRect(null);
8448
+ if (session.wasSelected && selectedElRef.current === session.draggedEl) {
8449
+ setToolbarRect(rect);
8450
+ }
8451
+ const initialSlot = session.kind === "link" && session.hrefKey ? hitTestLinkDropSlot(session.lastClientX, session.lastClientY, session.hrefKey) : hitTestColumnDropSlot(session.lastClientX, session.lastClientY);
8452
+ refreshFooterDragVisuals(session, initialSlot, session.lastClientX, session.lastClientY);
8453
+ },
8454
+ [refreshFooterDragVisuals]
8455
+ );
8456
+ beginFooterDragRef.current = beginFooterDrag;
8457
+ const commitFooterDrag = useCallback4(
8458
+ (clientX, clientY) => {
8459
+ const session = footerDragRef.current;
8460
+ if (!session) {
8461
+ clearFooterDragVisuals();
8462
+ return;
8463
+ }
8464
+ const x = typeof clientX === "number" && (clientX !== 0 || clientY !== 0) ? clientX : session.lastClientX;
8465
+ const y = typeof clientY === "number" && (clientX !== 0 || clientY !== 0) ? clientY : session.lastClientY;
8466
+ let nextOrder = null;
8467
+ const slot = session.activeSlot ?? (session.kind === "link" && session.hrefKey ? hitTestLinkDropSlot(x, y, session.hrefKey) : session.kind === "column" ? hitTestColumnDropSlot(x, y) : null);
8468
+ if (session.kind === "link" && session.hrefKey && slot) {
8469
+ nextOrder = planFooterLinkMove(session.hrefKey, slot.columnIndex, slot.insertIndex);
8470
+ } else if (session.kind === "column" && slot) {
8471
+ nextOrder = planFooterColumnMove(session.sourceColumnIndex, slot.insertIndex);
8472
+ }
8473
+ const wasSelected = session.wasSelected;
8474
+ const draggedEl = session.draggedEl;
8475
+ const hrefKey = session.hrefKey;
8476
+ let movedColumnIndex = null;
8477
+ if (session.kind === "column" && slot && nextOrder) {
8478
+ let adjusted = slot.insertIndex;
8479
+ if (session.sourceColumnIndex < slot.insertIndex) adjusted -= 1;
8480
+ movedColumnIndex = Math.max(0, Math.min(adjusted, nextOrder.length - 1));
8481
+ }
8482
+ clearFooterDragVisuals();
8483
+ const applySelectionAfterDrop = () => {
8484
+ if (!wasSelected) {
8485
+ deselectRef.current();
8486
+ return;
8487
+ }
8488
+ if (hrefKey) {
8489
+ const link = document.querySelector(
8490
+ `footer [data-ohw-href-key="${CSS.escape(hrefKey)}"]`
8491
+ );
8492
+ if (link && isNavigationItem(link)) {
8493
+ selectRef.current(link);
8494
+ return;
8495
+ }
8496
+ }
8497
+ if (movedColumnIndex !== null && nextOrder) {
8498
+ const colKeys = nextOrder[movedColumnIndex] ?? [];
8499
+ let column = null;
8500
+ for (const key of colKeys) {
8501
+ const link = document.querySelector(
8502
+ `footer [data-ohw-href-key="${CSS.escape(key)}"]`
8503
+ );
8504
+ if (link) {
8505
+ column = findFooterColumnForLink(link);
8506
+ if (column) break;
8507
+ }
8508
+ }
8509
+ if (!column) column = listFooterColumns()[movedColumnIndex] ?? null;
8510
+ if (column && isNavigationContainer(column)) {
8511
+ selectFrameRef.current(column);
8512
+ return;
8513
+ }
8514
+ }
8515
+ if (document.body.contains(draggedEl)) {
8516
+ if (isNavigationItem(draggedEl)) {
8517
+ selectRef.current(draggedEl);
8518
+ return;
8519
+ }
8520
+ if (isNavigationContainer(draggedEl)) {
8521
+ selectFrameRef.current(draggedEl);
8522
+ return;
8523
+ }
8524
+ }
8525
+ deselectRef.current();
8526
+ };
8527
+ if (!wasSelected) {
8528
+ deselectRef.current();
8529
+ }
8530
+ if (nextOrder) {
8531
+ const orderJson = JSON.stringify(nextOrder);
8532
+ editContentRef.current = {
8533
+ ...editContentRef.current,
8534
+ [FOOTER_ORDER_KEY]: orderJson
8535
+ };
8536
+ applyFooterOrder(nextOrder);
8537
+ document.querySelectorAll("footer [data-ohw-href-key]").forEach(disableNativeHrefDrag);
8538
+ postToParentRef.current({
8539
+ type: "ow:change",
8540
+ nodes: [{ key: FOOTER_ORDER_KEY, text: orderJson }]
8541
+ });
8542
+ requestAnimationFrame(() => {
8543
+ if (editContentRef.current[FOOTER_ORDER_KEY] === orderJson) {
8544
+ applyFooterOrder(nextOrder);
8545
+ document.querySelectorAll("footer [data-ohw-href-key]").forEach(disableNativeHrefDrag);
8546
+ }
8547
+ requestAnimationFrame(applySelectionAfterDrop);
8548
+ });
8549
+ return;
8550
+ }
8551
+ applySelectionAfterDrop();
8552
+ },
8553
+ [clearFooterDragVisuals]
8554
+ );
8555
+ commitFooterDragRef.current = commitFooterDrag;
8556
+ const startFooterLinkDrag = useCallback4(
8557
+ (anchor, clientX, clientY, wasSelected) => {
8558
+ const hrefKey = anchor.getAttribute("data-ohw-href-key");
8559
+ if (!hrefKey || !isFooterHrefKey(hrefKey)) return false;
8560
+ const column = findFooterColumnForLink(anchor);
8561
+ const columns = listFooterColumns();
8562
+ beginFooterDrag({
8563
+ kind: "link",
8564
+ hrefKey,
8565
+ columnEl: column,
8566
+ sourceColumnIndex: column ? columns.indexOf(column) : 0,
8567
+ wasSelected,
8568
+ draggedEl: anchor,
8569
+ lastClientX: clientX,
8570
+ lastClientY: clientY,
8571
+ activeSlot: null
8572
+ });
8573
+ return true;
8574
+ },
8575
+ [beginFooterDrag]
8576
+ );
8577
+ const startFooterColumnDrag = useCallback4(
8578
+ (columnEl, clientX, clientY, wasSelected) => {
8579
+ const columns = listFooterColumns();
8580
+ const idx = columns.indexOf(columnEl);
8581
+ if (idx < 0) return false;
8582
+ beginFooterDrag({
8583
+ kind: "column",
8584
+ hrefKey: null,
8585
+ columnEl,
8586
+ sourceColumnIndex: idx,
8587
+ wasSelected,
8588
+ draggedEl: columnEl,
8589
+ lastClientX: clientX,
8590
+ lastClientY: clientY,
8591
+ activeSlot: null
8592
+ });
8593
+ return true;
8594
+ },
8595
+ [beginFooterDrag]
8596
+ );
8597
+ const handleItemDragStart = useCallback4(
8598
+ (e) => {
8599
+ const selected = selectedElRef.current;
8600
+ if (!selected) {
8601
+ setIsItemDragging(true);
8602
+ return;
8603
+ }
8604
+ const clientX = e?.clientX ?? selected.getBoundingClientRect().left + 8;
8605
+ const clientY = e?.clientY ?? selected.getBoundingClientRect().top + 8;
8606
+ if (startFooterLinkDrag(selected, clientX, clientY, true)) return;
8607
+ if (selected.hasAttribute("data-ohw-footer-col") || isInferredFooterGroup(selected) && selected.closest("footer")) {
8608
+ if (startFooterColumnDrag(selected, clientX, clientY, true)) return;
8609
+ }
8610
+ siblingHintElRef.current = null;
8611
+ setSiblingHintRect(null);
8612
+ setIsItemDragging(true);
8613
+ },
8614
+ [startFooterColumnDrag, startFooterLinkDrag]
8615
+ );
8616
+ const handleItemDragEnd = useCallback4(
8617
+ (e) => {
8618
+ if (footerDragRef.current) {
8619
+ const x = e?.clientX;
8620
+ const y = e?.clientY;
8621
+ if (typeof x === "number" && typeof y === "number" && (x !== 0 || y !== 0)) {
8622
+ commitFooterDrag(x, y);
8623
+ } else {
8624
+ commitFooterDrag();
8625
+ }
8626
+ return;
8627
+ }
8628
+ setIsItemDragging(false);
8629
+ },
8630
+ [commitFooterDrag]
8631
+ );
8632
+ const handleItemChromePointerDown = useCallback4((e) => {
8633
+ if (e.button !== 0) return;
6889
8634
  const selected = selectedElRef.current;
6890
8635
  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);
8636
+ armFooterPressDrag();
8637
+ const hrefKey = selected.getAttribute("data-ohw-href-key");
8638
+ if (hrefKey && isFooterHrefKey(hrefKey)) {
8639
+ footerPointerDragRef.current = {
8640
+ el: selected,
8641
+ kind: "link",
8642
+ startX: e.clientX,
8643
+ startY: e.clientY,
8644
+ pointerId: e.pointerId,
8645
+ wasSelected: true,
8646
+ started: false
8647
+ };
8648
+ return;
8649
+ }
8650
+ if (selected.hasAttribute("data-ohw-footer-col") || isInferredFooterGroup(selected) && selected.closest("footer")) {
8651
+ footerPointerDragRef.current = {
8652
+ el: selected,
8653
+ kind: "column",
8654
+ startX: e.clientX,
8655
+ startY: e.clientY,
8656
+ pointerId: e.pointerId,
8657
+ wasSelected: true,
8658
+ started: false
8659
+ };
8660
+ }
6899
8661
  }, []);
6900
- const handleItemDragEnd = useCallback2(() => {
6901
- setIsItemDragging(false);
8662
+ const handleItemChromeClick = useCallback4((clientX, clientY) => {
8663
+ if (suppressNextClickRef.current) {
8664
+ suppressNextClickRef.current = false;
8665
+ return;
8666
+ }
8667
+ const selected = selectedElRef.current;
8668
+ if (!selected || !isNavigationItem(selected)) return;
8669
+ const editable = selected.querySelector('[data-ohw-editable="text"]') ?? selected.querySelector("[data-ohw-editable]");
8670
+ if (!editable) return;
8671
+ activateRef.current(editable, { caretX: clientX, caretY: clientY });
6902
8672
  }, []);
6903
8673
  reselectNavigationItemRef.current = reselectNavigationItem;
6904
8674
  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) {
8675
+ const select = useCallback4(
8676
+ (anchor) => {
8677
+ if (!isNavigationItem(anchor)) return;
8678
+ if (activeElRef.current) deactivate();
8679
+ selectedElRef.current = anchor;
8680
+ clearHrefKeyHover(anchor);
8681
+ hoveredNavContainerRef.current = null;
8682
+ setHoveredNavContainerRect(null);
8683
+ setHoveredItemRect(null);
8684
+ hoveredItemElRef.current = null;
8685
+ siblingHintElRef.current = null;
8686
+ setSiblingHintRect(null);
8687
+ setSiblingHintRects([]);
8688
+ setIsItemDragging(false);
8689
+ const { key, disabled } = getNavigationItemReorderState(anchor);
8690
+ setReorderHrefKey(key);
8691
+ setReorderDragDisabled(disabled);
8692
+ setIsFooterFrameSelection(false);
8693
+ setToolbarVariant("link-action");
8694
+ setToolbarRect(anchor.getBoundingClientRect());
8695
+ setToolbarShowEditLink(false);
8696
+ setActiveCommands(/* @__PURE__ */ new Set());
8697
+ },
8698
+ [deactivate]
8699
+ );
8700
+ const selectFrame = useCallback4(
8701
+ (el) => {
8702
+ if (!isNavigationContainer(el)) return;
8703
+ if (activeElRef.current) deactivate();
8704
+ const isFooterColumn = el.hasAttribute("data-ohw-footer-col") || Boolean(el.closest("footer") && isInferredFooterGroup(el));
8705
+ selectedElRef.current = el;
8706
+ clearHrefKeyHover(el);
8707
+ hoveredNavContainerRef.current = null;
8708
+ setHoveredNavContainerRect(null);
8709
+ setHoveredItemRect(null);
8710
+ hoveredItemElRef.current = null;
8711
+ siblingHintElRef.current = null;
8712
+ setSiblingHintRect(null);
8713
+ setSiblingHintRects(
8714
+ isFooterColumn ? listFooterColumns().filter((column) => column !== el).map((column) => column.getBoundingClientRect()) : []
8715
+ );
8716
+ setIsItemDragging(false);
6944
8717
  setReorderHrefKey(null);
6945
8718
  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]);
8719
+ setIsFooterFrameSelection(isFooterColumn);
8720
+ setToolbarVariant("select-frame");
8721
+ setToolbarRect(el.getBoundingClientRect());
8722
+ setToolbarShowEditLink(false);
8723
+ setActiveCommands(/* @__PURE__ */ new Set());
8724
+ },
8725
+ [deactivate]
8726
+ );
8727
+ const activate = useCallback4(
8728
+ (el, options) => {
8729
+ if (activeElRef.current === el) return;
8730
+ selectedElRef.current = null;
8731
+ deactivate();
8732
+ if (hoveredImageRef.current) {
8733
+ hoveredImageRef.current = null;
8734
+ postToParentRef.current({ type: "ow:image-unhover" });
8735
+ }
8736
+ setToolbarVariant("rich-text");
8737
+ siblingHintElRef.current = null;
8738
+ setSiblingHintRect(null);
8739
+ setSiblingHintRects([]);
8740
+ setIsItemDragging(false);
8741
+ el.setAttribute("contenteditable", "true");
8742
+ el.removeAttribute("data-ohw-hovered");
8743
+ el.closest("[data-ohw-href-key]")?.removeAttribute("data-ohw-hovered");
8744
+ activeElRef.current = el;
8745
+ originalContentRef.current = el.innerHTML;
8746
+ el.focus();
8747
+ if (options?.caretX !== void 0 && options?.caretY !== void 0) {
8748
+ placeCaretAtPoint(el, options.caretX, options.caretY);
8749
+ }
8750
+ setToolbarShowEditLink(Boolean(getHrefKeyFromElement(el)));
8751
+ const navAnchor = getNavigationItemAnchor(el);
8752
+ if (navAnchor) {
8753
+ setReorderHrefKey(null);
8754
+ setReorderDragDisabled(false);
8755
+ } else {
8756
+ const { key: reorderKey, disabled: reorderDisabled } = getReorderHandleState(el);
8757
+ setReorderHrefKey(reorderKey);
8758
+ setReorderDragDisabled(reorderDisabled);
8759
+ }
8760
+ setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
8761
+ postToParent2({ type: "ow:enter-edit", key: el.dataset.ohwKey });
8762
+ requestAnimationFrame(() => refreshActiveCommandsRef.current());
8763
+ },
8764
+ [deactivate, postToParent2]
8765
+ );
6955
8766
  activateRef.current = activate;
6956
8767
  deactivateRef.current = deactivate;
6957
8768
  selectRef.current = select;
8769
+ selectFrameRef.current = selectFrame;
6958
8770
  deselectRef.current = deselect;
6959
8771
  useLayoutEffect2(() => {
6960
8772
  if (!subdomain || isEditMode) {
@@ -6966,6 +8778,7 @@ function OhhwellsBridge() {
6966
8778
  for (const [key, val] of Object.entries(content)) {
6967
8779
  if (key === "__ohw_sections") continue;
6968
8780
  if (applyVideoSettingNode(key, val)) continue;
8781
+ if (applyCarouselNode(key, val)) continue;
6969
8782
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
6970
8783
  if (el.dataset.ohwEditable === "image") {
6971
8784
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -6996,6 +8809,8 @@ function OhhwellsBridge() {
6996
8809
  });
6997
8810
  applyLinkByKey(key, val);
6998
8811
  }
8812
+ reconcileNavbarItemsFromContent(content);
8813
+ reconcileFooterOrderFromContent(content);
6999
8814
  enforceLinkHrefs();
7000
8815
  initSectionsFromContent(content, true);
7001
8816
  sectionsLoadedRef.current = true;
@@ -7024,7 +8839,7 @@ function OhhwellsBridge() {
7024
8839
  cancelled = true;
7025
8840
  };
7026
8841
  }, [subdomain, isEditMode]);
7027
- useEffect4(() => {
8842
+ useEffect8(() => {
7028
8843
  if (!subdomain || isEditMode) return;
7029
8844
  let debounceTimer = null;
7030
8845
  let observer = null;
@@ -7037,6 +8852,7 @@ function OhhwellsBridge() {
7037
8852
  for (const [key, val] of Object.entries(content)) {
7038
8853
  if (key === "__ohw_sections") continue;
7039
8854
  if (applyVideoSettingNode(key, val)) continue;
8855
+ if (applyCarouselNode(key, val)) continue;
7040
8856
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
7041
8857
  if (el.dataset.ohwEditable === "image") {
7042
8858
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -7055,6 +8871,8 @@ function OhhwellsBridge() {
7055
8871
  });
7056
8872
  applyLinkByKey(key, val);
7057
8873
  }
8874
+ reconcileNavbarItemsFromContent(content);
8875
+ reconcileFooterOrderFromContent(content);
7058
8876
  } finally {
7059
8877
  observer?.observe(document.body, { childList: true, subtree: true });
7060
8878
  }
@@ -7078,20 +8896,65 @@ function OhhwellsBridge() {
7078
8896
  const visible = Boolean(subdomain) && fetchState !== "done";
7079
8897
  el.style.display = visible ? "flex" : "none";
7080
8898
  }, [subdomain, fetchState]);
7081
- useEffect4(() => {
7082
- postToParent({ type: "ow:navigation", path: pathname });
7083
- }, [pathname, postToParent]);
7084
- useEffect4(() => {
8899
+ useEffect8(() => {
8900
+ postToParent2({ type: "ow:navigation", path: pathname });
8901
+ }, [pathname, postToParent2]);
8902
+ useEffect8(() => {
7085
8903
  if (!isEditMode) return;
8904
+ if (linkPopoverSessionRef.current?.intent === "add-nav") return;
8905
+ if (document.querySelector("[data-ohw-section-picker]")) return;
7086
8906
  setLinkPopover(null);
7087
8907
  deselectRef.current();
7088
8908
  deactivateRef.current();
7089
8909
  }, [pathname, isEditMode]);
7090
- useEffect4(() => {
8910
+ useEffect8(() => {
8911
+ const contentForNav = () => {
8912
+ if (isEditMode) return editContentRef.current;
8913
+ if (!subdomain) return {};
8914
+ return contentCache.get(subdomain) ?? {};
8915
+ };
8916
+ let applying = false;
8917
+ let rafId = null;
8918
+ const run = () => {
8919
+ if (footerDragRef.current || applying) return;
8920
+ applying = true;
8921
+ try {
8922
+ const content = contentForNav();
8923
+ reconcileNavbarItemsFromContent(content);
8924
+ reconcileFooterOrderFromContent(content);
8925
+ document.querySelectorAll("footer [data-ohw-href-key]").forEach((el) => {
8926
+ if (isFooterHrefKey(el.getAttribute("data-ohw-href-key"))) {
8927
+ disableNativeHrefDrag(el);
8928
+ }
8929
+ });
8930
+ } finally {
8931
+ applying = false;
8932
+ }
8933
+ };
8934
+ const scheduleRun = () => {
8935
+ if (rafId !== null) cancelAnimationFrame(rafId);
8936
+ rafId = requestAnimationFrame(() => {
8937
+ rafId = null;
8938
+ run();
8939
+ });
8940
+ };
8941
+ run();
8942
+ const nav = document.querySelector("nav");
8943
+ const footer = document.querySelector("footer");
8944
+ const observer = new MutationObserver(scheduleRun);
8945
+ if (nav) observer.observe(nav, { childList: true, subtree: true });
8946
+ if (footer) observer.observe(footer, { childList: true, subtree: true });
8947
+ if (!nav && !footer) return;
8948
+ return () => {
8949
+ observer.disconnect();
8950
+ if (rafId !== null) cancelAnimationFrame(rafId);
8951
+ };
8952
+ }, [isEditMode, pathname, subdomain, fetchState]);
8953
+ useEffect8(() => {
7091
8954
  if (!isEditMode) return;
7092
8955
  const measure = () => {
7093
8956
  const h = document.body.scrollHeight;
7094
- if (h > 50) postToParent({ type: "ow:height", height: h });
8957
+ if (h > 50) postToParent2({ type: "ow:height", height: h });
7095
8958
  };
7096
8959
  const t1 = setTimeout(measure, 50);
7097
8960
  const t2 = setTimeout(measure, 500);
@@ -7110,8 +8973,8 @@ function OhhwellsBridge() {
7110
8973
  if (resizeTimer) clearTimeout(resizeTimer);
7111
8974
  window.removeEventListener("resize", handleResize);
7112
8975
  };
7113
- }, [pathname, isEditMode, postToParent]);
7114
- useEffect4(() => {
8976
+ }, [pathname, isEditMode, postToParent2]);
8977
+ useEffect8(() => {
7115
8978
  if (!subdomainFromQuery || isEditMode) return;
7116
8979
  const handleClick = (e) => {
7117
8980
  const anchor = e.target.closest("a");
@@ -7127,7 +8990,7 @@ function OhhwellsBridge() {
7127
8990
  document.addEventListener("click", handleClick, true);
7128
8991
  return () => document.removeEventListener("click", handleClick, true);
7129
8992
  }, [subdomainFromQuery, isEditMode, router]);
7130
- useEffect4(() => {
8993
+ useEffect8(() => {
7131
8994
  if (!isEditMode) {
7132
8995
  editStylesRef.current?.base.remove();
7133
8996
  editStylesRef.current?.forceHover.remove();
@@ -7150,6 +9013,32 @@ function OhhwellsBridge() {
7150
9013
  [data-ohw-editable] {
7151
9014
  display: block;
7152
9015
  }
9016
+ /* Native <a> drag steals clicks \u2014 disable for edit links; reorder via press-to-drag / handle. */
9017
+ footer [data-ohw-href-key] {
9018
+ -webkit-user-drag: none !important;
9019
+ }
9020
+ footer [data-ohw-footer-col]:hover {
9021
+ outline: 1.5px dashed ${PRIMARY2} !important;
9022
+ outline-offset: 6px;
9023
+ border-radius: 8px;
9024
+ }
9025
+ html[data-ohw-link-popover-open] footer [data-ohw-footer-col]:hover {
9026
+ outline: none !important;
9027
+ }
9028
+ html[data-ohw-footer-press-drag] footer,
9029
+ html[data-ohw-footer-press-drag] footer * {
9030
+ user-select: none !important;
9031
+ -webkit-user-select: none !important;
9032
+ }
9033
+ html[data-ohw-item-dragging] footer,
9034
+ html[data-ohw-item-dragging] footer * {
9035
+ user-select: none !important;
9036
+ -webkit-user-select: none !important;
9037
+ pointer-events: none !important;
9038
+ }
9039
+ html[data-ohw-item-dragging] {
9040
+ cursor: grabbing !important;
9041
+ }
7153
9042
  [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
9043
  [data-ohw-editable="image"], [data-ohw-editable="image"] *,
7155
9044
  [data-ohw-editable="video"], [data-ohw-editable="video"] *,
@@ -7195,12 +9084,46 @@ function OhhwellsBridge() {
7195
9084
  }
7196
9085
  refreshStateRules();
7197
9086
  const handleClick = (e) => {
9087
+ if (suppressNextClickRef.current) {
9088
+ suppressNextClickRef.current = false;
9089
+ e.preventDefault();
9090
+ e.stopPropagation();
9091
+ return;
9092
+ }
7198
9093
  const target = e.target;
7199
9094
  if (target.closest("[data-ohw-toolbar]")) return;
7200
9095
  if (target.closest("[data-ohw-state-toggle]")) return;
7201
9096
  if (target.closest("[data-ohw-max-badge]")) return;
7202
9097
  if (isInsideLinkEditor(target)) return;
7203
9098
  if (target.closest('[data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
9099
+ if (target.closest("[data-ohw-item-drag-surface]")) {
9100
+ e.preventDefault();
9101
+ e.stopPropagation();
9102
+ const selected = selectedElRef.current;
9103
+ if (selected && isNavigationItem(selected)) {
9104
+ const editable2 = selected.querySelector('[data-ohw-editable="text"]') ?? selected.querySelector("[data-ohw-editable]");
9105
+ if (editable2) {
9106
+ activateRef.current(editable2, {
9107
+ caretX: e.clientX,
9108
+ caretY: e.clientY
9109
+ });
9110
+ }
9111
+ } else if (selected?.hasAttribute("data-ohw-footer-col")) {
9112
+ const link = listFooterLinksInColumn(selected).find((el) => {
9113
+ const r2 = el.getBoundingClientRect();
9114
+ return e.clientX >= r2.left && e.clientX <= r2.right && e.clientY >= r2.top && e.clientY <= r2.bottom;
9115
+ });
9116
+ if (link) selectRef.current(link);
9117
+ }
9118
+ return;
9119
+ }
9120
+ const navFromChrome = resolveNavContainerSelectionTarget(target, e.clientX, e.clientY);
9121
+ if (navFromChrome) {
9122
+ e.preventDefault();
9123
+ e.stopPropagation();
9124
+ selectFrameRef.current(navFromChrome);
9125
+ return;
9126
+ }
7204
9127
  e.preventDefault();
7205
9128
  e.stopPropagation();
7206
9129
  return;
@@ -7215,7 +9138,8 @@ function OhhwellsBridge() {
7215
9138
  bumpLinkPopoverGrace();
7216
9139
  setLinkPopoverRef.current({
7217
9140
  key: editable.dataset.ohwKey ?? "",
7218
- target: getLinkHref(editable)
9141
+ mode: "edit",
9142
+ target: getLinkHref2(editable)
7219
9143
  });
7220
9144
  return;
7221
9145
  }
@@ -7231,7 +9155,8 @@ function OhhwellsBridge() {
7231
9155
  e.preventDefault();
7232
9156
  e.stopPropagation();
7233
9157
  if (selectedElRef.current === navAnchor) {
7234
- activateRef.current(editable);
9158
+ if (e.detail >= 2) return;
9159
+ activateRef.current(editable, { caretX: e.clientX, caretY: e.clientY });
7235
9160
  return;
7236
9161
  }
7237
9162
  selectRef.current(navAnchor);
@@ -7246,10 +9171,42 @@ function OhhwellsBridge() {
7246
9171
  if (hrefAnchor) {
7247
9172
  e.preventDefault();
7248
9173
  e.stopPropagation();
7249
- if (selectedElRef.current === hrefAnchor) return;
9174
+ if (selectedElRef.current === hrefAnchor) {
9175
+ const textEditable = hrefAnchor.querySelector('[data-ohw-editable="text"]') ?? hrefAnchor.querySelector("[data-ohw-editable]");
9176
+ if (textEditable) {
9177
+ activateRef.current(textEditable, {
9178
+ caretX: e.clientX,
9179
+ caretY: e.clientY
9180
+ });
9181
+ }
9182
+ return;
9183
+ }
7250
9184
  selectRef.current(hrefAnchor);
7251
9185
  return;
7252
9186
  }
9187
+ const footerColClick = target.closest("[data-ohw-footer-col]");
9188
+ if (footerColClick) {
9189
+ e.preventDefault();
9190
+ e.stopPropagation();
9191
+ selectFrameRef.current(footerColClick);
9192
+ return;
9193
+ }
9194
+ const navContainerToSelect = resolveNavContainerSelectionTarget(target, e.clientX, e.clientY);
9195
+ if (navContainerToSelect) {
9196
+ e.preventDefault();
9197
+ e.stopPropagation();
9198
+ selectFrameRef.current(navContainerToSelect);
9199
+ return;
9200
+ }
9201
+ const selectedContainer = selectedElRef.current;
9202
+ if (selectedContainer && isNavigationContainer(selectedContainer)) {
9203
+ const navItem = getNavigationItemAnchor(target);
9204
+ if (!navItem && (selectedContainer === target || selectedContainer.contains(target))) {
9205
+ e.preventDefault();
9206
+ e.stopPropagation();
9207
+ return;
9208
+ }
9209
+ }
7253
9210
  if (activeElRef.current) {
7254
9211
  const sel = window.getSelection();
7255
9212
  if (sel && !sel.isCollapsed) {
@@ -7275,10 +9232,65 @@ function OhhwellsBridge() {
7275
9232
  deselectRef.current();
7276
9233
  deactivateRef.current();
7277
9234
  };
9235
+ const handleDblClick = (e) => {
9236
+ const target = e.target;
9237
+ if (target.closest("[data-ohw-toolbar]")) return;
9238
+ if (target.closest("[data-ohw-state-toggle]")) return;
9239
+ if (target.closest("[data-ohw-max-badge]")) return;
9240
+ if (isInsideLinkEditor(target)) return;
9241
+ if (target.closest('[data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
9242
+ return;
9243
+ }
9244
+ const navLabel = getNavigationLabelEditable(target);
9245
+ if (!navLabel) return;
9246
+ const { editable } = navLabel;
9247
+ e.preventDefault();
9248
+ e.stopPropagation();
9249
+ if (activeElRef.current !== editable) {
9250
+ activateRef.current(editable);
9251
+ }
9252
+ requestAnimationFrame(() => {
9253
+ selectAllTextInEditable(editable);
9254
+ refreshActiveCommandsRef.current();
9255
+ });
9256
+ };
7278
9257
  const handleMouseOver = (e) => {
9258
+ if (document.documentElement.hasAttribute("data-ohw-section-picking")) return;
7279
9259
  const target = e.target;
9260
+ if (linkPopoverOpenRef.current) {
9261
+ hoveredItemElRef.current = null;
9262
+ setHoveredItemRect(null);
9263
+ hoveredNavContainerRef.current = null;
9264
+ setHoveredNavContainerRect(null);
9265
+ return;
9266
+ }
9267
+ if (toolbarVariantRef.current !== "select-frame") {
9268
+ const navContainer = target.closest("[data-ohw-nav-container]");
9269
+ if (navContainer && !getNavigationItemAnchor(target)) {
9270
+ hoveredNavContainerRef.current = navContainer;
9271
+ setHoveredNavContainerRect(navContainer.getBoundingClientRect());
9272
+ hoveredItemElRef.current = null;
9273
+ setHoveredItemRect(null);
9274
+ return;
9275
+ }
9276
+ if (!target.closest("[data-ohw-nav-container]")) {
9277
+ hoveredNavContainerRef.current = null;
9278
+ setHoveredNavContainerRect(null);
9279
+ }
9280
+ }
9281
+ const footerCol = target.closest("[data-ohw-footer-col]");
9282
+ if (footerCol) {
9283
+ hoveredNavContainerRef.current = null;
9284
+ setHoveredNavContainerRect(null);
9285
+ if (selectedElRef.current === footerCol) return;
9286
+ hoveredItemElRef.current = footerCol;
9287
+ setHoveredItemRect(footerCol.getBoundingClientRect());
9288
+ return;
9289
+ }
7280
9290
  const navAnchor = getNavigationItemAnchor(target);
7281
9291
  if (navAnchor) {
9292
+ hoveredNavContainerRef.current = null;
9293
+ setHoveredNavContainerRect(null);
7282
9294
  const selected2 = selectedElRef.current;
7283
9295
  if (selected2 === navAnchor || selected2?.contains(navAnchor)) return;
7284
9296
  clearHrefKeyHover(navAnchor);
@@ -7303,6 +9315,23 @@ function OhhwellsBridge() {
7303
9315
  };
7304
9316
  const handleMouseOut = (e) => {
7305
9317
  const target = e.target;
9318
+ if (target.closest("[data-ohw-nav-container]")) {
9319
+ const related2 = e.relatedTarget instanceof Element ? e.relatedTarget : null;
9320
+ if (related2?.closest("[data-ohw-nav-container], [data-ohw-navbar-container-chrome], [data-ohw-navbar-add-button]")) {
9321
+ return;
9322
+ }
9323
+ hoveredNavContainerRef.current = null;
9324
+ setHoveredNavContainerRect(null);
9325
+ }
9326
+ const footerCol = target.closest("[data-ohw-footer-col]");
9327
+ if (footerCol && hoveredItemElRef.current === footerCol) {
9328
+ const related2 = e.relatedTarget instanceof Element ? e.relatedTarget : null;
9329
+ if (related2?.closest("[data-ohw-drag-handle-container], [data-ohw-item-interaction]")) return;
9330
+ if (related2?.closest("[data-ohw-footer-col]") === footerCol) return;
9331
+ hoveredItemElRef.current = null;
9332
+ setHoveredItemRect(null);
9333
+ return;
9334
+ }
7306
9335
  const navAnchor = getNavigationItemAnchor(target);
7307
9336
  if (navAnchor) {
7308
9337
  const related2 = e.relatedTarget instanceof Element ? e.relatedTarget : null;
@@ -7424,44 +9453,87 @@ function OhhwellsBridge() {
7424
9453
  }
7425
9454
  return smallest;
7426
9455
  };
9456
+ const dismissImageHover = () => {
9457
+ if (hoveredImageRef.current) {
9458
+ hoveredImageRef.current = null;
9459
+ hoveredImageHasTextOverlapRef.current = false;
9460
+ resumeAnimTracks();
9461
+ postToParentRef.current({ type: "ow:image-unhover" });
9462
+ }
9463
+ clearImageHover();
9464
+ };
9465
+ const probeNavigationHoverAt = (x, y) => {
9466
+ if (toolbarVariantRef.current === "select-frame") {
9467
+ hoveredNavContainerRef.current = null;
9468
+ setHoveredNavContainerRect(null);
9469
+ return;
9470
+ }
9471
+ const navContainer = document.querySelector("[data-ohw-nav-container]");
9472
+ if (!navContainer) {
9473
+ hoveredNavContainerRef.current = null;
9474
+ setHoveredNavContainerRect(null);
9475
+ return;
9476
+ }
9477
+ const containerRect = navContainer.getBoundingClientRect();
9478
+ const overContainer = x >= containerRect.left && x <= containerRect.right && y >= containerRect.top && y <= containerRect.bottom;
9479
+ if (!overContainer) {
9480
+ hoveredNavContainerRef.current = null;
9481
+ setHoveredNavContainerRect(null);
9482
+ return;
9483
+ }
9484
+ const navItemHit = Array.from(
9485
+ navContainer.querySelectorAll("[data-ohw-href-key]")
9486
+ ).find((el) => {
9487
+ if (!isNavigationItem(el) || isNavbarButton2(el)) return false;
9488
+ const itemRect = el.getBoundingClientRect();
9489
+ return x >= itemRect.left && x <= itemRect.right && y >= itemRect.top && y <= itemRect.bottom;
9490
+ });
9491
+ if (navItemHit) {
9492
+ hoveredNavContainerRef.current = null;
9493
+ setHoveredNavContainerRect(null);
9494
+ const selected = selectedElRef.current;
9495
+ if (selected !== navItemHit && !selected?.contains(navItemHit)) {
9496
+ clearHrefKeyHover(navItemHit);
9497
+ hoveredItemElRef.current = navItemHit;
9498
+ setHoveredItemRect(navItemHit.getBoundingClientRect());
9499
+ }
9500
+ return;
9501
+ }
9502
+ hoveredNavContainerRef.current = navContainer;
9503
+ setHoveredNavContainerRect(containerRect);
9504
+ hoveredItemElRef.current = null;
9505
+ setHoveredItemRect(null);
9506
+ };
7427
9507
  const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
7428
9508
  if (linkPopoverOpenRef.current) {
7429
- if (hoveredImageRef.current) {
7430
- hoveredImageRef.current = null;
7431
- hoveredImageHasTextOverlapRef.current = false;
7432
- resumeAnimTracks();
7433
- clearImageHover();
7434
- }
9509
+ dismissImageHover();
9510
+ return;
9511
+ }
9512
+ const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
9513
+ if (isPointOverNavigation(x, y)) {
9514
+ dismissImageHover();
9515
+ probeNavigationHoverAt(x, y);
7435
9516
  return;
7436
9517
  }
9518
+ hoveredNavContainerRef.current = null;
9519
+ setHoveredNavContainerRect(null);
7437
9520
  const toggleEl = document.querySelector("[data-ohw-state-toggle]");
7438
9521
  if (toggleEl) {
7439
- const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
7440
9522
  const tr = toggleEl.getBoundingClientRect();
7441
9523
  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
- }
9524
+ dismissImageHover();
7447
9525
  return;
7448
9526
  }
7449
9527
  }
7450
9528
  const imgEl = findImageAtPoint(clientX, clientY, fromParentViewport);
7451
9529
  const activeEl = activeElRef.current;
7452
9530
  if (activeEl && (!imgEl || imgEl.contains(activeEl))) {
7453
- if (hoveredImageRef.current) {
7454
- hoveredImageRef.current = null;
7455
- hoveredImageHasTextOverlapRef.current = false;
7456
- resumeAnimTracks();
7457
- clearImageHover();
7458
- }
9531
+ dismissImageHover();
7459
9532
  return;
7460
9533
  }
7461
9534
  if (imgEl) {
7462
- const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
7463
9535
  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"]')) {
9536
+ 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
9537
  if (hoveredImageRef.current) {
7466
9538
  hoveredImageRef.current = null;
7467
9539
  resumeAnimTracks();
@@ -7559,12 +9631,12 @@ function OhhwellsBridge() {
7559
9631
  resumeAnimTracks();
7560
9632
  clearImageHover();
7561
9633
  }
7562
- const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
9634
+ const { x: x2, y: y2 } = toProbeCoords(clientX, clientY, fromParentViewport);
7563
9635
  const textEl = Array.from(
7564
9636
  document.querySelectorAll(NON_MEDIA_SELECTOR)
7565
9637
  ).find((el) => {
7566
9638
  const er = el.getBoundingClientRect();
7567
- return x >= er.left && x <= er.right && y >= er.top && y <= er.bottom;
9639
+ return x2 >= er.left && x2 <= er.right && y2 >= er.top && y2 <= er.bottom;
7568
9640
  });
7569
9641
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
7570
9642
  if (textEl && !textEl.hasAttribute("contenteditable")) {
@@ -7588,6 +9660,14 @@ function OhhwellsBridge() {
7588
9660
  }
7589
9661
  };
7590
9662
  const probeHoverCardsAt = (clientX, clientY, fromParentViewport = false) => {
9663
+ if (linkPopoverOpenRef.current || document.documentElement.hasAttribute("data-ohw-section-picking")) {
9664
+ if (activeStateElRef.current) {
9665
+ activeStateElRef.current.removeAttribute("data-ohw-state-hovered");
9666
+ activeStateElRef.current = null;
9667
+ setToggleState(null);
9668
+ }
9669
+ return;
9670
+ }
7591
9671
  const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
7592
9672
  const toggleEl = document.querySelector("[data-ohw-state-toggle]");
7593
9673
  if (toggleEl) {
@@ -7660,6 +9740,14 @@ function OhhwellsBridge() {
7660
9740
  probeHoverCardsAt(clientX, clientY);
7661
9741
  };
7662
9742
  const handleDragOver = (e) => {
9743
+ const footerSession = footerDragRef.current;
9744
+ if (footerSession) {
9745
+ e.preventDefault();
9746
+ if (e.dataTransfer) e.dataTransfer.dropEffect = "move";
9747
+ const slot = footerSession.kind === "link" && footerSession.hrefKey ? hitTestLinkDropSlot(e.clientX, e.clientY, footerSession.hrefKey) : hitTestColumnDropSlot(e.clientX, e.clientY);
9748
+ refreshFooterDragVisualsRef.current(footerSession, slot, e.clientX, e.clientY);
9749
+ return;
9750
+ }
7663
9751
  e.preventDefault();
7664
9752
  const el = findImageAtPoint(e.clientX, e.clientY, false);
7665
9753
  if (!el) {
@@ -7842,6 +9930,7 @@ function OhhwellsBridge() {
7842
9930
  continue;
7843
9931
  }
7844
9932
  if (applyVideoSettingNode(key, val)) continue;
9933
+ if (applyCarouselNode(key, val)) continue;
7845
9934
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
7846
9935
  if (el.dataset.ohwEditable === "image") {
7847
9936
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -7864,6 +9953,9 @@ function OhhwellsBridge() {
7864
9953
  sectionsLoadedRef.current = true;
7865
9954
  pendingScheduleConfigRequests.current.splice(0).forEach(processConfigRequest);
7866
9955
  }
9956
+ editContentRef.current = { ...editContentRef.current, ...content };
9957
+ reconcileNavbarItemsFromContent(editContentRef.current);
9958
+ reconcileFooterOrderFromContent(editContentRef.current);
7867
9959
  enforceLinkHrefs();
7868
9960
  postToParentRef.current({ type: "ow:hydrate-done" });
7869
9961
  };
@@ -7871,6 +9963,7 @@ function OhhwellsBridge() {
7871
9963
  const handleDeactivate = (e) => {
7872
9964
  if (e.data?.type !== "ow:deactivate") return;
7873
9965
  if (Date.now() < linkPopoverGraceUntilRef.current) return;
9966
+ if (document.querySelector("[data-ohw-section-picker]")) return;
7874
9967
  if (linkPopoverOpenRef.current) {
7875
9968
  setLinkPopoverRef.current(null);
7876
9969
  return;
@@ -7879,13 +9972,40 @@ function OhhwellsBridge() {
7879
9972
  deactivateRef.current();
7880
9973
  };
7881
9974
  window.addEventListener("message", handleDeactivate);
9975
+ const handleUiEscape = (e) => {
9976
+ if (e.data?.type !== "ui:escape") return;
9977
+ if (linkPopoverOpenRef.current) {
9978
+ setLinkPopoverRef.current(null);
9979
+ }
9980
+ };
9981
+ window.addEventListener("message", handleUiEscape);
7882
9982
  const handleKeyDown = (e) => {
9983
+ if (e.key === "Escape" && document.querySelector("[data-ohw-section-picker]")) return;
9984
+ if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "a" && activeElRef.current) {
9985
+ const navAnchor = getNavigationItemAnchor(activeElRef.current);
9986
+ if (navAnchor) {
9987
+ e.preventDefault();
9988
+ selectAllTextInEditable(activeElRef.current);
9989
+ refreshActiveCommandsRef.current();
9990
+ return;
9991
+ }
9992
+ }
7883
9993
  if (e.key === "Escape" && linkPopoverOpenRef.current) {
7884
9994
  setLinkPopoverRef.current(null);
7885
9995
  return;
7886
9996
  }
7887
9997
  if (e.key === "Escape" && selectedElRef.current && !activeElRef.current) {
7888
- deselectRef.current();
9998
+ if (toolbarVariantRef.current === "select-frame") {
9999
+ deselectRef.current();
10000
+ return;
10001
+ }
10002
+ const parent = getNavigationSelectionParent(selectedElRef.current);
10003
+ if (parent) {
10004
+ e.preventDefault();
10005
+ selectFrameRef.current(parent);
10006
+ } else {
10007
+ deselectRef.current();
10008
+ }
7889
10009
  return;
7890
10010
  }
7891
10011
  if (e.key === "Escape" && activeElRef.current) {
@@ -7943,9 +10063,23 @@ function OhhwellsBridge() {
7943
10063
  if (hoveredItemElRef.current) {
7944
10064
  setHoveredItemRect(hoveredItemElRef.current.getBoundingClientRect());
7945
10065
  }
10066
+ if (hoveredNavContainerRef.current) {
10067
+ setHoveredNavContainerRect(hoveredNavContainerRef.current.getBoundingClientRect());
10068
+ }
7946
10069
  if (siblingHintElRef.current) {
7947
10070
  setSiblingHintRect(siblingHintElRef.current.getBoundingClientRect());
7948
10071
  }
10072
+ const selected = selectedElRef.current;
10073
+ if (selected && !footerDragRef.current && (selected.hasAttribute("data-ohw-footer-col") || Boolean(selected.closest("footer") && isInferredFooterGroup(selected)))) {
10074
+ setSiblingHintRects(
10075
+ listFooterColumns().filter((column) => column !== selected).map((column) => column.getBoundingClientRect())
10076
+ );
10077
+ }
10078
+ if (footerDragRef.current) {
10079
+ const session = footerDragRef.current;
10080
+ const slot = session.kind === "link" && session.hrefKey ? hitTestLinkDropSlot(session.lastClientX, session.lastClientY, session.hrefKey) : hitTestColumnDropSlot(session.lastClientX, session.lastClientY);
10081
+ refreshFooterDragVisualsRef.current(session, slot, session.lastClientX, session.lastClientY);
10082
+ }
7949
10083
  if (hoveredImageRef.current) {
7950
10084
  const el = hoveredImageRef.current;
7951
10085
  const r2 = el.getBoundingClientRect();
@@ -7956,7 +10090,7 @@ function OhhwellsBridge() {
7956
10090
  };
7957
10091
  const handleSave = (e) => {
7958
10092
  if (e.data?.type !== "ow:save") return;
7959
- const nodes = collectEditableNodes();
10093
+ const nodes = collectEditableNodes(editContentRef.current);
7960
10094
  const tracker = document.querySelector("[data-ohw-sections-tracker]");
7961
10095
  if (tracker?.textContent) nodes.push({ key: "__ohw_sections", type: "sections", text: tracker.textContent });
7962
10096
  postToParentRef.current({ type: "ow:save-result", nodes });
@@ -8041,6 +10175,7 @@ function OhhwellsBridge() {
8041
10175
  const handleDocMouseLeave = () => {
8042
10176
  hoveredImageRef.current = null;
8043
10177
  setMediaHover(null);
10178
+ setCarouselHover(null);
8044
10179
  document.querySelectorAll("[data-ohw-state-hovered]").forEach((el) => el.removeAttribute("data-ohw-state-hovered"));
8045
10180
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
8046
10181
  activeStateElRef.current = null;
@@ -8080,6 +10215,48 @@ function OhhwellsBridge() {
8080
10215
  if (e.data?.type !== "ow:canvas-height" || typeof e.data.height !== "number") return;
8081
10216
  document.documentElement.style.setProperty("--ohw-canvas-h", `${e.data.height}px`);
8082
10217
  };
10218
+ const handleCarouselChange = (e) => {
10219
+ if (e.data?.type !== "ow:carousel-change") return;
10220
+ const { key, images } = e.data;
10221
+ if (!key || !Array.isArray(images)) return;
10222
+ const text = JSON.stringify(images);
10223
+ applyCarouselValue(key, images);
10224
+ editContentRef.current = { ...editContentRef.current, [key]: text };
10225
+ postToParentRef.current({ type: "ow:change", nodes: [{ key, text }] });
10226
+ setCarouselHover(null);
10227
+ };
10228
+ const findCarouselAtPoint = (clientX, clientY) => {
10229
+ const containers = Array.from(document.querySelectorAll(`[${CAROUSEL_ATTR}]`));
10230
+ for (let i = containers.length - 1; i >= 0; i--) {
10231
+ const r2 = containers[i].getBoundingClientRect();
10232
+ if (clientX >= r2.left && clientX <= r2.right && clientY >= r2.top && clientY <= r2.bottom) {
10233
+ return containers[i];
10234
+ }
10235
+ }
10236
+ return null;
10237
+ };
10238
+ const isPointOverEditable = (scope, clientX, clientY) => {
10239
+ const editables = scope.querySelectorAll("[data-ohw-editable], [data-ohw-href-key]");
10240
+ for (const el of editables) {
10241
+ const r2 = el.getBoundingClientRect();
10242
+ if (clientX >= r2.left && clientX <= r2.right && clientY >= r2.top && clientY <= r2.bottom) return true;
10243
+ }
10244
+ return false;
10245
+ };
10246
+ const handleCarouselHover = (e) => {
10247
+ const container = findCarouselAtPoint(e.clientX, e.clientY);
10248
+ const scope = container?.closest("[data-ohw-section]") ?? container?.parentElement ?? null;
10249
+ if (!container || !scope || isPointOverEditable(scope, e.clientX, e.clientY)) {
10250
+ setCarouselHover((prev) => prev ? null : prev);
10251
+ return;
10252
+ }
10253
+ const key = container.getAttribute("data-ohw-key") ?? "";
10254
+ if (!key) return;
10255
+ const r2 = getVisibleRect(container);
10256
+ setCarouselHover(
10257
+ (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 } }
10258
+ );
10259
+ };
8083
10260
  const applyToolbarPos = (rect) => {
8084
10261
  const ps = parentScrollRef.current;
8085
10262
  const approxW = 330;
@@ -8139,9 +10316,45 @@ function OhhwellsBridge() {
8139
10316
  return clientX >= r2.left && clientX <= r2.right && clientY >= r2.top && clientY <= r2.bottom;
8140
10317
  });
8141
10318
  if (textEditable) {
8142
- activateRef.current(textEditable);
10319
+ const hrefCtx = getHrefKeyFromElement(textEditable);
10320
+ const navAnchor = hrefCtx ? getNavigationItemAnchor(hrefCtx.anchor) : null;
10321
+ if (navAnchor) {
10322
+ if (selectedElRef.current === navAnchor) {
10323
+ activateRef.current(textEditable, { caretX: clientX, caretY: clientY });
10324
+ } else {
10325
+ selectRef.current(navAnchor);
10326
+ }
10327
+ return;
10328
+ }
10329
+ activateRef.current(textEditable, { caretX: clientX, caretY: clientY });
8143
10330
  return;
8144
10331
  }
10332
+ const navContainer = document.querySelector("[data-ohw-nav-container]");
10333
+ if (navContainer) {
10334
+ const r2 = navContainer.getBoundingClientRect();
10335
+ const pad = 8;
10336
+ const over = clientX >= r2.left - pad && clientX <= r2.right + pad && clientY >= r2.top - pad && clientY <= r2.bottom + pad;
10337
+ if (over && !isPointOverNavItem(navContainer, clientX, clientY)) {
10338
+ selectFrameRef.current(navContainer);
10339
+ return;
10340
+ }
10341
+ }
10342
+ const navRoot = document.querySelector("[data-ohw-nav-root]");
10343
+ if (navRoot && navContainer) {
10344
+ const rr = navRoot.getBoundingClientRect();
10345
+ if (clientX >= rr.left && clientX <= rr.right && clientY >= rr.top && clientY <= rr.bottom && !isPointOverNavItem(navContainer, clientX, clientY)) {
10346
+ const book = navRoot.querySelector('[data-ohw-role="navbar-button"]');
10347
+ if (book) {
10348
+ const br = book.getBoundingClientRect();
10349
+ if (clientX >= br.left && clientX <= br.right && clientY >= br.top && clientY <= br.bottom) {
10350
+ deactivateRef.current();
10351
+ return;
10352
+ }
10353
+ }
10354
+ selectFrameRef.current(navContainer);
10355
+ return;
10356
+ }
10357
+ }
8145
10358
  deactivateRef.current();
8146
10359
  };
8147
10360
  window.addEventListener("message", handleSave);
@@ -8152,6 +10365,7 @@ function OhhwellsBridge() {
8152
10365
  window.addEventListener("message", handleRemoveSchedulingSection);
8153
10366
  window.addEventListener("message", handleImageUrl);
8154
10367
  window.addEventListener("message", handleImageUploading);
10368
+ window.addEventListener("message", handleCarouselChange);
8155
10369
  window.addEventListener("message", handleCanvasHeight);
8156
10370
  window.addEventListener("message", handleParentScroll);
8157
10371
  window.addEventListener("message", handlePointerSync);
@@ -8163,11 +10377,13 @@ function OhhwellsBridge() {
8163
10377
  };
8164
10378
  window.addEventListener("resize", handleViewportResize, { passive: true });
8165
10379
  document.addEventListener("click", handleClick, true);
10380
+ document.addEventListener("dblclick", handleDblClick, true);
8166
10381
  document.addEventListener("paste", handlePaste, true);
8167
10382
  document.addEventListener("input", handleInput, true);
8168
10383
  document.addEventListener("mouseover", handleMouseOver, true);
8169
10384
  document.addEventListener("mouseout", handleMouseOut, true);
8170
10385
  document.addEventListener("mousemove", handleMouseMove, true);
10386
+ document.addEventListener("mousemove", handleCarouselHover, true);
8171
10387
  document.addEventListener("mouseleave", handleDocMouseLeave);
8172
10388
  document.addEventListener("dragover", handleDragOver, true);
8173
10389
  document.addEventListener("dragleave", handleDragLeave, true);
@@ -8177,11 +10393,13 @@ function OhhwellsBridge() {
8177
10393
  window.addEventListener("scroll", handleScroll, true);
8178
10394
  return () => {
8179
10395
  document.removeEventListener("click", handleClick, true);
10396
+ document.removeEventListener("dblclick", handleDblClick, true);
8180
10397
  document.removeEventListener("paste", handlePaste, true);
8181
10398
  document.removeEventListener("input", handleInput, true);
8182
10399
  document.removeEventListener("mouseover", handleMouseOver, true);
8183
10400
  document.removeEventListener("mouseout", handleMouseOut, true);
8184
10401
  document.removeEventListener("mousemove", handleMouseMove, true);
10402
+ document.removeEventListener("mousemove", handleCarouselHover, true);
8185
10403
  document.removeEventListener("mouseleave", handleDocMouseLeave);
8186
10404
  document.removeEventListener("dragover", handleDragOver, true);
8187
10405
  document.removeEventListener("dragleave", handleDragLeave, true);
@@ -8197,6 +10415,7 @@ function OhhwellsBridge() {
8197
10415
  window.removeEventListener("message", handleRemoveSchedulingSection);
8198
10416
  window.removeEventListener("message", handleImageUrl);
8199
10417
  window.removeEventListener("message", handleImageUploading);
10418
+ window.removeEventListener("message", handleCarouselChange);
8200
10419
  window.removeEventListener("message", handleCanvasHeight);
8201
10420
  window.removeEventListener("message", handleParentScroll);
8202
10421
  window.removeEventListener("resize", handleViewportResize);
@@ -8204,13 +10423,165 @@ function OhhwellsBridge() {
8204
10423
  window.removeEventListener("message", handleClickAt);
8205
10424
  window.removeEventListener("message", handleHydrate);
8206
10425
  window.removeEventListener("message", handleDeactivate);
10426
+ window.removeEventListener("message", handleUiEscape);
8207
10427
  autoSaveTimers.current.forEach(clearTimeout);
8208
10428
  autoSaveTimers.current.clear();
8209
10429
  if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
8210
10430
  if (imageShowTimerRef.current) clearTimeout(imageShowTimerRef.current);
8211
10431
  };
8212
10432
  }, [isEditMode, refreshStateRules]);
8213
- useEffect4(() => {
10433
+ useEffect8(() => {
10434
+ if (!isEditMode) return;
10435
+ const THRESHOLD = 10;
10436
+ const resolveWasSelected = (el) => {
10437
+ if (selectedElRef.current === el) return true;
10438
+ const activeAnchor = activeElRef.current ? getNavigationItemAnchor(activeElRef.current) : null;
10439
+ return activeAnchor === el;
10440
+ };
10441
+ const onPointerDown = (e) => {
10442
+ if (e.button !== 0) return;
10443
+ if (suppressNextClickRef.current && !footerDragRef.current && !footerPointerDragRef.current?.started) {
10444
+ suppressNextClickRef.current = false;
10445
+ }
10446
+ if (footerDragRef.current) return;
10447
+ const target = e.target;
10448
+ if (!target) return;
10449
+ 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]')) {
10450
+ return;
10451
+ }
10452
+ if (target.closest("[data-ohw-item-drag-surface]")) return;
10453
+ const anchor = getNavigationItemAnchor(target);
10454
+ const hrefKey = anchor?.getAttribute("data-ohw-href-key") ?? null;
10455
+ if (anchor && isFooterHrefKey(hrefKey)) {
10456
+ armFooterPressDrag();
10457
+ footerPointerDragRef.current = {
10458
+ el: anchor,
10459
+ kind: "link",
10460
+ startX: e.clientX,
10461
+ startY: e.clientY,
10462
+ pointerId: e.pointerId,
10463
+ wasSelected: resolveWasSelected(anchor),
10464
+ started: false
10465
+ };
10466
+ return;
10467
+ }
10468
+ const col = target.closest("[data-ohw-footer-col]");
10469
+ if (col && !getNavigationItemAnchor(target)) {
10470
+ armFooterPressDrag();
10471
+ footerPointerDragRef.current = {
10472
+ el: col,
10473
+ kind: "column",
10474
+ startX: e.clientX,
10475
+ startY: e.clientY,
10476
+ pointerId: e.pointerId,
10477
+ wasSelected: resolveWasSelected(col),
10478
+ started: false
10479
+ };
10480
+ }
10481
+ };
10482
+ const onPointerMove = (e) => {
10483
+ const pending = footerPointerDragRef.current;
10484
+ if (!pending) return;
10485
+ if (pending.started) {
10486
+ e.preventDefault();
10487
+ clearTextSelection();
10488
+ const session = footerDragRef.current;
10489
+ if (!session) return;
10490
+ const slot = session.kind === "link" && session.hrefKey ? hitTestLinkDropSlot(e.clientX, e.clientY, session.hrefKey) : hitTestColumnDropSlot(e.clientX, e.clientY);
10491
+ refreshFooterDragVisualsRef.current(session, slot, e.clientX, e.clientY);
10492
+ return;
10493
+ }
10494
+ const dx = e.clientX - pending.startX;
10495
+ const dy = e.clientY - pending.startY;
10496
+ if (dx * dx + dy * dy < THRESHOLD * THRESHOLD) return;
10497
+ e.preventDefault();
10498
+ pending.started = true;
10499
+ clearTextSelection();
10500
+ try {
10501
+ document.body.setPointerCapture(pending.pointerId);
10502
+ } catch {
10503
+ }
10504
+ if (linkPopoverOpenRef.current) {
10505
+ setLinkPopoverRef.current(null);
10506
+ }
10507
+ if (activeElRef.current) {
10508
+ deactivateRef.current();
10509
+ }
10510
+ if (pending.kind === "link") {
10511
+ const key = pending.el.getAttribute("data-ohw-href-key");
10512
+ if (!key) return;
10513
+ const column = findFooterColumnForLink(pending.el);
10514
+ const columns2 = listFooterColumns();
10515
+ beginFooterDragRef.current({
10516
+ kind: "link",
10517
+ hrefKey: key,
10518
+ columnEl: column,
10519
+ sourceColumnIndex: column ? columns2.indexOf(column) : 0,
10520
+ wasSelected: pending.wasSelected,
10521
+ draggedEl: pending.el,
10522
+ lastClientX: e.clientX,
10523
+ lastClientY: e.clientY,
10524
+ activeSlot: null
10525
+ });
10526
+ return;
10527
+ }
10528
+ const columns = listFooterColumns();
10529
+ const idx = columns.indexOf(pending.el);
10530
+ if (idx < 0) return;
10531
+ beginFooterDragRef.current({
10532
+ kind: "column",
10533
+ hrefKey: null,
10534
+ columnEl: pending.el,
10535
+ sourceColumnIndex: idx,
10536
+ wasSelected: pending.wasSelected,
10537
+ draggedEl: pending.el,
10538
+ lastClientX: e.clientX,
10539
+ lastClientY: e.clientY,
10540
+ activeSlot: null
10541
+ });
10542
+ };
10543
+ const endPointerDrag = (e) => {
10544
+ const pending = footerPointerDragRef.current;
10545
+ footerPointerDragRef.current = null;
10546
+ try {
10547
+ if (document.body.hasPointerCapture(e.pointerId)) {
10548
+ document.body.releasePointerCapture(e.pointerId);
10549
+ }
10550
+ } catch {
10551
+ }
10552
+ if (!pending?.started) {
10553
+ unlockFooterDragInteraction();
10554
+ return;
10555
+ }
10556
+ suppressNextClickRef.current = true;
10557
+ commitFooterDragRef.current(e.clientX, e.clientY);
10558
+ };
10559
+ const blockSelectStart = (e) => {
10560
+ if (footerDragRef.current || footerPointerDragRef.current?.started || document.documentElement.hasAttribute("data-ohw-footer-press-drag")) {
10561
+ e.preventDefault();
10562
+ }
10563
+ };
10564
+ const onDragEnd = (_e) => {
10565
+ if (!footerDragRef.current) return;
10566
+ commitFooterDragRef.current();
10567
+ };
10568
+ document.addEventListener("pointerdown", onPointerDown, true);
10569
+ document.addEventListener("pointermove", onPointerMove, true);
10570
+ document.addEventListener("pointerup", endPointerDrag, true);
10571
+ document.addEventListener("pointercancel", endPointerDrag, true);
10572
+ document.addEventListener("selectstart", blockSelectStart, true);
10573
+ document.addEventListener("dragend", onDragEnd, true);
10574
+ return () => {
10575
+ document.removeEventListener("pointerdown", onPointerDown, true);
10576
+ document.removeEventListener("pointermove", onPointerMove, true);
10577
+ document.removeEventListener("pointerup", endPointerDrag, true);
10578
+ document.removeEventListener("pointercancel", endPointerDrag, true);
10579
+ document.removeEventListener("selectstart", blockSelectStart, true);
10580
+ document.removeEventListener("dragend", onDragEnd, true);
10581
+ unlockFooterDragInteraction();
10582
+ };
10583
+ }, [isEditMode]);
10584
+ useEffect8(() => {
8214
10585
  const handler = (e) => {
8215
10586
  if (e.data?.type !== "ow:request-schedule-config") return;
8216
10587
  const insertAfterVal = e.data.insertAfter;
@@ -8226,7 +10597,7 @@ function OhhwellsBridge() {
8226
10597
  window.addEventListener("message", handler);
8227
10598
  return () => window.removeEventListener("message", handler);
8228
10599
  }, [processConfigRequest]);
8229
- useEffect4(() => {
10600
+ useEffect8(() => {
8230
10601
  if (!isEditMode) return;
8231
10602
  document.querySelectorAll("[data-ohw-active-state]").forEach((el) => {
8232
10603
  el.removeAttribute("data-ohw-active-state");
@@ -8247,27 +10618,33 @@ function OhhwellsBridge() {
8247
10618
  const next = { ...prev, [pathKey]: sections };
8248
10619
  return shouldUseDevFixtures() ? { ...DEV_SECTIONS_BY_PATH, ...next } : next;
8249
10620
  });
8250
- postToParent({ type: "ow:ready", version: "1", path: pathname, nodes: collectEditableNodes(), sections });
8251
- postToParent({ type: "ow:request-site-pages" });
10621
+ postToParent2({
10622
+ type: "ow:ready",
10623
+ version: "1",
10624
+ path: pathname,
10625
+ nodes: collectEditableNodes(editContentRef.current),
10626
+ sections
10627
+ });
10628
+ postToParent2({ type: "ow:request-site-pages" });
8252
10629
  }, 150);
8253
10630
  return () => {
8254
10631
  cancelAnimationFrame(raf);
8255
10632
  clearTimeout(timer);
8256
10633
  };
8257
- }, [pathname, isEditMode, refreshStateRules, postToParent]);
8258
- useEffect4(() => {
10634
+ }, [pathname, isEditMode, refreshStateRules, postToParent2]);
10635
+ useEffect8(() => {
8259
10636
  scrollToHashSectionWhenReady();
8260
10637
  const onHashChange = () => scrollToHashSectionWhenReady();
8261
10638
  window.addEventListener("hashchange", onHashChange);
8262
10639
  return () => window.removeEventListener("hashchange", onHashChange);
8263
10640
  }, [pathname]);
8264
- const handleCommand = useCallback2((cmd) => {
10641
+ const handleCommand = useCallback4((cmd) => {
8265
10642
  document.execCommand(cmd, false);
8266
10643
  activeElRef.current?.focus();
8267
10644
  if (activeElRef.current) setToolbarRect(getEditMeasureEl(activeElRef.current).getBoundingClientRect());
8268
10645
  refreshActiveCommandsRef.current();
8269
10646
  }, []);
8270
- const handleStateChange = useCallback2((state) => {
10647
+ const handleStateChange = useCallback4((state) => {
8271
10648
  if (!activeStateElRef.current) return;
8272
10649
  const el = activeStateElRef.current;
8273
10650
  if (state === "Default") {
@@ -8280,18 +10657,22 @@ function OhhwellsBridge() {
8280
10657
  }
8281
10658
  setToggleState((prev) => prev ? { ...prev, activeState: state } : null);
8282
10659
  }, [deactivate]);
8283
- const closeLinkPopover = useCallback2(() => setLinkPopover(null), []);
8284
- const openLinkPopoverForActive = useCallback2(() => {
10660
+ const closeLinkPopover = useCallback4(() => {
10661
+ addNavAfterAnchorRef.current = null;
10662
+ setLinkPopover(null);
10663
+ }, []);
10664
+ const openLinkPopoverForActive = useCallback4(() => {
8285
10665
  const hrefCtx = getHrefKeyFromElement(activeElRef.current);
8286
10666
  if (!hrefCtx) return;
8287
10667
  bumpLinkPopoverGrace();
8288
10668
  setLinkPopover({
8289
10669
  key: hrefCtx.key,
8290
- target: getLinkHref(hrefCtx.anchor)
10670
+ mode: "edit",
10671
+ target: getLinkHref2(hrefCtx.anchor)
8291
10672
  });
8292
10673
  deactivate();
8293
10674
  }, [deactivate]);
8294
- const openLinkPopoverForSelected = useCallback2(() => {
10675
+ const openLinkPopoverForSelected = useCallback4(() => {
8295
10676
  const anchor = selectedElRef.current;
8296
10677
  if (!anchor) return;
8297
10678
  const key = anchor.getAttribute("data-ohw-href-key");
@@ -8299,30 +10680,80 @@ function OhhwellsBridge() {
8299
10680
  bumpLinkPopoverGrace();
8300
10681
  setLinkPopover({
8301
10682
  key,
8302
- target: getLinkHref(anchor)
10683
+ mode: "edit",
10684
+ target: getLinkHref2(anchor)
8303
10685
  });
8304
10686
  deselect();
8305
10687
  }, [deselect]);
8306
- const handleLinkPopoverSubmit = useCallback2(
10688
+ const handleLinkPopoverSubmit = useCallback4(
8307
10689
  (target) => {
8308
- if (!linkPopover) return;
8309
- const { key } = linkPopover;
10690
+ const session = linkPopoverSessionRef.current;
10691
+ if (!session) return;
10692
+ if (session.intent === "add-nav") {
10693
+ const trimmed = target.trim();
10694
+ let href = trimmed;
10695
+ let label = "Untitled";
10696
+ if (trimmed) {
10697
+ const { pageRoute, sectionId } = parseTarget(trimmed);
10698
+ const page = resolvePage(pageRoute, sitePages);
10699
+ const pageSections = getSectionsForPath(sectionsByPath, pageRoute);
10700
+ const section = sectionId ? pageSections.find((s) => s.id === sectionId) : void 0;
10701
+ label = section?.label ?? page.title;
10702
+ href = trimmed;
10703
+ }
10704
+ const afterAnchor = addNavAfterAnchorRef.current;
10705
+ const { anchor, hrefKey, labelKey, index, order } = insertNavbarItem(href, label, afterAnchor);
10706
+ applyLinkByKey(hrefKey, href);
10707
+ document.querySelectorAll(`[data-ohw-key="${labelKey}"]`).forEach((el) => {
10708
+ el.textContent = label;
10709
+ });
10710
+ const navCount = String(index + 1);
10711
+ const orderJson = JSON.stringify(order);
10712
+ editContentRef.current = {
10713
+ ...editContentRef.current,
10714
+ [hrefKey]: href,
10715
+ [labelKey]: label,
10716
+ [NAV_COUNT_KEY]: navCount,
10717
+ [NAV_ORDER_KEY]: orderJson
10718
+ };
10719
+ postToParent2({
10720
+ type: "ow:change",
10721
+ nodes: [
10722
+ { key: hrefKey, text: href },
10723
+ { key: labelKey, text: label },
10724
+ { key: NAV_COUNT_KEY, text: navCount },
10725
+ { key: NAV_ORDER_KEY, text: orderJson }
10726
+ ]
10727
+ });
10728
+ postToParent2({ type: "ow:toast", title: "Link added", toastType: "success" });
10729
+ addNavAfterAnchorRef.current = null;
10730
+ setLinkPopover(null);
10731
+ enforceLinkHrefs();
10732
+ requestAnimationFrame(() => {
10733
+ selectRef.current(anchor);
10734
+ });
10735
+ return;
10736
+ }
10737
+ const { key } = session;
8310
10738
  applyLinkByKey(key, target);
8311
- postToParent({ type: "ow:change", nodes: [{ key, text: target }] });
10739
+ postToParent2({ type: "ow:change", nodes: [{ key, text: target }] });
8312
10740
  setLinkPopover(null);
8313
10741
  },
8314
- [linkPopover, postToParent]
10742
+ [postToParent2, sitePages, sectionsByPath]
8315
10743
  );
8316
- const showEditLink = toolbarShowEditLink;
8317
- const currentSections = sectionsByPath[pathname] ?? [];
8318
- linkPopoverOpenRef.current = linkPopover !== null;
8319
- const handleMediaReplace = useCallback2(
10744
+ const handleMediaReplace = useCallback4(
10745
+ (key) => {
10746
+ postToParent2({ type: "ow:image-pick", key, elementType: mediaHover?.elementType ?? "image" });
10747
+ },
10748
+ [postToParent2, mediaHover?.elementType]
10749
+ );
10750
+ const handleEditCarousel = useCallback4(
8320
10751
  (key) => {
8321
- postToParent({ type: "ow:image-pick", key, elementType: mediaHover?.elementType ?? "image" });
10752
+ postToParent2({ type: "ow:carousel-open", key, images: readCarouselValue(key) });
8322
10753
  },
8323
- [postToParent, mediaHover?.elementType]
10754
+ [postToParent2]
8324
10755
  );
8325
- const handleMediaFadeOutComplete = useCallback2((key) => {
10756
+ const handleMediaFadeOutComplete = useCallback4((key) => {
8326
10757
  setUploadingRects((prev) => {
8327
10758
  if (!(key in prev)) return prev;
8328
10759
  const next = { ...prev };
@@ -8330,7 +10761,7 @@ function OhhwellsBridge() {
8330
10761
  return next;
8331
10762
  });
8332
10763
  }, []);
8333
- const handleVideoSettingsChange = useCallback2(
10764
+ const handleVideoSettingsChange = useCallback4(
8334
10765
  (key, settings) => {
8335
10766
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
8336
10767
  const video = getVideoEl(el);
@@ -8338,7 +10769,7 @@ function OhhwellsBridge() {
8338
10769
  if (typeof settings.autoplay === "boolean") video.autoplay = settings.autoplay;
8339
10770
  if (typeof settings.muted === "boolean") video.muted = settings.muted;
8340
10771
  syncVideoPlayback(video);
8341
- postToParent({
10772
+ postToParent2({
8342
10773
  type: "ow:change",
8343
10774
  nodes: [
8344
10775
  { key: `${key}${VIDEO_AUTOPLAY_SUFFIX}`, text: String(video.autoplay) },
@@ -8350,12 +10781,15 @@ function OhhwellsBridge() {
8350
10781
  );
8351
10782
  });
8352
10783
  },
8353
- [postToParent]
10784
+ [postToParent2]
8354
10785
  );
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(
10786
+ const showEditLink = toolbarShowEditLink;
10787
+ const currentSections = sectionsByPath[pathname] ?? [];
10788
+ linkPopoverOpenRef.current = linkPopover !== null;
10789
+ return bridgeRoot ? createPortal2(
10790
+ /* @__PURE__ */ jsxs14(Fragment5, { children: [
10791
+ /* @__PURE__ */ jsx25("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
10792
+ Object.entries(uploadingRects).map(([key, { rect, fadingOut }]) => /* @__PURE__ */ jsx25(
8359
10793
  MediaOverlay,
8360
10794
  {
8361
10795
  hover: { key, rect, elementType: "image", isDragOver: false, hasTextOverlap: false },
@@ -8366,7 +10800,7 @@ function OhhwellsBridge() {
8366
10800
  },
8367
10801
  `uploading-${key}`
8368
10802
  )),
8369
- mediaHover && !(mediaHover.key in uploadingRects) && /* @__PURE__ */ jsx22(
10803
+ mediaHover && !(mediaHover.key in uploadingRects) && /* @__PURE__ */ jsx25(
8370
10804
  MediaOverlay,
8371
10805
  {
8372
10806
  hover: mediaHover,
@@ -8375,53 +10809,33 @@ function OhhwellsBridge() {
8375
10809
  onVideoSettingsChange: handleVideoSettingsChange
8376
10810
  }
8377
10811
  ),
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,
10812
+ carouselHover && /* @__PURE__ */ jsx25(CarouselOverlay, { hover: carouselHover, onEdit: handleEditCarousel }),
10813
+ siblingHintRect && !isItemDragging && siblingHintRects.length === 0 && /* @__PURE__ */ jsx25(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
10814
+ siblingHintRects.map((rect, i) => /* @__PURE__ */ jsx25(ItemInteractionLayer, { rect, state: "sibling-hint" }, `sibling-hint-${i}`)),
10815
+ isItemDragging && draggedItemRect && selectedElRef.current !== footerDragRef.current?.draggedEl && /* @__PURE__ */ jsx25(ItemInteractionLayer, { rect: draggedItemRect, state: "dragging" }),
10816
+ isItemDragging && footerDropSlots.map((slot, i) => /* @__PURE__ */ jsx25(
10817
+ "div",
8382
10818
  {
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
- )
10819
+ className: "pointer-events-none fixed z-2147483646",
10820
+ style: {
10821
+ left: slot.left,
10822
+ top: slot.top,
10823
+ width: slot.width,
10824
+ height: slot.height
10825
+ },
10826
+ children: /* @__PURE__ */ jsx25(DropIndicator, { direction: slot.direction, state: activeFooterDropIndex === i ? "dragActive" : "dragIdle", className: slot.direction === "horizontal" ? "!h-full !w-full" : "!h-full !w-full" })
10827
+ },
10828
+ `footer-drop-${slot.direction}-${slot.columnIndex}-${slot.insertIndex}-${i}`
10829
+ )),
10830
+ hoveredNavContainerRect && toolbarVariant !== "select-frame" && !linkPopover && !isItemDragging && !siblingHintRect && siblingHintRects.length === 0 && /* @__PURE__ */ jsx25(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
10831
+ toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && /* @__PURE__ */ jsx25(NavbarContainerChrome, { rect: toolbarRect, onAdd: handleAddTopLevelNavItem }),
10832
+ hoveredItemRect && !linkPopover && !hoveredNavContainerRect && !siblingHintRect && siblingHintRects.length === 0 && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ jsx25(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
10833
+ 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 }),
10834
+ toolbarRect && toolbarVariant === "rich-text" && /* @__PURE__ */ jsxs14(Fragment5, { children: [
10835
+ /* @__PURE__ */ jsx25(EditGlowChrome, { rect: toolbarRect, elRef: glowElRef, reorderHrefKey, dragDisabled: reorderDragDisabled }),
10836
+ /* @__PURE__ */ jsx25(FloatingToolbar, { rect: toolbarRect, parentScroll: parentScrollRef.current, elRef: toolbarElRef, onCommand: handleCommand, activeCommands, showEditLink, onEditLink: openLinkPopoverForActive })
8423
10837
  ] }),
8424
- maxBadge && /* @__PURE__ */ jsxs12(
10838
+ maxBadge && /* @__PURE__ */ jsxs14(
8425
10839
  "div",
8426
10840
  {
8427
10841
  "data-ohw-max-badge": "",
@@ -8447,56 +10861,29 @@ function OhhwellsBridge() {
8447
10861
  ]
8448
10862
  }
8449
10863
  ),
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
- );
10864
+ toggleState && !linkPopover && /* @__PURE__ */ jsx25(StateToggle, { rect: toggleState.rect, activeState: toggleState.activeState, states: toggleState.states, onStateChange: handleStateChange }),
10865
+ 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: [
10866
+ /* @__PURE__ */ jsx25("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
10867
+ /* @__PURE__ */ jsx25(
10868
+ Badge,
10869
+ {
10870
+ 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",
10871
+ onClick: () => {
10872
+ window.parent.postMessage(
10873
+ {
10874
+ type: "ow:add-section",
10875
+ insertAfter: sectionGap.insertAfter,
10876
+ insertBefore: sectionGap.insertBefore
8476
10877
  },
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
10878
+ "*"
10879
+ );
10880
+ },
10881
+ children: "Add Section"
10882
+ }
10883
+ ),
10884
+ /* @__PURE__ */ jsx25("div", { className: "flex-1 bg-primary", style: { height: 3 } })
10885
+ ] }),
10886
+ 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
10887
  ] }),
8501
10888
  bridgeRoot
8502
10889
  ) : null;
@@ -8506,6 +10893,7 @@ export {
8506
10893
  CustomToolbarButton,
8507
10894
  CustomToolbarDivider,
8508
10895
  DragHandle,
10896
+ DropIndicator,
8509
10897
  ItemActionToolbar,
8510
10898
  ItemInteractionLayer,
8511
10899
  LinkEditorPanel,
@@ -8520,12 +10908,14 @@ export {
8520
10908
  TooltipProvider,
8521
10909
  TooltipTrigger,
8522
10910
  buildTarget,
10911
+ dropIndicatorVariants,
8523
10912
  filterAvailablePages,
8524
10913
  getEditModeInitialState,
8525
10914
  isEditSessionActive,
8526
10915
  isValidUrl,
8527
10916
  parseTarget,
8528
10917
  toggleVariants,
10918
+ useOhwCarousel,
8529
10919
  validateUrlInput
8530
10920
  };
8531
10921
  //# sourceMappingURL=index.js.map