@ohhwells/bridge 0.1.37 → 0.1.38-next.56

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  // src/OhhwellsBridge.tsx
4
- import React8, { useCallback as useCallback2, useEffect as useEffect3, useLayoutEffect as useLayoutEffect2, useRef as useRef3, useState as useState5 } from "react";
4
+ import React9, { useCallback as useCallback4, useEffect as useEffect7, useLayoutEffect as useLayoutEffect2, useRef as useRef4, useState as useState6 } from "react";
5
5
  import { createRoot } from "react-dom/client";
6
6
  import { flushSync } from "react-dom";
7
7
 
@@ -10,6 +10,9 @@ var linkHrefStore = /* @__PURE__ */ new Map();
10
10
  function setStoredLinkHref(key, href) {
11
11
  linkHrefStore.set(key, href);
12
12
  }
13
+ function getStoredLinkHref(key) {
14
+ return linkHrefStore.get(key);
15
+ }
13
16
  function enforceLinkHrefs() {
14
17
  document.querySelectorAll("[data-ohw-href-key]").forEach((el) => {
15
18
  const key = el.getAttribute("data-ohw-href-key") ?? "";
@@ -45,12 +48,13 @@ function useLinkHrefGuardian(...deps) {
45
48
  }
46
49
 
47
50
  // src/ui/SchedulingWidget.tsx
48
- import { useEffect, useId, useMemo, useRef, useState as useState2 } from "react";
51
+ import { useCallback, useEffect, useId, useMemo, useRef, useState as useState2 } from "react";
49
52
 
50
53
  // src/ui/EmailCaptureModal.tsx
51
54
  import { useState } from "react";
52
55
  import { Dialog } from "radix-ui";
53
56
  import { jsx, jsxs } from "react/jsx-runtime";
57
+ var isValidEmail = (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
54
58
  function Spinner() {
55
59
  return /* @__PURE__ */ jsxs(
56
60
  "svg",
@@ -84,12 +88,17 @@ function EmailCaptureModal({ title, subtitle, onSubmit, onClose }) {
84
88
  const [error, setError] = useState(null);
85
89
  const isBook = title === "Confirm your spot";
86
90
  const handleSubmit = async () => {
87
- if (!email.trim()) return;
91
+ const trimmed = email.trim();
92
+ if (!trimmed) return;
93
+ if (!isValidEmail(trimmed)) {
94
+ setError("Please enter a valid email address.");
95
+ return;
96
+ }
88
97
  setLoading(true);
89
98
  setError(null);
90
99
  try {
91
- await onSubmit(email.trim());
92
- setSubmittedEmail(email.trim());
100
+ await onSubmit(trimmed);
101
+ setSubmittedEmail(trimmed);
93
102
  setSuccess(true);
94
103
  } catch (e) {
95
104
  setError(e instanceof Error ? e.message : "Something went wrong. Please try again.");
@@ -164,7 +173,10 @@ function EmailCaptureModal({ title, subtitle, onSubmit, onClose }) {
164
173
  {
165
174
  type: "email",
166
175
  value: email,
167
- onChange: (e) => setEmail(e.target.value),
176
+ onChange: (e) => {
177
+ setEmail(e.target.value);
178
+ if (error) setError(null);
179
+ },
168
180
  onKeyDown: handleKeyDown,
169
181
  placeholder: "hello@gmail.com",
170
182
  autoFocus: true,
@@ -221,12 +233,20 @@ function formatClassTime(cls) {
221
233
  const em = endTotal % 60;
222
234
  return `${h}:${cls.startTime.minutes}-${eh}:${String(em).padStart(2, "0")}`;
223
235
  }
236
+ function formatBookingLabel(cls) {
237
+ const price = cls.price;
238
+ const isFree = price === null || price === void 0 || `${price}` === "" || `${price}` === "0";
239
+ return isFree ? "Book for free" : `Book for ${cls.currency ?? ""} ${price}`.trim();
240
+ }
224
241
  function getBookingsOnDate(cls, date) {
225
242
  if (!cls.bookings?.length) return 0;
226
- const ds = date.toISOString().split("T")[0];
243
+ const target = new Date(date);
244
+ target.setHours(0, 0, 0, 0);
227
245
  return cls.bookings.filter((b) => {
228
246
  try {
229
- return new Date(b.classDate).toISOString().split("T")[0] === ds;
247
+ const bookingDate = new Date(b.classDate);
248
+ bookingDate.setHours(0, 0, 0, 0);
249
+ return bookingDate.getTime() === target.getTime();
230
250
  } catch {
231
251
  return false;
232
252
  }
@@ -530,7 +550,7 @@ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal
530
550
  if (!cls.id) return;
531
551
  onOpenModal?.(isFull ? "waitlist" : "book", cls.id, selectedDate);
532
552
  },
533
- children: isFull ? "Join Waitlist" : "Book Now"
553
+ children: isFull ? "Join Waitlist" : formatBookingLabel(cls)
534
554
  }
535
555
  )
536
556
  ] })
@@ -572,6 +592,17 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
572
592
  const [isHovered, setIsHovered] = useState2(false);
573
593
  const [modalState, setModalState] = useState2(null);
574
594
  const switchScheduleIdRef = useRef(null);
595
+ const liveScheduleIdRef = useRef(null);
596
+ const refetchLiveSchedule = useCallback(async () => {
597
+ const id = liveScheduleIdRef.current;
598
+ if (!id) return;
599
+ try {
600
+ const res = await fetch(`${API_URL}/api/schedule/id/${id}`);
601
+ const data = await res.json();
602
+ if (data?.id) setSchedule(data);
603
+ } catch {
604
+ }
605
+ }, []);
575
606
  const dates = useMemo(() => {
576
607
  const today = /* @__PURE__ */ new Date();
577
608
  today.setHours(0, 0, 0, 0);
@@ -591,6 +622,18 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
591
622
  }
592
623
  }
593
624
  }, [schedule, dates]);
625
+ useEffect(() => {
626
+ if (typeof window === "undefined") return;
627
+ const onVisible = () => {
628
+ if (!document.hidden) void refetchLiveSchedule();
629
+ };
630
+ window.addEventListener("focus", refetchLiveSchedule);
631
+ document.addEventListener("visibilitychange", onVisible);
632
+ return () => {
633
+ window.removeEventListener("focus", refetchLiveSchedule);
634
+ document.removeEventListener("visibilitychange", onVisible);
635
+ };
636
+ }, [refetchLiveSchedule]);
594
637
  useEffect(() => {
595
638
  if (typeof window === "undefined") return;
596
639
  const isInEditor = window.self !== window.top;
@@ -652,7 +695,7 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
652
695
  setLoading(false);
653
696
  return;
654
697
  }
655
- ;
698
+ liveScheduleIdRef.current = effectiveId;
656
699
  (async () => {
657
700
  try {
658
701
  const res = await fetch(`${API_URL}/api/schedule/id/${effectiveId}`);
@@ -709,18 +752,14 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
709
752
  const data = await res.json().catch(() => ({}));
710
753
  throw new Error(data?.message ?? "Something went wrong. Please try again.");
711
754
  }
755
+ await refetchLiveSchedule();
712
756
  };
713
757
  const handleReplaceSchedule = () => {
714
758
  setIsHovered(false);
715
- if (schedule) {
716
- window.parent.postMessage({
717
- type: "ow:schedule-connected",
718
- schedule: { id: schedule.id, name: schedule.name },
719
- insertAfter
720
- }, "*");
721
- } else {
722
- window.parent.postMessage({ type: "ow:scheduling-not-connected", insertAfter }, "*");
723
- }
759
+ window.parent.postMessage(
760
+ { type: "ow:scheduling-not-connected", insertAfter, currentScheduleId: schedule?.id },
761
+ "*"
762
+ );
724
763
  };
725
764
  if (!inEditor && !loading && !schedule) return null;
726
765
  const sectionId = `scheduling-${insertAfter}`;
@@ -763,7 +802,19 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
763
802
  ),
764
803
  /* @__PURE__ */ jsxs2("div", { className: "max-w-[1280px] mx-auto flex flex-col items-center gap-16", children: [
765
804
  /* @__PURE__ */ jsxs2("div", { className: "flex flex-col items-center gap-4", children: [
766
- /* @__PURE__ */ jsx2("h2", { className: "font-display text-4xl sm:text-5xl font-bold text-center m-0 text-(--color-dark,#200C02)", children: schedule?.name ?? "Book an appointment" }),
805
+ /* @__PURE__ */ jsx2(
806
+ "h2",
807
+ {
808
+ className: "font-display text-center m-0 text-(--color-dark,#200C02)",
809
+ style: {
810
+ fontSize: "var(--fs-section-h2, clamp(40px, 4.4vw, 64px))",
811
+ fontWeight: "var(--font-weight-heading, 400)",
812
+ lineHeight: 1.05,
813
+ letterSpacing: "-0.02em"
814
+ },
815
+ children: schedule?.name ?? "Book an appointment"
816
+ }
817
+ ),
767
818
  schedule?.description && /* @__PURE__ */ jsx2("p", { className: "font-body text-body text-center m-0 text-(--color-accent,#A89B83)", children: schedule.description })
768
819
  ] }),
769
820
  /* @__PURE__ */ jsxs2("div", { className: "w-full flex flex-col items-end gap-3", children: [
@@ -4349,6 +4400,7 @@ function ItemActionToolbar({
4349
4400
  onEditLink,
4350
4401
  onAddItem,
4351
4402
  onMore,
4403
+ editLinkDisabled = false,
4352
4404
  addItemDisabled = true,
4353
4405
  moreDisabled = true,
4354
4406
  tooltipSide = "bottom"
@@ -4357,10 +4409,12 @@ function ItemActionToolbar({
4357
4409
  /* @__PURE__ */ jsx8(
4358
4410
  ToolbarActionTooltip,
4359
4411
  {
4360
- label: "Add link",
4412
+ label: "Edit link",
4361
4413
  side: tooltipSide,
4414
+ disabled: editLinkDisabled,
4362
4415
  buttonProps: {
4363
4416
  onMouseDown: (e) => {
4417
+ if (editLinkDisabled) return;
4364
4418
  e.preventDefault();
4365
4419
  e.stopPropagation();
4366
4420
  onEditLink?.();
@@ -4532,9 +4586,223 @@ function ItemInteractionLayer({
4532
4586
  );
4533
4587
  }
4534
4588
 
4589
+ // src/ui/MediaOverlay.tsx
4590
+ import * as React6 from "react";
4591
+ import { Film, ImageIcon, Pause, Play, Volume2, VolumeX } from "lucide-react";
4592
+
4593
+ // src/ui/button.tsx
4594
+ import * as React5 from "react";
4595
+ import { Slot } from "radix-ui";
4596
+ import { jsx as jsx10 } from "react/jsx-runtime";
4597
+ var buttonVariants = cva(
4598
+ "inline-flex items-center justify-center gap-1 whitespace-nowrap rounded-md text-sm font-medium transition-colors outline-none disabled:pointer-events-none disabled:opacity-50 min-w-[80px] px-3 py-2",
4599
+ {
4600
+ variants: {
4601
+ variant: {
4602
+ default: "bg-primary text-primary-foreground hover:opacity-90",
4603
+ outline: "border border-border bg-background text-foreground shadow-sm hover:bg-muted/80",
4604
+ ghost: "min-w-0 px-3 py-2 text-foreground hover:bg-muted/50"
4605
+ },
4606
+ size: {
4607
+ default: "h-9",
4608
+ sm: "h-8 min-w-[64px] px-2 py-1.5 text-sm"
4609
+ }
4610
+ },
4611
+ defaultVariants: {
4612
+ variant: "default",
4613
+ size: "default"
4614
+ }
4615
+ }
4616
+ );
4617
+ var Button = React5.forwardRef(
4618
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
4619
+ const Comp = asChild ? Slot.Root : "button";
4620
+ return /* @__PURE__ */ jsx10(
4621
+ Comp,
4622
+ {
4623
+ ref,
4624
+ "data-slot": "button",
4625
+ className: cn(buttonVariants({ variant, size, className })),
4626
+ ...props
4627
+ }
4628
+ );
4629
+ }
4630
+ );
4631
+ Button.displayName = "Button";
4632
+
4633
+ // src/ui/MediaOverlay.tsx
4634
+ import { Fragment as Fragment2, jsx as jsx11, jsxs as jsxs5 } from "react/jsx-runtime";
4635
+ var MEDIA_UPLOAD_FADE_MS = 300;
4636
+ var VIDEO_SETTINGS_BAR_INSET = 8;
4637
+ var OVERLAY_BUTTON_STYLE = {
4638
+ pointerEvents: "auto",
4639
+ fontFamily: "Inter, sans-serif",
4640
+ fontSize: 12,
4641
+ color: "#000"
4642
+ };
4643
+ var SKELETON_CSS = `
4644
+ @keyframes ohw-media-shimmer {
4645
+ 0% { transform: translateX(-100%); }
4646
+ 100% { transform: translateX(100%); }
4647
+ }
4648
+ [data-ohw-media-skeleton] {
4649
+ overflow: hidden;
4650
+ background-color: #d1d5db; /* tailwind gray-300 */
4651
+ }
4652
+ [data-ohw-media-skeleton]::after {
4653
+ content: "";
4654
+ position: absolute;
4655
+ inset: 0;
4656
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.6), transparent);
4657
+ animation: ohw-media-shimmer 1.4s infinite;
4658
+ }
4659
+ `;
4660
+ function MediaOverlay({
4661
+ hover,
4662
+ isUploading,
4663
+ fadingOut = false,
4664
+ onFadeOutComplete,
4665
+ onReplace,
4666
+ onVideoSettingsChange
4667
+ }) {
4668
+ const { rect } = hover;
4669
+ const skeletonRef = React6.useRef(null);
4670
+ const isVideo = hover.elementType === "video";
4671
+ const autoplay = hover.videoAutoplay ?? true;
4672
+ const muted = hover.videoMuted ?? true;
4673
+ const box = {
4674
+ position: "fixed",
4675
+ top: rect.top,
4676
+ left: rect.left,
4677
+ width: rect.width,
4678
+ height: rect.height,
4679
+ zIndex: 2147483646
4680
+ };
4681
+ React6.useEffect(() => {
4682
+ if (!isUploading || !fadingOut || !skeletonRef.current) return;
4683
+ const anim = skeletonRef.current.animate([{ opacity: 1 }, { opacity: 0 }], {
4684
+ duration: MEDIA_UPLOAD_FADE_MS,
4685
+ easing: "ease-out",
4686
+ fill: "forwards"
4687
+ });
4688
+ anim.finished.then(() => onFadeOutComplete?.(hover.key)).catch(() => onFadeOutComplete?.(hover.key));
4689
+ return () => anim.cancel();
4690
+ }, [isUploading, fadingOut, onFadeOutComplete, hover.key]);
4691
+ if (isUploading) {
4692
+ return /* @__PURE__ */ jsx11(
4693
+ "div",
4694
+ {
4695
+ ref: skeletonRef,
4696
+ "data-ohw-bridge": "",
4697
+ "data-ohw-media-overlay": "",
4698
+ "data-ohw-media-skeleton": "",
4699
+ "aria-hidden": true,
4700
+ style: { ...box, pointerEvents: "none" },
4701
+ children: /* @__PURE__ */ jsx11("style", { children: SKELETON_CSS })
4702
+ }
4703
+ );
4704
+ }
4705
+ const settingsBar = isVideo && !hover.isDragOver ? /* @__PURE__ */ jsxs5(
4706
+ "div",
4707
+ {
4708
+ "data-ohw-bridge": "",
4709
+ "data-ohw-media-overlay": "",
4710
+ className: "flex items-center justify-center gap-1.5",
4711
+ style: {
4712
+ position: "fixed",
4713
+ top: rect.top + VIDEO_SETTINGS_BAR_INSET,
4714
+ left: rect.left,
4715
+ width: rect.width,
4716
+ zIndex: 2147483647,
4717
+ pointerEvents: "auto"
4718
+ },
4719
+ onClick: (e) => e.stopPropagation(),
4720
+ children: [
4721
+ /* @__PURE__ */ jsx11(
4722
+ Button,
4723
+ {
4724
+ "data-ohw-media-overlay": "",
4725
+ variant: "outline",
4726
+ size: "sm",
4727
+ className: "cursor-pointer hover:bg-background",
4728
+ style: { ...OVERLAY_BUTTON_STYLE, minWidth: 0, padding: "0 8px" },
4729
+ "aria-label": autoplay ? "Disable autoplay" : "Enable autoplay",
4730
+ title: autoplay ? "Autoplay on \u2014 click to disable" : "Autoplay off \u2014 click to enable",
4731
+ onMouseDown: (e) => e.preventDefault(),
4732
+ onClick: (e) => {
4733
+ e.stopPropagation();
4734
+ onVideoSettingsChange?.(hover.key, { autoplay: !autoplay });
4735
+ },
4736
+ children: autoplay ? /* @__PURE__ */ jsx11(Pause, { size: 14 }) : /* @__PURE__ */ jsx11(Play, { size: 14 })
4737
+ }
4738
+ ),
4739
+ /* @__PURE__ */ jsx11(
4740
+ Button,
4741
+ {
4742
+ "data-ohw-media-overlay": "",
4743
+ variant: "outline",
4744
+ size: "sm",
4745
+ className: "cursor-pointer hover:bg-background",
4746
+ style: { ...OVERLAY_BUTTON_STYLE, minWidth: 0, padding: "0 8px" },
4747
+ "aria-label": muted ? "Unmute video" : "Mute video",
4748
+ title: muted ? "Muted \u2014 click to unmute" : "Sound on \u2014 click to mute",
4749
+ onMouseDown: (e) => e.preventDefault(),
4750
+ onClick: (e) => {
4751
+ e.stopPropagation();
4752
+ onVideoSettingsChange?.(hover.key, { muted: !muted });
4753
+ },
4754
+ children: muted ? /* @__PURE__ */ jsx11(VolumeX, { size: 14 }) : /* @__PURE__ */ jsx11(Volume2, { size: 14 })
4755
+ }
4756
+ )
4757
+ ]
4758
+ }
4759
+ ) : null;
4760
+ return /* @__PURE__ */ jsxs5(Fragment2, { children: [
4761
+ settingsBar,
4762
+ /* @__PURE__ */ jsx11(
4763
+ "div",
4764
+ {
4765
+ "data-ohw-bridge": "",
4766
+ "data-ohw-media-overlay": "",
4767
+ className: "flex items-center justify-center cursor-pointer",
4768
+ style: {
4769
+ ...box,
4770
+ // When text overlays this media, let clicks fall THROUGH to the text underneath. In the
4771
+ // parent this needed a whole `ow:click-at` round-trip to re-hit-test inside the iframe;
4772
+ // in-document, pointer-events does it natively. The button below opts back in, so
4773
+ // Replace still works.
4774
+ pointerEvents: hover.hasTextOverlap ? "none" : "auto",
4775
+ boxShadow: "inset 0 0 0 1.5px var(--color-primary)",
4776
+ background: "color-mix(in srgb, var(--color-primary) 20%, transparent)"
4777
+ },
4778
+ onClick: () => onReplace(hover.key),
4779
+ children: /* @__PURE__ */ jsxs5(
4780
+ Button,
4781
+ {
4782
+ "data-ohw-media-overlay": "",
4783
+ variant: "outline",
4784
+ size: "sm",
4785
+ className: "gap-1.5 cursor-pointer hover:bg-background",
4786
+ style: OVERLAY_BUTTON_STYLE,
4787
+ onMouseDown: (e) => e.preventDefault(),
4788
+ onClick: (e) => {
4789
+ e.stopPropagation();
4790
+ onReplace(hover.key);
4791
+ },
4792
+ children: [
4793
+ isVideo ? /* @__PURE__ */ jsx11(Film, { size: 14 }) : /* @__PURE__ */ jsx11(ImageIcon, { size: 14 }),
4794
+ isVideo ? "Replace video" : "Replace image"
4795
+ ]
4796
+ }
4797
+ )
4798
+ }
4799
+ )
4800
+ ] });
4801
+ }
4802
+
4535
4803
  // src/OhhwellsBridge.tsx
4536
- import { createPortal } from "react-dom";
4537
- import { usePathname, useRouter, useSearchParams } from "next/navigation";
4804
+ import { createPortal as createPortal2 } from "react-dom";
4805
+ import { usePathname as usePathname2, useRouter as useRouter2, useSearchParams } from "next/navigation";
4538
4806
 
4539
4807
  // src/lib/session-search.ts
4540
4808
  var STORAGE_KEY = "ohw-preserved-search";
@@ -4810,61 +5078,29 @@ function scrollToHashSectionWhenReady(behavior = "smooth") {
4810
5078
  tick();
4811
5079
  }
4812
5080
 
4813
- // src/ui/dialog.tsx
4814
- import * as React5 from "react";
4815
- import { Dialog as DialogPrimitive } from "radix-ui";
4816
-
4817
- // src/ui/icons.tsx
4818
- import { jsx as jsx10, jsxs as jsxs5 } from "react/jsx-runtime";
4819
- function IconX({ className, ...props }) {
4820
- return /* @__PURE__ */ jsxs5("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
4821
- /* @__PURE__ */ jsx10("path", { d: "M18 6 6 18" }),
4822
- /* @__PURE__ */ jsx10("path", { d: "m6 6 12 12" })
4823
- ] });
4824
- }
4825
- function IconChevronDown({ className, ...props }) {
4826
- return /* @__PURE__ */ jsx10("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: /* @__PURE__ */ jsx10("path", { d: "m6 9 6 6 6-6" }) });
4827
- }
4828
- function IconFile({ className, ...props }) {
4829
- return /* @__PURE__ */ jsxs5("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
4830
- /* @__PURE__ */ jsx10("path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }),
4831
- /* @__PURE__ */ jsx10("path", { d: "M14 2v4a2 2 0 0 0 2 2h4" })
4832
- ] });
4833
- }
4834
- function IconInfo({ className, ...props }) {
4835
- return /* @__PURE__ */ jsxs5("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
4836
- /* @__PURE__ */ jsx10("circle", { cx: "12", cy: "12", r: "10" }),
4837
- /* @__PURE__ */ jsx10("path", { d: "M12 16v-4" }),
4838
- /* @__PURE__ */ jsx10("path", { d: "M12 8h.01" })
4839
- ] });
4840
- }
4841
- function IconArrowRight({ className, ...props }) {
4842
- return /* @__PURE__ */ jsxs5("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
4843
- /* @__PURE__ */ jsx10("path", { d: "M5 12h14" }),
4844
- /* @__PURE__ */ jsx10("path", { d: "m12 5 7 7-7 7" })
4845
- ] });
4846
- }
4847
- function IconSection({ className, ...props }) {
4848
- return /* @__PURE__ */ jsxs5("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
4849
- /* @__PURE__ */ jsx10("rect", { width: "18", height: "7", x: "3", y: "3", rx: "1" }),
4850
- /* @__PURE__ */ jsx10("rect", { width: "9", height: "7", x: "3", y: "14", rx: "1" }),
4851
- /* @__PURE__ */ jsx10("rect", { width: "5", height: "7", x: "16", y: "14", rx: "1" })
4852
- ] });
4853
- }
5081
+ // src/ui/link-modal/LinkPopover.tsx
5082
+ import { useEffect as useEffect6 } from "react";
4854
5083
 
4855
5084
  // src/ui/dialog.tsx
4856
- import { jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
4857
- function Dialog2({ ...props }) {
4858
- return /* @__PURE__ */ jsx11(DialogPrimitive.Root, { "data-slot": "dialog", ...props });
5085
+ import * as React7 from "react";
5086
+ import { Dialog as DialogPrimitive } from "radix-ui";
5087
+ import { X } from "lucide-react";
5088
+ import { jsx as jsx12, jsxs as jsxs6 } from "react/jsx-runtime";
5089
+ function Dialog2({
5090
+ ...props
5091
+ }) {
5092
+ return /* @__PURE__ */ jsx12(DialogPrimitive.Root, { "data-slot": "dialog", ...props });
4859
5093
  }
4860
- function DialogPortal({ ...props }) {
4861
- return /* @__PURE__ */ jsx11(DialogPrimitive.Portal, { ...props });
5094
+ function DialogPortal({
5095
+ ...props
5096
+ }) {
5097
+ return /* @__PURE__ */ jsx12(DialogPrimitive.Portal, { ...props });
4862
5098
  }
4863
5099
  function DialogOverlay({
4864
5100
  className,
4865
5101
  ...props
4866
5102
  }) {
4867
- return /* @__PURE__ */ jsx11(
5103
+ return /* @__PURE__ */ jsx12(
4868
5104
  DialogPrimitive.Overlay,
4869
5105
  {
4870
5106
  "data-slot": "dialog-overlay",
@@ -4874,57 +5110,74 @@ function DialogOverlay({
4874
5110
  }
4875
5111
  );
4876
5112
  }
4877
- var DialogContent = React5.forwardRef(({ className, children, showCloseButton = true, container, ...props }, ref) => {
4878
- const positionMode = container ? "absolute" : "fixed";
4879
- return /* @__PURE__ */ jsxs6(DialogPortal, { container: container ?? void 0, children: [
4880
- /* @__PURE__ */ jsx11(DialogOverlay, { className: cn(positionMode, "inset-0") }),
4881
- /* @__PURE__ */ jsxs6(
4882
- DialogPrimitive.Content,
4883
- {
4884
- ref,
4885
- "data-slot": "dialog-content",
4886
- className: cn(
4887
- positionMode,
4888
- "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",
4889
- "rounded-xl border border-border bg-background font-sans shadow-lg outline-none",
4890
- container ? "max-h-[calc(100%-32px)] max-w-[calc(100%-16px)]" : "max-h-[calc(100vh-32px)] max-w-[calc(100vw-16px)]",
4891
- className
4892
- ),
4893
- onMouseDown: (e) => e.stopPropagation(),
4894
- ...props,
4895
- children: [
4896
- children,
4897
- showCloseButton ? /* @__PURE__ */ jsx11(
4898
- DialogPrimitive.Close,
4899
- {
4900
- type: "button",
4901
- className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
4902
- "aria-label": "Close",
4903
- children: /* @__PURE__ */ jsx11(IconX, { "aria-hidden": true })
4904
- }
4905
- ) : null
4906
- ]
4907
- }
4908
- )
4909
- ] });
4910
- });
5113
+ var DialogContent = React7.forwardRef(
5114
+ ({ className, children, showCloseButton = true, container, ...props }, ref) => {
5115
+ const positionMode = container ? "absolute" : "fixed";
5116
+ return /* @__PURE__ */ jsxs6(DialogPortal, { container: container ?? void 0, children: [
5117
+ /* @__PURE__ */ jsx12(DialogOverlay, { className: cn(positionMode, "inset-0") }),
5118
+ /* @__PURE__ */ jsxs6(
5119
+ DialogPrimitive.Content,
5120
+ {
5121
+ ref,
5122
+ "data-slot": "dialog-content",
5123
+ className: cn(
5124
+ positionMode,
5125
+ "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-visible",
5126
+ "rounded-xl border border-border bg-background font-sans shadow-lg outline-none",
5127
+ container ? "max-h-[calc(100%-32px)] max-w-[calc(100%-16px)]" : "max-h-[calc(100vh-32px)] max-w-[calc(100vw-16px)]",
5128
+ className
5129
+ ),
5130
+ onMouseDown: (e) => e.stopPropagation(),
5131
+ ...props,
5132
+ children: [
5133
+ children,
5134
+ showCloseButton ? /* @__PURE__ */ jsx12(
5135
+ DialogPrimitive.Close,
5136
+ {
5137
+ type: "button",
5138
+ className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
5139
+ "aria-label": "Close",
5140
+ children: /* @__PURE__ */ jsx12(X, { size: 16, "aria-hidden": true })
5141
+ }
5142
+ ) : null
5143
+ ]
5144
+ }
5145
+ )
5146
+ ] });
5147
+ }
5148
+ );
4911
5149
  DialogContent.displayName = DialogPrimitive.Content.displayName;
4912
- function DialogHeader({ className, ...props }) {
4913
- return /* @__PURE__ */ jsx11("div", { className: cn("flex flex-col gap-1.5", className), ...props });
5150
+ function DialogHeader({
5151
+ className,
5152
+ ...props
5153
+ }) {
5154
+ return /* @__PURE__ */ jsx12("div", { className: cn("flex flex-col gap-1.5", className), ...props });
4914
5155
  }
4915
- function DialogFooter({ className, ...props }) {
4916
- return /* @__PURE__ */ jsx11("div", { className: cn("flex items-center justify-end gap-2", className), ...props });
5156
+ function DialogFooter({
5157
+ className,
5158
+ ...props
5159
+ }) {
5160
+ return /* @__PURE__ */ jsx12(
5161
+ "div",
5162
+ {
5163
+ className: cn("flex items-center justify-end gap-2", className),
5164
+ ...props
5165
+ }
5166
+ );
4917
5167
  }
4918
- var DialogTitle = React5.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx11(
5168
+ var DialogTitle = React7.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx12(
4919
5169
  DialogPrimitive.Title,
4920
5170
  {
4921
5171
  ref,
4922
- className: cn("text-lg font-semibold leading-none tracking-tight text-card-foreground", className),
5172
+ className: cn(
5173
+ "text-lg font-semibold leading-none tracking-tight text-card-foreground",
5174
+ className
5175
+ ),
4923
5176
  ...props
4924
5177
  }
4925
5178
  ));
4926
5179
  DialogTitle.displayName = DialogPrimitive.Title.displayName;
4927
- var DialogDescription = React5.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx11(
5180
+ var DialogDescription = React7.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx12(
4928
5181
  DialogPrimitive.Description,
4929
5182
  {
4930
5183
  ref,
@@ -4935,59 +5188,40 @@ var DialogDescription = React5.forwardRef(({ className, ...props }, ref) => /* @
4935
5188
  DialogDescription.displayName = DialogPrimitive.Description.displayName;
4936
5189
  var DialogClose = DialogPrimitive.Close;
4937
5190
 
4938
- // src/ui/button.tsx
4939
- import * as React6 from "react";
4940
- import { Slot } from "radix-ui";
4941
- import { jsx as jsx12 } from "react/jsx-runtime";
4942
- var buttonVariants = cva(
4943
- "inline-flex items-center justify-center gap-1 whitespace-nowrap rounded-md text-sm font-medium transition-colors outline-none disabled:pointer-events-none disabled:opacity-50 min-w-[80px] px-3 py-2",
4944
- {
4945
- variants: {
4946
- variant: {
4947
- default: "bg-primary text-primary-foreground hover:opacity-90",
4948
- outline: "border border-border bg-background text-foreground shadow-sm hover:bg-muted/80",
4949
- ghost: "min-w-0 px-3 py-2 text-foreground hover:bg-muted/50"
4950
- },
4951
- size: {
4952
- default: "h-9",
4953
- sm: "h-8 min-w-[64px] px-2 py-1.5 text-sm"
4954
- }
4955
- },
4956
- defaultVariants: {
4957
- variant: "default",
4958
- size: "default"
4959
- }
4960
- }
4961
- );
4962
- var Button = React6.forwardRef(
4963
- ({ className, variant, size, asChild = false, ...props }, ref) => {
4964
- const Comp = asChild ? Slot.Root : "button";
4965
- return /* @__PURE__ */ jsx12(
4966
- Comp,
4967
- {
4968
- ref,
4969
- "data-slot": "button",
4970
- className: cn(buttonVariants({ variant, size, className })),
4971
- ...props
4972
- }
4973
- );
4974
- }
4975
- );
4976
- Button.displayName = "Button";
5191
+ // src/ui/link-modal/LinkEditorPanel.tsx
5192
+ import { Info, X as X3 } from "lucide-react";
4977
5193
 
4978
5194
  // src/ui/link-modal/DestinationBreadcrumb.tsx
5195
+ import { ArrowRight, File, GalleryVertical } from "lucide-react";
4979
5196
  import { jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
4980
- function DestinationBreadcrumb({ pageTitle, sectionLabel }) {
5197
+ function DestinationBreadcrumb({
5198
+ pageTitle,
5199
+ sectionLabel
5200
+ }) {
4981
5201
  return /* @__PURE__ */ jsxs7("div", { className: "flex w-full flex-col gap-2", children: [
4982
- /* @__PURE__ */ jsx13("p", { className: "text-sm font-medium text-foreground", children: "Destination" }),
5202
+ /* @__PURE__ */ jsx13("p", { className: "text-sm font-medium! text-foreground m-0", children: "Destination" }),
4983
5203
  /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-3", children: [
4984
5204
  /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-2", children: [
4985
- /* @__PURE__ */ jsx13(IconFile, { className: "shrink-0 text-foreground", "aria-hidden": true }),
4986
- /* @__PURE__ */ jsx13("span", { className: "text-sm font-medium leading-none text-foreground", children: pageTitle })
5205
+ /* @__PURE__ */ jsx13(File, { size: 16, className: "shrink-0 text-foreground", "aria-hidden": true }),
5206
+ /* @__PURE__ */ jsx13("span", { className: "text-sm font-medium leading-none text-foreground!", children: pageTitle })
4987
5207
  ] }),
4988
- /* @__PURE__ */ jsx13(IconArrowRight, { className: "shrink-0 text-muted-foreground", "aria-hidden": true }),
5208
+ /* @__PURE__ */ jsx13(
5209
+ ArrowRight,
5210
+ {
5211
+ size: 16,
5212
+ className: "shrink-0 text-muted-foreground",
5213
+ "aria-hidden": true
5214
+ }
5215
+ ),
4989
5216
  /* @__PURE__ */ jsxs7("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [
4990
- /* @__PURE__ */ jsx13(IconSection, { className: "shrink-0 text-foreground", "aria-hidden": true }),
5217
+ /* @__PURE__ */ jsx13(
5218
+ GalleryVertical,
5219
+ {
5220
+ size: 16,
5221
+ className: "shrink-0 text-foreground",
5222
+ "aria-hidden": true
5223
+ }
5224
+ ),
4991
5225
  /* @__PURE__ */ jsx13("span", { className: "truncate text-sm font-medium leading-none text-foreground", children: sectionLabel })
4992
5226
  ] })
4993
5227
  ] })
@@ -4995,8 +5229,13 @@ function DestinationBreadcrumb({ pageTitle, sectionLabel }) {
4995
5229
  }
4996
5230
 
4997
5231
  // src/ui/link-modal/SectionTreeItem.tsx
5232
+ import { GalleryVertical as GalleryVertical2 } from "lucide-react";
4998
5233
  import { jsx as jsx14, jsxs as jsxs8 } from "react/jsx-runtime";
4999
- function SectionTreeItem({ section, onSelect, selected }) {
5234
+ function SectionTreeItem({
5235
+ section,
5236
+ onSelect,
5237
+ selected
5238
+ }) {
5000
5239
  const interactive = Boolean(onSelect);
5001
5240
  return /* @__PURE__ */ jsxs8("div", { className: "flex h-9 w-full items-end pl-3", children: [
5002
5241
  /* @__PURE__ */ jsx14(
@@ -5024,30 +5263,28 @@ function SectionTreeItem({ section, onSelect, selected }) {
5024
5263
  interactive && selected && "border-primary"
5025
5264
  ),
5026
5265
  children: [
5027
- /* @__PURE__ */ jsx14(IconSection, { className: "shrink-0 text-foreground", "aria-hidden": true }),
5266
+ /* @__PURE__ */ jsx14(
5267
+ GalleryVertical2,
5268
+ {
5269
+ size: 16,
5270
+ className: "shrink-0 text-foreground",
5271
+ "aria-hidden": true
5272
+ }
5273
+ ),
5028
5274
  /* @__PURE__ */ jsx14("span", { className: "truncate text-sm font-normal leading-5 text-foreground", children: section.label })
5029
5275
  ]
5030
5276
  }
5031
5277
  )
5032
5278
  ] });
5033
5279
  }
5034
- function SectionPickerList({ sections, onSelect }) {
5035
- if (sections.length === 0) {
5036
- return /* @__PURE__ */ jsx14("p", { className: "text-sm text-muted-foreground", children: "No sections found on this page." });
5037
- }
5038
- return /* @__PURE__ */ jsxs8("div", { className: "flex flex-col gap-1", children: [
5039
- /* @__PURE__ */ jsx14("p", { className: "text-sm text-muted-foreground", children: "Pick a section this link should scroll to." }),
5040
- sections.map((section) => /* @__PURE__ */ jsx14(SectionTreeItem, { section, onSelect }, section.id))
5041
- ] });
5042
- }
5043
5280
 
5044
5281
  // src/ui/link-modal/UrlOrPageInput.tsx
5045
- import { useId as useId2, useRef as useRef2, useState as useState3 } from "react";
5282
+ import { useEffect as useEffect3, useId as useId2, useRef as useRef3, useState as useState3 } from "react";
5046
5283
 
5047
5284
  // src/ui/input.tsx
5048
- import * as React7 from "react";
5285
+ import * as React8 from "react";
5049
5286
  import { jsx as jsx15 } from "react/jsx-runtime";
5050
- var Input = React7.forwardRef(
5287
+ var Input = React8.forwardRef(
5051
5288
  ({ className, type, ...props }, ref) => {
5052
5289
  return /* @__PURE__ */ jsx15(
5053
5290
  "input",
@@ -5081,8 +5318,11 @@ function Label({ className, ...props }) {
5081
5318
  }
5082
5319
 
5083
5320
  // src/ui/link-modal/UrlOrPageInput.tsx
5321
+ import { ChevronDown, File as File2, X as X2 } from "lucide-react";
5084
5322
  import { jsx as jsx17, jsxs as jsxs9 } from "react/jsx-runtime";
5085
- function FieldChevron({ onClick }) {
5323
+ function FieldChevron({
5324
+ onClick
5325
+ }) {
5086
5326
  return /* @__PURE__ */ jsx17(
5087
5327
  "button",
5088
5328
  {
@@ -5091,7 +5331,7 @@ function FieldChevron({ onClick }) {
5091
5331
  onClick,
5092
5332
  "aria-label": "Open page list",
5093
5333
  tabIndex: -1,
5094
- children: /* @__PURE__ */ jsx17(IconChevronDown, {})
5334
+ children: /* @__PURE__ */ jsx17(ChevronDown, { size: 16 })
5095
5335
  }
5096
5336
  );
5097
5337
  }
@@ -5108,8 +5348,30 @@ function UrlOrPageInput({
5108
5348
  urlError
5109
5349
  }) {
5110
5350
  const inputId = useId2();
5111
- const inputRef = useRef2(null);
5351
+ const inputRef = useRef3(null);
5352
+ const rootRef = useRef3(null);
5112
5353
  const [isFocused, setIsFocused] = useState3(false);
5354
+ useEffect3(() => {
5355
+ if (!dropdownOpen) return;
5356
+ const isOutside = (e) => {
5357
+ const root = rootRef.current;
5358
+ if (!root) return true;
5359
+ const path = typeof e.composedPath === "function" ? e.composedPath() : [];
5360
+ if (path.includes(root)) return false;
5361
+ const target = e.target;
5362
+ return !(target instanceof Node && root.contains(target));
5363
+ };
5364
+ const closeIfOutside = (e) => {
5365
+ if (!isOutside(e)) return;
5366
+ onDropdownOpenChange(false);
5367
+ };
5368
+ document.addEventListener("pointerdown", closeIfOutside, true);
5369
+ document.addEventListener("mousedown", closeIfOutside, true);
5370
+ return () => {
5371
+ document.removeEventListener("pointerdown", closeIfOutside, true);
5372
+ document.removeEventListener("mousedown", closeIfOutside, true);
5373
+ };
5374
+ }, [dropdownOpen, onDropdownOpenChange]);
5113
5375
  const openDropdown = () => {
5114
5376
  if (readOnly || filteredPages.length === 0) return;
5115
5377
  onDropdownOpenChange(true);
@@ -5132,9 +5394,16 @@ function UrlOrPageInput({
5132
5394
  );
5133
5395
  return /* @__PURE__ */ jsxs9("div", { className: "flex w-full flex-col gap-2 p-0", children: [
5134
5396
  /* @__PURE__ */ jsx17(Label, { htmlFor: inputId, className: cn(urlError && "text-destructive"), children: "Destination" }),
5135
- /* @__PURE__ */ jsxs9("div", { className: "relative w-full", children: [
5397
+ /* @__PURE__ */ jsxs9("div", { ref: rootRef, className: "relative w-full", children: [
5136
5398
  /* @__PURE__ */ jsxs9("div", { "data-ohw-link-field": true, className: fieldClassName, children: [
5137
- selectedPage ? /* @__PURE__ */ jsx17("div", { className: "flex shrink-0 items-center pr-2", children: /* @__PURE__ */ jsx17(IconFile, { className: "text-foreground", "aria-hidden": true }) }) : null,
5399
+ selectedPage ? /* @__PURE__ */ jsx17("div", { className: "flex shrink-0 items-center pr-2", children: /* @__PURE__ */ jsx17(
5400
+ File2,
5401
+ {
5402
+ size: 16,
5403
+ className: "shrink-0 text-foreground",
5404
+ "aria-hidden": true
5405
+ }
5406
+ ) }) : null,
5138
5407
  readOnly ? /* @__PURE__ */ jsx17("span", { className: "min-w-0 flex-1 truncate text-sm leading-5 text-foreground", children: selectedPage?.title ?? value }) : /* @__PURE__ */ jsx17(
5139
5408
  Input,
5140
5409
  {
@@ -5146,7 +5415,14 @@ function UrlOrPageInput({
5146
5415
  setIsFocused(true);
5147
5416
  if (!selectedPage) openDropdown();
5148
5417
  },
5149
- onBlur: () => setIsFocused(false),
5418
+ onBlur: () => {
5419
+ setIsFocused(false);
5420
+ requestAnimationFrame(() => {
5421
+ if (!rootRef.current?.contains(document.activeElement)) {
5422
+ onDropdownOpenChange(false);
5423
+ }
5424
+ });
5425
+ },
5150
5426
  placeholder,
5151
5427
  className: cn(
5152
5428
  "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",
@@ -5162,7 +5438,7 @@ function UrlOrPageInput({
5162
5438
  onMouseDown: clearSelection,
5163
5439
  "aria-label": "Clear selected page",
5164
5440
  tabIndex: -1,
5165
- children: /* @__PURE__ */ jsx17(IconX, { "aria-hidden": true })
5441
+ children: /* @__PURE__ */ jsx17(X2, { size: 16, "aria-hidden": true })
5166
5442
  }
5167
5443
  ) : null,
5168
5444
  !readOnly ? /* @__PURE__ */ jsx17(FieldChevron, { onClick: toggleDropdown }) : null
@@ -5171,7 +5447,7 @@ function UrlOrPageInput({
5171
5447
  "div",
5172
5448
  {
5173
5449
  "data-ohw-link-page-dropdown": "",
5174
- 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",
5450
+ 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",
5175
5451
  onMouseDown: (e) => e.preventDefault(),
5176
5452
  children: filteredPages.map((page) => /* @__PURE__ */ jsxs9(
5177
5453
  "button",
@@ -5180,7 +5456,7 @@ function UrlOrPageInput({
5180
5456
  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",
5181
5457
  onClick: () => onPageSelect(page),
5182
5458
  children: [
5183
- /* @__PURE__ */ jsx17(IconFile, { className: "shrink-0", "aria-hidden": true }),
5459
+ /* @__PURE__ */ jsx17(File2, { size: 16, className: "shrink-0", "aria-hidden": true }),
5184
5460
  /* @__PURE__ */ jsx17("span", { className: "truncate", children: page.title })
5185
5461
  ]
5186
5462
  },
@@ -5193,41 +5469,378 @@ function UrlOrPageInput({
5193
5469
  ] });
5194
5470
  }
5195
5471
 
5196
- // src/ui/link-modal/useLinkModalState.ts
5197
- import { useCallback, useEffect as useEffect2, useMemo as useMemo2, useState as useState4 } from "react";
5198
- function useLinkModalState({
5199
- open,
5200
- mode,
5201
- pages,
5202
- sections: _sections,
5203
- sectionsByPath,
5204
- initialTarget,
5205
- existingTargets,
5206
- onClose,
5207
- onSubmit
5208
- }) {
5209
- const availablePages = useMemo2(
5210
- () => mode === "create" ? filterAvailablePages(pages, existingTargets) : pages,
5211
- [mode, pages, existingTargets]
5212
- );
5213
- const [searchValue, setSearchValue] = useState4("");
5214
- const [selectedPage, setSelectedPage] = useState4(null);
5215
- const [selectedSection, setSelectedSection] = useState4(null);
5216
- const [step, setStep] = useState4("input");
5217
- const [dropdownOpen, setDropdownOpen] = useState4(false);
5218
- const [urlError, setUrlError] = useState4("");
5219
- const reset = useCallback(() => {
5220
- setSearchValue("");
5221
- setSelectedPage(null);
5222
- setSelectedSection(null);
5223
- setStep("input");
5224
- setDropdownOpen(false);
5225
- setUrlError("");
5226
- }, []);
5227
- useEffect2(() => {
5228
- if (!open) return;
5229
- if (mode === "edit" && initialTarget) {
5230
- const { pageRoute } = parseTarget(initialTarget);
5472
+ // src/ui/link-modal/LinkEditorPanel.tsx
5473
+ import { Fragment as Fragment3, jsx as jsx18, jsxs as jsxs10 } from "react/jsx-runtime";
5474
+ function LinkEditorPanel({ state, onClose }) {
5475
+ const isCancel = state.secondaryLabel === "Cancel" || state.secondaryLabel === "Back to sections";
5476
+ return /* @__PURE__ */ jsxs10(Fragment3, { children: [
5477
+ /* @__PURE__ */ jsx18(DialogClose, { asChild: true, children: /* @__PURE__ */ jsx18(
5478
+ "button",
5479
+ {
5480
+ type: "button",
5481
+ className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50 h-7",
5482
+ "aria-label": "Close",
5483
+ onClick: onClose,
5484
+ children: /* @__PURE__ */ jsx18(X3, { size: 16, "aria-hidden": true })
5485
+ }
5486
+ ) }),
5487
+ /* @__PURE__ */ jsx18(DialogHeader, { className: "w-full gap-1.5 p-6 pr-12", children: /* @__PURE__ */ jsx18(DialogTitle, { className: "m-0 w-full break-words", children: state.title }) }),
5488
+ /* @__PURE__ */ jsxs10("div", { className: "flex w-full flex-col gap-3 px-6 pb-8 pt-1", children: [
5489
+ state.showBreadcrumb && state.selectedPage && state.selectedSection ? /* @__PURE__ */ jsx18(
5490
+ DestinationBreadcrumb,
5491
+ {
5492
+ pageTitle: state.selectedPage.title,
5493
+ sectionLabel: state.selectedSection.label
5494
+ }
5495
+ ) : /* @__PURE__ */ jsx18(
5496
+ UrlOrPageInput,
5497
+ {
5498
+ value: state.searchValue,
5499
+ selectedPage: state.selectedPage,
5500
+ dropdownOpen: state.dropdownOpen,
5501
+ onDropdownOpenChange: state.setDropdownOpen,
5502
+ filteredPages: state.filteredPages,
5503
+ onInputChange: state.handleInputChange,
5504
+ onPageSelect: state.handlePageSelect,
5505
+ urlError: state.urlError
5506
+ }
5507
+ ),
5508
+ state.showChooseSection ? /* @__PURE__ */ jsxs10("div", { className: "flex flex-col justify-center gap-2", children: [
5509
+ /* @__PURE__ */ jsx18(
5510
+ Button,
5511
+ {
5512
+ type: "button",
5513
+ variant: "outline",
5514
+ className: "w-fit cursor-pointer",
5515
+ size: "sm",
5516
+ onClick: state.handleChooseSection,
5517
+ children: "Choose a section"
5518
+ }
5519
+ ),
5520
+ /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-1 text-sm text-muted-foreground", children: [
5521
+ /* @__PURE__ */ jsx18(Info, { size: 16, className: "shrink-0", "aria-hidden": true }),
5522
+ /* @__PURE__ */ jsx18("span", { children: "Pick a section this link should scroll to." })
5523
+ ] })
5524
+ ] }) : null,
5525
+ state.showSectionRow && state.selectedSection ? /* @__PURE__ */ jsx18(SectionTreeItem, { section: state.selectedSection, selected: true }) : null
5526
+ ] }),
5527
+ /* @__PURE__ */ jsxs10(DialogFooter, { className: "w-full px-6 pb-6", children: [
5528
+ /* @__PURE__ */ jsx18(
5529
+ Button,
5530
+ {
5531
+ type: "button",
5532
+ variant: isCancel ? "outline" : "ghost",
5533
+ className: "leading-6 shadow-none cursor-pointer",
5534
+ style: isCancel ? {
5535
+ backgroundColor: "var(--ohw-background)",
5536
+ borderColor: "var(--ohw-border)",
5537
+ color: "var(--ohw-foreground)"
5538
+ } : void 0,
5539
+ onClick: state.handleSecondary,
5540
+ children: state.secondaryLabel
5541
+ }
5542
+ ),
5543
+ /* @__PURE__ */ jsx18(
5544
+ Button,
5545
+ {
5546
+ type: "button",
5547
+ className: "border-0 leading-6 shadow-none hover:opacity-90 disabled:opacity-50 cursor-pointer",
5548
+ style: {
5549
+ backgroundColor: "var(--ohw-primary)",
5550
+ color: "var(--ohw-primary-foreground)"
5551
+ },
5552
+ disabled: !state.isValid,
5553
+ onClick: state.handleSubmit,
5554
+ children: state.submitLabel
5555
+ }
5556
+ )
5557
+ ] })
5558
+ ] });
5559
+ }
5560
+
5561
+ // src/ui/link-modal/SectionPickerOverlay.tsx
5562
+ import { useCallback as useCallback2, useEffect as useEffect4, useMemo as useMemo2, useState as useState4 } from "react";
5563
+ import { createPortal } from "react-dom";
5564
+ import { ArrowLeft, Check } from "lucide-react";
5565
+ import { usePathname, useRouter } from "next/navigation";
5566
+ import { jsx as jsx19, jsxs as jsxs11 } from "react/jsx-runtime";
5567
+ var DIM_OVERLAY = "rgba(0, 0, 0, 0.45)";
5568
+ function rectsEqual(a, b) {
5569
+ if (a.size !== b.size) return false;
5570
+ for (const [id, rect] of a) {
5571
+ const other = b.get(id);
5572
+ if (!other || rect.top !== other.top || rect.left !== other.left || rect.width !== other.width || rect.height !== other.height) {
5573
+ return false;
5574
+ }
5575
+ }
5576
+ return true;
5577
+ }
5578
+ function useSectionRects(sectionIdsKey, enabled) {
5579
+ const [rects, setRects] = useState4(/* @__PURE__ */ new Map());
5580
+ const sectionIds = useMemo2(
5581
+ () => sectionIdsKey ? sectionIdsKey.split("\0") : [],
5582
+ [sectionIdsKey]
5583
+ );
5584
+ useEffect4(() => {
5585
+ if (!enabled || sectionIds.length === 0) {
5586
+ setRects((prev) => prev.size === 0 ? prev : /* @__PURE__ */ new Map());
5587
+ return;
5588
+ }
5589
+ const update = () => {
5590
+ const next = /* @__PURE__ */ new Map();
5591
+ for (const id of sectionIds) {
5592
+ const el = document.querySelector(
5593
+ `[data-ohw-section="${CSS.escape(id)}"]`
5594
+ );
5595
+ if (el) next.set(id, el.getBoundingClientRect());
5596
+ }
5597
+ setRects((prev) => rectsEqual(prev, next) ? prev : next);
5598
+ };
5599
+ update();
5600
+ const opts = { capture: true, passive: true };
5601
+ window.addEventListener("scroll", update, opts);
5602
+ window.addEventListener("resize", update);
5603
+ const vv = window.visualViewport;
5604
+ vv?.addEventListener("resize", update);
5605
+ vv?.addEventListener("scroll", update);
5606
+ const ro = new ResizeObserver(update);
5607
+ for (const id of sectionIds) {
5608
+ const el = document.querySelector(
5609
+ `[data-ohw-section="${CSS.escape(id)}"]`
5610
+ );
5611
+ if (el) ro.observe(el);
5612
+ }
5613
+ return () => {
5614
+ window.removeEventListener("scroll", update, opts);
5615
+ window.removeEventListener("resize", update);
5616
+ vv?.removeEventListener("resize", update);
5617
+ vv?.removeEventListener("scroll", update);
5618
+ ro.disconnect();
5619
+ };
5620
+ }, [sectionIds, enabled]);
5621
+ return rects;
5622
+ }
5623
+ function SectionPickerOverlay({
5624
+ pagePath,
5625
+ sections,
5626
+ onBack,
5627
+ onSelect
5628
+ }) {
5629
+ const router = useRouter();
5630
+ const pathname = usePathname();
5631
+ const [selectedId, setSelectedId] = useState4(null);
5632
+ const [hoveredId, setHoveredId] = useState4(null);
5633
+ const [chromeClip, setChromeClip] = useState4(
5634
+ () => ({
5635
+ top: 0,
5636
+ bottom: typeof window !== "undefined" ? window.innerHeight : 0
5637
+ })
5638
+ );
5639
+ const normalizedTarget = normalizePath(pagePath);
5640
+ const isOnTargetPage = normalizePath(pathname) === normalizedTarget;
5641
+ useEffect4(() => {
5642
+ if (isOnTargetPage) return;
5643
+ const search = readPreservedSearch();
5644
+ router.push(`${normalizedTarget}${search}`);
5645
+ }, [isOnTargetPage, normalizedTarget, router]);
5646
+ useEffect4(() => {
5647
+ document.documentElement.setAttribute("data-ohw-section-picking", "");
5648
+ document.querySelectorAll("[data-ohw-hovered]").forEach((el) => {
5649
+ el.removeAttribute("data-ohw-hovered");
5650
+ });
5651
+ return () => {
5652
+ document.documentElement.removeAttribute("data-ohw-section-picking");
5653
+ };
5654
+ }, []);
5655
+ useEffect4(() => {
5656
+ const applyClip = (iframeOffsetTop, visibleCanvasTop, canvasH) => {
5657
+ const top = Math.max(0, visibleCanvasTop - iframeOffsetTop);
5658
+ const bottom = Math.min(
5659
+ window.innerHeight,
5660
+ visibleCanvasTop + canvasH - iframeOffsetTop
5661
+ );
5662
+ setChromeClip(
5663
+ (prev) => prev.top === top && prev.bottom === bottom ? prev : { top, bottom: Math.max(top, bottom) }
5664
+ );
5665
+ };
5666
+ const onParentScroll = (e) => {
5667
+ if (e.data?.type !== "ow:parent-scroll") return;
5668
+ const { iframeOffsetTop, headerH, canvasH } = e.data;
5669
+ applyClip(iframeOffsetTop, headerH, canvasH);
5670
+ };
5671
+ const onResize = () => {
5672
+ setChromeClip((prev) => {
5673
+ const bottom = Math.max(prev.top, window.innerHeight);
5674
+ return prev.bottom === bottom ? prev : { ...prev, bottom };
5675
+ });
5676
+ };
5677
+ window.addEventListener("message", onParentScroll);
5678
+ window.addEventListener("resize", onResize);
5679
+ return () => {
5680
+ window.removeEventListener("message", onParentScroll);
5681
+ window.removeEventListener("resize", onResize);
5682
+ };
5683
+ }, []);
5684
+ const liveSections = useMemo2(() => {
5685
+ if (!isOnTargetPage) return sections;
5686
+ const live = collectSectionsFromDom();
5687
+ if (live.length === 0) return sections;
5688
+ const labels = new Map(sections.map((s) => [s.id, s.label]));
5689
+ return live.map((s) => ({ id: s.id, label: labels.get(s.id) ?? s.label }));
5690
+ }, [isOnTargetPage, sections, pathname]);
5691
+ const sectionIdsKey = useMemo2(
5692
+ () => liveSections.map((s) => s.id).join("\0"),
5693
+ [liveSections]
5694
+ );
5695
+ const rects = useSectionRects(sectionIdsKey, isOnTargetPage);
5696
+ const handleSelect = useCallback2(
5697
+ (section) => {
5698
+ if (selectedId) return;
5699
+ setSelectedId(section.id);
5700
+ onSelect(section);
5701
+ },
5702
+ [selectedId, onSelect]
5703
+ );
5704
+ useEffect4(() => {
5705
+ const onKeyDown = (e) => {
5706
+ if (e.key === "Escape") {
5707
+ e.preventDefault();
5708
+ e.stopPropagation();
5709
+ onBack();
5710
+ }
5711
+ };
5712
+ window.addEventListener("keydown", onKeyDown, true);
5713
+ return () => window.removeEventListener("keydown", onKeyDown, true);
5714
+ }, [onBack]);
5715
+ const portalRoot = typeof document !== "undefined" ? document.querySelector("[data-ohw-bridge-root]") ?? document.body : null;
5716
+ if (!portalRoot) return null;
5717
+ return createPortal(
5718
+ /* @__PURE__ */ jsxs11(
5719
+ "div",
5720
+ {
5721
+ "data-ohw-section-picker": "",
5722
+ "data-ohw-bridge": "",
5723
+ className: `pointer-events-none fixed inset-0 z-[2147483647] transition-opacity duration-200 ${"opacity-100"}`,
5724
+ "aria-modal": true,
5725
+ role: "dialog",
5726
+ "aria-label": "Choose a section",
5727
+ children: [
5728
+ /* @__PURE__ */ jsx19(
5729
+ "div",
5730
+ {
5731
+ className: "pointer-events-auto fixed left-5 z-[2]",
5732
+ style: { top: chromeClip.top + 20 },
5733
+ children: /* @__PURE__ */ jsxs11(
5734
+ Button,
5735
+ {
5736
+ type: "button",
5737
+ variant: "outline",
5738
+ size: "sm",
5739
+ className: "h-8 min-w-0 gap-1 border-border bg-background px-2 py-1.5 shadow-sm hover:bg-muted cursor-pointer",
5740
+ onClick: onBack,
5741
+ children: [
5742
+ /* @__PURE__ */ jsx19(ArrowLeft, { className: "size-4 shrink-0", "aria-hidden": true }),
5743
+ "Back"
5744
+ ]
5745
+ }
5746
+ )
5747
+ }
5748
+ ),
5749
+ /* @__PURE__ */ jsx19(
5750
+ "div",
5751
+ {
5752
+ 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",
5753
+ style: {
5754
+ top: chromeClip.bottom - 20,
5755
+ transform: "translate(-50%, -100%)",
5756
+ backgroundColor: "var(--ohw-popover-foreground, #0c0a09)"
5757
+ },
5758
+ "data-ohw-section-picker-hint": "",
5759
+ children: "Click on section to select"
5760
+ }
5761
+ ),
5762
+ !isOnTargetPage ? /* @__PURE__ */ jsx19(
5763
+ "div",
5764
+ {
5765
+ 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",
5766
+ style: { top: chromeClip.top + 64 },
5767
+ children: "Loading page preview\u2026"
5768
+ }
5769
+ ) : null,
5770
+ isOnTargetPage && liveSections.length === 0 ? /* @__PURE__ */ jsx19("div", { className: "pointer-events-auto fixed inset-0 z-[1] flex items-center justify-center bg-muted/40", children: /* @__PURE__ */ jsx19("p", { className: "text-sm text-muted-foreground", children: "No sections found on this page." }) }) : null,
5771
+ isOnTargetPage ? liveSections.map((section) => {
5772
+ const rect = rects.get(section.id);
5773
+ if (!rect || rect.width <= 0 || rect.height <= 0) return null;
5774
+ const isSelected = selectedId === section.id;
5775
+ const isLit = isSelected || hoveredId === section.id;
5776
+ return /* @__PURE__ */ jsx19(
5777
+ "button",
5778
+ {
5779
+ type: "button",
5780
+ className: "pointer-events-auto fixed z-[1] cursor-pointer border-0 bg-transparent p-0 transition-[background-color] duration-150",
5781
+ style: {
5782
+ top: rect.top,
5783
+ left: rect.left,
5784
+ width: rect.width,
5785
+ height: rect.height,
5786
+ backgroundColor: isLit ? "transparent" : DIM_OVERLAY
5787
+ },
5788
+ "aria-label": `Select section ${section.label}`,
5789
+ onMouseEnter: () => setHoveredId(section.id),
5790
+ onMouseLeave: () => setHoveredId((prev) => prev === section.id ? null : prev),
5791
+ onClick: () => handleSelect(section),
5792
+ children: isSelected ? /* @__PURE__ */ jsx19(
5793
+ "span",
5794
+ {
5795
+ className: "absolute right-3 top-3 flex size-8 items-center justify-center rounded-full text-white",
5796
+ style: { backgroundColor: "var(--ohw-primary, #0885fe)" },
5797
+ "aria-hidden": true,
5798
+ children: /* @__PURE__ */ jsx19(Check, { className: "size-5" })
5799
+ }
5800
+ ) : null
5801
+ },
5802
+ section.id
5803
+ );
5804
+ }) : null
5805
+ ]
5806
+ }
5807
+ ),
5808
+ portalRoot
5809
+ );
5810
+ }
5811
+
5812
+ // src/ui/link-modal/useLinkModalState.ts
5813
+ import { useCallback as useCallback3, useEffect as useEffect5, useMemo as useMemo3, useState as useState5 } from "react";
5814
+ function useLinkModalState({
5815
+ open,
5816
+ mode,
5817
+ pages,
5818
+ sections: _sections,
5819
+ sectionsByPath,
5820
+ initialTarget,
5821
+ existingTargets: _existingTargets,
5822
+ onClose,
5823
+ onSubmit
5824
+ }) {
5825
+ const availablePages = useMemo3(() => pages, [pages]);
5826
+ const [searchValue, setSearchValue] = useState5("");
5827
+ const [selectedPage, setSelectedPage] = useState5(null);
5828
+ const [selectedSection, setSelectedSection] = useState5(null);
5829
+ const [step, setStep] = useState5("input");
5830
+ const [dropdownOpen, setDropdownOpen] = useState5(false);
5831
+ const [urlError, setUrlError] = useState5("");
5832
+ const reset = useCallback3(() => {
5833
+ setSearchValue("");
5834
+ setSelectedPage(null);
5835
+ setSelectedSection(null);
5836
+ setStep("input");
5837
+ setDropdownOpen(false);
5838
+ setUrlError("");
5839
+ }, []);
5840
+ useEffect5(() => {
5841
+ if (!open) return;
5842
+ if (mode === "edit" && initialTarget) {
5843
+ const { pageRoute } = parseTarget(initialTarget);
5231
5844
  const targetSections = getSectionsForPath(sectionsByPath, pageRoute);
5232
5845
  const init = getEditModeInitialState(initialTarget, pages, targetSections);
5233
5846
  setSearchValue(init.searchValue);
@@ -5239,12 +5852,12 @@ function useLinkModalState({
5239
5852
  }
5240
5853
  setDropdownOpen(false);
5241
5854
  setUrlError("");
5242
- }, [open, mode, initialTarget, pages, sectionsByPath, reset]);
5243
- const filteredPages = useMemo2(() => {
5855
+ }, [open, mode, initialTarget, reset]);
5856
+ const filteredPages = useMemo3(() => {
5244
5857
  if (!searchValue.trim()) return availablePages;
5245
5858
  return availablePages.filter((p) => p.title.toLowerCase().startsWith(searchValue.toLowerCase()));
5246
5859
  }, [availablePages, searchValue]);
5247
- const activeSections = useMemo2(() => {
5860
+ const activeSections = useMemo3(() => {
5248
5861
  if (!selectedPage) return [];
5249
5862
  return getSectionsForPath(sectionsByPath, selectedPage.path);
5250
5863
  }, [selectedPage, sectionsByPath]);
@@ -5286,11 +5899,14 @@ function useLinkModalState({
5286
5899
  const handleBackToSections = () => {
5287
5900
  setStep("sectionPicker");
5288
5901
  };
5902
+ const handleSectionPickerBack = () => {
5903
+ setStep("input");
5904
+ };
5289
5905
  const handleClose = () => {
5290
5906
  reset();
5291
5907
  onClose();
5292
5908
  };
5293
- const isValid = useMemo2(() => {
5909
+ const isValid = useMemo3(() => {
5294
5910
  if (urlError) return false;
5295
5911
  if (showBreadcrumb) return true;
5296
5912
  if (selectedPage) return true;
@@ -5323,7 +5939,7 @@ function useLinkModalState({
5323
5939
  onSubmit(normalizeUrl(searchValue));
5324
5940
  handleClose();
5325
5941
  };
5326
- const submitLabel = mode === "edit" ? "Save" : "Create";
5942
+ const submitLabel = showBreadcrumb ? "Save" : mode === "edit" ? "Save" : "Create";
5327
5943
  const title = mode === "edit" ? "Edit navigation item" : "Create navigation item";
5328
5944
  const secondaryLabel = showBreadcrumb ? "Back to sections" : "Cancel";
5329
5945
  const handleSecondary = showBreadcrumb ? handleBackToSections : handleClose;
@@ -5350,23 +5966,29 @@ function useLinkModalState({
5350
5966
  handleChooseSection,
5351
5967
  handleSectionSelect,
5352
5968
  handleBackToSections,
5969
+ handleSectionPickerBack,
5353
5970
  handleClose,
5354
5971
  handleSecondary,
5355
5972
  handleSubmit
5356
5973
  };
5357
5974
  }
5358
5975
 
5359
- // src/ui/link-modal/LinkEditorPanel.tsx
5360
- import { Fragment as Fragment2, jsx as jsx18, jsxs as jsxs10 } from "react/jsx-runtime";
5361
- function LinkEditorPanel({
5976
+ // src/ui/link-modal/LinkPopover.tsx
5977
+ import { Fragment as Fragment4, jsx as jsx20, jsxs as jsxs12 } from "react/jsx-runtime";
5978
+ function postToParent(data) {
5979
+ window.parent?.postMessage(data, "*");
5980
+ }
5981
+ function LinkPopover({
5362
5982
  open = true,
5983
+ panelRef,
5984
+ portalContainer,
5985
+ onClose,
5363
5986
  mode = "create",
5364
5987
  pages,
5365
5988
  sections = [],
5366
5989
  sectionsByPath,
5367
5990
  initialTarget,
5368
5991
  existingTargets = [],
5369
- onClose,
5370
5992
  onSubmit
5371
5993
  }) {
5372
5994
  const state = useLinkModalState({
@@ -5380,115 +6002,100 @@ function LinkEditorPanel({
5380
6002
  onClose,
5381
6003
  onSubmit
5382
6004
  });
5383
- const isCancel = state.secondaryLabel === "Cancel";
5384
- return /* @__PURE__ */ jsxs10(Fragment2, { children: [
5385
- /* @__PURE__ */ jsx18(DialogClose, { asChild: true, children: /* @__PURE__ */ jsx18(
5386
- "button",
6005
+ const sectionPickerActive = state.showSectionPicker && Boolean(state.selectedPage);
6006
+ useEffect6(() => {
6007
+ if (!open) return;
6008
+ if (sectionPickerActive) {
6009
+ postToParent({ type: "ow:link-modal-lock", locked: false });
6010
+ postToParent({ type: "ow:section-picker", active: true });
6011
+ document.documentElement.style.overflow = "";
6012
+ document.body.style.overflow = "";
6013
+ return () => {
6014
+ postToParent({ type: "ow:section-picker", active: false });
6015
+ };
6016
+ }
6017
+ postToParent({ type: "ow:section-picker", active: false });
6018
+ postToParent({ type: "ow:link-modal-lock", locked: true });
6019
+ const html = document.documentElement;
6020
+ const body = document.body;
6021
+ const prevHtmlOverflow = html.style.overflow;
6022
+ const prevBodyOverflow = body.style.overflow;
6023
+ const prevHtmlOverscroll = html.style.overscrollBehavior;
6024
+ const prevBodyOverscroll = body.style.overscrollBehavior;
6025
+ html.style.overflow = "hidden";
6026
+ body.style.overflow = "hidden";
6027
+ html.style.overscrollBehavior = "none";
6028
+ body.style.overscrollBehavior = "none";
6029
+ const canScrollElement = (el, deltaY) => {
6030
+ const { overflowY } = getComputedStyle(el);
6031
+ if (overflowY !== "auto" && overflowY !== "scroll") return false;
6032
+ if (el.scrollHeight <= el.clientHeight + 1) return false;
6033
+ if (deltaY < 0) return el.scrollTop > 0;
6034
+ if (deltaY > 0) return el.scrollTop + el.clientHeight < el.scrollHeight - 1;
6035
+ return true;
6036
+ };
6037
+ const allowsInternalScroll = (e) => {
6038
+ const target = e.target;
6039
+ if (!(target instanceof Element)) return false;
6040
+ const scrollRoot = target.closest(
6041
+ "[data-ohw-link-page-dropdown], [data-ohw-allow-scroll]"
6042
+ );
6043
+ if (!scrollRoot) return false;
6044
+ const deltaY = e instanceof WheelEvent ? e.deltaY : 0;
6045
+ return canScrollElement(scrollRoot, deltaY);
6046
+ };
6047
+ const preventBackgroundScroll = (e) => {
6048
+ if (allowsInternalScroll(e)) return;
6049
+ e.preventDefault();
6050
+ };
6051
+ const scrollOpts = { passive: false, capture: true };
6052
+ document.addEventListener("wheel", preventBackgroundScroll, scrollOpts);
6053
+ document.addEventListener("touchmove", preventBackgroundScroll, scrollOpts);
6054
+ return () => {
6055
+ postToParent({ type: "ow:link-modal-lock", locked: false });
6056
+ html.style.overflow = prevHtmlOverflow;
6057
+ body.style.overflow = prevBodyOverflow;
6058
+ html.style.overscrollBehavior = prevHtmlOverscroll;
6059
+ body.style.overscrollBehavior = prevBodyOverscroll;
6060
+ document.removeEventListener("wheel", preventBackgroundScroll, scrollOpts);
6061
+ document.removeEventListener("touchmove", preventBackgroundScroll, scrollOpts);
6062
+ };
6063
+ }, [open, sectionPickerActive]);
6064
+ return /* @__PURE__ */ jsxs12(Fragment4, { children: [
6065
+ /* @__PURE__ */ jsx20(
6066
+ Dialog2,
5387
6067
  {
5388
- type: "button",
5389
- className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
5390
- "aria-label": "Close",
5391
- children: /* @__PURE__ */ jsx18(IconX, { "aria-hidden": true })
6068
+ open: open && !sectionPickerActive,
6069
+ onOpenChange: (next) => {
6070
+ if (!next) onClose?.();
6071
+ },
6072
+ children: /* @__PURE__ */ jsx20(
6073
+ DialogContent,
6074
+ {
6075
+ ref: panelRef,
6076
+ container: portalContainer,
6077
+ "data-ohw-link-popover-root": "",
6078
+ "data-ohw-link-modal-root": "",
6079
+ "data-ohw-bridge": "",
6080
+ showCloseButton: false,
6081
+ className: "gap-0 p-0 w-full md:w-md pointer-events-auto z-[2147483646] overflow-visible",
6082
+ children: /* @__PURE__ */ jsx20(LinkEditorPanel, { state, onClose })
6083
+ }
6084
+ )
5392
6085
  }
5393
- ) }),
5394
- /* @__PURE__ */ jsx18(DialogHeader, { className: "w-full gap-1.5 p-6 pr-12", children: /* @__PURE__ */ jsx18(DialogTitle, { className: "m-0 w-full break-words", children: state.title }) }),
5395
- /* @__PURE__ */ jsxs10("div", { className: "flex w-full flex-col gap-3 px-6 pb-8 pt-1", children: [
5396
- state.showBreadcrumb && state.selectedPage && state.selectedSection ? /* @__PURE__ */ jsx18(
5397
- DestinationBreadcrumb,
5398
- {
5399
- pageTitle: state.selectedPage.title,
5400
- sectionLabel: state.selectedSection.label
5401
- }
5402
- ) : /* @__PURE__ */ jsx18(
5403
- UrlOrPageInput,
5404
- {
5405
- value: state.searchValue,
5406
- selectedPage: state.selectedPage,
5407
- dropdownOpen: state.dropdownOpen,
5408
- onDropdownOpenChange: state.setDropdownOpen,
5409
- filteredPages: state.filteredPages,
5410
- onInputChange: state.handleInputChange,
5411
- onPageSelect: state.handlePageSelect,
5412
- urlError: state.urlError
5413
- }
5414
- ),
5415
- state.showChooseSection ? /* @__PURE__ */ jsxs10("div", { className: "flex flex-col justify-center gap-2", children: [
5416
- /* @__PURE__ */ jsx18(Button, { type: "button", variant: "outline", className: "w-fit cursor-pointer", size: "sm", onClick: state.handleChooseSection, children: "Choose a section" }),
5417
- /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-1 text-sm text-muted-foreground", children: [
5418
- /* @__PURE__ */ jsx18(IconInfo, { className: "shrink-0", "aria-hidden": true }),
5419
- /* @__PURE__ */ jsx18("span", { children: "Pick a section this link should scroll to." })
5420
- ] })
5421
- ] }) : null,
5422
- state.showSectionPicker ? /* @__PURE__ */ jsx18(SectionPickerList, { sections: state.activeSections, onSelect: state.handleSectionSelect }) : null,
5423
- state.showSectionRow && state.selectedSection ? /* @__PURE__ */ jsx18(SectionTreeItem, { section: state.selectedSection, selected: true }) : null
5424
- ] }),
5425
- /* @__PURE__ */ jsxs10(DialogFooter, { className: "w-full px-6 pb-6", children: [
5426
- /* @__PURE__ */ jsx18(
5427
- Button,
5428
- {
5429
- type: "button",
5430
- variant: isCancel ? "outline" : "ghost",
5431
- className: "leading-6 shadow-none cursor-pointer",
5432
- style: isCancel ? {
5433
- backgroundColor: "var(--ohw-background)",
5434
- borderColor: "var(--ohw-border)",
5435
- color: "var(--ohw-foreground)"
5436
- } : void 0,
5437
- onClick: state.handleSecondary,
5438
- children: state.secondaryLabel
5439
- }
5440
- ),
5441
- /* @__PURE__ */ jsx18(
5442
- Button,
5443
- {
5444
- type: "button",
5445
- className: "border-0 leading-6 shadow-none hover:opacity-90 disabled:opacity-50 cursor-pointer",
5446
- style: {
5447
- backgroundColor: "var(--ohw-primary)",
5448
- color: "var(--ohw-primary-foreground)"
5449
- },
5450
- disabled: !state.isValid,
5451
- onClick: state.handleSubmit,
5452
- children: state.submitLabel
5453
- }
5454
- )
5455
- ] })
6086
+ ),
6087
+ sectionPickerActive && state.selectedPage ? /* @__PURE__ */ jsx20(
6088
+ SectionPickerOverlay,
6089
+ {
6090
+ pagePath: state.selectedPage.path,
6091
+ sections: state.activeSections,
6092
+ onBack: state.handleSectionPickerBack,
6093
+ onSelect: state.handleSectionSelect
6094
+ }
6095
+ ) : null
5456
6096
  ] });
5457
6097
  }
5458
6098
 
5459
- // src/ui/link-modal/LinkPopover.tsx
5460
- import { jsx as jsx19 } from "react/jsx-runtime";
5461
- function LinkPopover({
5462
- open = true,
5463
- panelRef,
5464
- portalContainer,
5465
- onClose,
5466
- ...editorProps
5467
- }) {
5468
- return /* @__PURE__ */ jsx19(
5469
- Dialog2,
5470
- {
5471
- open,
5472
- onOpenChange: (next) => {
5473
- if (!next) onClose?.();
5474
- },
5475
- children: /* @__PURE__ */ jsx19(
5476
- DialogContent,
5477
- {
5478
- ref: panelRef,
5479
- container: portalContainer,
5480
- "data-ohw-link-popover-root": "",
5481
- "data-ohw-link-modal-root": "",
5482
- "data-ohw-bridge": "",
5483
- showCloseButton: false,
5484
- className: "gap-0 p-0 w-full md:w-md pointer-events-auto",
5485
- children: /* @__PURE__ */ jsx19(LinkEditorPanel, { ...editorProps, open, onClose })
5486
- }
5487
- )
5488
- }
5489
- );
5490
- }
5491
-
5492
6099
  // src/ui/link-modal/devFixtures.ts
5493
6100
  var DEV_SITE_PAGES = [
5494
6101
  { path: "/", title: "Home" },
@@ -5508,16 +6115,21 @@ var DEV_SECTIONS_BY_PATH = {
5508
6115
  { id: "founder-teaser", label: "Founder" },
5509
6116
  { id: "plan-form", label: "Plan Form" },
5510
6117
  { id: "testimonials", label: "Testimonials" },
5511
- { id: "wordmark", label: "Wordmark" }
6118
+ { id: "wordmark", label: "Wordmark" },
6119
+ { id: "footer", label: "Footer" }
5512
6120
  ],
5513
6121
  "/about": [
5514
6122
  { id: "manifesto", label: "Manifesto" },
5515
6123
  { id: "story-letter", label: "Our Story" },
5516
6124
  { id: "lagree-explainer", label: "Lagree Explainer" },
5517
6125
  { id: "waitlist-cta", label: "Waitlist" },
5518
- { id: "personal-training", label: "Personal training" }
6126
+ { id: "personal-training", label: "Personal training" },
6127
+ { id: "footer", label: "Footer" }
6128
+ ],
6129
+ "/classes": [
6130
+ { id: "class-library", label: "Class Library" },
6131
+ { id: "footer", label: "Footer" }
5519
6132
  ],
5520
- "/classes": [{ id: "class-library", label: "Class Library" }],
5521
6133
  "/pricing": [
5522
6134
  { id: "page-header", label: "Page Header" },
5523
6135
  { id: "free-class-cta", label: "Free Class" },
@@ -5525,19 +6137,25 @@ var DEV_SECTIONS_BY_PATH = {
5525
6137
  { id: "monthly-memberships", label: "Memberships" },
5526
6138
  { id: "specialty-programs", label: "Specialty Programs" },
5527
6139
  { id: "package-matcher", label: "Packages" },
5528
- { id: "wordmark", label: "Wordmark" }
6140
+ { id: "wordmark", label: "Wordmark" },
6141
+ { id: "footer", label: "Footer" }
5529
6142
  ],
5530
6143
  "/studio": [
5531
6144
  { id: "studio-intro", label: "Studio Intro" },
5532
6145
  { id: "studio-features", label: "Studio Features" },
5533
6146
  { id: "studio-gallery", label: "Studio Gallery" },
5534
- { id: "studio-visit", label: "Visit Studio" }
6147
+ { id: "studio-visit", label: "Visit Studio" },
6148
+ { id: "footer", label: "Footer" }
6149
+ ],
6150
+ "/instructors": [
6151
+ { id: "instructor-grid", label: "Instructors" },
6152
+ { id: "footer", label: "Footer" }
5535
6153
  ],
5536
- "/instructors": [{ id: "instructor-grid", label: "Instructors" }],
5537
6154
  "/contact": [
5538
6155
  { id: "hello-hero", label: "Hello" },
5539
6156
  { id: "contact-form", label: "Contact Form" },
5540
- { id: "faq", label: "FAQ" }
6157
+ { id: "faq", label: "FAQ" },
6158
+ { id: "footer", label: "Footer" }
5541
6159
  ]
5542
6160
  };
5543
6161
  function shouldUseDevFixtures() {
@@ -5547,8 +6165,277 @@ function shouldUseDevFixtures() {
5547
6165
  return new URLSearchParams(q).get("ohw-fixtures") === "1";
5548
6166
  }
5549
6167
 
6168
+ // src/lib/nav-items.ts
6169
+ var NAV_COUNT_KEY = "__ohw_nav_count";
6170
+ var NAV_ORDER_KEY = "__ohw_nav_order";
6171
+ function getLinkHref(el) {
6172
+ const key = el.getAttribute("data-ohw-href-key");
6173
+ if (key) {
6174
+ const stored = getStoredLinkHref(key);
6175
+ if (stored !== void 0) return stored;
6176
+ }
6177
+ return el.getAttribute("href") ?? "";
6178
+ }
6179
+ function isNavbarButton(el) {
6180
+ return el.hasAttribute("data-ohw-role") && el.getAttribute("data-ohw-role") === "navbar-button";
6181
+ }
6182
+ function isNavbarLinkItem(el) {
6183
+ if (!el.matches("[data-ohw-href-key]")) return false;
6184
+ if (isNavbarButton(el)) return false;
6185
+ if (!el.querySelector('[data-ohw-editable="text"]')) return false;
6186
+ return Boolean(el.closest("nav, [data-ohw-nav-container], [data-ohw-nav-drawer]"));
6187
+ }
6188
+ function getNavbarDesktopContainer() {
6189
+ return document.querySelector("[data-ohw-nav-container]");
6190
+ }
6191
+ function getNavbarDrawerContainer() {
6192
+ return document.querySelector("[data-ohw-nav-drawer]");
6193
+ }
6194
+ function listNavbarItems() {
6195
+ const desktop = getNavbarDesktopContainer();
6196
+ if (desktop) {
6197
+ return Array.from(desktop.querySelectorAll("[data-ohw-href-key]")).filter(isNavbarLinkItem);
6198
+ }
6199
+ const drawer = getNavbarDrawerContainer();
6200
+ if (drawer) {
6201
+ return Array.from(drawer.querySelectorAll("[data-ohw-href-key]")).filter(isNavbarLinkItem);
6202
+ }
6203
+ return Array.from(document.querySelectorAll("nav [data-ohw-href-key]")).filter(isNavbarLinkItem);
6204
+ }
6205
+ function parseNavIndexFromKey(key) {
6206
+ if (!key) return null;
6207
+ const match = key.match(/^nav-(\d+)-href$/);
6208
+ if (!match) return null;
6209
+ return parseInt(match[1], 10);
6210
+ }
6211
+ function getNavbarIndicesInDom() {
6212
+ const indices = /* @__PURE__ */ new Set();
6213
+ for (const item of listNavbarItems()) {
6214
+ const idx = parseNavIndexFromKey(item.getAttribute("data-ohw-href-key"));
6215
+ if (idx !== null) indices.add(idx);
6216
+ }
6217
+ return [...indices].sort((a, b) => a - b);
6218
+ }
6219
+ function getNextNavbarIndex() {
6220
+ const indices = getNavbarIndicesInDom();
6221
+ if (indices.length === 0) return 0;
6222
+ return Math.max(...indices) + 1;
6223
+ }
6224
+ function getNavbarExistingTargets() {
6225
+ return listNavbarItems().map((el) => getLinkHref(el)).filter(Boolean);
6226
+ }
6227
+ function getNavOrderFromDom() {
6228
+ return listNavbarItems().map((el) => el.getAttribute("data-ohw-href-key")).filter((key) => Boolean(key));
6229
+ }
6230
+ function parseNavOrder(content) {
6231
+ const raw = content[NAV_ORDER_KEY];
6232
+ if (!raw) return null;
6233
+ try {
6234
+ const parsed = JSON.parse(raw);
6235
+ if (!Array.isArray(parsed)) return null;
6236
+ return parsed.filter((key) => typeof key === "string" && key.length > 0);
6237
+ } catch {
6238
+ return null;
6239
+ }
6240
+ }
6241
+ function findCounterpartByHrefKey(hrefKey, root) {
6242
+ return root.querySelector(`[data-ohw-href-key="${CSS.escape(hrefKey)}"]`);
6243
+ }
6244
+ function cloneNavLinkShell(template) {
6245
+ const anchor = document.createElement("a");
6246
+ if (template) {
6247
+ anchor.style.cssText = template.style.cssText;
6248
+ if (template.className) anchor.className = template.className;
6249
+ }
6250
+ anchor.style.cursor = "pointer";
6251
+ anchor.style.textDecoration = "none";
6252
+ return anchor;
6253
+ }
6254
+ function buildNavLink(index, href, label, template) {
6255
+ const anchor = cloneNavLinkShell(template);
6256
+ anchor.href = href;
6257
+ anchor.setAttribute("data-ohw-href-key", `nav-${index}-href`);
6258
+ const span = document.createElement("span");
6259
+ span.setAttribute("data-ohw-editable", "text");
6260
+ span.setAttribute("data-ohw-key", `nav-${index}-label`);
6261
+ span.textContent = label;
6262
+ anchor.appendChild(span);
6263
+ return anchor;
6264
+ }
6265
+ function insertNavLinkInContainer(container, anchor, afterHrefKey) {
6266
+ if (!afterHrefKey) {
6267
+ container.appendChild(anchor);
6268
+ return;
6269
+ }
6270
+ const afterEl = findCounterpartByHrefKey(afterHrefKey, container);
6271
+ if (afterEl?.parentElement === container) {
6272
+ afterEl.insertAdjacentElement("afterend", anchor);
6273
+ return;
6274
+ }
6275
+ container.appendChild(anchor);
6276
+ }
6277
+ function insertNavbarItemDom(index, href, label, afterAnchor) {
6278
+ const afterHrefKey = afterAnchor?.getAttribute("data-ohw-href-key") ?? null;
6279
+ const desktopItems = getNavbarDesktopContainer()?.querySelectorAll("[data-ohw-href-key]") ?? [];
6280
+ const drawerItems = getNavbarDrawerContainer()?.querySelectorAll("[data-ohw-href-key]") ?? [];
6281
+ const desktopTemplate = Array.from(desktopItems).find(isNavbarLinkItem) ?? null;
6282
+ const drawerTemplate = Array.from(drawerItems).find(isNavbarLinkItem) ?? null;
6283
+ let primary = null;
6284
+ const desktopContainer = getNavbarDesktopContainer();
6285
+ if (desktopContainer) {
6286
+ const desktopAnchor = buildNavLink(index, href, label, desktopTemplate);
6287
+ insertNavLinkInContainer(desktopContainer, desktopAnchor, afterHrefKey);
6288
+ primary = desktopAnchor;
6289
+ }
6290
+ const drawerContainer = getNavbarDrawerContainer();
6291
+ if (drawerContainer) {
6292
+ const drawerAnchor = buildNavLink(index, href, label, drawerTemplate);
6293
+ insertNavLinkInContainer(drawerContainer, drawerAnchor, afterHrefKey);
6294
+ if (!primary) primary = drawerAnchor;
6295
+ }
6296
+ if (!primary) {
6297
+ const fallback = buildNavLink(index, href, label, listNavbarItems()[0] ?? null);
6298
+ const nav = document.querySelector("nav");
6299
+ nav?.appendChild(fallback);
6300
+ primary = fallback;
6301
+ }
6302
+ return primary;
6303
+ }
6304
+ function applyNavOrderToContainer(container, order) {
6305
+ const seen = /* @__PURE__ */ new Set();
6306
+ for (const hrefKey of order) {
6307
+ if (seen.has(hrefKey)) continue;
6308
+ seen.add(hrefKey);
6309
+ const el = findCounterpartByHrefKey(hrefKey, container);
6310
+ if (el) container.appendChild(el);
6311
+ }
6312
+ for (const el of container.querySelectorAll("[data-ohw-href-key]")) {
6313
+ if (!isNavbarLinkItem(el)) continue;
6314
+ const key = el.getAttribute("data-ohw-href-key");
6315
+ if (!key || seen.has(key)) continue;
6316
+ container.appendChild(el);
6317
+ }
6318
+ }
6319
+ function applyNavOrder(order) {
6320
+ const desktop = getNavbarDesktopContainer();
6321
+ if (desktop) applyNavOrderToContainer(desktop, order);
6322
+ const drawer = getNavbarDrawerContainer();
6323
+ if (drawer) applyNavOrderToContainer(drawer, order);
6324
+ }
6325
+ function collectNavbarIndicesFromContent(content) {
6326
+ const indices = /* @__PURE__ */ new Set();
6327
+ for (const key of Object.keys(content)) {
6328
+ const idx = parseNavIndexFromKey(key);
6329
+ if (idx !== null) indices.add(idx);
6330
+ }
6331
+ const countRaw = content[NAV_COUNT_KEY];
6332
+ if (countRaw) {
6333
+ const count = parseInt(countRaw, 10);
6334
+ if (Number.isFinite(count) && count > 0) {
6335
+ for (let i = 0; i < count; i++) indices.add(i);
6336
+ }
6337
+ }
6338
+ return [...indices].sort((a, b) => a - b);
6339
+ }
6340
+ function navbarIndexExistsInDom(index) {
6341
+ return document.querySelector(`[data-ohw-href-key="nav-${index}-href"]`) !== null;
6342
+ }
6343
+ function reconcileNavbarItemsFromContent(content) {
6344
+ const indices = collectNavbarIndicesFromContent(content);
6345
+ for (const index of indices) {
6346
+ if (navbarIndexExistsInDom(index)) continue;
6347
+ const href = content[`nav-${index}-href`];
6348
+ const label = content[`nav-${index}-label`];
6349
+ if (!href && !label) continue;
6350
+ insertNavbarItemDom(index, href ?? "/", label ?? "Untitled", null);
6351
+ }
6352
+ const order = parseNavOrder(content);
6353
+ if (order?.length) applyNavOrder(order);
6354
+ }
6355
+ function buildNavOrderAfterInsert(afterAnchor, newHrefKey) {
6356
+ const order = getNavOrderFromDom();
6357
+ if (!afterAnchor) {
6358
+ if (!order.includes(newHrefKey)) order.push(newHrefKey);
6359
+ return order;
6360
+ }
6361
+ const afterKey = afterAnchor.getAttribute("data-ohw-href-key");
6362
+ if (!afterKey) {
6363
+ if (!order.includes(newHrefKey)) order.push(newHrefKey);
6364
+ return order;
6365
+ }
6366
+ const withoutNew = order.filter((key) => key !== newHrefKey);
6367
+ const pos = withoutNew.indexOf(afterKey);
6368
+ if (pos >= 0) {
6369
+ withoutNew.splice(pos + 1, 0, newHrefKey);
6370
+ return withoutNew;
6371
+ }
6372
+ withoutNew.push(newHrefKey);
6373
+ return withoutNew;
6374
+ }
6375
+ function insertNavbarItem(href, label, afterAnchor = null) {
6376
+ const index = getNextNavbarIndex();
6377
+ const anchor = insertNavbarItemDom(index, href, label, afterAnchor);
6378
+ if (!anchor) throw new Error("Failed to insert navbar item");
6379
+ const hrefKey = `nav-${index}-href`;
6380
+ const order = buildNavOrderAfterInsert(afterAnchor, hrefKey);
6381
+ applyNavOrder(order);
6382
+ return {
6383
+ anchor,
6384
+ index,
6385
+ hrefKey,
6386
+ labelKey: `nav-${index}-label`,
6387
+ href,
6388
+ label,
6389
+ order
6390
+ };
6391
+ }
6392
+
6393
+ // src/ui/navbar-container-chrome.tsx
6394
+ import { Plus as Plus2 } from "lucide-react";
6395
+ import { jsx as jsx21 } from "react/jsx-runtime";
6396
+ function NavbarContainerChrome({
6397
+ rect,
6398
+ onAdd
6399
+ }) {
6400
+ const chromeGap = 6;
6401
+ return /* @__PURE__ */ jsx21(
6402
+ "div",
6403
+ {
6404
+ "data-ohw-navbar-container-chrome": "",
6405
+ "data-ohw-bridge": "",
6406
+ className: "pointer-events-none fixed z-[2147483647]",
6407
+ style: {
6408
+ top: rect.top - chromeGap,
6409
+ left: rect.left - chromeGap,
6410
+ width: rect.width + chromeGap * 2,
6411
+ height: rect.height + chromeGap * 2
6412
+ },
6413
+ children: /* @__PURE__ */ jsx21(
6414
+ "button",
6415
+ {
6416
+ type: "button",
6417
+ "data-ohw-navbar-add-button": "",
6418
+ 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",
6419
+ style: { top: "70%" },
6420
+ "aria-label": "Add item",
6421
+ onMouseDown: (e) => {
6422
+ e.preventDefault();
6423
+ e.stopPropagation();
6424
+ },
6425
+ onClick: (e) => {
6426
+ e.preventDefault();
6427
+ e.stopPropagation();
6428
+ onAdd();
6429
+ },
6430
+ children: /* @__PURE__ */ jsx21(Plus2, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
6431
+ }
6432
+ )
6433
+ }
6434
+ );
6435
+ }
6436
+
5550
6437
  // src/ui/badge.tsx
5551
- import { jsx as jsx20 } from "react/jsx-runtime";
6438
+ import { jsx as jsx22 } from "react/jsx-runtime";
5552
6439
  var badgeVariants = cva(
5553
6440
  "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",
5554
6441
  {
@@ -5566,12 +6453,12 @@ var badgeVariants = cva(
5566
6453
  }
5567
6454
  );
5568
6455
  function Badge({ className, variant, ...props }) {
5569
- return /* @__PURE__ */ jsx20("div", { className: cn(badgeVariants({ variant }), className), ...props });
6456
+ return /* @__PURE__ */ jsx22("div", { className: cn(badgeVariants({ variant }), className), ...props });
5570
6457
  }
5571
6458
 
5572
6459
  // src/OhhwellsBridge.tsx
5573
6460
  import { Link as Link2 } from "lucide-react";
5574
- import { Fragment as Fragment3, jsx as jsx21, jsxs as jsxs11 } from "react/jsx-runtime";
6461
+ import { Fragment as Fragment5, jsx as jsx23, jsxs as jsxs13 } from "react/jsx-runtime";
5575
6462
  var PRIMARY2 = "#0885FE";
5576
6463
  var IMAGE_FADE_MS = 300;
5577
6464
  function runOpacityFade(el, onDone) {
@@ -5750,7 +6637,7 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
5750
6637
  const root = createRoot(container);
5751
6638
  flushSync(() => {
5752
6639
  root.render(
5753
- /* @__PURE__ */ jsx21(
6640
+ /* @__PURE__ */ jsx23(
5754
6641
  SchedulingWidget,
5755
6642
  {
5756
6643
  notifyOnConnect,
@@ -5790,7 +6677,7 @@ function mountSchedulingEntries(entries, notifyOnConnect = false) {
5790
6677
  }
5791
6678
  }
5792
6679
  }
5793
- function getLinkHref(el) {
6680
+ function getLinkHref2(el) {
5794
6681
  const anchor = el instanceof HTMLAnchorElement ? el : el.querySelector("a");
5795
6682
  return anchor?.getAttribute("href") ?? "";
5796
6683
  }
@@ -5823,8 +6710,11 @@ function collectEditableNodes() {
5823
6710
  const url = raw.replace(/^url\(['"]?/, "").replace(/['"]?\)$/, "");
5824
6711
  return { key: el.dataset.ohwKey ?? "", type: "bg-image", text: url };
5825
6712
  }
6713
+ if (el.dataset.ohwEditable === "video") {
6714
+ return { key: el.dataset.ohwKey ?? "", type: "video", text: getVideoEl(el)?.src ?? "" };
6715
+ }
5826
6716
  if (el.dataset.ohwEditable === "link") {
5827
- return { key: el.dataset.ohwKey ?? "", type: "link", text: getLinkHref(el) };
6717
+ return { key: el.dataset.ohwKey ?? "", type: "link", text: getLinkHref2(el) };
5828
6718
  }
5829
6719
  return {
5830
6720
  key: el.dataset.ohwKey ?? "",
@@ -5835,9 +6725,79 @@ function collectEditableNodes() {
5835
6725
  document.querySelectorAll("[data-ohw-href-key]").forEach((el) => {
5836
6726
  const key = el.getAttribute("data-ohw-href-key") ?? "";
5837
6727
  if (!key) return;
5838
- nodes.push({ key, type: "link", text: getLinkHref(el) });
6728
+ nodes.push({ key, type: "link", text: getLinkHref2(el) });
6729
+ });
6730
+ document.querySelectorAll('[data-ohw-editable="video"]').forEach((el) => {
6731
+ const key = el.dataset.ohwKey ?? "";
6732
+ const video = getVideoEl(el);
6733
+ if (!key || !video) return;
6734
+ nodes.push({ key: `${key}${VIDEO_AUTOPLAY_SUFFIX}`, type: "video-setting", text: String(video.autoplay) });
6735
+ nodes.push({ key: `${key}${VIDEO_MUTED_SUFFIX}`, type: "video-setting", text: String(video.muted) });
6736
+ });
6737
+ return nodes;
6738
+ }
6739
+ function isMediaEditable(el) {
6740
+ const t = el.dataset.ohwEditable;
6741
+ return t === "image" || t === "bg-image" || t === "video";
6742
+ }
6743
+ var MEDIA_SELECTOR = '[data-ohw-editable="image"], [data-ohw-editable="bg-image"], [data-ohw-editable="video"]';
6744
+ var NON_MEDIA_SELECTOR = '[data-ohw-editable]:not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"]):not([data-ohw-editable="video"])';
6745
+ function getVideoEl(el) {
6746
+ return el instanceof HTMLVideoElement ? el : el.querySelector("video");
6747
+ }
6748
+ var VIDEO_AUTOPLAY_SUFFIX = "__ohw_autoplay";
6749
+ var VIDEO_MUTED_SUFFIX = "__ohw_muted";
6750
+ function isBackgroundVideo(video) {
6751
+ return video.autoplay && video.muted;
6752
+ }
6753
+ function syncVideoPlayback(video) {
6754
+ const background = isBackgroundVideo(video);
6755
+ video.controls = !background;
6756
+ if (video.autoplay) void video.play().catch(() => {
6757
+ });
6758
+ else video.pause();
6759
+ }
6760
+ function lockVideoBox(video) {
6761
+ const { width, height } = video.getBoundingClientRect();
6762
+ if (width > 0 && height > 0 && !video.style.aspectRatio) {
6763
+ video.style.aspectRatio = `${width} / ${height}`;
6764
+ }
6765
+ if (getComputedStyle(video).objectFit === "fill") {
6766
+ video.style.objectFit = "contain";
6767
+ }
6768
+ const fit = video.style.objectFit || getComputedStyle(video).objectFit;
6769
+ const bg = getComputedStyle(video).backgroundColor;
6770
+ const isTransparent = bg === "rgba(0, 0, 0, 0)" || bg === "transparent" || !bg;
6771
+ if (fit === "contain" && isTransparent) {
6772
+ video.style.backgroundColor = "var(--color-dark, #000)";
6773
+ }
6774
+ }
6775
+ function applyVideoSrc(video, url) {
6776
+ const { autoplay, muted } = video;
6777
+ lockVideoBox(video);
6778
+ video.src = url;
6779
+ video.loop = true;
6780
+ video.playsInline = true;
6781
+ video.autoplay = autoplay;
6782
+ video.muted = muted;
6783
+ video.load();
6784
+ syncVideoPlayback(video);
6785
+ }
6786
+ function applyVideoSettingNode(key, val) {
6787
+ const isAutoplay = key.endsWith(VIDEO_AUTOPLAY_SUFFIX);
6788
+ const isMuted = key.endsWith(VIDEO_MUTED_SUFFIX);
6789
+ if (!isAutoplay && !isMuted) return false;
6790
+ const suffixLen = (isAutoplay ? VIDEO_AUTOPLAY_SUFFIX : VIDEO_MUTED_SUFFIX).length;
6791
+ const baseKey = key.slice(0, key.length - suffixLen);
6792
+ const on = val === "true";
6793
+ document.querySelectorAll(`[data-ohw-key="${baseKey}"]`).forEach((el) => {
6794
+ const video = getVideoEl(el);
6795
+ if (!video) return;
6796
+ if (isAutoplay) video.autoplay = on;
6797
+ else video.muted = on;
6798
+ syncVideoPlayback(video);
5839
6799
  });
5840
- return nodes;
6800
+ return true;
5841
6801
  }
5842
6802
  function applyLinkByKey(key, val) {
5843
6803
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -5851,7 +6811,7 @@ function applyLinkByKey(key, val) {
5851
6811
  }
5852
6812
  function isInsideLinkEditor(target) {
5853
6813
  return Boolean(
5854
- 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"]')
6814
+ 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"]')
5855
6815
  );
5856
6816
  }
5857
6817
  function getHrefKeyFromElement(el) {
@@ -5862,7 +6822,7 @@ function getHrefKeyFromElement(el) {
5862
6822
  if (!key) return null;
5863
6823
  return { anchor, key };
5864
6824
  }
5865
- function isNavbarButton(el) {
6825
+ function isNavbarButton2(el) {
5866
6826
  return Boolean(el.closest('[data-ohw-role="navbar-button"]'));
5867
6827
  }
5868
6828
  function getNavigationItemAnchor(el) {
@@ -5874,19 +6834,8 @@ function getNavigationItemAnchor(el) {
5874
6834
  function isNavigationItem(el) {
5875
6835
  return getNavigationItemAnchor(el) !== null;
5876
6836
  }
5877
- function getNavigationItemAddHintTarget(anchor) {
5878
- const items = listNavigationItems();
5879
- const idx = items.indexOf(anchor);
5880
- if (idx >= 0 && idx < items.length - 1) return items[idx + 1];
5881
- return anchor;
5882
- }
5883
- function listNavigationItems() {
5884
- return Array.from(
5885
- document.querySelectorAll("nav [data-ohw-href-key], footer [data-ohw-href-key]")
5886
- ).filter((el) => isNavigationItem(el));
5887
- }
5888
6837
  function getNavigationItemReorderState(anchor) {
5889
- if (isNavbarButton(anchor)) return { key: null, disabled: false };
6838
+ if (isNavbarButton2(anchor)) return { key: null, disabled: false };
5890
6839
  return getReorderHandleStateFromAnchor(anchor);
5891
6840
  }
5892
6841
  function getReorderHandleState(el) {
@@ -5908,6 +6857,120 @@ function clearHrefKeyHover(anchor) {
5908
6857
  function isInsideNavigationItem(el) {
5909
6858
  return getNavigationItemAnchor(el) !== null;
5910
6859
  }
6860
+ function getNavigationRoot(el) {
6861
+ const explicit = el.closest("[data-ohw-nav-root]");
6862
+ if (explicit) return explicit;
6863
+ return el.closest("nav, footer, aside");
6864
+ }
6865
+ function findFooterItemGroup(item) {
6866
+ const footer = item.closest("footer");
6867
+ if (!footer) return null;
6868
+ let node = item.parentElement;
6869
+ while (node && node !== footer) {
6870
+ const hasDirectNavItems = Array.from(
6871
+ node.querySelectorAll(":scope > [data-ohw-href-key]")
6872
+ ).some(isNavigationItem);
6873
+ if (hasDirectNavItems) return node;
6874
+ node = node.parentElement;
6875
+ }
6876
+ return null;
6877
+ }
6878
+ function isNavigationRoot(el) {
6879
+ return el.hasAttribute("data-ohw-nav-root") || el.matches("nav, footer, aside");
6880
+ }
6881
+ function isInferredFooterGroup(el) {
6882
+ const footer = el.closest("footer");
6883
+ if (!footer || el === footer) return false;
6884
+ return Array.from(el.querySelectorAll(":scope > [data-ohw-href-key]")).some(isNavigationItem);
6885
+ }
6886
+ function isNavigationContainer(el) {
6887
+ return el.hasAttribute("data-ohw-nav-container") || isNavigationRoot(el) || isInferredFooterGroup(el);
6888
+ }
6889
+ function isPointOverNavigation(x, y) {
6890
+ const navRoot = document.querySelector("[data-ohw-nav-root]") ?? document.querySelector("nav");
6891
+ if (!navRoot) return false;
6892
+ const r2 = navRoot.getBoundingClientRect();
6893
+ return x >= r2.left && x <= r2.right && y >= r2.top && y <= r2.bottom;
6894
+ }
6895
+ function isPointOverNavItem(container, x, y) {
6896
+ return Array.from(container.querySelectorAll("[data-ohw-href-key]")).some((el) => {
6897
+ if (!isNavigationItem(el) || isNavbarButton2(el)) return false;
6898
+ const itemRect = el.getBoundingClientRect();
6899
+ return x >= itemRect.left && x <= itemRect.right && y >= itemRect.top && y <= itemRect.bottom;
6900
+ });
6901
+ }
6902
+ function resolveNavContainerSelectionTarget(target, clientX, clientY) {
6903
+ if (getNavigationItemAnchor(target)) return null;
6904
+ if (target.closest('[data-ohw-role="navbar-button"]')) return null;
6905
+ const plainLink = target.closest("a");
6906
+ if (plainLink && !plainLink.hasAttribute("data-ohw-href-key") && !plainLink.closest("[data-ohw-nav-container]")) {
6907
+ return null;
6908
+ }
6909
+ const fromClosest = target.closest("[data-ohw-nav-container]");
6910
+ if (fromClosest && !isPointOverNavItem(fromClosest, clientX, clientY)) {
6911
+ return fromClosest;
6912
+ }
6913
+ const navRoot = target.closest("[data-ohw-nav-root]");
6914
+ if (!navRoot) return null;
6915
+ const container = navRoot.querySelector("[data-ohw-nav-container]");
6916
+ if (!container || isPointOverNavItem(container, clientX, clientY)) return null;
6917
+ if (target === navRoot || target.hasAttribute("data-ohw-nav-root")) {
6918
+ return container;
6919
+ }
6920
+ return null;
6921
+ }
6922
+ function getNavigationSelectionParent(el) {
6923
+ if (isNavigationItem(el)) {
6924
+ const explicit = el.closest("[data-ohw-nav-container]");
6925
+ if (explicit) return explicit;
6926
+ const footerGroup = findFooterItemGroup(el);
6927
+ if (footerGroup) return footerGroup;
6928
+ return getNavigationRoot(el);
6929
+ }
6930
+ if (el.hasAttribute("data-ohw-nav-container") || isInferredFooterGroup(el)) {
6931
+ return getNavigationRoot(el);
6932
+ }
6933
+ return null;
6934
+ }
6935
+ function placeCaretAtPoint(el, x, y) {
6936
+ const selection = window.getSelection();
6937
+ if (!selection) return;
6938
+ if (typeof document.caretRangeFromPoint === "function") {
6939
+ const range = document.caretRangeFromPoint(x, y);
6940
+ if (range && el.contains(range.startContainer)) {
6941
+ selection.removeAllRanges();
6942
+ selection.addRange(range);
6943
+ return;
6944
+ }
6945
+ }
6946
+ const caretPositionFromPoint = document.caretPositionFromPoint;
6947
+ if (typeof caretPositionFromPoint === "function") {
6948
+ const position = caretPositionFromPoint(x, y);
6949
+ if (position && el.contains(position.offsetNode)) {
6950
+ const range = document.createRange();
6951
+ range.setStart(position.offsetNode, position.offset);
6952
+ range.collapse(true);
6953
+ selection.removeAllRanges();
6954
+ selection.addRange(range);
6955
+ }
6956
+ }
6957
+ }
6958
+ function selectAllTextInEditable(el) {
6959
+ const selection = window.getSelection();
6960
+ if (!selection) return;
6961
+ const range = document.createRange();
6962
+ range.selectNodeContents(el);
6963
+ selection.removeAllRanges();
6964
+ selection.addRange(range);
6965
+ }
6966
+ function getNavigationLabelEditable(target) {
6967
+ const editable = target.closest('[data-ohw-editable="text"]');
6968
+ if (!editable) return null;
6969
+ const hrefCtx = getHrefKeyFromElement(editable);
6970
+ const navAnchor = hrefCtx ? getNavigationItemAnchor(hrefCtx.anchor) : null;
6971
+ if (!navAnchor) return null;
6972
+ return { editable, navAnchor };
6973
+ }
5911
6974
  function collectSections() {
5912
6975
  return collectSectionsFromDom();
5913
6976
  }
@@ -6031,6 +7094,8 @@ var ICONS = {
6031
7094
  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"/>',
6032
7095
  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"/>'
6033
7096
  };
7097
+ var TOOLBAR_PILL_PADDING = 2;
7098
+ var TOOLBAR_TOGGLE_GAP = 4;
6034
7099
  var TOOLBAR_GROUPS = [
6035
7100
  [
6036
7101
  { cmd: "bold", title: "Bold" },
@@ -6055,7 +7120,7 @@ function EditGlowChrome({
6055
7120
  dragDisabled = false
6056
7121
  }) {
6057
7122
  const GAP = 6;
6058
- return /* @__PURE__ */ jsxs11(
7123
+ return /* @__PURE__ */ jsxs13(
6059
7124
  "div",
6060
7125
  {
6061
7126
  ref: elRef,
@@ -6070,7 +7135,7 @@ function EditGlowChrome({
6070
7135
  zIndex: 2147483646
6071
7136
  },
6072
7137
  children: [
6073
- /* @__PURE__ */ jsx21(
7138
+ /* @__PURE__ */ jsx23(
6074
7139
  "div",
6075
7140
  {
6076
7141
  style: {
@@ -6083,7 +7148,7 @@ function EditGlowChrome({
6083
7148
  }
6084
7149
  }
6085
7150
  ),
6086
- reorderHrefKey && /* @__PURE__ */ jsx21(
7151
+ reorderHrefKey && /* @__PURE__ */ jsx23(
6087
7152
  "div",
6088
7153
  {
6089
7154
  "data-ohw-drag-handle-container": "",
@@ -6095,7 +7160,7 @@ function EditGlowChrome({
6095
7160
  transform: "translate(calc(-100% - 7px), -50%)",
6096
7161
  pointerEvents: dragDisabled ? "none" : "auto"
6097
7162
  },
6098
- children: /* @__PURE__ */ jsx21(
7163
+ children: /* @__PURE__ */ jsx23(
6099
7164
  DragHandle,
6100
7165
  {
6101
7166
  "aria-label": `Reorder ${reorderHrefKey}`,
@@ -6199,7 +7264,7 @@ function FloatingToolbar({
6199
7264
  onEditLink
6200
7265
  }) {
6201
7266
  const { top, left, transform } = calcToolbarPos(rect, parentScroll, 330);
6202
- return /* @__PURE__ */ jsx21(
7267
+ return /* @__PURE__ */ jsx23(
6203
7268
  "div",
6204
7269
  {
6205
7270
  ref: elRef,
@@ -6209,14 +7274,23 @@ function FloatingToolbar({
6209
7274
  left,
6210
7275
  transform,
6211
7276
  zIndex: 2147483647,
7277
+ background: "#fff",
7278
+ border: "1px solid #E7E5E4",
7279
+ borderRadius: 6,
7280
+ boxShadow: "0px 2px 4px -2px rgba(0,0,0,.1),0px 4px 6px -1px rgba(0,0,0,.1)",
7281
+ display: "flex",
7282
+ alignItems: "center",
7283
+ padding: TOOLBAR_PILL_PADDING,
7284
+ gap: TOOLBAR_TOGGLE_GAP,
7285
+ fontFamily: "sans-serif",
6212
7286
  pointerEvents: "auto"
6213
7287
  },
6214
- children: /* @__PURE__ */ jsxs11(CustomToolbar, { children: [
6215
- TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ jsxs11(React8.Fragment, { children: [
6216
- gi > 0 && /* @__PURE__ */ jsx21(CustomToolbarDivider, {}),
7288
+ children: /* @__PURE__ */ jsxs13(CustomToolbar, { children: [
7289
+ TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ jsxs13(React9.Fragment, { children: [
7290
+ gi > 0 && /* @__PURE__ */ jsx23(CustomToolbarDivider, {}),
6217
7291
  btns.map((btn) => {
6218
7292
  const isActive = activeCommands.has(btn.cmd);
6219
- return /* @__PURE__ */ jsx21(
7293
+ return /* @__PURE__ */ jsx23(
6220
7294
  CustomToolbarButton,
6221
7295
  {
6222
7296
  title: btn.title,
@@ -6225,7 +7299,7 @@ function FloatingToolbar({
6225
7299
  e.preventDefault();
6226
7300
  onCommand(btn.cmd);
6227
7301
  },
6228
- children: /* @__PURE__ */ jsx21(
7302
+ children: /* @__PURE__ */ jsx23(
6229
7303
  "svg",
6230
7304
  {
6231
7305
  width: "16",
@@ -6246,7 +7320,7 @@ function FloatingToolbar({
6246
7320
  );
6247
7321
  })
6248
7322
  ] }, gi)),
6249
- showEditLink ? /* @__PURE__ */ jsx21(
7323
+ showEditLink ? /* @__PURE__ */ jsx23(
6250
7324
  CustomToolbarButton,
6251
7325
  {
6252
7326
  type: "button",
@@ -6260,7 +7334,7 @@ function FloatingToolbar({
6260
7334
  e.preventDefault();
6261
7335
  e.stopPropagation();
6262
7336
  },
6263
- children: /* @__PURE__ */ jsx21(Link2, { className: "size-4 shrink-0", "aria-hidden": true })
7337
+ children: /* @__PURE__ */ jsx23(Link2, { className: "size-4 shrink-0", "aria-hidden": true })
6264
7338
  }
6265
7339
  ) : null
6266
7340
  ] })
@@ -6277,7 +7351,7 @@ function StateToggle({
6277
7351
  states,
6278
7352
  onStateChange
6279
7353
  }) {
6280
- return /* @__PURE__ */ jsx21(
7354
+ return /* @__PURE__ */ jsx23(
6281
7355
  ToggleGroup,
6282
7356
  {
6283
7357
  "data-ohw-state-toggle": "",
@@ -6291,7 +7365,7 @@ function StateToggle({
6291
7365
  left: rect.right - 8,
6292
7366
  transform: "translateX(-100%)"
6293
7367
  },
6294
- children: states.map((state) => /* @__PURE__ */ jsx21(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
7368
+ children: states.map((state) => /* @__PURE__ */ jsx23(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
6295
7369
  }
6296
7370
  );
6297
7371
  }
@@ -6314,12 +7388,12 @@ function resolveSubdomain(subdomainFromQuery) {
6314
7388
  return "";
6315
7389
  }
6316
7390
  function OhhwellsBridge() {
6317
- const pathname = usePathname();
6318
- const router = useRouter();
7391
+ const pathname = usePathname2();
7392
+ const router = useRouter2();
6319
7393
  const searchParams = useSearchParams();
6320
7394
  const isEditMode = isEditSessionActive();
6321
- const [bridgeRoot, setBridgeRoot] = useState5(null);
6322
- useEffect3(() => {
7395
+ const [bridgeRoot, setBridgeRoot] = useState6(null);
7396
+ useEffect7(() => {
6323
7397
  const figtreeFontId = "ohw-figtree-font";
6324
7398
  if (!document.getElementById(figtreeFontId)) {
6325
7399
  const preconnect1 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.googleapis.com" });
@@ -6327,7 +7401,10 @@ function OhhwellsBridge() {
6327
7401
  const stylesheet = Object.assign(document.createElement("link"), {
6328
7402
  id: figtreeFontId,
6329
7403
  rel: "stylesheet",
6330
- href: "https://fonts.googleapis.com/css2?family=Figtree:ital,wght@0,300..900;1,300..900&display=swap"
7404
+ // Inter is loaded alongside Figtree for the media overlay's Replace button. The bridge
7405
+ // renders inside the customer's document, so it cannot assume any font is present — an
7406
+ // unloaded family would silently fall back to whatever the template happens to use.
7407
+ href: "https://fonts.googleapis.com/css2?family=Figtree:ital,wght@0,300..900;1,300..900&family=Inter:wght@400;500;600&display=swap"
6331
7408
  });
6332
7409
  document.head.append(preconnect1, preconnect2, stylesheet);
6333
7410
  }
@@ -6344,80 +7421,91 @@ function OhhwellsBridge() {
6344
7421
  const subdomainFromQuery = searchParams.get("subdomain");
6345
7422
  const subdomain = resolveSubdomain(subdomainFromQuery);
6346
7423
  useLinkHrefGuardian(pathname, subdomain, isEditMode);
6347
- const postToParent = useCallback2((data) => {
7424
+ const postToParent2 = useCallback4((data) => {
6348
7425
  if (typeof window !== "undefined" && window.parent !== window) {
6349
7426
  window.parent.postMessage(data, "*");
6350
7427
  }
6351
7428
  }, []);
6352
- const [fetchState, setFetchState] = useState5("idle");
6353
- const autoSaveTimers = useRef3(/* @__PURE__ */ new Map());
6354
- const activeElRef = useRef3(null);
6355
- const selectedElRef = useRef3(null);
6356
- const originalContentRef = useRef3(null);
6357
- const activeStateElRef = useRef3(null);
6358
- const parentScrollRef = useRef3(null);
6359
- const visibleViewportRef = useRef3(null);
6360
- const [dialogPortalContainer, setDialogPortalContainer] = useState5(null);
6361
- const attachVisibleViewport = useCallback2((node) => {
7429
+ const [fetchState, setFetchState] = useState6("idle");
7430
+ const autoSaveTimers = useRef4(/* @__PURE__ */ new Map());
7431
+ const activeElRef = useRef4(null);
7432
+ const selectedElRef = useRef4(null);
7433
+ const originalContentRef = useRef4(null);
7434
+ const activeStateElRef = useRef4(null);
7435
+ const parentScrollRef = useRef4(null);
7436
+ const visibleViewportRef = useRef4(null);
7437
+ const [dialogPortalContainer, setDialogPortalContainer] = useState6(null);
7438
+ const attachVisibleViewport = useCallback4((node) => {
6362
7439
  visibleViewportRef.current = node;
6363
7440
  setDialogPortalContainer(node);
6364
7441
  if (node) applyVisibleViewport(node, parentScrollRef.current);
6365
7442
  }, []);
6366
- const toolbarElRef = useRef3(null);
6367
- const glowElRef = useRef3(null);
6368
- const hoveredImageRef = useRef3(null);
6369
- const hoveredImageHasTextOverlapRef = useRef3(false);
6370
- const hoveredGapRef = useRef3(null);
6371
- const imageUnhoverTimerRef = useRef3(null);
6372
- const imageShowTimerRef = useRef3(null);
6373
- const editStylesRef = useRef3(null);
6374
- const activateRef = useRef3(() => {
7443
+ const toolbarElRef = useRef4(null);
7444
+ const glowElRef = useRef4(null);
7445
+ const hoveredImageRef = useRef4(null);
7446
+ const hoveredImageHasTextOverlapRef = useRef4(false);
7447
+ const dragOverElRef = useRef4(null);
7448
+ const [mediaHover, setMediaHover] = useState6(null);
7449
+ const [uploadingRects, setUploadingRects] = useState6({});
7450
+ const hoveredGapRef = useRef4(null);
7451
+ const imageUnhoverTimerRef = useRef4(null);
7452
+ const imageShowTimerRef = useRef4(null);
7453
+ const editStylesRef = useRef4(null);
7454
+ const activateRef = useRef4(() => {
7455
+ });
7456
+ const deactivateRef = useRef4(() => {
6375
7457
  });
6376
- const deactivateRef = useRef3(() => {
7458
+ const selectRef = useRef4(() => {
6377
7459
  });
6378
- const selectRef = useRef3(() => {
7460
+ const selectFrameRef = useRef4(() => {
6379
7461
  });
6380
- const deselectRef = useRef3(() => {
7462
+ const deselectRef = useRef4(() => {
6381
7463
  });
6382
- const reselectNavigationItemRef = useRef3(() => {
7464
+ const reselectNavigationItemRef = useRef4(() => {
6383
7465
  });
6384
- const commitNavigationTextEditRef = useRef3(() => {
7466
+ const commitNavigationTextEditRef = useRef4(() => {
6385
7467
  });
6386
- const refreshActiveCommandsRef = useRef3(() => {
7468
+ const refreshActiveCommandsRef = useRef4(() => {
6387
7469
  });
6388
- const postToParentRef = useRef3(postToParent);
6389
- postToParentRef.current = postToParent;
6390
- const sectionsLoadedRef = useRef3(false);
6391
- const pendingScheduleConfigRequests = useRef3([]);
6392
- const [toolbarRect, setToolbarRect] = useState5(null);
6393
- const [toolbarVariant, setToolbarVariant] = useState5("none");
6394
- const toolbarVariantRef = useRef3("none");
7470
+ const postToParentRef = useRef4(postToParent2);
7471
+ postToParentRef.current = postToParent2;
7472
+ const sectionsLoadedRef = useRef4(false);
7473
+ const pendingScheduleConfigRequests = useRef4([]);
7474
+ const [toolbarRect, setToolbarRect] = useState6(null);
7475
+ const [toolbarVariant, setToolbarVariant] = useState6("none");
7476
+ const toolbarVariantRef = useRef4("none");
6395
7477
  toolbarVariantRef.current = toolbarVariant;
6396
- const [reorderHrefKey, setReorderHrefKey] = useState5(null);
6397
- const [reorderDragDisabled, setReorderDragDisabled] = useState5(false);
6398
- const [toggleState, setToggleState] = useState5(null);
6399
- const [maxBadge, setMaxBadge] = useState5(null);
6400
- const [activeCommands, setActiveCommands] = useState5(/* @__PURE__ */ new Set());
6401
- const [sectionGap, setSectionGap] = useState5(null);
6402
- const [toolbarShowEditLink, setToolbarShowEditLink] = useState5(false);
6403
- const hoveredItemElRef = useRef3(null);
6404
- const [hoveredItemRect, setHoveredItemRect] = useState5(null);
6405
- const siblingHintElRef = useRef3(null);
6406
- const [siblingHintRect, setSiblingHintRect] = useState5(null);
6407
- const [isItemDragging, setIsItemDragging] = useState5(false);
6408
- const [linkPopover, setLinkPopover] = useState5(null);
6409
- const [sitePages, setSitePages] = useState5([]);
6410
- const [sectionsByPath, setSectionsByPath] = useState5({});
6411
- const sectionsPrefetchGenRef = useRef3(0);
6412
- const setLinkPopoverRef = useRef3(setLinkPopover);
6413
- const linkPopoverPanelRef = useRef3(null);
6414
- const linkPopoverOpenRef = useRef3(false);
6415
- const linkPopoverGraceUntilRef = useRef3(0);
7478
+ const [reorderHrefKey, setReorderHrefKey] = useState6(null);
7479
+ const [reorderDragDisabled, setReorderDragDisabled] = useState6(false);
7480
+ const [toggleState, setToggleState] = useState6(null);
7481
+ const [maxBadge, setMaxBadge] = useState6(null);
7482
+ const [activeCommands, setActiveCommands] = useState6(/* @__PURE__ */ new Set());
7483
+ const [sectionGap, setSectionGap] = useState6(null);
7484
+ const [toolbarShowEditLink, setToolbarShowEditLink] = useState6(false);
7485
+ const hoveredNavContainerRef = useRef4(null);
7486
+ const [hoveredNavContainerRect, setHoveredNavContainerRect] = useState6(null);
7487
+ const hoveredItemElRef = useRef4(null);
7488
+ const [hoveredItemRect, setHoveredItemRect] = useState6(null);
7489
+ const siblingHintElRef = useRef4(null);
7490
+ const [siblingHintRect, setSiblingHintRect] = useState6(null);
7491
+ const [isItemDragging, setIsItemDragging] = useState6(false);
7492
+ const [linkPopover, setLinkPopover] = useState6(null);
7493
+ const linkPopoverSessionRef = useRef4(null);
7494
+ const addNavAfterAnchorRef = useRef4(null);
7495
+ const editContentRef = useRef4({});
7496
+ const [sitePages, setSitePages] = useState6([]);
7497
+ const [sectionsByPath, setSectionsByPath] = useState6({});
7498
+ const sectionsPrefetchGenRef = useRef4(0);
7499
+ const setLinkPopoverRef = useRef4(setLinkPopover);
7500
+ const linkPopoverPanelRef = useRef4(null);
7501
+ const linkPopoverOpenRef = useRef4(false);
7502
+ const linkPopoverGraceUntilRef = useRef4(0);
6416
7503
  setLinkPopoverRef.current = setLinkPopover;
7504
+ linkPopoverSessionRef.current = linkPopover;
6417
7505
  const bumpLinkPopoverGrace = () => {
6418
7506
  linkPopoverGraceUntilRef.current = Date.now() + 350;
6419
7507
  };
6420
- const runSectionsPrefetch = useCallback2((pages) => {
7508
+ const runSectionsPrefetch = useCallback4((pages) => {
6421
7509
  if (!isEditMode || shouldUseDevFixtures() || pages.length === 0) return;
6422
7510
  const gen = ++sectionsPrefetchGenRef.current;
6423
7511
  const paths = pages.map((p) => p.path);
@@ -6436,9 +7524,9 @@ function OhhwellsBridge() {
6436
7524
  );
6437
7525
  });
6438
7526
  }, [isEditMode, pathname]);
6439
- const runSectionsPrefetchRef = useRef3(runSectionsPrefetch);
7527
+ const runSectionsPrefetchRef = useRef4(runSectionsPrefetch);
6440
7528
  runSectionsPrefetchRef.current = runSectionsPrefetch;
6441
- useEffect3(() => {
7529
+ useEffect7(() => {
6442
7530
  if (!linkPopover) return;
6443
7531
  if (hoveredImageRef.current) {
6444
7532
  hoveredImageRef.current = null;
@@ -6446,33 +7534,9 @@ function OhhwellsBridge() {
6446
7534
  }
6447
7535
  hoveredGapRef.current = null;
6448
7536
  setSectionGap(null);
6449
- postToParent({ type: "ow:image-unhover" });
6450
- postToParent({ type: "ow:link-modal-lock", locked: true });
6451
- const html = document.documentElement;
6452
- const body = document.body;
6453
- const prevHtmlOverflow = html.style.overflow;
6454
- const prevBodyOverflow = body.style.overflow;
6455
- html.style.overflow = "hidden";
6456
- body.style.overflow = "hidden";
6457
- const preventBackgroundScroll = (e) => {
6458
- const target = e.target;
6459
- if (target instanceof Element && target.closest('[data-ohw-link-modal-root], [data-slot="dialog-overlay"]')) {
6460
- return;
6461
- }
6462
- e.preventDefault();
6463
- };
6464
- const scrollOpts = { passive: false, capture: true };
6465
- document.addEventListener("wheel", preventBackgroundScroll, scrollOpts);
6466
- document.addEventListener("touchmove", preventBackgroundScroll, scrollOpts);
6467
- return () => {
6468
- postToParent({ type: "ow:link-modal-lock", locked: false });
6469
- html.style.overflow = prevHtmlOverflow;
6470
- body.style.overflow = prevBodyOverflow;
6471
- document.removeEventListener("wheel", preventBackgroundScroll, scrollOpts);
6472
- document.removeEventListener("touchmove", preventBackgroundScroll, scrollOpts);
6473
- };
6474
- }, [linkPopover, postToParent]);
6475
- useEffect3(() => {
7537
+ postToParent2({ type: "ow:image-unhover" });
7538
+ }, [linkPopover, postToParent2]);
7539
+ useEffect7(() => {
6476
7540
  if (!isEditMode) return;
6477
7541
  const useFixtures = shouldUseDevFixtures();
6478
7542
  if (useFixtures) {
@@ -6493,17 +7557,17 @@ function OhhwellsBridge() {
6493
7557
  runSectionsPrefetchRef.current(mapped);
6494
7558
  };
6495
7559
  window.addEventListener("message", onSitePages);
6496
- if (!useFixtures) postToParent({ type: "ow:request-site-pages" });
7560
+ if (!useFixtures) postToParent2({ type: "ow:request-site-pages" });
6497
7561
  return () => window.removeEventListener("message", onSitePages);
6498
- }, [isEditMode, postToParent]);
6499
- useEffect3(() => {
7562
+ }, [isEditMode, postToParent2]);
7563
+ useEffect7(() => {
6500
7564
  if (!isEditMode || shouldUseDevFixtures()) return;
6501
7565
  void loadAllSectionsManifest().then((manifest) => {
6502
7566
  if (Object.keys(manifest).length === 0) return;
6503
7567
  setSectionsByPath((prev) => ({ ...manifest, ...prev }));
6504
7568
  });
6505
7569
  }, [isEditMode]);
6506
- useEffect3(() => {
7570
+ useEffect7(() => {
6507
7571
  const update = () => {
6508
7572
  const el = activeElRef.current ?? selectedElRef.current;
6509
7573
  if (el) setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
@@ -6527,10 +7591,10 @@ function OhhwellsBridge() {
6527
7591
  vvp.removeEventListener("resize", update);
6528
7592
  };
6529
7593
  }, []);
6530
- const refreshStateRules = useCallback2(() => {
7594
+ const refreshStateRules = useCallback4(() => {
6531
7595
  editStylesRef.current?.forceHover && (editStylesRef.current.forceHover.textContent = collectStateRules());
6532
7596
  }, []);
6533
- const processConfigRequest = useCallback2((insertAfterVal) => {
7597
+ const processConfigRequest = useCallback4((insertAfterVal) => {
6534
7598
  const tracker = getSectionsTracker();
6535
7599
  let entries = [];
6536
7600
  try {
@@ -6553,7 +7617,7 @@ function OhhwellsBridge() {
6553
7617
  }
6554
7618
  window.postMessage({ type: "ow:schedule-config", insertAfter: insertAfterVal, scheduleId: null }, "*");
6555
7619
  }, [isEditMode]);
6556
- const deactivate = useCallback2(() => {
7620
+ const deactivate = useCallback4(() => {
6557
7621
  const el = activeElRef.current;
6558
7622
  if (!el) return;
6559
7623
  const key = el.dataset.ohwKey;
@@ -6582,21 +7646,23 @@ function OhhwellsBridge() {
6582
7646
  setMaxBadge(null);
6583
7647
  setActiveCommands(/* @__PURE__ */ new Set());
6584
7648
  setToolbarShowEditLink(false);
6585
- postToParent({ type: "ow:exit-edit" });
6586
- }, [postToParent]);
6587
- const deselect = useCallback2(() => {
7649
+ postToParent2({ type: "ow:exit-edit" });
7650
+ }, [postToParent2]);
7651
+ const deselect = useCallback4(() => {
6588
7652
  selectedElRef.current = null;
6589
7653
  setReorderHrefKey(null);
6590
7654
  setReorderDragDisabled(false);
6591
7655
  siblingHintElRef.current = null;
6592
7656
  setSiblingHintRect(null);
6593
7657
  setIsItemDragging(false);
7658
+ hoveredNavContainerRef.current = null;
7659
+ setHoveredNavContainerRect(null);
6594
7660
  if (!activeElRef.current) {
6595
7661
  setToolbarRect(null);
6596
7662
  setToolbarVariant("none");
6597
7663
  }
6598
7664
  }, []);
6599
- const reselectNavigationItem = useCallback2((navAnchor) => {
7665
+ const reselectNavigationItem = useCallback4((navAnchor) => {
6600
7666
  selectedElRef.current = navAnchor;
6601
7667
  const { key, disabled } = getNavigationItemReorderState(navAnchor);
6602
7668
  setReorderHrefKey(key);
@@ -6606,7 +7672,7 @@ function OhhwellsBridge() {
6606
7672
  setToolbarShowEditLink(false);
6607
7673
  setActiveCommands(/* @__PURE__ */ new Set());
6608
7674
  }, []);
6609
- const commitNavigationTextEdit = useCallback2((navAnchor) => {
7675
+ const commitNavigationTextEdit = useCallback4((navAnchor) => {
6610
7676
  const el = activeElRef.current;
6611
7677
  if (!el) return;
6612
7678
  const key = el.dataset.ohwKey;
@@ -6619,9 +7685,9 @@ function OhhwellsBridge() {
6619
7685
  const html = sanitizeHtml(el.innerHTML);
6620
7686
  const original = originalContentRef.current ?? "";
6621
7687
  if (html !== sanitizeHtml(original)) {
6622
- postToParent({ type: "ow:change", nodes: [{ key, text: html }] });
7688
+ postToParent2({ type: "ow:change", nodes: [{ key, text: html }] });
6623
7689
  const h = document.documentElement.scrollHeight;
6624
- if (h > 50) postToParent({ type: "ow:height", height: h });
7690
+ if (h > 50) postToParent2({ type: "ow:height", height: h });
6625
7691
  }
6626
7692
  }
6627
7693
  el.removeAttribute("contenteditable");
@@ -6629,31 +7695,38 @@ function OhhwellsBridge() {
6629
7695
  setMaxBadge(null);
6630
7696
  setActiveCommands(/* @__PURE__ */ new Set());
6631
7697
  setToolbarShowEditLink(false);
6632
- postToParent({ type: "ow:exit-edit" });
7698
+ postToParent2({ type: "ow:exit-edit" });
6633
7699
  reselectNavigationItem(navAnchor);
6634
- }, [postToParent, reselectNavigationItem]);
6635
- const handleAddNavigationItem = useCallback2(() => {
6636
- const selected = selectedElRef.current;
6637
- if (!selected) return;
6638
- const target = getNavigationItemAddHintTarget(selected);
6639
- siblingHintElRef.current = target;
6640
- setSiblingHintRect(target.getBoundingClientRect());
7700
+ }, [postToParent2, reselectNavigationItem]);
7701
+ const handleAddTopLevelNavItem = useCallback4(() => {
7702
+ const items = listNavbarItems();
7703
+ addNavAfterAnchorRef.current = items[items.length - 1] ?? null;
7704
+ deselectRef.current();
7705
+ deactivateRef.current();
7706
+ bumpLinkPopoverGrace();
7707
+ setLinkPopover({
7708
+ key: "__add-nav__",
7709
+ mode: "create",
7710
+ intent: "add-nav"
7711
+ });
6641
7712
  }, []);
6642
- const handleItemDragStart = useCallback2(() => {
7713
+ const handleItemDragStart = useCallback4(() => {
6643
7714
  siblingHintElRef.current = null;
6644
7715
  setSiblingHintRect(null);
6645
7716
  setIsItemDragging(true);
6646
7717
  }, []);
6647
- const handleItemDragEnd = useCallback2(() => {
7718
+ const handleItemDragEnd = useCallback4(() => {
6648
7719
  setIsItemDragging(false);
6649
7720
  }, []);
6650
7721
  reselectNavigationItemRef.current = reselectNavigationItem;
6651
7722
  commitNavigationTextEditRef.current = commitNavigationTextEdit;
6652
- const select = useCallback2((anchor) => {
7723
+ const select = useCallback4((anchor) => {
6653
7724
  if (!isNavigationItem(anchor)) return;
6654
7725
  if (activeElRef.current) deactivate();
6655
7726
  selectedElRef.current = anchor;
6656
7727
  clearHrefKeyHover(anchor);
7728
+ hoveredNavContainerRef.current = null;
7729
+ setHoveredNavContainerRect(null);
6657
7730
  setHoveredItemRect(null);
6658
7731
  hoveredItemElRef.current = null;
6659
7732
  siblingHintElRef.current = null;
@@ -6667,13 +7740,32 @@ function OhhwellsBridge() {
6667
7740
  setToolbarShowEditLink(false);
6668
7741
  setActiveCommands(/* @__PURE__ */ new Set());
6669
7742
  }, [deactivate]);
6670
- const activate = useCallback2((el) => {
7743
+ const selectFrame = useCallback4((el) => {
7744
+ if (!isNavigationContainer(el)) return;
7745
+ if (activeElRef.current) deactivate();
7746
+ selectedElRef.current = el;
7747
+ clearHrefKeyHover(el);
7748
+ hoveredNavContainerRef.current = null;
7749
+ setHoveredNavContainerRect(null);
7750
+ setHoveredItemRect(null);
7751
+ hoveredItemElRef.current = null;
7752
+ siblingHintElRef.current = null;
7753
+ setSiblingHintRect(null);
7754
+ setIsItemDragging(false);
7755
+ setReorderHrefKey(null);
7756
+ setReorderDragDisabled(false);
7757
+ setToolbarVariant("select-frame");
7758
+ setToolbarRect(el.getBoundingClientRect());
7759
+ setToolbarShowEditLink(false);
7760
+ setActiveCommands(/* @__PURE__ */ new Set());
7761
+ }, [deactivate]);
7762
+ const activate = useCallback4((el, options) => {
6671
7763
  if (activeElRef.current === el) return;
6672
7764
  selectedElRef.current = null;
6673
7765
  deactivate();
6674
7766
  if (hoveredImageRef.current) {
6675
7767
  hoveredImageRef.current = null;
6676
- postToParentRef.current({ type: "ow:image-unhover" });
7768
+ setMediaHover(null);
6677
7769
  }
6678
7770
  setToolbarVariant("rich-text");
6679
7771
  siblingHintElRef.current = null;
@@ -6685,6 +7777,9 @@ function OhhwellsBridge() {
6685
7777
  activeElRef.current = el;
6686
7778
  originalContentRef.current = el.innerHTML;
6687
7779
  el.focus();
7780
+ if (options?.caretX !== void 0 && options?.caretY !== void 0) {
7781
+ placeCaretAtPoint(el, options.caretX, options.caretY);
7782
+ }
6688
7783
  setToolbarShowEditLink(Boolean(getHrefKeyFromElement(el)));
6689
7784
  const navAnchor = getNavigationItemAnchor(el);
6690
7785
  if (navAnchor) {
@@ -6696,12 +7791,13 @@ function OhhwellsBridge() {
6696
7791
  setReorderDragDisabled(reorderDisabled);
6697
7792
  }
6698
7793
  setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
6699
- postToParent({ type: "ow:enter-edit", key: el.dataset.ohwKey });
7794
+ postToParent2({ type: "ow:enter-edit", key: el.dataset.ohwKey });
6700
7795
  requestAnimationFrame(() => refreshActiveCommandsRef.current());
6701
- }, [deactivate, postToParent]);
7796
+ }, [deactivate, postToParent2]);
6702
7797
  activateRef.current = activate;
6703
7798
  deactivateRef.current = deactivate;
6704
7799
  selectRef.current = select;
7800
+ selectFrameRef.current = selectFrame;
6705
7801
  deselectRef.current = deselect;
6706
7802
  useLayoutEffect2(() => {
6707
7803
  if (!subdomain || isEditMode) {
@@ -6712,6 +7808,7 @@ function OhhwellsBridge() {
6712
7808
  const imageLoads = [];
6713
7809
  for (const [key, val] of Object.entries(content)) {
6714
7810
  if (key === "__ohw_sections") continue;
7811
+ if (applyVideoSettingNode(key, val)) continue;
6715
7812
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
6716
7813
  if (el.dataset.ohwEditable === "image") {
6717
7814
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -6725,6 +7822,15 @@ function OhhwellsBridge() {
6725
7822
  } else if (el.dataset.ohwEditable === "bg-image") {
6726
7823
  const next = `url('${val}')`;
6727
7824
  if (el.style.backgroundImage !== next) el.style.backgroundImage = next;
7825
+ } else if (el.dataset.ohwEditable === "video") {
7826
+ const video = getVideoEl(el);
7827
+ if (video && video.src !== val) {
7828
+ applyVideoSrc(video, val);
7829
+ imageLoads.push(new Promise((resolve) => {
7830
+ video.onloadeddata = () => resolve();
7831
+ video.onerror = () => resolve();
7832
+ }));
7833
+ }
6728
7834
  } else if (el.dataset.ohwEditable === "link") {
6729
7835
  applyLinkHref(el, val);
6730
7836
  } else if (el.innerHTML !== val) {
@@ -6733,6 +7839,7 @@ function OhhwellsBridge() {
6733
7839
  });
6734
7840
  applyLinkByKey(key, val);
6735
7841
  }
7842
+ reconcileNavbarItemsFromContent(content);
6736
7843
  enforceLinkHrefs();
6737
7844
  initSectionsFromContent(content, true);
6738
7845
  sectionsLoadedRef.current = true;
@@ -6761,7 +7868,7 @@ function OhhwellsBridge() {
6761
7868
  cancelled = true;
6762
7869
  };
6763
7870
  }, [subdomain, isEditMode]);
6764
- useEffect3(() => {
7871
+ useEffect7(() => {
6765
7872
  if (!subdomain || isEditMode) return;
6766
7873
  let debounceTimer = null;
6767
7874
  let observer = null;
@@ -6773,6 +7880,7 @@ function OhhwellsBridge() {
6773
7880
  try {
6774
7881
  for (const [key, val] of Object.entries(content)) {
6775
7882
  if (key === "__ohw_sections") continue;
7883
+ if (applyVideoSettingNode(key, val)) continue;
6776
7884
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
6777
7885
  if (el.dataset.ohwEditable === "image") {
6778
7886
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -6780,6 +7888,9 @@ function OhhwellsBridge() {
6780
7888
  } else if (el.dataset.ohwEditable === "bg-image") {
6781
7889
  const next = `url('${val}')`;
6782
7890
  if (el.style.backgroundImage !== next) el.style.backgroundImage = next;
7891
+ } else if (el.dataset.ohwEditable === "video") {
7892
+ const video = getVideoEl(el);
7893
+ if (video && video.src !== val) applyVideoSrc(video, val);
6783
7894
  } else if (el.dataset.ohwEditable === "link") {
6784
7895
  applyLinkHref(el, val);
6785
7896
  } else if (el.innerHTML !== val) {
@@ -6788,6 +7899,7 @@ function OhhwellsBridge() {
6788
7899
  });
6789
7900
  applyLinkByKey(key, val);
6790
7901
  }
7902
+ reconcileNavbarItemsFromContent(content);
6791
7903
  } finally {
6792
7904
  observer?.observe(document.body, { childList: true, subtree: true });
6793
7905
  }
@@ -6811,20 +7923,38 @@ function OhhwellsBridge() {
6811
7923
  const visible = Boolean(subdomain) && fetchState !== "done";
6812
7924
  el.style.display = visible ? "flex" : "none";
6813
7925
  }, [subdomain, fetchState]);
6814
- useEffect3(() => {
6815
- postToParent({ type: "ow:navigation", path: pathname });
6816
- }, [pathname, postToParent]);
6817
- useEffect3(() => {
7926
+ useEffect7(() => {
7927
+ postToParent2({ type: "ow:navigation", path: pathname });
7928
+ }, [pathname, postToParent2]);
7929
+ useEffect7(() => {
6818
7930
  if (!isEditMode) return;
7931
+ if (linkPopoverSessionRef.current?.intent === "add-nav") return;
7932
+ if (document.querySelector("[data-ohw-section-picker]")) return;
6819
7933
  setLinkPopover(null);
6820
7934
  deselectRef.current();
6821
7935
  deactivateRef.current();
6822
7936
  }, [pathname, isEditMode]);
6823
- useEffect3(() => {
7937
+ useEffect7(() => {
7938
+ const contentForNav = () => {
7939
+ if (isEditMode) return editContentRef.current;
7940
+ if (!subdomain) return {};
7941
+ return contentCache.get(subdomain) ?? {};
7942
+ };
7943
+ const run = () => reconcileNavbarItemsFromContent(contentForNav());
7944
+ run();
7945
+ const nav = document.querySelector("nav");
7946
+ if (!nav) return;
7947
+ const observer = new MutationObserver(() => {
7948
+ requestAnimationFrame(run);
7949
+ });
7950
+ observer.observe(nav, { childList: true, subtree: true });
7951
+ return () => observer.disconnect();
7952
+ }, [isEditMode, pathname, subdomain, fetchState]);
7953
+ useEffect7(() => {
6824
7954
  if (!isEditMode) return;
6825
7955
  const measure = () => {
6826
7956
  const h = document.body.scrollHeight;
6827
- if (h > 50) postToParent({ type: "ow:height", height: h });
7957
+ if (h > 50) postToParent2({ type: "ow:height", height: h });
6828
7958
  };
6829
7959
  const t1 = setTimeout(measure, 50);
6830
7960
  const t2 = setTimeout(measure, 500);
@@ -6843,8 +7973,8 @@ function OhhwellsBridge() {
6843
7973
  if (resizeTimer) clearTimeout(resizeTimer);
6844
7974
  window.removeEventListener("resize", handleResize);
6845
7975
  };
6846
- }, [pathname, isEditMode, postToParent]);
6847
- useEffect3(() => {
7976
+ }, [pathname, isEditMode, postToParent2]);
7977
+ useEffect7(() => {
6848
7978
  if (!subdomainFromQuery || isEditMode) return;
6849
7979
  const handleClick = (e) => {
6850
7980
  const anchor = e.target.closest("a");
@@ -6860,7 +7990,7 @@ function OhhwellsBridge() {
6860
7990
  document.addEventListener("click", handleClick, true);
6861
7991
  return () => document.removeEventListener("click", handleClick, true);
6862
7992
  }, [subdomainFromQuery, isEditMode, router]);
6863
- useEffect3(() => {
7993
+ useEffect7(() => {
6864
7994
  if (!isEditMode) {
6865
7995
  editStylesRef.current?.base.remove();
6866
7996
  editStylesRef.current?.forceHover.remove();
@@ -6883,8 +8013,9 @@ function OhhwellsBridge() {
6883
8013
  [data-ohw-editable] {
6884
8014
  display: block;
6885
8015
  }
6886
- [data-ohw-editable]:not([contenteditable]):not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"]) { cursor: text !important; }
8016
+ [data-ohw-editable]:not([contenteditable]):not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"]):not([data-ohw-editable="video"]) { cursor: text !important; }
6887
8017
  [data-ohw-editable="image"], [data-ohw-editable="image"] *,
8018
+ [data-ohw-editable="video"], [data-ohw-editable="video"] *,
6888
8019
  [data-ohw-editable="bg-image"], [data-ohw-editable="bg-image"] * { cursor: pointer !important; }
6889
8020
  [data-ohw-editable="link"], [data-ohw-editable="link"] * { cursor: pointer !important; }
6890
8021
  [data-ohw-hovered]:not([contenteditable]):not([data-ohw-href-key]) {
@@ -6933,6 +8064,13 @@ function OhhwellsBridge() {
6933
8064
  if (target.closest("[data-ohw-max-badge]")) return;
6934
8065
  if (isInsideLinkEditor(target)) return;
6935
8066
  if (target.closest('[data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
8067
+ const navFromChrome = resolveNavContainerSelectionTarget(target, e.clientX, e.clientY);
8068
+ if (navFromChrome) {
8069
+ e.preventDefault();
8070
+ e.stopPropagation();
8071
+ selectFrameRef.current(navFromChrome);
8072
+ return;
8073
+ }
6936
8074
  e.preventDefault();
6937
8075
  e.stopPropagation();
6938
8076
  return;
@@ -6947,14 +8085,15 @@ function OhhwellsBridge() {
6947
8085
  bumpLinkPopoverGrace();
6948
8086
  setLinkPopoverRef.current({
6949
8087
  key: editable.dataset.ohwKey ?? "",
6950
- target: getLinkHref(editable)
8088
+ mode: "edit",
8089
+ target: getLinkHref2(editable)
6951
8090
  });
6952
8091
  return;
6953
8092
  }
6954
- if (editable.dataset.ohwEditable === "image" || editable.dataset.ohwEditable === "bg-image") {
8093
+ if (isMediaEditable(editable)) {
6955
8094
  e.preventDefault();
6956
8095
  e.stopPropagation();
6957
- postToParentRef.current({ type: "ow:image-pick", key: editable.dataset.ohwKey ?? "" });
8096
+ postToParentRef.current({ type: "ow:image-pick", key: editable.dataset.ohwKey ?? "", elementType: editable.dataset.ohwEditable ?? "image" });
6958
8097
  return;
6959
8098
  }
6960
8099
  const hrefCtx = getHrefKeyFromElement(editable);
@@ -6963,7 +8102,8 @@ function OhhwellsBridge() {
6963
8102
  e.preventDefault();
6964
8103
  e.stopPropagation();
6965
8104
  if (selectedElRef.current === navAnchor) {
6966
- activateRef.current(editable);
8105
+ if (e.detail >= 2) return;
8106
+ activateRef.current(editable, { caretX: e.clientX, caretY: e.clientY });
6967
8107
  return;
6968
8108
  }
6969
8109
  selectRef.current(navAnchor);
@@ -6982,6 +8122,22 @@ function OhhwellsBridge() {
6982
8122
  selectRef.current(hrefAnchor);
6983
8123
  return;
6984
8124
  }
8125
+ const navContainerToSelect = resolveNavContainerSelectionTarget(target, e.clientX, e.clientY);
8126
+ if (navContainerToSelect) {
8127
+ e.preventDefault();
8128
+ e.stopPropagation();
8129
+ selectFrameRef.current(navContainerToSelect);
8130
+ return;
8131
+ }
8132
+ const selectedContainer = selectedElRef.current;
8133
+ if (selectedContainer && isNavigationContainer(selectedContainer)) {
8134
+ const navItem = getNavigationItemAnchor(target);
8135
+ if (!navItem && (selectedContainer === target || selectedContainer.contains(target))) {
8136
+ e.preventDefault();
8137
+ e.stopPropagation();
8138
+ return;
8139
+ }
8140
+ }
6985
8141
  if (activeElRef.current) {
6986
8142
  const sel = window.getSelection();
6987
8143
  if (sel && !sel.isCollapsed) {
@@ -7007,10 +8163,48 @@ function OhhwellsBridge() {
7007
8163
  deselectRef.current();
7008
8164
  deactivateRef.current();
7009
8165
  };
8166
+ const handleDblClick = (e) => {
8167
+ const target = e.target;
8168
+ if (target.closest("[data-ohw-toolbar]")) return;
8169
+ if (target.closest("[data-ohw-state-toggle]")) return;
8170
+ if (target.closest("[data-ohw-max-badge]")) return;
8171
+ if (isInsideLinkEditor(target)) return;
8172
+ if (target.closest('[data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
8173
+ return;
8174
+ }
8175
+ const navLabel = getNavigationLabelEditable(target);
8176
+ if (!navLabel) return;
8177
+ const { editable } = navLabel;
8178
+ e.preventDefault();
8179
+ e.stopPropagation();
8180
+ if (activeElRef.current !== editable) {
8181
+ activateRef.current(editable);
8182
+ }
8183
+ requestAnimationFrame(() => {
8184
+ selectAllTextInEditable(editable);
8185
+ refreshActiveCommandsRef.current();
8186
+ });
8187
+ };
7010
8188
  const handleMouseOver = (e) => {
7011
8189
  const target = e.target;
8190
+ if (toolbarVariantRef.current !== "select-frame") {
8191
+ const navContainer = target.closest("[data-ohw-nav-container]");
8192
+ if (navContainer && !getNavigationItemAnchor(target)) {
8193
+ hoveredNavContainerRef.current = navContainer;
8194
+ setHoveredNavContainerRect(navContainer.getBoundingClientRect());
8195
+ hoveredItemElRef.current = null;
8196
+ setHoveredItemRect(null);
8197
+ return;
8198
+ }
8199
+ if (!target.closest("[data-ohw-nav-container]")) {
8200
+ hoveredNavContainerRef.current = null;
8201
+ setHoveredNavContainerRect(null);
8202
+ }
8203
+ }
7012
8204
  const navAnchor = getNavigationItemAnchor(target);
7013
8205
  if (navAnchor) {
8206
+ hoveredNavContainerRef.current = null;
8207
+ setHoveredNavContainerRect(null);
7014
8208
  const selected2 = selectedElRef.current;
7015
8209
  if (selected2 === navAnchor || selected2?.contains(navAnchor)) return;
7016
8210
  clearHrefKeyHover(navAnchor);
@@ -7022,7 +8216,7 @@ function OhhwellsBridge() {
7022
8216
  if (!editable) return;
7023
8217
  const selected = selectedElRef.current;
7024
8218
  if (selected && (selected === editable || selected.contains(editable))) return;
7025
- if (editable.dataset.ohwEditable !== "image" && editable.dataset.ohwEditable !== "bg-image" && !editable.hasAttribute("contenteditable")) {
8219
+ if (!isMediaEditable(editable) && !editable.hasAttribute("contenteditable")) {
7026
8220
  const hoverTarget = editable.closest("[data-ohw-href-key]") ?? editable;
7027
8221
  if (hoverTarget.hasAttribute("data-ohw-href-key")) {
7028
8222
  clearHrefKeyHover(hoverTarget);
@@ -7035,6 +8229,14 @@ function OhhwellsBridge() {
7035
8229
  };
7036
8230
  const handleMouseOut = (e) => {
7037
8231
  const target = e.target;
8232
+ if (target.closest("[data-ohw-nav-container]")) {
8233
+ const related2 = e.relatedTarget instanceof Element ? e.relatedTarget : null;
8234
+ if (related2?.closest("[data-ohw-nav-container], [data-ohw-navbar-container-chrome], [data-ohw-navbar-add-button]")) {
8235
+ return;
8236
+ }
8237
+ hoveredNavContainerRef.current = null;
8238
+ setHoveredNavContainerRect(null);
8239
+ }
7038
8240
  const navAnchor = getNavigationItemAnchor(target);
7039
8241
  if (navAnchor) {
7040
8242
  const related2 = e.relatedTarget instanceof Element ? e.relatedTarget : null;
@@ -7050,7 +8252,7 @@ function OhhwellsBridge() {
7050
8252
  if (!editable) return;
7051
8253
  const related = e.relatedTarget instanceof Element ? e.relatedTarget : null;
7052
8254
  if (related?.closest("[data-ohw-drag-handle-container], [data-ohw-item-interaction]")) return;
7053
- if (editable.dataset.ohwEditable !== "image" && editable.dataset.ohwEditable !== "bg-image") {
8255
+ if (!isMediaEditable(editable)) {
7054
8256
  const hoverTarget = editable.closest("[data-ohw-href-key]") ?? editable;
7055
8257
  if (hoverTarget.hasAttribute("data-ohw-href-key")) {
7056
8258
  if (!related?.closest("[data-ohw-href-key]")) {
@@ -7096,23 +8298,26 @@ function OhhwellsBridge() {
7096
8298
  }
7097
8299
  return { top, left, width: Math.max(0, right - left), height: Math.max(0, bottom - top) };
7098
8300
  };
7099
- const postImageHover = (imgEl, isDragOver = false, forceTextOverlap) => {
8301
+ const showImageHover = (imgEl, isDragOver = false, forceTextOverlap) => {
7100
8302
  const r2 = getVisibleRect(imgEl);
7101
8303
  const hasTextOverlap = forceTextOverlap ?? false;
7102
8304
  hoveredImageHasTextOverlapRef.current = hasTextOverlap;
7103
- postToParentRef.current({
7104
- type: "ow:image-hover",
8305
+ const video = imgEl.dataset.ohwEditable === "video" ? getVideoEl(imgEl) : null;
8306
+ setMediaHover({
7105
8307
  key: imgEl.dataset.ohwKey ?? "",
7106
8308
  rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height },
8309
+ elementType: imgEl.dataset.ohwEditable ?? "image",
7107
8310
  hasTextOverlap,
7108
- ...isDragOver ? { isDragOver: true } : {}
8311
+ isDragOver,
8312
+ ...video ? { videoAutoplay: video.autoplay, videoMuted: video.muted } : {}
7109
8313
  });
7110
8314
  const track = imgEl.closest("[data-ohw-hover-pause]");
7111
8315
  if (track) track.setAttribute("data-ohw-hover-paused", "");
7112
8316
  };
8317
+ const clearImageHover = () => setMediaHover(null);
7113
8318
  const findImageAtPoint = (clientX, clientY, fromParentViewport) => {
7114
8319
  const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
7115
- const images = Array.from(document.querySelectorAll('[data-ohw-editable="image"], [data-ohw-editable="bg-image"]'));
8320
+ const images = Array.from(document.querySelectorAll(MEDIA_SELECTOR));
7116
8321
  const matches = [];
7117
8322
  for (let i = images.length - 1; i >= 0; i--) {
7118
8323
  const el = images[i];
@@ -7128,7 +8333,9 @@ function OhhwellsBridge() {
7128
8333
  if (x >= r2.left && x <= r2.left + r2.width && y >= r2.top && y <= r2.top + r2.height) matches.push(el);
7129
8334
  }
7130
8335
  if (matches.length === 0) return null;
7131
- const imageTypeMatches = matches.filter((el) => el.dataset.ohwEditable === "image");
8336
+ const imageTypeMatches = matches.filter(
8337
+ (el) => el.dataset.ohwEditable === "image" || el.dataset.ohwEditable === "video"
8338
+ );
7132
8339
  const candidatePool = imageTypeMatches.length > 0 ? imageTypeMatches : matches;
7133
8340
  const smallest = candidatePool.reduce((best, el) => {
7134
8341
  const br = getVisibleRect(best);
@@ -7151,47 +8358,91 @@ function OhhwellsBridge() {
7151
8358
  }
7152
8359
  return smallest;
7153
8360
  };
8361
+ const dismissImageHover = () => {
8362
+ if (hoveredImageRef.current) {
8363
+ hoveredImageRef.current = null;
8364
+ hoveredImageHasTextOverlapRef.current = false;
8365
+ resumeAnimTracks();
8366
+ postToParentRef.current({ type: "ow:image-unhover" });
8367
+ }
8368
+ clearImageHover();
8369
+ };
8370
+ const probeNavigationHoverAt = (x, y) => {
8371
+ if (toolbarVariantRef.current === "select-frame") {
8372
+ hoveredNavContainerRef.current = null;
8373
+ setHoveredNavContainerRect(null);
8374
+ return;
8375
+ }
8376
+ const navContainer = document.querySelector("[data-ohw-nav-container]");
8377
+ if (!navContainer) {
8378
+ hoveredNavContainerRef.current = null;
8379
+ setHoveredNavContainerRect(null);
8380
+ return;
8381
+ }
8382
+ const containerRect = navContainer.getBoundingClientRect();
8383
+ const overContainer = x >= containerRect.left && x <= containerRect.right && y >= containerRect.top && y <= containerRect.bottom;
8384
+ if (!overContainer) {
8385
+ hoveredNavContainerRef.current = null;
8386
+ setHoveredNavContainerRect(null);
8387
+ return;
8388
+ }
8389
+ const navItemHit = Array.from(
8390
+ navContainer.querySelectorAll("[data-ohw-href-key]")
8391
+ ).find((el) => {
8392
+ if (!isNavigationItem(el) || isNavbarButton2(el)) return false;
8393
+ const itemRect = el.getBoundingClientRect();
8394
+ return x >= itemRect.left && x <= itemRect.right && y >= itemRect.top && y <= itemRect.bottom;
8395
+ });
8396
+ if (navItemHit) {
8397
+ hoveredNavContainerRef.current = null;
8398
+ setHoveredNavContainerRect(null);
8399
+ const selected = selectedElRef.current;
8400
+ if (selected !== navItemHit && !selected?.contains(navItemHit)) {
8401
+ clearHrefKeyHover(navItemHit);
8402
+ hoveredItemElRef.current = navItemHit;
8403
+ setHoveredItemRect(navItemHit.getBoundingClientRect());
8404
+ }
8405
+ return;
8406
+ }
8407
+ hoveredNavContainerRef.current = navContainer;
8408
+ setHoveredNavContainerRect(containerRect);
8409
+ hoveredItemElRef.current = null;
8410
+ setHoveredItemRect(null);
8411
+ };
7154
8412
  const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
7155
8413
  if (linkPopoverOpenRef.current) {
7156
- if (hoveredImageRef.current) {
7157
- hoveredImageRef.current = null;
7158
- hoveredImageHasTextOverlapRef.current = false;
7159
- resumeAnimTracks();
7160
- postToParentRef.current({ type: "ow:image-unhover" });
7161
- }
8414
+ dismissImageHover();
8415
+ return;
8416
+ }
8417
+ const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
8418
+ if (isPointOverNavigation(x, y)) {
8419
+ dismissImageHover();
8420
+ probeNavigationHoverAt(x, y);
7162
8421
  return;
7163
8422
  }
8423
+ hoveredNavContainerRef.current = null;
8424
+ setHoveredNavContainerRect(null);
7164
8425
  const toggleEl = document.querySelector("[data-ohw-state-toggle]");
7165
8426
  if (toggleEl) {
7166
- const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
7167
8427
  const tr = toggleEl.getBoundingClientRect();
7168
8428
  if (x >= tr.left && x <= tr.right && y >= tr.top && y <= tr.bottom) {
7169
- if (hoveredImageRef.current) {
7170
- hoveredImageRef.current = null;
7171
- resumeAnimTracks();
7172
- postToParentRef.current({ type: "ow:image-unhover" });
7173
- }
8429
+ dismissImageHover();
7174
8430
  return;
7175
8431
  }
7176
8432
  }
7177
- if (activeElRef.current) {
7178
- if (hoveredImageRef.current) {
7179
- hoveredImageRef.current = null;
7180
- hoveredImageHasTextOverlapRef.current = false;
7181
- resumeAnimTracks();
7182
- postToParentRef.current({ type: "ow:image-unhover" });
7183
- }
8433
+ const imgEl = findImageAtPoint(clientX, clientY, fromParentViewport);
8434
+ const activeEl = activeElRef.current;
8435
+ if (activeEl && (!imgEl || imgEl.contains(activeEl))) {
8436
+ dismissImageHover();
7184
8437
  return;
7185
8438
  }
7186
- const imgEl = findImageAtPoint(clientX, clientY, fromParentViewport);
7187
8439
  if (imgEl) {
7188
- const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
7189
8440
  const topEl = document.elementFromPoint(x, y);
7190
8441
  if (topEl?.closest('[data-ohw-toolbar], [data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
7191
8442
  if (hoveredImageRef.current) {
7192
8443
  hoveredImageRef.current = null;
7193
8444
  resumeAnimTracks();
7194
- postToParentRef.current({ type: "ow:image-unhover" });
8445
+ clearImageHover();
7195
8446
  }
7196
8447
  return;
7197
8448
  }
@@ -7200,13 +8451,13 @@ function OhhwellsBridge() {
7200
8451
  hoveredImageRef.current = null;
7201
8452
  hoveredImageHasTextOverlapRef.current = false;
7202
8453
  resumeAnimTracks();
7203
- postToParentRef.current({ type: "ow:image-unhover" });
8454
+ clearImageHover();
7204
8455
  }
7205
8456
  return;
7206
8457
  }
7207
8458
  const isStateCardImage = !!imgEl.closest("[data-ohw-editable-state]");
7208
8459
  const textEditable = Array.from(
7209
- document.querySelectorAll('[data-ohw-editable]:not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"])')
8460
+ document.querySelectorAll(NON_MEDIA_SELECTOR)
7210
8461
  ).find((el) => {
7211
8462
  const er = el.getBoundingClientRect();
7212
8463
  return x >= er.left && x <= er.right && y >= er.top && y <= er.bottom;
@@ -7225,14 +8476,14 @@ function OhhwellsBridge() {
7225
8476
  hoveredImageRef.current = null;
7226
8477
  hoveredImageHasTextOverlapRef.current = false;
7227
8478
  resumeAnimTracks();
7228
- postToParentRef.current({ type: "ow:image-unhover" });
8479
+ clearImageHover();
7229
8480
  }
7230
8481
  return;
7231
8482
  }
7232
8483
  if (isStateCardImage) {
7233
8484
  if (imgEl !== hoveredImageRef.current || !hoveredImageHasTextOverlapRef.current) {
7234
8485
  hoveredImageRef.current = imgEl;
7235
- postImageHover(imgEl, isDragOver, true);
8486
+ showImageHover(imgEl, isDragOver, true);
7236
8487
  }
7237
8488
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
7238
8489
  if (textEditable && !isInsideNavigationItem(textEditable)) {
@@ -7244,7 +8495,7 @@ function OhhwellsBridge() {
7244
8495
  hoveredImageRef.current = null;
7245
8496
  hoveredImageHasTextOverlapRef.current = false;
7246
8497
  resumeAnimTracks();
7247
- postToParentRef.current({ type: "ow:image-unhover" });
8498
+ clearImageHover();
7248
8499
  }
7249
8500
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
7250
8501
  if (textEditable && !isInsideNavigationItem(textEditable)) {
@@ -7256,7 +8507,7 @@ function OhhwellsBridge() {
7256
8507
  if (hoveredImageRef.current) {
7257
8508
  hoveredImageRef.current = null;
7258
8509
  resumeAnimTracks();
7259
- postToParentRef.current({ type: "ow:image-unhover" });
8510
+ clearImageHover();
7260
8511
  }
7261
8512
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
7262
8513
  return;
@@ -7264,9 +8515,9 @@ function OhhwellsBridge() {
7264
8515
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
7265
8516
  if (imgEl !== hoveredImageRef.current || hoveredImageHasTextOverlapRef.current) {
7266
8517
  hoveredImageRef.current = imgEl;
7267
- postImageHover(imgEl, isDragOver);
8518
+ showImageHover(imgEl, isDragOver);
7268
8519
  } else if (isDragOver) {
7269
- postImageHover(imgEl, true);
8520
+ showImageHover(imgEl, true);
7270
8521
  }
7271
8522
  } else {
7272
8523
  const inIframeView = clientX >= 0 && clientY >= 0 && clientX <= window.innerWidth && clientY <= window.innerHeight;
@@ -7283,14 +8534,14 @@ function OhhwellsBridge() {
7283
8534
  hoveredImageRef.current = null;
7284
8535
  hoveredImageHasTextOverlapRef.current = false;
7285
8536
  resumeAnimTracks();
7286
- postToParentRef.current({ type: "ow:image-unhover" });
8537
+ clearImageHover();
7287
8538
  }
7288
- const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
8539
+ const { x: x2, y: y2 } = toProbeCoords(clientX, clientY, fromParentViewport);
7289
8540
  const textEl = Array.from(
7290
- document.querySelectorAll('[data-ohw-editable]:not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"])')
8541
+ document.querySelectorAll(NON_MEDIA_SELECTOR)
7291
8542
  ).find((el) => {
7292
8543
  const er = el.getBoundingClientRect();
7293
- return x >= er.left && x <= er.right && y >= er.top && y <= er.bottom;
8544
+ return x2 >= er.left && x2 <= er.right && y2 >= er.top && y2 <= er.bottom;
7294
8545
  });
7295
8546
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
7296
8547
  if (textEl && !textEl.hasAttribute("contenteditable")) {
@@ -7393,18 +8644,20 @@ function OhhwellsBridge() {
7393
8644
  return;
7394
8645
  }
7395
8646
  e.dataTransfer.dropEffect = "copy";
7396
- if (hoveredImageRef.current !== el) {
8647
+ if (dragOverElRef.current !== el) {
8648
+ dragOverElRef.current = el;
7397
8649
  hoveredImageRef.current = el;
7398
8650
  const isStateCard = !!el.closest("[data-ohw-editable-state]");
7399
- postImageHover(el, true, isStateCard ? true : void 0);
8651
+ showImageHover(el, true, isStateCard ? true : void 0);
7400
8652
  }
7401
8653
  };
7402
8654
  const handleDragLeave = (e) => {
7403
8655
  const imgEl = findImageAtPoint(e.clientX, e.clientY, false);
7404
8656
  if (imgEl) return;
8657
+ dragOverElRef.current = null;
7405
8658
  hoveredImageRef.current = null;
7406
8659
  resumeAnimTracks();
7407
- postToParentRef.current({ type: "ow:image-unhover" });
8660
+ clearImageHover();
7408
8661
  };
7409
8662
  const handleDrop = (e) => {
7410
8663
  e.preventDefault();
@@ -7412,7 +8665,9 @@ function OhhwellsBridge() {
7412
8665
  if (!el) return;
7413
8666
  const file = e.dataTransfer?.files?.[0];
7414
8667
  if (file) {
7415
- if (file.type.startsWith("image/")) {
8668
+ const wantsVideo = el.dataset.ohwEditable === "video";
8669
+ const accepted = wantsVideo ? file.type.startsWith("video/") : file.type.startsWith("image/");
8670
+ if (accepted) {
7416
8671
  const key = el.dataset.ohwKey ?? "";
7417
8672
  const { name, type: mimeType } = file;
7418
8673
  const r2 = el.getBoundingClientRect();
@@ -7421,12 +8676,13 @@ function OhhwellsBridge() {
7421
8676
  window.parent.postMessage({ type: "ow:image-drop", key, name, mimeType, buf, rect }, "*", [buf]);
7422
8677
  });
7423
8678
  } else {
7424
- postToParentRef.current({ type: "ow:image-drop-invalid" });
8679
+ postToParentRef.current({ type: "ow:image-drop-invalid", expected: wantsVideo ? "video" : "image" });
7425
8680
  }
7426
8681
  }
8682
+ dragOverElRef.current = null;
7427
8683
  hoveredImageRef.current = null;
7428
8684
  resumeAnimTracks();
7429
- postToParentRef.current({ type: "ow:image-unhover" });
8685
+ clearImageHover();
7430
8686
  };
7431
8687
  const handleImageUrl = (e) => {
7432
8688
  if (e.data?.type !== "ow:image-url") return;
@@ -7444,7 +8700,11 @@ function OhhwellsBridge() {
7444
8700
  }
7445
8701
  });
7446
8702
  hoveredImageRef.current = null;
7447
- postToParentRef.current({ type: "ow:image-loaded", key });
8703
+ setUploadingRects((prev) => {
8704
+ const entry = prev[key];
8705
+ if (!entry || entry.fadingOut) return prev;
8706
+ return { ...prev, [key]: { ...entry, fadingOut: true } };
8707
+ });
7448
8708
  };
7449
8709
  let found = false;
7450
8710
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -7479,6 +8739,19 @@ function OhhwellsBridge() {
7479
8739
  requestAnimationFrame(() => onReady());
7480
8740
  }
7481
8741
  }
8742
+ } else if (el.dataset.ohwEditable === "video") {
8743
+ const video = getVideoEl(el);
8744
+ if (video) {
8745
+ found = true;
8746
+ const onReady = () => {
8747
+ video.onloadeddata = null;
8748
+ video.onerror = null;
8749
+ notify();
8750
+ };
8751
+ video.onloadeddata = onReady;
8752
+ video.onerror = onReady;
8753
+ applyVideoSrc(video, url);
8754
+ }
7482
8755
  }
7483
8756
  });
7484
8757
  if (!found) notify();
@@ -7545,12 +8818,16 @@ function OhhwellsBridge() {
7545
8818
  sectionsJson = val;
7546
8819
  continue;
7547
8820
  }
8821
+ if (applyVideoSettingNode(key, val)) continue;
7548
8822
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
7549
8823
  if (el.dataset.ohwEditable === "image") {
7550
8824
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
7551
8825
  if (img) applyEditableImageSrc(img, val);
7552
8826
  } else if (el.dataset.ohwEditable === "bg-image") {
7553
8827
  el.style.backgroundImage = `url('${val}')`;
8828
+ } else if (el.dataset.ohwEditable === "video") {
8829
+ const video = getVideoEl(el);
8830
+ if (video && video.src !== val) applyVideoSrc(video, val);
7554
8831
  } else if (el.dataset.ohwEditable === "link") {
7555
8832
  applyLinkHref(el, val);
7556
8833
  } else {
@@ -7564,6 +8841,8 @@ function OhhwellsBridge() {
7564
8841
  sectionsLoadedRef.current = true;
7565
8842
  pendingScheduleConfigRequests.current.splice(0).forEach(processConfigRequest);
7566
8843
  }
8844
+ editContentRef.current = { ...editContentRef.current, ...content };
8845
+ reconcileNavbarItemsFromContent(editContentRef.current);
7567
8846
  enforceLinkHrefs();
7568
8847
  postToParentRef.current({ type: "ow:hydrate-done" });
7569
8848
  };
@@ -7571,6 +8850,7 @@ function OhhwellsBridge() {
7571
8850
  const handleDeactivate = (e) => {
7572
8851
  if (e.data?.type !== "ow:deactivate") return;
7573
8852
  if (Date.now() < linkPopoverGraceUntilRef.current) return;
8853
+ if (document.querySelector("[data-ohw-section-picker]")) return;
7574
8854
  if (linkPopoverOpenRef.current) {
7575
8855
  setLinkPopoverRef.current(null);
7576
8856
  return;
@@ -7580,12 +8860,32 @@ function OhhwellsBridge() {
7580
8860
  };
7581
8861
  window.addEventListener("message", handleDeactivate);
7582
8862
  const handleKeyDown = (e) => {
8863
+ if (e.key === "Escape" && document.querySelector("[data-ohw-section-picker]")) return;
8864
+ if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "a" && activeElRef.current) {
8865
+ const navAnchor = getNavigationItemAnchor(activeElRef.current);
8866
+ if (navAnchor) {
8867
+ e.preventDefault();
8868
+ selectAllTextInEditable(activeElRef.current);
8869
+ refreshActiveCommandsRef.current();
8870
+ return;
8871
+ }
8872
+ }
7583
8873
  if (e.key === "Escape" && linkPopoverOpenRef.current) {
7584
8874
  setLinkPopoverRef.current(null);
7585
8875
  return;
7586
8876
  }
7587
8877
  if (e.key === "Escape" && selectedElRef.current && !activeElRef.current) {
7588
- deselectRef.current();
8878
+ if (toolbarVariantRef.current === "select-frame") {
8879
+ deselectRef.current();
8880
+ return;
8881
+ }
8882
+ const parent = getNavigationSelectionParent(selectedElRef.current);
8883
+ if (parent) {
8884
+ e.preventDefault();
8885
+ selectFrameRef.current(parent);
8886
+ } else {
8887
+ deselectRef.current();
8888
+ }
7589
8889
  return;
7590
8890
  }
7591
8891
  if (e.key === "Escape" && activeElRef.current) {
@@ -7643,18 +8943,18 @@ function OhhwellsBridge() {
7643
8943
  if (hoveredItemElRef.current) {
7644
8944
  setHoveredItemRect(hoveredItemElRef.current.getBoundingClientRect());
7645
8945
  }
8946
+ if (hoveredNavContainerRef.current) {
8947
+ setHoveredNavContainerRect(hoveredNavContainerRef.current.getBoundingClientRect());
8948
+ }
7646
8949
  if (siblingHintElRef.current) {
7647
8950
  setSiblingHintRect(siblingHintElRef.current.getBoundingClientRect());
7648
8951
  }
7649
8952
  if (hoveredImageRef.current) {
7650
8953
  const el = hoveredImageRef.current;
7651
8954
  const r2 = el.getBoundingClientRect();
7652
- postToParentRef.current({
7653
- type: "ow:image-hover",
7654
- key: el.dataset.ohwKey ?? "",
7655
- rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height },
7656
- hasTextOverlap: hoveredImageHasTextOverlapRef.current
7657
- });
8955
+ setMediaHover(
8956
+ (prev) => prev ? { ...prev, rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height } } : prev
8957
+ );
7658
8958
  }
7659
8959
  };
7660
8960
  const handleSave = (e) => {
@@ -7743,19 +9043,39 @@ function OhhwellsBridge() {
7743
9043
  refreshActiveCommandsRef.current = handleSelectionChange;
7744
9044
  const handleDocMouseLeave = () => {
7745
9045
  hoveredImageRef.current = null;
9046
+ setMediaHover(null);
7746
9047
  document.querySelectorAll("[data-ohw-state-hovered]").forEach((el) => el.removeAttribute("data-ohw-state-hovered"));
7747
9048
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
7748
9049
  activeStateElRef.current = null;
7749
9050
  setToggleState(null);
7750
9051
  };
7751
- const handleAnimLock = (e) => {
7752
- if (e.data?.type !== "ow:anim-lock") return;
7753
- const { key } = e.data;
9052
+ const handleImageUploading = (e) => {
9053
+ if (e.data?.type !== "ow:image-uploading") return;
9054
+ const { key, uploading } = e.data;
9055
+ setUploadingRects((prev) => {
9056
+ if (!uploading) {
9057
+ if (!(key in prev)) return prev;
9058
+ const next = { ...prev };
9059
+ delete next[key];
9060
+ return next;
9061
+ }
9062
+ const el = document.querySelector(`[data-ohw-key="${key}"]`);
9063
+ if (!el) return prev;
9064
+ const r2 = getVisibleRect(el);
9065
+ return {
9066
+ ...prev,
9067
+ [key]: { rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height } }
9068
+ };
9069
+ });
7754
9070
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
7755
9071
  const track = el.closest("[data-ohw-hover-pause]");
7756
- if (track) {
9072
+ if (!track) return;
9073
+ if (uploading) {
7757
9074
  uploadLockedTracks.add(track);
7758
9075
  track.setAttribute("data-ohw-hover-paused", "");
9076
+ } else {
9077
+ uploadLockedTracks.delete(track);
9078
+ track.removeAttribute("data-ohw-hover-paused");
7759
9079
  }
7760
9080
  });
7761
9081
  };
@@ -7797,7 +9117,7 @@ function OhhwellsBridge() {
7797
9117
  if (e.data?.type !== "ow:click-at") return;
7798
9118
  const { clientX, clientY } = e.data;
7799
9119
  const allImages = Array.from(
7800
- document.querySelectorAll('[data-ohw-editable="image"], [data-ohw-editable="bg-image"]')
9120
+ document.querySelectorAll(MEDIA_SELECTOR)
7801
9121
  ).filter((el) => !!el.closest("[data-ohw-editable-state]"));
7802
9122
  const stateCardImage = allImages.find((el) => {
7803
9123
  const ownerCard = el.closest("[data-ohw-editable-state]");
@@ -7812,21 +9132,55 @@ function OhhwellsBridge() {
7812
9132
  return clientX >= r2.left && clientX <= r2.right && clientY >= r2.top && clientY <= r2.bottom;
7813
9133
  });
7814
9134
  if (stateCardImage) {
7815
- postToParentRef.current({ type: "ow:image-pick", key: stateCardImage.dataset.ohwKey ?? "" });
9135
+ postToParentRef.current({ type: "ow:image-pick", key: stateCardImage.dataset.ohwKey ?? "", elementType: stateCardImage.dataset.ohwEditable ?? "image" });
7816
9136
  return;
7817
9137
  }
7818
9138
  const textEditable = Array.from(
7819
- document.querySelectorAll(
7820
- '[data-ohw-editable]:not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"])'
7821
- )
9139
+ document.querySelectorAll(NON_MEDIA_SELECTOR)
7822
9140
  ).find((el) => {
7823
9141
  const r2 = el.getBoundingClientRect();
7824
9142
  return clientX >= r2.left && clientX <= r2.right && clientY >= r2.top && clientY <= r2.bottom;
7825
9143
  });
7826
9144
  if (textEditable) {
7827
- activateRef.current(textEditable);
9145
+ const hrefCtx = getHrefKeyFromElement(textEditable);
9146
+ const navAnchor = hrefCtx ? getNavigationItemAnchor(hrefCtx.anchor) : null;
9147
+ if (navAnchor) {
9148
+ if (selectedElRef.current === navAnchor) {
9149
+ activateRef.current(textEditable, { caretX: clientX, caretY: clientY });
9150
+ } else {
9151
+ selectRef.current(navAnchor);
9152
+ }
9153
+ return;
9154
+ }
9155
+ activateRef.current(textEditable, { caretX: clientX, caretY: clientY });
7828
9156
  return;
7829
9157
  }
9158
+ const navContainer = document.querySelector("[data-ohw-nav-container]");
9159
+ if (navContainer) {
9160
+ const r2 = navContainer.getBoundingClientRect();
9161
+ const pad = 8;
9162
+ const over = clientX >= r2.left - pad && clientX <= r2.right + pad && clientY >= r2.top - pad && clientY <= r2.bottom + pad;
9163
+ if (over && !isPointOverNavItem(navContainer, clientX, clientY)) {
9164
+ selectFrameRef.current(navContainer);
9165
+ return;
9166
+ }
9167
+ }
9168
+ const navRoot = document.querySelector("[data-ohw-nav-root]");
9169
+ if (navRoot && navContainer) {
9170
+ const rr = navRoot.getBoundingClientRect();
9171
+ if (clientX >= rr.left && clientX <= rr.right && clientY >= rr.top && clientY <= rr.bottom && !isPointOverNavItem(navContainer, clientX, clientY)) {
9172
+ const book = navRoot.querySelector('[data-ohw-role="navbar-button"]');
9173
+ if (book) {
9174
+ const br = book.getBoundingClientRect();
9175
+ if (clientX >= br.left && clientX <= br.right && clientY >= br.top && clientY <= br.bottom) {
9176
+ deactivateRef.current();
9177
+ return;
9178
+ }
9179
+ }
9180
+ selectFrameRef.current(navContainer);
9181
+ return;
9182
+ }
9183
+ }
7830
9184
  deactivateRef.current();
7831
9185
  };
7832
9186
  window.addEventListener("message", handleSave);
@@ -7836,7 +9190,7 @@ function OhhwellsBridge() {
7836
9190
  window.addEventListener("message", handleClearSchedulingWidget);
7837
9191
  window.addEventListener("message", handleRemoveSchedulingSection);
7838
9192
  window.addEventListener("message", handleImageUrl);
7839
- window.addEventListener("message", handleAnimLock);
9193
+ window.addEventListener("message", handleImageUploading);
7840
9194
  window.addEventListener("message", handleCanvasHeight);
7841
9195
  window.addEventListener("message", handleParentScroll);
7842
9196
  window.addEventListener("message", handlePointerSync);
@@ -7848,6 +9202,7 @@ function OhhwellsBridge() {
7848
9202
  };
7849
9203
  window.addEventListener("resize", handleViewportResize, { passive: true });
7850
9204
  document.addEventListener("click", handleClick, true);
9205
+ document.addEventListener("dblclick", handleDblClick, true);
7851
9206
  document.addEventListener("paste", handlePaste, true);
7852
9207
  document.addEventListener("input", handleInput, true);
7853
9208
  document.addEventListener("mouseover", handleMouseOver, true);
@@ -7862,6 +9217,7 @@ function OhhwellsBridge() {
7862
9217
  window.addEventListener("scroll", handleScroll, true);
7863
9218
  return () => {
7864
9219
  document.removeEventListener("click", handleClick, true);
9220
+ document.removeEventListener("dblclick", handleDblClick, true);
7865
9221
  document.removeEventListener("paste", handlePaste, true);
7866
9222
  document.removeEventListener("input", handleInput, true);
7867
9223
  document.removeEventListener("mouseover", handleMouseOver, true);
@@ -7881,7 +9237,7 @@ function OhhwellsBridge() {
7881
9237
  window.removeEventListener("message", handleClearSchedulingWidget);
7882
9238
  window.removeEventListener("message", handleRemoveSchedulingSection);
7883
9239
  window.removeEventListener("message", handleImageUrl);
7884
- window.removeEventListener("message", handleAnimLock);
9240
+ window.removeEventListener("message", handleImageUploading);
7885
9241
  window.removeEventListener("message", handleCanvasHeight);
7886
9242
  window.removeEventListener("message", handleParentScroll);
7887
9243
  window.removeEventListener("resize", handleViewportResize);
@@ -7895,7 +9251,7 @@ function OhhwellsBridge() {
7895
9251
  if (imageShowTimerRef.current) clearTimeout(imageShowTimerRef.current);
7896
9252
  };
7897
9253
  }, [isEditMode, refreshStateRules]);
7898
- useEffect3(() => {
9254
+ useEffect7(() => {
7899
9255
  const handler = (e) => {
7900
9256
  if (e.data?.type !== "ow:request-schedule-config") return;
7901
9257
  const insertAfterVal = e.data.insertAfter;
@@ -7911,7 +9267,7 @@ function OhhwellsBridge() {
7911
9267
  window.addEventListener("message", handler);
7912
9268
  return () => window.removeEventListener("message", handler);
7913
9269
  }, [processConfigRequest]);
7914
- useEffect3(() => {
9270
+ useEffect7(() => {
7915
9271
  if (!isEditMode) return;
7916
9272
  document.querySelectorAll("[data-ohw-active-state]").forEach((el) => {
7917
9273
  el.removeAttribute("data-ohw-active-state");
@@ -7932,27 +9288,27 @@ function OhhwellsBridge() {
7932
9288
  const next = { ...prev, [pathKey]: sections };
7933
9289
  return shouldUseDevFixtures() ? { ...DEV_SECTIONS_BY_PATH, ...next } : next;
7934
9290
  });
7935
- postToParent({ type: "ow:ready", version: "1", path: pathname, nodes: collectEditableNodes(), sections });
7936
- postToParent({ type: "ow:request-site-pages" });
9291
+ postToParent2({ type: "ow:ready", version: "1", path: pathname, nodes: collectEditableNodes(), sections });
9292
+ postToParent2({ type: "ow:request-site-pages" });
7937
9293
  }, 150);
7938
9294
  return () => {
7939
9295
  cancelAnimationFrame(raf);
7940
9296
  clearTimeout(timer);
7941
9297
  };
7942
- }, [pathname, isEditMode, refreshStateRules, postToParent]);
7943
- useEffect3(() => {
9298
+ }, [pathname, isEditMode, refreshStateRules, postToParent2]);
9299
+ useEffect7(() => {
7944
9300
  scrollToHashSectionWhenReady();
7945
9301
  const onHashChange = () => scrollToHashSectionWhenReady();
7946
9302
  window.addEventListener("hashchange", onHashChange);
7947
9303
  return () => window.removeEventListener("hashchange", onHashChange);
7948
9304
  }, [pathname]);
7949
- const handleCommand = useCallback2((cmd) => {
9305
+ const handleCommand = useCallback4((cmd) => {
7950
9306
  document.execCommand(cmd, false);
7951
9307
  activeElRef.current?.focus();
7952
9308
  if (activeElRef.current) setToolbarRect(getEditMeasureEl(activeElRef.current).getBoundingClientRect());
7953
9309
  refreshActiveCommandsRef.current();
7954
9310
  }, []);
7955
- const handleStateChange = useCallback2((state) => {
9311
+ const handleStateChange = useCallback4((state) => {
7956
9312
  if (!activeStateElRef.current) return;
7957
9313
  const el = activeStateElRef.current;
7958
9314
  if (state === "Default") {
@@ -7965,18 +9321,22 @@ function OhhwellsBridge() {
7965
9321
  }
7966
9322
  setToggleState((prev) => prev ? { ...prev, activeState: state } : null);
7967
9323
  }, [deactivate]);
7968
- const closeLinkPopover = useCallback2(() => setLinkPopover(null), []);
7969
- const openLinkPopoverForActive = useCallback2(() => {
9324
+ const closeLinkPopover = useCallback4(() => {
9325
+ addNavAfterAnchorRef.current = null;
9326
+ setLinkPopover(null);
9327
+ }, []);
9328
+ const openLinkPopoverForActive = useCallback4(() => {
7970
9329
  const hrefCtx = getHrefKeyFromElement(activeElRef.current);
7971
9330
  if (!hrefCtx) return;
7972
9331
  bumpLinkPopoverGrace();
7973
9332
  setLinkPopover({
7974
9333
  key: hrefCtx.key,
7975
- target: getLinkHref(hrefCtx.anchor)
9334
+ mode: "edit",
9335
+ target: getLinkHref2(hrefCtx.anchor)
7976
9336
  });
7977
9337
  deactivate();
7978
9338
  }, [deactivate]);
7979
- const openLinkPopoverForSelected = useCallback2(() => {
9339
+ const openLinkPopoverForSelected = useCallback4(() => {
7980
9340
  const anchor = selectedElRef.current;
7981
9341
  if (!anchor) return;
7982
9342
  const key = anchor.getAttribute("data-ohw-href-key");
@@ -7984,51 +9344,156 @@ function OhhwellsBridge() {
7984
9344
  bumpLinkPopoverGrace();
7985
9345
  setLinkPopover({
7986
9346
  key,
7987
- target: getLinkHref(anchor)
9347
+ mode: "edit",
9348
+ target: getLinkHref2(anchor)
7988
9349
  });
7989
9350
  deselect();
7990
9351
  }, [deselect]);
7991
- const handleLinkPopoverSubmit = useCallback2(
9352
+ const handleLinkPopoverSubmit = useCallback4(
7992
9353
  (target) => {
7993
- if (!linkPopover) return;
7994
- const { key } = linkPopover;
9354
+ const session = linkPopoverSessionRef.current;
9355
+ if (!session) return;
9356
+ if (session.intent === "add-nav") {
9357
+ const { pageRoute, sectionId } = parseTarget(target);
9358
+ const page = resolvePage(pageRoute, sitePages);
9359
+ const pageSections = getSectionsForPath(sectionsByPath, pageRoute);
9360
+ const section = sectionId ? pageSections.find((s) => s.id === sectionId) : void 0;
9361
+ const label = section?.label ?? page.title;
9362
+ const afterAnchor = addNavAfterAnchorRef.current;
9363
+ const { anchor, hrefKey, labelKey, index, order } = insertNavbarItem(target, label, afterAnchor);
9364
+ applyLinkByKey(hrefKey, target);
9365
+ document.querySelectorAll(`[data-ohw-key="${labelKey}"]`).forEach((el) => {
9366
+ el.textContent = label;
9367
+ });
9368
+ const navCount = String(index + 1);
9369
+ const orderJson = JSON.stringify(order);
9370
+ editContentRef.current = {
9371
+ ...editContentRef.current,
9372
+ [hrefKey]: target,
9373
+ [labelKey]: label,
9374
+ [NAV_COUNT_KEY]: navCount,
9375
+ [NAV_ORDER_KEY]: orderJson
9376
+ };
9377
+ postToParent2({
9378
+ type: "ow:change",
9379
+ nodes: [
9380
+ { key: hrefKey, text: target },
9381
+ { key: labelKey, text: label },
9382
+ { key: NAV_COUNT_KEY, text: navCount },
9383
+ { key: NAV_ORDER_KEY, text: orderJson }
9384
+ ]
9385
+ });
9386
+ addNavAfterAnchorRef.current = null;
9387
+ setLinkPopover(null);
9388
+ enforceLinkHrefs();
9389
+ requestAnimationFrame(() => {
9390
+ selectRef.current(anchor);
9391
+ });
9392
+ return;
9393
+ }
9394
+ const { key } = session;
7995
9395
  applyLinkByKey(key, target);
7996
- postToParent({ type: "ow:change", nodes: [{ key, text: target }] });
9396
+ postToParent2({ type: "ow:change", nodes: [{ key, text: target }] });
7997
9397
  setLinkPopover(null);
7998
9398
  },
7999
- [linkPopover, postToParent]
9399
+ [postToParent2, sitePages, sectionsByPath]
8000
9400
  );
8001
9401
  const showEditLink = toolbarShowEditLink;
8002
9402
  const currentSections = sectionsByPath[pathname] ?? [];
8003
9403
  linkPopoverOpenRef.current = linkPopover !== null;
8004
- return bridgeRoot ? createPortal(
8005
- /* @__PURE__ */ jsxs11(Fragment3, { children: [
8006
- /* @__PURE__ */ jsx21("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
8007
- siblingHintRect && !isItemDragging && /* @__PURE__ */ jsx21(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
8008
- hoveredItemRect && !siblingHintRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ jsx21(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
8009
- toolbarRect && toolbarVariant === "link-action" && /* @__PURE__ */ jsx21(
9404
+ const handleMediaReplace = useCallback4(
9405
+ (key) => {
9406
+ postToParent2({ type: "ow:image-pick", key, elementType: mediaHover?.elementType ?? "image" });
9407
+ },
9408
+ [postToParent2, mediaHover?.elementType]
9409
+ );
9410
+ const handleMediaFadeOutComplete = useCallback4((key) => {
9411
+ setUploadingRects((prev) => {
9412
+ if (!(key in prev)) return prev;
9413
+ const next = { ...prev };
9414
+ delete next[key];
9415
+ return next;
9416
+ });
9417
+ }, []);
9418
+ const handleVideoSettingsChange = useCallback4(
9419
+ (key, settings) => {
9420
+ document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
9421
+ const video = getVideoEl(el);
9422
+ if (!video) return;
9423
+ if (typeof settings.autoplay === "boolean") video.autoplay = settings.autoplay;
9424
+ if (typeof settings.muted === "boolean") video.muted = settings.muted;
9425
+ syncVideoPlayback(video);
9426
+ postToParent2({
9427
+ type: "ow:change",
9428
+ nodes: [
9429
+ { key: `${key}${VIDEO_AUTOPLAY_SUFFIX}`, text: String(video.autoplay) },
9430
+ { key: `${key}${VIDEO_MUTED_SUFFIX}`, text: String(video.muted) }
9431
+ ]
9432
+ });
9433
+ setMediaHover(
9434
+ (prev) => prev && prev.key === key ? { ...prev, videoAutoplay: video.autoplay, videoMuted: video.muted } : prev
9435
+ );
9436
+ });
9437
+ },
9438
+ [postToParent2]
9439
+ );
9440
+ return bridgeRoot ? createPortal2(
9441
+ /* @__PURE__ */ jsxs13(Fragment5, { children: [
9442
+ /* @__PURE__ */ jsx23("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
9443
+ Object.entries(uploadingRects).map(([key, { rect, fadingOut }]) => /* @__PURE__ */ jsx23(
9444
+ MediaOverlay,
9445
+ {
9446
+ hover: { key, rect, elementType: "image", isDragOver: false, hasTextOverlap: false },
9447
+ isUploading: true,
9448
+ fadingOut,
9449
+ onFadeOutComplete: handleMediaFadeOutComplete,
9450
+ onReplace: handleMediaReplace
9451
+ },
9452
+ `uploading-${key}`
9453
+ )),
9454
+ mediaHover && !(mediaHover.key in uploadingRects) && /* @__PURE__ */ jsx23(
9455
+ MediaOverlay,
9456
+ {
9457
+ hover: mediaHover,
9458
+ isUploading: false,
9459
+ onReplace: handleMediaReplace,
9460
+ onVideoSettingsChange: handleVideoSettingsChange
9461
+ }
9462
+ ),
9463
+ siblingHintRect && !isItemDragging && /* @__PURE__ */ jsx23(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
9464
+ hoveredNavContainerRect && toolbarVariant !== "select-frame" && !linkPopover && !isItemDragging && !siblingHintRect && /* @__PURE__ */ jsx23(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
9465
+ toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && /* @__PURE__ */ jsx23(
9466
+ NavbarContainerChrome,
9467
+ {
9468
+ rect: toolbarRect,
9469
+ onAdd: handleAddTopLevelNavItem
9470
+ }
9471
+ ),
9472
+ hoveredItemRect && !hoveredNavContainerRect && !siblingHintRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ jsx23(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
9473
+ toolbarRect && (toolbarVariant === "link-action" || toolbarVariant === "select-frame") && /* @__PURE__ */ jsx23(
8010
9474
  ItemInteractionLayer,
8011
9475
  {
8012
9476
  rect: toolbarRect,
8013
9477
  elRef: glowElRef,
8014
9478
  state: isItemDragging ? "dragging" : resolveItemInteractionState(toolbarRect, parentScrollRef.current),
8015
- showHandle: Boolean(reorderHrefKey),
9479
+ showHandle: toolbarVariant === "link-action" && Boolean(reorderHrefKey),
8016
9480
  dragDisabled: reorderDragDisabled,
8017
9481
  dragHandleLabel: reorderHrefKey ? `Reorder ${reorderHrefKey}` : "Reorder item",
8018
9482
  onDragHandleDragStart: handleItemDragStart,
8019
9483
  onDragHandleDragEnd: handleItemDragEnd,
8020
- toolbar: isItemDragging ? void 0 : /* @__PURE__ */ jsx21(
9484
+ toolbar: toolbarVariant === "link-action" && !isItemDragging ? /* @__PURE__ */ jsx23(
8021
9485
  ItemActionToolbar,
8022
9486
  {
8023
9487
  onEditLink: openLinkPopoverForSelected,
8024
- onAddItem: handleAddNavigationItem,
8025
- addItemDisabled: false
9488
+ addItemDisabled: true,
9489
+ editLinkDisabled: false,
9490
+ moreDisabled: true
8026
9491
  }
8027
- )
9492
+ ) : void 0
8028
9493
  }
8029
9494
  ),
8030
- toolbarRect && toolbarVariant === "rich-text" && /* @__PURE__ */ jsxs11(Fragment3, { children: [
8031
- /* @__PURE__ */ jsx21(
9495
+ toolbarRect && toolbarVariant === "rich-text" && /* @__PURE__ */ jsxs13(Fragment5, { children: [
9496
+ /* @__PURE__ */ jsx23(
8032
9497
  EditGlowChrome,
8033
9498
  {
8034
9499
  rect: toolbarRect,
@@ -8037,7 +9502,7 @@ function OhhwellsBridge() {
8037
9502
  dragDisabled: reorderDragDisabled
8038
9503
  }
8039
9504
  ),
8040
- /* @__PURE__ */ jsx21(
9505
+ /* @__PURE__ */ jsx23(
8041
9506
  FloatingToolbar,
8042
9507
  {
8043
9508
  rect: toolbarRect,
@@ -8050,7 +9515,7 @@ function OhhwellsBridge() {
8050
9515
  }
8051
9516
  )
8052
9517
  ] }),
8053
- maxBadge && /* @__PURE__ */ jsxs11(
9518
+ maxBadge && /* @__PURE__ */ jsxs13(
8054
9519
  "div",
8055
9520
  {
8056
9521
  "data-ohw-max-badge": "",
@@ -8076,7 +9541,7 @@ function OhhwellsBridge() {
8076
9541
  ]
8077
9542
  }
8078
9543
  ),
8079
- toggleState && /* @__PURE__ */ jsx21(
9544
+ toggleState && !linkPopover && /* @__PURE__ */ jsx23(
8080
9545
  StateToggle,
8081
9546
  {
8082
9547
  rect: toggleState.rect,
@@ -8085,15 +9550,15 @@ function OhhwellsBridge() {
8085
9550
  onStateChange: handleStateChange
8086
9551
  }
8087
9552
  ),
8088
- sectionGap && /* @__PURE__ */ jsxs11(
9553
+ sectionGap && !linkPopover && /* @__PURE__ */ jsxs13(
8089
9554
  "div",
8090
9555
  {
8091
9556
  "data-ohw-section-insert-line": "",
8092
9557
  className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
8093
9558
  style: { top: sectionGap.y, transform: "translateY(-50%)" },
8094
9559
  children: [
8095
- /* @__PURE__ */ jsx21("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
8096
- /* @__PURE__ */ jsx21(
9560
+ /* @__PURE__ */ jsx23("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
9561
+ /* @__PURE__ */ jsx23(
8097
9562
  Badge,
8098
9563
  {
8099
9564
  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",
@@ -8106,26 +9571,52 @@ function OhhwellsBridge() {
8106
9571
  children: "Add Section"
8107
9572
  }
8108
9573
  ),
8109
- /* @__PURE__ */ jsx21("div", { className: "flex-1 bg-primary", style: { height: 3 } })
9574
+ /* @__PURE__ */ jsx23("div", { className: "flex-1 bg-primary", style: { height: 3 } })
8110
9575
  ]
8111
9576
  }
8112
9577
  ),
8113
- linkPopover && dialogPortalContainer ? /* @__PURE__ */ jsx21(
9578
+ linkPopover && dialogPortalContainer ? /* @__PURE__ */ jsx23(
8114
9579
  LinkPopover,
8115
9580
  {
8116
9581
  panelRef: linkPopoverPanelRef,
8117
9582
  portalContainer: dialogPortalContainer,
8118
9583
  open: true,
8119
- mode: "edit",
9584
+ mode: linkPopover.mode ?? "edit",
8120
9585
  pages: sitePages,
8121
9586
  sections: currentSections,
8122
9587
  sectionsByPath,
8123
9588
  initialTarget: linkPopover.target,
9589
+ existingTargets: linkPopover.intent === "add-nav" ? getNavbarExistingTargets() : [],
8124
9590
  onClose: closeLinkPopover,
8125
9591
  onSubmit: handleLinkPopoverSubmit
8126
9592
  },
8127
- `${linkPopover.key}-${pathname}`
8128
- ) : null
9593
+ linkPopover.key
9594
+ ) : null,
9595
+ sectionGap && /* @__PURE__ */ jsxs13(
9596
+ "div",
9597
+ {
9598
+ "data-ohw-section-insert-line": "",
9599
+ className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
9600
+ style: { top: sectionGap.y, transform: "translateY(-50%)" },
9601
+ children: [
9602
+ /* @__PURE__ */ jsx23("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
9603
+ /* @__PURE__ */ jsx23(
9604
+ Badge,
9605
+ {
9606
+ 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",
9607
+ onClick: () => {
9608
+ window.parent.postMessage(
9609
+ { type: "ow:add-section", insertAfter: sectionGap.insertAfter, insertBefore: sectionGap.insertBefore },
9610
+ "*"
9611
+ );
9612
+ },
9613
+ children: "Add Section"
9614
+ }
9615
+ ),
9616
+ /* @__PURE__ */ jsx23("div", { className: "flex-1 bg-primary", style: { height: 3 } })
9617
+ ]
9618
+ }
9619
+ )
8129
9620
  ] }),
8130
9621
  bridgeRoot
8131
9622
  ) : null;