@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.cjs CHANGED
@@ -35,6 +35,7 @@ __export(index_exports, {
35
35
  CustomToolbarButton: () => CustomToolbarButton,
36
36
  CustomToolbarDivider: () => CustomToolbarDivider,
37
37
  DragHandle: () => DragHandle,
38
+ DropIndicator: () => DropIndicator,
38
39
  ItemActionToolbar: () => ItemActionToolbar,
39
40
  ItemInteractionLayer: () => ItemInteractionLayer,
40
41
  LinkEditorPanel: () => LinkEditorPanel,
@@ -49,26 +50,31 @@ __export(index_exports, {
49
50
  TooltipProvider: () => TooltipProvider,
50
51
  TooltipTrigger: () => TooltipTrigger,
51
52
  buildTarget: () => buildTarget,
53
+ dropIndicatorVariants: () => dropIndicatorVariants,
52
54
  filterAvailablePages: () => filterAvailablePages,
53
55
  getEditModeInitialState: () => getEditModeInitialState,
54
56
  isEditSessionActive: () => isEditSessionActive,
55
57
  isValidUrl: () => isValidUrl,
56
58
  parseTarget: () => parseTarget,
57
59
  toggleVariants: () => toggleVariants,
60
+ useOhwCarousel: () => useOhwCarousel,
58
61
  validateUrlInput: () => validateUrlInput
59
62
  });
60
63
  module.exports = __toCommonJS(index_exports);
61
64
 
62
65
  // src/OhhwellsBridge.tsx
63
- var import_react6 = __toESM(require("react"), 1);
66
+ var import_react9 = __toESM(require("react"), 1);
64
67
  var import_client = require("react-dom/client");
65
- var import_react_dom = require("react-dom");
68
+ var import_react_dom2 = require("react-dom");
66
69
 
67
70
  // src/linkHrefStore.ts
68
71
  var linkHrefStore = /* @__PURE__ */ new Map();
69
72
  function setStoredLinkHref(key, href) {
70
73
  linkHrefStore.set(key, href);
71
74
  }
75
+ function getStoredLinkHref(key) {
76
+ return linkHrefStore.get(key);
77
+ }
72
78
  function enforceLinkHrefs() {
73
79
  document.querySelectorAll("[data-ohw-href-key]").forEach((el) => {
74
80
  const key = el.getAttribute("data-ohw-href-key") ?? "";
@@ -110,6 +116,7 @@ var import_react3 = require("react");
110
116
  var import_react2 = require("react");
111
117
  var import_radix_ui = require("radix-ui");
112
118
  var import_jsx_runtime = require("react/jsx-runtime");
119
+ var isValidEmail = (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
113
120
  function Spinner() {
114
121
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
115
122
  "svg",
@@ -143,12 +150,17 @@ function EmailCaptureModal({ title, subtitle, onSubmit, onClose }) {
143
150
  const [error, setError] = (0, import_react2.useState)(null);
144
151
  const isBook = title === "Confirm your spot";
145
152
  const handleSubmit = async () => {
146
- if (!email.trim()) return;
153
+ const trimmed = email.trim();
154
+ if (!trimmed) return;
155
+ if (!isValidEmail(trimmed)) {
156
+ setError("Please enter a valid email address.");
157
+ return;
158
+ }
147
159
  setLoading(true);
148
160
  setError(null);
149
161
  try {
150
- await onSubmit(email.trim());
151
- setSubmittedEmail(email.trim());
162
+ await onSubmit(trimmed);
163
+ setSubmittedEmail(trimmed);
152
164
  setSuccess(true);
153
165
  } catch (e) {
154
166
  setError(e instanceof Error ? e.message : "Something went wrong. Please try again.");
@@ -223,7 +235,10 @@ function EmailCaptureModal({ title, subtitle, onSubmit, onClose }) {
223
235
  {
224
236
  type: "email",
225
237
  value: email,
226
- onChange: (e) => setEmail(e.target.value),
238
+ onChange: (e) => {
239
+ setEmail(e.target.value);
240
+ if (error) setError(null);
241
+ },
227
242
  onKeyDown: handleKeyDown,
228
243
  placeholder: "hello@gmail.com",
229
244
  autoFocus: true,
@@ -280,12 +295,20 @@ function formatClassTime(cls) {
280
295
  const em = endTotal % 60;
281
296
  return `${h}:${cls.startTime.minutes}-${eh}:${String(em).padStart(2, "0")}`;
282
297
  }
298
+ function formatBookingLabel(cls) {
299
+ const price = cls.price;
300
+ const isFree = price === null || price === void 0 || `${price}` === "" || `${price}` === "0";
301
+ return isFree ? "Book for free" : `Book for ${cls.currency ?? ""} ${price}`.trim();
302
+ }
283
303
  function getBookingsOnDate(cls, date) {
284
304
  if (!cls.bookings?.length) return 0;
285
- const ds = date.toISOString().split("T")[0];
305
+ const target = new Date(date);
306
+ target.setHours(0, 0, 0, 0);
286
307
  return cls.bookings.filter((b) => {
287
308
  try {
288
- return new Date(b.classDate).toISOString().split("T")[0] === ds;
309
+ const bookingDate = new Date(b.classDate);
310
+ bookingDate.setHours(0, 0, 0, 0);
311
+ return bookingDate.getTime() === target.getTime();
289
312
  } catch {
290
313
  return false;
291
314
  }
@@ -550,7 +573,7 @@ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal
550
573
  }
551
574
  )
552
575
  ] }),
553
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "sm:hidden flex flex-col gap-px", children: isFull ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
576
+ cls.showAvailability !== false && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "sm:hidden flex flex-col gap-px", children: isFull ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
554
577
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
555
578
  available,
556
579
  "/",
@@ -562,9 +585,10 @@ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal
562
585
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-1 flex-col gap-2 min-w-0 sm:contents", children: [
563
586
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex-1 flex flex-col gap-2 min-w-0", children: [
564
587
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block truncate", children: cls.name }),
565
- cls.hostName && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-normal text-(--color-dark,#200C02) opacity-80 block truncate", children: cls.hostName })
588
+ cls.hostName && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-normal text-(--color-dark,#200C02) opacity-80 block truncate", children: cls.hostName }),
589
+ cls.description && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm font-normal text-(--color-dark,#200C02) opacity-70 block truncate", children: cls.description })
566
590
  ] }),
567
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hidden sm:flex w-14 shrink-0 flex-col gap-px", children: isFull ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
591
+ cls.showAvailability !== false && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hidden sm:flex w-14 shrink-0 flex-col gap-px", children: isFull ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
568
592
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
569
593
  available,
570
594
  "/",
@@ -589,7 +613,7 @@ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal
589
613
  if (!cls.id) return;
590
614
  onOpenModal?.(isFull ? "waitlist" : "book", cls.id, selectedDate);
591
615
  },
592
- children: isFull ? "Join Waitlist" : "Book Now"
616
+ children: isFull ? "Join Waitlist" : formatBookingLabel(cls)
593
617
  }
594
618
  )
595
619
  ] })
@@ -631,6 +655,17 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
631
655
  const [isHovered, setIsHovered] = (0, import_react3.useState)(false);
632
656
  const [modalState, setModalState] = (0, import_react3.useState)(null);
633
657
  const switchScheduleIdRef = (0, import_react3.useRef)(null);
658
+ const liveScheduleIdRef = (0, import_react3.useRef)(null);
659
+ const refetchLiveSchedule = (0, import_react3.useCallback)(async () => {
660
+ const id = liveScheduleIdRef.current;
661
+ if (!id) return;
662
+ try {
663
+ const res = await fetch(`${API_URL}/api/schedule/id/${id}`);
664
+ const data = await res.json();
665
+ if (data?.id) setSchedule(data);
666
+ } catch {
667
+ }
668
+ }, []);
634
669
  const dates = (0, import_react3.useMemo)(() => {
635
670
  const today = /* @__PURE__ */ new Date();
636
671
  today.setHours(0, 0, 0, 0);
@@ -650,6 +685,18 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
650
685
  }
651
686
  }
652
687
  }, [schedule, dates]);
688
+ (0, import_react3.useEffect)(() => {
689
+ if (typeof window === "undefined") return;
690
+ const onVisible = () => {
691
+ if (!document.hidden) void refetchLiveSchedule();
692
+ };
693
+ window.addEventListener("focus", refetchLiveSchedule);
694
+ document.addEventListener("visibilitychange", onVisible);
695
+ return () => {
696
+ window.removeEventListener("focus", refetchLiveSchedule);
697
+ document.removeEventListener("visibilitychange", onVisible);
698
+ };
699
+ }, [refetchLiveSchedule]);
653
700
  (0, import_react3.useEffect)(() => {
654
701
  if (typeof window === "undefined") return;
655
702
  const isInEditor = window.self !== window.top;
@@ -711,7 +758,7 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
711
758
  setLoading(false);
712
759
  return;
713
760
  }
714
- ;
761
+ liveScheduleIdRef.current = effectiveId;
715
762
  (async () => {
716
763
  try {
717
764
  const res = await fetch(`${API_URL}/api/schedule/id/${effectiveId}`);
@@ -768,18 +815,14 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
768
815
  const data = await res.json().catch(() => ({}));
769
816
  throw new Error(data?.message ?? "Something went wrong. Please try again.");
770
817
  }
818
+ await refetchLiveSchedule();
771
819
  };
772
820
  const handleReplaceSchedule = () => {
773
821
  setIsHovered(false);
774
- if (schedule) {
775
- window.parent.postMessage({
776
- type: "ow:schedule-connected",
777
- schedule: { id: schedule.id, name: schedule.name },
778
- insertAfter
779
- }, "*");
780
- } else {
781
- window.parent.postMessage({ type: "ow:scheduling-not-connected", insertAfter }, "*");
782
- }
822
+ window.parent.postMessage(
823
+ { type: "ow:scheduling-not-connected", insertAfter, currentScheduleId: schedule?.id },
824
+ "*"
825
+ );
783
826
  };
784
827
  if (!inEditor && !loading && !schedule) return null;
785
828
  const sectionId = `scheduling-${insertAfter}`;
@@ -822,7 +865,19 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
822
865
  ),
823
866
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "max-w-[1280px] mx-auto flex flex-col items-center gap-16", children: [
824
867
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col items-center gap-4", children: [
825
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h2", { className: "font-display text-4xl sm:text-5xl font-bold text-center m-0 text-(--color-dark,#200C02)", children: schedule?.name ?? "Book an appointment" }),
868
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
869
+ "h2",
870
+ {
871
+ className: "font-display text-center m-0 text-(--color-dark,#200C02)",
872
+ style: {
873
+ fontSize: "var(--fs-section-h2, clamp(40px, 4.4vw, 64px))",
874
+ fontWeight: "var(--font-weight-heading, 400)",
875
+ lineHeight: 1.05,
876
+ letterSpacing: "-0.02em"
877
+ },
878
+ children: schedule?.name ?? "Book an appointment"
879
+ }
880
+ ),
826
881
  schedule?.description && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "font-body text-body text-center m-0 text-(--color-accent,#A89B83)", children: schedule.description })
827
882
  ] }),
828
883
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "w-full flex flex-col items-end gap-3", children: [
@@ -4408,6 +4463,7 @@ function ItemActionToolbar({
4408
4463
  onEditLink,
4409
4464
  onAddItem,
4410
4465
  onMore,
4466
+ editLinkDisabled = false,
4411
4467
  addItemDisabled = true,
4412
4468
  moreDisabled = true,
4413
4469
  tooltipSide = "bottom"
@@ -4416,10 +4472,12 @@ function ItemActionToolbar({
4416
4472
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4417
4473
  ToolbarActionTooltip,
4418
4474
  {
4419
- label: "Add link",
4475
+ label: "Edit link",
4420
4476
  side: tooltipSide,
4477
+ disabled: editLinkDisabled,
4421
4478
  buttonProps: {
4422
4479
  onMouseDown: (e) => {
4480
+ if (editLinkDisabled) return;
4423
4481
  e.preventDefault();
4424
4482
  e.stopPropagation();
4425
4483
  onEditLink?.();
@@ -4497,7 +4555,7 @@ function getChromeStyle(state) {
4497
4555
  case "dragging":
4498
4556
  return {
4499
4557
  border: `2px solid ${PRIMARY}`,
4500
- boxShadow: DRAG_SHADOW
4558
+ boxShadow: `${FOCUS_RING}, ${DRAG_SHADOW}`
4501
4559
  };
4502
4560
  default:
4503
4561
  return {};
@@ -4513,6 +4571,8 @@ function ItemInteractionLayer({
4513
4571
  dragHandleLabel = "Reorder item",
4514
4572
  onDragHandleDragStart,
4515
4573
  onDragHandleDragEnd,
4574
+ onItemPointerDown,
4575
+ onItemClick,
4516
4576
  chromeGap = 6,
4517
4577
  className
4518
4578
  }) {
@@ -4520,7 +4580,8 @@ function ItemInteractionLayer({
4520
4580
  const isActive = state === "active-top" || state === "active-bottom";
4521
4581
  const isDragging = state === "dragging";
4522
4582
  const showToolbar = isActive && toolbar;
4523
- const showDragHandle = isActive && showHandle && !isDragging;
4583
+ const showDragHandle = (isActive || isDragging) && showHandle;
4584
+ const itemDragEnabled = isActive && showHandle && !isDragging && !dragDisabled;
4524
4585
  return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
4525
4586
  "div",
4526
4587
  {
@@ -4545,11 +4606,28 @@ function ItemInteractionLayer({
4545
4606
  style: getChromeStyle(state)
4546
4607
  }
4547
4608
  ),
4609
+ itemDragEnabled && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4610
+ "div",
4611
+ {
4612
+ "data-ohw-item-drag-surface": "",
4613
+ className: "pointer-events-auto absolute inset-0 cursor-grab rounded-lg active:cursor-grabbing",
4614
+ onPointerDown: (e) => {
4615
+ if (e.button !== 0) return;
4616
+ onItemPointerDown?.(e);
4617
+ },
4618
+ onClick: (e) => {
4619
+ e.preventDefault();
4620
+ e.stopPropagation();
4621
+ onItemClick?.(e.clientX, e.clientY);
4622
+ }
4623
+ }
4624
+ ),
4548
4625
  showDragHandle && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4549
4626
  "div",
4550
4627
  {
4551
4628
  "data-ohw-drag-handle-container": "",
4552
- className: "pointer-events-auto absolute left-0 top-1/2 -translate-x-[calc(100%+7px)] -translate-y-1/2",
4629
+ className: "pointer-events-auto absolute left-0 top-1/2 z-10 -translate-x-[calc(100%+7px)] -translate-y-1/2",
4630
+ style: isDragging ? { visibility: "hidden" } : void 0,
4553
4631
  children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4554
4632
  DragHandle,
4555
4633
  {
@@ -4772,15 +4850,12 @@ function MediaOverlay({
4772
4850
  className: "flex items-center justify-center cursor-pointer",
4773
4851
  style: {
4774
4852
  ...box,
4775
- // When text overlays this media, let clicks fall THROUGH to the text underneath. In the
4776
- // parent this needed a whole `ow:click-at` round-trip to re-hit-test inside the iframe;
4777
- // in-document, pointer-events does it natively. The button below opts back in, so
4778
- // Replace still works.
4779
- pointerEvents: hover.hasTextOverlap ? "none" : "auto",
4853
+ // Keep the full-size visual overlay click-through so carousel controls and editable
4854
+ // content underneath remain interactive. The Replace button opts back in below.
4855
+ pointerEvents: "none",
4780
4856
  boxShadow: "inset 0 0 0 1.5px var(--color-primary)",
4781
4857
  background: "color-mix(in srgb, var(--color-primary) 20%, transparent)"
4782
4858
  },
4783
- onClick: () => onReplace(hover.key),
4784
4859
  children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
4785
4860
  Button,
4786
4861
  {
@@ -4805,9 +4880,64 @@ function MediaOverlay({
4805
4880
  ] });
4806
4881
  }
4807
4882
 
4883
+ // src/ui/CarouselOverlay.tsx
4884
+ var import_lucide_react4 = require("lucide-react");
4885
+ var import_jsx_runtime12 = require("react/jsx-runtime");
4886
+ var OVERLAY_BUTTON_STYLE2 = {
4887
+ pointerEvents: "auto",
4888
+ fontFamily: "Inter, sans-serif",
4889
+ fontSize: 12,
4890
+ color: "#000"
4891
+ };
4892
+ function CarouselOverlay({
4893
+ hover,
4894
+ onEdit
4895
+ }) {
4896
+ const { rect } = hover;
4897
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
4898
+ "div",
4899
+ {
4900
+ "data-ohw-bridge": "",
4901
+ "data-ohw-carousel-overlay": "",
4902
+ className: "flex cursor-pointer items-center justify-center",
4903
+ style: {
4904
+ position: "fixed",
4905
+ top: rect.top,
4906
+ left: rect.left,
4907
+ width: rect.width,
4908
+ height: rect.height,
4909
+ zIndex: 2147483646,
4910
+ pointerEvents: "auto",
4911
+ boxShadow: "inset 0 0 0 1.5px var(--color-primary)",
4912
+ background: "color-mix(in srgb, var(--color-primary) 20%, transparent)"
4913
+ },
4914
+ onClick: () => onEdit(hover.key),
4915
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
4916
+ Button,
4917
+ {
4918
+ "data-ohw-carousel-overlay": "",
4919
+ variant: "outline",
4920
+ size: "sm",
4921
+ className: "cursor-pointer gap-1.5 hover:bg-background",
4922
+ style: OVERLAY_BUTTON_STYLE2,
4923
+ onMouseDown: (e) => e.preventDefault(),
4924
+ onClick: (e) => {
4925
+ e.stopPropagation();
4926
+ onEdit(hover.key);
4927
+ },
4928
+ children: [
4929
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react4.GalleryHorizontal, { size: 14 }),
4930
+ "Edit gallery"
4931
+ ]
4932
+ }
4933
+ )
4934
+ }
4935
+ );
4936
+ }
4937
+
4808
4938
  // src/OhhwellsBridge.tsx
4809
- var import_react_dom2 = require("react-dom");
4810
- var import_navigation = require("next/navigation");
4939
+ var import_react_dom3 = require("react-dom");
4940
+ var import_navigation2 = require("next/navigation");
4811
4941
 
4812
4942
  // src/lib/session-search.ts
4813
4943
  var STORAGE_KEY = "ohw-preserved-search";
@@ -4837,6 +4967,7 @@ function parseSectionsFromRoot(root) {
4837
4967
  for (const el of root.querySelectorAll("[data-ohw-section]")) {
4838
4968
  const id = el.getAttribute("data-ohw-section") ?? "";
4839
4969
  if (!id || seen.has(id)) continue;
4970
+ if (el.parentElement?.closest("[data-ohw-section]")) continue;
4840
4971
  seen.add(id);
4841
4972
  const label = el.getAttribute("data-ohw-section-label") ?? titleCaseSectionId(id);
4842
4973
  sections.push({ id, label });
@@ -5083,54 +5214,22 @@ function scrollToHashSectionWhenReady(behavior = "smooth") {
5083
5214
  tick();
5084
5215
  }
5085
5216
 
5217
+ // src/ui/link-modal/LinkPopover.tsx
5218
+ var import_react7 = require("react");
5219
+
5086
5220
  // src/ui/dialog.tsx
5087
5221
  var React7 = __toESM(require("react"), 1);
5088
5222
  var import_radix_ui5 = require("radix-ui");
5089
-
5090
- // src/ui/icons.tsx
5091
- var import_jsx_runtime12 = require("react/jsx-runtime");
5092
- function IconX({ className, ...props }) {
5093
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
5094
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("path", { d: "M18 6 6 18" }),
5095
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("path", { d: "m6 6 12 12" })
5096
- ] });
5097
- }
5098
- function IconChevronDown({ className, ...props }) {
5099
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("path", { d: "m6 9 6 6 6-6" }) });
5100
- }
5101
- function IconFile({ className, ...props }) {
5102
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
5103
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }),
5104
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("path", { d: "M14 2v4a2 2 0 0 0 2 2h4" })
5105
- ] });
5106
- }
5107
- function IconInfo({ className, ...props }) {
5108
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
5109
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
5110
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("path", { d: "M12 16v-4" }),
5111
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("path", { d: "M12 8h.01" })
5112
- ] });
5113
- }
5114
- function IconArrowRight({ className, ...props }) {
5115
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
5116
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("path", { d: "M5 12h14" }),
5117
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("path", { d: "m12 5 7 7-7 7" })
5118
- ] });
5119
- }
5120
- function IconSection({ className, ...props }) {
5121
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
5122
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("rect", { width: "18", height: "7", x: "3", y: "3", rx: "1" }),
5123
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("rect", { width: "9", height: "7", x: "3", y: "14", rx: "1" }),
5124
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("rect", { width: "5", height: "7", x: "16", y: "14", rx: "1" })
5125
- ] });
5126
- }
5127
-
5128
- // src/ui/dialog.tsx
5223
+ var import_lucide_react5 = require("lucide-react");
5129
5224
  var import_jsx_runtime13 = require("react/jsx-runtime");
5130
- function Dialog2({ ...props }) {
5225
+ function Dialog2({
5226
+ ...props
5227
+ }) {
5131
5228
  return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_radix_ui5.Dialog.Root, { "data-slot": "dialog", ...props });
5132
5229
  }
5133
- function DialogPortal({ ...props }) {
5230
+ function DialogPortal({
5231
+ ...props
5232
+ }) {
5134
5233
  return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_radix_ui5.Dialog.Portal, { ...props });
5135
5234
  }
5136
5235
  function DialogOverlay({
@@ -5147,52 +5246,69 @@ function DialogOverlay({
5147
5246
  }
5148
5247
  );
5149
5248
  }
5150
- var DialogContent = React7.forwardRef(({ className, children, showCloseButton = true, container, ...props }, ref) => {
5151
- const positionMode = container ? "absolute" : "fixed";
5152
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(DialogPortal, { container: container ?? void 0, children: [
5153
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(DialogOverlay, { className: cn(positionMode, "inset-0") }),
5154
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
5155
- import_radix_ui5.Dialog.Content,
5156
- {
5157
- ref,
5158
- "data-slot": "dialog-content",
5159
- className: cn(
5160
- positionMode,
5161
- "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",
5162
- "rounded-xl border border-border bg-background font-sans shadow-lg outline-none",
5163
- container ? "max-h-[calc(100%-32px)] max-w-[calc(100%-16px)]" : "max-h-[calc(100vh-32px)] max-w-[calc(100vw-16px)]",
5164
- className
5165
- ),
5166
- onMouseDown: (e) => e.stopPropagation(),
5167
- ...props,
5168
- children: [
5169
- children,
5170
- showCloseButton ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5171
- import_radix_ui5.Dialog.Close,
5172
- {
5173
- type: "button",
5174
- className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
5175
- "aria-label": "Close",
5176
- children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(IconX, { "aria-hidden": true })
5177
- }
5178
- ) : null
5179
- ]
5180
- }
5181
- )
5182
- ] });
5183
- });
5249
+ var DialogContent = React7.forwardRef(
5250
+ ({ className, children, showCloseButton = true, container, ...props }, ref) => {
5251
+ const positionMode = container ? "absolute" : "fixed";
5252
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(DialogPortal, { container: container ?? void 0, children: [
5253
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(DialogOverlay, { className: cn(positionMode, "inset-0") }),
5254
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
5255
+ import_radix_ui5.Dialog.Content,
5256
+ {
5257
+ ref,
5258
+ "data-slot": "dialog-content",
5259
+ className: cn(
5260
+ positionMode,
5261
+ "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",
5262
+ "rounded-xl border border-border bg-background font-sans shadow-lg outline-none",
5263
+ container ? "max-h-[calc(100%-32px)]" : "max-h-[calc(100vh-32px)]",
5264
+ className
5265
+ ),
5266
+ onMouseDown: (e) => e.stopPropagation(),
5267
+ ...props,
5268
+ children: [
5269
+ children,
5270
+ showCloseButton ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5271
+ import_radix_ui5.Dialog.Close,
5272
+ {
5273
+ type: "button",
5274
+ className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
5275
+ "aria-label": "Close",
5276
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react5.X, { size: 16, "aria-hidden": true })
5277
+ }
5278
+ ) : null
5279
+ ]
5280
+ }
5281
+ )
5282
+ ] });
5283
+ }
5284
+ );
5184
5285
  DialogContent.displayName = import_radix_ui5.Dialog.Content.displayName;
5185
- function DialogHeader({ className, ...props }) {
5286
+ function DialogHeader({
5287
+ className,
5288
+ ...props
5289
+ }) {
5186
5290
  return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: cn("flex flex-col gap-1.5", className), ...props });
5187
5291
  }
5188
- function DialogFooter({ className, ...props }) {
5189
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: cn("flex items-center justify-end gap-2", className), ...props });
5292
+ function DialogFooter({
5293
+ className,
5294
+ ...props
5295
+ }) {
5296
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5297
+ "div",
5298
+ {
5299
+ className: cn("flex items-center justify-end gap-2", className),
5300
+ ...props
5301
+ }
5302
+ );
5190
5303
  }
5191
5304
  var DialogTitle = React7.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5192
5305
  import_radix_ui5.Dialog.Title,
5193
5306
  {
5194
5307
  ref,
5195
- className: cn("text-lg font-semibold leading-none tracking-tight text-card-foreground", className),
5308
+ className: cn(
5309
+ "text-lg font-semibold leading-none tracking-tight text-card-foreground",
5310
+ className
5311
+ ),
5196
5312
  ...props
5197
5313
  }
5198
5314
  ));
