@ohhwells/bridge 0.1.36 → 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) {
@@ -5598,6 +6485,11 @@ function fadeInImageElement(img, onReady) {
5598
6485
  img.style.opacity = "";
5599
6486
  });
5600
6487
  }
6488
+ function applyEditableImageSrc(img, url) {
6489
+ img.removeAttribute("srcset");
6490
+ img.removeAttribute("sizes");
6491
+ img.src = url;
6492
+ }
5601
6493
  function fadeInBgImage(el, url, onReady) {
5602
6494
  const prevPos = el.style.position;
5603
6495
  if (!prevPos || prevPos === "static") el.style.position = "relative";
@@ -5745,7 +6637,7 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
5745
6637
  const root = createRoot(container);
5746
6638
  flushSync(() => {
5747
6639
  root.render(
5748
- /* @__PURE__ */ jsx21(
6640
+ /* @__PURE__ */ jsx23(
5749
6641
  SchedulingWidget,
5750
6642
  {
5751
6643
  notifyOnConnect,
@@ -5785,7 +6677,7 @@ function mountSchedulingEntries(entries, notifyOnConnect = false) {
5785
6677
  }
5786
6678
  }
5787
6679
  }
5788
- function getLinkHref(el) {
6680
+ function getLinkHref2(el) {
5789
6681
  const anchor = el instanceof HTMLAnchorElement ? el : el.querySelector("a");
5790
6682
  return anchor?.getAttribute("href") ?? "";
5791
6683
  }
@@ -5818,8 +6710,11 @@ function collectEditableNodes() {
5818
6710
  const url = raw.replace(/^url\(['"]?/, "").replace(/['"]?\)$/, "");
5819
6711
  return { key: el.dataset.ohwKey ?? "", type: "bg-image", text: url };
5820
6712
  }
6713
+ if (el.dataset.ohwEditable === "video") {
6714
+ return { key: el.dataset.ohwKey ?? "", type: "video", text: getVideoEl(el)?.src ?? "" };
6715
+ }
5821
6716
  if (el.dataset.ohwEditable === "link") {
5822
- return { key: el.dataset.ohwKey ?? "", type: "link", text: getLinkHref(el) };
6717
+ return { key: el.dataset.ohwKey ?? "", type: "link", text: getLinkHref2(el) };
5823
6718
  }
5824
6719
  return {
5825
6720
  key: el.dataset.ohwKey ?? "",
@@ -5830,9 +6725,79 @@ function collectEditableNodes() {
5830
6725
  document.querySelectorAll("[data-ohw-href-key]").forEach((el) => {
5831
6726
  const key = el.getAttribute("data-ohw-href-key") ?? "";
5832
6727
  if (!key) return;
5833
- 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);
5834
6799
  });
5835
- return nodes;
6800
+ return true;
5836
6801
  }
5837
6802
  function applyLinkByKey(key, val) {
5838
6803
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -5846,7 +6811,7 @@ function applyLinkByKey(key, val) {
5846
6811
  }
5847
6812
  function isInsideLinkEditor(target) {
5848
6813
  return Boolean(
5849
- 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"]')
5850
6815
  );
5851
6816
  }
5852
6817
  function getHrefKeyFromElement(el) {
@@ -5857,7 +6822,7 @@ function getHrefKeyFromElement(el) {
5857
6822
  if (!key) return null;
5858
6823
  return { anchor, key };
5859
6824
  }
5860
- function isNavbarButton(el) {
6825
+ function isNavbarButton2(el) {
5861
6826
  return Boolean(el.closest('[data-ohw-role="navbar-button"]'));
5862
6827
  }
5863
6828
  function getNavigationItemAnchor(el) {
@@ -5869,19 +6834,8 @@ function getNavigationItemAnchor(el) {
5869
6834
  function isNavigationItem(el) {
5870
6835
  return getNavigationItemAnchor(el) !== null;
5871
6836
  }
5872
- function getNavigationItemAddHintTarget(anchor) {
5873
- const items = listNavigationItems();
5874
- const idx = items.indexOf(anchor);
5875
- if (idx >= 0 && idx < items.length - 1) return items[idx + 1];
5876
- return anchor;
5877
- }
5878
- function listNavigationItems() {
5879
- return Array.from(
5880
- document.querySelectorAll("nav [data-ohw-href-key], footer [data-ohw-href-key]")
5881
- ).filter((el) => isNavigationItem(el));
5882
- }
5883
6837
  function getNavigationItemReorderState(anchor) {
5884
- if (isNavbarButton(anchor)) return { key: null, disabled: false };
6838
+ if (isNavbarButton2(anchor)) return { key: null, disabled: false };
5885
6839
  return getReorderHandleStateFromAnchor(anchor);
5886
6840
  }
5887
6841
  function getReorderHandleState(el) {
@@ -5903,6 +6857,120 @@ function clearHrefKeyHover(anchor) {
5903
6857
  function isInsideNavigationItem(el) {
5904
6858
  return getNavigationItemAnchor(el) !== null;
5905
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
+ }
5906
6974
  function collectSections() {
5907
6975
  return collectSectionsFromDom();
5908
6976
  }
@@ -6026,6 +7094,8 @@ var ICONS = {
6026
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"/>',
6027
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"/>'
6028
7096
  };
7097
+ var TOOLBAR_PILL_PADDING = 2;
7098
+ var TOOLBAR_TOGGLE_GAP = 4;
6029
7099
  var TOOLBAR_GROUPS = [
6030
7100
  [
6031
7101
  { cmd: "bold", title: "Bold" },
@@ -6050,7 +7120,7 @@ function EditGlowChrome({
6050
7120
  dragDisabled = false
6051
7121
  }) {
6052
7122
  const GAP = 6;
6053
- return /* @__PURE__ */ jsxs11(
7123
+ return /* @__PURE__ */ jsxs13(
6054
7124
  "div",
6055
7125
  {
6056
7126
  ref: elRef,
@@ -6065,7 +7135,7 @@ function EditGlowChrome({
6065
7135
  zIndex: 2147483646
6066
7136
  },
6067
7137
  children: [
6068
- /* @__PURE__ */ jsx21(
7138
+ /* @__PURE__ */ jsx23(
6069
7139
  "div",
6070
7140
  {
6071
7141
  style: {
@@ -6078,7 +7148,7 @@ function EditGlowChrome({
6078
7148
  }
6079
7149
  }
6080
7150
  ),
6081
- reorderHrefKey && /* @__PURE__ */ jsx21(
7151
+ reorderHrefKey && /* @__PURE__ */ jsx23(
6082
7152
  "div",
6083
7153
  {
6084
7154
  "data-ohw-drag-handle-container": "",
@@ -6090,7 +7160,7 @@ function EditGlowChrome({
6090
7160
  transform: "translate(calc(-100% - 7px), -50%)",
6091
7161
  pointerEvents: dragDisabled ? "none" : "auto"
6092
7162
  },
6093
- children: /* @__PURE__ */ jsx21(
7163
+ children: /* @__PURE__ */ jsx23(
6094
7164
  DragHandle,
6095
7165
  {
6096
7166
  "aria-label": `Reorder ${reorderHrefKey}`,
@@ -6194,7 +7264,7 @@ function FloatingToolbar({
6194
7264
  onEditLink
6195
7265
  }) {
6196
7266
  const { top, left, transform } = calcToolbarPos(rect, parentScroll, 330);
6197
- return /* @__PURE__ */ jsx21(
7267
+ return /* @__PURE__ */ jsx23(
6198
7268
  "div",
6199
7269
  {
6200
7270
  ref: elRef,
@@ -6204,14 +7274,23 @@ function FloatingToolbar({
6204
7274
  left,
6205
7275
  transform,
6206
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",
6207
7286
  pointerEvents: "auto"
6208
7287
  },
6209
- children: /* @__PURE__ */ jsxs11(CustomToolbar, { children: [
6210
- TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ jsxs11(React8.Fragment, { children: [
6211
- 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, {}),
6212
7291
  btns.map((btn) => {
6213
7292
  const isActive = activeCommands.has(btn.cmd);
6214
- return /* @__PURE__ */ jsx21(
7293
+ return /* @__PURE__ */ jsx23(
6215
7294
  CustomToolbarButton,
6216
7295
  {
6217
7296
  title: btn.title,
@@ -6220,7 +7299,7 @@ function FloatingToolbar({
6220
7299
  e.preventDefault();
6221
7300
  onCommand(btn.cmd);
6222
7301
  },
6223
- children: /* @__PURE__ */ jsx21(
7302
+ children: /* @__PURE__ */ jsx23(
6224
7303
  "svg",
6225
7304
  {
6226
7305
  width: "16",
@@ -6241,7 +7320,7 @@ function FloatingToolbar({
6241
7320
  );
6242
7321
  })
6243
7322
  ] }, gi)),
6244
- showEditLink ? /* @__PURE__ */ jsx21(
7323
+ showEditLink ? /* @__PURE__ */ jsx23(
6245
7324
  CustomToolbarButton,
6246
7325
  {
6247
7326
  type: "button",
@@ -6255,7 +7334,7 @@ function FloatingToolbar({
6255
7334
  e.preventDefault();
6256
7335
  e.stopPropagation();
6257
7336
  },
6258
- 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 })
6259
7338
  }
6260
7339
  ) : null
6261
7340
  ] })
@@ -6272,7 +7351,7 @@ function StateToggle({
6272
7351
  states,
6273
7352
  onStateChange
6274
7353
  }) {
6275
- return /* @__PURE__ */ jsx21(
7354
+ return /* @__PURE__ */ jsx23(
6276
7355
  ToggleGroup,
6277
7356
  {
6278
7357
  "data-ohw-state-toggle": "",
@@ -6286,7 +7365,7 @@ function StateToggle({
6286
7365
  left: rect.right - 8,
6287
7366
  transform: "translateX(-100%)"
6288
7367
  },
6289
- 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))
6290
7369
  }
6291
7370
  );
6292
7371
  }
@@ -6309,12 +7388,12 @@ function resolveSubdomain(subdomainFromQuery) {
6309
7388
  return "";
6310
7389
  }
6311
7390
  function OhhwellsBridge() {
6312
- const pathname = usePathname();
6313
- const router = useRouter();
7391
+ const pathname = usePathname2();
7392
+ const router = useRouter2();
6314
7393
  const searchParams = useSearchParams();
6315
7394
  const isEditMode = isEditSessionActive();
6316
- const [bridgeRoot, setBridgeRoot] = useState5(null);
6317
- useEffect3(() => {
7395
+ const [bridgeRoot, setBridgeRoot] = useState6(null);
7396
+ useEffect7(() => {
6318
7397
  const figtreeFontId = "ohw-figtree-font";
6319
7398
  if (!document.getElementById(figtreeFontId)) {
6320
7399
  const preconnect1 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.googleapis.com" });
@@ -6322,7 +7401,10 @@ function OhhwellsBridge() {
6322
7401
  const stylesheet = Object.assign(document.createElement("link"), {
6323
7402
  id: figtreeFontId,
6324
7403
  rel: "stylesheet",
6325
- 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"
6326
7408
  });
6327
7409
  document.head.append(preconnect1, preconnect2, stylesheet);
6328
7410
  }
@@ -6339,80 +7421,91 @@ function OhhwellsBridge() {
6339
7421
  const subdomainFromQuery = searchParams.get("subdomain");
6340
7422
  const subdomain = resolveSubdomain(subdomainFromQuery);
6341
7423
  useLinkHrefGuardian(pathname, subdomain, isEditMode);
6342
- const postToParent = useCallback2((data) => {
7424
+ const postToParent2 = useCallback4((data) => {
6343
7425
  if (typeof window !== "undefined" && window.parent !== window) {
6344
7426
  window.parent.postMessage(data, "*");
6345
7427
  }
6346
7428
  }, []);
6347
- const [fetchState, setFetchState] = useState5("idle");
6348
- const autoSaveTimers = useRef3(/* @__PURE__ */ new Map());
6349
- const activeElRef = useRef3(null);
6350
- const selectedElRef = useRef3(null);
6351
- const originalContentRef = useRef3(null);
6352
- const activeStateElRef = useRef3(null);
6353
- const parentScrollRef = useRef3(null);
6354
- const visibleViewportRef = useRef3(null);
6355
- const [dialogPortalContainer, setDialogPortalContainer] = useState5(null);
6356
- 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) => {
6357
7439
  visibleViewportRef.current = node;
6358
7440
  setDialogPortalContainer(node);
6359
7441
  if (node) applyVisibleViewport(node, parentScrollRef.current);
6360
7442
  }, []);
6361
- const toolbarElRef = useRef3(null);
6362
- const glowElRef = useRef3(null);
6363
- const hoveredImageRef = useRef3(null);
6364
- const hoveredImageHasTextOverlapRef = useRef3(false);
6365
- const hoveredGapRef = useRef3(null);
6366
- const imageUnhoverTimerRef = useRef3(null);
6367
- const imageShowTimerRef = useRef3(null);
6368
- const editStylesRef = useRef3(null);
6369
- 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(() => {
6370
7457
  });
6371
- const deactivateRef = useRef3(() => {
7458
+ const selectRef = useRef4(() => {
6372
7459
  });
6373
- const selectRef = useRef3(() => {
7460
+ const selectFrameRef = useRef4(() => {
6374
7461
  });
6375
- const deselectRef = useRef3(() => {
7462
+ const deselectRef = useRef4(() => {
6376
7463
  });
6377
- const reselectNavigationItemRef = useRef3(() => {
7464
+ const reselectNavigationItemRef = useRef4(() => {
6378
7465
  });
6379
- const commitNavigationTextEditRef = useRef3(() => {
7466
+ const commitNavigationTextEditRef = useRef4(() => {
6380
7467
  });
6381
- const refreshActiveCommandsRef = useRef3(() => {
7468
+ const refreshActiveCommandsRef = useRef4(() => {
6382
7469
  });
6383
- const postToParentRef = useRef3(postToParent);
6384
- postToParentRef.current = postToParent;
6385
- const sectionsLoadedRef = useRef3(false);
6386
- const pendingScheduleConfigRequests = useRef3([]);
6387
- const [toolbarRect, setToolbarRect] = useState5(null);
6388
- const [toolbarVariant, setToolbarVariant] = useState5("none");
6389
- 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");
6390
7477
  toolbarVariantRef.current = toolbarVariant;
6391
- const [reorderHrefKey, setReorderHrefKey] = useState5(null);
6392
- const [reorderDragDisabled, setReorderDragDisabled] = useState5(false);
6393
- const [toggleState, setToggleState] = useState5(null);
6394
- const [maxBadge, setMaxBadge] = useState5(null);
6395
- const [activeCommands, setActiveCommands] = useState5(/* @__PURE__ */ new Set());
6396
- const [sectionGap, setSectionGap] = useState5(null);
6397
- const [toolbarShowEditLink, setToolbarShowEditLink] = useState5(false);
6398
- const hoveredItemElRef = useRef3(null);
6399
- const [hoveredItemRect, setHoveredItemRect] = useState5(null);
6400
- const siblingHintElRef = useRef3(null);
6401
- const [siblingHintRect, setSiblingHintRect] = useState5(null);
6402
- const [isItemDragging, setIsItemDragging] = useState5(false);
6403
- const [linkPopover, setLinkPopover] = useState5(null);
6404
- const [sitePages, setSitePages] = useState5([]);
6405
- const [sectionsByPath, setSectionsByPath] = useState5({});
6406
- const sectionsPrefetchGenRef = useRef3(0);
6407
- const setLinkPopoverRef = useRef3(setLinkPopover);
6408
- const linkPopoverPanelRef = useRef3(null);
6409
- const linkPopoverOpenRef = useRef3(false);
6410
- 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);
6411
7503
  setLinkPopoverRef.current = setLinkPopover;
7504
+ linkPopoverSessionRef.current = linkPopover;
6412
7505
  const bumpLinkPopoverGrace = () => {
6413
7506
  linkPopoverGraceUntilRef.current = Date.now() + 350;
6414
7507
  };
6415
- const runSectionsPrefetch = useCallback2((pages) => {
7508
+ const runSectionsPrefetch = useCallback4((pages) => {
6416
7509
  if (!isEditMode || shouldUseDevFixtures() || pages.length === 0) return;
6417
7510
  const gen = ++sectionsPrefetchGenRef.current;
6418
7511
  const paths = pages.map((p) => p.path);
@@ -6431,9 +7524,9 @@ function OhhwellsBridge() {
6431
7524
  );
6432
7525
  });
6433
7526
  }, [isEditMode, pathname]);
6434
- const runSectionsPrefetchRef = useRef3(runSectionsPrefetch);
7527
+ const runSectionsPrefetchRef = useRef4(runSectionsPrefetch);
6435
7528
  runSectionsPrefetchRef.current = runSectionsPrefetch;
6436
- useEffect3(() => {
7529
+ useEffect7(() => {
6437
7530
  if (!linkPopover) return;
6438
7531
  if (hoveredImageRef.current) {
6439
7532
  hoveredImageRef.current = null;
@@ -6441,33 +7534,9 @@ function OhhwellsBridge() {
6441
7534
  }
6442
7535
  hoveredGapRef.current = null;
6443
7536
  setSectionGap(null);
6444
- postToParent({ type: "ow:image-unhover" });
6445
- postToParent({ type: "ow:link-modal-lock", locked: true });
6446
- const html = document.documentElement;
6447
- const body = document.body;
6448
- const prevHtmlOverflow = html.style.overflow;
6449
- const prevBodyOverflow = body.style.overflow;
6450
- html.style.overflow = "hidden";
6451
- body.style.overflow = "hidden";
6452
- const preventBackgroundScroll = (e) => {
6453
- const target = e.target;
6454
- if (target instanceof Element && target.closest('[data-ohw-link-modal-root], [data-slot="dialog-overlay"]')) {
6455
- return;
6456
- }
6457
- e.preventDefault();
6458
- };
6459
- const scrollOpts = { passive: false, capture: true };
6460
- document.addEventListener("wheel", preventBackgroundScroll, scrollOpts);
6461
- document.addEventListener("touchmove", preventBackgroundScroll, scrollOpts);
6462
- return () => {
6463
- postToParent({ type: "ow:link-modal-lock", locked: false });
6464
- html.style.overflow = prevHtmlOverflow;
6465
- body.style.overflow = prevBodyOverflow;
6466
- document.removeEventListener("wheel", preventBackgroundScroll, scrollOpts);
6467
- document.removeEventListener("touchmove", preventBackgroundScroll, scrollOpts);
6468
- };
6469
- }, [linkPopover, postToParent]);
6470
- useEffect3(() => {
7537
+ postToParent2({ type: "ow:image-unhover" });
7538
+ }, [linkPopover, postToParent2]);
7539
+ useEffect7(() => {
6471
7540
  if (!isEditMode) return;
6472
7541
  const useFixtures = shouldUseDevFixtures();
6473
7542
  if (useFixtures) {
@@ -6488,17 +7557,17 @@ function OhhwellsBridge() {
6488
7557
  runSectionsPrefetchRef.current(mapped);
6489
7558
  };
6490
7559
  window.addEventListener("message", onSitePages);
6491
- if (!useFixtures) postToParent({ type: "ow:request-site-pages" });
7560
+ if (!useFixtures) postToParent2({ type: "ow:request-site-pages" });
6492
7561
  return () => window.removeEventListener("message", onSitePages);
6493
- }, [isEditMode, postToParent]);
6494
- useEffect3(() => {
7562
+ }, [isEditMode, postToParent2]);
7563
+ useEffect7(() => {
6495
7564
  if (!isEditMode || shouldUseDevFixtures()) return;
6496
7565
  void loadAllSectionsManifest().then((manifest) => {
6497
7566
  if (Object.keys(manifest).length === 0) return;
6498
7567
  setSectionsByPath((prev) => ({ ...manifest, ...prev }));
6499
7568
  });
6500
7569
  }, [isEditMode]);
6501
- useEffect3(() => {
7570
+ useEffect7(() => {
6502
7571
  const update = () => {
6503
7572
  const el = activeElRef.current ?? selectedElRef.current;
6504
7573
  if (el) setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
@@ -6522,10 +7591,10 @@ function OhhwellsBridge() {
6522
7591
  vvp.removeEventListener("resize", update);
6523
7592
  };
6524
7593
  }, []);
6525
- const refreshStateRules = useCallback2(() => {
7594
+ const refreshStateRules = useCallback4(() => {
6526
7595
  editStylesRef.current?.forceHover && (editStylesRef.current.forceHover.textContent = collectStateRules());
6527
7596
  }, []);
6528
- const processConfigRequest = useCallback2((insertAfterVal) => {
7597
+ const processConfigRequest = useCallback4((insertAfterVal) => {
6529
7598
  const tracker = getSectionsTracker();
6530
7599
  let entries = [];
6531
7600
  try {
@@ -6548,7 +7617,7 @@ function OhhwellsBridge() {
6548
7617
  }
6549
7618
  window.postMessage({ type: "ow:schedule-config", insertAfter: insertAfterVal, scheduleId: null }, "*");
6550
7619
  }, [isEditMode]);
6551
- const deactivate = useCallback2(() => {
7620
+ const deactivate = useCallback4(() => {
6552
7621
  const el = activeElRef.current;
6553
7622
  if (!el) return;
6554
7623
  const key = el.dataset.ohwKey;
@@ -6577,21 +7646,23 @@ function OhhwellsBridge() {
6577
7646
  setMaxBadge(null);
6578
7647
  setActiveCommands(/* @__PURE__ */ new Set());
6579
7648
  setToolbarShowEditLink(false);
6580
- postToParent({ type: "ow:exit-edit" });
6581
- }, [postToParent]);
6582
- const deselect = useCallback2(() => {
7649
+ postToParent2({ type: "ow:exit-edit" });
7650
+ }, [postToParent2]);
7651
+ const deselect = useCallback4(() => {
6583
7652
  selectedElRef.current = null;
6584
7653
  setReorderHrefKey(null);
6585
7654
  setReorderDragDisabled(false);
6586
7655
  siblingHintElRef.current = null;
6587
7656
  setSiblingHintRect(null);
6588
7657
  setIsItemDragging(false);
7658
+ hoveredNavContainerRef.current = null;
7659
+ setHoveredNavContainerRect(null);
6589
7660
  if (!activeElRef.current) {
6590
7661
  setToolbarRect(null);
6591
7662
  setToolbarVariant("none");
6592
7663
  }
6593
7664
  }, []);
6594
- const reselectNavigationItem = useCallback2((navAnchor) => {
7665
+ const reselectNavigationItem = useCallback4((navAnchor) => {
6595
7666
  selectedElRef.current = navAnchor;
6596
7667
  const { key, disabled } = getNavigationItemReorderState(navAnchor);
6597
7668
  setReorderHrefKey(key);
@@ -6601,7 +7672,7 @@ function OhhwellsBridge() {
6601
7672
  setToolbarShowEditLink(false);
6602
7673
  setActiveCommands(/* @__PURE__ */ new Set());
6603
7674
  }, []);
6604
- const commitNavigationTextEdit = useCallback2((navAnchor) => {
7675
+ const commitNavigationTextEdit = useCallback4((navAnchor) => {
6605
7676
  const el = activeElRef.current;
6606
7677
  if (!el) return;
6607
7678
  const key = el.dataset.ohwKey;
@@ -6614,9 +7685,9 @@ function OhhwellsBridge() {
6614
7685
  const html = sanitizeHtml(el.innerHTML);
6615
7686
  const original = originalContentRef.current ?? "";
6616
7687
  if (html !== sanitizeHtml(original)) {
6617
- postToParent({ type: "ow:change", nodes: [{ key, text: html }] });
7688
+ postToParent2({ type: "ow:change", nodes: [{ key, text: html }] });
6618
7689
  const h = document.documentElement.scrollHeight;
6619
- if (h > 50) postToParent({ type: "ow:height", height: h });
7690
+ if (h > 50) postToParent2({ type: "ow:height", height: h });
6620
7691
  }
6621
7692
  }
6622
7693
  el.removeAttribute("contenteditable");
@@ -6624,31 +7695,38 @@ function OhhwellsBridge() {
6624
7695
  setMaxBadge(null);
6625
7696
  setActiveCommands(/* @__PURE__ */ new Set());
6626
7697
  setToolbarShowEditLink(false);
6627
- postToParent({ type: "ow:exit-edit" });
7698
+ postToParent2({ type: "ow:exit-edit" });
6628
7699
  reselectNavigationItem(navAnchor);
6629
- }, [postToParent, reselectNavigationItem]);
6630
- const handleAddNavigationItem = useCallback2(() => {
6631
- const selected = selectedElRef.current;
6632
- if (!selected) return;
6633
- const target = getNavigationItemAddHintTarget(selected);
6634
- siblingHintElRef.current = target;
6635
- 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
+ });
6636
7712
  }, []);
6637
- const handleItemDragStart = useCallback2(() => {
7713
+ const handleItemDragStart = useCallback4(() => {
6638
7714
  siblingHintElRef.current = null;
6639
7715
  setSiblingHintRect(null);
6640
7716
  setIsItemDragging(true);
6641
7717
  }, []);
6642
- const handleItemDragEnd = useCallback2(() => {
7718
+ const handleItemDragEnd = useCallback4(() => {
6643
7719
  setIsItemDragging(false);
6644
7720
  }, []);
6645
7721
  reselectNavigationItemRef.current = reselectNavigationItem;
6646
7722
  commitNavigationTextEditRef.current = commitNavigationTextEdit;
6647
- const select = useCallback2((anchor) => {
7723
+ const select = useCallback4((anchor) => {
6648
7724
  if (!isNavigationItem(anchor)) return;
6649
7725
  if (activeElRef.current) deactivate();
6650
7726
  selectedElRef.current = anchor;
6651
7727
  clearHrefKeyHover(anchor);
7728
+ hoveredNavContainerRef.current = null;
7729
+ setHoveredNavContainerRect(null);
6652
7730
  setHoveredItemRect(null);
6653
7731
  hoveredItemElRef.current = null;
6654
7732
  siblingHintElRef.current = null;
@@ -6662,13 +7740,32 @@ function OhhwellsBridge() {
6662
7740
  setToolbarShowEditLink(false);
6663
7741
  setActiveCommands(/* @__PURE__ */ new Set());
6664
7742
  }, [deactivate]);
6665
- 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) => {
6666
7763
  if (activeElRef.current === el) return;
6667
7764
  selectedElRef.current = null;
6668
7765
  deactivate();
6669
7766
  if (hoveredImageRef.current) {
6670
7767
  hoveredImageRef.current = null;
6671
- postToParentRef.current({ type: "ow:image-unhover" });
7768
+ setMediaHover(null);
6672
7769
  }
6673
7770
  setToolbarVariant("rich-text");
6674
7771
  siblingHintElRef.current = null;
@@ -6680,6 +7777,9 @@ function OhhwellsBridge() {
6680
7777
  activeElRef.current = el;
6681
7778
  originalContentRef.current = el.innerHTML;
6682
7779
  el.focus();
7780
+ if (options?.caretX !== void 0 && options?.caretY !== void 0) {
7781
+ placeCaretAtPoint(el, options.caretX, options.caretY);
7782
+ }
6683
7783
  setToolbarShowEditLink(Boolean(getHrefKeyFromElement(el)));
6684
7784
  const navAnchor = getNavigationItemAnchor(el);
6685
7785
  if (navAnchor) {
@@ -6691,12 +7791,13 @@ function OhhwellsBridge() {
6691
7791
  setReorderDragDisabled(reorderDisabled);
6692
7792
  }
6693
7793
  setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
6694
- postToParent({ type: "ow:enter-edit", key: el.dataset.ohwKey });
7794
+ postToParent2({ type: "ow:enter-edit", key: el.dataset.ohwKey });
6695
7795
  requestAnimationFrame(() => refreshActiveCommandsRef.current());
6696
- }, [deactivate, postToParent]);
7796
+ }, [deactivate, postToParent2]);
6697
7797
  activateRef.current = activate;
6698
7798
  deactivateRef.current = deactivate;
6699
7799
  selectRef.current = select;
7800
+ selectFrameRef.current = selectFrame;
6700
7801
  deselectRef.current = deselect;
6701
7802
  useLayoutEffect2(() => {
6702
7803
  if (!subdomain || isEditMode) {
@@ -6707,11 +7808,12 @@ function OhhwellsBridge() {
6707
7808
  const imageLoads = [];
6708
7809
  for (const [key, val] of Object.entries(content)) {
6709
7810
  if (key === "__ohw_sections") continue;
7811
+ if (applyVideoSettingNode(key, val)) continue;
6710
7812
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
6711
7813
  if (el.dataset.ohwEditable === "image") {
6712
7814
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
6713
7815
  if (img && img.src !== val) {
6714
- img.src = val;
7816
+ applyEditableImageSrc(img, val);
6715
7817
  imageLoads.push(new Promise((resolve) => {
6716
7818
  img.onload = () => resolve();
6717
7819
  img.onerror = () => resolve();
@@ -6720,6 +7822,15 @@ function OhhwellsBridge() {
6720
7822
  } else if (el.dataset.ohwEditable === "bg-image") {
6721
7823
  const next = `url('${val}')`;
6722
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
+ }
6723
7834
  } else if (el.dataset.ohwEditable === "link") {
6724
7835
  applyLinkHref(el, val);
6725
7836
  } else if (el.innerHTML !== val) {
@@ -6728,6 +7839,7 @@ function OhhwellsBridge() {
6728
7839
  });
6729
7840
  applyLinkByKey(key, val);
6730
7841
  }
7842
+ reconcileNavbarItemsFromContent(content);
6731
7843
  enforceLinkHrefs();
6732
7844
  initSectionsFromContent(content, true);
6733
7845
  sectionsLoadedRef.current = true;
@@ -6756,7 +7868,7 @@ function OhhwellsBridge() {
6756
7868
  cancelled = true;
6757
7869
  };
6758
7870
  }, [subdomain, isEditMode]);
6759
- useEffect3(() => {
7871
+ useEffect7(() => {
6760
7872
  if (!subdomain || isEditMode) return;
6761
7873
  let debounceTimer = null;
6762
7874
  let observer = null;
@@ -6768,13 +7880,17 @@ function OhhwellsBridge() {
6768
7880
  try {
6769
7881
  for (const [key, val] of Object.entries(content)) {
6770
7882
  if (key === "__ohw_sections") continue;
7883
+ if (applyVideoSettingNode(key, val)) continue;
6771
7884
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
6772
7885
  if (el.dataset.ohwEditable === "image") {
6773
7886
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
6774
- if (img && img.src !== val) img.src = val;
7887
+ if (img && img.src !== val) applyEditableImageSrc(img, val);
6775
7888
  } else if (el.dataset.ohwEditable === "bg-image") {
6776
7889
  const next = `url('${val}')`;
6777
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);
6778
7894
  } else if (el.dataset.ohwEditable === "link") {
6779
7895
  applyLinkHref(el, val);
6780
7896
  } else if (el.innerHTML !== val) {
@@ -6783,6 +7899,7 @@ function OhhwellsBridge() {
6783
7899
  });
6784
7900
  applyLinkByKey(key, val);
6785
7901
  }
7902
+ reconcileNavbarItemsFromContent(content);
6786
7903
  } finally {
6787
7904
  observer?.observe(document.body, { childList: true, subtree: true });
6788
7905
  }
@@ -6806,20 +7923,38 @@ function OhhwellsBridge() {
6806
7923
  const visible = Boolean(subdomain) && fetchState !== "done";
6807
7924
  el.style.display = visible ? "flex" : "none";
6808
7925
  }, [subdomain, fetchState]);
6809
- useEffect3(() => {
6810
- postToParent({ type: "ow:navigation", path: pathname });
6811
- }, [pathname, postToParent]);
6812
- useEffect3(() => {
7926
+ useEffect7(() => {
7927
+ postToParent2({ type: "ow:navigation", path: pathname });
7928
+ }, [pathname, postToParent2]);
7929
+ useEffect7(() => {
6813
7930
  if (!isEditMode) return;
7931
+ if (linkPopoverSessionRef.current?.intent === "add-nav") return;
7932
+ if (document.querySelector("[data-ohw-section-picker]")) return;
6814
7933
  setLinkPopover(null);
6815
7934
  deselectRef.current();
6816
7935
  deactivateRef.current();
6817
7936
  }, [pathname, isEditMode]);
6818
- 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(() => {
6819
7954
  if (!isEditMode) return;
6820
7955
  const measure = () => {
6821
7956
  const h = document.body.scrollHeight;
6822
- if (h > 50) postToParent({ type: "ow:height", height: h });
7957
+ if (h > 50) postToParent2({ type: "ow:height", height: h });
6823
7958
  };
6824
7959
  const t1 = setTimeout(measure, 50);
6825
7960
  const t2 = setTimeout(measure, 500);
@@ -6838,8 +7973,8 @@ function OhhwellsBridge() {
6838
7973
  if (resizeTimer) clearTimeout(resizeTimer);
6839
7974
  window.removeEventListener("resize", handleResize);
6840
7975
  };
6841
- }, [pathname, isEditMode, postToParent]);
6842
- useEffect3(() => {
7976
+ }, [pathname, isEditMode, postToParent2]);
7977
+ useEffect7(() => {
6843
7978
  if (!subdomainFromQuery || isEditMode) return;
6844
7979
  const handleClick = (e) => {
6845
7980
  const anchor = e.target.closest("a");
@@ -6855,7 +7990,7 @@ function OhhwellsBridge() {
6855
7990
  document.addEventListener("click", handleClick, true);
6856
7991
  return () => document.removeEventListener("click", handleClick, true);
6857
7992
  }, [subdomainFromQuery, isEditMode, router]);
6858
- useEffect3(() => {
7993
+ useEffect7(() => {
6859
7994
  if (!isEditMode) {
6860
7995
  editStylesRef.current?.base.remove();
6861
7996
  editStylesRef.current?.forceHover.remove();
@@ -6878,8 +8013,9 @@ function OhhwellsBridge() {
6878
8013
  [data-ohw-editable] {
6879
8014
  display: block;
6880
8015
  }
6881
- [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; }
6882
8017
  [data-ohw-editable="image"], [data-ohw-editable="image"] *,
8018
+ [data-ohw-editable="video"], [data-ohw-editable="video"] *,
6883
8019
  [data-ohw-editable="bg-image"], [data-ohw-editable="bg-image"] * { cursor: pointer !important; }
6884
8020
  [data-ohw-editable="link"], [data-ohw-editable="link"] * { cursor: pointer !important; }
6885
8021
  [data-ohw-hovered]:not([contenteditable]):not([data-ohw-href-key]) {
@@ -6928,6 +8064,13 @@ function OhhwellsBridge() {
6928
8064
  if (target.closest("[data-ohw-max-badge]")) return;
6929
8065
  if (isInsideLinkEditor(target)) return;
6930
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
+ }
6931
8074
  e.preventDefault();
6932
8075
  e.stopPropagation();
6933
8076
  return;
@@ -6942,14 +8085,15 @@ function OhhwellsBridge() {
6942
8085
  bumpLinkPopoverGrace();
6943
8086
  setLinkPopoverRef.current({
6944
8087
  key: editable.dataset.ohwKey ?? "",
6945
- target: getLinkHref(editable)
8088
+ mode: "edit",
8089
+ target: getLinkHref2(editable)
6946
8090
  });
6947
8091
  return;
6948
8092
  }
6949
- if (editable.dataset.ohwEditable === "image" || editable.dataset.ohwEditable === "bg-image") {
8093
+ if (isMediaEditable(editable)) {
6950
8094
  e.preventDefault();
6951
8095
  e.stopPropagation();
6952
- 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" });
6953
8097
  return;
6954
8098
  }
6955
8099
  const hrefCtx = getHrefKeyFromElement(editable);
@@ -6958,7 +8102,8 @@ function OhhwellsBridge() {
6958
8102
  e.preventDefault();
6959
8103
  e.stopPropagation();
6960
8104
  if (selectedElRef.current === navAnchor) {
6961
- activateRef.current(editable);
8105
+ if (e.detail >= 2) return;
8106
+ activateRef.current(editable, { caretX: e.clientX, caretY: e.clientY });
6962
8107
  return;
6963
8108
  }
6964
8109
  selectRef.current(navAnchor);
@@ -6977,6 +8122,22 @@ function OhhwellsBridge() {
6977
8122
  selectRef.current(hrefAnchor);
6978
8123
  return;
6979
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
+ }
6980
8141
  if (activeElRef.current) {
6981
8142
  const sel = window.getSelection();
6982
8143
  if (sel && !sel.isCollapsed) {
@@ -7002,10 +8163,48 @@ function OhhwellsBridge() {
7002
8163
  deselectRef.current();
7003
8164
  deactivateRef.current();
7004
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
+ };
7005
8188
  const handleMouseOver = (e) => {
7006
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
+ }
7007
8204
  const navAnchor = getNavigationItemAnchor(target);
7008
8205
  if (navAnchor) {
8206
+ hoveredNavContainerRef.current = null;
8207
+ setHoveredNavContainerRect(null);
7009
8208
  const selected2 = selectedElRef.current;
7010
8209
  if (selected2 === navAnchor || selected2?.contains(navAnchor)) return;
7011
8210
  clearHrefKeyHover(navAnchor);
@@ -7017,7 +8216,7 @@ function OhhwellsBridge() {
7017
8216
  if (!editable) return;
7018
8217
  const selected = selectedElRef.current;
7019
8218
  if (selected && (selected === editable || selected.contains(editable))) return;
7020
- if (editable.dataset.ohwEditable !== "image" && editable.dataset.ohwEditable !== "bg-image" && !editable.hasAttribute("contenteditable")) {
8219
+ if (!isMediaEditable(editable) && !editable.hasAttribute("contenteditable")) {
7021
8220
  const hoverTarget = editable.closest("[data-ohw-href-key]") ?? editable;
7022
8221
  if (hoverTarget.hasAttribute("data-ohw-href-key")) {
7023
8222
  clearHrefKeyHover(hoverTarget);
@@ -7030,6 +8229,14 @@ function OhhwellsBridge() {
7030
8229
  };
7031
8230
  const handleMouseOut = (e) => {
7032
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
+ }
7033
8240
  const navAnchor = getNavigationItemAnchor(target);
7034
8241
  if (navAnchor) {
7035
8242
  const related2 = e.relatedTarget instanceof Element ? e.relatedTarget : null;
@@ -7045,7 +8252,7 @@ function OhhwellsBridge() {
7045
8252
  if (!editable) return;
7046
8253
  const related = e.relatedTarget instanceof Element ? e.relatedTarget : null;
7047
8254
  if (related?.closest("[data-ohw-drag-handle-container], [data-ohw-item-interaction]")) return;
7048
- if (editable.dataset.ohwEditable !== "image" && editable.dataset.ohwEditable !== "bg-image") {
8255
+ if (!isMediaEditable(editable)) {
7049
8256
  const hoverTarget = editable.closest("[data-ohw-href-key]") ?? editable;
7050
8257
  if (hoverTarget.hasAttribute("data-ohw-href-key")) {
7051
8258
  if (!related?.closest("[data-ohw-href-key]")) {
@@ -7091,23 +8298,26 @@ function OhhwellsBridge() {
7091
8298
  }
7092
8299
  return { top, left, width: Math.max(0, right - left), height: Math.max(0, bottom - top) };
7093
8300
  };
7094
- const postImageHover = (imgEl, isDragOver = false, forceTextOverlap) => {
8301
+ const showImageHover = (imgEl, isDragOver = false, forceTextOverlap) => {
7095
8302
  const r2 = getVisibleRect(imgEl);
7096
8303
  const hasTextOverlap = forceTextOverlap ?? false;
7097
8304
  hoveredImageHasTextOverlapRef.current = hasTextOverlap;
7098
- postToParentRef.current({
7099
- type: "ow:image-hover",
8305
+ const video = imgEl.dataset.ohwEditable === "video" ? getVideoEl(imgEl) : null;
8306
+ setMediaHover({
7100
8307
  key: imgEl.dataset.ohwKey ?? "",
7101
8308
  rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height },
8309
+ elementType: imgEl.dataset.ohwEditable ?? "image",
7102
8310
  hasTextOverlap,
7103
- ...isDragOver ? { isDragOver: true } : {}
8311
+ isDragOver,
8312
+ ...video ? { videoAutoplay: video.autoplay, videoMuted: video.muted } : {}
7104
8313
  });
7105
8314
  const track = imgEl.closest("[data-ohw-hover-pause]");
7106
8315
  if (track) track.setAttribute("data-ohw-hover-paused", "");
7107
8316
  };
8317
+ const clearImageHover = () => setMediaHover(null);
7108
8318
  const findImageAtPoint = (clientX, clientY, fromParentViewport) => {
7109
8319
  const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
7110
- const images = Array.from(document.querySelectorAll('[data-ohw-editable="image"], [data-ohw-editable="bg-image"]'));
8320
+ const images = Array.from(document.querySelectorAll(MEDIA_SELECTOR));
7111
8321
  const matches = [];
7112
8322
  for (let i = images.length - 1; i >= 0; i--) {
7113
8323
  const el = images[i];
@@ -7123,7 +8333,9 @@ function OhhwellsBridge() {
7123
8333
  if (x >= r2.left && x <= r2.left + r2.width && y >= r2.top && y <= r2.top + r2.height) matches.push(el);
7124
8334
  }
7125
8335
  if (matches.length === 0) return null;
7126
- 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
+ );
7127
8339
  const candidatePool = imageTypeMatches.length > 0 ? imageTypeMatches : matches;
7128
8340
  const smallest = candidatePool.reduce((best, el) => {
7129
8341
  const br = getVisibleRect(best);
@@ -7146,47 +8358,91 @@ function OhhwellsBridge() {
7146
8358
  }
7147
8359
  return smallest;
7148
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
+ };
7149
8412
  const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
7150
8413
  if (linkPopoverOpenRef.current) {
7151
- if (hoveredImageRef.current) {
7152
- hoveredImageRef.current = null;
7153
- hoveredImageHasTextOverlapRef.current = false;
7154
- resumeAnimTracks();
7155
- postToParentRef.current({ type: "ow:image-unhover" });
7156
- }
8414
+ dismissImageHover();
8415
+ return;
8416
+ }
8417
+ const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
8418
+ if (isPointOverNavigation(x, y)) {
8419
+ dismissImageHover();
8420
+ probeNavigationHoverAt(x, y);
7157
8421
  return;
7158
8422
  }
8423
+ hoveredNavContainerRef.current = null;
8424
+ setHoveredNavContainerRect(null);
7159
8425
  const toggleEl = document.querySelector("[data-ohw-state-toggle]");
7160
8426
  if (toggleEl) {
7161
- const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
7162
8427
  const tr = toggleEl.getBoundingClientRect();
7163
8428
  if (x >= tr.left && x <= tr.right && y >= tr.top && y <= tr.bottom) {
7164
- if (hoveredImageRef.current) {
7165
- hoveredImageRef.current = null;
7166
- resumeAnimTracks();
7167
- postToParentRef.current({ type: "ow:image-unhover" });
7168
- }
8429
+ dismissImageHover();
7169
8430
  return;
7170
8431
  }
7171
8432
  }
7172
- if (activeElRef.current) {
7173
- if (hoveredImageRef.current) {
7174
- hoveredImageRef.current = null;
7175
- hoveredImageHasTextOverlapRef.current = false;
7176
- resumeAnimTracks();
7177
- postToParentRef.current({ type: "ow:image-unhover" });
7178
- }
8433
+ const imgEl = findImageAtPoint(clientX, clientY, fromParentViewport);
8434
+ const activeEl = activeElRef.current;
8435
+ if (activeEl && (!imgEl || imgEl.contains(activeEl))) {
8436
+ dismissImageHover();
7179
8437
  return;
7180
8438
  }
7181
- const imgEl = findImageAtPoint(clientX, clientY, fromParentViewport);
7182
8439
  if (imgEl) {
7183
- const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
7184
8440
  const topEl = document.elementFromPoint(x, y);
7185
8441
  if (topEl?.closest('[data-ohw-toolbar], [data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
7186
8442
  if (hoveredImageRef.current) {
7187
8443
  hoveredImageRef.current = null;
7188
8444
  resumeAnimTracks();
7189
- postToParentRef.current({ type: "ow:image-unhover" });
8445
+ clearImageHover();
7190
8446
  }
7191
8447
  return;
7192
8448
  }
@@ -7195,13 +8451,13 @@ function OhhwellsBridge() {
7195
8451
  hoveredImageRef.current = null;
7196
8452
  hoveredImageHasTextOverlapRef.current = false;
7197
8453
  resumeAnimTracks();
7198
- postToParentRef.current({ type: "ow:image-unhover" });
8454
+ clearImageHover();
7199
8455
  }
7200
8456
  return;
7201
8457
  }
7202
8458
  const isStateCardImage = !!imgEl.closest("[data-ohw-editable-state]");
7203
8459
  const textEditable = Array.from(
7204
- document.querySelectorAll('[data-ohw-editable]:not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"])')
8460
+ document.querySelectorAll(NON_MEDIA_SELECTOR)
7205
8461
  ).find((el) => {
7206
8462
  const er = el.getBoundingClientRect();
7207
8463
  return x >= er.left && x <= er.right && y >= er.top && y <= er.bottom;
@@ -7220,14 +8476,14 @@ function OhhwellsBridge() {
7220
8476
  hoveredImageRef.current = null;
7221
8477
  hoveredImageHasTextOverlapRef.current = false;
7222
8478
  resumeAnimTracks();
7223
- postToParentRef.current({ type: "ow:image-unhover" });
8479
+ clearImageHover();
7224
8480
  }
7225
8481
  return;
7226
8482
  }
7227
8483
  if (isStateCardImage) {
7228
8484
  if (imgEl !== hoveredImageRef.current || !hoveredImageHasTextOverlapRef.current) {
7229
8485
  hoveredImageRef.current = imgEl;
7230
- postImageHover(imgEl, isDragOver, true);
8486
+ showImageHover(imgEl, isDragOver, true);
7231
8487
  }
7232
8488
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
7233
8489
  if (textEditable && !isInsideNavigationItem(textEditable)) {
@@ -7239,7 +8495,7 @@ function OhhwellsBridge() {
7239
8495
  hoveredImageRef.current = null;
7240
8496
  hoveredImageHasTextOverlapRef.current = false;
7241
8497
  resumeAnimTracks();
7242
- postToParentRef.current({ type: "ow:image-unhover" });
8498
+ clearImageHover();
7243
8499
  }
7244
8500
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
7245
8501
  if (textEditable && !isInsideNavigationItem(textEditable)) {
@@ -7251,7 +8507,7 @@ function OhhwellsBridge() {
7251
8507
  if (hoveredImageRef.current) {
7252
8508
  hoveredImageRef.current = null;
7253
8509
  resumeAnimTracks();
7254
- postToParentRef.current({ type: "ow:image-unhover" });
8510
+ clearImageHover();
7255
8511
  }
7256
8512
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
7257
8513
  return;
@@ -7259,9 +8515,9 @@ function OhhwellsBridge() {
7259
8515
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
7260
8516
  if (imgEl !== hoveredImageRef.current || hoveredImageHasTextOverlapRef.current) {
7261
8517
  hoveredImageRef.current = imgEl;
7262
- postImageHover(imgEl, isDragOver);
8518
+ showImageHover(imgEl, isDragOver);
7263
8519
  } else if (isDragOver) {
7264
- postImageHover(imgEl, true);
8520
+ showImageHover(imgEl, true);
7265
8521
  }
7266
8522
  } else {
7267
8523
  const inIframeView = clientX >= 0 && clientY >= 0 && clientX <= window.innerWidth && clientY <= window.innerHeight;
@@ -7278,14 +8534,14 @@ function OhhwellsBridge() {
7278
8534
  hoveredImageRef.current = null;
7279
8535
  hoveredImageHasTextOverlapRef.current = false;
7280
8536
  resumeAnimTracks();
7281
- postToParentRef.current({ type: "ow:image-unhover" });
8537
+ clearImageHover();
7282
8538
  }
7283
- const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
8539
+ const { x: x2, y: y2 } = toProbeCoords(clientX, clientY, fromParentViewport);
7284
8540
  const textEl = Array.from(
7285
- document.querySelectorAll('[data-ohw-editable]:not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"])')
8541
+ document.querySelectorAll(NON_MEDIA_SELECTOR)
7286
8542
  ).find((el) => {
7287
8543
  const er = el.getBoundingClientRect();
7288
- 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;
7289
8545
  });
7290
8546
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
7291
8547
  if (textEl && !textEl.hasAttribute("contenteditable")) {
@@ -7388,18 +8644,20 @@ function OhhwellsBridge() {
7388
8644
  return;
7389
8645
  }
7390
8646
  e.dataTransfer.dropEffect = "copy";
7391
- if (hoveredImageRef.current !== el) {
8647
+ if (dragOverElRef.current !== el) {
8648
+ dragOverElRef.current = el;
7392
8649
  hoveredImageRef.current = el;
7393
8650
  const isStateCard = !!el.closest("[data-ohw-editable-state]");
7394
- postImageHover(el, true, isStateCard ? true : void 0);
8651
+ showImageHover(el, true, isStateCard ? true : void 0);
7395
8652
  }
7396
8653
  };
7397
8654
  const handleDragLeave = (e) => {
7398
8655
  const imgEl = findImageAtPoint(e.clientX, e.clientY, false);
7399
8656
  if (imgEl) return;
8657
+ dragOverElRef.current = null;
7400
8658
  hoveredImageRef.current = null;
7401
8659
  resumeAnimTracks();
7402
- postToParentRef.current({ type: "ow:image-unhover" });
8660
+ clearImageHover();
7403
8661
  };
7404
8662
  const handleDrop = (e) => {
7405
8663
  e.preventDefault();
@@ -7407,7 +8665,9 @@ function OhhwellsBridge() {
7407
8665
  if (!el) return;
7408
8666
  const file = e.dataTransfer?.files?.[0];
7409
8667
  if (file) {
7410
- 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) {
7411
8671
  const key = el.dataset.ohwKey ?? "";
7412
8672
  const { name, type: mimeType } = file;
7413
8673
  const r2 = el.getBoundingClientRect();
@@ -7416,12 +8676,13 @@ function OhhwellsBridge() {
7416
8676
  window.parent.postMessage({ type: "ow:image-drop", key, name, mimeType, buf, rect }, "*", [buf]);
7417
8677
  });
7418
8678
  } else {
7419
- postToParentRef.current({ type: "ow:image-drop-invalid" });
8679
+ postToParentRef.current({ type: "ow:image-drop-invalid", expected: wantsVideo ? "video" : "image" });
7420
8680
  }
7421
8681
  }
8682
+ dragOverElRef.current = null;
7422
8683
  hoveredImageRef.current = null;
7423
8684
  resumeAnimTracks();
7424
- postToParentRef.current({ type: "ow:image-unhover" });
8685
+ clearImageHover();
7425
8686
  };
7426
8687
  const handleImageUrl = (e) => {
7427
8688
  if (e.data?.type !== "ow:image-url") return;
@@ -7439,7 +8700,11 @@ function OhhwellsBridge() {
7439
8700
  }
7440
8701
  });
7441
8702
  hoveredImageRef.current = null;
7442
- 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
+ });
7443
8708
  };
7444
8709
  let found = false;
7445
8710
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -7469,11 +8734,24 @@ function OhhwellsBridge() {
7469
8734
  };
7470
8735
  img.onload = onReady;
7471
8736
  img.onerror = onReady;
7472
- img.src = url;
8737
+ applyEditableImageSrc(img, url);
7473
8738
  if (img.complete && img.naturalWidth > 0) {
7474
8739
  requestAnimationFrame(() => onReady());
7475
8740
  }
7476
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
+ }
7477
8755
  }
7478
8756
  });
7479
8757
  if (!found) notify();
@@ -7540,12 +8818,16 @@ function OhhwellsBridge() {
7540
8818
  sectionsJson = val;
7541
8819
  continue;
7542
8820
  }
8821
+ if (applyVideoSettingNode(key, val)) continue;
7543
8822
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
7544
8823
  if (el.dataset.ohwEditable === "image") {
7545
8824
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
7546
- if (img) img.src = val;
8825
+ if (img) applyEditableImageSrc(img, val);
7547
8826
  } else if (el.dataset.ohwEditable === "bg-image") {
7548
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);
7549
8831
  } else if (el.dataset.ohwEditable === "link") {
7550
8832
  applyLinkHref(el, val);
7551
8833
  } else {
@@ -7559,6 +8841,8 @@ function OhhwellsBridge() {
7559
8841
  sectionsLoadedRef.current = true;
7560
8842
  pendingScheduleConfigRequests.current.splice(0).forEach(processConfigRequest);
7561
8843
  }
8844
+ editContentRef.current = { ...editContentRef.current, ...content };
8845
+ reconcileNavbarItemsFromContent(editContentRef.current);
7562
8846
  enforceLinkHrefs();
7563
8847
  postToParentRef.current({ type: "ow:hydrate-done" });
7564
8848
  };
@@ -7566,6 +8850,7 @@ function OhhwellsBridge() {
7566
8850
  const handleDeactivate = (e) => {
7567
8851
  if (e.data?.type !== "ow:deactivate") return;
7568
8852
  if (Date.now() < linkPopoverGraceUntilRef.current) return;
8853
+ if (document.querySelector("[data-ohw-section-picker]")) return;
7569
8854
  if (linkPopoverOpenRef.current) {
7570
8855
  setLinkPopoverRef.current(null);
7571
8856
  return;
@@ -7575,12 +8860,32 @@ function OhhwellsBridge() {
7575
8860
  };
7576
8861
  window.addEventListener("message", handleDeactivate);
7577
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
+ }
7578
8873
  if (e.key === "Escape" && linkPopoverOpenRef.current) {
7579
8874
  setLinkPopoverRef.current(null);
7580
8875
  return;
7581
8876
  }
7582
8877
  if (e.key === "Escape" && selectedElRef.current && !activeElRef.current) {
7583
- 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
+ }
7584
8889
  return;
7585
8890
  }
7586
8891
  if (e.key === "Escape" && activeElRef.current) {
@@ -7638,18 +8943,18 @@ function OhhwellsBridge() {
7638
8943
  if (hoveredItemElRef.current) {
7639
8944
  setHoveredItemRect(hoveredItemElRef.current.getBoundingClientRect());
7640
8945
  }
8946
+ if (hoveredNavContainerRef.current) {
8947
+ setHoveredNavContainerRect(hoveredNavContainerRef.current.getBoundingClientRect());
8948
+ }
7641
8949
  if (siblingHintElRef.current) {
7642
8950
  setSiblingHintRect(siblingHintElRef.current.getBoundingClientRect());
7643
8951
  }
7644
8952
  if (hoveredImageRef.current) {
7645
8953
  const el = hoveredImageRef.current;
7646
8954
  const r2 = el.getBoundingClientRect();
7647
- postToParentRef.current({
7648
- type: "ow:image-hover",
7649
- key: el.dataset.ohwKey ?? "",
7650
- rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height },
7651
- hasTextOverlap: hoveredImageHasTextOverlapRef.current
7652
- });
8955
+ setMediaHover(
8956
+ (prev) => prev ? { ...prev, rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height } } : prev
8957
+ );
7653
8958
  }
7654
8959
  };
7655
8960
  const handleSave = (e) => {
@@ -7738,19 +9043,39 @@ function OhhwellsBridge() {
7738
9043
  refreshActiveCommandsRef.current = handleSelectionChange;
7739
9044
  const handleDocMouseLeave = () => {
7740
9045
  hoveredImageRef.current = null;
9046
+ setMediaHover(null);
7741
9047
  document.querySelectorAll("[data-ohw-state-hovered]").forEach((el) => el.removeAttribute("data-ohw-state-hovered"));
7742
9048
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
7743
9049
  activeStateElRef.current = null;
7744
9050
  setToggleState(null);
7745
9051
  };
7746
- const handleAnimLock = (e) => {
7747
- if (e.data?.type !== "ow:anim-lock") return;
7748
- 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
+ });
7749
9070
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
7750
9071
  const track = el.closest("[data-ohw-hover-pause]");
7751
- if (track) {
9072
+ if (!track) return;
9073
+ if (uploading) {
7752
9074
  uploadLockedTracks.add(track);
7753
9075
  track.setAttribute("data-ohw-hover-paused", "");
9076
+ } else {
9077
+ uploadLockedTracks.delete(track);
9078
+ track.removeAttribute("data-ohw-hover-paused");
7754
9079
  }
7755
9080
  });
7756
9081
  };
@@ -7792,7 +9117,7 @@ function OhhwellsBridge() {
7792
9117
  if (e.data?.type !== "ow:click-at") return;
7793
9118
  const { clientX, clientY } = e.data;
7794
9119
  const allImages = Array.from(
7795
- document.querySelectorAll('[data-ohw-editable="image"], [data-ohw-editable="bg-image"]')
9120
+ document.querySelectorAll(MEDIA_SELECTOR)
7796
9121
  ).filter((el) => !!el.closest("[data-ohw-editable-state]"));
7797
9122
  const stateCardImage = allImages.find((el) => {
7798
9123
  const ownerCard = el.closest("[data-ohw-editable-state]");
@@ -7807,21 +9132,55 @@ function OhhwellsBridge() {
7807
9132
  return clientX >= r2.left && clientX <= r2.right && clientY >= r2.top && clientY <= r2.bottom;
7808
9133
  });
7809
9134
  if (stateCardImage) {
7810
- 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" });
7811
9136
  return;
7812
9137
  }
7813
9138
  const textEditable = Array.from(
7814
- document.querySelectorAll(
7815
- '[data-ohw-editable]:not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"])'
7816
- )
9139
+ document.querySelectorAll(NON_MEDIA_SELECTOR)
7817
9140
  ).find((el) => {
7818
9141
  const r2 = el.getBoundingClientRect();
7819
9142
  return clientX >= r2.left && clientX <= r2.right && clientY >= r2.top && clientY <= r2.bottom;
7820
9143
  });
7821
9144
  if (textEditable) {
7822
- 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 });
7823
9156
  return;
7824
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
+ }
7825
9184
  deactivateRef.current();
7826
9185
  };
7827
9186
  window.addEventListener("message", handleSave);
@@ -7831,7 +9190,7 @@ function OhhwellsBridge() {
7831
9190
  window.addEventListener("message", handleClearSchedulingWidget);
7832
9191
  window.addEventListener("message", handleRemoveSchedulingSection);
7833
9192
  window.addEventListener("message", handleImageUrl);
7834
- window.addEventListener("message", handleAnimLock);
9193
+ window.addEventListener("message", handleImageUploading);
7835
9194
  window.addEventListener("message", handleCanvasHeight);
7836
9195
  window.addEventListener("message", handleParentScroll);
7837
9196
  window.addEventListener("message", handlePointerSync);
@@ -7843,6 +9202,7 @@ function OhhwellsBridge() {
7843
9202
  };
7844
9203
  window.addEventListener("resize", handleViewportResize, { passive: true });
7845
9204
  document.addEventListener("click", handleClick, true);
9205
+ document.addEventListener("dblclick", handleDblClick, true);
7846
9206
  document.addEventListener("paste", handlePaste, true);
7847
9207
  document.addEventListener("input", handleInput, true);
7848
9208
  document.addEventListener("mouseover", handleMouseOver, true);
@@ -7857,6 +9217,7 @@ function OhhwellsBridge() {
7857
9217
  window.addEventListener("scroll", handleScroll, true);
7858
9218
  return () => {
7859
9219
  document.removeEventListener("click", handleClick, true);
9220
+ document.removeEventListener("dblclick", handleDblClick, true);
7860
9221
  document.removeEventListener("paste", handlePaste, true);
7861
9222
  document.removeEventListener("input", handleInput, true);
7862
9223
  document.removeEventListener("mouseover", handleMouseOver, true);
@@ -7876,7 +9237,7 @@ function OhhwellsBridge() {
7876
9237
  window.removeEventListener("message", handleClearSchedulingWidget);
7877
9238
  window.removeEventListener("message", handleRemoveSchedulingSection);
7878
9239
  window.removeEventListener("message", handleImageUrl);
7879
- window.removeEventListener("message", handleAnimLock);
9240
+ window.removeEventListener("message", handleImageUploading);
7880
9241
  window.removeEventListener("message", handleCanvasHeight);
7881
9242
  window.removeEventListener("message", handleParentScroll);
7882
9243
  window.removeEventListener("resize", handleViewportResize);
@@ -7890,7 +9251,7 @@ function OhhwellsBridge() {
7890
9251
  if (imageShowTimerRef.current) clearTimeout(imageShowTimerRef.current);
7891
9252
  };
7892
9253
  }, [isEditMode, refreshStateRules]);
7893
- useEffect3(() => {
9254
+ useEffect7(() => {
7894
9255
  const handler = (e) => {
7895
9256
  if (e.data?.type !== "ow:request-schedule-config") return;
7896
9257
  const insertAfterVal = e.data.insertAfter;
@@ -7906,7 +9267,7 @@ function OhhwellsBridge() {
7906
9267
  window.addEventListener("message", handler);
7907
9268
  return () => window.removeEventListener("message", handler);
7908
9269
  }, [processConfigRequest]);
7909
- useEffect3(() => {
9270
+ useEffect7(() => {
7910
9271
  if (!isEditMode) return;
7911
9272
  document.querySelectorAll("[data-ohw-active-state]").forEach((el) => {
7912
9273
  el.removeAttribute("data-ohw-active-state");
@@ -7927,27 +9288,27 @@ function OhhwellsBridge() {
7927
9288
  const next = { ...prev, [pathKey]: sections };
7928
9289
  return shouldUseDevFixtures() ? { ...DEV_SECTIONS_BY_PATH, ...next } : next;
7929
9290
  });
7930
- postToParent({ type: "ow:ready", version: "1", path: pathname, nodes: collectEditableNodes(), sections });
7931
- 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" });
7932
9293
  }, 150);
7933
9294
  return () => {
7934
9295
  cancelAnimationFrame(raf);
7935
9296
  clearTimeout(timer);
7936
9297
  };
7937
- }, [pathname, isEditMode, refreshStateRules, postToParent]);
7938
- useEffect3(() => {
9298
+ }, [pathname, isEditMode, refreshStateRules, postToParent2]);
9299
+ useEffect7(() => {
7939
9300
  scrollToHashSectionWhenReady();
7940
9301
  const onHashChange = () => scrollToHashSectionWhenReady();
7941
9302
  window.addEventListener("hashchange", onHashChange);
7942
9303
  return () => window.removeEventListener("hashchange", onHashChange);
7943
9304
  }, [pathname]);
7944
- const handleCommand = useCallback2((cmd) => {
9305
+ const handleCommand = useCallback4((cmd) => {
7945
9306
  document.execCommand(cmd, false);
7946
9307
  activeElRef.current?.focus();
7947
9308
  if (activeElRef.current) setToolbarRect(getEditMeasureEl(activeElRef.current).getBoundingClientRect());
7948
9309
  refreshActiveCommandsRef.current();
7949
9310
  }, []);
7950
- const handleStateChange = useCallback2((state) => {
9311
+ const handleStateChange = useCallback4((state) => {
7951
9312
  if (!activeStateElRef.current) return;
7952
9313
  const el = activeStateElRef.current;
7953
9314
  if (state === "Default") {
@@ -7960,18 +9321,22 @@ function OhhwellsBridge() {
7960
9321
  }
7961
9322
  setToggleState((prev) => prev ? { ...prev, activeState: state } : null);
7962
9323
  }, [deactivate]);
7963
- const closeLinkPopover = useCallback2(() => setLinkPopover(null), []);
7964
- const openLinkPopoverForActive = useCallback2(() => {
9324
+ const closeLinkPopover = useCallback4(() => {
9325
+ addNavAfterAnchorRef.current = null;
9326
+ setLinkPopover(null);
9327
+ }, []);
9328
+ const openLinkPopoverForActive = useCallback4(() => {
7965
9329
  const hrefCtx = getHrefKeyFromElement(activeElRef.current);
7966
9330
  if (!hrefCtx) return;
7967
9331
  bumpLinkPopoverGrace();
7968
9332
  setLinkPopover({
7969
9333
  key: hrefCtx.key,
7970
- target: getLinkHref(hrefCtx.anchor)
9334
+ mode: "edit",
9335
+ target: getLinkHref2(hrefCtx.anchor)
7971
9336
  });
7972
9337
  deactivate();
7973
9338
  }, [deactivate]);
7974
- const openLinkPopoverForSelected = useCallback2(() => {
9339
+ const openLinkPopoverForSelected = useCallback4(() => {
7975
9340
  const anchor = selectedElRef.current;
7976
9341
  if (!anchor) return;
7977
9342
  const key = anchor.getAttribute("data-ohw-href-key");
@@ -7979,51 +9344,156 @@ function OhhwellsBridge() {
7979
9344
  bumpLinkPopoverGrace();
7980
9345
  setLinkPopover({
7981
9346
  key,
7982
- target: getLinkHref(anchor)
9347
+ mode: "edit",
9348
+ target: getLinkHref2(anchor)
7983
9349
  });
7984
9350
  deselect();
7985
9351
  }, [deselect]);
7986
- const handleLinkPopoverSubmit = useCallback2(
9352
+ const handleLinkPopoverSubmit = useCallback4(
7987
9353
  (target) => {
7988
- if (!linkPopover) return;
7989
- 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;
7990
9395
  applyLinkByKey(key, target);
7991
- postToParent({ type: "ow:change", nodes: [{ key, text: target }] });
9396
+ postToParent2({ type: "ow:change", nodes: [{ key, text: target }] });
7992
9397
  setLinkPopover(null);
7993
9398
  },
7994
- [linkPopover, postToParent]
9399
+ [postToParent2, sitePages, sectionsByPath]
7995
9400
  );
7996
9401
  const showEditLink = toolbarShowEditLink;
7997
9402
  const currentSections = sectionsByPath[pathname] ?? [];
7998
9403
  linkPopoverOpenRef.current = linkPopover !== null;
7999
- return bridgeRoot ? createPortal(
8000
- /* @__PURE__ */ jsxs11(Fragment3, { children: [
8001
- /* @__PURE__ */ jsx21("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
8002
- siblingHintRect && !isItemDragging && /* @__PURE__ */ jsx21(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
8003
- hoveredItemRect && !siblingHintRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ jsx21(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
8004
- 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(
8005
9474
  ItemInteractionLayer,
8006
9475
  {
8007
9476
  rect: toolbarRect,
8008
9477
  elRef: glowElRef,
8009
9478
  state: isItemDragging ? "dragging" : resolveItemInteractionState(toolbarRect, parentScrollRef.current),
8010
- showHandle: Boolean(reorderHrefKey),
9479
+ showHandle: toolbarVariant === "link-action" && Boolean(reorderHrefKey),
8011
9480
  dragDisabled: reorderDragDisabled,
8012
9481
  dragHandleLabel: reorderHrefKey ? `Reorder ${reorderHrefKey}` : "Reorder item",
8013
9482
  onDragHandleDragStart: handleItemDragStart,
8014
9483
  onDragHandleDragEnd: handleItemDragEnd,
8015
- toolbar: isItemDragging ? void 0 : /* @__PURE__ */ jsx21(
9484
+ toolbar: toolbarVariant === "link-action" && !isItemDragging ? /* @__PURE__ */ jsx23(
8016
9485
  ItemActionToolbar,
8017
9486
  {
8018
9487
  onEditLink: openLinkPopoverForSelected,
8019
- onAddItem: handleAddNavigationItem,
8020
- addItemDisabled: false
9488
+ addItemDisabled: true,
9489
+ editLinkDisabled: false,
9490
+ moreDisabled: true
8021
9491
  }
8022
- )
9492
+ ) : void 0
8023
9493
  }
8024
9494
  ),
8025
- toolbarRect && toolbarVariant === "rich-text" && /* @__PURE__ */ jsxs11(Fragment3, { children: [
8026
- /* @__PURE__ */ jsx21(
9495
+ toolbarRect && toolbarVariant === "rich-text" && /* @__PURE__ */ jsxs13(Fragment5, { children: [
9496
+ /* @__PURE__ */ jsx23(
8027
9497
  EditGlowChrome,
8028
9498
  {
8029
9499
  rect: toolbarRect,
@@ -8032,7 +9502,7 @@ function OhhwellsBridge() {
8032
9502
  dragDisabled: reorderDragDisabled
8033
9503
  }
8034
9504
  ),
8035
- /* @__PURE__ */ jsx21(
9505
+ /* @__PURE__ */ jsx23(
8036
9506
  FloatingToolbar,
8037
9507
  {
8038
9508
  rect: toolbarRect,
@@ -8045,7 +9515,7 @@ function OhhwellsBridge() {
8045
9515
  }
8046
9516
  )
8047
9517
  ] }),
8048
- maxBadge && /* @__PURE__ */ jsxs11(
9518
+ maxBadge && /* @__PURE__ */ jsxs13(
8049
9519
  "div",
8050
9520
  {
8051
9521
  "data-ohw-max-badge": "",
@@ -8071,7 +9541,7 @@ function OhhwellsBridge() {
8071
9541
  ]
8072
9542
  }
8073
9543
  ),
8074
- toggleState && /* @__PURE__ */ jsx21(
9544
+ toggleState && !linkPopover && /* @__PURE__ */ jsx23(
8075
9545
  StateToggle,
8076
9546
  {
8077
9547
  rect: toggleState.rect,
@@ -8080,15 +9550,15 @@ function OhhwellsBridge() {
8080
9550
  onStateChange: handleStateChange
8081
9551
  }
8082
9552
  ),
8083
- sectionGap && /* @__PURE__ */ jsxs11(
9553
+ sectionGap && !linkPopover && /* @__PURE__ */ jsxs13(
8084
9554
  "div",
8085
9555
  {
8086
9556
  "data-ohw-section-insert-line": "",
8087
9557
  className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
8088
9558
  style: { top: sectionGap.y, transform: "translateY(-50%)" },
8089
9559
  children: [
8090
- /* @__PURE__ */ jsx21("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
8091
- /* @__PURE__ */ jsx21(
9560
+ /* @__PURE__ */ jsx23("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
9561
+ /* @__PURE__ */ jsx23(
8092
9562
  Badge,
8093
9563
  {
8094
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",
@@ -8101,26 +9571,52 @@ function OhhwellsBridge() {
8101
9571
  children: "Add Section"
8102
9572
  }
8103
9573
  ),
8104
- /* @__PURE__ */ jsx21("div", { className: "flex-1 bg-primary", style: { height: 3 } })
9574
+ /* @__PURE__ */ jsx23("div", { className: "flex-1 bg-primary", style: { height: 3 } })
8105
9575
  ]
8106
9576
  }
8107
9577
  ),
8108
- linkPopover && dialogPortalContainer ? /* @__PURE__ */ jsx21(
9578
+ linkPopover && dialogPortalContainer ? /* @__PURE__ */ jsx23(
8109
9579
  LinkPopover,
8110
9580
  {
8111
9581
  panelRef: linkPopoverPanelRef,
8112
9582
  portalContainer: dialogPortalContainer,
8113
9583
  open: true,
8114
- mode: "edit",
9584
+ mode: linkPopover.mode ?? "edit",
8115
9585
  pages: sitePages,
8116
9586
  sections: currentSections,
8117
9587
  sectionsByPath,
8118
9588
  initialTarget: linkPopover.target,
9589
+ existingTargets: linkPopover.intent === "add-nav" ? getNavbarExistingTargets() : [],
8119
9590
  onClose: closeLinkPopover,
8120
9591
  onSubmit: handleLinkPopoverSubmit
8121
9592
  },
8122
- `${linkPopover.key}-${pathname}`
8123
- ) : 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
+ )
8124
9620
  ] }),
8125
9621
  bridgeRoot
8126
9622
  ) : null;