@@ -5208,19 +5324,40 @@ var DialogDescription = React7.forwardRef(({ className, ...props }, ref) => /* @
5208
5324
  DialogDescription.displayName = import_radix_ui5.Dialog.Description.displayName;
5209
5325
  var DialogClose = import_radix_ui5.Dialog.Close;
5210
5326
 
5327
+ // src/ui/link-modal/LinkEditorPanel.tsx
5328
+ var import_lucide_react9 = require("lucide-react");
5329
+
5211
5330
  // src/ui/link-modal/DestinationBreadcrumb.tsx
5331
+ var import_lucide_react6 = require("lucide-react");
5212
5332
  var import_jsx_runtime14 = require("react/jsx-runtime");
5213
- function DestinationBreadcrumb({ pageTitle, sectionLabel }) {
5333
+ function DestinationBreadcrumb({
5334
+ pageTitle,
5335
+ sectionLabel
5336
+ }) {
5214
5337
  return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex w-full flex-col gap-2", children: [
5215
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { className: "text-sm font-medium text-foreground", children: "Destination" }),
5338
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { className: "text-sm font-medium! text-foreground m-0", children: "Destination" }),
5216
5339
  /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex items-center gap-3", children: [
5217
5340
  /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex items-center gap-2", children: [
5218
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(IconFile, { className: "shrink-0 text-foreground", "aria-hidden": true }),
5219
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "text-sm font-medium leading-none text-foreground", children: pageTitle })
5341
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_lucide_react6.File, { size: 16, className: "shrink-0 text-foreground", "aria-hidden": true }),
5342
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "text-sm font-medium leading-none text-foreground!", children: pageTitle })
5220
5343
  ] }),
5221
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(IconArrowRight, { className: "shrink-0 text-muted-foreground", "aria-hidden": true }),
5344
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
5345
+ import_lucide_react6.ArrowRight,
5346
+ {
5347
+ size: 16,
5348
+ className: "shrink-0 text-muted-foreground",
5349
+ "aria-hidden": true
5350
+ }
5351
+ ),
5222
5352
  /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [
5223
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(IconSection, { className: "shrink-0 text-foreground", "aria-hidden": true }),
5353
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
5354
+ import_lucide_react6.GalleryVertical,
5355
+ {
5356
+ size: 16,
5357
+ className: "shrink-0 text-foreground",
5358
+ "aria-hidden": true
5359
+ }
5360
+ ),
5224
5361
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "truncate text-sm font-medium leading-none text-foreground", children: sectionLabel })
5225
5362
  ] })
5226
5363
  ] })
@@ -5228,8 +5365,13 @@ function DestinationBreadcrumb({ pageTitle, sectionLabel }) {
5228
5365
  }
5229
5366
 
5230
5367
  // src/ui/link-modal/SectionTreeItem.tsx
5368
+ var import_lucide_react7 = require("lucide-react");
5231
5369
  var import_jsx_runtime15 = require("react/jsx-runtime");
5232
- function SectionTreeItem({ section, onSelect, selected }) {
5370
+ function SectionTreeItem({
5371
+ section,
5372
+ onSelect,
5373
+ selected
5374
+ }) {
5233
5375
  const interactive = Boolean(onSelect);
5234
5376
  return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex h-9 w-full items-end pl-3", children: [
5235
5377
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
@@ -5257,22 +5399,20 @@ function SectionTreeItem({ section, onSelect, selected }) {
5257
5399
  interactive && selected && "border-primary"
5258
5400
  ),
5259
5401
  children: [
5260
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(IconSection, { className: "shrink-0 text-foreground", "aria-hidden": true }),
5402
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
5403
+ import_lucide_react7.GalleryVertical,
5404
+ {
5405
+ size: 16,
5406
+ className: "shrink-0 text-foreground",
5407
+ "aria-hidden": true
5408
+ }
5409
+ ),
5261
5410
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "truncate text-sm font-normal leading-5 text-foreground", children: section.label })
5262
5411
  ]
5263
5412
  }
5264
5413
  )
5265
5414
  ] });
5266
5415
  }
5267
- function SectionPickerList({ sections, onSelect }) {
5268
- if (sections.length === 0) {
5269
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: "text-sm text-muted-foreground", children: "No sections found on this page." });
5270
- }
5271
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex flex-col gap-1", children: [
5272
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: "text-sm text-muted-foreground", children: "Pick a section this link should scroll to." }),
5273
- sections.map((section) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(SectionTreeItem, { section, onSelect }, section.id))
5274
- ] });
5275
- }
5276
5416
 
5277
5417
  // src/ui/link-modal/UrlOrPageInput.tsx
5278
5418
  var import_react4 = require("react");
@@ -5314,8 +5454,11 @@ function Label({ className, ...props }) {
5314
5454
  }
5315
5455
 
5316
5456
  // src/ui/link-modal/UrlOrPageInput.tsx
5457
+ var import_lucide_react8 = require("lucide-react");
5317
5458
  var import_jsx_runtime18 = require("react/jsx-runtime");
5318
- function FieldChevron({ onClick }) {
5459
+ function FieldChevron({
5460
+ onClick
5461
+ }) {
5319
5462
  return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5320
5463
  "button",
5321
5464
  {
@@ -5324,7 +5467,7 @@ function FieldChevron({ onClick }) {
5324
5467
  onClick,
5325
5468
  "aria-label": "Open page list",
5326
5469
  tabIndex: -1,
5327
- children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(IconChevronDown, {})
5470
+ children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_lucide_react8.ChevronDown, { size: 16 })
5328
5471
  }
5329
5472
  );
5330
5473
  }
@@ -5342,7 +5485,29 @@ function UrlOrPageInput({
5342
5485
  }) {
5343
5486
  const inputId = (0, import_react4.useId)();
5344
5487
  const inputRef = (0, import_react4.useRef)(null);
5488
+ const rootRef = (0, import_react4.useRef)(null);
5345
5489
  const [isFocused, setIsFocused] = (0, import_react4.useState)(false);
5490
+ (0, import_react4.useEffect)(() => {
5491
+ if (!dropdownOpen) return;
5492
+ const isOutside = (e) => {
5493
+ const root = rootRef.current;
5494
+ if (!root) return true;
5495
+ const path = typeof e.composedPath === "function" ? e.composedPath() : [];
5496
+ if (path.includes(root)) return false;
5497
+ const target = e.target;
5498
+ return !(target instanceof Node && root.contains(target));
5499
+ };
5500
+ const closeIfOutside = (e) => {
5501
+ if (!isOutside(e)) return;
5502
+ onDropdownOpenChange(false);
5503
+ };
5504
+ document.addEventListener("pointerdown", closeIfOutside, true);
5505
+ document.addEventListener("mousedown", closeIfOutside, true);
5506
+ return () => {
5507
+ document.removeEventListener("pointerdown", closeIfOutside, true);
5508
+ document.removeEventListener("mousedown", closeIfOutside, true);
5509
+ };
5510
+ }, [dropdownOpen, onDropdownOpenChange]);
5346
5511
  const openDropdown = () => {
5347
5512
  if (readOnly || filteredPages.length === 0) return;
5348
5513
  onDropdownOpenChange(true);
@@ -5360,14 +5525,21 @@ function UrlOrPageInput({
5360
5525
  requestAnimationFrame(() => inputRef.current?.focus());
5361
5526
  };
5362
5527
  const fieldClassName = cn(
5363
- "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]",
5528
+ "data-ohw-link-field flex h-[36px] w-full items-center overflow-hidden rounded-md border bg-background pl-3 pr-3 py-2 outline-none transition-[border-color,box-shadow]",
5364
5529
  urlError ? "border-destructive shadow-[0_0_0_1px_var(--ohw-destructive)]" : isFocused ? "border-primary shadow-[0_0_0_1px_var(--ohw-primary)]" : "border-input"
5365
5530
  );
5366
5531
  return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex w-full flex-col gap-2 p-0", children: [
5367
5532
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Label, { htmlFor: inputId, className: cn(urlError && "text-destructive"), children: "Destination" }),
5368
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "relative w-full", children: [
5533
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { ref: rootRef, className: "relative w-full", children: [
5369
5534
  /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { "data-ohw-link-field": true, className: fieldClassName, children: [
5370
- selectedPage ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "flex shrink-0 items-center pr-2", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(IconFile, { className: "text-foreground", "aria-hidden": true }) }) : null,
5535
+ selectedPage ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "flex shrink-0 items-center pr-2", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5536
+ import_lucide_react8.File,
5537
+ {
5538
+ size: 16,
5539
+ className: "shrink-0 text-foreground",
5540
+ "aria-hidden": true
5541
+ }
5542
+ ) }) : null,
5371
5543
  readOnly ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "min-w-0 flex-1 truncate text-sm leading-5 text-foreground", children: selectedPage?.title ?? value }) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5372
5544
  Input,
5373
5545
  {
@@ -5379,7 +5551,14 @@ function UrlOrPageInput({
5379
5551
  setIsFocused(true);
5380
5552
  if (!selectedPage) openDropdown();
5381
5553
  },
5382
- onBlur: () => setIsFocused(false),
5554
+ onBlur: () => {
5555
+ setIsFocused(false);
5556
+ requestAnimationFrame(() => {
5557
+ if (!rootRef.current?.contains(document.activeElement)) {
5558
+ onDropdownOpenChange(false);
5559
+ }
5560
+ });
5561
+ },
5383
5562
  placeholder,
5384
5563
  className: cn(
5385
5564
  "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",
@@ -5395,7 +5574,7 @@ function UrlOrPageInput({
5395
5574
  onMouseDown: clearSelection,
5396
5575
  "aria-label": "Clear selected page",
5397
5576
  tabIndex: -1,
5398
- children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(IconX, { "aria-hidden": true })
5577
+ children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_lucide_react8.X, { size: 16, "aria-hidden": true })
5399
5578
  }
5400
5579
  ) : null,
5401
5580
  !readOnly ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(FieldChevron, { onClick: toggleDropdown }) : null
@@ -5404,7 +5583,7 @@ function UrlOrPageInput({
5404
5583
  "div",
5405
5584
  {
5406
5585
  "data-ohw-link-page-dropdown": "",
5407
- 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",
5586
+ className: "absolute left-0 right-0 top-[calc(100%+4px)] z-50 max-h-48 overflow-auto rounded-lg border border-border bg-popover py-1 shadow-lg",
5408
5587
  onMouseDown: (e) => e.preventDefault(),
5409
5588
  children: filteredPages.map((page) => /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
5410
5589
  "button",
@@ -5413,7 +5592,7 @@ function UrlOrPageInput({
5413
5592
  className: "flex h-9 w-full items-center gap-2 border-0 bg-transparent px-3 text-left text-sm leading-5 text-foreground outline-none hover:bg-muted",
5414
5593
  onClick: () => onPageSelect(page),
5415
5594
  children: [
5416
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(IconFile, { className: "shrink-0", "aria-hidden": true }),
5595
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_lucide_react8.File, { size: 16, className: "shrink-0", "aria-hidden": true }),
5417
5596
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "truncate", children: page.title })
5418
5597
  ]
5419
5598
  },
@@ -5426,42 +5605,470 @@ function UrlOrPageInput({
5426
5605
  ] });
5427
5606
  }
5428
5607
 
5429
- // src/ui/link-modal/useLinkModalState.ts
5608
+ // src/ui/link-modal/LinkEditorPanel.tsx
5609
+ var import_jsx_runtime19 = require("react/jsx-runtime");
5610
+ function LinkEditorPanel({ state, onClose }) {
5611
+ const isCancel = state.secondaryLabel === "Cancel" || state.secondaryLabel === "Back to sections";
5612
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_jsx_runtime19.Fragment, { children: [
5613
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(DialogClose, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5614
+ "button",
5615
+ {
5616
+ type: "button",
5617
+ className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50 h-7",
5618
+ "aria-label": "Close",
5619
+ onClick: onClose,
5620
+ children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react9.X, { size: 16, "aria-hidden": true })
5621
+ }
5622
+ ) }),
5623
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(DialogHeader, { className: "w-full gap-1.5 p-6 pr-12", children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(DialogTitle, { className: "m-0 w-full break-words", children: state.title }) }),
5624
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex w-full flex-col gap-3 px-6 pb-8 pt-1", children: [
5625
+ state.showBreadcrumb && state.selectedPage && state.selectedSection ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5626
+ DestinationBreadcrumb,
5627
+ {
5628
+ pageTitle: state.selectedPage.title,
5629
+ sectionLabel: state.selectedSection.label
5630
+ }
5631
+ ) : /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5632
+ UrlOrPageInput,
5633
+ {
5634
+ value: state.searchValue,
5635
+ selectedPage: state.selectedPage,
5636
+ dropdownOpen: state.dropdownOpen,
5637
+ onDropdownOpenChange: state.setDropdownOpen,
5638
+ filteredPages: state.filteredPages,
5639
+ onInputChange: state.handleInputChange,
5640
+ onPageSelect: state.handlePageSelect,
5641
+ urlError: state.urlError
5642
+ }
5643
+ ),
5644
+ state.showChooseSection ? /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex flex-col justify-center gap-2", children: [
5645
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5646
+ Button,
5647
+ {
5648
+ type: "button",
5649
+ variant: "outline",
5650
+ className: "w-fit cursor-pointer",
5651
+ size: "sm",
5652
+ onClick: state.handleChooseSection,
5653
+ children: "Choose a section"
5654
+ }
5655
+ ),
5656
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex items-center gap-1 text-sm text-muted-foreground", children: [
5657
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react9.Info, { size: 16, className: "shrink-0", "aria-hidden": true }),
5658
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { children: "Pick a section this link should scroll to." })
5659
+ ] })
5660
+ ] }) : null,
5661
+ state.showSectionRow && state.selectedSection ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(SectionTreeItem, { section: state.selectedSection, selected: true }) : null
5662
+ ] }),
5663
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(DialogFooter, { className: "w-full px-6 pb-6", children: [
5664
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5665
+ Button,
5666
+ {
5667
+ type: "button",
5668
+ variant: isCancel ? "outline" : "ghost",
5669
+ className: "leading-6 shadow-none cursor-pointer",
5670
+ style: isCancel ? {
5671
+ backgroundColor: "var(--ohw-background)",
5672
+ borderColor: "var(--ohw-border)",
5673
+ color: "var(--ohw-foreground)"
5674
+ } : void 0,
5675
+ onClick: state.handleSecondary,
5676
+ children: state.secondaryLabel
5677
+ }
5678
+ ),
5679
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5680
+ Button,
5681
+ {
5682
+ type: "button",
5683
+ className: "border-0 leading-6 shadow-none hover:opacity-90 disabled:opacity-50 cursor-pointer",
5684
+ style: {
5685
+ backgroundColor: "var(--ohw-primary)",
5686
+ color: "var(--ohw-primary-foreground)"
5687
+ },
5688
+ disabled: !state.isValid,
5689
+ onClick: state.handleSubmit,
5690
+ children: state.submitLabel
5691
+ }
5692
+ )
5693
+ ] })
5694
+ ] });
5695
+ }
5696
+
5697
+ // src/ui/link-modal/SectionPickerOverlay.tsx
5430
5698
  var import_react5 = require("react");
5431
- function useLinkModalState({
5432
- open,
5433
- mode,
5434
- pages,
5435
- sections: _sections,
5436
- sectionsByPath,
5437
- initialTarget,
5438
- existingTargets,
5439
- onClose,
5440
- onSubmit
5441
- }) {
5442
- const availablePages = (0, import_react5.useMemo)(
5443
- () => mode === "create" ? filterAvailablePages(pages, existingTargets) : pages,
5444
- [mode, pages, existingTargets]
5445
- );
5446
- const [searchValue, setSearchValue] = (0, import_react5.useState)("");
5447
- const [selectedPage, setSelectedPage] = (0, import_react5.useState)(null);
5448
- const [selectedSection, setSelectedSection] = (0, import_react5.useState)(null);
5449
- const [step, setStep] = (0, import_react5.useState)("input");
5450
- const [dropdownOpen, setDropdownOpen] = (0, import_react5.useState)(false);
5451
- const [urlError, setUrlError] = (0, import_react5.useState)("");
5452
- const reset = (0, import_react5.useCallback)(() => {
5453
- setSearchValue("");
5454
- setSelectedPage(null);
5455
- setSelectedSection(null);
5456
- setStep("input");
5457
- setDropdownOpen(false);
5458
- setUrlError("");
5459
- }, []);
5460
- (0, import_react5.useEffect)(() => {
5461
- if (!open) return;
5462
- if (mode === "edit" && initialTarget) {
5463
- const { pageRoute } = parseTarget(initialTarget);
5464
- const targetSections = getSectionsForPath(sectionsByPath, pageRoute);
5699
+ var import_react_dom = require("react-dom");
5700
+ var import_lucide_react10 = require("lucide-react");
5701
+ var import_navigation = require("next/navigation");
5702
+ var import_jsx_runtime20 = require("react/jsx-runtime");
5703
+ var DIM_OVERLAY = "rgba(0, 0, 0, 0.45)";
5704
+ function rectsEqual(a, b) {
5705
+ if (a.size !== b.size) return false;
5706
+ for (const [id, rect] of a) {
5707
+ const other = b.get(id);
5708
+ if (!other || rect.top !== other.top || rect.left !== other.left || rect.width !== other.width || rect.height !== other.height) {
5709
+ return false;
5710
+ }
5711
+ }
5712
+ return true;
5713
+ }
5714
+ function readSectionRects(sectionIds) {
5715
+ const next = /* @__PURE__ */ new Map();
5716
+ for (const id of sectionIds) {
5717
+ const el = document.querySelector(
5718
+ `[data-ohw-section="${CSS.escape(id)}"]`
5719
+ );
5720
+ if (el) next.set(id, el.getBoundingClientRect());
5721
+ }
5722
+ return next;
5723
+ }
5724
+ function hitTestSectionId(x, y, sectionIds, rects) {
5725
+ for (const id of sectionIds) {
5726
+ const rect = rects.get(id);
5727
+ if (!rect || rect.width <= 0 || rect.height <= 0) continue;
5728
+ if (x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom) {
5729
+ return id;
5730
+ }
5731
+ }
5732
+ return null;
5733
+ }
5734
+ function useSectionRects(sectionIdsKey, enabled) {
5735
+ const [rects, setRects] = (0, import_react5.useState)(/* @__PURE__ */ new Map());
5736
+ const sectionIds = (0, import_react5.useMemo)(
5737
+ () => sectionIdsKey ? sectionIdsKey.split("\0") : [],
5738
+ [sectionIdsKey]
5739
+ );
5740
+ (0, import_react5.useEffect)(() => {
5741
+ if (!enabled || sectionIds.length === 0) {
5742
+ setRects((prev) => prev.size === 0 ? prev : /* @__PURE__ */ new Map());
5743
+ return;
5744
+ }
5745
+ const update = () => {
5746
+ const next = readSectionRects(sectionIds);
5747
+ setRects((prev) => rectsEqual(prev, next) ? prev : next);
5748
+ };
5749
+ update();
5750
+ const opts = { capture: true, passive: true };
5751
+ window.addEventListener("scroll", update, opts);
5752
+ window.addEventListener("resize", update);
5753
+ const vv = window.visualViewport;
5754
+ vv?.addEventListener("resize", update);
5755
+ vv?.addEventListener("scroll", update);
5756
+ const ro = new ResizeObserver(update);
5757
+ for (const id of sectionIds) {
5758
+ const el = document.querySelector(
5759
+ `[data-ohw-section="${CSS.escape(id)}"]`
5760
+ );
5761
+ if (el) ro.observe(el);
5762
+ }
5763
+ return () => {
5764
+ window.removeEventListener("scroll", update, opts);
5765
+ window.removeEventListener("resize", update);
5766
+ vv?.removeEventListener("resize", update);
5767
+ vv?.removeEventListener("scroll", update);
5768
+ ro.disconnect();
5769
+ };
5770
+ }, [sectionIds, enabled]);
5771
+ return rects;
5772
+ }
5773
+ function SectionPickerOverlay({
5774
+ pagePath,
5775
+ sections,
5776
+ onBack,
5777
+ onSelect
5778
+ }) {
5779
+ const router = (0, import_navigation.useRouter)();
5780
+ const pathname = (0, import_navigation.usePathname)();
5781
+ const [selectedId, setSelectedId] = (0, import_react5.useState)(null);
5782
+ const [hoveredId, setHoveredId] = (0, import_react5.useState)(null);
5783
+ const pointerRef = (0, import_react5.useRef)(null);
5784
+ const pointerScreenRef = (0, import_react5.useRef)(null);
5785
+ const iframeOffsetTopRef = (0, import_react5.useRef)(null);
5786
+ const sectionIdsRef = (0, import_react5.useRef)([]);
5787
+ const [chromeClip, setChromeClip] = (0, import_react5.useState)(
5788
+ () => ({
5789
+ top: 0,
5790
+ bottom: typeof window !== "undefined" ? window.innerHeight : 0
5791
+ })
5792
+ );
5793
+ const normalizedTarget = normalizePath(pagePath);
5794
+ const isOnTargetPage = normalizePath(pathname) === normalizedTarget;
5795
+ (0, import_react5.useEffect)(() => {
5796
+ if (isOnTargetPage) return;
5797
+ const search = readPreservedSearch();
5798
+ router.push(`${normalizedTarget}${search}`);
5799
+ }, [isOnTargetPage, normalizedTarget, router]);
5800
+ (0, import_react5.useEffect)(() => {
5801
+ document.documentElement.setAttribute("data-ohw-section-picking", "");
5802
+ document.querySelectorAll("[data-ohw-hovered]").forEach((el) => {
5803
+ el.removeAttribute("data-ohw-hovered");
5804
+ });
5805
+ document.querySelectorAll("[data-ohw-state-hovered]").forEach((el) => {
5806
+ el.removeAttribute("data-ohw-state-hovered");
5807
+ });
5808
+ return () => {
5809
+ document.documentElement.removeAttribute("data-ohw-section-picking");
5810
+ };
5811
+ }, []);
5812
+ const liveSections = (0, import_react5.useMemo)(() => {
5813
+ if (!isOnTargetPage) return sections;
5814
+ const live = collectSectionsFromDom();
5815
+ if (live.length === 0) return sections;
5816
+ const labels = new Map(sections.map((s) => [s.id, s.label]));
5817
+ return live.map((s) => ({ id: s.id, label: labels.get(s.id) ?? s.label }));
5818
+ }, [isOnTargetPage, sections, pathname]);
5819
+ const sectionIdsKey = (0, import_react5.useMemo)(
5820
+ () => liveSections.map((s) => s.id).join("\0"),
5821
+ [liveSections]
5822
+ );
5823
+ const sectionIds = (0, import_react5.useMemo)(
5824
+ () => liveSections.map((s) => s.id),
5825
+ [liveSections]
5826
+ );
5827
+ sectionIdsRef.current = sectionIds;
5828
+ const rects = useSectionRects(sectionIdsKey, isOnTargetPage);
5829
+ const applyHoverAt = (0, import_react5.useCallback)(
5830
+ (x, y, liveRects) => {
5831
+ const ids = sectionIdsRef.current;
5832
+ const map = liveRects ?? readSectionRects(ids);
5833
+ const next = hitTestSectionId(x, y, ids, map);
5834
+ setHoveredId((prev) => prev === next ? prev : next);
5835
+ },
5836
+ []
5837
+ );
5838
+ (0, import_react5.useEffect)(() => {
5839
+ const applyClip = (iframeOffsetTop, visibleCanvasTop, canvasH) => {
5840
+ const top = Math.max(0, visibleCanvasTop - iframeOffsetTop);
5841
+ const bottom = Math.min(
5842
+ window.innerHeight,
5843
+ visibleCanvasTop + canvasH - iframeOffsetTop
5844
+ );
5845
+ setChromeClip(
5846
+ (prev) => prev.top === top && prev.bottom === bottom ? prev : { top, bottom: Math.max(top, bottom) }
5847
+ );
5848
+ };
5849
+ const onParentScroll = (e) => {
5850
+ if (e.data?.type !== "ow:parent-scroll") return;
5851
+ const { iframeOffsetTop, headerH, canvasH } = e.data;
5852
+ applyClip(iframeOffsetTop, headerH, canvasH);
5853
+ iframeOffsetTopRef.current = iframeOffsetTop;
5854
+ if (!pointerScreenRef.current && pointerRef.current) {
5855
+ pointerScreenRef.current = {
5856
+ x: pointerRef.current.x,
5857
+ y: pointerRef.current.y + iframeOffsetTop
5858
+ };
5859
+ }
5860
+ const screen = pointerScreenRef.current;
5861
+ if (screen) {
5862
+ const next = { x: screen.x, y: screen.y - iframeOffsetTop };
5863
+ pointerRef.current = next;
5864
+ applyHoverAt(next.x, next.y);
5865
+ }
5866
+ };
5867
+ const onResize = () => {
5868
+ setChromeClip((prev) => {
5869
+ const bottom = Math.max(prev.top, window.innerHeight);
5870
+ return prev.bottom === bottom ? prev : { ...prev, bottom };
5871
+ });
5872
+ };
5873
+ window.addEventListener("message", onParentScroll);
5874
+ window.addEventListener("resize", onResize);
5875
+ return () => {
5876
+ window.removeEventListener("message", onParentScroll);
5877
+ window.removeEventListener("resize", onResize);
5878
+ };
5879
+ }, [applyHoverAt]);
5880
+ (0, import_react5.useEffect)(() => {
5881
+ const ptr = pointerRef.current;
5882
+ if (!ptr) return;
5883
+ applyHoverAt(ptr.x, ptr.y, rects);
5884
+ }, [rects, applyHoverAt]);
5885
+ (0, import_react5.useEffect)(() => {
5886
+ const rememberPointer = (clientX, clientY) => {
5887
+ pointerRef.current = { x: clientX, y: clientY };
5888
+ const top = iframeOffsetTopRef.current;
5889
+ if (top != null) {
5890
+ pointerScreenRef.current = { x: clientX, y: clientY + top };
5891
+ }
5892
+ applyHoverAt(clientX, clientY, rects);
5893
+ };
5894
+ const onPointerMove = (e) => {
5895
+ rememberPointer(e.clientX, e.clientY);
5896
+ };
5897
+ const onPointerSync = (e) => {
5898
+ if (e.data?.type !== "ow:pointer-sync") return;
5899
+ const { clientX, clientY } = e.data;
5900
+ rememberPointer(clientX, clientY);
5901
+ };
5902
+ window.addEventListener("pointermove", onPointerMove, { passive: true });
5903
+ window.addEventListener("message", onPointerSync);
5904
+ return () => {
5905
+ window.removeEventListener("pointermove", onPointerMove);
5906
+ window.removeEventListener("message", onPointerSync);
5907
+ };
5908
+ }, [rects, applyHoverAt]);
5909
+ const handleSelect = (0, import_react5.useCallback)(
5910
+ (section) => {
5911
+ if (selectedId) return;
5912
+ setSelectedId(section.id);
5913
+ onSelect(section);
5914
+ },
5915
+ [selectedId, onSelect]
5916
+ );
5917
+ (0, import_react5.useEffect)(() => {
5918
+ const onKeyDown = (e) => {
5919
+ if (e.key === "Escape") {
5920
+ e.preventDefault();
5921
+ e.stopPropagation();
5922
+ onBack();
5923
+ }
5924
+ };
5925
+ window.addEventListener("keydown", onKeyDown, true);
5926
+ return () => window.removeEventListener("keydown", onKeyDown, true);
5927
+ }, [onBack]);
5928
+ const portalRoot = typeof document !== "undefined" ? document.querySelector("[data-ohw-bridge-root]") ?? document.body : null;
5929
+ if (!portalRoot) return null;
5930
+ return (0, import_react_dom.createPortal)(
5931
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
5932
+ "div",
5933
+ {
5934
+ "data-ohw-section-picker": "",
5935
+ "data-ohw-bridge": "",
5936
+ className: `pointer-events-none fixed inset-0 z-[2147483647] transition-opacity duration-200 ${"opacity-100"}`,
5937
+ "aria-modal": true,
5938
+ role: "dialog",
5939
+ "aria-label": "Choose a section",
5940
+ children: [
5941
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
5942
+ "div",
5943
+ {
5944
+ className: "pointer-events-auto fixed left-5 z-[2]",
5945
+ style: { top: chromeClip.top + 20 },
5946
+ children: /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
5947
+ Button,
5948
+ {
5949
+ type: "button",
5950
+ variant: "outline",
5951
+ size: "sm",
5952
+ className: "h-8 min-w-0 gap-1 border-border bg-background px-2 py-1.5 shadow-sm hover:bg-muted cursor-pointer",
5953
+ onClick: onBack,
5954
+ children: [
5955
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react10.ArrowLeft, { className: "size-4 shrink-0", "aria-hidden": true }),
5956
+ "Back"
5957
+ ]
5958
+ }
5959
+ )
5960
+ }
5961
+ ),
5962
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
5963
+ "div",
5964
+ {
5965
+ className: "pointer-events-none fixed left-1/2 z-[2] rounded-lg px-4 py-3 text-xs leading-4 tracking-[0.18px] text-white shadow-md",
5966
+ style: {
5967
+ top: chromeClip.bottom - 20,
5968
+ transform: "translate(-50%, -100%)",
5969
+ backgroundColor: "var(--ohw-popover-foreground, #0c0a09)"
5970
+ },
5971
+ "data-ohw-section-picker-hint": "",
5972
+ children: "Click on section to select"
5973
+ }
5974
+ ),
5975
+ !isOnTargetPage ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
5976
+ "div",
5977
+ {
5978
+ className: "pointer-events-none fixed left-1/2 z-[1] -translate-x-1/2 rounded-md bg-background/90 px-3 py-2 text-sm text-muted-foreground shadow-sm",
5979
+ style: { top: chromeClip.top + 64 },
5980
+ children: "Loading page preview\u2026"
5981
+ }
5982
+ ) : null,
5983
+ isOnTargetPage && liveSections.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "pointer-events-auto fixed inset-0 z-[1] flex items-center justify-center bg-muted/40", children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("p", { className: "text-sm text-muted-foreground", children: "No sections found on this page." }) }) : null,
5984
+ isOnTargetPage ? liveSections.map((section) => {
5985
+ const rect = rects.get(section.id);
5986
+ if (!rect || rect.width <= 0 || rect.height <= 0) return null;
5987
+ const isSelected = selectedId === section.id;
5988
+ const isHovered = hoveredId === section.id;
5989
+ const isLit = isSelected || isHovered;
5990
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
5991
+ "button",
5992
+ {
5993
+ type: "button",
5994
+ className: "pointer-events-auto fixed z-[1] cursor-pointer border-0 bg-transparent p-0 transition-[background-color] duration-150",
5995
+ style: {
5996
+ top: rect.top,
5997
+ left: rect.left,
5998
+ width: rect.width,
5999
+ height: rect.height,
6000
+ backgroundColor: isLit ? "transparent" : DIM_OVERLAY
6001
+ },
6002
+ "aria-label": `Select section ${section.label}`,
6003
+ onClick: () => handleSelect(section),
6004
+ children: [
6005
+ isLit ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
6006
+ "span",
6007
+ {
6008
+ className: "pointer-events-none absolute",
6009
+ style: {
6010
+ inset: 4,
6011
+ borderRadius: "var(--radius, 6px)",
6012
+ border: "1.5px dashed var(--primary, #0885FE)",
6013
+ boxSizing: "border-box"
6014
+ },
6015
+ "aria-hidden": true
6016
+ }
6017
+ ) : null,
6018
+ isSelected ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
6019
+ "span",
6020
+ {
6021
+ className: "absolute right-3 top-3 flex size-8 items-center justify-center rounded-full text-white",
6022
+ style: { backgroundColor: "var(--ohw-primary, #0885fe)" },
6023
+ "aria-hidden": true,
6024
+ children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react10.Check, { className: "size-5" })
6025
+ }
6026
+ ) : null
6027
+ ]
6028
+ },
6029
+ section.id
6030
+ );
6031
+ }) : null
6032
+ ]
6033
+ }
6034
+ ),
6035
+ portalRoot
6036
+ );
6037
+ }
6038
+
6039
+ // src/ui/link-modal/useLinkModalState.ts
6040
+ var import_react6 = require("react");
6041
+ function useLinkModalState({
6042
+ open,
6043
+ mode,
6044
+ pages,
6045
+ sections: _sections,
6046
+ sectionsByPath,
6047
+ initialTarget,
6048
+ existingTargets: _existingTargets,
6049
+ onClose,
6050
+ onSubmit
6051
+ }) {
6052
+ const availablePages = (0, import_react6.useMemo)(() => pages, [pages]);
6053
+ const [searchValue, setSearchValue] = (0, import_react6.useState)("");
6054
+ const [selectedPage, setSelectedPage] = (0, import_react6.useState)(null);
6055
+ const [selectedSection, setSelectedSection] = (0, import_react6.useState)(null);
6056
+ const [step, setStep] = (0, import_react6.useState)("input");
6057
+ const [dropdownOpen, setDropdownOpen] = (0, import_react6.useState)(false);
6058
+ const [urlError, setUrlError] = (0, import_react6.useState)("");
6059
+ const reset = (0, import_react6.useCallback)(() => {
6060
+ setSearchValue("");
6061
+ setSelectedPage(null);
6062
+ setSelectedSection(null);
6063
+ setStep("input");
6064
+ setDropdownOpen(false);
6065
+ setUrlError("");
6066
+ }, []);
6067
+ (0, import_react6.useEffect)(() => {
6068
+ if (!open) return;
6069
+ if (mode === "edit" && initialTarget) {
6070
+ const { pageRoute } = parseTarget(initialTarget);
6071
+ const targetSections = getSectionsForPath(sectionsByPath, pageRoute);
5465
6072
  const init = getEditModeInitialState(initialTarget, pages, targetSections);
5466
6073
  setSearchValue(init.searchValue);
5467
6074
  setSelectedPage(init.selectedPage);
@@ -5472,12 +6079,12 @@ function useLinkModalState({
5472
6079
  }
5473
6080
  setDropdownOpen(false);
5474
6081
  setUrlError("");
5475
- }, [open, mode, initialTarget, pages, sectionsByPath, reset]);
5476
- const filteredPages = (0, import_react5.useMemo)(() => {
6082
+ }, [open, mode, initialTarget, reset]);
6083
+ const filteredPages = (0, import_react6.useMemo)(() => {
5477
6084
  if (!searchValue.trim()) return availablePages;
5478
6085
  return availablePages.filter((p) => p.title.toLowerCase().startsWith(searchValue.toLowerCase()));
5479
6086
  }, [availablePages, searchValue]);
5480
- const activeSections = (0, import_react5.useMemo)(() => {
6087
+ const activeSections = (0, import_react6.useMemo)(() => {
5481
6088
  if (!selectedPage) return [];
5482
6089
  return getSectionsForPath(sectionsByPath, selectedPage.path);
5483
6090
  }, [selectedPage, sectionsByPath]);
@@ -5519,11 +6126,14 @@ function useLinkModalState({
5519
6126
  const handleBackToSections = () => {
5520
6127
  setStep("sectionPicker");
5521
6128
  };
6129
+ const handleSectionPickerBack = () => {
6130
+ setStep("input");
6131
+ };
5522
6132
  const handleClose = () => {
5523
6133
  reset();
5524
6134
  onClose();
5525
6135
  };
5526
- const isValid = (0, import_react5.useMemo)(() => {
6136
+ const isValid = (0, import_react6.useMemo)(() => {
5527
6137
  if (urlError) return false;
5528
6138
  if (showBreadcrumb) return true;
5529
6139
  if (selectedPage) return true;
@@ -5556,7 +6166,7 @@ function useLinkModalState({
5556
6166
  onSubmit(normalizeUrl(searchValue));
5557
6167
  handleClose();
5558
6168
  };
5559
- const submitLabel = mode === "edit" ? "Save" : "Create";
6169
+ const submitLabel = showBreadcrumb ? "Save" : mode === "edit" ? "Save" : "Create";
5560
6170
  const title = mode === "edit" ? "Edit navigation item" : "Create navigation item";
5561
6171
  const secondaryLabel = showBreadcrumb ? "Back to sections" : "Cancel";
5562
6172
  const handleSecondary = showBreadcrumb ? handleBackToSections : handleClose;
@@ -5583,23 +6193,29 @@ function useLinkModalState({
5583
6193
  handleChooseSection,
5584
6194
  handleSectionSelect,
5585
6195
  handleBackToSections,
6196
+ handleSectionPickerBack,
5586
6197
  handleClose,
5587
6198
  handleSecondary,
5588
6199
  handleSubmit
5589
6200
  };
5590
6201
  }
5591
6202
 
5592
- // src/ui/link-modal/LinkEditorPanel.tsx
5593
- var import_jsx_runtime19 = require("react/jsx-runtime");
5594
- function LinkEditorPanel({
6203
+ // src/ui/link-modal/LinkPopover.tsx
6204
+ var import_jsx_runtime21 = require("react/jsx-runtime");
6205
+ function postToParent(data) {
6206
+ window.parent?.postMessage(data, "*");
6207
+ }
6208
+ function LinkPopover({
5595
6209
  open = true,
6210
+ panelRef,
6211
+ portalContainer,
6212
+ onClose,
5596
6213
  mode = "create",
5597
6214
  pages,
5598
6215
  sections = [],
5599
6216
  sectionsByPath,
5600
6217
  initialTarget,
5601
6218
  existingTargets = [],
5602
- onClose,
5603
6219
  onSubmit
5604
6220
  }) {
5605
6221
  const state = useLinkModalState({
@@ -5613,115 +6229,109 @@ function LinkEditorPanel({
5613
6229
  onClose,
5614
6230
  onSubmit
5615
6231
  });
5616
- const isCancel = state.secondaryLabel === "Cancel";
5617
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_jsx_runtime19.Fragment, { children: [
5618
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(DialogClose, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5619
- "button",
6232
+ const sectionPickerActive = state.showSectionPicker && Boolean(state.selectedPage);
6233
+ (0, import_react7.useEffect)(() => {
6234
+ if (!open) return;
6235
+ if (sectionPickerActive) {
6236
+ postToParent({ type: "ui:unlock" });
6237
+ postToParent({ type: "ow:section-picker", active: true });
6238
+ document.documentElement.style.overflow = "";
6239
+ document.body.style.overflow = "";
6240
+ return () => {
6241
+ postToParent({ type: "ow:section-picker", active: false });
6242
+ };
6243
+ }
6244
+ postToParent({ type: "ow:section-picker", active: false });
6245
+ postToParent({ type: "ui:lock" });
6246
+ const html = document.documentElement;
6247
+ const body = document.body;
6248
+ const prevHtmlOverflow = html.style.overflow;
6249
+ const prevBodyOverflow = body.style.overflow;
6250
+ const prevHtmlOverscroll = html.style.overscrollBehavior;
6251
+ const prevBodyOverscroll = body.style.overscrollBehavior;
6252
+ html.style.overflow = "hidden";
6253
+ body.style.overflow = "hidden";
6254
+ html.style.overscrollBehavior = "none";
6255
+ body.style.overscrollBehavior = "none";
6256
+ const canScrollElement = (el, deltaY) => {
6257
+ const { overflowY } = getComputedStyle(el);
6258
+ if (overflowY !== "auto" && overflowY !== "scroll") return false;
6259
+ if (el.scrollHeight <= el.clientHeight + 1) return false;
6260
+ if (deltaY < 0) return el.scrollTop > 0;
6261
+ if (deltaY > 0)
6262
+ return el.scrollTop + el.clientHeight < el.scrollHeight - 1;
6263
+ return true;
6264
+ };
6265
+ const allowsInternalScroll = (e) => {
6266
+ const target = e.target;
6267
+ if (!(target instanceof Element)) return false;
6268
+ const scrollRoot = target.closest(
6269
+ "[data-ohw-link-page-dropdown], [data-ohw-allow-scroll]"
6270
+ );
6271
+ if (!scrollRoot) return false;
6272
+ const deltaY = e instanceof WheelEvent ? e.deltaY : 0;
6273
+ return canScrollElement(scrollRoot, deltaY);
6274
+ };
6275
+ const preventBackgroundScroll = (e) => {
6276
+ if (allowsInternalScroll(e)) return;
6277
+ e.preventDefault();
6278
+ };
6279
+ const scrollOpts = { passive: false, capture: true };
6280
+ document.addEventListener("wheel", preventBackgroundScroll, scrollOpts);
6281
+ document.addEventListener("touchmove", preventBackgroundScroll, scrollOpts);
6282
+ return () => {
6283
+ postToParent({ type: "ui:unlock" });
6284
+ html.style.overflow = prevHtmlOverflow;
6285
+ body.style.overflow = prevBodyOverflow;
6286
+ html.style.overscrollBehavior = prevHtmlOverscroll;
6287
+ body.style.overscrollBehavior = prevBodyOverscroll;
6288
+ document.removeEventListener(
6289
+ "wheel",
6290
+ preventBackgroundScroll,
6291
+ scrollOpts
6292
+ );
6293
+ document.removeEventListener(
6294
+ "touchmove",
6295
+ preventBackgroundScroll,
6296
+ scrollOpts
6297
+ );
6298
+ };
6299
+ }, [open, sectionPickerActive]);
6300
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
6301
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
6302
+ Dialog2,
5620
6303
  {
5621
- type: "button",
5622
- className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
5623
- "aria-label": "Close",
5624
- children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(IconX, { "aria-hidden": true })
6304
+ open: open && !sectionPickerActive,
6305
+ onOpenChange: (next) => {
6306
+ if (!next) onClose?.();
6307
+ },
6308
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
6309
+ DialogContent,
6310
+ {
6311
+ ref: panelRef,
6312
+ container: portalContainer,
6313
+ "data-ohw-link-popover-root": "",
6314
+ "data-ohw-link-modal-root": "",
6315
+ "data-ohw-bridge": "",
6316
+ showCloseButton: false,
6317
+ className: "gap-0 p-0 w-full max-w-[448px] pointer-events-auto z-[2147483646] overflow-visible",
6318
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(LinkEditorPanel, { state, onClose })
6319
+ }
6320
+ )
5625
6321
  }
5626
- ) }),
5627
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(DialogHeader, { className: "w-full gap-1.5 p-6 pr-12", children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(DialogTitle, { className: "m-0 w-full break-words", children: state.title }) }),
5628
- /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex w-full flex-col gap-3 px-6 pb-8 pt-1", children: [
5629
- state.showBreadcrumb && state.selectedPage && state.selectedSection ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5630
- DestinationBreadcrumb,
5631
- {
5632
- pageTitle: state.selectedPage.title,
5633
- sectionLabel: state.selectedSection.label
5634
- }
5635
- ) : /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5636
- UrlOrPageInput,
5637
- {
5638
- value: state.searchValue,
5639
- selectedPage: state.selectedPage,
5640
- dropdownOpen: state.dropdownOpen,
5641
- onDropdownOpenChange: state.setDropdownOpen,
5642
- filteredPages: state.filteredPages,
5643
- onInputChange: state.handleInputChange,
5644
- onPageSelect: state.handlePageSelect,
5645
- urlError: state.urlError
5646
- }
5647
- ),
5648
- state.showChooseSection ? /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex flex-col justify-center gap-2", children: [
5649
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Button, { type: "button", variant: "outline", className: "w-fit cursor-pointer", size: "sm", onClick: state.handleChooseSection, children: "Choose a section" }),
5650
- /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex items-center gap-1 text-sm text-muted-foreground", children: [
5651
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(IconInfo, { className: "shrink-0", "aria-hidden": true }),
5652
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { children: "Pick a section this link should scroll to." })
5653
- ] })
5654
- ] }) : null,
5655
- state.showSectionPicker ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(SectionPickerList, { sections: state.activeSections, onSelect: state.handleSectionSelect }) : null,
5656
- state.showSectionRow && state.selectedSection ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(SectionTreeItem, { section: state.selectedSection, selected: true }) : null
5657
- ] }),
5658
- /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(DialogFooter, { className: "w-full px-6 pb-6", children: [
5659
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5660
- Button,
5661
- {
5662
- type: "button",
5663
- variant: isCancel ? "outline" : "ghost",
5664
- className: "leading-6 shadow-none cursor-pointer",
5665
- style: isCancel ? {
5666
- backgroundColor: "var(--ohw-background)",
5667
- borderColor: "var(--ohw-border)",
5668
- color: "var(--ohw-foreground)"
5669
- } : void 0,
5670
- onClick: state.handleSecondary,
5671
- children: state.secondaryLabel
5672
- }
5673
- ),
5674
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5675
- Button,
5676
- {
5677
- type: "button",
5678
- className: "border-0 leading-6 shadow-none hover:opacity-90 disabled:opacity-50 cursor-pointer",
5679
- style: {
5680
- backgroundColor: "var(--ohw-primary)",
5681
- color: "var(--ohw-primary-foreground)"
5682
- },
5683
- disabled: !state.isValid,
5684
- onClick: state.handleSubmit,
5685
- children: state.submitLabel
5686
- }
5687
- )
5688
- ] })
6322
+ ),
6323
+ sectionPickerActive && state.selectedPage ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
6324
+ SectionPickerOverlay,
6325
+ {
6326
+ pagePath: state.selectedPage.path,
6327
+ sections: state.activeSections,
6328
+ onBack: state.handleSectionPickerBack,
6329
+ onSelect: state.handleSectionSelect
6330
+ }
6331
+ ) : null
5689
6332
  ] });
5690
6333
  }
5691
6334
 
5692
- // src/ui/link-modal/LinkPopover.tsx
5693
- var import_jsx_runtime20 = require("react/jsx-runtime");
5694
- function LinkPopover({
5695
- open = true,
5696
- panelRef,
5697
- portalContainer,
5698
- onClose,
5699
- ...editorProps
5700
- }) {
5701
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
5702
- Dialog2,
5703
- {
5704
- open,
5705
- onOpenChange: (next) => {
5706
- if (!next) onClose?.();
5707
- },
5708
- children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
5709
- DialogContent,
5710
- {
5711
- ref: panelRef,
5712
- container: portalContainer,
5713
- "data-ohw-link-popover-root": "",
5714
- "data-ohw-link-modal-root": "",
5715
- "data-ohw-bridge": "",
5716
- showCloseButton: false,
5717
- className: "gap-0 p-0 w-full md:w-md pointer-events-auto",
5718
- children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(LinkEditorPanel, { ...editorProps, open, onClose })
5719
- }
5720
- )
5721
- }
5722
- );
5723
- }
5724
-
5725
6335
  // src/ui/link-modal/devFixtures.ts
5726
6336
  var DEV_SITE_PAGES = [
5727
6337
  { path: "/", title: "Home" },
@@ -5741,16 +6351,21 @@ var DEV_SECTIONS_BY_PATH = {
5741
6351
  { id: "founder-teaser", label: "Founder" },
5742
6352
  { id: "plan-form", label: "Plan Form" },
5743
6353
  { id: "testimonials", label: "Testimonials" },
5744
- { id: "wordmark", label: "Wordmark" }
6354
+ { id: "wordmark", label: "Wordmark" },
6355
+ { id: "footer", label: "Footer" }
5745
6356
  ],
5746
6357
  "/about": [
5747
6358
  { id: "manifesto", label: "Manifesto" },
5748
6359
  { id: "story-letter", label: "Our Story" },
5749
6360
  { id: "lagree-explainer", label: "Lagree Explainer" },
5750
6361
  { id: "waitlist-cta", label: "Waitlist" },
5751
- { id: "personal-training", label: "Personal training" }
6362
+ { id: "personal-training", label: "Personal training" },
6363
+ { id: "footer", label: "Footer" }
6364
+ ],
6365
+ "/classes": [
6366
+ { id: "class-library", label: "Class Library" },
6367
+ { id: "footer", label: "Footer" }
5752
6368
  ],
5753
- "/classes": [{ id: "class-library", label: "Class Library" }],
5754
6369
  "/pricing": [
5755
6370
  { id: "page-header", label: "Page Header" },
5756
6371
  { id: "free-class-cta", label: "Free Class" },
@@ -5758,19 +6373,25 @@ var DEV_SECTIONS_BY_PATH = {
5758
6373
  { id: "monthly-memberships", label: "Memberships" },
5759
6374
  { id: "specialty-programs", label: "Specialty Programs" },
5760
6375
  { id: "package-matcher", label: "Packages" },
5761
- { id: "wordmark", label: "Wordmark" }
6376
+ { id: "wordmark", label: "Wordmark" },
6377
+ { id: "footer", label: "Footer" }
5762
6378
  ],
5763
6379
  "/studio": [
5764
6380
  { id: "studio-intro", label: "Studio Intro" },
5765
6381
  { id: "studio-features", label: "Studio Features" },
5766
6382
  { id: "studio-gallery", label: "Studio Gallery" },
5767
- { id: "studio-visit", label: "Visit Studio" }
6383
+ { id: "studio-visit", label: "Visit Studio" },
6384
+ { id: "footer", label: "Footer" }
6385
+ ],
6386
+ "/instructors": [
6387
+ { id: "instructor-grid", label: "Instructors" },
6388
+ { id: "footer", label: "Footer" }
5768
6389
  ],
5769
- "/instructors": [{ id: "instructor-grid", label: "Instructors" }],
5770
6390
  "/contact": [
5771
6391
  { id: "hello-hero", label: "Hello" },
5772
6392
  { id: "contact-form", label: "Contact Form" },
5773
- { id: "faq", label: "FAQ" }
6393
+ { id: "faq", label: "FAQ" },
6394
+ { id: "footer", label: "Footer" }
5774
6395
  ]
5775
6396
  };
5776
6397
  function shouldUseDevFixtures() {
@@ -5780,8 +6401,716 @@ function shouldUseDevFixtures() {
5780
6401
  return new URLSearchParams(q).get("ohw-fixtures") === "1";
5781
6402
  }
5782
6403
 
6404
+ // src/lib/nav-items.ts
6405
+ var NAV_COUNT_KEY = "__ohw_nav_count";
6406
+ var NAV_ORDER_KEY = "__ohw_nav_order";
6407
+ function getLinkHref(el) {
6408
+ const key = el.getAttribute("data-ohw-href-key");
6409
+ if (key) {
6410
+ const stored = getStoredLinkHref(key);
6411
+ if (stored !== void 0) return stored;
6412
+ }
6413
+ return el.getAttribute("href") ?? "";
6414
+ }
6415
+ function isNavbarButton(el) {
6416
+ return el.hasAttribute("data-ohw-role") && el.getAttribute("data-ohw-role") === "navbar-button";
6417
+ }
6418
+ function isNavbarLinkItem(el) {
6419
+ if (!el.matches("[data-ohw-href-key]")) return false;
6420
+ if (isNavbarButton(el)) return false;
6421
+ if (!el.querySelector('[data-ohw-editable="text"]')) return false;
6422
+ return Boolean(el.closest("nav, [data-ohw-nav-container], [data-ohw-nav-drawer]"));
6423
+ }
6424
+ function getNavbarDesktopContainer() {
6425
+ return document.querySelector("[data-ohw-nav-container]");
6426
+ }
6427
+ function getNavbarDrawerContainer() {
6428
+ return document.querySelector("[data-ohw-nav-drawer]");
6429
+ }
6430
+ function listNavbarItems() {
6431
+ const desktop = getNavbarDesktopContainer();
6432
+ if (desktop) {
6433
+ return Array.from(desktop.querySelectorAll("[data-ohw-href-key]")).filter(isNavbarLinkItem);
6434
+ }
6435
+ const drawer = getNavbarDrawerContainer();
6436
+ if (drawer) {
6437
+ return Array.from(drawer.querySelectorAll("[data-ohw-href-key]")).filter(isNavbarLinkItem);
6438
+ }
6439
+ return Array.from(document.querySelectorAll("nav [data-ohw-href-key]")).filter(isNavbarLinkItem);
6440
+ }
6441
+ function parseNavIndexFromKey(key) {
6442
+ if (!key) return null;
6443
+ const match = key.match(/^nav-(\d+)-href$/);
6444
+ if (!match) return null;
6445
+ return parseInt(match[1], 10);
6446
+ }
6447
+ function getNavbarIndicesInDom() {
6448
+ const indices = /* @__PURE__ */ new Set();
6449
+ for (const item of listNavbarItems()) {
6450
+ const idx = parseNavIndexFromKey(item.getAttribute("data-ohw-href-key"));
6451
+ if (idx !== null) indices.add(idx);
6452
+ }
6453
+ return [...indices].sort((a, b) => a - b);
6454
+ }
6455
+ function getNextNavbarIndex() {
6456
+ const indices = getNavbarIndicesInDom();
6457
+ if (indices.length === 0) return 0;
6458
+ return Math.max(...indices) + 1;
6459
+ }
6460
+ function getNavbarExistingTargets() {
6461
+ return listNavbarItems().map((el) => getLinkHref(el)).filter(Boolean);
6462
+ }
6463
+ function getNavOrderFromDom() {
6464
+ return listNavbarItems().map((el) => el.getAttribute("data-ohw-href-key")).filter((key) => Boolean(key));
6465
+ }
6466
+ function parseNavOrder(content) {
6467
+ const raw = content[NAV_ORDER_KEY];
6468
+ if (!raw) return null;
6469
+ try {
6470
+ const parsed = JSON.parse(raw);
6471
+ if (!Array.isArray(parsed)) return null;
6472
+ return parsed.filter((key) => typeof key === "string" && key.length > 0);
6473
+ } catch {
6474
+ return null;
6475
+ }
6476
+ }
6477
+ function findCounterpartByHrefKey(hrefKey, root) {
6478
+ return root.querySelector(`[data-ohw-href-key="${CSS.escape(hrefKey)}"]`);
6479
+ }
6480
+ function cloneNavLinkShell(template) {
6481
+ const anchor = document.createElement("a");
6482
+ if (template) {
6483
+ anchor.style.cssText = template.style.cssText;
6484
+ if (template.className) anchor.className = template.className;
6485
+ }
6486
+ anchor.style.cursor = "pointer";
6487
+ anchor.style.textDecoration = "none";
6488
+ return anchor;
6489
+ }
6490
+ function buildNavLink(index, href, label, template) {
6491
+ const anchor = cloneNavLinkShell(template);
6492
+ anchor.href = href;
6493
+ anchor.setAttribute("data-ohw-href-key", `nav-${index}-href`);
6494
+ const span = document.createElement("span");
6495
+ span.setAttribute("data-ohw-editable", "text");
6496
+ span.setAttribute("data-ohw-key", `nav-${index}-label`);
6497
+ span.textContent = label;
6498
+ anchor.appendChild(span);
6499
+ return anchor;
6500
+ }
6501
+ function insertNavLinkInContainer(container, anchor, afterHrefKey) {
6502
+ if (!afterHrefKey) {
6503
+ container.appendChild(anchor);
6504
+ return;
6505
+ }
6506
+ const afterEl = findCounterpartByHrefKey(afterHrefKey, container);
6507
+ if (afterEl?.parentElement === container) {
6508
+ afterEl.insertAdjacentElement("afterend", anchor);
6509
+ return;
6510
+ }
6511
+ container.appendChild(anchor);
6512
+ }
6513
+ function insertNavbarItemDom(index, href, label, afterAnchor) {
6514
+ const afterHrefKey = afterAnchor?.getAttribute("data-ohw-href-key") ?? null;
6515
+ const desktopItems = getNavbarDesktopContainer()?.querySelectorAll("[data-ohw-href-key]") ?? [];
6516
+ const drawerItems = getNavbarDrawerContainer()?.querySelectorAll("[data-ohw-href-key]") ?? [];
6517
+ const desktopTemplate = Array.from(desktopItems).find(isNavbarLinkItem) ?? null;
6518
+ const drawerTemplate = Array.from(drawerItems).find(isNavbarLinkItem) ?? null;
6519
+ let primary = null;
6520
+ const desktopContainer = getNavbarDesktopContainer();
6521
+ if (desktopContainer) {
6522
+ const desktopAnchor = buildNavLink(index, href, label, desktopTemplate);
6523
+ insertNavLinkInContainer(desktopContainer, desktopAnchor, afterHrefKey);
6524
+ primary = desktopAnchor;
6525
+ }
6526
+ const drawerContainer = getNavbarDrawerContainer();
6527
+ if (drawerContainer) {
6528
+ const drawerAnchor = buildNavLink(index, href, label, drawerTemplate);
6529
+ insertNavLinkInContainer(drawerContainer, drawerAnchor, afterHrefKey);
6530
+ if (!primary) primary = drawerAnchor;
6531
+ }
6532
+ if (!primary) {
6533
+ const fallback = buildNavLink(index, href, label, listNavbarItems()[0] ?? null);
6534
+ const nav = document.querySelector("nav");
6535
+ nav?.appendChild(fallback);
6536
+ primary = fallback;
6537
+ }
6538
+ return primary;
6539
+ }
6540
+ function applyNavOrderToContainer(container, order) {
6541
+ const seen = /* @__PURE__ */ new Set();
6542
+ for (const hrefKey of order) {
6543
+ if (seen.has(hrefKey)) continue;
6544
+ seen.add(hrefKey);
6545
+ const el = findCounterpartByHrefKey(hrefKey, container);
6546
+ if (el) container.appendChild(el);
6547
+ }
6548
+ for (const el of container.querySelectorAll("[data-ohw-href-key]")) {
6549
+ if (!isNavbarLinkItem(el)) continue;
6550
+ const key = el.getAttribute("data-ohw-href-key");
6551
+ if (!key || seen.has(key)) continue;
6552
+ container.appendChild(el);
6553
+ }
6554
+ }
6555
+ function applyNavOrder(order) {
6556
+ const desktop = getNavbarDesktopContainer();
6557
+ if (desktop) applyNavOrderToContainer(desktop, order);
6558
+ const drawer = getNavbarDrawerContainer();
6559
+ if (drawer) applyNavOrderToContainer(drawer, order);
6560
+ }
6561
+ function collectNavbarIndicesFromContent(content) {
6562
+ const indices = /* @__PURE__ */ new Set();
6563
+ for (const key of Object.keys(content)) {
6564
+ const idx = parseNavIndexFromKey(key);
6565
+ if (idx !== null) indices.add(idx);
6566
+ }
6567
+ const countRaw = content[NAV_COUNT_KEY];
6568
+ if (countRaw) {
6569
+ const count = parseInt(countRaw, 10);
6570
+ if (Number.isFinite(count) && count > 0) {
6571
+ for (let i = 0; i < count; i++) indices.add(i);
6572
+ }
6573
+ }
6574
+ return [...indices].sort((a, b) => a - b);
6575
+ }
6576
+ function navbarIndexExistsInDom(index) {
6577
+ return document.querySelector(`[data-ohw-href-key="nav-${index}-href"]`) !== null;
6578
+ }
6579
+ function reconcileNavbarItemsFromContent(content) {
6580
+ const indices = collectNavbarIndicesFromContent(content);
6581
+ for (const index of indices) {
6582
+ if (navbarIndexExistsInDom(index)) continue;
6583
+ const href = content[`nav-${index}-href`];
6584
+ const label = content[`nav-${index}-label`];
6585
+ if (!href && !label) continue;
6586
+ insertNavbarItemDom(index, href ?? "/", label ?? "Untitled", null);
6587
+ }
6588
+ const order = parseNavOrder(content);
6589
+ if (order?.length) applyNavOrder(order);
6590
+ }
6591
+ function buildNavOrderAfterInsert(afterAnchor, newHrefKey) {
6592
+ const order = getNavOrderFromDom();
6593
+ if (!afterAnchor) {
6594
+ if (!order.includes(newHrefKey)) order.push(newHrefKey);
6595
+ return order;
6596
+ }
6597
+ const afterKey = afterAnchor.getAttribute("data-ohw-href-key");
6598
+ if (!afterKey) {
6599
+ if (!order.includes(newHrefKey)) order.push(newHrefKey);
6600
+ return order;
6601
+ }
6602
+ const withoutNew = order.filter((key) => key !== newHrefKey);
6603
+ const pos = withoutNew.indexOf(afterKey);
6604
+ if (pos >= 0) {
6605
+ withoutNew.splice(pos + 1, 0, newHrefKey);
6606
+ return withoutNew;
6607
+ }
6608
+ withoutNew.push(newHrefKey);
6609
+ return withoutNew;
6610
+ }
6611
+ function insertNavbarItem(href, label, afterAnchor = null) {
6612
+ const index = getNextNavbarIndex();
6613
+ const anchor = insertNavbarItemDom(index, href, label, afterAnchor);
6614
+ if (!anchor) throw new Error("Failed to insert navbar item");
6615
+ const hrefKey = `nav-${index}-href`;
6616
+ const order = buildNavOrderAfterInsert(afterAnchor, hrefKey);
6617
+ applyNavOrder(order);
6618
+ return {
6619
+ anchor,
6620
+ index,
6621
+ hrefKey,
6622
+ labelKey: `nav-${index}-label`,
6623
+ href,
6624
+ label,
6625
+ order
6626
+ };
6627
+ }
6628
+
6629
+ // src/lib/footer-items.ts
6630
+ var FOOTER_ORDER_KEY = "__ohw_footer_order";
6631
+ var FOOTER_HREF_RE = /^footer-(\d+)-(\d+)-href$/;
6632
+ function parseFooterHrefKey(key) {
6633
+ if (!key) return null;
6634
+ const match = key.match(FOOTER_HREF_RE);
6635
+ if (!match) return null;
6636
+ return { col: parseInt(match[1], 10), item: parseInt(match[2], 10) };
6637
+ }
6638
+ function isFooterHrefKey(key) {
6639
+ return parseFooterHrefKey(key) !== null;
6640
+ }
6641
+ function getFooterRoot() {
6642
+ return document.querySelector('footer[data-ohw-section="footer"], footer');
6643
+ }
6644
+ function getFooterLinksContainer() {
6645
+ return document.querySelector("[data-ohw-footer-links]") ?? document.querySelector(".rb-footer-links");
6646
+ }
6647
+ function isFooterLinkAnchor(el) {
6648
+ if (!el.matches("[data-ohw-href-key]")) return false;
6649
+ if (!isFooterHrefKey(el.getAttribute("data-ohw-href-key"))) return false;
6650
+ return Boolean(el.querySelector('[data-ohw-editable="text"]'));
6651
+ }
6652
+ function listFooterColumns() {
6653
+ const root = getFooterLinksContainer() ?? getFooterRoot()?.querySelector(".rb-footer-links") ?? null;
6654
+ if (!root) return [];
6655
+ const explicit = Array.from(root.querySelectorAll(":scope > [data-ohw-footer-col]"));
6656
+ if (explicit.length > 0) return explicit;
6657
+ return Array.from(root.children).filter((el) => {
6658
+ if (!(el instanceof HTMLElement)) return false;
6659
+ return Array.from(el.querySelectorAll(":scope > [data-ohw-href-key]")).some(
6660
+ isFooterLinkAnchor
6661
+ );
6662
+ });
6663
+ }
6664
+ function listFooterLinksInColumn(column) {
6665
+ return Array.from(column.querySelectorAll(":scope > [data-ohw-href-key]")).filter(
6666
+ isFooterLinkAnchor
6667
+ );
6668
+ }
6669
+ function findFooterColumnForLink(anchor) {
6670
+ const columns = listFooterColumns();
6671
+ return columns.find((col) => col.contains(anchor)) ?? null;
6672
+ }
6673
+ function getFooterOrderFromDom() {
6674
+ return listFooterColumns().map(
6675
+ (col) => listFooterLinksInColumn(col).map((el) => el.getAttribute("data-ohw-href-key")).filter((key) => Boolean(key))
6676
+ );
6677
+ }
6678
+ function findFooterLinkByKey(hrefKey) {
6679
+ return document.querySelector(
6680
+ `footer [data-ohw-href-key="${CSS.escape(hrefKey)}"]`
6681
+ );
6682
+ }
6683
+ function syncFooterColumnIndices(columns) {
6684
+ columns.forEach((col, index) => {
6685
+ const next = String(index);
6686
+ if (col.getAttribute("data-ohw-footer-col") !== next) {
6687
+ col.setAttribute("data-ohw-footer-col", next);
6688
+ }
6689
+ });
6690
+ }
6691
+ function appendChildIfNeeded(parent, child) {
6692
+ if (parent.lastElementChild !== child) {
6693
+ parent.appendChild(child);
6694
+ }
6695
+ }
6696
+ function insertLinkBeforeNext(col, el, nextEl) {
6697
+ if (nextEl) {
6698
+ if (el.nextElementSibling !== nextEl) {
6699
+ col.insertBefore(el, nextEl);
6700
+ }
6701
+ return;
6702
+ }
6703
+ if (col.lastElementChild !== el) {
6704
+ col.appendChild(el);
6705
+ }
6706
+ }
6707
+ function applyFooterOrder(order) {
6708
+ const container = getFooterLinksContainer();
6709
+ if (!container) return;
6710
+ const columns = listFooterColumns();
6711
+ if (columns.length === 0) return;
6712
+ const currentOrder = getFooterOrderFromDom();
6713
+ if (ordersEqual(order, currentOrder)) {
6714
+ syncFooterColumnIndices(columns);
6715
+ return;
6716
+ }
6717
+ const colCount = Math.max(columns.length, order.length);
6718
+ const working = columns.slice(0, colCount);
6719
+ for (let i = 0; i < order.length && i < working.length; i++) {
6720
+ appendChildIfNeeded(container, working[i]);
6721
+ }
6722
+ for (let i = order.length; i < working.length; i++) {
6723
+ appendChildIfNeeded(container, working[i]);
6724
+ }
6725
+ const freshColumns = listFooterColumns();
6726
+ syncFooterColumnIndices(freshColumns);
6727
+ for (let c = 0; c < order.length; c++) {
6728
+ const col = freshColumns[c];
6729
+ if (!col) continue;
6730
+ const colOrder = order[c];
6731
+ for (let j = 0; j < colOrder.length; j++) {
6732
+ const hrefKey = colOrder[j];
6733
+ const el = findFooterLinkByKey(hrefKey);
6734
+ if (!el) continue;
6735
+ if (el.parentElement !== col) {
6736
+ col.appendChild(el);
6737
+ }
6738
+ const nextKey = colOrder[j + 1];
6739
+ const nextEl = nextKey ? findFooterLinkByKey(nextKey) : null;
6740
+ insertLinkBeforeNext(col, el, nextEl?.parentElement === col ? nextEl : null);
6741
+ }
6742
+ }
6743
+ }
6744
+ function ordersEqual(a, b) {
6745
+ if (a.length !== b.length) return false;
6746
+ for (let i = 0; i < a.length; i++) {
6747
+ const left = a[i];
6748
+ const right = b[i];
6749
+ if (left.length !== right.length) return false;
6750
+ for (let j = 0; j < left.length; j++) {
6751
+ if (left[j] !== right[j]) return false;
6752
+ }
6753
+ }
6754
+ return true;
6755
+ }
6756
+ function planFooterLinkMove(hrefKey, targetColIndex, insertIndex) {
6757
+ const order = getFooterOrderFromDom();
6758
+ if (targetColIndex < 0 || targetColIndex >= order.length) return null;
6759
+ let fromCol = -1;
6760
+ let fromIdx = -1;
6761
+ for (let c = 0; c < order.length; c++) {
6762
+ const idx = order[c].indexOf(hrefKey);
6763
+ if (idx >= 0) {
6764
+ fromCol = c;
6765
+ fromIdx = idx;
6766
+ break;
6767
+ }
6768
+ }
6769
+ if (fromCol < 0) return null;
6770
+ const next = order.map((col) => [...col]);
6771
+ next[fromCol].splice(fromIdx, 1);
6772
+ let adjusted = insertIndex;
6773
+ if (fromCol === targetColIndex && fromIdx < insertIndex) adjusted -= 1;
6774
+ adjusted = Math.max(0, Math.min(adjusted, next[targetColIndex].length));
6775
+ next[targetColIndex].splice(adjusted, 0, hrefKey);
6776
+ if (ordersEqual(next, order)) return null;
6777
+ return next;
6778
+ }
6779
+ function planFooterColumnMove(fromIndex, toIndex) {
6780
+ const order = getFooterOrderFromDom();
6781
+ const columns = listFooterColumns();
6782
+ if (fromIndex < 0 || fromIndex >= columns.length || toIndex < 0 || toIndex > columns.length) {
6783
+ return null;
6784
+ }
6785
+ if (toIndex === fromIndex || toIndex === fromIndex + 1) return null;
6786
+ const nextOrder = order.map((col) => [...col]);
6787
+ const [movedOrder] = nextOrder.splice(fromIndex, 1);
6788
+ if (!movedOrder) return null;
6789
+ let adjusted = toIndex;
6790
+ if (fromIndex < toIndex) adjusted -= 1;
6791
+ adjusted = Math.max(0, Math.min(adjusted, nextOrder.length));
6792
+ nextOrder.splice(adjusted, 0, movedOrder);
6793
+ if (ordersEqual(nextOrder, order)) return null;
6794
+ return nextOrder;
6795
+ }
6796
+ function parseFooterOrder(content) {
6797
+ const raw = content[FOOTER_ORDER_KEY];
6798
+ if (!raw) return null;
6799
+ try {
6800
+ const parsed = JSON.parse(raw);
6801
+ if (!Array.isArray(parsed)) return null;
6802
+ return parsed.filter((col) => Array.isArray(col)).map((col) => col.filter((key) => typeof key === "string" && key.length > 0));
6803
+ } catch {
6804
+ return null;
6805
+ }
6806
+ }
6807
+ function reconcileFooterOrderFromContent(content) {
6808
+ const order = parseFooterOrder(content);
6809
+ if (!order?.length) return;
6810
+ const current = getFooterOrderFromDom();
6811
+ if (ordersEqual(order, current)) {
6812
+ syncFooterColumnIndices(listFooterColumns());
6813
+ return;
6814
+ }
6815
+ applyFooterOrder(order);
6816
+ }
6817
+ function buildLinkDropSlots(column, columnIndex) {
6818
+ const links = listFooterLinksInColumn(column);
6819
+ const colRect = column.getBoundingClientRect();
6820
+ const slots = [];
6821
+ const barThickness = 3;
6822
+ if (links.length === 0) {
6823
+ slots.push({
6824
+ insertIndex: 0,
6825
+ columnIndex,
6826
+ left: colRect.left,
6827
+ top: colRect.top + colRect.height / 2 - barThickness / 2,
6828
+ width: colRect.width,
6829
+ height: barThickness,
6830
+ direction: "horizontal"
6831
+ });
6832
+ return slots;
6833
+ }
6834
+ for (let i = 0; i <= links.length; i++) {
6835
+ let top;
6836
+ if (i === 0) {
6837
+ top = links[0].getBoundingClientRect().top - barThickness / 2;
6838
+ } else if (i === links.length) {
6839
+ const last = links[links.length - 1].getBoundingClientRect();
6840
+ top = last.bottom - barThickness / 2;
6841
+ } else {
6842
+ const prev = links[i - 1].getBoundingClientRect();
6843
+ const next = links[i].getBoundingClientRect();
6844
+ top = (prev.bottom + next.top) / 2 - barThickness / 2;
6845
+ }
6846
+ const widthRef = i === 0 ? links[0].getBoundingClientRect() : i === links.length ? links[links.length - 1].getBoundingClientRect() : links[i].getBoundingClientRect();
6847
+ slots.push({
6848
+ insertIndex: i,
6849
+ columnIndex,
6850
+ left: widthRef.left,
6851
+ top,
6852
+ width: Math.max(widthRef.width, colRect.width * 0.8),
6853
+ height: barThickness,
6854
+ direction: "horizontal"
6855
+ });
6856
+ }
6857
+ return slots;
6858
+ }
6859
+ function buildColumnDropSlots() {
6860
+ const columns = listFooterColumns();
6861
+ const slots = [];
6862
+ const barThickness = 3;
6863
+ if (columns.length === 0) return slots;
6864
+ for (let i = 0; i <= columns.length; i++) {
6865
+ let left;
6866
+ let height;
6867
+ let top;
6868
+ if (i === 0) {
6869
+ const first = columns[0].getBoundingClientRect();
6870
+ left = first.left - barThickness / 2;
6871
+ top = first.top;
6872
+ height = first.height;
6873
+ } else if (i === columns.length) {
6874
+ const last = columns[columns.length - 1].getBoundingClientRect();
6875
+ left = last.right - barThickness / 2;
6876
+ top = last.top;
6877
+ height = last.height;
6878
+ } else {
6879
+ const prev = columns[i - 1].getBoundingClientRect();
6880
+ const next = columns[i].getBoundingClientRect();
6881
+ left = (prev.right + next.left) / 2 - barThickness / 2;
6882
+ top = Math.min(prev.top, next.top);
6883
+ height = Math.max(prev.bottom, next.bottom) - top;
6884
+ }
6885
+ slots.push({
6886
+ insertIndex: i,
6887
+ columnIndex: i,
6888
+ left,
6889
+ top,
6890
+ width: barThickness,
6891
+ height: Math.max(height, 24),
6892
+ direction: "vertical"
6893
+ });
6894
+ }
6895
+ return slots;
6896
+ }
6897
+ function hitTestLinkDropSlot(clientX, clientY, draggedHrefKey) {
6898
+ const columns = listFooterColumns();
6899
+ let best = null;
6900
+ for (let c = 0; c < columns.length; c++) {
6901
+ const col = columns[c];
6902
+ const colRect = col.getBoundingClientRect();
6903
+ const inColX = clientX >= colRect.left - 24 && clientX <= colRect.right + 24;
6904
+ if (!inColX) continue;
6905
+ const slots = buildLinkDropSlots(col, c).filter((slot) => {
6906
+ const links = listFooterLinksInColumn(col);
6907
+ const fromIdx = links.findIndex(
6908
+ (el) => el.getAttribute("data-ohw-href-key") === draggedHrefKey
6909
+ );
6910
+ if (fromIdx < 0) return true;
6911
+ if (c === findColumnIndexForKey(draggedHrefKey) && (slot.insertIndex === fromIdx || slot.insertIndex === fromIdx + 1)) {
6912
+ return true;
6913
+ }
6914
+ return true;
6915
+ });
6916
+ for (const slot of slots) {
6917
+ const cy = slot.top + slot.height / 2;
6918
+ const dist = Math.abs(clientY - cy) + (inColX ? 0 : 1e3);
6919
+ if (!best || dist < best.dist) best = { slot, dist };
6920
+ }
6921
+ }
6922
+ return best?.slot ?? null;
6923
+ }
6924
+ function findColumnIndexForKey(hrefKey) {
6925
+ const order = getFooterOrderFromDom();
6926
+ for (let c = 0; c < order.length; c++) {
6927
+ if (order[c].includes(hrefKey)) return c;
6928
+ }
6929
+ return -1;
6930
+ }
6931
+ function hitTestColumnDropSlot(clientX, _clientY) {
6932
+ const slots = buildColumnDropSlots();
6933
+ let best = null;
6934
+ for (const slot of slots) {
6935
+ const cx2 = slot.left + slot.width / 2;
6936
+ const dist = Math.abs(clientX - cx2);
6937
+ if (!best || dist < best.dist) best = { slot, dist };
6938
+ }
6939
+ return best?.slot ?? null;
6940
+ }
6941
+
6942
+ // src/lib/carousel.ts
6943
+ var import_react8 = require("react");
6944
+ var CAROUSEL_ATTR = "data-ohw-carousel";
6945
+ var CAROUSEL_VALUE_ATTR = "data-ohw-carousel-value";
6946
+ var CAROUSEL_SLIDE_ATTR = "data-ohw-carousel-slide";
6947
+ var CAROUSEL_EVENT = "ohw:carousel-change";
6948
+ function listCarouselKeys() {
6949
+ const keys = /* @__PURE__ */ new Set();
6950
+ document.querySelectorAll(`[${CAROUSEL_ATTR}]`).forEach((el) => {
6951
+ const key = el.getAttribute("data-ohw-key");
6952
+ if (key) keys.add(key);
6953
+ });
6954
+ return [...keys];
6955
+ }
6956
+ function containersForKey(key) {
6957
+ return Array.from(
6958
+ document.querySelectorAll(`[${CAROUSEL_ATTR}][data-ohw-key="${CSS.escape(key)}"]`)
6959
+ );
6960
+ }
6961
+ function isCarouselKey(key) {
6962
+ return containersForKey(key).length > 0;
6963
+ }
6964
+ function parseSlides(raw) {
6965
+ if (!raw) return [];
6966
+ try {
6967
+ const parsed = JSON.parse(raw);
6968
+ if (!Array.isArray(parsed)) return [];
6969
+ return parsed.filter((s) => Boolean(s) && typeof s === "object").map((s) => ({ src: String(s.src ?? ""), alt: String(s.alt ?? "") }));
6970
+ } catch {
6971
+ return [];
6972
+ }
6973
+ }
6974
+ function readCarouselValue(key) {
6975
+ const container = containersForKey(key)[0];
6976
+ if (!container) return [];
6977
+ const fromAttr = parseSlides(container.getAttribute(CAROUSEL_VALUE_ATTR));
6978
+ if (fromAttr.length > 0) return fromAttr;
6979
+ return Array.from(container.querySelectorAll(`[${CAROUSEL_SLIDE_ATTR}]`)).filter((slide) => slide.closest(`[${CAROUSEL_ATTR}]`) === container).sort((a, b) => slideIndex(a) - slideIndex(b)).map((slide) => {
6980
+ const img = slide instanceof HTMLImageElement ? slide : slide.querySelector("img");
6981
+ return { src: img?.src ?? "", alt: img?.alt ?? "" };
6982
+ });
6983
+ }
6984
+ function slideIndex(el) {
6985
+ const raw = el.getAttribute(CAROUSEL_SLIDE_ATTR);
6986
+ const n = raw ? parseInt(raw, 10) : NaN;
6987
+ return Number.isFinite(n) ? n : 0;
6988
+ }
6989
+ function applyCarouselValue(key, slides) {
6990
+ const value = JSON.stringify(slides);
6991
+ for (const container of containersForKey(key)) {
6992
+ if (container.getAttribute(CAROUSEL_VALUE_ATTR) === value) continue;
6993
+ container.setAttribute(CAROUSEL_VALUE_ATTR, value);
6994
+ container.dispatchEvent(
6995
+ new CustomEvent(CAROUSEL_EVENT, { detail: { images: slides } })
6996
+ );
6997
+ }
6998
+ }
6999
+ function applyCarouselNode(key, val) {
7000
+ if (!isCarouselKey(key)) return false;
7001
+ applyCarouselValue(key, parseSlides(val));
7002
+ return true;
7003
+ }
7004
+ function useOhwCarousel(key, initial) {
7005
+ const [images, setImages] = (0, import_react8.useState)(initial);
7006
+ (0, import_react8.useEffect)(() => {
7007
+ const el = document.querySelector(
7008
+ `[${CAROUSEL_ATTR}][data-ohw-key="${CSS.escape(key)}"]`
7009
+ );
7010
+ if (!el) return;
7011
+ const onChange = (e) => {
7012
+ const detail = e.detail;
7013
+ if (Array.isArray(detail?.images)) setImages(detail.images);
7014
+ };
7015
+ el.addEventListener(CAROUSEL_EVENT, onChange);
7016
+ return () => el.removeEventListener(CAROUSEL_EVENT, onChange);
7017
+ }, [key]);
7018
+ const bind = {
7019
+ [CAROUSEL_ATTR]: "",
7020
+ "data-ohw-key": key,
7021
+ [CAROUSEL_VALUE_ATTR]: JSON.stringify(images)
7022
+ };
7023
+ return { images, setImages, bind };
7024
+ }
7025
+
7026
+ // src/ui/navbar-container-chrome.tsx
7027
+ var import_lucide_react11 = require("lucide-react");
7028
+ var import_jsx_runtime22 = require("react/jsx-runtime");
7029
+ function NavbarContainerChrome({
7030
+ rect,
7031
+ onAdd
7032
+ }) {
7033
+ const chromeGap = 6;
7034
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
7035
+ "div",
7036
+ {
7037
+ "data-ohw-navbar-container-chrome": "",
7038
+ "data-ohw-bridge": "",
7039
+ className: "pointer-events-none fixed z-[2147483647]",
7040
+ style: {
7041
+ top: rect.top - chromeGap,
7042
+ left: rect.left - chromeGap,
7043
+ width: rect.width + chromeGap * 2,
7044
+ height: rect.height + chromeGap * 2
7045
+ },
7046
+ children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
7047
+ "button",
7048
+ {
7049
+ type: "button",
7050
+ "data-ohw-navbar-add-button": "",
7051
+ 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",
7052
+ style: { top: "70%" },
7053
+ "aria-label": "Add item",
7054
+ onMouseDown: (e) => {
7055
+ e.preventDefault();
7056
+ e.stopPropagation();
7057
+ },
7058
+ onClick: (e) => {
7059
+ e.preventDefault();
7060
+ e.stopPropagation();
7061
+ onAdd();
7062
+ },
7063
+ children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react11.Plus, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
7064
+ }
7065
+ )
7066
+ }
7067
+ );
7068
+ }
7069
+
7070
+ // src/ui/drop-indicator.tsx
7071
+ var React9 = __toESM(require("react"), 1);
7072
+ var import_jsx_runtime23 = require("react/jsx-runtime");
7073
+ var dropIndicatorVariants = cva(
7074
+ "ov-gap-line pointer-events-none shrink-0 transition-opacity duration-150",
7075
+ {
7076
+ variants: {
7077
+ direction: {
7078
+ vertical: "h-6 w-[3px]",
7079
+ horizontal: "h-[3px] w-[200px]"
7080
+ },
7081
+ state: {
7082
+ default: "opacity-0",
7083
+ hover: "opacity-100",
7084
+ dragIdle: "opacity-40",
7085
+ dragActive: "opacity-100"
7086
+ }
7087
+ },
7088
+ defaultVariants: {
7089
+ direction: "vertical",
7090
+ state: "default"
7091
+ }
7092
+ }
7093
+ );
7094
+ var DropIndicator = React9.forwardRef(
7095
+ ({ className, direction, state, ...props }, ref) => {
7096
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
7097
+ "div",
7098
+ {
7099
+ ref,
7100
+ "data-slot": "drop-indicator",
7101
+ "data-direction": direction ?? "vertical",
7102
+ "data-state": state ?? "default",
7103
+ "aria-hidden": "true",
7104
+ className: cn(dropIndicatorVariants({ direction, state }), className),
7105
+ ...props
7106
+ }
7107
+ );
7108
+ }
7109
+ );
7110
+ DropIndicator.displayName = "DropIndicator";
7111
+
5783
7112
  // src/ui/badge.tsx
5784
- var import_jsx_runtime21 = require("react/jsx-runtime");
7113
+ var import_jsx_runtime24 = require("react/jsx-runtime");
5785
7114
  var badgeVariants = cva(
5786
7115
  "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
5787
7116
  {
@@ -5799,12 +7128,12 @@ var badgeVariants = cva(
5799
7128
  }
5800
7129
  );
5801
7130
  function Badge({ className, variant, ...props }) {
5802
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: cn(badgeVariants({ variant }), className), ...props });
7131
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: cn(badgeVariants({ variant }), className), ...props });
5803
7132
  }
5804
7133
 
5805
7134
  // src/OhhwellsBridge.tsx
5806
- var import_lucide_react4 = require("lucide-react");
5807
- var import_jsx_runtime22 = require("react/jsx-runtime");
7135
+ var import_lucide_react12 = require("lucide-react");
7136
+ var import_jsx_runtime25 = require("react/jsx-runtime");
5808
7137
  var PRIMARY2 = "#0885FE";
5809
7138
  var IMAGE_FADE_MS = 300;
5810
7139
  function runOpacityFade(el, onDone) {
@@ -5981,9 +7310,9 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
5981
7310
  tail.insertAdjacentElement("afterend", container);
5982
7311
  }
5983
7312
  const root = (0, import_client.createRoot)(container);
5984
- (0, import_react_dom.flushSync)(() => {
7313
+ (0, import_react_dom2.flushSync)(() => {
5985
7314
  root.render(
5986
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
7315
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
5987
7316
  SchedulingWidget,
5988
7317
  {
5989
7318
  notifyOnConnect,
@@ -6023,7 +7352,7 @@ function mountSchedulingEntries(entries, notifyOnConnect = false) {
6023
7352
  }
6024
7353
  }
6025
7354
  }
6026
- function getLinkHref(el) {
7355
+ function getLinkHref2(el) {
6027
7356
  const anchor = el instanceof HTMLAnchorElement ? el : el.querySelector("a");
6028
7357
  return anchor?.getAttribute("href") ?? "";
6029
7358
  }
@@ -6045,7 +7374,7 @@ function isDragHandleDisabled(el) {
6045
7374
  return raw === "true" || raw === "";
6046
7375
  });
6047
7376
  }
6048
- function collectEditableNodes() {
7377
+ function collectEditableNodes(extraContent) {
6049
7378
  const nodes = Array.from(document.querySelectorAll("[data-ohw-editable]")).map((el) => {
6050
7379
  if (el.dataset.ohwEditable === "image") {
6051
7380
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -6060,7 +7389,7 @@ function collectEditableNodes() {
6060
7389
  return { key: el.dataset.ohwKey ?? "", type: "video", text: getVideoEl(el)?.src ?? "" };
6061
7390
  }
6062
7391
  if (el.dataset.ohwEditable === "link") {
6063
- return { key: el.dataset.ohwKey ?? "", type: "link", text: getLinkHref(el) };
7392
+ return { key: el.dataset.ohwKey ?? "", type: "link", text: getLinkHref2(el) };
6064
7393
  }
6065
7394
  return {
6066
7395
  key: el.dataset.ohwKey ?? "",
@@ -6071,8 +7400,16 @@ function collectEditableNodes() {
6071
7400
  document.querySelectorAll("[data-ohw-href-key]").forEach((el) => {
6072
7401
  const key = el.getAttribute("data-ohw-href-key") ?? "";
6073
7402
  if (!key) return;
6074
- nodes.push({ key, type: "link", text: getLinkHref(el) });
7403
+ nodes.push({ key, type: "link", text: getLinkHref2(el) });
6075
7404
  });
7405
+ if (extraContent) {
7406
+ for (const key of [NAV_ORDER_KEY, FOOTER_ORDER_KEY, NAV_COUNT_KEY]) {
7407
+ const text = extraContent[key];
7408
+ if (typeof text === "string" && text.length > 0) {
7409
+ nodes.push({ key, type: "meta", text });
7410
+ }
7411
+ }
7412
+ }
6076
7413
  document.querySelectorAll('[data-ohw-editable="video"]').forEach((el) => {
6077
7414
  const key = el.dataset.ohwKey ?? "";
6078
7415
  const video = getVideoEl(el);
@@ -6080,6 +7417,9 @@ function collectEditableNodes() {
6080
7417
  nodes.push({ key: `${key}${VIDEO_AUTOPLAY_SUFFIX}`, type: "video-setting", text: String(video.autoplay) });
6081
7418
  nodes.push({ key: `${key}${VIDEO_MUTED_SUFFIX}`, type: "video-setting", text: String(video.muted) });
6082
7419
  });
7420
+ for (const key of listCarouselKeys()) {
7421
+ nodes.push({ key, type: "carousel", text: JSON.stringify(readCarouselValue(key)) });
7422
+ }
6083
7423
  return nodes;
6084
7424
  }
6085
7425
  function isMediaEditable(el) {
@@ -6157,7 +7497,7 @@ function applyLinkByKey(key, val) {
6157
7497
  }
6158
7498
  function isInsideLinkEditor(target) {
6159
7499
  return Boolean(
6160
- 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"]')
7500
+ 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"]')
6161
7501
  );
6162
7502
  }
6163
7503
  function getHrefKeyFromElement(el) {
@@ -6168,7 +7508,30 @@ function getHrefKeyFromElement(el) {
6168
7508
  if (!key) return null;
6169
7509
  return { anchor, key };
6170
7510
  }
6171
- function isNavbarButton(el) {
7511
+ function disableNativeHrefDrag(el) {
7512
+ if (el.draggable) el.draggable = false;
7513
+ if (el.getAttribute("draggable") !== "false") {
7514
+ el.setAttribute("draggable", "false");
7515
+ }
7516
+ }
7517
+ function clearTextSelection() {
7518
+ const sel = window.getSelection();
7519
+ if (sel && !sel.isCollapsed) sel.removeAllRanges();
7520
+ }
7521
+ function armFooterPressDrag() {
7522
+ document.documentElement.setAttribute("data-ohw-footer-press-drag", "");
7523
+ }
7524
+ function lockFooterDuringDrag() {
7525
+ document.documentElement.removeAttribute("data-ohw-footer-press-drag");
7526
+ document.documentElement.setAttribute("data-ohw-item-dragging", "");
7527
+ clearTextSelection();
7528
+ }
7529
+ function unlockFooterDragInteraction() {
7530
+ document.documentElement.removeAttribute("data-ohw-footer-press-drag");
7531
+ document.documentElement.removeAttribute("data-ohw-item-dragging");
7532
+ clearTextSelection();
7533
+ }
7534
+ function isNavbarButton2(el) {
6172
7535
  return Boolean(el.closest('[data-ohw-role="navbar-button"]'));
6173
7536
  }
6174
7537
  function getNavigationItemAnchor(el) {
@@ -6180,19 +7543,8 @@ function getNavigationItemAnchor(el) {
6180
7543
  function isNavigationItem(el) {
6181
7544
  return getNavigationItemAnchor(el) !== null;
6182
7545
  }
6183
- function getNavigationItemAddHintTarget(anchor) {
6184
- const items = listNavigationItems();
6185
- const idx = items.indexOf(anchor);
6186
- if (idx >= 0 && idx < items.length - 1) return items[idx + 1];
6187
- return anchor;
6188
- }
6189
- function listNavigationItems() {
6190
- return Array.from(
6191
- document.querySelectorAll("nav [data-ohw-href-key], footer [data-ohw-href-key]")
6192
- ).filter((el) => isNavigationItem(el));
6193
- }
6194
7546
  function getNavigationItemReorderState(anchor) {
6195
- if (isNavbarButton(anchor)) return { key: null, disabled: false };
7547
+ if (isNavbarButton2(anchor)) return { key: null, disabled: false };
6196
7548
  return getReorderHandleStateFromAnchor(anchor);
6197
7549
  }
6198
7550
  function getReorderHandleState(el) {
@@ -6214,6 +7566,120 @@ function clearHrefKeyHover(anchor) {
6214
7566
  function isInsideNavigationItem(el) {
6215
7567
  return getNavigationItemAnchor(el) !== null;
6216
7568
  }
7569
+ function getNavigationRoot(el) {
7570
+ const explicit = el.closest("[data-ohw-nav-root]");
7571
+ if (explicit) return explicit;
7572
+ return el.closest("nav, footer, aside");
7573
+ }
7574
+ function findFooterItemGroup(item) {
7575
+ const footer = item.closest("footer");
7576
+ if (!footer) return null;
7577
+ let node = item.parentElement;
7578
+ while (node && node !== footer) {
7579
+ const hasDirectNavItems = Array.from(
7580
+ node.querySelectorAll(":scope > [data-ohw-href-key]")
7581
+ ).some(isNavigationItem);
7582
+ if (hasDirectNavItems) return node;
7583
+ node = node.parentElement;
7584
+ }
7585
+ return null;
7586
+ }
7587
+ function isNavigationRoot(el) {
7588
+ return el.hasAttribute("data-ohw-nav-root") || el.matches("nav, footer, aside");
7589
+ }
7590
+ function isInferredFooterGroup(el) {
7591
+ const footer = el.closest("footer");
7592
+ if (!footer || el === footer) return false;
7593
+ return Array.from(el.querySelectorAll(":scope > [data-ohw-href-key]")).some(isNavigationItem);
7594
+ }
7595
+ function isNavigationContainer(el) {
7596
+ return el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-footer-col") || isNavigationRoot(el) || isInferredFooterGroup(el);
7597
+ }
7598
+ function isPointOverNavigation(x, y) {
7599
+ const navRoot = document.querySelector("[data-ohw-nav-root]") ?? document.querySelector("nav");
7600
+ if (!navRoot) return false;
7601
+ const r2 = navRoot.getBoundingClientRect();
7602
+ return x >= r2.left && x <= r2.right && y >= r2.top && y <= r2.bottom;
7603
+ }
7604
+ function isPointOverNavItem(container, x, y) {
7605
+ return Array.from(container.querySelectorAll("[data-ohw-href-key]")).some((el) => {
7606
+ if (!isNavigationItem(el) || isNavbarButton2(el)) return false;
7607
+ const itemRect = el.getBoundingClientRect();
7608
+ return x >= itemRect.left && x <= itemRect.right && y >= itemRect.top && y <= itemRect.bottom;
7609
+ });
7610
+ }
7611
+ function resolveNavContainerSelectionTarget(target, clientX, clientY) {
7612
+ if (getNavigationItemAnchor(target)) return null;
7613
+ if (target.closest('[data-ohw-role="navbar-button"]')) return null;
7614
+ const plainLink = target.closest("a");
7615
+ if (plainLink && !plainLink.hasAttribute("data-ohw-href-key") && !plainLink.closest("[data-ohw-nav-container]")) {
7616
+ return null;
7617
+ }
7618
+ const fromClosest = target.closest("[data-ohw-nav-container]");
7619
+ if (fromClosest && !isPointOverNavItem(fromClosest, clientX, clientY)) {
7620
+ return fromClosest;
7621
+ }
7622
+ const navRoot = target.closest("[data-ohw-nav-root]");
7623
+ if (!navRoot) return null;
7624
+ const container = navRoot.querySelector("[data-ohw-nav-container]");
7625
+ if (!container || isPointOverNavItem(container, clientX, clientY)) return null;
7626
+ if (target === navRoot || target.hasAttribute("data-ohw-nav-root")) {
7627
+ return container;
7628
+ }
7629
+ return null;
7630
+ }
7631
+ function getNavigationSelectionParent(el) {
7632
+ if (isNavigationItem(el)) {
7633
+ const explicit = el.closest("[data-ohw-nav-container]");
7634
+ if (explicit) return explicit;
7635
+ const footerGroup = findFooterItemGroup(el);
7636
+ if (footerGroup) return footerGroup;
7637
+ return getNavigationRoot(el);
7638
+ }
7639
+ if (el.hasAttribute("data-ohw-nav-container") || isInferredFooterGroup(el)) {
7640
+ return getNavigationRoot(el);
7641
+ }
7642
+ return null;
7643
+ }
7644
+ function placeCaretAtPoint(el, x, y) {
7645
+ const selection = window.getSelection();
7646
+ if (!selection) return;
7647
+ if (typeof document.caretRangeFromPoint === "function") {
7648
+ const range = document.caretRangeFromPoint(x, y);
7649
+ if (range && el.contains(range.startContainer)) {
7650
+ selection.removeAllRanges();
7651
+ selection.addRange(range);
7652
+ return;
7653
+ }
7654
+ }
7655
+ const caretPositionFromPoint = document.caretPositionFromPoint;
7656
+ if (typeof caretPositionFromPoint === "function") {
7657
+ const position = caretPositionFromPoint(x, y);
7658
+ if (position && el.contains(position.offsetNode)) {
7659
+ const range = document.createRange();
7660
+ range.setStart(position.offsetNode, position.offset);
7661
+ range.collapse(true);
7662
+ selection.removeAllRanges();
7663
+ selection.addRange(range);
7664
+ }
7665
+ }
7666
+ }
7667
+ function selectAllTextInEditable(el) {
7668
+ const selection = window.getSelection();
7669
+ if (!selection) return;
7670
+ const range = document.createRange();
7671
+ range.selectNodeContents(el);
7672
+ selection.removeAllRanges();
7673
+ selection.addRange(range);
7674
+ }
7675
+ function getNavigationLabelEditable(target) {
7676
+ const editable = target.closest('[data-ohw-editable="text"]');
7677
+ if (!editable) return null;
7678
+ const hrefCtx = getHrefKeyFromElement(editable);
7679
+ const navAnchor = hrefCtx ? getNavigationItemAnchor(hrefCtx.anchor) : null;
7680
+ if (!navAnchor) return null;
7681
+ return { editable, navAnchor };
7682
+ }
6217
7683
  function collectSections() {
6218
7684
  return collectSectionsFromDom();
6219
7685
  }
@@ -6337,6 +7803,8 @@ var ICONS = {
6337
7803
  insertUnorderedList: '<line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/>',
6338
7804
  insertOrderedList: '<line x1="10" y1="6" x2="21" y2="6"/><line x1="10" y1="12" x2="21" y2="12"/><line x1="10" y1="18" x2="21" y2="18"/><path d="M4 6h1v4"/><path d="M4 10h2"/><path d="M6 18H4c0-1 2-2 2-3s-1-1.5-2-1"/>'
6339
7805
  };
7806
+ var TOOLBAR_PILL_PADDING = 2;
7807
+ var TOOLBAR_TOGGLE_GAP = 4;
6340
7808
  var TOOLBAR_GROUPS = [
6341
7809
  [
6342
7810
  { cmd: "bold", title: "Bold" },
@@ -6361,7 +7829,7 @@ function EditGlowChrome({
6361
7829
  dragDisabled = false
6362
7830
  }) {
6363
7831
  const GAP = 6;
6364
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
7832
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
6365
7833
  "div",
6366
7834
  {
6367
7835
  ref: elRef,
@@ -6376,7 +7844,7 @@ function EditGlowChrome({
6376
7844
  zIndex: 2147483646
6377
7845
  },
6378
7846
  children: [
6379
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
7847
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
6380
7848
  "div",
6381
7849
  {
6382
7850
  style: {
@@ -6389,7 +7857,7 @@ function EditGlowChrome({
6389
7857
  }
6390
7858
  }
6391
7859
  ),
6392
- reorderHrefKey && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
7860
+ reorderHrefKey && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
6393
7861
  "div",
6394
7862
  {
6395
7863
  "data-ohw-drag-handle-container": "",
@@ -6401,7 +7869,7 @@ function EditGlowChrome({
6401
7869
  transform: "translate(calc(-100% - 7px), -50%)",
6402
7870
  pointerEvents: dragDisabled ? "none" : "auto"
6403
7871
  },
6404
- children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
7872
+ children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
6405
7873
  DragHandle,
6406
7874
  {
6407
7875
  "aria-label": `Reorder ${reorderHrefKey}`,
@@ -6505,7 +7973,7 @@ function FloatingToolbar({
6505
7973
  onEditLink
6506
7974
  }) {
6507
7975
  const { top, left, transform } = calcToolbarPos(rect, parentScroll, 330);
6508
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
7976
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
6509
7977
  "div",
6510
7978
  {
6511
7979
  ref: elRef,
@@ -6515,14 +7983,23 @@ function FloatingToolbar({
6515
7983
  left,
6516
7984
  transform,
6517
7985
  zIndex: 2147483647,
7986
+ background: "#fff",
7987
+ border: "1px solid #E7E5E4",
7988
+ borderRadius: 6,
7989
+ boxShadow: "0px 2px 4px -2px rgba(0,0,0,.1),0px 4px 6px -1px rgba(0,0,0,.1)",
7990
+ display: "flex",
7991
+ alignItems: "center",
7992
+ padding: TOOLBAR_PILL_PADDING,
7993
+ gap: TOOLBAR_TOGGLE_GAP,
7994
+ fontFamily: "sans-serif",
6518
7995
  pointerEvents: "auto"
6519
7996
  },
6520
- children: /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(CustomToolbar, { children: [
6521
- TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_react6.default.Fragment, { children: [
6522
- gi > 0 && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(CustomToolbarDivider, {}),
7997
+ children: /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(CustomToolbar, { children: [
7998
+ TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(import_react9.default.Fragment, { children: [
7999
+ gi > 0 && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(CustomToolbarDivider, {}),
6523
8000
  btns.map((btn) => {
6524
8001
  const isActive = activeCommands.has(btn.cmd);
6525
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8002
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
6526
8003
  CustomToolbarButton,
6527
8004
  {
6528
8005
  title: btn.title,
@@ -6531,7 +8008,7 @@ function FloatingToolbar({
6531
8008
  e.preventDefault();
6532
8009
  onCommand(btn.cmd);
6533
8010
  },
6534
- children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8011
+ children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
6535
8012
  "svg",
6536
8013
  {
6537
8014
  width: "16",
@@ -6552,7 +8029,7 @@ function FloatingToolbar({
6552
8029
  );
6553
8030
  })
6554
8031
  ] }, gi)),
6555
- showEditLink ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8032
+ showEditLink ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
6556
8033
  CustomToolbarButton,
6557
8034
  {
6558
8035
  type: "button",
@@ -6566,7 +8043,7 @@ function FloatingToolbar({
6566
8043
  e.preventDefault();
6567
8044
  e.stopPropagation();
6568
8045
  },
6569
- children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react4.Link, { className: "size-4 shrink-0", "aria-hidden": true })
8046
+ children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_lucide_react12.Link, { className: "size-4 shrink-0", "aria-hidden": true })
6570
8047
  }
6571
8048
  ) : null
6572
8049
  ] })
@@ -6583,7 +8060,7 @@ function StateToggle({
6583
8060
  states,
6584
8061
  onStateChange
6585
8062
  }) {
6586
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8063
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
6587
8064
  ToggleGroup,
6588
8065
  {
6589
8066
  "data-ohw-state-toggle": "",
@@ -6597,7 +8074,7 @@ function StateToggle({
6597
8074
  left: rect.right - 8,
6598
8075
  transform: "translateX(-100%)"
6599
8076
  },
6600
- children: states.map((state) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
8077
+ children: states.map((state) => /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
6601
8078
  }
6602
8079
  );
6603
8080
  }
@@ -6620,12 +8097,12 @@ function resolveSubdomain(subdomainFromQuery) {
6620
8097
  return "";
6621
8098
  }
6622
8099
  function OhhwellsBridge() {
6623
- const pathname = (0, import_navigation.usePathname)();
6624
- const router = (0, import_navigation.useRouter)();
6625
- const searchParams = (0, import_navigation.useSearchParams)();
8100
+ const pathname = (0, import_navigation2.usePathname)();
8101
+ const router = (0, import_navigation2.useRouter)();
8102
+ const searchParams = (0, import_navigation2.useSearchParams)();
6626
8103
  const isEditMode = isEditSessionActive();
6627
- const [bridgeRoot, setBridgeRoot] = (0, import_react6.useState)(null);
6628
- (0, import_react6.useEffect)(() => {
8104
+ const [bridgeRoot, setBridgeRoot] = (0, import_react9.useState)(null);
8105
+ (0, import_react9.useEffect)(() => {
6629
8106
  const figtreeFontId = "ohw-figtree-font";
6630
8107
  if (!document.getElementById(figtreeFontId)) {
6631
8108
  const preconnect1 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.googleapis.com" });
@@ -6653,83 +8130,100 @@ function OhhwellsBridge() {
6653
8130
  const subdomainFromQuery = searchParams.get("subdomain");
6654
8131
  const subdomain = resolveSubdomain(subdomainFromQuery);
6655
8132
  useLinkHrefGuardian(pathname, subdomain, isEditMode);
6656
- const postToParent = (0, import_react6.useCallback)((data) => {
8133
+ const postToParent2 = (0, import_react9.useCallback)((data) => {
6657
8134
  if (typeof window !== "undefined" && window.parent !== window) {
6658
8135
  window.parent.postMessage(data, "*");
6659
8136
  }
6660
8137
  }, []);
6661
- const [fetchState, setFetchState] = (0, import_react6.useState)("idle");
6662
- const autoSaveTimers = (0, import_react6.useRef)(/* @__PURE__ */ new Map());
6663
- const activeElRef = (0, import_react6.useRef)(null);
6664
- const selectedElRef = (0, import_react6.useRef)(null);
6665
- const originalContentRef = (0, import_react6.useRef)(null);
6666
- const activeStateElRef = (0, import_react6.useRef)(null);
6667
- const parentScrollRef = (0, import_react6.useRef)(null);
6668
- const visibleViewportRef = (0, import_react6.useRef)(null);
6669
- const [dialogPortalContainer, setDialogPortalContainer] = (0, import_react6.useState)(null);
6670
- const attachVisibleViewport = (0, import_react6.useCallback)((node) => {
8138
+ const [fetchState, setFetchState] = (0, import_react9.useState)("idle");
8139
+ const autoSaveTimers = (0, import_react9.useRef)(/* @__PURE__ */ new Map());
8140
+ const activeElRef = (0, import_react9.useRef)(null);
8141
+ const selectedElRef = (0, import_react9.useRef)(null);
8142
+ const originalContentRef = (0, import_react9.useRef)(null);
8143
+ const activeStateElRef = (0, import_react9.useRef)(null);
8144
+ const parentScrollRef = (0, import_react9.useRef)(null);
8145
+ const visibleViewportRef = (0, import_react9.useRef)(null);
8146
+ const [dialogPortalContainer, setDialogPortalContainer] = (0, import_react9.useState)(null);
8147
+ const attachVisibleViewport = (0, import_react9.useCallback)((node) => {
6671
8148
  visibleViewportRef.current = node;
6672
8149
  setDialogPortalContainer(node);
6673
8150
  if (node) applyVisibleViewport(node, parentScrollRef.current);
6674
8151
  }, []);
6675
- const toolbarElRef = (0, import_react6.useRef)(null);
6676
- const glowElRef = (0, import_react6.useRef)(null);
6677
- const hoveredImageRef = (0, import_react6.useRef)(null);
6678
- const hoveredImageHasTextOverlapRef = (0, import_react6.useRef)(false);
6679
- const dragOverElRef = (0, import_react6.useRef)(null);
6680
- const [mediaHover, setMediaHover] = (0, import_react6.useState)(null);
6681
- const [uploadingRects, setUploadingRects] = (0, import_react6.useState)({});
6682
- const hoveredGapRef = (0, import_react6.useRef)(null);
6683
- const imageUnhoverTimerRef = (0, import_react6.useRef)(null);
6684
- const imageShowTimerRef = (0, import_react6.useRef)(null);
6685
- const editStylesRef = (0, import_react6.useRef)(null);
6686
- const activateRef = (0, import_react6.useRef)(() => {
8152
+ const toolbarElRef = (0, import_react9.useRef)(null);
8153
+ const glowElRef = (0, import_react9.useRef)(null);
8154
+ const hoveredImageRef = (0, import_react9.useRef)(null);
8155
+ const hoveredImageHasTextOverlapRef = (0, import_react9.useRef)(false);
8156
+ const dragOverElRef = (0, import_react9.useRef)(null);
8157
+ const [mediaHover, setMediaHover] = (0, import_react9.useState)(null);
8158
+ const [carouselHover, setCarouselHover] = (0, import_react9.useState)(null);
8159
+ const [uploadingRects, setUploadingRects] = (0, import_react9.useState)({});
8160
+ const hoveredGapRef = (0, import_react9.useRef)(null);
8161
+ const imageUnhoverTimerRef = (0, import_react9.useRef)(null);
8162
+ const imageShowTimerRef = (0, import_react9.useRef)(null);
8163
+ const editStylesRef = (0, import_react9.useRef)(null);
8164
+ const activateRef = (0, import_react9.useRef)(() => {
8165
+ });
8166
+ const deactivateRef = (0, import_react9.useRef)(() => {
6687
8167
  });
6688
- const deactivateRef = (0, import_react6.useRef)(() => {
8168
+ const selectRef = (0, import_react9.useRef)(() => {
6689
8169
  });
6690
- const selectRef = (0, import_react6.useRef)(() => {
8170
+ const selectFrameRef = (0, import_react9.useRef)(() => {
6691
8171
  });
6692
- const deselectRef = (0, import_react6.useRef)(() => {
8172
+ const deselectRef = (0, import_react9.useRef)(() => {
6693
8173
  });
6694
- const reselectNavigationItemRef = (0, import_react6.useRef)(() => {
8174
+ const reselectNavigationItemRef = (0, import_react9.useRef)(() => {
6695
8175
  });
6696
- const commitNavigationTextEditRef = (0, import_react6.useRef)(() => {
8176
+ const commitNavigationTextEditRef = (0, import_react9.useRef)(() => {
6697
8177
  });
6698
- const refreshActiveCommandsRef = (0, import_react6.useRef)(() => {
8178
+ const refreshActiveCommandsRef = (0, import_react9.useRef)(() => {
6699
8179
  });
6700
- const postToParentRef = (0, import_react6.useRef)(postToParent);
6701
- postToParentRef.current = postToParent;
6702
- const sectionsLoadedRef = (0, import_react6.useRef)(false);
6703
- const pendingScheduleConfigRequests = (0, import_react6.useRef)([]);
6704
- const [toolbarRect, setToolbarRect] = (0, import_react6.useState)(null);
6705
- const [toolbarVariant, setToolbarVariant] = (0, import_react6.useState)("none");
6706
- const toolbarVariantRef = (0, import_react6.useRef)("none");
8180
+ const postToParentRef = (0, import_react9.useRef)(postToParent2);
8181
+ postToParentRef.current = postToParent2;
8182
+ const sectionsLoadedRef = (0, import_react9.useRef)(false);
8183
+ const pendingScheduleConfigRequests = (0, import_react9.useRef)([]);
8184
+ const [toolbarRect, setToolbarRect] = (0, import_react9.useState)(null);
8185
+ const [toolbarVariant, setToolbarVariant] = (0, import_react9.useState)("none");
8186
+ const toolbarVariantRef = (0, import_react9.useRef)("none");
6707
8187
  toolbarVariantRef.current = toolbarVariant;
6708
- const [reorderHrefKey, setReorderHrefKey] = (0, import_react6.useState)(null);
6709
- const [reorderDragDisabled, setReorderDragDisabled] = (0, import_react6.useState)(false);
6710
- const [toggleState, setToggleState] = (0, import_react6.useState)(null);
6711
- const [maxBadge, setMaxBadge] = (0, import_react6.useState)(null);
6712
- const [activeCommands, setActiveCommands] = (0, import_react6.useState)(/* @__PURE__ */ new Set());
6713
- const [sectionGap, setSectionGap] = (0, import_react6.useState)(null);
6714
- const [toolbarShowEditLink, setToolbarShowEditLink] = (0, import_react6.useState)(false);
6715
- const hoveredItemElRef = (0, import_react6.useRef)(null);
6716
- const [hoveredItemRect, setHoveredItemRect] = (0, import_react6.useState)(null);
6717
- const siblingHintElRef = (0, import_react6.useRef)(null);
6718
- const [siblingHintRect, setSiblingHintRect] = (0, import_react6.useState)(null);
6719
- const [isItemDragging, setIsItemDragging] = (0, import_react6.useState)(false);
6720
- const [linkPopover, setLinkPopover] = (0, import_react6.useState)(null);
6721
- const [sitePages, setSitePages] = (0, import_react6.useState)([]);
6722
- const [sectionsByPath, setSectionsByPath] = (0, import_react6.useState)({});
6723
- const sectionsPrefetchGenRef = (0, import_react6.useRef)(0);
6724
- const setLinkPopoverRef = (0, import_react6.useRef)(setLinkPopover);
6725
- const linkPopoverPanelRef = (0, import_react6.useRef)(null);
6726
- const linkPopoverOpenRef = (0, import_react6.useRef)(false);
6727
- const linkPopoverGraceUntilRef = (0, import_react6.useRef)(0);
8188
+ const [reorderHrefKey, setReorderHrefKey] = (0, import_react9.useState)(null);
8189
+ const [reorderDragDisabled, setReorderDragDisabled] = (0, import_react9.useState)(false);
8190
+ const [toggleState, setToggleState] = (0, import_react9.useState)(null);
8191
+ const [maxBadge, setMaxBadge] = (0, import_react9.useState)(null);
8192
+ const [activeCommands, setActiveCommands] = (0, import_react9.useState)(/* @__PURE__ */ new Set());
8193
+ const [sectionGap, setSectionGap] = (0, import_react9.useState)(null);
8194
+ const [toolbarShowEditLink, setToolbarShowEditLink] = (0, import_react9.useState)(false);
8195
+ const hoveredNavContainerRef = (0, import_react9.useRef)(null);
8196
+ const [hoveredNavContainerRect, setHoveredNavContainerRect] = (0, import_react9.useState)(null);
8197
+ const hoveredItemElRef = (0, import_react9.useRef)(null);
8198
+ const [hoveredItemRect, setHoveredItemRect] = (0, import_react9.useState)(null);
8199
+ const siblingHintElRef = (0, import_react9.useRef)(null);
8200
+ const [siblingHintRect, setSiblingHintRect] = (0, import_react9.useState)(null);
8201
+ const [siblingHintRects, setSiblingHintRects] = (0, import_react9.useState)([]);
8202
+ const [isItemDragging, setIsItemDragging] = (0, import_react9.useState)(false);
8203
+ const [isFooterFrameSelection, setIsFooterFrameSelection] = (0, import_react9.useState)(false);
8204
+ const footerDragRef = (0, import_react9.useRef)(null);
8205
+ const [footerDropSlots, setFooterDropSlots] = (0, import_react9.useState)([]);
8206
+ const [activeFooterDropIndex, setActiveFooterDropIndex] = (0, import_react9.useState)(null);
8207
+ const [draggedItemRect, setDraggedItemRect] = (0, import_react9.useState)(null);
8208
+ const footerPointerDragRef = (0, import_react9.useRef)(null);
8209
+ const suppressNextClickRef = (0, import_react9.useRef)(false);
8210
+ const [linkPopover, setLinkPopover] = (0, import_react9.useState)(null);
8211
+ const linkPopoverSessionRef = (0, import_react9.useRef)(null);
8212
+ const addNavAfterAnchorRef = (0, import_react9.useRef)(null);
8213
+ const editContentRef = (0, import_react9.useRef)({});
8214
+ const [sitePages, setSitePages] = (0, import_react9.useState)([]);
8215
+ const [sectionsByPath, setSectionsByPath] = (0, import_react9.useState)({});
8216
+ const sectionsPrefetchGenRef = (0, import_react9.useRef)(0);
8217
+ const setLinkPopoverRef = (0, import_react9.useRef)(setLinkPopover);
8218
+ const linkPopoverPanelRef = (0, import_react9.useRef)(null);
8219
+ const linkPopoverOpenRef = (0, import_react9.useRef)(false);
8220
+ const linkPopoverGraceUntilRef = (0, import_react9.useRef)(0);
6728
8221
  setLinkPopoverRef.current = setLinkPopover;
8222
+ linkPopoverSessionRef.current = linkPopover;
6729
8223
  const bumpLinkPopoverGrace = () => {
6730
8224
  linkPopoverGraceUntilRef.current = Date.now() + 350;
6731
8225
  };
6732
- const runSectionsPrefetch = (0, import_react6.useCallback)((pages) => {
8226
+ const runSectionsPrefetch = (0, import_react9.useCallback)((pages) => {
6733
8227
  if (!isEditMode || shouldUseDevFixtures() || pages.length === 0) return;
6734
8228
  const gen = ++sectionsPrefetchGenRef.current;
6735
8229
  const paths = pages.map((p) => p.path);
@@ -6748,43 +8242,33 @@ function OhhwellsBridge() {
6748
8242
  );
6749
8243
  });
6750
8244
  }, [isEditMode, pathname]);
6751
- const runSectionsPrefetchRef = (0, import_react6.useRef)(runSectionsPrefetch);
8245
+ const runSectionsPrefetchRef = (0, import_react9.useRef)(runSectionsPrefetch);
6752
8246
  runSectionsPrefetchRef.current = runSectionsPrefetch;
6753
- (0, import_react6.useEffect)(() => {
6754
- if (!linkPopover) return;
8247
+ (0, import_react9.useEffect)(() => {
8248
+ if (!linkPopover) {
8249
+ document.documentElement.removeAttribute("data-ohw-link-popover-open");
8250
+ return;
8251
+ }
8252
+ document.documentElement.setAttribute("data-ohw-link-popover-open", "");
6755
8253
  if (hoveredImageRef.current) {
6756
8254
  hoveredImageRef.current = null;
6757
8255
  hoveredImageHasTextOverlapRef.current = false;
6758
8256
  }
6759
8257
  hoveredGapRef.current = null;
6760
8258
  setSectionGap(null);
6761
- setMediaHover(null);
6762
- postToParent({ type: "ow:link-modal-lock", locked: true });
6763
- const html = document.documentElement;
6764
- const body = document.body;
6765
- const prevHtmlOverflow = html.style.overflow;
6766
- const prevBodyOverflow = body.style.overflow;
6767
- html.style.overflow = "hidden";
6768
- body.style.overflow = "hidden";
6769
- const preventBackgroundScroll = (e) => {
6770
- const target = e.target;
6771
- if (target instanceof Element && target.closest('[data-ohw-link-modal-root], [data-slot="dialog-overlay"]')) {
6772
- return;
6773
- }
6774
- e.preventDefault();
6775
- };
6776
- const scrollOpts = { passive: false, capture: true };
6777
- document.addEventListener("wheel", preventBackgroundScroll, scrollOpts);
6778
- document.addEventListener("touchmove", preventBackgroundScroll, scrollOpts);
8259
+ hoveredItemElRef.current = null;
8260
+ setHoveredItemRect(null);
8261
+ hoveredNavContainerRef.current = null;
8262
+ setHoveredNavContainerRect(null);
8263
+ document.querySelectorAll("[data-ohw-hovered]").forEach((el) => {
8264
+ el.removeAttribute("data-ohw-hovered");
8265
+ });
8266
+ postToParent2({ type: "ow:image-unhover" });
6779
8267
  return () => {
6780
- postToParent({ type: "ow:link-modal-lock", locked: false });
6781
- html.style.overflow = prevHtmlOverflow;
6782
- body.style.overflow = prevBodyOverflow;
6783
- document.removeEventListener("wheel", preventBackgroundScroll, scrollOpts);
6784
- document.removeEventListener("touchmove", preventBackgroundScroll, scrollOpts);
8268
+ document.documentElement.removeAttribute("data-ohw-link-popover-open");
6785
8269
  };
6786
- }, [linkPopover, postToParent]);
6787
- (0, import_react6.useEffect)(() => {
8270
+ }, [linkPopover, postToParent2]);
8271
+ (0, import_react9.useEffect)(() => {
6788
8272
  if (!isEditMode) return;
6789
8273
  const useFixtures = shouldUseDevFixtures();
6790
8274
  if (useFixtures) {
@@ -6805,17 +8289,17 @@ function OhhwellsBridge() {
6805
8289
  runSectionsPrefetchRef.current(mapped);
6806
8290
  };
6807
8291
  window.addEventListener("message", onSitePages);
6808
- if (!useFixtures) postToParent({ type: "ow:request-site-pages" });
8292
+ if (!useFixtures) postToParent2({ type: "ow:request-site-pages" });
6809
8293
  return () => window.removeEventListener("message", onSitePages);
6810
- }, [isEditMode, postToParent]);
6811
- (0, import_react6.useEffect)(() => {
8294
+ }, [isEditMode, postToParent2]);
8295
+ (0, import_react9.useEffect)(() => {
6812
8296
  if (!isEditMode || shouldUseDevFixtures()) return;
6813
8297
  void loadAllSectionsManifest().then((manifest) => {
6814
8298
  if (Object.keys(manifest).length === 0) return;
6815
8299
  setSectionsByPath((prev) => ({ ...manifest, ...prev }));
6816
8300
  });
6817
8301
  }, [isEditMode]);
6818
- (0, import_react6.useEffect)(() => {
8302
+ (0, import_react9.useEffect)(() => {
6819
8303
  const update = () => {
6820
8304
  const el = activeElRef.current ?? selectedElRef.current;
6821
8305
  if (el) setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
@@ -6839,10 +8323,10 @@ function OhhwellsBridge() {
6839
8323
  vvp.removeEventListener("resize", update);
6840
8324
  };
6841
8325
  }, []);
6842
- const refreshStateRules = (0, import_react6.useCallback)(() => {
8326
+ const refreshStateRules = (0, import_react9.useCallback)(() => {
6843
8327
  editStylesRef.current?.forceHover && (editStylesRef.current.forceHover.textContent = collectStateRules());
6844
8328
  }, []);
6845
- const processConfigRequest = (0, import_react6.useCallback)((insertAfterVal) => {
8329
+ const processConfigRequest = (0, import_react9.useCallback)((insertAfterVal) => {
6846
8330
  const tracker = getSectionsTracker();
6847
8331
  let entries = [];
6848
8332
  try {
@@ -6865,7 +8349,7 @@ function OhhwellsBridge() {
6865
8349
  }
6866
8350
  window.postMessage({ type: "ow:schedule-config", insertAfter: insertAfterVal, scheduleId: null }, "*");
6867
8351
  }, [isEditMode]);
6868
- const deactivate = (0, import_react6.useCallback)(() => {
8352
+ const deactivate = (0, import_react9.useCallback)(() => {
6869
8353
  const el = activeElRef.current;
6870
8354
  if (!el) return;
6871
8355
  const key = el.dataset.ohwKey;
@@ -6894,21 +8378,25 @@ function OhhwellsBridge() {
6894
8378
  setMaxBadge(null);
6895
8379
  setActiveCommands(/* @__PURE__ */ new Set());
6896
8380
  setToolbarShowEditLink(false);
6897
- postToParent({ type: "ow:exit-edit" });
6898
- }, [postToParent]);
6899
- const deselect = (0, import_react6.useCallback)(() => {
8381
+ postToParent2({ type: "ow:exit-edit" });
8382
+ }, [postToParent2]);
8383
+ const deselect = (0, import_react9.useCallback)(() => {
6900
8384
  selectedElRef.current = null;
6901
8385
  setReorderHrefKey(null);
6902
8386
  setReorderDragDisabled(false);
8387
+ setIsFooterFrameSelection(false);
6903
8388
  siblingHintElRef.current = null;
6904
8389
  setSiblingHintRect(null);
8390
+ setSiblingHintRects([]);
6905
8391
  setIsItemDragging(false);
8392
+ hoveredNavContainerRef.current = null;
8393
+ setHoveredNavContainerRect(null);
6906
8394
  if (!activeElRef.current) {
6907
8395
  setToolbarRect(null);
6908
8396
  setToolbarVariant("none");
6909
8397
  }
6910
8398
  }, []);
6911
- const reselectNavigationItem = (0, import_react6.useCallback)((navAnchor) => {
8399
+ const reselectNavigationItem = (0, import_react9.useCallback)((navAnchor) => {
6912
8400
  selectedElRef.current = navAnchor;
6913
8401
  const { key, disabled } = getNavigationItemReorderState(navAnchor);
6914
8402
  setReorderHrefKey(key);
@@ -6918,7 +8406,7 @@ function OhhwellsBridge() {
6918
8406
  setToolbarShowEditLink(false);
6919
8407
  setActiveCommands(/* @__PURE__ */ new Set());
6920
8408
  }, []);
6921
- const commitNavigationTextEdit = (0, import_react6.useCallback)((navAnchor) => {
8409
+ const commitNavigationTextEdit = (0, import_react9.useCallback)((navAnchor) => {
6922
8410
  const el = activeElRef.current;
6923
8411
  if (!el) return;
6924
8412
  const key = el.dataset.ohwKey;
@@ -6931,9 +8419,9 @@ function OhhwellsBridge() {
6931
8419
  const html = sanitizeHtml(el.innerHTML);
6932
8420
  const original = originalContentRef.current ?? "";
6933
8421
  if (html !== sanitizeHtml(original)) {
6934
- postToParent({ type: "ow:change", nodes: [{ key, text: html }] });
8422
+ postToParent2({ type: "ow:change", nodes: [{ key, text: html }] });
6935
8423
  const h = document.documentElement.scrollHeight;
6936
- if (h > 50) postToParent({ type: "ow:height", height: h });
8424
+ if (h > 50) postToParent2({ type: "ow:height", height: h });
6937
8425
  }
6938
8426
  }
6939
8427
  el.removeAttribute("contenteditable");
@@ -6941,81 +8429,408 @@ function OhhwellsBridge() {
6941
8429
  setMaxBadge(null);
6942
8430
  setActiveCommands(/* @__PURE__ */ new Set());
6943
8431
  setToolbarShowEditLink(false);
6944
- postToParent({ type: "ow:exit-edit" });
8432
+ postToParent2({ type: "ow:exit-edit" });
6945
8433
  reselectNavigationItem(navAnchor);
6946
- }, [postToParent, reselectNavigationItem]);
6947
- const handleAddNavigationItem = (0, import_react6.useCallback)(() => {
8434
+ }, [postToParent2, reselectNavigationItem]);
8435
+ const handleAddTopLevelNavItem = (0, import_react9.useCallback)(() => {
8436
+ const items = listNavbarItems();
8437
+ addNavAfterAnchorRef.current = items[items.length - 1] ?? null;
8438
+ deselectRef.current();
8439
+ deactivateRef.current();
8440
+ bumpLinkPopoverGrace();
8441
+ setLinkPopover({
8442
+ key: "__add-nav__",
8443
+ mode: "create",
8444
+ intent: "add-nav"
8445
+ });
8446
+ }, []);
8447
+ const clearFooterDragVisuals = (0, import_react9.useCallback)(() => {
8448
+ footerDragRef.current = null;
8449
+ setSiblingHintRects([]);
8450
+ setFooterDropSlots([]);
8451
+ setActiveFooterDropIndex(null);
8452
+ setDraggedItemRect(null);
8453
+ setIsItemDragging(false);
8454
+ unlockFooterDragInteraction();
8455
+ }, []);
8456
+ const refreshFooterDragVisuals = (0, import_react9.useCallback)((session, activeSlot, clientX, clientY) => {
8457
+ const dragged = session.draggedEl;
8458
+ setDraggedItemRect(dragged.getBoundingClientRect());
8459
+ if (typeof clientX === "number" && typeof clientY === "number") {
8460
+ session.lastClientX = clientX;
8461
+ session.lastClientY = clientY;
8462
+ }
8463
+ session.activeSlot = activeSlot;
8464
+ if (session.kind === "link") {
8465
+ const columns2 = listFooterColumns();
8466
+ const focusCols = /* @__PURE__ */ new Set([session.sourceColumnIndex]);
8467
+ if (activeSlot) focusCols.add(activeSlot.columnIndex);
8468
+ const siblingRects = [];
8469
+ columns2.forEach((col, i) => {
8470
+ if (!focusCols.has(i)) return;
8471
+ for (const link of listFooterLinksInColumn(col)) {
8472
+ if (link === dragged) continue;
8473
+ siblingRects.push(link.getBoundingClientRect());
8474
+ }
8475
+ });
8476
+ setSiblingHintRects(siblingRects);
8477
+ const slots2 = [];
8478
+ columns2.forEach((col, i) => {
8479
+ slots2.push(...buildLinkDropSlots(col, i));
8480
+ });
8481
+ setFooterDropSlots(slots2);
8482
+ const activeIdx2 = activeSlot ? slots2.findIndex((s) => s.columnIndex === activeSlot.columnIndex && s.insertIndex === activeSlot.insertIndex) : -1;
8483
+ setActiveFooterDropIndex(activeIdx2 >= 0 ? activeIdx2 : null);
8484
+ return;
8485
+ }
8486
+ const columns = listFooterColumns();
8487
+ setSiblingHintRects(columns.filter((col) => col !== dragged).map((col) => col.getBoundingClientRect()));
8488
+ const slots = buildColumnDropSlots();
8489
+ setFooterDropSlots(slots);
8490
+ const activeIdx = activeSlot ? slots.findIndex((s) => s.insertIndex === activeSlot.insertIndex) : -1;
8491
+ setActiveFooterDropIndex(activeIdx >= 0 ? activeIdx : null);
8492
+ }, []);
8493
+ const refreshFooterDragVisualsRef = (0, import_react9.useRef)(refreshFooterDragVisuals);
8494
+ refreshFooterDragVisualsRef.current = refreshFooterDragVisuals;
8495
+ const commitFooterDragRef = (0, import_react9.useRef)(() => {
8496
+ });
8497
+ const beginFooterDragRef = (0, import_react9.useRef)(() => {
8498
+ });
8499
+ const beginFooterDrag = (0, import_react9.useCallback)(
8500
+ (session) => {
8501
+ const rect = session.draggedEl.getBoundingClientRect();
8502
+ session.lastClientX = session.lastClientX || rect.left + rect.width / 2;
8503
+ session.lastClientY = session.lastClientY || rect.top + rect.height / 2;
8504
+ session.activeSlot = session.activeSlot ?? null;
8505
+ footerDragRef.current = session;
8506
+ setIsItemDragging(true);
8507
+ lockFooterDuringDrag();
8508
+ siblingHintElRef.current = null;
8509
+ setSiblingHintRect(null);
8510
+ if (session.wasSelected && selectedElRef.current === session.draggedEl) {
8511
+ setToolbarRect(rect);
8512
+ }
8513
+ const initialSlot = session.kind === "link" && session.hrefKey ? hitTestLinkDropSlot(session.lastClientX, session.lastClientY, session.hrefKey) : hitTestColumnDropSlot(session.lastClientX, session.lastClientY);
8514
+ refreshFooterDragVisuals(session, initialSlot, session.lastClientX, session.lastClientY);
8515
+ },
8516
+ [refreshFooterDragVisuals]
8517
+ );
8518
+ beginFooterDragRef.current = beginFooterDrag;
8519
+ const commitFooterDrag = (0, import_react9.useCallback)(
8520
+ (clientX, clientY) => {
8521
+ const session = footerDragRef.current;
8522
+ if (!session) {
8523
+ clearFooterDragVisuals();
8524
+ return;
8525
+ }
8526
+ const x = typeof clientX === "number" && (clientX !== 0 || clientY !== 0) ? clientX : session.lastClientX;
8527
+ const y = typeof clientY === "number" && (clientX !== 0 || clientY !== 0) ? clientY : session.lastClientY;
8528
+ let nextOrder = null;
8529
+ const slot = session.activeSlot ?? (session.kind === "link" && session.hrefKey ? hitTestLinkDropSlot(x, y, session.hrefKey) : session.kind === "column" ? hitTestColumnDropSlot(x, y) : null);
8530
+ if (session.kind === "link" && session.hrefKey && slot) {
8531
+ nextOrder = planFooterLinkMove(session.hrefKey, slot.columnIndex, slot.insertIndex);
8532
+ } else if (session.kind === "column" && slot) {
8533
+ nextOrder = planFooterColumnMove(session.sourceColumnIndex, slot.insertIndex);
8534
+ }
8535
+ const wasSelected = session.wasSelected;
8536
+ const draggedEl = session.draggedEl;
8537
+ const hrefKey = session.hrefKey;
8538
+ let movedColumnIndex = null;
8539
+ if (session.kind === "column" && slot && nextOrder) {
8540
+ let adjusted = slot.insertIndex;
8541
+ if (session.sourceColumnIndex < slot.insertIndex) adjusted -= 1;
8542
+ movedColumnIndex = Math.max(0, Math.min(adjusted, nextOrder.length - 1));
8543
+ }
8544
+ clearFooterDragVisuals();
8545
+ const applySelectionAfterDrop = () => {
8546
+ if (!wasSelected) {
8547
+ deselectRef.current();
8548
+ return;
8549
+ }
8550
+ if (hrefKey) {
8551
+ const link = document.querySelector(
8552
+ `footer [data-ohw-href-key="${CSS.escape(hrefKey)}"]`
8553
+ );
8554
+ if (link && isNavigationItem(link)) {
8555
+ selectRef.current(link);
8556
+ return;
8557
+ }
8558
+ }
8559
+ if (movedColumnIndex !== null && nextOrder) {
8560
+ const colKeys = nextOrder[movedColumnIndex] ?? [];
8561
+ let column = null;
8562
+ for (const key of colKeys) {
8563
+ const link = document.querySelector(
8564
+ `footer [data-ohw-href-key="${CSS.escape(key)}"]`
8565
+ );
8566
+ if (link) {
8567
+ column = findFooterColumnForLink(link);
8568
+ if (column) break;
8569
+ }
8570
+ }
8571
+ if (!column) column = listFooterColumns()[movedColumnIndex] ?? null;
8572
+ if (column && isNavigationContainer(column)) {
8573
+ selectFrameRef.current(column);
8574
+ return;
8575
+ }
8576
+ }
8577
+ if (document.body.contains(draggedEl)) {
8578
+ if (isNavigationItem(draggedEl)) {
8579
+ selectRef.current(draggedEl);
8580
+ return;
8581
+ }
8582
+ if (isNavigationContainer(draggedEl)) {
8583
+ selectFrameRef.current(draggedEl);
8584
+ return;
8585
+ }
8586
+ }
8587
+ deselectRef.current();
8588
+ };
8589
+ if (!wasSelected) {
8590
+ deselectRef.current();
8591
+ }
8592
+ if (nextOrder) {
8593
+ const orderJson = JSON.stringify(nextOrder);
8594
+ editContentRef.current = {
8595
+ ...editContentRef.current,
8596
+ [FOOTER_ORDER_KEY]: orderJson
8597
+ };
8598
+ applyFooterOrder(nextOrder);
8599
+ document.querySelectorAll("footer [data-ohw-href-key]").forEach(disableNativeHrefDrag);
8600
+ postToParentRef.current({
8601
+ type: "ow:change",
8602
+ nodes: [{ key: FOOTER_ORDER_KEY, text: orderJson }]
8603
+ });
8604
+ requestAnimationFrame(() => {
8605
+ if (editContentRef.current[FOOTER_ORDER_KEY] === orderJson) {
8606
+ applyFooterOrder(nextOrder);
8607
+ document.querySelectorAll("footer [data-ohw-href-key]").forEach(disableNativeHrefDrag);
8608
+ }
8609
+ requestAnimationFrame(applySelectionAfterDrop);
8610
+ });
8611
+ return;
8612
+ }
8613
+ applySelectionAfterDrop();
8614
+ },
8615
+ [clearFooterDragVisuals]
8616
+ );
8617
+ commitFooterDragRef.current = commitFooterDrag;
8618
+ const startFooterLinkDrag = (0, import_react9.useCallback)(
8619
+ (anchor, clientX, clientY, wasSelected) => {
8620
+ const hrefKey = anchor.getAttribute("data-ohw-href-key");
8621
+ if (!hrefKey || !isFooterHrefKey(hrefKey)) return false;
8622
+ const column = findFooterColumnForLink(anchor);
8623
+ const columns = listFooterColumns();
8624
+ beginFooterDrag({
8625
+ kind: "link",
8626
+ hrefKey,
8627
+ columnEl: column,
8628
+ sourceColumnIndex: column ? columns.indexOf(column) : 0,
8629
+ wasSelected,
8630
+ draggedEl: anchor,
8631
+ lastClientX: clientX,
8632
+ lastClientY: clientY,
8633
+ activeSlot: null
8634
+ });
8635
+ return true;
8636
+ },
8637
+ [beginFooterDrag]
8638
+ );
8639
+ const startFooterColumnDrag = (0, import_react9.useCallback)(
8640
+ (columnEl, clientX, clientY, wasSelected) => {
8641
+ const columns = listFooterColumns();
8642
+ const idx = columns.indexOf(columnEl);
8643
+ if (idx < 0) return false;
8644
+ beginFooterDrag({
8645
+ kind: "column",
8646
+ hrefKey: null,
8647
+ columnEl,
8648
+ sourceColumnIndex: idx,
8649
+ wasSelected,
8650
+ draggedEl: columnEl,
8651
+ lastClientX: clientX,
8652
+ lastClientY: clientY,
8653
+ activeSlot: null
8654
+ });
8655
+ return true;
8656
+ },
8657
+ [beginFooterDrag]
8658
+ );
8659
+ const handleItemDragStart = (0, import_react9.useCallback)(
8660
+ (e) => {
8661
+ const selected = selectedElRef.current;
8662
+ if (!selected) {
8663
+ setIsItemDragging(true);
8664
+ return;
8665
+ }
8666
+ const clientX = e?.clientX ?? selected.getBoundingClientRect().left + 8;
8667
+ const clientY = e?.clientY ?? selected.getBoundingClientRect().top + 8;
8668
+ if (startFooterLinkDrag(selected, clientX, clientY, true)) return;
8669
+ if (selected.hasAttribute("data-ohw-footer-col") || isInferredFooterGroup(selected) && selected.closest("footer")) {
8670
+ if (startFooterColumnDrag(selected, clientX, clientY, true)) return;
8671
+ }
8672
+ siblingHintElRef.current = null;
8673
+ setSiblingHintRect(null);
8674
+ setIsItemDragging(true);
8675
+ },
8676
+ [startFooterColumnDrag, startFooterLinkDrag]
8677
+ );
8678
+ const handleItemDragEnd = (0, import_react9.useCallback)(
8679
+ (e) => {
8680
+ if (footerDragRef.current) {
8681
+ const x = e?.clientX;
8682
+ const y = e?.clientY;
8683
+ if (typeof x === "number" && typeof y === "number" && (x !== 0 || y !== 0)) {
8684
+ commitFooterDrag(x, y);
8685
+ } else {
8686
+ commitFooterDrag();
8687
+ }
8688
+ return;
8689
+ }
8690
+ setIsItemDragging(false);
8691
+ },
8692
+ [commitFooterDrag]
8693
+ );
8694
+ const handleItemChromePointerDown = (0, import_react9.useCallback)((e) => {
8695
+ if (e.button !== 0) return;
6948
8696
  const selected = selectedElRef.current;
6949
8697
  if (!selected) return;
6950
- const target = getNavigationItemAddHintTarget(selected);
6951
- siblingHintElRef.current = target;
6952
- setSiblingHintRect(target.getBoundingClientRect());
6953
- }, []);
6954
- const handleItemDragStart = (0, import_react6.useCallback)(() => {
6955
- siblingHintElRef.current = null;
6956
- setSiblingHintRect(null);
6957
- setIsItemDragging(true);
8698
+ armFooterPressDrag();
8699
+ const hrefKey = selected.getAttribute("data-ohw-href-key");
8700
+ if (hrefKey && isFooterHrefKey(hrefKey)) {
8701
+ footerPointerDragRef.current = {
8702
+ el: selected,
8703
+ kind: "link",
8704
+ startX: e.clientX,
8705
+ startY: e.clientY,
8706
+ pointerId: e.pointerId,
8707
+ wasSelected: true,
8708
+ started: false
8709
+ };
8710
+ return;
8711
+ }
8712
+ if (selected.hasAttribute("data-ohw-footer-col") || isInferredFooterGroup(selected) && selected.closest("footer")) {
8713
+ footerPointerDragRef.current = {
8714
+ el: selected,
8715
+ kind: "column",
8716
+ startX: e.clientX,
8717
+ startY: e.clientY,
8718
+ pointerId: e.pointerId,
8719
+ wasSelected: true,
8720
+ started: false
8721
+ };
8722
+ }
6958
8723
  }, []);
6959
- const handleItemDragEnd = (0, import_react6.useCallback)(() => {
6960
- setIsItemDragging(false);
8724
+ const handleItemChromeClick = (0, import_react9.useCallback)((clientX, clientY) => {
8725
+ if (suppressNextClickRef.current) {
8726
+ suppressNextClickRef.current = false;
8727
+ return;
8728
+ }
8729
+ const selected = selectedElRef.current;
8730
+ if (!selected || !isNavigationItem(selected)) return;
8731
+ const editable = selected.querySelector('[data-ohw-editable="text"]') ?? selected.querySelector("[data-ohw-editable]");
8732
+ if (!editable) return;
8733
+ activateRef.current(editable, { caretX: clientX, caretY: clientY });
6961
8734
  }, []);
6962
8735
  reselectNavigationItemRef.current = reselectNavigationItem;
6963
8736
  commitNavigationTextEditRef.current = commitNavigationTextEdit;
6964
- const select = (0, import_react6.useCallback)((anchor) => {
6965
- if (!isNavigationItem(anchor)) return;
6966
- if (activeElRef.current) deactivate();
6967
- selectedElRef.current = anchor;
6968
- clearHrefKeyHover(anchor);
6969
- setHoveredItemRect(null);
6970
- hoveredItemElRef.current = null;
6971
- siblingHintElRef.current = null;
6972
- setSiblingHintRect(null);
6973
- setIsItemDragging(false);
6974
- const { key, disabled } = getNavigationItemReorderState(anchor);
6975
- setReorderHrefKey(key);
6976
- setReorderDragDisabled(disabled);
6977
- setToolbarVariant("link-action");
6978
- setToolbarRect(anchor.getBoundingClientRect());
6979
- setToolbarShowEditLink(false);
6980
- setActiveCommands(/* @__PURE__ */ new Set());
6981
- }, [deactivate]);
6982
- const activate = (0, import_react6.useCallback)((el) => {
6983
- if (activeElRef.current === el) return;
6984
- selectedElRef.current = null;
6985
- deactivate();
6986
- if (hoveredImageRef.current) {
6987
- hoveredImageRef.current = null;
6988
- setMediaHover(null);
6989
- }
6990
- setToolbarVariant("rich-text");
6991
- siblingHintElRef.current = null;
6992
- setSiblingHintRect(null);
6993
- setIsItemDragging(false);
6994
- el.setAttribute("contenteditable", "true");
6995
- el.removeAttribute("data-ohw-hovered");
6996
- el.closest("[data-ohw-href-key]")?.removeAttribute("data-ohw-hovered");
6997
- activeElRef.current = el;
6998
- originalContentRef.current = el.innerHTML;
6999
- el.focus();
7000
- setToolbarShowEditLink(Boolean(getHrefKeyFromElement(el)));
7001
- const navAnchor = getNavigationItemAnchor(el);
7002
- if (navAnchor) {
8737
+ const select = (0, import_react9.useCallback)(
8738
+ (anchor) => {
8739
+ if (!isNavigationItem(anchor)) return;
8740
+ if (activeElRef.current) deactivate();
8741
+ selectedElRef.current = anchor;
8742
+ clearHrefKeyHover(anchor);
8743
+ hoveredNavContainerRef.current = null;
8744
+ setHoveredNavContainerRect(null);
8745
+ setHoveredItemRect(null);
8746
+ hoveredItemElRef.current = null;
8747
+ siblingHintElRef.current = null;
8748
+ setSiblingHintRect(null);
8749
+ setSiblingHintRects([]);
8750
+ setIsItemDragging(false);
8751
+ const { key, disabled } = getNavigationItemReorderState(anchor);
8752
+ setReorderHrefKey(key);
8753
+ setReorderDragDisabled(disabled);
8754
+ setIsFooterFrameSelection(false);
8755
+ setToolbarVariant("link-action");
8756
+ setToolbarRect(anchor.getBoundingClientRect());
8757
+ setToolbarShowEditLink(false);
8758
+ setActiveCommands(/* @__PURE__ */ new Set());
8759
+ },
8760
+ [deactivate]
8761
+ );
8762
+ const selectFrame = (0, import_react9.useCallback)(
8763
+ (el) => {
8764
+ if (!isNavigationContainer(el)) return;
8765
+ if (activeElRef.current) deactivate();
8766
+ const isFooterColumn = el.hasAttribute("data-ohw-footer-col") || Boolean(el.closest("footer") && isInferredFooterGroup(el));
8767
+ selectedElRef.current = el;
8768
+ clearHrefKeyHover(el);
8769
+ hoveredNavContainerRef.current = null;
8770
+ setHoveredNavContainerRect(null);
8771
+ setHoveredItemRect(null);
8772
+ hoveredItemElRef.current = null;
8773
+ siblingHintElRef.current = null;
8774
+ setSiblingHintRect(null);
8775
+ setSiblingHintRects(
8776
+ isFooterColumn ? listFooterColumns().filter((column) => column !== el).map((column) => column.getBoundingClientRect()) : []
8777
+ );
8778
+ setIsItemDragging(false);
7003
8779
  setReorderHrefKey(null);
7004
8780
  setReorderDragDisabled(false);
7005
- } else {
7006
- const { key: reorderKey, disabled: reorderDisabled } = getReorderHandleState(el);
7007
- setReorderHrefKey(reorderKey);
7008
- setReorderDragDisabled(reorderDisabled);
7009
- }
7010
- setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
7011
- postToParent({ type: "ow:enter-edit", key: el.dataset.ohwKey });
7012
- requestAnimationFrame(() => refreshActiveCommandsRef.current());
7013
- }, [deactivate, postToParent]);
8781
+ setIsFooterFrameSelection(isFooterColumn);
8782
+ setToolbarVariant("select-frame");
8783
+ setToolbarRect(el.getBoundingClientRect());
8784
+ setToolbarShowEditLink(false);
8785
+ setActiveCommands(/* @__PURE__ */ new Set());
8786
+ },
8787
+ [deactivate]
8788
+ );
8789
+ const activate = (0, import_react9.useCallback)(
8790
+ (el, options) => {
8791
+ if (activeElRef.current === el) return;
8792
+ selectedElRef.current = null;
8793
+ deactivate();
8794
+ if (hoveredImageRef.current) {
8795
+ hoveredImageRef.current = null;
8796
+ postToParentRef.current({ type: "ow:image-unhover" });
8797
+ }
8798
+ setToolbarVariant("rich-text");
8799
+ siblingHintElRef.current = null;
8800
+ setSiblingHintRect(null);
8801
+ setSiblingHintRects([]);
8802
+ setIsItemDragging(false);
8803
+ el.setAttribute("contenteditable", "true");
8804
+ el.removeAttribute("data-ohw-hovered");
8805
+ el.closest("[data-ohw-href-key]")?.removeAttribute("data-ohw-hovered");
8806
+ activeElRef.current = el;
8807
+ originalContentRef.current = el.innerHTML;
8808
+ el.focus();
8809
+ if (options?.caretX !== void 0 && options?.caretY !== void 0) {
8810
+ placeCaretAtPoint(el, options.caretX, options.caretY);
8811
+ }
8812
+ setToolbarShowEditLink(Boolean(getHrefKeyFromElement(el)));
8813
+ const navAnchor = getNavigationItemAnchor(el);
8814
+ if (navAnchor) {
8815
+ setReorderHrefKey(null);
8816
+ setReorderDragDisabled(false);
8817
+ } else {
8818
+ const { key: reorderKey, disabled: reorderDisabled } = getReorderHandleState(el);
8819
+ setReorderHrefKey(reorderKey);
8820
+ setReorderDragDisabled(reorderDisabled);
8821
+ }
8822
+ setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
8823
+ postToParent2({ type: "ow:enter-edit", key: el.dataset.ohwKey });
8824
+ requestAnimationFrame(() => refreshActiveCommandsRef.current());
8825
+ },
8826
+ [deactivate, postToParent2]
8827
+ );
7014
8828
  activateRef.current = activate;
7015
8829
  deactivateRef.current = deactivate;
7016
8830
  selectRef.current = select;
8831
+ selectFrameRef.current = selectFrame;
7017
8832
  deselectRef.current = deselect;
7018
- (0, import_react6.useLayoutEffect)(() => {
8833
+ (0, import_react9.useLayoutEffect)(() => {
7019
8834
  if (!subdomain || isEditMode) {
7020
8835
  setFetchState("done");
7021
8836
  return;
@@ -7025,6 +8840,7 @@ function OhhwellsBridge() {
7025
8840
  for (const [key, val] of Object.entries(content)) {
7026
8841
  if (key === "__ohw_sections") continue;
7027
8842
  if (applyVideoSettingNode(key, val)) continue;
8843
+ if (applyCarouselNode(key, val)) continue;
7028
8844
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
7029
8845
  if (el.dataset.ohwEditable === "image") {
7030
8846
  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
  enforceLinkHrefs();
7059
8877
  initSectionsFromContent(content, true);
7060
8878
  sectionsLoadedRef.current = true;
@@ -7083,7 +8901,7 @@ function OhhwellsBridge() {
7083
8901
  cancelled = true;
7084
8902
  };
7085
8903
  }, [subdomain, isEditMode]);
7086
- (0, import_react6.useEffect)(() => {
8904
+ (0, import_react9.useEffect)(() => {
7087
8905
  if (!subdomain || isEditMode) return;
7088
8906
  let debounceTimer = null;
7089
8907
  let observer = null;
@@ -7096,6 +8914,7 @@ function OhhwellsBridge() {
7096
8914
  for (const [key, val] of Object.entries(content)) {
7097
8915
  if (key === "__ohw_sections") continue;
7098
8916
  if (applyVideoSettingNode(key, val)) continue;
8917
+ if (applyCarouselNode(key, val)) continue;
7099
8918
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
7100
8919
  if (el.dataset.ohwEditable === "image") {
7101
8920
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -7114,6 +8933,8 @@ function OhhwellsBridge() {
7114
8933
  });
7115
8934
  applyLinkByKey(key, val);
7116
8935
  }
8936
+ reconcileNavbarItemsFromContent(content);
8937
+ reconcileFooterOrderFromContent(content);
7117
8938
  } finally {
7118
8939
  observer?.observe(document.body, { childList: true, subtree: true });
7119
8940
  }
@@ -7131,26 +8952,71 @@ function OhhwellsBridge() {
7131
8952
  if (debounceTimer) clearTimeout(debounceTimer);
7132
8953
  };
7133
8954
  }, [subdomain, isEditMode, pathname]);
7134
- (0, import_react6.useLayoutEffect)(() => {
8955
+ (0, import_react9.useLayoutEffect)(() => {
7135
8956
  const el = document.getElementById("ohw-loader");
7136
8957
  if (!el) return;
7137
8958
  const visible = Boolean(subdomain) && fetchState !== "done";
7138
8959
  el.style.display = visible ? "flex" : "none";
7139
8960
  }, [subdomain, fetchState]);
7140
- (0, import_react6.useEffect)(() => {
7141
- postToParent({ type: "ow:navigation", path: pathname });
7142
- }, [pathname, postToParent]);
7143
- (0, import_react6.useEffect)(() => {
8961
+ (0, import_react9.useEffect)(() => {
8962
+ postToParent2({ type: "ow:navigation", path: pathname });
8963
+ }, [pathname, postToParent2]);
8964
+ (0, import_react9.useEffect)(() => {
7144
8965
  if (!isEditMode) return;
8966
+ if (linkPopoverSessionRef.current?.intent === "add-nav") return;
8967
+ if (document.querySelector("[data-ohw-section-picker]")) return;
7145
8968
  setLinkPopover(null);
7146
8969
  deselectRef.current();
7147
8970
  deactivateRef.current();
7148
8971
  }, [pathname, isEditMode]);
7149
- (0, import_react6.useEffect)(() => {
8972
+ (0, import_react9.useEffect)(() => {
8973
+ const contentForNav = () => {
8974
+ if (isEditMode) return editContentRef.current;
8975
+ if (!subdomain) return {};
8976
+ return contentCache.get(subdomain) ?? {};
8977
+ };
8978
+ let applying = false;
8979
+ let rafId = null;
8980
+ const run = () => {
8981
+ if (footerDragRef.current || applying) return;
8982
+ applying = true;
8983
+ try {
8984
+ const content = contentForNav();
8985
+ reconcileNavbarItemsFromContent(content);
8986
+ reconcileFooterOrderFromContent(content);
8987
+ document.querySelectorAll("footer [data-ohw-href-key]").forEach((el) => {
8988
+ if (isFooterHrefKey(el.getAttribute("data-ohw-href-key"))) {
8989
+ disableNativeHrefDrag(el);
8990
+ }
8991
+ });
8992
+ } finally {
8993
+ applying = false;
8994
+ }
8995
+ };
8996
+ const scheduleRun = () => {
8997
+ if (rafId !== null) cancelAnimationFrame(rafId);
8998
+ rafId = requestAnimationFrame(() => {
8999
+ rafId = null;
9000
+ run();
9001
+ });
9002
+ };
9003
+ run();
9004
+ const nav = document.querySelector("nav");
9005
+ const footer = document.querySelector("footer");
9006
+ const observer = new MutationObserver(scheduleRun);
9007
+ if (nav) observer.observe(nav, { childList: true, subtree: true });
9008
+ if (footer) observer.observe(footer, { childList: true, subtree: true });
9009
+ if (!nav && !footer) return;
9010
+ return () => {
9011
+ observer.disconnect();
9012
+ if (rafId !== null) cancelAnimationFrame(rafId);
9013
+ };
9014
+ }, [isEditMode, pathname, subdomain, fetchState]);
9015
+ (0, import_react9.useEffect)(() => {
7150
9016
  if (!isEditMode) return;
7151
9017
  const measure = () => {
7152
9018
  const h = document.body.scrollHeight;
7153
- if (h > 50) postToParent({ type: "ow:height", height: h });
9019
+ if (h > 50) postToParent2({ type: "ow:height", height: h });
7154
9020
  };
7155
9021
  const t1 = setTimeout(measure, 50);
7156
9022
  const t2 = setTimeout(measure, 500);
@@ -7169,8 +9035,8 @@ function OhhwellsBridge() {
7169
9035
  if (resizeTimer) clearTimeout(resizeTimer);
7170
9036
  window.removeEventListener("resize", handleResize);
7171
9037
  };
7172
- }, [pathname, isEditMode, postToParent]);
7173
- (0, import_react6.useEffect)(() => {
9038
+ }, [pathname, isEditMode, postToParent2]);
9039
+ (0, import_react9.useEffect)(() => {
7174
9040
  if (!subdomainFromQuery || isEditMode) return;
7175
9041
  const handleClick = (e) => {
7176
9042
  const anchor = e.target.closest("a");
@@ -7186,7 +9052,7 @@ function OhhwellsBridge() {
7186
9052
  document.addEventListener("click", handleClick, true);
7187
9053
  return () => document.removeEventListener("click", handleClick, true);
7188
9054
  }, [subdomainFromQuery, isEditMode, router]);
7189
- (0, import_react6.useEffect)(() => {
9055
+ (0, import_react9.useEffect)(() => {
7190
9056
  if (!isEditMode) {
7191
9057
  editStylesRef.current?.base.remove();
7192
9058
  editStylesRef.current?.forceHover.remove();
@@ -7209,6 +9075,32 @@ function OhhwellsBridge() {
7209
9075
  [data-ohw-editable] {
7210
9076
  display: block;
7211
9077
  }
9078
+ /* Native <a> drag steals clicks \u2014 disable for edit links; reorder via press-to-drag / handle. */
9079
+ footer [data-ohw-href-key] {
9080
+ -webkit-user-drag: none !important;
9081
+ }
9082
+ footer [data-ohw-footer-col]:hover {
9083
+ outline: 1.5px dashed ${PRIMARY2} !important;
9084
+ outline-offset: 6px;
9085
+ border-radius: 8px;
9086
+ }
9087
+ html[data-ohw-link-popover-open] footer [data-ohw-footer-col]:hover {
9088
+ outline: none !important;
9089
+ }
9090
+ html[data-ohw-footer-press-drag] footer,
9091
+ html[data-ohw-footer-press-drag] footer * {
9092
+ user-select: none !important;
9093
+ -webkit-user-select: none !important;
9094
+ }
9095
+ html[data-ohw-item-dragging] footer,
9096
+ html[data-ohw-item-dragging] footer * {
9097
+ user-select: none !important;
9098
+ -webkit-user-select: none !important;
9099
+ pointer-events: none !important;
9100
+ }
9101
+ html[data-ohw-item-dragging] {
9102
+ cursor: grabbing !important;
9103
+ }
7212
9104
  [data-ohw-editable]:not([contenteditable]):not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"]):not([data-ohw-editable="video"]) { cursor: text !important; }
7213
9105
  [data-ohw-editable="image"], [data-ohw-editable="image"] *,
7214
9106
  [data-ohw-editable="video"], [data-ohw-editable="video"] *,
@@ -7254,12 +9146,46 @@ function OhhwellsBridge() {
7254
9146
  }
7255
9147
  refreshStateRules();
7256
9148
  const handleClick = (e) => {
9149
+ if (suppressNextClickRef.current) {
9150
+ suppressNextClickRef.current = false;
9151
+ e.preventDefault();
9152
+ e.stopPropagation();
9153
+ return;
9154
+ }
7257
9155
  const target = e.target;
7258
9156
  if (target.closest("[data-ohw-toolbar]")) return;
7259
9157
  if (target.closest("[data-ohw-state-toggle]")) return;
7260
9158
  if (target.closest("[data-ohw-max-badge]")) return;
7261
9159
  if (isInsideLinkEditor(target)) return;
7262
9160
  if (target.closest('[data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
9161
+ if (target.closest("[data-ohw-item-drag-surface]")) {
9162
+ e.preventDefault();
9163
+ e.stopPropagation();
9164
+ const selected = selectedElRef.current;
9165
+ if (selected && isNavigationItem(selected)) {
9166
+ const editable2 = selected.querySelector('[data-ohw-editable="text"]') ?? selected.querySelector("[data-ohw-editable]");
9167
+ if (editable2) {
9168
+ activateRef.current(editable2, {
9169
+ caretX: e.clientX,
9170
+ caretY: e.clientY
9171
+ });
9172
+ }
9173
+ } else if (selected?.hasAttribute("data-ohw-footer-col")) {
9174
+ const link = listFooterLinksInColumn(selected).find((el) => {
9175
+ const r2 = el.getBoundingClientRect();
9176
+ return e.clientX >= r2.left && e.clientX <= r2.right && e.clientY >= r2.top && e.clientY <= r2.bottom;
9177
+ });
9178
+ if (link) selectRef.current(link);
9179
+ }
9180
+ return;
9181
+ }
9182
+ const navFromChrome = resolveNavContainerSelectionTarget(target, e.clientX, e.clientY);
9183
+ if (navFromChrome) {
9184
+ e.preventDefault();
9185
+ e.stopPropagation();
9186
+ selectFrameRef.current(navFromChrome);
9187
+ return;
9188
+ }
7263
9189
  e.preventDefault();
7264
9190
  e.stopPropagation();
7265
9191
  return;
@@ -7274,7 +9200,8 @@ function OhhwellsBridge() {
7274
9200
  bumpLinkPopoverGrace();
7275
9201
  setLinkPopoverRef.current({
7276
9202
  key: editable.dataset.ohwKey ?? "",
7277
- target: getLinkHref(editable)
9203
+ mode: "edit",
9204
+ target: getLinkHref2(editable)
7278
9205
  });
7279
9206
  return;
7280
9207
  }
@@ -7290,7 +9217,8 @@ function OhhwellsBridge() {
7290
9217
  e.preventDefault();
7291
9218
  e.stopPropagation();
7292
9219
  if (selectedElRef.current === navAnchor) {
7293
- activateRef.current(editable);
9220
+ if (e.detail >= 2) return;
9221
+ activateRef.current(editable, { caretX: e.clientX, caretY: e.clientY });
7294
9222
  return;
7295
9223
  }
7296
9224
  selectRef.current(navAnchor);
@@ -7305,10 +9233,42 @@ function OhhwellsBridge() {
7305
9233
  if (hrefAnchor) {
7306
9234
  e.preventDefault();
7307
9235
  e.stopPropagation();
7308
- if (selectedElRef.current === hrefAnchor) return;
9236
+ if (selectedElRef.current === hrefAnchor) {
9237
+ const textEditable = hrefAnchor.querySelector('[data-ohw-editable="text"]') ?? hrefAnchor.querySelector("[data-ohw-editable]");
9238
+ if (textEditable) {
9239
+ activateRef.current(textEditable, {
9240
+ caretX: e.clientX,
9241
+ caretY: e.clientY
9242
+ });
9243
+ }
9244
+ return;
9245
+ }
7309
9246
  selectRef.current(hrefAnchor);
7310
9247
  return;
7311
9248
  }
9249
+ const footerColClick = target.closest("[data-ohw-footer-col]");
9250
+ if (footerColClick) {
9251
+ e.preventDefault();
9252
+ e.stopPropagation();
9253
+ selectFrameRef.current(footerColClick);
9254
+ return;
9255
+ }
9256
+ const navContainerToSelect = resolveNavContainerSelectionTarget(target, e.clientX, e.clientY);
9257
+ if (navContainerToSelect) {
9258
+ e.preventDefault();
9259
+ e.stopPropagation();
9260
+ selectFrameRef.current(navContainerToSelect);
9261
+ return;
9262
+ }
9263
+ const selectedContainer = selectedElRef.current;
9264
+ if (selectedContainer && isNavigationContainer(selectedContainer)) {
9265
+ const navItem = getNavigationItemAnchor(target);
9266
+ if (!navItem && (selectedContainer === target || selectedContainer.contains(target))) {
9267
+ e.preventDefault();
9268
+ e.stopPropagation();
9269
+ return;
9270
+ }
9271
+ }
7312
9272
  if (activeElRef.current) {
7313
9273
  const sel = window.getSelection();
7314
9274
  if (sel && !sel.isCollapsed) {
@@ -7334,10 +9294,65 @@ function OhhwellsBridge() {
7334
9294
  deselectRef.current();
7335
9295
  deactivateRef.current();
7336
9296
  };
9297
+ const handleDblClick = (e) => {
9298
+ const target = e.target;
9299
+ if (target.closest("[data-ohw-toolbar]")) return;
9300
+ if (target.closest("[data-ohw-state-toggle]")) return;
9301
+ if (target.closest("[data-ohw-max-badge]")) return;
9302
+ if (isInsideLinkEditor(target)) return;
9303
+ if (target.closest('[data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
9304
+ return;
9305
+ }
9306
+ const navLabel = getNavigationLabelEditable(target);
9307
+ if (!navLabel) return;
9308
+ const { editable } = navLabel;
9309
+ e.preventDefault();
9310
+ e.stopPropagation();
9311
+ if (activeElRef.current !== editable) {
9312
+ activateRef.current(editable);
9313
+ }
9314
+ requestAnimationFrame(() => {
9315
+ selectAllTextInEditable(editable);
9316
+ refreshActiveCommandsRef.current();
9317
+ });
9318
+ };
7337
9319
  const handleMouseOver = (e) => {
9320
+ if (document.documentElement.hasAttribute("data-ohw-section-picking")) return;
7338
9321
  const target = e.target;
9322
+ if (linkPopoverOpenRef.current) {
9323
+ hoveredItemElRef.current = null;
9324
+ setHoveredItemRect(null);
9325
+ hoveredNavContainerRef.current = null;
9326
+ setHoveredNavContainerRect(null);
9327
+ return;
9328
+ }
9329
+ if (toolbarVariantRef.current !== "select-frame") {
9330
+ const navContainer = target.closest("[data-ohw-nav-container]");
9331
+ if (navContainer && !getNavigationItemAnchor(target)) {
9332
+ hoveredNavContainerRef.current = navContainer;
9333
+ setHoveredNavContainerRect(navContainer.getBoundingClientRect());
9334
+ hoveredItemElRef.current = null;
9335
+ setHoveredItemRect(null);
9336
+ return;
9337
+ }
9338
+ if (!target.closest("[data-ohw-nav-container]")) {
9339
+ hoveredNavContainerRef.current = null;
9340
+ setHoveredNavContainerRect(null);
9341
+ }
9342
+ }
9343
+ const footerCol = target.closest("[data-ohw-footer-col]");
9344
+ if (footerCol) {
9345
+ hoveredNavContainerRef.current = null;
9346
+ setHoveredNavContainerRect(null);
9347
+ if (selectedElRef.current === footerCol) return;
9348
+ hoveredItemElRef.current = footerCol;
9349
+ setHoveredItemRect(footerCol.getBoundingClientRect());
9350
+ return;
9351
+ }
7339
9352
  const navAnchor = getNavigationItemAnchor(target);
7340
9353
  if (navAnchor) {
9354
+ hoveredNavContainerRef.current = null;
9355
+ setHoveredNavContainerRect(null);
7341
9356
  const selected2 = selectedElRef.current;
7342
9357
  if (selected2 === navAnchor || selected2?.contains(navAnchor)) return;
7343
9358
  clearHrefKeyHover(navAnchor);
@@ -7362,6 +9377,23 @@ function OhhwellsBridge() {
7362
9377
  };
7363
9378
  const handleMouseOut = (e) => {
7364
9379
  const target = e.target;
9380
+ if (target.closest("[data-ohw-nav-container]")) {
9381
+ const related2 = e.relatedTarget instanceof Element ? e.relatedTarget : null;
9382
+ if (related2?.closest("[data-ohw-nav-container], [data-ohw-navbar-container-chrome], [data-ohw-navbar-add-button]")) {
9383
+ return;
9384
+ }
9385
+ hoveredNavContainerRef.current = null;
9386
+ setHoveredNavContainerRect(null);
9387
+ }
9388
+ const footerCol = target.closest("[data-ohw-footer-col]");
9389
+ if (footerCol && hoveredItemElRef.current === footerCol) {
9390
+ const related2 = e.relatedTarget instanceof Element ? e.relatedTarget : null;
9391
+ if (related2?.closest("[data-ohw-drag-handle-container], [data-ohw-item-interaction]")) return;
9392
+ if (related2?.closest("[data-ohw-footer-col]") === footerCol) return;
9393
+ hoveredItemElRef.current = null;
9394
+ setHoveredItemRect(null);
9395
+ return;
9396
+ }
7365
9397
  const navAnchor = getNavigationItemAnchor(target);
7366
9398
  if (navAnchor) {
7367
9399
  const related2 = e.relatedTarget instanceof Element ? e.relatedTarget : null;
@@ -7483,44 +9515,87 @@ function OhhwellsBridge() {
7483
9515
  }
7484
9516
  return smallest;
7485
9517
  };
9518
+ const dismissImageHover = () => {
9519
+ if (hoveredImageRef.current) {
9520
+ hoveredImageRef.current = null;
9521
+ hoveredImageHasTextOverlapRef.current = false;
9522
+ resumeAnimTracks();
9523
+ postToParentRef.current({ type: "ow:image-unhover" });
9524
+ }
9525
+ clearImageHover();
9526
+ };
9527
+ const probeNavigationHoverAt = (x, y) => {
9528
+ if (toolbarVariantRef.current === "select-frame") {
9529
+ hoveredNavContainerRef.current = null;
9530
+ setHoveredNavContainerRect(null);
9531
+ return;
9532
+ }
9533
+ const navContainer = document.querySelector("[data-ohw-nav-container]");
9534
+ if (!navContainer) {
9535
+ hoveredNavContainerRef.current = null;
9536
+ setHoveredNavContainerRect(null);
9537
+ return;
9538
+ }
9539
+ const containerRect = navContainer.getBoundingClientRect();
9540
+ const overContainer = x >= containerRect.left && x <= containerRect.right && y >= containerRect.top && y <= containerRect.bottom;
9541
+ if (!overContainer) {
9542
+ hoveredNavContainerRef.current = null;
9543
+ setHoveredNavContainerRect(null);
9544
+ return;
9545
+ }
9546
+ const navItemHit = Array.from(
9547
+ navContainer.querySelectorAll("[data-ohw-href-key]")
9548
+ ).find((el) => {
9549
+ if (!isNavigationItem(el) || isNavbarButton2(el)) return false;
9550
+ const itemRect = el.getBoundingClientRect();
9551
+ return x >= itemRect.left && x <= itemRect.right && y >= itemRect.top && y <= itemRect.bottom;
9552
+ });
9553
+ if (navItemHit) {
9554
+ hoveredNavContainerRef.current = null;
9555
+ setHoveredNavContainerRect(null);
9556
+ const selected = selectedElRef.current;
9557
+ if (selected !== navItemHit && !selected?.contains(navItemHit)) {
9558
+ clearHrefKeyHover(navItemHit);
9559
+ hoveredItemElRef.current = navItemHit;
9560
+ setHoveredItemRect(navItemHit.getBoundingClientRect());
9561
+ }
9562
+ return;
9563
+ }
9564
+ hoveredNavContainerRef.current = navContainer;
9565
+ setHoveredNavContainerRect(containerRect);
9566
+ hoveredItemElRef.current = null;
9567
+ setHoveredItemRect(null);
9568
+ };
7486
9569
  const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
7487
9570
  if (linkPopoverOpenRef.current) {
7488
- if (hoveredImageRef.current) {
7489
- hoveredImageRef.current = null;
7490
- hoveredImageHasTextOverlapRef.current = false;
7491
- resumeAnimTracks();
7492
- clearImageHover();
7493
- }
9571
+ dismissImageHover();
9572
+ return;
9573
+ }
9574
+ const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
9575
+ if (isPointOverNavigation(x, y)) {
9576
+ dismissImageHover();
9577
+ probeNavigationHoverAt(x, y);
7494
9578
  return;
7495
9579
  }
9580
+ hoveredNavContainerRef.current = null;
9581
+ setHoveredNavContainerRect(null);
7496
9582
  const toggleEl = document.querySelector("[data-ohw-state-toggle]");
7497
9583
  if (toggleEl) {
7498
- const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
7499
9584
  const tr = toggleEl.getBoundingClientRect();
7500
9585
  if (x >= tr.left && x <= tr.right && y >= tr.top && y <= tr.bottom) {
7501
- if (hoveredImageRef.current) {
7502
- hoveredImageRef.current = null;
7503
- resumeAnimTracks();
7504
- clearImageHover();
7505
- }
9586
+ dismissImageHover();
7506
9587
  return;
7507
9588
  }
7508
9589
  }
7509
9590
  const imgEl = findImageAtPoint(clientX, clientY, fromParentViewport);
7510
9591
  const activeEl = activeElRef.current;
7511
9592
  if (activeEl && (!imgEl || imgEl.contains(activeEl))) {
7512
- if (hoveredImageRef.current) {
7513
- hoveredImageRef.current = null;
7514
- hoveredImageHasTextOverlapRef.current = false;
7515
- resumeAnimTracks();
7516
- clearImageHover();
7517
- }
9593
+ dismissImageHover();
7518
9594
  return;
7519
9595
  }
7520
9596
  if (imgEl) {
7521
- const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
7522
9597
  const topEl = document.elementFromPoint(x, y);
7523
- if (topEl?.closest('[data-ohw-toolbar], [data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
9598
+ 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]')) {
7524
9599
  if (hoveredImageRef.current) {
7525
9600
  hoveredImageRef.current = null;
7526
9601
  resumeAnimTracks();
@@ -7618,12 +9693,12 @@ function OhhwellsBridge() {
7618
9693
  resumeAnimTracks();
7619
9694
  clearImageHover();
7620
9695
  }
7621
- const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
9696
+ const { x: x2, y: y2 } = toProbeCoords(clientX, clientY, fromParentViewport);
7622
9697
  const textEl = Array.from(
7623
9698
  document.querySelectorAll(NON_MEDIA_SELECTOR)
7624
9699
  ).find((el) => {
7625
9700
  const er = el.getBoundingClientRect();
7626
- return x >= er.left && x <= er.right && y >= er.top && y <= er.bottom;
9701
+ return x2 >= er.left && x2 <= er.right && y2 >= er.top && y2 <= er.bottom;
7627
9702
  });
7628
9703
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
7629
9704
  if (textEl && !textEl.hasAttribute("contenteditable")) {
@@ -7647,6 +9722,14 @@ function OhhwellsBridge() {
7647
9722
  }
7648
9723
  };
7649
9724
  const probeHoverCardsAt = (clientX, clientY, fromParentViewport = false) => {
9725
+ if (linkPopoverOpenRef.current || document.documentElement.hasAttribute("data-ohw-section-picking")) {
9726
+ if (activeStateElRef.current) {
9727
+ activeStateElRef.current.removeAttribute("data-ohw-state-hovered");
9728
+ activeStateElRef.current = null;
9729
+ setToggleState(null);
9730
+ }
9731
+ return;
9732
+ }
7650
9733
  const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
7651
9734
  const toggleEl = document.querySelector("[data-ohw-state-toggle]");
7652
9735
  if (toggleEl) {
@@ -7719,6 +9802,14 @@ function OhhwellsBridge() {
7719
9802
  probeHoverCardsAt(clientX, clientY);
7720
9803
  };
7721
9804
  const handleDragOver = (e) => {
9805
+ const footerSession = footerDragRef.current;
9806
+ if (footerSession) {
9807
+ e.preventDefault();
9808
+ if (e.dataTransfer) e.dataTransfer.dropEffect = "move";
9809
+ const slot = footerSession.kind === "link" && footerSession.hrefKey ? hitTestLinkDropSlot(e.clientX, e.clientY, footerSession.hrefKey) : hitTestColumnDropSlot(e.clientX, e.clientY);
9810
+ refreshFooterDragVisualsRef.current(footerSession, slot, e.clientX, e.clientY);
9811
+ return;
9812
+ }
7722
9813
  e.preventDefault();
7723
9814
  const el = findImageAtPoint(e.clientX, e.clientY, false);
7724
9815
  if (!el) {
@@ -7901,6 +9992,7 @@ function OhhwellsBridge() {
7901
9992
  continue;
7902
9993
  }
7903
9994
  if (applyVideoSettingNode(key, val)) continue;
9995
+ if (applyCarouselNode(key, val)) continue;
7904
9996
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
7905
9997
  if (el.dataset.ohwEditable === "image") {
7906
9998
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -7923,6 +10015,9 @@ function OhhwellsBridge() {
7923
10015
  sectionsLoadedRef.current = true;
7924
10016
  pendingScheduleConfigRequests.current.splice(0).forEach(processConfigRequest);
7925
10017
  }
10018
+ editContentRef.current = { ...editContentRef.current, ...content };
10019
+ reconcileNavbarItemsFromContent(editContentRef.current);
10020
+ reconcileFooterOrderFromContent(editContentRef.current);
7926
10021
  enforceLinkHrefs();
7927
10022
  postToParentRef.current({ type: "ow:hydrate-done" });
7928
10023
  };
@@ -7930,6 +10025,7 @@ function OhhwellsBridge() {
7930
10025
  const handleDeactivate = (e) => {
7931
10026
  if (e.data?.type !== "ow:deactivate") return;
7932
10027
  if (Date.now() < linkPopoverGraceUntilRef.current) return;
10028
+ if (document.querySelector("[data-ohw-section-picker]")) return;
7933
10029
  if (linkPopoverOpenRef.current) {
7934
10030
  setLinkPopoverRef.current(null);
7935
10031
  return;
@@ -7938,13 +10034,40 @@ function OhhwellsBridge() {
7938
10034
  deactivateRef.current();
7939
10035
  };
7940
10036
  window.addEventListener("message", handleDeactivate);
10037
+ const handleUiEscape = (e) => {
10038
+ if (e.data?.type !== "ui:escape") return;
10039
+ if (linkPopoverOpenRef.current) {
10040
+ setLinkPopoverRef.current(null);
10041
+ }
10042
+ };
10043
+ window.addEventListener("message", handleUiEscape);
7941
10044
  const handleKeyDown = (e) => {
10045
+ if (e.key === "Escape" && document.querySelector("[data-ohw-section-picker]")) return;
10046
+ if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "a" && activeElRef.current) {
10047
+ const navAnchor = getNavigationItemAnchor(activeElRef.current);
10048
+ if (navAnchor) {
10049
+ e.preventDefault();
10050
+ selectAllTextInEditable(activeElRef.current);
10051
+ refreshActiveCommandsRef.current();
10052
+ return;
10053
+ }
10054
+ }
7942
10055
  if (e.key === "Escape" && linkPopoverOpenRef.current) {
7943
10056
  setLinkPopoverRef.current(null);
7944
10057
  return;
7945
10058
  }
7946
10059
  if (e.key === "Escape" && selectedElRef.current && !activeElRef.current) {
7947
- deselectRef.current();
10060
+ if (toolbarVariantRef.current === "select-frame") {
10061
+ deselectRef.current();
10062
+ return;
10063
+ }
10064
+ const parent = getNavigationSelectionParent(selectedElRef.current);
10065
+ if (parent) {
10066
+ e.preventDefault();
10067
+ selectFrameRef.current(parent);
10068
+ } else {
10069
+ deselectRef.current();
10070
+ }
7948
10071
  return;
7949
10072
  }
7950
10073
  if (e.key === "Escape" && activeElRef.current) {
@@ -8002,9 +10125,23 @@ function OhhwellsBridge() {
8002
10125
  if (hoveredItemElRef.current) {
8003
10126
  setHoveredItemRect(hoveredItemElRef.current.getBoundingClientRect());
8004
10127
  }
10128
+ if (hoveredNavContainerRef.current) {
10129
+ setHoveredNavContainerRect(hoveredNavContainerRef.current.getBoundingClientRect());
10130
+ }
8005
10131
  if (siblingHintElRef.current) {
8006
10132
  setSiblingHintRect(siblingHintElRef.current.getBoundingClientRect());
8007
10133
  }
10134
+ const selected = selectedElRef.current;
10135
+ if (selected && !footerDragRef.current && (selected.hasAttribute("data-ohw-footer-col") || Boolean(selected.closest("footer") && isInferredFooterGroup(selected)))) {
10136
+ setSiblingHintRects(
10137
+ listFooterColumns().filter((column) => column !== selected).map((column) => column.getBoundingClientRect())
10138
+ );
10139
+ }
10140
+ if (footerDragRef.current) {
10141
+ const session = footerDragRef.current;
10142
+ const slot = session.kind === "link" && session.hrefKey ? hitTestLinkDropSlot(session.lastClientX, session.lastClientY, session.hrefKey) : hitTestColumnDropSlot(session.lastClientX, session.lastClientY);
10143
+ refreshFooterDragVisualsRef.current(session, slot, session.lastClientX, session.lastClientY);
10144
+ }
8008
10145
  if (hoveredImageRef.current) {
8009
10146
  const el = hoveredImageRef.current;
8010
10147
  const r2 = el.getBoundingClientRect();
@@ -8015,7 +10152,7 @@ function OhhwellsBridge() {
8015
10152
  };
8016
10153
  const handleSave = (e) => {
8017
10154
  if (e.data?.type !== "ow:save") return;
8018
- const nodes = collectEditableNodes();
10155
+ const nodes = collectEditableNodes(editContentRef.current);
8019
10156
  const tracker = document.querySelector("[data-ohw-sections-tracker]");
8020
10157
  if (tracker?.textContent) nodes.push({ key: "__ohw_sections", type: "sections", text: tracker.textContent });
8021
10158
  postToParentRef.current({ type: "ow:save-result", nodes });
@@ -8100,6 +10237,7 @@ function OhhwellsBridge() {
8100
10237
  const handleDocMouseLeave = () => {
8101
10238
  hoveredImageRef.current = null;
8102
10239
  setMediaHover(null);
10240
+ setCarouselHover(null);
8103
10241
  document.querySelectorAll("[data-ohw-state-hovered]").forEach((el) => el.removeAttribute("data-ohw-state-hovered"));
8104
10242
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
8105
10243
  activeStateElRef.current = null;
@@ -8139,6 +10277,48 @@ function OhhwellsBridge() {
8139
10277
  if (e.data?.type !== "ow:canvas-height" || typeof e.data.height !== "number") return;
8140
10278
  document.documentElement.style.setProperty("--ohw-canvas-h", `${e.data.height}px`);
8141
10279
  };
10280
+ const handleCarouselChange = (e) => {
10281
+ if (e.data?.type !== "ow:carousel-change") return;
10282
+ const { key, images } = e.data;
10283
+ if (!key || !Array.isArray(images)) return;
10284
+ const text = JSON.stringify(images);
10285
+ applyCarouselValue(key, images);
10286
+ editContentRef.current = { ...editContentRef.current, [key]: text };
10287
+ postToParentRef.current({ type: "ow:change", nodes: [{ key, text }] });
10288
+ setCarouselHover(null);
10289
+ };
10290
+ const findCarouselAtPoint = (clientX, clientY) => {
10291
+ const containers = Array.from(document.querySelectorAll(`[${CAROUSEL_ATTR}]`));
10292
+ for (let i = containers.length - 1; i >= 0; i--) {
10293
+ const r2 = containers[i].getBoundingClientRect();
10294
+ if (clientX >= r2.left && clientX <= r2.right && clientY >= r2.top && clientY <= r2.bottom) {
10295
+ return containers[i];
10296
+ }
10297
+ }
10298
+ return null;
10299
+ };
10300
+ const isPointOverEditable = (scope, clientX, clientY) => {
10301
+ const editables = scope.querySelectorAll("[data-ohw-editable], [data-ohw-href-key]");
10302
+ for (const el of editables) {
10303
+ const r2 = el.getBoundingClientRect();
10304
+ if (clientX >= r2.left && clientX <= r2.right && clientY >= r2.top && clientY <= r2.bottom) return true;
10305
+ }
10306
+ return false;
10307
+ };
10308
+ const handleCarouselHover = (e) => {
10309
+ const container = findCarouselAtPoint(e.clientX, e.clientY);
10310
+ const scope = container?.closest("[data-ohw-section]") ?? container?.parentElement ?? null;
10311
+ if (!container || !scope || isPointOverEditable(scope, e.clientX, e.clientY)) {
10312
+ setCarouselHover((prev) => prev ? null : prev);
10313
+ return;
10314
+ }
10315
+ const key = container.getAttribute("data-ohw-key") ?? "";
10316
+ if (!key) return;
10317
+ const r2 = getVisibleRect(container);
10318
+ setCarouselHover(
10319
+ (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 } }
10320
+ );
10321
+ };
8142
10322
  const applyToolbarPos = (rect) => {
8143
10323
  const ps = parentScrollRef.current;
8144
10324
  const approxW = 330;
@@ -8198,9 +10378,45 @@ function OhhwellsBridge() {
8198
10378
  return clientX >= r2.left && clientX <= r2.right && clientY >= r2.top && clientY <= r2.bottom;
8199
10379
  });
8200
10380
  if (textEditable) {
8201
- activateRef.current(textEditable);
10381
+ const hrefCtx = getHrefKeyFromElement(textEditable);
10382
+ const navAnchor = hrefCtx ? getNavigationItemAnchor(hrefCtx.anchor) : null;
10383
+ if (navAnchor) {
10384
+ if (selectedElRef.current === navAnchor) {
10385
+ activateRef.current(textEditable, { caretX: clientX, caretY: clientY });
10386
+ } else {
10387
+ selectRef.current(navAnchor);
10388
+ }
10389
+ return;
10390
+ }
10391
+ activateRef.current(textEditable, { caretX: clientX, caretY: clientY });
8202
10392
  return;
8203
10393
  }
10394
+ const navContainer = document.querySelector("[data-ohw-nav-container]");
10395
+ if (navContainer) {
10396
+ const r2 = navContainer.getBoundingClientRect();
10397
+ const pad = 8;
10398
+ const over = clientX >= r2.left - pad && clientX <= r2.right + pad && clientY >= r2.top - pad && clientY <= r2.bottom + pad;
10399
+ if (over && !isPointOverNavItem(navContainer, clientX, clientY)) {
10400
+ selectFrameRef.current(navContainer);
10401
+ return;
10402
+ }
10403
+ }
10404
+ const navRoot = document.querySelector("[data-ohw-nav-root]");
10405
+ if (navRoot && navContainer) {
10406
+ const rr = navRoot.getBoundingClientRect();
10407
+ if (clientX >= rr.left && clientX <= rr.right && clientY >= rr.top && clientY <= rr.bottom && !isPointOverNavItem(navContainer, clientX, clientY)) {
10408
+ const book = navRoot.querySelector('[data-ohw-role="navbar-button"]');
10409
+ if (book) {
10410
+ const br = book.getBoundingClientRect();
10411
+ if (clientX >= br.left && clientX <= br.right && clientY >= br.top && clientY <= br.bottom) {
10412
+ deactivateRef.current();
10413
+ return;
10414
+ }
10415
+ }
10416
+ selectFrameRef.current(navContainer);
10417
+ return;
10418
+ }
10419
+ }
8204
10420
  deactivateRef.current();
8205
10421
  };
8206
10422
  window.addEventListener("message", handleSave);
@@ -8211,6 +10427,7 @@ function OhhwellsBridge() {
8211
10427
  window.addEventListener("message", handleRemoveSchedulingSection);
8212
10428
  window.addEventListener("message", handleImageUrl);
8213
10429
  window.addEventListener("message", handleImageUploading);
10430
+ window.addEventListener("message", handleCarouselChange);
8214
10431
  window.addEventListener("message", handleCanvasHeight);
8215
10432
  window.addEventListener("message", handleParentScroll);
8216
10433
  window.addEventListener("message", handlePointerSync);
@@ -8222,11 +10439,13 @@ function OhhwellsBridge() {
8222
10439
  };
8223
10440
  window.addEventListener("resize", handleViewportResize, { passive: true });
8224
10441
  document.addEventListener("click", handleClick, true);
10442
+ document.addEventListener("dblclick", handleDblClick, true);
8225
10443
  document.addEventListener("paste", handlePaste, true);
8226
10444
  document.addEventListener("input", handleInput, true);
8227
10445
  document.addEventListener("mouseover", handleMouseOver, true);
8228
10446
  document.addEventListener("mouseout", handleMouseOut, true);
8229
10447
  document.addEventListener("mousemove", handleMouseMove, true);
10448
+ document.addEventListener("mousemove", handleCarouselHover, true);
8230
10449
  document.addEventListener("mouseleave", handleDocMouseLeave);
8231
10450
  document.addEventListener("dragover", handleDragOver, true);
8232
10451
  document.addEventListener("dragleave", handleDragLeave, true);
@@ -8236,11 +10455,13 @@ function OhhwellsBridge() {
8236
10455
  window.addEventListener("scroll", handleScroll, true);
8237
10456
  return () => {
8238
10457
  document.removeEventListener("click", handleClick, true);
10458
+ document.removeEventListener("dblclick", handleDblClick, true);
8239
10459
  document.removeEventListener("paste", handlePaste, true);
8240
10460
  document.removeEventListener("input", handleInput, true);
8241
10461
  document.removeEventListener("mouseover", handleMouseOver, true);
8242
10462
  document.removeEventListener("mouseout", handleMouseOut, true);
8243
10463
  document.removeEventListener("mousemove", handleMouseMove, true);
10464
+ document.removeEventListener("mousemove", handleCarouselHover, true);
8244
10465
  document.removeEventListener("mouseleave", handleDocMouseLeave);
8245
10466
  document.removeEventListener("dragover", handleDragOver, true);
8246
10467
  document.removeEventListener("dragleave", handleDragLeave, true);
@@ -8256,6 +10477,7 @@ function OhhwellsBridge() {
8256
10477
  window.removeEventListener("message", handleRemoveSchedulingSection);
8257
10478
  window.removeEventListener("message", handleImageUrl);
8258
10479
  window.removeEventListener("message", handleImageUploading);
10480
+ window.removeEventListener("message", handleCarouselChange);
8259
10481
  window.removeEventListener("message", handleCanvasHeight);
8260
10482
  window.removeEventListener("message", handleParentScroll);
8261
10483
  window.removeEventListener("resize", handleViewportResize);
@@ -8263,13 +10485,165 @@ function OhhwellsBridge() {
8263
10485
  window.removeEventListener("message", handleClickAt);
8264
10486
  window.removeEventListener("message", handleHydrate);
8265
10487
  window.removeEventListener("message", handleDeactivate);
10488
+ window.removeEventListener("message", handleUiEscape);
8266
10489
  autoSaveTimers.current.forEach(clearTimeout);
8267
10490
  autoSaveTimers.current.clear();
8268
10491
  if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
8269
10492
  if (imageShowTimerRef.current) clearTimeout(imageShowTimerRef.current);
8270
10493
  };
8271
10494
  }, [isEditMode, refreshStateRules]);
8272
- (0, import_react6.useEffect)(() => {
10495
+ (0, import_react9.useEffect)(() => {
10496
+ if (!isEditMode) return;
10497
+ const THRESHOLD = 10;
10498
+ const resolveWasSelected = (el) => {
10499
+ if (selectedElRef.current === el) return true;
10500
+ const activeAnchor = activeElRef.current ? getNavigationItemAnchor(activeElRef.current) : null;
10501
+ return activeAnchor === el;
10502
+ };
10503
+ const onPointerDown = (e) => {
10504
+ if (e.button !== 0) return;
10505
+ if (suppressNextClickRef.current && !footerDragRef.current && !footerPointerDragRef.current?.started) {
10506
+ suppressNextClickRef.current = false;
10507
+ }
10508
+ if (footerDragRef.current) return;
10509
+ const target = e.target;
10510
+ if (!target) return;
10511
+ 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]')) {
10512
+ return;
10513
+ }
10514
+ if (target.closest("[data-ohw-item-drag-surface]")) return;
10515
+ const anchor = getNavigationItemAnchor(target);
10516
+ const hrefKey = anchor?.getAttribute("data-ohw-href-key") ?? null;
10517
+ if (anchor && isFooterHrefKey(hrefKey)) {
10518
+ armFooterPressDrag();
10519
+ footerPointerDragRef.current = {
10520
+ el: anchor,
10521
+ kind: "link",
10522
+ startX: e.clientX,
10523
+ startY: e.clientY,
10524
+ pointerId: e.pointerId,
10525
+ wasSelected: resolveWasSelected(anchor),
10526
+ started: false
10527
+ };
10528
+ return;
10529
+ }
10530
+ const col = target.closest("[data-ohw-footer-col]");
10531
+ if (col && !getNavigationItemAnchor(target)) {
10532
+ armFooterPressDrag();
10533
+ footerPointerDragRef.current = {
10534
+ el: col,
10535
+ kind: "column",
10536
+ startX: e.clientX,
10537
+ startY: e.clientY,
10538
+ pointerId: e.pointerId,
10539
+ wasSelected: resolveWasSelected(col),
10540
+ started: false
10541
+ };
10542
+ }
10543
+ };
10544
+ const onPointerMove = (e) => {
10545
+ const pending = footerPointerDragRef.current;
10546
+ if (!pending) return;
10547
+ if (pending.started) {
10548
+ e.preventDefault();
10549
+ clearTextSelection();
10550
+ const session = footerDragRef.current;
10551
+ if (!session) return;
10552
+ const slot = session.kind === "link" && session.hrefKey ? hitTestLinkDropSlot(e.clientX, e.clientY, session.hrefKey) : hitTestColumnDropSlot(e.clientX, e.clientY);
10553
+ refreshFooterDragVisualsRef.current(session, slot, e.clientX, e.clientY);
10554
+ return;
10555
+ }
10556
+ const dx = e.clientX - pending.startX;
10557
+ const dy = e.clientY - pending.startY;
10558
+ if (dx * dx + dy * dy < THRESHOLD * THRESHOLD) return;
10559
+ e.preventDefault();
10560
+ pending.started = true;
10561
+ clearTextSelection();
10562
+ try {
10563
+ document.body.setPointerCapture(pending.pointerId);
10564
+ } catch {
10565
+ }
10566
+ if (linkPopoverOpenRef.current) {
10567
+ setLinkPopoverRef.current(null);
10568
+ }
10569
+ if (activeElRef.current) {
10570
+ deactivateRef.current();
10571
+ }
10572
+ if (pending.kind === "link") {
10573
+ const key = pending.el.getAttribute("data-ohw-href-key");
10574
+ if (!key) return;
10575
+ const column = findFooterColumnForLink(pending.el);
10576
+ const columns2 = listFooterColumns();
10577
+ beginFooterDragRef.current({
10578
+ kind: "link",
10579
+ hrefKey: key,
10580
+ columnEl: column,
10581
+ sourceColumnIndex: column ? columns2.indexOf(column) : 0,
10582
+ wasSelected: pending.wasSelected,
10583
+ draggedEl: pending.el,
10584
+ lastClientX: e.clientX,
10585
+ lastClientY: e.clientY,
10586
+ activeSlot: null
10587
+ });
10588
+ return;
10589
+ }
10590
+ const columns = listFooterColumns();
10591
+ const idx = columns.indexOf(pending.el);
10592
+ if (idx < 0) return;
10593
+ beginFooterDragRef.current({
10594
+ kind: "column",
10595
+ hrefKey: null,
10596
+ columnEl: pending.el,
10597
+ sourceColumnIndex: idx,
10598
+ wasSelected: pending.wasSelected,
10599
+ draggedEl: pending.el,
10600
+ lastClientX: e.clientX,
10601
+ lastClientY: e.clientY,
10602
+ activeSlot: null
10603
+ });
10604
+ };
10605
+ const endPointerDrag = (e) => {
10606
+ const pending = footerPointerDragRef.current;
10607
+ footerPointerDragRef.current = null;
10608
+ try {
10609
+ if (document.body.hasPointerCapture(e.pointerId)) {
10610
+ document.body.releasePointerCapture(e.pointerId);
10611
+ }
10612
+ } catch {
10613
+ }
10614
+ if (!pending?.started) {
10615
+ unlockFooterDragInteraction();
10616
+ return;
10617
+ }
10618
+ suppressNextClickRef.current = true;
10619
+ commitFooterDragRef.current(e.clientX, e.clientY);
10620
+ };
10621
+ const blockSelectStart = (e) => {
10622
+ if (footerDragRef.current || footerPointerDragRef.current?.started || document.documentElement.hasAttribute("data-ohw-footer-press-drag")) {
10623
+ e.preventDefault();
10624
+ }
10625
+ };
10626
+ const onDragEnd = (_e) => {
10627
+ if (!footerDragRef.current) return;
10628
+ commitFooterDragRef.current();
10629
+ };
10630
+ document.addEventListener("pointerdown", onPointerDown, true);
10631
+ document.addEventListener("pointermove", onPointerMove, true);
10632
+ document.addEventListener("pointerup", endPointerDrag, true);
10633
+ document.addEventListener("pointercancel", endPointerDrag, true);
10634
+ document.addEventListener("selectstart", blockSelectStart, true);
10635
+ document.addEventListener("dragend", onDragEnd, true);
10636
+ return () => {
10637
+ document.removeEventListener("pointerdown", onPointerDown, true);
10638
+ document.removeEventListener("pointermove", onPointerMove, true);
10639
+ document.removeEventListener("pointerup", endPointerDrag, true);
10640
+ document.removeEventListener("pointercancel", endPointerDrag, true);
10641
+ document.removeEventListener("selectstart", blockSelectStart, true);
10642
+ document.removeEventListener("dragend", onDragEnd, true);
10643
+ unlockFooterDragInteraction();
10644
+ };
10645
+ }, [isEditMode]);
10646
+ (0, import_react9.useEffect)(() => {
8273
10647
  const handler = (e) => {
8274
10648
  if (e.data?.type !== "ow:request-schedule-config") return;
8275
10649
  const insertAfterVal = e.data.insertAfter;
@@ -8285,7 +10659,7 @@ function OhhwellsBridge() {
8285
10659
  window.addEventListener("message", handler);
8286
10660
  return () => window.removeEventListener("message", handler);
8287
10661
  }, [processConfigRequest]);
8288
- (0, import_react6.useEffect)(() => {
10662
+ (0, import_react9.useEffect)(() => {
8289
10663
  if (!isEditMode) return;
8290
10664
  document.querySelectorAll("[data-ohw-active-state]").forEach((el) => {
8291
10665
  el.removeAttribute("data-ohw-active-state");
@@ -8306,27 +10680,33 @@ function OhhwellsBridge() {
8306
10680
  const next = { ...prev, [pathKey]: sections };
8307
10681
  return shouldUseDevFixtures() ? { ...DEV_SECTIONS_BY_PATH, ...next } : next;
8308
10682
  });
8309
- postToParent({ type: "ow:ready", version: "1", path: pathname, nodes: collectEditableNodes(), sections });
8310
- postToParent({ type: "ow:request-site-pages" });
10683
+ postToParent2({
10684
+ type: "ow:ready",
10685
+ version: "1",
10686
+ path: pathname,
10687
+ nodes: collectEditableNodes(editContentRef.current),
10688
+ sections
10689
+ });
10690
+ postToParent2({ type: "ow:request-site-pages" });
8311
10691
  }, 150);
8312
10692
  return () => {
8313
10693
  cancelAnimationFrame(raf);
8314
10694
  clearTimeout(timer);
8315
10695
  };
8316
- }, [pathname, isEditMode, refreshStateRules, postToParent]);
8317
- (0, import_react6.useEffect)(() => {
10696
+ }, [pathname, isEditMode, refreshStateRules, postToParent2]);
10697
+ (0, import_react9.useEffect)(() => {
8318
10698
  scrollToHashSectionWhenReady();
8319
10699
  const onHashChange = () => scrollToHashSectionWhenReady();
8320
10700
  window.addEventListener("hashchange", onHashChange);
8321
10701
  return () => window.removeEventListener("hashchange", onHashChange);
8322
10702
  }, [pathname]);
8323
- const handleCommand = (0, import_react6.useCallback)((cmd) => {
10703
+ const handleCommand = (0, import_react9.useCallback)((cmd) => {
8324
10704
  document.execCommand(cmd, false);
8325
10705
  activeElRef.current?.focus();
8326
10706
  if (activeElRef.current) setToolbarRect(getEditMeasureEl(activeElRef.current).getBoundingClientRect());
8327
10707
  refreshActiveCommandsRef.current();
8328
10708
  }, []);
8329
- const handleStateChange = (0, import_react6.useCallback)((state) => {
10709
+ const handleStateChange = (0, import_react9.useCallback)((state) => {
8330
10710
  if (!activeStateElRef.current) return;
8331
10711
  const el = activeStateElRef.current;
8332
10712
  if (state === "Default") {
@@ -8339,18 +10719,22 @@ function OhhwellsBridge() {
8339
10719
  }
8340
10720
  setToggleState((prev) => prev ? { ...prev, activeState: state } : null);
8341
10721
  }, [deactivate]);
8342
- const closeLinkPopover = (0, import_react6.useCallback)(() => setLinkPopover(null), []);
8343
- const openLinkPopoverForActive = (0, import_react6.useCallback)(() => {
10722
+ const closeLinkPopover = (0, import_react9.useCallback)(() => {
10723
+ addNavAfterAnchorRef.current = null;
10724
+ setLinkPopover(null);
10725
+ }, []);
10726
+ const openLinkPopoverForActive = (0, import_react9.useCallback)(() => {
8344
10727
  const hrefCtx = getHrefKeyFromElement(activeElRef.current);
8345
10728
  if (!hrefCtx) return;
8346
10729
  bumpLinkPopoverGrace();
8347
10730
  setLinkPopover({
8348
10731
  key: hrefCtx.key,
8349
- target: getLinkHref(hrefCtx.anchor)
10732
+ mode: "edit",
10733
+ target: getLinkHref2(hrefCtx.anchor)
8350
10734
  });
8351
10735
  deactivate();
8352
10736
  }, [deactivate]);
8353
- const openLinkPopoverForSelected = (0, import_react6.useCallback)(() => {
10737
+ const openLinkPopoverForSelected = (0, import_react9.useCallback)(() => {
8354
10738
  const anchor = selectedElRef.current;
8355
10739
  if (!anchor) return;
8356
10740
  const key = anchor.getAttribute("data-ohw-href-key");
@@ -8358,30 +10742,80 @@ function OhhwellsBridge() {
8358
10742
  bumpLinkPopoverGrace();
8359
10743
  setLinkPopover({
8360
10744
  key,
8361
- target: getLinkHref(anchor)
10745
+ mode: "edit",
10746
+ target: getLinkHref2(anchor)
8362
10747
  });
8363
10748
  deselect();
8364
10749
  }, [deselect]);
8365
- const handleLinkPopoverSubmit = (0, import_react6.useCallback)(
10750
+ const handleLinkPopoverSubmit = (0, import_react9.useCallback)(
8366
10751
  (target) => {
8367
- if (!linkPopover) return;
8368
- const { key } = linkPopover;
10752
+ const session = linkPopoverSessionRef.current;
10753
+ if (!session) return;
10754
+ if (session.intent === "add-nav") {
10755
+ const trimmed = target.trim();
10756
+ let href = trimmed;
10757
+ let label = "Untitled";
10758
+ if (trimmed) {
10759
+ const { pageRoute, sectionId } = parseTarget(trimmed);
10760
+ const page = resolvePage(pageRoute, sitePages);
10761
+ const pageSections = getSectionsForPath(sectionsByPath, pageRoute);
10762
+ const section = sectionId ? pageSections.find((s) => s.id === sectionId) : void 0;
10763
+ label = section?.label ?? page.title;
10764
+ href = trimmed;
10765
+ }
10766
+ const afterAnchor = addNavAfterAnchorRef.current;
10767
+ const { anchor, hrefKey, labelKey, index, order } = insertNavbarItem(href, label, afterAnchor);
10768
+ applyLinkByKey(hrefKey, href);
10769
+ document.querySelectorAll(`[data-ohw-key="${labelKey}"]`).forEach((el) => {
10770
+ el.textContent = label;
10771
+ });
10772
+ const navCount = String(index + 1);
10773
+ const orderJson = JSON.stringify(order);
10774
+ editContentRef.current = {
10775
+ ...editContentRef.current,
10776
+ [hrefKey]: href,
10777
+ [labelKey]: label,
10778
+ [NAV_COUNT_KEY]: navCount,
10779
+ [NAV_ORDER_KEY]: orderJson
10780
+ };
10781
+ postToParent2({
10782
+ type: "ow:change",
10783
+ nodes: [
10784
+ { key: hrefKey, text: href },
10785
+ { key: labelKey, text: label },
10786
+ { key: NAV_COUNT_KEY, text: navCount },
10787
+ { key: NAV_ORDER_KEY, text: orderJson }
10788
+ ]
10789
+ });
10790
+ postToParent2({ type: "ow:toast", title: "Link added", toastType: "success" });
10791
+ addNavAfterAnchorRef.current = null;
10792
+ setLinkPopover(null);
10793
+ enforceLinkHrefs();
10794
+ requestAnimationFrame(() => {
10795
+ selectRef.current(anchor);
10796
+ });
10797
+ return;
10798
+ }
10799
+ const { key } = session;
8369
10800
  applyLinkByKey(key, target);
8370
- postToParent({ type: "ow:change", nodes: [{ key, text: target }] });
10801
+ postToParent2({ type: "ow:change", nodes: [{ key, text: target }] });
8371
10802
  setLinkPopover(null);
8372
10803
  },
8373
- [linkPopover, postToParent]
10804
+ [postToParent2, sitePages, sectionsByPath]
8374
10805
  );
8375
- const showEditLink = toolbarShowEditLink;
8376
- const currentSections = sectionsByPath[pathname] ?? [];
8377
- linkPopoverOpenRef.current = linkPopover !== null;
8378
- const handleMediaReplace = (0, import_react6.useCallback)(
10806
+ const handleMediaReplace = (0, import_react9.useCallback)(
10807
+ (key) => {
10808
+ postToParent2({ type: "ow:image-pick", key, elementType: mediaHover?.elementType ?? "image" });
10809
+ },
10810
+ [postToParent2, mediaHover?.elementType]
10811
+ );
10812
+ const handleEditCarousel = (0, import_react9.useCallback)(
8379
10813
  (key) => {
8380
- postToParent({ type: "ow:image-pick", key, elementType: mediaHover?.elementType ?? "image" });
10814
+ postToParent2({ type: "ow:carousel-open", key, images: readCarouselValue(key) });
8381
10815
  },
8382
- [postToParent, mediaHover?.elementType]
10816
+ [postToParent2]
8383
10817
  );
8384
- const handleMediaFadeOutComplete = (0, import_react6.useCallback)((key) => {
10818
+ const handleMediaFadeOutComplete = (0, import_react9.useCallback)((key) => {
8385
10819
  setUploadingRects((prev) => {
8386
10820
  if (!(key in prev)) return prev;
8387
10821
  const next = { ...prev };
@@ -8389,7 +10823,7 @@ function OhhwellsBridge() {
8389
10823
  return next;
8390
10824
  });
8391
10825
  }, []);
8392
- const handleVideoSettingsChange = (0, import_react6.useCallback)(
10826
+ const handleVideoSettingsChange = (0, import_react9.useCallback)(
8393
10827
  (key, settings) => {
8394
10828
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
8395
10829
  const video = getVideoEl(el);
@@ -8397,7 +10831,7 @@ function OhhwellsBridge() {
8397
10831
  if (typeof settings.autoplay === "boolean") video.autoplay = settings.autoplay;
8398
10832
  if (typeof settings.muted === "boolean") video.muted = settings.muted;
8399
10833
  syncVideoPlayback(video);
8400
- postToParent({
10834
+ postToParent2({
8401
10835
  type: "ow:change",
8402
10836
  nodes: [
8403
10837
  { key: `${key}${VIDEO_AUTOPLAY_SUFFIX}`, text: String(video.autoplay) },
@@ -8409,12 +10843,15 @@ function OhhwellsBridge() {
8409
10843
  );
8410
10844
  });
8411
10845
  },
8412
- [postToParent]
10846
+ [postToParent2]
8413
10847
  );
8414
- return bridgeRoot ? (0, import_react_dom2.createPortal)(
8415
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
8416
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
8417
- Object.entries(uploadingRects).map(([key, { rect, fadingOut }]) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
10848
+ const showEditLink = toolbarShowEditLink;
10849
+ const currentSections = sectionsByPath[pathname] ?? [];
10850
+ linkPopoverOpenRef.current = linkPopover !== null;
10851
+ return bridgeRoot ? (0, import_react_dom3.createPortal)(
10852
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(import_jsx_runtime25.Fragment, { children: [
10853
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
10854
+ Object.entries(uploadingRects).map(([key, { rect, fadingOut }]) => /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
8418
10855
  MediaOverlay,
8419
10856
  {
8420
10857
  hover: { key, rect, elementType: "image", isDragOver: false, hasTextOverlap: false },
@@ -8425,7 +10862,7 @@ function OhhwellsBridge() {
8425
10862
  },
8426
10863
  `uploading-${key}`
8427
10864
  )),
8428
- mediaHover && !(mediaHover.key in uploadingRects) && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
10865
+ mediaHover && !(mediaHover.key in uploadingRects) && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
8429
10866
  MediaOverlay,
8430
10867
  {
8431
10868
  hover: mediaHover,
@@ -8434,53 +10871,33 @@ function OhhwellsBridge() {
8434
10871
  onVideoSettingsChange: handleVideoSettingsChange
8435
10872
  }
8436
10873
  ),
8437
- siblingHintRect && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
8438
- hoveredItemRect && !siblingHintRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
8439
- toolbarRect && toolbarVariant === "link-action" && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8440
- ItemInteractionLayer,
10874
+ carouselHover && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(CarouselOverlay, { hover: carouselHover, onEdit: handleEditCarousel }),
10875
+ siblingHintRect && !isItemDragging && siblingHintRects.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
10876
+ siblingHintRects.map((rect, i) => /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ItemInteractionLayer, { rect, state: "sibling-hint" }, `sibling-hint-${i}`)),
10877
+ isItemDragging && draggedItemRect && selectedElRef.current !== footerDragRef.current?.draggedEl && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ItemInteractionLayer, { rect: draggedItemRect, state: "dragging" }),
10878
+ isItemDragging && footerDropSlots.map((slot, i) => /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
10879
+ "div",
8441
10880
  {
8442
- rect: toolbarRect,
8443
- elRef: glowElRef,
8444
- state: isItemDragging ? "dragging" : resolveItemInteractionState(toolbarRect, parentScrollRef.current),
8445
- showHandle: Boolean(reorderHrefKey),
8446
- dragDisabled: reorderDragDisabled,
8447
- dragHandleLabel: reorderHrefKey ? `Reorder ${reorderHrefKey}` : "Reorder item",
8448
- onDragHandleDragStart: handleItemDragStart,
8449
- onDragHandleDragEnd: handleItemDragEnd,
8450
- toolbar: isItemDragging ? void 0 : /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8451
- ItemActionToolbar,
8452
- {
8453
- onEditLink: openLinkPopoverForSelected,
8454
- onAddItem: handleAddNavigationItem,
8455
- addItemDisabled: false
8456
- }
8457
- )
8458
- }
8459
- ),
8460
- toolbarRect && toolbarVariant === "rich-text" && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
8461
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8462
- EditGlowChrome,
8463
- {
8464
- rect: toolbarRect,
8465
- elRef: glowElRef,
8466
- reorderHrefKey,
8467
- dragDisabled: reorderDragDisabled
8468
- }
8469
- ),
8470
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8471
- FloatingToolbar,
8472
- {
8473
- rect: toolbarRect,
8474
- parentScroll: parentScrollRef.current,
8475
- elRef: toolbarElRef,
8476
- onCommand: handleCommand,
8477
- activeCommands,
8478
- showEditLink,
8479
- onEditLink: openLinkPopoverForActive
8480
- }
8481
- )
10881
+ className: "pointer-events-none fixed z-2147483646",
10882
+ style: {
10883
+ left: slot.left,
10884
+ top: slot.top,
10885
+ width: slot.width,
10886
+ height: slot.height
10887
+ },
10888
+ children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(DropIndicator, { direction: slot.direction, state: activeFooterDropIndex === i ? "dragActive" : "dragIdle", className: slot.direction === "horizontal" ? "!h-full !w-full" : "!h-full !w-full" })
10889
+ },
10890
+ `footer-drop-${slot.direction}-${slot.columnIndex}-${slot.insertIndex}-${i}`
10891
+ )),
10892
+ hoveredNavContainerRect && toolbarVariant !== "select-frame" && !linkPopover && !isItemDragging && !siblingHintRect && siblingHintRects.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
10893
+ toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(NavbarContainerChrome, { rect: toolbarRect, onAdd: handleAddTopLevelNavItem }),
10894
+ hoveredItemRect && !linkPopover && !hoveredNavContainerRect && !siblingHintRect && siblingHintRects.length === 0 && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
10895
+ toolbarRect && (toolbarVariant === "link-action" || toolbarVariant === "select-frame") && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ItemInteractionLayer, { rect: isItemDragging && draggedItemRect && footerDragRef.current?.wasSelected ? draggedItemRect : toolbarRect, elRef: glowElRef, state: isItemDragging ? "dragging" : resolveItemInteractionState(toolbarRect, parentScrollRef.current), showHandle: toolbarVariant === "link-action" && Boolean(reorderHrefKey) || toolbarVariant === "select-frame" && isFooterFrameSelection, dragDisabled: reorderDragDisabled, dragHandleLabel: reorderHrefKey ? `Reorder ${reorderHrefKey}` : "Reorder item", onDragHandleDragStart: handleItemDragStart, onDragHandleDragEnd: handleItemDragEnd, onItemPointerDown: handleItemChromePointerDown, onItemClick: handleItemChromeClick, toolbar: toolbarVariant === "link-action" && !isItemDragging ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ItemActionToolbar, { onEditLink: openLinkPopoverForSelected, addItemDisabled: true, editLinkDisabled: false, moreDisabled: true }) : void 0 }),
10896
+ toolbarRect && toolbarVariant === "rich-text" && /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(import_jsx_runtime25.Fragment, { children: [
10897
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(EditGlowChrome, { rect: toolbarRect, elRef: glowElRef, reorderHrefKey, dragDisabled: reorderDragDisabled }),
10898
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(FloatingToolbar, { rect: toolbarRect, parentScroll: parentScrollRef.current, elRef: toolbarElRef, onCommand: handleCommand, activeCommands, showEditLink, onEditLink: openLinkPopoverForActive })
8482
10899
  ] }),
8483
- maxBadge && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
10900
+ maxBadge && /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
8484
10901
  "div",
8485
10902
  {
8486
10903
  "data-ohw-max-badge": "",
@@ -8506,56 +10923,29 @@ function OhhwellsBridge() {
8506
10923
  ]
8507
10924
  }
8508
10925
  ),
8509
- toggleState && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8510
- StateToggle,
8511
- {
8512
- rect: toggleState.rect,
8513
- activeState: toggleState.activeState,
8514
- states: toggleState.states,
8515
- onStateChange: handleStateChange
8516
- }
8517
- ),
8518
- sectionGap && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
8519
- "div",
8520
- {
8521
- "data-ohw-section-insert-line": "",
8522
- className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
8523
- style: { top: sectionGap.y, transform: "translateY(-50%)" },
8524
- children: [
8525
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
8526
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8527
- Badge,
8528
- {
8529
- 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",
8530
- onClick: () => {
8531
- window.parent.postMessage(
8532
- { type: "ow:add-section", insertAfter: sectionGap.insertAfter, insertBefore: sectionGap.insertBefore },
8533
- "*"
8534
- );
10926
+ toggleState && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(StateToggle, { rect: toggleState.rect, activeState: toggleState.activeState, states: toggleState.states, onStateChange: handleStateChange }),
10927
+ sectionGap && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { "data-ohw-section-insert-line": "", className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none", style: { top: sectionGap.y, transform: "translateY(-50%)" }, children: [
10928
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
10929
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
10930
+ Badge,
10931
+ {
10932
+ 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",
10933
+ onClick: () => {
10934
+ window.parent.postMessage(
10935
+ {
10936
+ type: "ow:add-section",
10937
+ insertAfter: sectionGap.insertAfter,
10938
+ insertBefore: sectionGap.insertBefore
8535
10939
  },
8536
- children: "Add Section"
8537
- }
8538
- ),
8539
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
8540
- ]
8541
- }
8542
- ),
8543
- linkPopover && dialogPortalContainer ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
8544
- LinkPopover,
8545
- {
8546
- panelRef: linkPopoverPanelRef,
8547
- portalContainer: dialogPortalContainer,
8548
- open: true,
8549
- mode: "edit",
8550
- pages: sitePages,
8551
- sections: currentSections,
8552
- sectionsByPath,
8553
- initialTarget: linkPopover.target,
8554
- onClose: closeLinkPopover,
8555
- onSubmit: handleLinkPopoverSubmit
8556
- },
8557
- `${linkPopover.key}-${pathname}`
8558
- ) : null
10940
+ "*"
10941
+ );
10942
+ },
10943
+ children: "Add Section"
10944
+ }
10945
+ ),
10946
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
10947
+ ] }),
10948
+ linkPopover && dialogPortalContainer ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(LinkPopover, { panelRef: linkPopoverPanelRef, portalContainer: dialogPortalContainer, open: true, mode: linkPopover.mode ?? "edit", pages: sitePages, sections: currentSections, sectionsByPath, initialTarget: linkPopover.target, existingTargets: linkPopover.intent === "add-nav" ? getNavbarExistingTargets() : [], onClose: closeLinkPopover, onSubmit: handleLinkPopoverSubmit }, linkPopover.key) : null
8559
10949
  ] }),
8560
10950
  bridgeRoot
8561
10951
  ) : null;
@@ -8566,6 +10956,7 @@ function OhhwellsBridge() {
8566
10956
  CustomToolbarButton,
8567
10957
  CustomToolbarDivider,
8568
10958
  DragHandle,
10959
+ DropIndicator,
8569
10960
  ItemActionToolbar,
8570
10961
  ItemInteractionLayer,
8571
10962
  LinkEditorPanel,
@@ -8580,12 +10971,14 @@ function OhhwellsBridge() {
8580
10971
  TooltipProvider,
8581
10972
  TooltipTrigger,
8582
10973
  buildTarget,
10974
+ dropIndicatorVariants,
8583
10975
  filterAvailablePages,
8584
10976
  getEditModeInitialState,
8585
10977
  isEditSessionActive,
8586
10978
  isValidUrl,
8587
10979
  parseTarget,
8588
10980
  toggleVariants,
10981
+ useOhwCarousel,
8589
10982
  validateUrlInput
8590
10983
  });
8591
10984
  //# sourceMappingURL=index.cjs.map