@ohhwells/bridge 0.1.37 → 0.1.38-next.57

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 useRef5, 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-[448px] -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-h-[calc(100vh-32px)]",
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);
@@ -5127,14 +5389,21 @@ function UrlOrPageInput({
5127
5389
  requestAnimationFrame(() => inputRef.current?.focus());
5128
5390
  };
5129
5391
  const fieldClassName = cn(
5130
- "data-ohw-link-field flex h-10 w-full items-center overflow-hidden rounded-md border bg-background pl-3 pr-3 py-2 outline-none transition-[border-color,box-shadow]",
5392
+ "data-ohw-link-field flex h-[36px] w-full items-center overflow-hidden rounded-md border bg-background pl-3 pr-3 py-2 outline-none transition-[border-color,box-shadow]",
5131
5393
  urlError ? "border-destructive shadow-[0_0_0_1px_var(--ohw-destructive)]" : isFocused ? "border-primary shadow-[0_0_0_1px_var(--ohw-primary)]" : "border-input"
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,447 @@ 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, useRef as useRef4, 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 readSectionRects(sectionIds) {
5579
+ const next = /* @__PURE__ */ new Map();
5580
+ for (const id of sectionIds) {
5581
+ const el = document.querySelector(
5582
+ `[data-ohw-section="${CSS.escape(id)}"]`
5583
+ );
5584
+ if (el) next.set(id, el.getBoundingClientRect());
5585
+ }
5586
+ return next;
5587
+ }
5588
+ function hitTestSectionId(x, y, sectionIds, rects) {
5589
+ for (const id of sectionIds) {
5590
+ const rect = rects.get(id);
5591
+ if (!rect || rect.width <= 0 || rect.height <= 0) continue;
5592
+ if (x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom) {
5593
+ return id;
5594
+ }
5595
+ }
5596
+ return null;
5597
+ }
5598
+ function useSectionRects(sectionIdsKey, enabled) {
5599
+ const [rects, setRects] = useState4(/* @__PURE__ */ new Map());
5600
+ const sectionIds = useMemo2(
5601
+ () => sectionIdsKey ? sectionIdsKey.split("\0") : [],
5602
+ [sectionIdsKey]
5603
+ );
5604
+ useEffect4(() => {
5605
+ if (!enabled || sectionIds.length === 0) {
5606
+ setRects((prev) => prev.size === 0 ? prev : /* @__PURE__ */ new Map());
5607
+ return;
5608
+ }
5609
+ const update = () => {
5610
+ const next = readSectionRects(sectionIds);
5611
+ setRects((prev) => rectsEqual(prev, next) ? prev : next);
5612
+ };
5613
+ update();
5614
+ const opts = { capture: true, passive: true };
5615
+ window.addEventListener("scroll", update, opts);
5616
+ window.addEventListener("resize", update);
5617
+ const vv = window.visualViewport;
5618
+ vv?.addEventListener("resize", update);
5619
+ vv?.addEventListener("scroll", update);
5620
+ const ro = new ResizeObserver(update);
5621
+ for (const id of sectionIds) {
5622
+ const el = document.querySelector(
5623
+ `[data-ohw-section="${CSS.escape(id)}"]`
5624
+ );
5625
+ if (el) ro.observe(el);
5626
+ }
5627
+ return () => {
5628
+ window.removeEventListener("scroll", update, opts);
5629
+ window.removeEventListener("resize", update);
5630
+ vv?.removeEventListener("resize", update);
5631
+ vv?.removeEventListener("scroll", update);
5632
+ ro.disconnect();
5633
+ };
5634
+ }, [sectionIds, enabled]);
5635
+ return rects;
5636
+ }
5637
+ function SectionPickerOverlay({
5638
+ pagePath,
5639
+ sections,
5640
+ onBack,
5641
+ onSelect
5642
+ }) {
5643
+ const router = useRouter();
5644
+ const pathname = usePathname();
5645
+ const [selectedId, setSelectedId] = useState4(null);
5646
+ const [hoveredId, setHoveredId] = useState4(null);
5647
+ const pointerRef = useRef4(null);
5648
+ const pointerScreenRef = useRef4(null);
5649
+ const iframeOffsetTopRef = useRef4(null);
5650
+ const sectionIdsRef = useRef4([]);
5651
+ const [chromeClip, setChromeClip] = useState4(
5652
+ () => ({
5653
+ top: 0,
5654
+ bottom: typeof window !== "undefined" ? window.innerHeight : 0
5655
+ })
5656
+ );
5657
+ const normalizedTarget = normalizePath(pagePath);
5658
+ const isOnTargetPage = normalizePath(pathname) === normalizedTarget;
5659
+ useEffect4(() => {
5660
+ if (isOnTargetPage) return;
5661
+ const search = readPreservedSearch();
5662
+ router.push(`${normalizedTarget}${search}`);
5663
+ }, [isOnTargetPage, normalizedTarget, router]);
5664
+ useEffect4(() => {
5665
+ document.documentElement.setAttribute("data-ohw-section-picking", "");
5666
+ document.querySelectorAll("[data-ohw-hovered]").forEach((el) => {
5667
+ el.removeAttribute("data-ohw-hovered");
5668
+ });
5669
+ return () => {
5670
+ document.documentElement.removeAttribute("data-ohw-section-picking");
5671
+ };
5672
+ }, []);
5673
+ const liveSections = useMemo2(() => {
5674
+ if (!isOnTargetPage) return sections;
5675
+ const live = collectSectionsFromDom();
5676
+ if (live.length === 0) return sections;
5677
+ const labels = new Map(sections.map((s) => [s.id, s.label]));
5678
+ return live.map((s) => ({ id: s.id, label: labels.get(s.id) ?? s.label }));
5679
+ }, [isOnTargetPage, sections, pathname]);
5680
+ const sectionIdsKey = useMemo2(
5681
+ () => liveSections.map((s) => s.id).join("\0"),
5682
+ [liveSections]
5683
+ );
5684
+ const sectionIds = useMemo2(
5685
+ () => liveSections.map((s) => s.id),
5686
+ [liveSections]
5687
+ );
5688
+ sectionIdsRef.current = sectionIds;
5689
+ const rects = useSectionRects(sectionIdsKey, isOnTargetPage);
5690
+ const applyHoverAt = useCallback2((x, y, liveRects) => {
5691
+ const ids = sectionIdsRef.current;
5692
+ const map = liveRects ?? readSectionRects(ids);
5693
+ const next = hitTestSectionId(x, y, ids, map);
5694
+ setHoveredId((prev) => prev === next ? prev : next);
5695
+ }, []);
5696
+ useEffect4(() => {
5697
+ const applyClip = (iframeOffsetTop, visibleCanvasTop, canvasH) => {
5698
+ const top = Math.max(0, visibleCanvasTop - iframeOffsetTop);
5699
+ const bottom = Math.min(
5700
+ window.innerHeight,
5701
+ visibleCanvasTop + canvasH - iframeOffsetTop
5702
+ );
5703
+ setChromeClip(
5704
+ (prev) => prev.top === top && prev.bottom === bottom ? prev : { top, bottom: Math.max(top, bottom) }
5705
+ );
5706
+ };
5707
+ const onParentScroll = (e) => {
5708
+ if (e.data?.type !== "ow:parent-scroll") return;
5709
+ const { iframeOffsetTop, headerH, canvasH } = e.data;
5710
+ applyClip(iframeOffsetTop, headerH, canvasH);
5711
+ iframeOffsetTopRef.current = iframeOffsetTop;
5712
+ if (!pointerScreenRef.current && pointerRef.current) {
5713
+ pointerScreenRef.current = {
5714
+ x: pointerRef.current.x,
5715
+ y: pointerRef.current.y + iframeOffsetTop
5716
+ };
5717
+ }
5718
+ const screen = pointerScreenRef.current;
5719
+ if (screen) {
5720
+ const next = { x: screen.x, y: screen.y - iframeOffsetTop };
5721
+ pointerRef.current = next;
5722
+ applyHoverAt(next.x, next.y);
5723
+ }
5724
+ };
5725
+ const onResize = () => {
5726
+ setChromeClip((prev) => {
5727
+ const bottom = Math.max(prev.top, window.innerHeight);
5728
+ return prev.bottom === bottom ? prev : { ...prev, bottom };
5729
+ });
5730
+ };
5731
+ window.addEventListener("message", onParentScroll);
5732
+ window.addEventListener("resize", onResize);
5733
+ return () => {
5734
+ window.removeEventListener("message", onParentScroll);
5735
+ window.removeEventListener("resize", onResize);
5736
+ };
5737
+ }, [applyHoverAt]);
5738
+ useEffect4(() => {
5739
+ const ptr = pointerRef.current;
5740
+ if (!ptr) return;
5741
+ applyHoverAt(ptr.x, ptr.y, rects);
5742
+ }, [rects, applyHoverAt]);
5743
+ useEffect4(() => {
5744
+ const rememberPointer = (clientX, clientY) => {
5745
+ pointerRef.current = { x: clientX, y: clientY };
5746
+ const top = iframeOffsetTopRef.current;
5747
+ if (top != null) {
5748
+ pointerScreenRef.current = { x: clientX, y: clientY + top };
5749
+ }
5750
+ applyHoverAt(clientX, clientY, rects);
5751
+ };
5752
+ const onPointerMove = (e) => {
5753
+ rememberPointer(e.clientX, e.clientY);
5754
+ };
5755
+ const onPointerSync = (e) => {
5756
+ if (e.data?.type !== "ow:pointer-sync") return;
5757
+ const { clientX, clientY } = e.data;
5758
+ rememberPointer(clientX, clientY);
5759
+ };
5760
+ window.addEventListener("pointermove", onPointerMove, { passive: true });
5761
+ window.addEventListener("message", onPointerSync);
5762
+ return () => {
5763
+ window.removeEventListener("pointermove", onPointerMove);
5764
+ window.removeEventListener("message", onPointerSync);
5765
+ };
5766
+ }, [rects, applyHoverAt]);
5767
+ const handleSelect = useCallback2(
5768
+ (section) => {
5769
+ if (selectedId) return;
5770
+ setSelectedId(section.id);
5771
+ onSelect(section);
5772
+ },
5773
+ [selectedId, onSelect]
5774
+ );
5775
+ useEffect4(() => {
5776
+ const onKeyDown = (e) => {
5777
+ if (e.key === "Escape") {
5778
+ e.preventDefault();
5779
+ e.stopPropagation();
5780
+ onBack();
5781
+ }
5782
+ };
5783
+ window.addEventListener("keydown", onKeyDown, true);
5784
+ return () => window.removeEventListener("keydown", onKeyDown, true);
5785
+ }, [onBack]);
5786
+ const portalRoot = typeof document !== "undefined" ? document.querySelector("[data-ohw-bridge-root]") ?? document.body : null;
5787
+ if (!portalRoot) return null;
5788
+ return createPortal(
5789
+ /* @__PURE__ */ jsxs11(
5790
+ "div",
5791
+ {
5792
+ "data-ohw-section-picker": "",
5793
+ "data-ohw-bridge": "",
5794
+ className: `pointer-events-none fixed inset-0 z-[2147483647] transition-opacity duration-200 ${"opacity-100"}`,
5795
+ "aria-modal": true,
5796
+ role: "dialog",
5797
+ "aria-label": "Choose a section",
5798
+ children: [
5799
+ /* @__PURE__ */ jsx19(
5800
+ "div",
5801
+ {
5802
+ className: "pointer-events-auto fixed left-5 z-[2]",
5803
+ style: { top: chromeClip.top + 20 },
5804
+ children: /* @__PURE__ */ jsxs11(
5805
+ Button,
5806
+ {
5807
+ type: "button",
5808
+ variant: "outline",
5809
+ size: "sm",
5810
+ className: "h-8 min-w-0 gap-1 border-border bg-background px-2 py-1.5 shadow-sm hover:bg-muted cursor-pointer",
5811
+ onClick: onBack,
5812
+ children: [
5813
+ /* @__PURE__ */ jsx19(ArrowLeft, { className: "size-4 shrink-0", "aria-hidden": true }),
5814
+ "Back"
5815
+ ]
5816
+ }
5817
+ )
5818
+ }
5819
+ ),
5820
+ /* @__PURE__ */ jsx19(
5821
+ "div",
5822
+ {
5823
+ 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",
5824
+ style: {
5825
+ top: chromeClip.bottom - 20,
5826
+ transform: "translate(-50%, -100%)",
5827
+ backgroundColor: "var(--ohw-popover-foreground, #0c0a09)"
5828
+ },
5829
+ "data-ohw-section-picker-hint": "",
5830
+ children: "Click on section to select"
5831
+ }
5832
+ ),
5833
+ !isOnTargetPage ? /* @__PURE__ */ jsx19(
5834
+ "div",
5835
+ {
5836
+ 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",
5837
+ style: { top: chromeClip.top + 64 },
5838
+ children: "Loading page preview\u2026"
5839
+ }
5840
+ ) : null,
5841
+ 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,
5842
+ isOnTargetPage ? liveSections.map((section) => {
5843
+ const rect = rects.get(section.id);
5844
+ if (!rect || rect.width <= 0 || rect.height <= 0) return null;
5845
+ const isSelected = selectedId === section.id;
5846
+ const isLit = isSelected || hoveredId === section.id;
5847
+ return /* @__PURE__ */ jsx19(
5848
+ "button",
5849
+ {
5850
+ type: "button",
5851
+ className: "pointer-events-auto fixed z-[1] cursor-pointer border-0 bg-transparent p-0 transition-[background-color] duration-150",
5852
+ style: {
5853
+ top: rect.top,
5854
+ left: rect.left,
5855
+ width: rect.width,
5856
+ height: rect.height,
5857
+ backgroundColor: isLit ? "transparent" : DIM_OVERLAY
5858
+ },
5859
+ "aria-label": `Select section ${section.label}`,
5860
+ onClick: () => handleSelect(section),
5861
+ children: isSelected ? /* @__PURE__ */ jsx19(
5862
+ "span",
5863
+ {
5864
+ className: "absolute right-3 top-3 flex size-8 items-center justify-center rounded-full text-white",
5865
+ style: { backgroundColor: "var(--ohw-primary, #0885fe)" },
5866
+ "aria-hidden": true,
5867
+ children: /* @__PURE__ */ jsx19(Check, { className: "size-5" })
5868
+ }
5869
+ ) : null
5870
+ },
5871
+ section.id
5872
+ );
5873
+ }) : null
5874
+ ]
5875
+ }
5876
+ ),
5877
+ portalRoot
5878
+ );
5879
+ }
5880
+
5881
+ // src/ui/link-modal/useLinkModalState.ts
5882
+ import { useCallback as useCallback3, useEffect as useEffect5, useMemo as useMemo3, useState as useState5 } from "react";
5883
+ function useLinkModalState({
5884
+ open,
5885
+ mode,
5886
+ pages,
5887
+ sections: _sections,
5888
+ sectionsByPath,
5889
+ initialTarget,
5890
+ existingTargets: _existingTargets,
5891
+ onClose,
5892
+ onSubmit
5893
+ }) {
5894
+ const availablePages = useMemo3(() => pages, [pages]);
5895
+ const [searchValue, setSearchValue] = useState5("");
5896
+ const [selectedPage, setSelectedPage] = useState5(null);
5897
+ const [selectedSection, setSelectedSection] = useState5(null);
5898
+ const [step, setStep] = useState5("input");
5899
+ const [dropdownOpen, setDropdownOpen] = useState5(false);
5900
+ const [urlError, setUrlError] = useState5("");
5901
+ const reset = useCallback3(() => {
5902
+ setSearchValue("");
5903
+ setSelectedPage(null);
5904
+ setSelectedSection(null);
5905
+ setStep("input");
5906
+ setDropdownOpen(false);
5907
+ setUrlError("");
5908
+ }, []);
5909
+ useEffect5(() => {
5910
+ if (!open) return;
5911
+ if (mode === "edit" && initialTarget) {
5912
+ const { pageRoute } = parseTarget(initialTarget);
5231
5913
  const targetSections = getSectionsForPath(sectionsByPath, pageRoute);
5232
5914
  const init = getEditModeInitialState(initialTarget, pages, targetSections);
5233
5915
  setSearchValue(init.searchValue);
@@ -5239,12 +5921,12 @@ function useLinkModalState({
5239
5921
  }
5240
5922
  setDropdownOpen(false);
5241
5923
  setUrlError("");
5242
- }, [open, mode, initialTarget, pages, sectionsByPath, reset]);
5243
- const filteredPages = useMemo2(() => {
5924
+ }, [open, mode, initialTarget, reset]);
5925
+ const filteredPages = useMemo3(() => {
5244
5926
  if (!searchValue.trim()) return availablePages;
5245
5927
  return availablePages.filter((p) => p.title.toLowerCase().startsWith(searchValue.toLowerCase()));
5246
5928
  }, [availablePages, searchValue]);
5247
- const activeSections = useMemo2(() => {
5929
+ const activeSections = useMemo3(() => {
5248
5930
  if (!selectedPage) return [];
5249
5931
  return getSectionsForPath(sectionsByPath, selectedPage.path);
5250
5932
  }, [selectedPage, sectionsByPath]);
@@ -5286,11 +5968,14 @@ function useLinkModalState({
5286
5968
  const handleBackToSections = () => {
5287
5969
  setStep("sectionPicker");
5288
5970
  };
5971
+ const handleSectionPickerBack = () => {
5972
+ setStep("input");
5973
+ };
5289
5974
  const handleClose = () => {
5290
5975
  reset();
5291
5976
  onClose();
5292
5977
  };
5293
- const isValid = useMemo2(() => {
5978
+ const isValid = useMemo3(() => {
5294
5979
  if (urlError) return false;
5295
5980
  if (showBreadcrumb) return true;
5296
5981
  if (selectedPage) return true;
@@ -5323,7 +6008,7 @@ function useLinkModalState({
5323
6008
  onSubmit(normalizeUrl(searchValue));
5324
6009
  handleClose();
5325
6010
  };
5326
- const submitLabel = mode === "edit" ? "Save" : "Create";
6011
+ const submitLabel = showBreadcrumb ? "Save" : mode === "edit" ? "Save" : "Create";
5327
6012
  const title = mode === "edit" ? "Edit navigation item" : "Create navigation item";
5328
6013
  const secondaryLabel = showBreadcrumb ? "Back to sections" : "Cancel";
5329
6014
  const handleSecondary = showBreadcrumb ? handleBackToSections : handleClose;
@@ -5350,23 +6035,29 @@ function useLinkModalState({
5350
6035
  handleChooseSection,
5351
6036
  handleSectionSelect,
5352
6037
  handleBackToSections,
6038
+ handleSectionPickerBack,
5353
6039
  handleClose,
5354
6040
  handleSecondary,
5355
6041
  handleSubmit
5356
6042
  };
5357
6043
  }
5358
6044
 
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({
6045
+ // src/ui/link-modal/LinkPopover.tsx
6046
+ import { Fragment as Fragment4, jsx as jsx20, jsxs as jsxs12 } from "react/jsx-runtime";
6047
+ function postToParent(data) {
6048
+ window.parent?.postMessage(data, "*");
6049
+ }
6050
+ function LinkPopover({
5362
6051
  open = true,
6052
+ panelRef,
6053
+ portalContainer,
6054
+ onClose,
5363
6055
  mode = "create",
5364
6056
  pages,
5365
6057
  sections = [],
5366
6058
  sectionsByPath,
5367
6059
  initialTarget,
5368
6060
  existingTargets = [],
5369
- onClose,
5370
6061
  onSubmit
5371
6062
  }) {
5372
6063
  const state = useLinkModalState({
@@ -5380,115 +6071,109 @@ function LinkEditorPanel({
5380
6071
  onClose,
5381
6072
  onSubmit
5382
6073
  });
5383
- const isCancel = state.secondaryLabel === "Cancel";
5384
- return /* @__PURE__ */ jsxs10(Fragment2, { children: [
5385
- /* @__PURE__ */ jsx18(DialogClose, { asChild: true, children: /* @__PURE__ */ jsx18(
5386
- "button",
6074
+ const sectionPickerActive = state.showSectionPicker && Boolean(state.selectedPage);
6075
+ useEffect6(() => {
6076
+ if (!open) return;
6077
+ if (sectionPickerActive) {
6078
+ postToParent({ type: "ow:link-modal-lock", locked: false });
6079
+ postToParent({ type: "ow:section-picker", active: true });
6080
+ document.documentElement.style.overflow = "";
6081
+ document.body.style.overflow = "";
6082
+ return () => {
6083
+ postToParent({ type: "ow:section-picker", active: false });
6084
+ };
6085
+ }
6086
+ postToParent({ type: "ow:section-picker", active: false });
6087
+ postToParent({ type: "ow:link-modal-lock", locked: true });
6088
+ const html = document.documentElement;
6089
+ const body = document.body;
6090
+ const prevHtmlOverflow = html.style.overflow;
6091
+ const prevBodyOverflow = body.style.overflow;
6092
+ const prevHtmlOverscroll = html.style.overscrollBehavior;
6093
+ const prevBodyOverscroll = body.style.overscrollBehavior;
6094
+ html.style.overflow = "hidden";
6095
+ body.style.overflow = "hidden";
6096
+ html.style.overscrollBehavior = "none";
6097
+ body.style.overscrollBehavior = "none";
6098
+ const canScrollElement = (el, deltaY) => {
6099
+ const { overflowY } = getComputedStyle(el);
6100
+ if (overflowY !== "auto" && overflowY !== "scroll") return false;
6101
+ if (el.scrollHeight <= el.clientHeight + 1) return false;
6102
+ if (deltaY < 0) return el.scrollTop > 0;
6103
+ if (deltaY > 0)
6104
+ return el.scrollTop + el.clientHeight < el.scrollHeight - 1;
6105
+ return true;
6106
+ };
6107
+ const allowsInternalScroll = (e) => {
6108
+ const target = e.target;
6109
+ if (!(target instanceof Element)) return false;
6110
+ const scrollRoot = target.closest(
6111
+ "[data-ohw-link-page-dropdown], [data-ohw-allow-scroll]"
6112
+ );
6113
+ if (!scrollRoot) return false;
6114
+ const deltaY = e instanceof WheelEvent ? e.deltaY : 0;
6115
+ return canScrollElement(scrollRoot, deltaY);
6116
+ };
6117
+ const preventBackgroundScroll = (e) => {
6118
+ if (allowsInternalScroll(e)) return;
6119
+ e.preventDefault();
6120
+ };
6121
+ const scrollOpts = { passive: false, capture: true };
6122
+ document.addEventListener("wheel", preventBackgroundScroll, scrollOpts);
6123
+ document.addEventListener("touchmove", preventBackgroundScroll, scrollOpts);
6124
+ return () => {
6125
+ postToParent({ type: "ow:link-modal-lock", locked: false });
6126
+ html.style.overflow = prevHtmlOverflow;
6127
+ body.style.overflow = prevBodyOverflow;
6128
+ html.style.overscrollBehavior = prevHtmlOverscroll;
6129
+ body.style.overscrollBehavior = prevBodyOverscroll;
6130
+ document.removeEventListener(
6131
+ "wheel",
6132
+ preventBackgroundScroll,
6133
+ scrollOpts
6134
+ );
6135
+ document.removeEventListener(
6136
+ "touchmove",
6137
+ preventBackgroundScroll,
6138
+ scrollOpts
6139
+ );
6140
+ };
6141
+ }, [open, sectionPickerActive]);
6142
+ return /* @__PURE__ */ jsxs12(Fragment4, { children: [
6143
+ /* @__PURE__ */ jsx20(
6144
+ Dialog2,
5387
6145
  {
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 })
6146
+ open: open && !sectionPickerActive,
6147
+ onOpenChange: (next) => {
6148
+ if (!next) onClose?.();
6149
+ },
6150
+ children: /* @__PURE__ */ jsx20(
6151
+ DialogContent,
6152
+ {
6153
+ ref: panelRef,
6154
+ container: portalContainer,
6155
+ "data-ohw-link-popover-root": "",
6156
+ "data-ohw-link-modal-root": "",
6157
+ "data-ohw-bridge": "",
6158
+ showCloseButton: false,
6159
+ className: "gap-0 p-0 w-full max-w-[448px] pointer-events-auto z-[2147483646] overflow-visible",
6160
+ children: /* @__PURE__ */ jsx20(LinkEditorPanel, { state, onClose })
6161
+ }
6162
+ )
5392
6163
  }
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
- ] })
6164
+ ),
6165
+ sectionPickerActive && state.selectedPage ? /* @__PURE__ */ jsx20(
6166
+ SectionPickerOverlay,
6167
+ {
6168
+ pagePath: state.selectedPage.path,
6169
+ sections: state.activeSections,
6170
+ onBack: state.handleSectionPickerBack,
6171
+ onSelect: state.handleSectionSelect
6172
+ }
6173
+ ) : null
5456
6174
  ] });
5457
6175
  }
5458
6176
 
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
6177
  // src/ui/link-modal/devFixtures.ts
5493
6178
  var DEV_SITE_PAGES = [
5494
6179
  { path: "/", title: "Home" },
@@ -5508,16 +6193,21 @@ var DEV_SECTIONS_BY_PATH = {
5508
6193
  { id: "founder-teaser", label: "Founder" },
5509
6194
  { id: "plan-form", label: "Plan Form" },
5510
6195
  { id: "testimonials", label: "Testimonials" },
5511
- { id: "wordmark", label: "Wordmark" }
6196
+ { id: "wordmark", label: "Wordmark" },
6197
+ { id: "footer", label: "Footer" }
5512
6198
  ],
5513
6199
  "/about": [
5514
6200
  { id: "manifesto", label: "Manifesto" },
5515
6201
  { id: "story-letter", label: "Our Story" },
5516
6202
  { id: "lagree-explainer", label: "Lagree Explainer" },
5517
6203
  { id: "waitlist-cta", label: "Waitlist" },
5518
- { id: "personal-training", label: "Personal training" }
6204
+ { id: "personal-training", label: "Personal training" },
6205
+ { id: "footer", label: "Footer" }
6206
+ ],
6207
+ "/classes": [
6208
+ { id: "class-library", label: "Class Library" },
6209
+ { id: "footer", label: "Footer" }
5519
6210
  ],
5520
- "/classes": [{ id: "class-library", label: "Class Library" }],
5521
6211
  "/pricing": [
5522
6212
  { id: "page-header", label: "Page Header" },
5523
6213
  { id: "free-class-cta", label: "Free Class" },
@@ -5525,19 +6215,25 @@ var DEV_SECTIONS_BY_PATH = {
5525
6215
  { id: "monthly-memberships", label: "Memberships" },
5526
6216
  { id: "specialty-programs", label: "Specialty Programs" },
5527
6217
  { id: "package-matcher", label: "Packages" },
5528
- { id: "wordmark", label: "Wordmark" }
6218
+ { id: "wordmark", label: "Wordmark" },
6219
+ { id: "footer", label: "Footer" }
5529
6220
  ],
5530
6221
  "/studio": [
5531
6222
  { id: "studio-intro", label: "Studio Intro" },
5532
6223
  { id: "studio-features", label: "Studio Features" },
5533
6224
  { id: "studio-gallery", label: "Studio Gallery" },
5534
- { id: "studio-visit", label: "Visit Studio" }
6225
+ { id: "studio-visit", label: "Visit Studio" },
6226
+ { id: "footer", label: "Footer" }
6227
+ ],
6228
+ "/instructors": [
6229
+ { id: "instructor-grid", label: "Instructors" },
6230
+ { id: "footer", label: "Footer" }
5535
6231
  ],
5536
- "/instructors": [{ id: "instructor-grid", label: "Instructors" }],
5537
6232
  "/contact": [
5538
6233
  { id: "hello-hero", label: "Hello" },
5539
6234
  { id: "contact-form", label: "Contact Form" },
5540
- { id: "faq", label: "FAQ" }
6235
+ { id: "faq", label: "FAQ" },
6236
+ { id: "footer", label: "Footer" }
5541
6237
  ]
5542
6238
  };
5543
6239
  function shouldUseDevFixtures() {
@@ -5547,8 +6243,277 @@ function shouldUseDevFixtures() {
5547
6243
  return new URLSearchParams(q).get("ohw-fixtures") === "1";
5548
6244
  }
5549
6245
 
6246
+ // src/lib/nav-items.ts
6247
+ var NAV_COUNT_KEY = "__ohw_nav_count";
6248
+ var NAV_ORDER_KEY = "__ohw_nav_order";
6249
+ function getLinkHref(el) {
6250
+ const key = el.getAttribute("data-ohw-href-key");
6251
+ if (key) {
6252
+ const stored = getStoredLinkHref(key);
6253
+ if (stored !== void 0) return stored;
6254
+ }
6255
+ return el.getAttribute("href") ?? "";
6256
+ }
6257
+ function isNavbarButton(el) {
6258
+ return el.hasAttribute("data-ohw-role") && el.getAttribute("data-ohw-role") === "navbar-button";
6259
+ }
6260
+ function isNavbarLinkItem(el) {
6261
+ if (!el.matches("[data-ohw-href-key]")) return false;
6262
+ if (isNavbarButton(el)) return false;
6263
+ if (!el.querySelector('[data-ohw-editable="text"]')) return false;
6264
+ return Boolean(el.closest("nav, [data-ohw-nav-container], [data-ohw-nav-drawer]"));
6265
+ }
6266
+ function getNavbarDesktopContainer() {
6267
+ return document.querySelector("[data-ohw-nav-container]");
6268
+ }
6269
+ function getNavbarDrawerContainer() {
6270
+ return document.querySelector("[data-ohw-nav-drawer]");
6271
+ }
6272
+ function listNavbarItems() {
6273
+ const desktop = getNavbarDesktopContainer();
6274
+ if (desktop) {
6275
+ return Array.from(desktop.querySelectorAll("[data-ohw-href-key]")).filter(isNavbarLinkItem);
6276
+ }
6277
+ const drawer = getNavbarDrawerContainer();
6278
+ if (drawer) {
6279
+ return Array.from(drawer.querySelectorAll("[data-ohw-href-key]")).filter(isNavbarLinkItem);
6280
+ }
6281
+ return Array.from(document.querySelectorAll("nav [data-ohw-href-key]")).filter(isNavbarLinkItem);
6282
+ }
6283
+ function parseNavIndexFromKey(key) {
6284
+ if (!key) return null;
6285
+ const match = key.match(/^nav-(\d+)-href$/);
6286
+ if (!match) return null;
6287
+ return parseInt(match[1], 10);
6288
+ }
6289
+ function getNavbarIndicesInDom() {
6290
+ const indices = /* @__PURE__ */ new Set();
6291
+ for (const item of listNavbarItems()) {
6292
+ const idx = parseNavIndexFromKey(item.getAttribute("data-ohw-href-key"));
6293
+ if (idx !== null) indices.add(idx);
6294
+ }
6295
+ return [...indices].sort((a, b) => a - b);
6296
+ }
6297
+ function getNextNavbarIndex() {
6298
+ const indices = getNavbarIndicesInDom();
6299
+ if (indices.length === 0) return 0;
6300
+ return Math.max(...indices) + 1;
6301
+ }
6302
+ function getNavbarExistingTargets() {
6303
+ return listNavbarItems().map((el) => getLinkHref(el)).filter(Boolean);
6304
+ }
6305
+ function getNavOrderFromDom() {
6306
+ return listNavbarItems().map((el) => el.getAttribute("data-ohw-href-key")).filter((key) => Boolean(key));
6307
+ }
6308
+ function parseNavOrder(content) {
6309
+ const raw = content[NAV_ORDER_KEY];
6310
+ if (!raw) return null;
6311
+ try {
6312
+ const parsed = JSON.parse(raw);
6313
+ if (!Array.isArray(parsed)) return null;
6314
+ return parsed.filter((key) => typeof key === "string" && key.length > 0);
6315
+ } catch {
6316
+ return null;
6317
+ }
6318
+ }
6319
+ function findCounterpartByHrefKey(hrefKey, root) {
6320
+ return root.querySelector(`[data-ohw-href-key="${CSS.escape(hrefKey)}"]`);
6321
+ }
6322
+ function cloneNavLinkShell(template) {
6323
+ const anchor = document.createElement("a");
6324
+ if (template) {
6325
+ anchor.style.cssText = template.style.cssText;
6326
+ if (template.className) anchor.className = template.className;
6327
+ }
6328
+ anchor.style.cursor = "pointer";
6329
+ anchor.style.textDecoration = "none";
6330
+ return anchor;
6331
+ }
6332
+ function buildNavLink(index, href, label, template) {
6333
+ const anchor = cloneNavLinkShell(template);
6334
+ anchor.href = href;
6335
+ anchor.setAttribute("data-ohw-href-key", `nav-${index}-href`);
6336
+ const span = document.createElement("span");
6337
+ span.setAttribute("data-ohw-editable", "text");
6338
+ span.setAttribute("data-ohw-key", `nav-${index}-label`);
6339
+ span.textContent = label;
6340
+ anchor.appendChild(span);
6341
+ return anchor;
6342
+ }
6343
+ function insertNavLinkInContainer(container, anchor, afterHrefKey) {
6344
+ if (!afterHrefKey) {
6345
+ container.appendChild(anchor);
6346
+ return;
6347
+ }
6348
+ const afterEl = findCounterpartByHrefKey(afterHrefKey, container);
6349
+ if (afterEl?.parentElement === container) {
6350
+ afterEl.insertAdjacentElement("afterend", anchor);
6351
+ return;
6352
+ }
6353
+ container.appendChild(anchor);
6354
+ }
6355
+ function insertNavbarItemDom(index, href, label, afterAnchor) {
6356
+ const afterHrefKey = afterAnchor?.getAttribute("data-ohw-href-key") ?? null;
6357
+ const desktopItems = getNavbarDesktopContainer()?.querySelectorAll("[data-ohw-href-key]") ?? [];
6358
+ const drawerItems = getNavbarDrawerContainer()?.querySelectorAll("[data-ohw-href-key]") ?? [];
6359
+ const desktopTemplate = Array.from(desktopItems).find(isNavbarLinkItem) ?? null;
6360
+ const drawerTemplate = Array.from(drawerItems).find(isNavbarLinkItem) ?? null;
6361
+ let primary = null;
6362
+ const desktopContainer = getNavbarDesktopContainer();
6363
+ if (desktopContainer) {
6364
+ const desktopAnchor = buildNavLink(index, href, label, desktopTemplate);
6365
+ insertNavLinkInContainer(desktopContainer, desktopAnchor, afterHrefKey);
6366
+ primary = desktopAnchor;
6367
+ }
6368
+ const drawerContainer = getNavbarDrawerContainer();
6369
+ if (drawerContainer) {
6370
+ const drawerAnchor = buildNavLink(index, href, label, drawerTemplate);
6371
+ insertNavLinkInContainer(drawerContainer, drawerAnchor, afterHrefKey);
6372
+ if (!primary) primary = drawerAnchor;
6373
+ }
6374
+ if (!primary) {
6375
+ const fallback = buildNavLink(index, href, label, listNavbarItems()[0] ?? null);
6376
+ const nav = document.querySelector("nav");
6377
+ nav?.appendChild(fallback);
6378
+ primary = fallback;
6379
+ }
6380
+ return primary;
6381
+ }
6382
+ function applyNavOrderToContainer(container, order) {
6383
+ const seen = /* @__PURE__ */ new Set();
6384
+ for (const hrefKey of order) {
6385
+ if (seen.has(hrefKey)) continue;
6386
+ seen.add(hrefKey);
6387
+ const el = findCounterpartByHrefKey(hrefKey, container);
6388
+ if (el) container.appendChild(el);
6389
+ }
6390
+ for (const el of container.querySelectorAll("[data-ohw-href-key]")) {
6391
+ if (!isNavbarLinkItem(el)) continue;
6392
+ const key = el.getAttribute("data-ohw-href-key");
6393
+ if (!key || seen.has(key)) continue;
6394
+ container.appendChild(el);
6395
+ }
6396
+ }
6397
+ function applyNavOrder(order) {
6398
+ const desktop = getNavbarDesktopContainer();
6399
+ if (desktop) applyNavOrderToContainer(desktop, order);
6400
+ const drawer = getNavbarDrawerContainer();
6401
+ if (drawer) applyNavOrderToContainer(drawer, order);
6402
+ }
6403
+ function collectNavbarIndicesFromContent(content) {
6404
+ const indices = /* @__PURE__ */ new Set();
6405
+ for (const key of Object.keys(content)) {
6406
+ const idx = parseNavIndexFromKey(key);
6407
+ if (idx !== null) indices.add(idx);
6408
+ }
6409
+ const countRaw = content[NAV_COUNT_KEY];
6410
+ if (countRaw) {
6411
+ const count = parseInt(countRaw, 10);
6412
+ if (Number.isFinite(count) && count > 0) {
6413
+ for (let i = 0; i < count; i++) indices.add(i);
6414
+ }
6415
+ }
6416
+ return [...indices].sort((a, b) => a - b);
6417
+ }
6418
+ function navbarIndexExistsInDom(index) {
6419
+ return document.querySelector(`[data-ohw-href-key="nav-${index}-href"]`) !== null;
6420
+ }
6421
+ function reconcileNavbarItemsFromContent(content) {
6422
+ const indices = collectNavbarIndicesFromContent(content);
6423
+ for (const index of indices) {
6424
+ if (navbarIndexExistsInDom(index)) continue;
6425
+ const href = content[`nav-${index}-href`];
6426
+ const label = content[`nav-${index}-label`];
6427
+ if (!href && !label) continue;
6428
+ insertNavbarItemDom(index, href ?? "/", label ?? "Untitled", null);
6429
+ }
6430
+ const order = parseNavOrder(content);
6431
+ if (order?.length) applyNavOrder(order);
6432
+ }
6433
+ function buildNavOrderAfterInsert(afterAnchor, newHrefKey) {
6434
+ const order = getNavOrderFromDom();
6435
+ if (!afterAnchor) {
6436
+ if (!order.includes(newHrefKey)) order.push(newHrefKey);
6437
+ return order;
6438
+ }
6439
+ const afterKey = afterAnchor.getAttribute("data-ohw-href-key");
6440
+ if (!afterKey) {
6441
+ if (!order.includes(newHrefKey)) order.push(newHrefKey);
6442
+ return order;
6443
+ }
6444
+ const withoutNew = order.filter((key) => key !== newHrefKey);
6445
+ const pos = withoutNew.indexOf(afterKey);
6446
+ if (pos >= 0) {
6447
+ withoutNew.splice(pos + 1, 0, newHrefKey);
6448
+ return withoutNew;
6449
+ }
6450
+ withoutNew.push(newHrefKey);
6451
+ return withoutNew;
6452
+ }
6453
+ function insertNavbarItem(href, label, afterAnchor = null) {
6454
+ const index = getNextNavbarIndex();
6455
+ const anchor = insertNavbarItemDom(index, href, label, afterAnchor);
6456
+ if (!anchor) throw new Error("Failed to insert navbar item");
6457
+ const hrefKey = `nav-${index}-href`;
6458
+ const order = buildNavOrderAfterInsert(afterAnchor, hrefKey);
6459
+ applyNavOrder(order);
6460
+ return {
6461
+ anchor,
6462
+ index,
6463
+ hrefKey,
6464
+ labelKey: `nav-${index}-label`,
6465
+ href,
6466
+ label,
6467
+ order
6468
+ };
6469
+ }
6470
+
6471
+ // src/ui/navbar-container-chrome.tsx
6472
+ import { Plus as Plus2 } from "lucide-react";
6473
+ import { jsx as jsx21 } from "react/jsx-runtime";
6474
+ function NavbarContainerChrome({
6475
+ rect,
6476
+ onAdd
6477
+ }) {
6478
+ const chromeGap = 6;
6479
+ return /* @__PURE__ */ jsx21(
6480
+ "div",
6481
+ {
6482
+ "data-ohw-navbar-container-chrome": "",
6483
+ "data-ohw-bridge": "",
6484
+ className: "pointer-events-none fixed z-[2147483647]",
6485
+ style: {
6486
+ top: rect.top - chromeGap,
6487
+ left: rect.left - chromeGap,
6488
+ width: rect.width + chromeGap * 2,
6489
+ height: rect.height + chromeGap * 2
6490
+ },
6491
+ children: /* @__PURE__ */ jsx21(
6492
+ "button",
6493
+ {
6494
+ type: "button",
6495
+ "data-ohw-navbar-add-button": "",
6496
+ 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",
6497
+ style: { top: "70%" },
6498
+ "aria-label": "Add item",
6499
+ onMouseDown: (e) => {
6500
+ e.preventDefault();
6501
+ e.stopPropagation();
6502
+ },
6503
+ onClick: (e) => {
6504
+ e.preventDefault();
6505
+ e.stopPropagation();
6506
+ onAdd();
6507
+ },
6508
+ children: /* @__PURE__ */ jsx21(Plus2, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
6509
+ }
6510
+ )
6511
+ }
6512
+ );
6513
+ }
6514
+
5550
6515
  // src/ui/badge.tsx
5551
- import { jsx as jsx20 } from "react/jsx-runtime";
6516
+ import { jsx as jsx22 } from "react/jsx-runtime";
5552
6517
  var badgeVariants = cva(
5553
6518
  "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
6519
  {
@@ -5566,12 +6531,12 @@ var badgeVariants = cva(
5566
6531
  }
5567
6532
  );
5568
6533
  function Badge({ className, variant, ...props }) {
5569
- return /* @__PURE__ */ jsx20("div", { className: cn(badgeVariants({ variant }), className), ...props });
6534
+ return /* @__PURE__ */ jsx22("div", { className: cn(badgeVariants({ variant }), className), ...props });
5570
6535
  }
5571
6536
 
5572
6537
  // src/OhhwellsBridge.tsx
5573
6538
  import { Link as Link2 } from "lucide-react";
5574
- import { Fragment as Fragment3, jsx as jsx21, jsxs as jsxs11 } from "react/jsx-runtime";
6539
+ import { Fragment as Fragment5, jsx as jsx23, jsxs as jsxs13 } from "react/jsx-runtime";
5575
6540
  var PRIMARY2 = "#0885FE";
5576
6541
  var IMAGE_FADE_MS = 300;
5577
6542
  function runOpacityFade(el, onDone) {
@@ -5750,7 +6715,7 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
5750
6715
  const root = createRoot(container);
5751
6716
  flushSync(() => {
5752
6717
  root.render(
5753
- /* @__PURE__ */ jsx21(
6718
+ /* @__PURE__ */ jsx23(
5754
6719
  SchedulingWidget,
5755
6720
  {
5756
6721
  notifyOnConnect,
@@ -5790,7 +6755,7 @@ function mountSchedulingEntries(entries, notifyOnConnect = false) {
5790
6755
  }
5791
6756
  }
5792
6757
  }
5793
- function getLinkHref(el) {
6758
+ function getLinkHref2(el) {
5794
6759
  const anchor = el instanceof HTMLAnchorElement ? el : el.querySelector("a");
5795
6760
  return anchor?.getAttribute("href") ?? "";
5796
6761
  }
@@ -5823,8 +6788,11 @@ function collectEditableNodes() {
5823
6788
  const url = raw.replace(/^url\(['"]?/, "").replace(/['"]?\)$/, "");
5824
6789
  return { key: el.dataset.ohwKey ?? "", type: "bg-image", text: url };
5825
6790
  }
6791
+ if (el.dataset.ohwEditable === "video") {
6792
+ return { key: el.dataset.ohwKey ?? "", type: "video", text: getVideoEl(el)?.src ?? "" };
6793
+ }
5826
6794
  if (el.dataset.ohwEditable === "link") {
5827
- return { key: el.dataset.ohwKey ?? "", type: "link", text: getLinkHref(el) };
6795
+ return { key: el.dataset.ohwKey ?? "", type: "link", text: getLinkHref2(el) };
5828
6796
  }
5829
6797
  return {
5830
6798
  key: el.dataset.ohwKey ?? "",
@@ -5835,9 +6803,79 @@ function collectEditableNodes() {
5835
6803
  document.querySelectorAll("[data-ohw-href-key]").forEach((el) => {
5836
6804
  const key = el.getAttribute("data-ohw-href-key") ?? "";
5837
6805
  if (!key) return;
5838
- nodes.push({ key, type: "link", text: getLinkHref(el) });
6806
+ nodes.push({ key, type: "link", text: getLinkHref2(el) });
6807
+ });
6808
+ document.querySelectorAll('[data-ohw-editable="video"]').forEach((el) => {
6809
+ const key = el.dataset.ohwKey ?? "";
6810
+ const video = getVideoEl(el);
6811
+ if (!key || !video) return;
6812
+ nodes.push({ key: `${key}${VIDEO_AUTOPLAY_SUFFIX}`, type: "video-setting", text: String(video.autoplay) });
6813
+ nodes.push({ key: `${key}${VIDEO_MUTED_SUFFIX}`, type: "video-setting", text: String(video.muted) });
6814
+ });
6815
+ return nodes;
6816
+ }
6817
+ function isMediaEditable(el) {
6818
+ const t = el.dataset.ohwEditable;
6819
+ return t === "image" || t === "bg-image" || t === "video";
6820
+ }
6821
+ var MEDIA_SELECTOR = '[data-ohw-editable="image"], [data-ohw-editable="bg-image"], [data-ohw-editable="video"]';
6822
+ var NON_MEDIA_SELECTOR = '[data-ohw-editable]:not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"]):not([data-ohw-editable="video"])';
6823
+ function getVideoEl(el) {
6824
+ return el instanceof HTMLVideoElement ? el : el.querySelector("video");
6825
+ }
6826
+ var VIDEO_AUTOPLAY_SUFFIX = "__ohw_autoplay";
6827
+ var VIDEO_MUTED_SUFFIX = "__ohw_muted";
6828
+ function isBackgroundVideo(video) {
6829
+ return video.autoplay && video.muted;
6830
+ }
6831
+ function syncVideoPlayback(video) {
6832
+ const background = isBackgroundVideo(video);
6833
+ video.controls = !background;
6834
+ if (video.autoplay) void video.play().catch(() => {
6835
+ });
6836
+ else video.pause();
6837
+ }
6838
+ function lockVideoBox(video) {
6839
+ const { width, height } = video.getBoundingClientRect();
6840
+ if (width > 0 && height > 0 && !video.style.aspectRatio) {
6841
+ video.style.aspectRatio = `${width} / ${height}`;
6842
+ }
6843
+ if (getComputedStyle(video).objectFit === "fill") {
6844
+ video.style.objectFit = "contain";
6845
+ }
6846
+ const fit = video.style.objectFit || getComputedStyle(video).objectFit;
6847
+ const bg = getComputedStyle(video).backgroundColor;
6848
+ const isTransparent = bg === "rgba(0, 0, 0, 0)" || bg === "transparent" || !bg;
6849
+ if (fit === "contain" && isTransparent) {
6850
+ video.style.backgroundColor = "var(--color-dark, #000)";
6851
+ }
6852
+ }
6853
+ function applyVideoSrc(video, url) {
6854
+ const { autoplay, muted } = video;
6855
+ lockVideoBox(video);
6856
+ video.src = url;
6857
+ video.loop = true;
6858
+ video.playsInline = true;
6859
+ video.autoplay = autoplay;
6860
+ video.muted = muted;
6861
+ video.load();
6862
+ syncVideoPlayback(video);
6863
+ }
6864
+ function applyVideoSettingNode(key, val) {
6865
+ const isAutoplay = key.endsWith(VIDEO_AUTOPLAY_SUFFIX);
6866
+ const isMuted = key.endsWith(VIDEO_MUTED_SUFFIX);
6867
+ if (!isAutoplay && !isMuted) return false;
6868
+ const suffixLen = (isAutoplay ? VIDEO_AUTOPLAY_SUFFIX : VIDEO_MUTED_SUFFIX).length;
6869
+ const baseKey = key.slice(0, key.length - suffixLen);
6870
+ const on = val === "true";
6871
+ document.querySelectorAll(`[data-ohw-key="${baseKey}"]`).forEach((el) => {
6872
+ const video = getVideoEl(el);
6873
+ if (!video) return;
6874
+ if (isAutoplay) video.autoplay = on;
6875
+ else video.muted = on;
6876
+ syncVideoPlayback(video);
5839
6877
  });
5840
- return nodes;
6878
+ return true;
5841
6879
  }
5842
6880
  function applyLinkByKey(key, val) {
5843
6881
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -5851,7 +6889,7 @@ function applyLinkByKey(key, val) {
5851
6889
  }
5852
6890
  function isInsideLinkEditor(target) {
5853
6891
  return Boolean(
5854
- target.closest("[data-ohw-link-popover-root]") || target.closest("[data-ohw-link-modal-root]") || target.closest("[data-ohw-link-page-dropdown]") || target.closest('[data-slot="popover-content"]') || target.closest('[data-slot="dialog-content"]') || target.closest('[data-slot="dialog-overlay"]')
6892
+ target.closest("[data-ohw-link-popover-root]") || target.closest("[data-ohw-link-modal-root]") || target.closest("[data-ohw-link-page-dropdown]") || target.closest("[data-ohw-section-picker]") || target.closest("[data-ohw-navbar-container-chrome]") || target.closest("[data-ohw-navbar-add-button]") || target.closest('[data-slot="popover-content"]') || target.closest('[data-slot="dialog-content"]') || target.closest('[data-slot="dialog-overlay"]')
5855
6893
  );
5856
6894
  }
5857
6895
  function getHrefKeyFromElement(el) {
@@ -5862,7 +6900,7 @@ function getHrefKeyFromElement(el) {
5862
6900
  if (!key) return null;
5863
6901
  return { anchor, key };
5864
6902
  }
5865
- function isNavbarButton(el) {
6903
+ function isNavbarButton2(el) {
5866
6904
  return Boolean(el.closest('[data-ohw-role="navbar-button"]'));
5867
6905
  }
5868
6906
  function getNavigationItemAnchor(el) {
@@ -5874,19 +6912,8 @@ function getNavigationItemAnchor(el) {
5874
6912
  function isNavigationItem(el) {
5875
6913
  return getNavigationItemAnchor(el) !== null;
5876
6914
  }
5877
- function getNavigationItemAddHintTarget(anchor) {
5878
- const items = listNavigationItems();
5879
- const idx = items.indexOf(anchor);
5880
- if (idx >= 0 && idx < items.length - 1) return items[idx + 1];
5881
- return anchor;
5882
- }
5883
- function listNavigationItems() {
5884
- return Array.from(
5885
- document.querySelectorAll("nav [data-ohw-href-key], footer [data-ohw-href-key]")
5886
- ).filter((el) => isNavigationItem(el));
5887
- }
5888
6915
  function getNavigationItemReorderState(anchor) {
5889
- if (isNavbarButton(anchor)) return { key: null, disabled: false };
6916
+ if (isNavbarButton2(anchor)) return { key: null, disabled: false };
5890
6917
  return getReorderHandleStateFromAnchor(anchor);
5891
6918
  }
5892
6919
  function getReorderHandleState(el) {
@@ -5908,6 +6935,120 @@ function clearHrefKeyHover(anchor) {
5908
6935
  function isInsideNavigationItem(el) {
5909
6936
  return getNavigationItemAnchor(el) !== null;
5910
6937
  }
6938
+ function getNavigationRoot(el) {
6939
+ const explicit = el.closest("[data-ohw-nav-root]");
6940
+ if (explicit) return explicit;
6941
+ return el.closest("nav, footer, aside");
6942
+ }
6943
+ function findFooterItemGroup(item) {
6944
+ const footer = item.closest("footer");
6945
+ if (!footer) return null;
6946
+ let node = item.parentElement;
6947
+ while (node && node !== footer) {
6948
+ const hasDirectNavItems = Array.from(
6949
+ node.querySelectorAll(":scope > [data-ohw-href-key]")
6950
+ ).some(isNavigationItem);
6951
+ if (hasDirectNavItems) return node;
6952
+ node = node.parentElement;
6953
+ }
6954
+ return null;
6955
+ }
6956
+ function isNavigationRoot(el) {
6957
+ return el.hasAttribute("data-ohw-nav-root") || el.matches("nav, footer, aside");
6958
+ }
6959
+ function isInferredFooterGroup(el) {
6960
+ const footer = el.closest("footer");
6961
+ if (!footer || el === footer) return false;
6962
+ return Array.from(el.querySelectorAll(":scope > [data-ohw-href-key]")).some(isNavigationItem);
6963
+ }
6964
+ function isNavigationContainer(el) {
6965
+ return el.hasAttribute("data-ohw-nav-container") || isNavigationRoot(el) || isInferredFooterGroup(el);
6966
+ }
6967
+ function isPointOverNavigation(x, y) {
6968
+ const navRoot = document.querySelector("[data-ohw-nav-root]") ?? document.querySelector("nav");
6969
+ if (!navRoot) return false;
6970
+ const r2 = navRoot.getBoundingClientRect();
6971
+ return x >= r2.left && x <= r2.right && y >= r2.top && y <= r2.bottom;
6972
+ }
6973
+ function isPointOverNavItem(container, x, y) {
6974
+ return Array.from(container.querySelectorAll("[data-ohw-href-key]")).some((el) => {
6975
+ if (!isNavigationItem(el) || isNavbarButton2(el)) return false;
6976
+ const itemRect = el.getBoundingClientRect();
6977
+ return x >= itemRect.left && x <= itemRect.right && y >= itemRect.top && y <= itemRect.bottom;
6978
+ });
6979
+ }
6980
+ function resolveNavContainerSelectionTarget(target, clientX, clientY) {
6981
+ if (getNavigationItemAnchor(target)) return null;
6982
+ if (target.closest('[data-ohw-role="navbar-button"]')) return null;
6983
+ const plainLink = target.closest("a");
6984
+ if (plainLink && !plainLink.hasAttribute("data-ohw-href-key") && !plainLink.closest("[data-ohw-nav-container]")) {
6985
+ return null;
6986
+ }
6987
+ const fromClosest = target.closest("[data-ohw-nav-container]");
6988
+ if (fromClosest && !isPointOverNavItem(fromClosest, clientX, clientY)) {
6989
+ return fromClosest;
6990
+ }
6991
+ const navRoot = target.closest("[data-ohw-nav-root]");
6992
+ if (!navRoot) return null;
6993
+ const container = navRoot.querySelector("[data-ohw-nav-container]");
6994
+ if (!container || isPointOverNavItem(container, clientX, clientY)) return null;
6995
+ if (target === navRoot || target.hasAttribute("data-ohw-nav-root")) {
6996
+ return container;
6997
+ }
6998
+ return null;
6999
+ }
7000
+ function getNavigationSelectionParent(el) {
7001
+ if (isNavigationItem(el)) {
7002
+ const explicit = el.closest("[data-ohw-nav-container]");
7003
+ if (explicit) return explicit;
7004
+ const footerGroup = findFooterItemGroup(el);
7005
+ if (footerGroup) return footerGroup;
7006
+ return getNavigationRoot(el);
7007
+ }
7008
+ if (el.hasAttribute("data-ohw-nav-container") || isInferredFooterGroup(el)) {
7009
+ return getNavigationRoot(el);
7010
+ }
7011
+ return null;
7012
+ }
7013
+ function placeCaretAtPoint(el, x, y) {
7014
+ const selection = window.getSelection();
7015
+ if (!selection) return;
7016
+ if (typeof document.caretRangeFromPoint === "function") {
7017
+ const range = document.caretRangeFromPoint(x, y);
7018
+ if (range && el.contains(range.startContainer)) {
7019
+ selection.removeAllRanges();
7020
+ selection.addRange(range);
7021
+ return;
7022
+ }
7023
+ }
7024
+ const caretPositionFromPoint = document.caretPositionFromPoint;
7025
+ if (typeof caretPositionFromPoint === "function") {
7026
+ const position = caretPositionFromPoint(x, y);
7027
+ if (position && el.contains(position.offsetNode)) {
7028
+ const range = document.createRange();
7029
+ range.setStart(position.offsetNode, position.offset);
7030
+ range.collapse(true);
7031
+ selection.removeAllRanges();
7032
+ selection.addRange(range);
7033
+ }
7034
+ }
7035
+ }
7036
+ function selectAllTextInEditable(el) {
7037
+ const selection = window.getSelection();
7038
+ if (!selection) return;
7039
+ const range = document.createRange();
7040
+ range.selectNodeContents(el);
7041
+ selection.removeAllRanges();
7042
+ selection.addRange(range);
7043
+ }
7044
+ function getNavigationLabelEditable(target) {
7045
+ const editable = target.closest('[data-ohw-editable="text"]');
7046
+ if (!editable) return null;
7047
+ const hrefCtx = getHrefKeyFromElement(editable);
7048
+ const navAnchor = hrefCtx ? getNavigationItemAnchor(hrefCtx.anchor) : null;
7049
+ if (!navAnchor) return null;
7050
+ return { editable, navAnchor };
7051
+ }
5911
7052
  function collectSections() {
5912
7053
  return collectSectionsFromDom();
5913
7054
  }
@@ -6031,6 +7172,8 @@ var ICONS = {
6031
7172
  insertUnorderedList: '<line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/>',
6032
7173
  insertOrderedList: '<line x1="10" y1="6" x2="21" y2="6"/><line x1="10" y1="12" x2="21" y2="12"/><line x1="10" y1="18" x2="21" y2="18"/><path d="M4 6h1v4"/><path d="M4 10h2"/><path d="M6 18H4c0-1 2-2 2-3s-1-1.5-2-1"/>'
6033
7174
  };
7175
+ var TOOLBAR_PILL_PADDING = 2;
7176
+ var TOOLBAR_TOGGLE_GAP = 4;
6034
7177
  var TOOLBAR_GROUPS = [
6035
7178
  [
6036
7179
  { cmd: "bold", title: "Bold" },
@@ -6055,7 +7198,7 @@ function EditGlowChrome({
6055
7198
  dragDisabled = false
6056
7199
  }) {
6057
7200
  const GAP = 6;
6058
- return /* @__PURE__ */ jsxs11(
7201
+ return /* @__PURE__ */ jsxs13(
6059
7202
  "div",
6060
7203
  {
6061
7204
  ref: elRef,
@@ -6070,7 +7213,7 @@ function EditGlowChrome({
6070
7213
  zIndex: 2147483646
6071
7214
  },
6072
7215
  children: [
6073
- /* @__PURE__ */ jsx21(
7216
+ /* @__PURE__ */ jsx23(
6074
7217
  "div",
6075
7218
  {
6076
7219
  style: {
@@ -6083,7 +7226,7 @@ function EditGlowChrome({
6083
7226
  }
6084
7227
  }
6085
7228
  ),
6086
- reorderHrefKey && /* @__PURE__ */ jsx21(
7229
+ reorderHrefKey && /* @__PURE__ */ jsx23(
6087
7230
  "div",
6088
7231
  {
6089
7232
  "data-ohw-drag-handle-container": "",
@@ -6095,7 +7238,7 @@ function EditGlowChrome({
6095
7238
  transform: "translate(calc(-100% - 7px), -50%)",
6096
7239
  pointerEvents: dragDisabled ? "none" : "auto"
6097
7240
  },
6098
- children: /* @__PURE__ */ jsx21(
7241
+ children: /* @__PURE__ */ jsx23(
6099
7242
  DragHandle,
6100
7243
  {
6101
7244
  "aria-label": `Reorder ${reorderHrefKey}`,
@@ -6199,7 +7342,7 @@ function FloatingToolbar({
6199
7342
  onEditLink
6200
7343
  }) {
6201
7344
  const { top, left, transform } = calcToolbarPos(rect, parentScroll, 330);
6202
- return /* @__PURE__ */ jsx21(
7345
+ return /* @__PURE__ */ jsx23(
6203
7346
  "div",
6204
7347
  {
6205
7348
  ref: elRef,
@@ -6209,14 +7352,23 @@ function FloatingToolbar({
6209
7352
  left,
6210
7353
  transform,
6211
7354
  zIndex: 2147483647,
7355
+ background: "#fff",
7356
+ border: "1px solid #E7E5E4",
7357
+ borderRadius: 6,
7358
+ boxShadow: "0px 2px 4px -2px rgba(0,0,0,.1),0px 4px 6px -1px rgba(0,0,0,.1)",
7359
+ display: "flex",
7360
+ alignItems: "center",
7361
+ padding: TOOLBAR_PILL_PADDING,
7362
+ gap: TOOLBAR_TOGGLE_GAP,
7363
+ fontFamily: "sans-serif",
6212
7364
  pointerEvents: "auto"
6213
7365
  },
6214
- children: /* @__PURE__ */ jsxs11(CustomToolbar, { children: [
6215
- TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ jsxs11(React8.Fragment, { children: [
6216
- gi > 0 && /* @__PURE__ */ jsx21(CustomToolbarDivider, {}),
7366
+ children: /* @__PURE__ */ jsxs13(CustomToolbar, { children: [
7367
+ TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ jsxs13(React9.Fragment, { children: [
7368
+ gi > 0 && /* @__PURE__ */ jsx23(CustomToolbarDivider, {}),
6217
7369
  btns.map((btn) => {
6218
7370
  const isActive = activeCommands.has(btn.cmd);
6219
- return /* @__PURE__ */ jsx21(
7371
+ return /* @__PURE__ */ jsx23(
6220
7372
  CustomToolbarButton,
6221
7373
  {
6222
7374
  title: btn.title,
@@ -6225,7 +7377,7 @@ function FloatingToolbar({
6225
7377
  e.preventDefault();
6226
7378
  onCommand(btn.cmd);
6227
7379
  },
6228
- children: /* @__PURE__ */ jsx21(
7380
+ children: /* @__PURE__ */ jsx23(
6229
7381
  "svg",
6230
7382
  {
6231
7383
  width: "16",
@@ -6246,7 +7398,7 @@ function FloatingToolbar({
6246
7398
  );
6247
7399
  })
6248
7400
  ] }, gi)),
6249
- showEditLink ? /* @__PURE__ */ jsx21(
7401
+ showEditLink ? /* @__PURE__ */ jsx23(
6250
7402
  CustomToolbarButton,
6251
7403
  {
6252
7404
  type: "button",
@@ -6260,7 +7412,7 @@ function FloatingToolbar({
6260
7412
  e.preventDefault();
6261
7413
  e.stopPropagation();
6262
7414
  },
6263
- children: /* @__PURE__ */ jsx21(Link2, { className: "size-4 shrink-0", "aria-hidden": true })
7415
+ children: /* @__PURE__ */ jsx23(Link2, { className: "size-4 shrink-0", "aria-hidden": true })
6264
7416
  }
6265
7417
  ) : null
6266
7418
  ] })
@@ -6277,7 +7429,7 @@ function StateToggle({
6277
7429
  states,
6278
7430
  onStateChange
6279
7431
  }) {
6280
- return /* @__PURE__ */ jsx21(
7432
+ return /* @__PURE__ */ jsx23(
6281
7433
  ToggleGroup,
6282
7434
  {
6283
7435
  "data-ohw-state-toggle": "",
@@ -6291,7 +7443,7 @@ function StateToggle({
6291
7443
  left: rect.right - 8,
6292
7444
  transform: "translateX(-100%)"
6293
7445
  },
6294
- children: states.map((state) => /* @__PURE__ */ jsx21(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
7446
+ children: states.map((state) => /* @__PURE__ */ jsx23(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
6295
7447
  }
6296
7448
  );
6297
7449
  }
@@ -6314,12 +7466,12 @@ function resolveSubdomain(subdomainFromQuery) {
6314
7466
  return "";
6315
7467
  }
6316
7468
  function OhhwellsBridge() {
6317
- const pathname = usePathname();
6318
- const router = useRouter();
7469
+ const pathname = usePathname2();
7470
+ const router = useRouter2();
6319
7471
  const searchParams = useSearchParams();
6320
7472
  const isEditMode = isEditSessionActive();
6321
- const [bridgeRoot, setBridgeRoot] = useState5(null);
6322
- useEffect3(() => {
7473
+ const [bridgeRoot, setBridgeRoot] = useState6(null);
7474
+ useEffect7(() => {
6323
7475
  const figtreeFontId = "ohw-figtree-font";
6324
7476
  if (!document.getElementById(figtreeFontId)) {
6325
7477
  const preconnect1 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.googleapis.com" });
@@ -6327,7 +7479,10 @@ function OhhwellsBridge() {
6327
7479
  const stylesheet = Object.assign(document.createElement("link"), {
6328
7480
  id: figtreeFontId,
6329
7481
  rel: "stylesheet",
6330
- href: "https://fonts.googleapis.com/css2?family=Figtree:ital,wght@0,300..900;1,300..900&display=swap"
7482
+ // Inter is loaded alongside Figtree for the media overlay's Replace button. The bridge
7483
+ // renders inside the customer's document, so it cannot assume any font is present — an
7484
+ // unloaded family would silently fall back to whatever the template happens to use.
7485
+ href: "https://fonts.googleapis.com/css2?family=Figtree:ital,wght@0,300..900;1,300..900&family=Inter:wght@400;500;600&display=swap"
6331
7486
  });
6332
7487
  document.head.append(preconnect1, preconnect2, stylesheet);
6333
7488
  }
@@ -6344,80 +7499,91 @@ function OhhwellsBridge() {
6344
7499
  const subdomainFromQuery = searchParams.get("subdomain");
6345
7500
  const subdomain = resolveSubdomain(subdomainFromQuery);
6346
7501
  useLinkHrefGuardian(pathname, subdomain, isEditMode);
6347
- const postToParent = useCallback2((data) => {
7502
+ const postToParent2 = useCallback4((data) => {
6348
7503
  if (typeof window !== "undefined" && window.parent !== window) {
6349
7504
  window.parent.postMessage(data, "*");
6350
7505
  }
6351
7506
  }, []);
6352
- const [fetchState, setFetchState] = useState5("idle");
6353
- const autoSaveTimers = useRef3(/* @__PURE__ */ new Map());
6354
- const activeElRef = useRef3(null);
6355
- const selectedElRef = useRef3(null);
6356
- const originalContentRef = useRef3(null);
6357
- const activeStateElRef = useRef3(null);
6358
- const parentScrollRef = useRef3(null);
6359
- const visibleViewportRef = useRef3(null);
6360
- const [dialogPortalContainer, setDialogPortalContainer] = useState5(null);
6361
- const attachVisibleViewport = useCallback2((node) => {
7507
+ const [fetchState, setFetchState] = useState6("idle");
7508
+ const autoSaveTimers = useRef5(/* @__PURE__ */ new Map());
7509
+ const activeElRef = useRef5(null);
7510
+ const selectedElRef = useRef5(null);
7511
+ const originalContentRef = useRef5(null);
7512
+ const activeStateElRef = useRef5(null);
7513
+ const parentScrollRef = useRef5(null);
7514
+ const visibleViewportRef = useRef5(null);
7515
+ const [dialogPortalContainer, setDialogPortalContainer] = useState6(null);
7516
+ const attachVisibleViewport = useCallback4((node) => {
6362
7517
  visibleViewportRef.current = node;
6363
7518
  setDialogPortalContainer(node);
6364
7519
  if (node) applyVisibleViewport(node, parentScrollRef.current);
6365
7520
  }, []);
6366
- const toolbarElRef = useRef3(null);
6367
- const glowElRef = useRef3(null);
6368
- const hoveredImageRef = useRef3(null);
6369
- const hoveredImageHasTextOverlapRef = useRef3(false);
6370
- const hoveredGapRef = useRef3(null);
6371
- const imageUnhoverTimerRef = useRef3(null);
6372
- const imageShowTimerRef = useRef3(null);
6373
- const editStylesRef = useRef3(null);
6374
- const activateRef = useRef3(() => {
7521
+ const toolbarElRef = useRef5(null);
7522
+ const glowElRef = useRef5(null);
7523
+ const hoveredImageRef = useRef5(null);
7524
+ const hoveredImageHasTextOverlapRef = useRef5(false);
7525
+ const dragOverElRef = useRef5(null);
7526
+ const [mediaHover, setMediaHover] = useState6(null);
7527
+ const [uploadingRects, setUploadingRects] = useState6({});
7528
+ const hoveredGapRef = useRef5(null);
7529
+ const imageUnhoverTimerRef = useRef5(null);
7530
+ const imageShowTimerRef = useRef5(null);
7531
+ const editStylesRef = useRef5(null);
7532
+ const activateRef = useRef5(() => {
6375
7533
  });
6376
- const deactivateRef = useRef3(() => {
7534
+ const deactivateRef = useRef5(() => {
6377
7535
  });
6378
- const selectRef = useRef3(() => {
7536
+ const selectRef = useRef5(() => {
6379
7537
  });
6380
- const deselectRef = useRef3(() => {
7538
+ const selectFrameRef = useRef5(() => {
6381
7539
  });
6382
- const reselectNavigationItemRef = useRef3(() => {
7540
+ const deselectRef = useRef5(() => {
6383
7541
  });
6384
- const commitNavigationTextEditRef = useRef3(() => {
7542
+ const reselectNavigationItemRef = useRef5(() => {
6385
7543
  });
6386
- const refreshActiveCommandsRef = useRef3(() => {
7544
+ const commitNavigationTextEditRef = useRef5(() => {
6387
7545
  });
6388
- const postToParentRef = useRef3(postToParent);
6389
- postToParentRef.current = postToParent;
6390
- const sectionsLoadedRef = useRef3(false);
6391
- const pendingScheduleConfigRequests = useRef3([]);
6392
- const [toolbarRect, setToolbarRect] = useState5(null);
6393
- const [toolbarVariant, setToolbarVariant] = useState5("none");
6394
- const toolbarVariantRef = useRef3("none");
7546
+ const refreshActiveCommandsRef = useRef5(() => {
7547
+ });
7548
+ const postToParentRef = useRef5(postToParent2);
7549
+ postToParentRef.current = postToParent2;
7550
+ const sectionsLoadedRef = useRef5(false);
7551
+ const pendingScheduleConfigRequests = useRef5([]);
7552
+ const [toolbarRect, setToolbarRect] = useState6(null);
7553
+ const [toolbarVariant, setToolbarVariant] = useState6("none");
7554
+ const toolbarVariantRef = useRef5("none");
6395
7555
  toolbarVariantRef.current = toolbarVariant;
6396
- const [reorderHrefKey, setReorderHrefKey] = useState5(null);
6397
- const [reorderDragDisabled, setReorderDragDisabled] = useState5(false);
6398
- const [toggleState, setToggleState] = useState5(null);
6399
- const [maxBadge, setMaxBadge] = useState5(null);
6400
- const [activeCommands, setActiveCommands] = useState5(/* @__PURE__ */ new Set());
6401
- const [sectionGap, setSectionGap] = useState5(null);
6402
- const [toolbarShowEditLink, setToolbarShowEditLink] = useState5(false);
6403
- const hoveredItemElRef = useRef3(null);
6404
- const [hoveredItemRect, setHoveredItemRect] = useState5(null);
6405
- const siblingHintElRef = useRef3(null);
6406
- const [siblingHintRect, setSiblingHintRect] = useState5(null);
6407
- const [isItemDragging, setIsItemDragging] = useState5(false);
6408
- const [linkPopover, setLinkPopover] = useState5(null);
6409
- const [sitePages, setSitePages] = useState5([]);
6410
- const [sectionsByPath, setSectionsByPath] = useState5({});
6411
- const sectionsPrefetchGenRef = useRef3(0);
6412
- const setLinkPopoverRef = useRef3(setLinkPopover);
6413
- const linkPopoverPanelRef = useRef3(null);
6414
- const linkPopoverOpenRef = useRef3(false);
6415
- const linkPopoverGraceUntilRef = useRef3(0);
7556
+ const [reorderHrefKey, setReorderHrefKey] = useState6(null);
7557
+ const [reorderDragDisabled, setReorderDragDisabled] = useState6(false);
7558
+ const [toggleState, setToggleState] = useState6(null);
7559
+ const [maxBadge, setMaxBadge] = useState6(null);
7560
+ const [activeCommands, setActiveCommands] = useState6(/* @__PURE__ */ new Set());
7561
+ const [sectionGap, setSectionGap] = useState6(null);
7562
+ const [toolbarShowEditLink, setToolbarShowEditLink] = useState6(false);
7563
+ const hoveredNavContainerRef = useRef5(null);
7564
+ const [hoveredNavContainerRect, setHoveredNavContainerRect] = useState6(null);
7565
+ const hoveredItemElRef = useRef5(null);
7566
+ const [hoveredItemRect, setHoveredItemRect] = useState6(null);
7567
+ const siblingHintElRef = useRef5(null);
7568
+ const [siblingHintRect, setSiblingHintRect] = useState6(null);
7569
+ const [isItemDragging, setIsItemDragging] = useState6(false);
7570
+ const [linkPopover, setLinkPopover] = useState6(null);
7571
+ const linkPopoverSessionRef = useRef5(null);
7572
+ const addNavAfterAnchorRef = useRef5(null);
7573
+ const editContentRef = useRef5({});
7574
+ const [sitePages, setSitePages] = useState6([]);
7575
+ const [sectionsByPath, setSectionsByPath] = useState6({});
7576
+ const sectionsPrefetchGenRef = useRef5(0);
7577
+ const setLinkPopoverRef = useRef5(setLinkPopover);
7578
+ const linkPopoverPanelRef = useRef5(null);
7579
+ const linkPopoverOpenRef = useRef5(false);
7580
+ const linkPopoverGraceUntilRef = useRef5(0);
6416
7581
  setLinkPopoverRef.current = setLinkPopover;
7582
+ linkPopoverSessionRef.current = linkPopover;
6417
7583
  const bumpLinkPopoverGrace = () => {
6418
7584
  linkPopoverGraceUntilRef.current = Date.now() + 350;
6419
7585
  };
6420
- const runSectionsPrefetch = useCallback2((pages) => {
7586
+ const runSectionsPrefetch = useCallback4((pages) => {
6421
7587
  if (!isEditMode || shouldUseDevFixtures() || pages.length === 0) return;
6422
7588
  const gen = ++sectionsPrefetchGenRef.current;
6423
7589
  const paths = pages.map((p) => p.path);
@@ -6436,9 +7602,9 @@ function OhhwellsBridge() {
6436
7602
  );
6437
7603
  });
6438
7604
  }, [isEditMode, pathname]);
6439
- const runSectionsPrefetchRef = useRef3(runSectionsPrefetch);
7605
+ const runSectionsPrefetchRef = useRef5(runSectionsPrefetch);
6440
7606
  runSectionsPrefetchRef.current = runSectionsPrefetch;
6441
- useEffect3(() => {
7607
+ useEffect7(() => {
6442
7608
  if (!linkPopover) return;
6443
7609
  if (hoveredImageRef.current) {
6444
7610
  hoveredImageRef.current = null;
@@ -6446,33 +7612,9 @@ function OhhwellsBridge() {
6446
7612
  }
6447
7613
  hoveredGapRef.current = null;
6448
7614
  setSectionGap(null);
6449
- postToParent({ type: "ow:image-unhover" });
6450
- postToParent({ type: "ow:link-modal-lock", locked: true });
6451
- const html = document.documentElement;
6452
- const body = document.body;
6453
- const prevHtmlOverflow = html.style.overflow;
6454
- const prevBodyOverflow = body.style.overflow;
6455
- html.style.overflow = "hidden";
6456
- body.style.overflow = "hidden";
6457
- const preventBackgroundScroll = (e) => {
6458
- const target = e.target;
6459
- if (target instanceof Element && target.closest('[data-ohw-link-modal-root], [data-slot="dialog-overlay"]')) {
6460
- return;
6461
- }
6462
- e.preventDefault();
6463
- };
6464
- const scrollOpts = { passive: false, capture: true };
6465
- document.addEventListener("wheel", preventBackgroundScroll, scrollOpts);
6466
- document.addEventListener("touchmove", preventBackgroundScroll, scrollOpts);
6467
- return () => {
6468
- postToParent({ type: "ow:link-modal-lock", locked: false });
6469
- html.style.overflow = prevHtmlOverflow;
6470
- body.style.overflow = prevBodyOverflow;
6471
- document.removeEventListener("wheel", preventBackgroundScroll, scrollOpts);
6472
- document.removeEventListener("touchmove", preventBackgroundScroll, scrollOpts);
6473
- };
6474
- }, [linkPopover, postToParent]);
6475
- useEffect3(() => {
7615
+ postToParent2({ type: "ow:image-unhover" });
7616
+ }, [linkPopover, postToParent2]);
7617
+ useEffect7(() => {
6476
7618
  if (!isEditMode) return;
6477
7619
  const useFixtures = shouldUseDevFixtures();
6478
7620
  if (useFixtures) {
@@ -6493,17 +7635,17 @@ function OhhwellsBridge() {
6493
7635
  runSectionsPrefetchRef.current(mapped);
6494
7636
  };
6495
7637
  window.addEventListener("message", onSitePages);
6496
- if (!useFixtures) postToParent({ type: "ow:request-site-pages" });
7638
+ if (!useFixtures) postToParent2({ type: "ow:request-site-pages" });
6497
7639
  return () => window.removeEventListener("message", onSitePages);
6498
- }, [isEditMode, postToParent]);
6499
- useEffect3(() => {
7640
+ }, [isEditMode, postToParent2]);
7641
+ useEffect7(() => {
6500
7642
  if (!isEditMode || shouldUseDevFixtures()) return;
6501
7643
  void loadAllSectionsManifest().then((manifest) => {
6502
7644
  if (Object.keys(manifest).length === 0) return;
6503
7645
  setSectionsByPath((prev) => ({ ...manifest, ...prev }));
6504
7646
  });
6505
7647
  }, [isEditMode]);
6506
- useEffect3(() => {
7648
+ useEffect7(() => {
6507
7649
  const update = () => {
6508
7650
  const el = activeElRef.current ?? selectedElRef.current;
6509
7651
  if (el) setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
@@ -6527,10 +7669,10 @@ function OhhwellsBridge() {
6527
7669
  vvp.removeEventListener("resize", update);
6528
7670
  };
6529
7671
  }, []);
6530
- const refreshStateRules = useCallback2(() => {
7672
+ const refreshStateRules = useCallback4(() => {
6531
7673
  editStylesRef.current?.forceHover && (editStylesRef.current.forceHover.textContent = collectStateRules());
6532
7674
  }, []);
6533
- const processConfigRequest = useCallback2((insertAfterVal) => {
7675
+ const processConfigRequest = useCallback4((insertAfterVal) => {
6534
7676
  const tracker = getSectionsTracker();
6535
7677
  let entries = [];
6536
7678
  try {
@@ -6553,7 +7695,7 @@ function OhhwellsBridge() {
6553
7695
  }
6554
7696
  window.postMessage({ type: "ow:schedule-config", insertAfter: insertAfterVal, scheduleId: null }, "*");
6555
7697
  }, [isEditMode]);
6556
- const deactivate = useCallback2(() => {
7698
+ const deactivate = useCallback4(() => {
6557
7699
  const el = activeElRef.current;
6558
7700
  if (!el) return;
6559
7701
  const key = el.dataset.ohwKey;
@@ -6582,21 +7724,23 @@ function OhhwellsBridge() {
6582
7724
  setMaxBadge(null);
6583
7725
  setActiveCommands(/* @__PURE__ */ new Set());
6584
7726
  setToolbarShowEditLink(false);
6585
- postToParent({ type: "ow:exit-edit" });
6586
- }, [postToParent]);
6587
- const deselect = useCallback2(() => {
7727
+ postToParent2({ type: "ow:exit-edit" });
7728
+ }, [postToParent2]);
7729
+ const deselect = useCallback4(() => {
6588
7730
  selectedElRef.current = null;
6589
7731
  setReorderHrefKey(null);
6590
7732
  setReorderDragDisabled(false);
6591
7733
  siblingHintElRef.current = null;
6592
7734
  setSiblingHintRect(null);
6593
7735
  setIsItemDragging(false);
7736
+ hoveredNavContainerRef.current = null;
7737
+ setHoveredNavContainerRect(null);
6594
7738
  if (!activeElRef.current) {
6595
7739
  setToolbarRect(null);
6596
7740
  setToolbarVariant("none");
6597
7741
  }
6598
7742
  }, []);
6599
- const reselectNavigationItem = useCallback2((navAnchor) => {
7743
+ const reselectNavigationItem = useCallback4((navAnchor) => {
6600
7744
  selectedElRef.current = navAnchor;
6601
7745
  const { key, disabled } = getNavigationItemReorderState(navAnchor);
6602
7746
  setReorderHrefKey(key);
@@ -6606,7 +7750,7 @@ function OhhwellsBridge() {
6606
7750
  setToolbarShowEditLink(false);
6607
7751
  setActiveCommands(/* @__PURE__ */ new Set());
6608
7752
  }, []);
6609
- const commitNavigationTextEdit = useCallback2((navAnchor) => {
7753
+ const commitNavigationTextEdit = useCallback4((navAnchor) => {
6610
7754
  const el = activeElRef.current;
6611
7755
  if (!el) return;
6612
7756
  const key = el.dataset.ohwKey;
@@ -6619,9 +7763,9 @@ function OhhwellsBridge() {
6619
7763
  const html = sanitizeHtml(el.innerHTML);
6620
7764
  const original = originalContentRef.current ?? "";
6621
7765
  if (html !== sanitizeHtml(original)) {
6622
- postToParent({ type: "ow:change", nodes: [{ key, text: html }] });
7766
+ postToParent2({ type: "ow:change", nodes: [{ key, text: html }] });
6623
7767
  const h = document.documentElement.scrollHeight;
6624
- if (h > 50) postToParent({ type: "ow:height", height: h });
7768
+ if (h > 50) postToParent2({ type: "ow:height", height: h });
6625
7769
  }
6626
7770
  }
6627
7771
  el.removeAttribute("contenteditable");
@@ -6629,31 +7773,38 @@ function OhhwellsBridge() {
6629
7773
  setMaxBadge(null);
6630
7774
  setActiveCommands(/* @__PURE__ */ new Set());
6631
7775
  setToolbarShowEditLink(false);
6632
- postToParent({ type: "ow:exit-edit" });
7776
+ postToParent2({ type: "ow:exit-edit" });
6633
7777
  reselectNavigationItem(navAnchor);
6634
- }, [postToParent, reselectNavigationItem]);
6635
- const handleAddNavigationItem = useCallback2(() => {
6636
- const selected = selectedElRef.current;
6637
- if (!selected) return;
6638
- const target = getNavigationItemAddHintTarget(selected);
6639
- siblingHintElRef.current = target;
6640
- setSiblingHintRect(target.getBoundingClientRect());
7778
+ }, [postToParent2, reselectNavigationItem]);
7779
+ const handleAddTopLevelNavItem = useCallback4(() => {
7780
+ const items = listNavbarItems();
7781
+ addNavAfterAnchorRef.current = items[items.length - 1] ?? null;
7782
+ deselectRef.current();
7783
+ deactivateRef.current();
7784
+ bumpLinkPopoverGrace();
7785
+ setLinkPopover({
7786
+ key: "__add-nav__",
7787
+ mode: "create",
7788
+ intent: "add-nav"
7789
+ });
6641
7790
  }, []);
6642
- const handleItemDragStart = useCallback2(() => {
7791
+ const handleItemDragStart = useCallback4(() => {
6643
7792
  siblingHintElRef.current = null;
6644
7793
  setSiblingHintRect(null);
6645
7794
  setIsItemDragging(true);
6646
7795
  }, []);
6647
- const handleItemDragEnd = useCallback2(() => {
7796
+ const handleItemDragEnd = useCallback4(() => {
6648
7797
  setIsItemDragging(false);
6649
7798
  }, []);
6650
7799
  reselectNavigationItemRef.current = reselectNavigationItem;
6651
7800
  commitNavigationTextEditRef.current = commitNavigationTextEdit;
6652
- const select = useCallback2((anchor) => {
7801
+ const select = useCallback4((anchor) => {
6653
7802
  if (!isNavigationItem(anchor)) return;
6654
7803
  if (activeElRef.current) deactivate();
6655
7804
  selectedElRef.current = anchor;
6656
7805
  clearHrefKeyHover(anchor);
7806
+ hoveredNavContainerRef.current = null;
7807
+ setHoveredNavContainerRect(null);
6657
7808
  setHoveredItemRect(null);
6658
7809
  hoveredItemElRef.current = null;
6659
7810
  siblingHintElRef.current = null;
@@ -6667,13 +7818,32 @@ function OhhwellsBridge() {
6667
7818
  setToolbarShowEditLink(false);
6668
7819
  setActiveCommands(/* @__PURE__ */ new Set());
6669
7820
  }, [deactivate]);
6670
- const activate = useCallback2((el) => {
7821
+ const selectFrame = useCallback4((el) => {
7822
+ if (!isNavigationContainer(el)) return;
7823
+ if (activeElRef.current) deactivate();
7824
+ selectedElRef.current = el;
7825
+ clearHrefKeyHover(el);
7826
+ hoveredNavContainerRef.current = null;
7827
+ setHoveredNavContainerRect(null);
7828
+ setHoveredItemRect(null);
7829
+ hoveredItemElRef.current = null;
7830
+ siblingHintElRef.current = null;
7831
+ setSiblingHintRect(null);
7832
+ setIsItemDragging(false);
7833
+ setReorderHrefKey(null);
7834
+ setReorderDragDisabled(false);
7835
+ setToolbarVariant("select-frame");
7836
+ setToolbarRect(el.getBoundingClientRect());
7837
+ setToolbarShowEditLink(false);
7838
+ setActiveCommands(/* @__PURE__ */ new Set());
7839
+ }, [deactivate]);
7840
+ const activate = useCallback4((el, options) => {
6671
7841
  if (activeElRef.current === el) return;
6672
7842
  selectedElRef.current = null;
6673
7843
  deactivate();
6674
7844
  if (hoveredImageRef.current) {
6675
7845
  hoveredImageRef.current = null;
6676
- postToParentRef.current({ type: "ow:image-unhover" });
7846
+ setMediaHover(null);
6677
7847
  }
6678
7848
  setToolbarVariant("rich-text");
6679
7849
  siblingHintElRef.current = null;
@@ -6685,6 +7855,9 @@ function OhhwellsBridge() {
6685
7855
  activeElRef.current = el;
6686
7856
  originalContentRef.current = el.innerHTML;
6687
7857
  el.focus();
7858
+ if (options?.caretX !== void 0 && options?.caretY !== void 0) {
7859
+ placeCaretAtPoint(el, options.caretX, options.caretY);
7860
+ }
6688
7861
  setToolbarShowEditLink(Boolean(getHrefKeyFromElement(el)));
6689
7862
  const navAnchor = getNavigationItemAnchor(el);
6690
7863
  if (navAnchor) {
@@ -6696,12 +7869,13 @@ function OhhwellsBridge() {
6696
7869
  setReorderDragDisabled(reorderDisabled);
6697
7870
  }
6698
7871
  setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
6699
- postToParent({ type: "ow:enter-edit", key: el.dataset.ohwKey });
7872
+ postToParent2({ type: "ow:enter-edit", key: el.dataset.ohwKey });
6700
7873
  requestAnimationFrame(() => refreshActiveCommandsRef.current());
6701
- }, [deactivate, postToParent]);
7874
+ }, [deactivate, postToParent2]);
6702
7875
  activateRef.current = activate;
6703
7876
  deactivateRef.current = deactivate;
6704
7877
  selectRef.current = select;
7878
+ selectFrameRef.current = selectFrame;
6705
7879
  deselectRef.current = deselect;
6706
7880
  useLayoutEffect2(() => {
6707
7881
  if (!subdomain || isEditMode) {
@@ -6712,6 +7886,7 @@ function OhhwellsBridge() {
6712
7886
  const imageLoads = [];
6713
7887
  for (const [key, val] of Object.entries(content)) {
6714
7888
  if (key === "__ohw_sections") continue;
7889
+ if (applyVideoSettingNode(key, val)) continue;
6715
7890
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
6716
7891
  if (el.dataset.ohwEditable === "image") {
6717
7892
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -6725,6 +7900,15 @@ function OhhwellsBridge() {
6725
7900
  } else if (el.dataset.ohwEditable === "bg-image") {
6726
7901
  const next = `url('${val}')`;
6727
7902
  if (el.style.backgroundImage !== next) el.style.backgroundImage = next;
7903
+ } else if (el.dataset.ohwEditable === "video") {
7904
+ const video = getVideoEl(el);
7905
+ if (video && video.src !== val) {
7906
+ applyVideoSrc(video, val);
7907
+ imageLoads.push(new Promise((resolve) => {
7908
+ video.onloadeddata = () => resolve();
7909
+ video.onerror = () => resolve();
7910
+ }));
7911
+ }
6728
7912
  } else if (el.dataset.ohwEditable === "link") {
6729
7913
  applyLinkHref(el, val);
6730
7914
  } else if (el.innerHTML !== val) {
@@ -6733,6 +7917,7 @@ function OhhwellsBridge() {
6733
7917
  });
6734
7918
  applyLinkByKey(key, val);
6735
7919
  }
7920
+ reconcileNavbarItemsFromContent(content);
6736
7921
  enforceLinkHrefs();
6737
7922
  initSectionsFromContent(content, true);
6738
7923
  sectionsLoadedRef.current = true;
@@ -6761,7 +7946,7 @@ function OhhwellsBridge() {
6761
7946
  cancelled = true;
6762
7947
  };
6763
7948
  }, [subdomain, isEditMode]);
6764
- useEffect3(() => {
7949
+ useEffect7(() => {
6765
7950
  if (!subdomain || isEditMode) return;
6766
7951
  let debounceTimer = null;
6767
7952
  let observer = null;
@@ -6773,6 +7958,7 @@ function OhhwellsBridge() {
6773
7958
  try {
6774
7959
  for (const [key, val] of Object.entries(content)) {
6775
7960
  if (key === "__ohw_sections") continue;
7961
+ if (applyVideoSettingNode(key, val)) continue;
6776
7962
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
6777
7963
  if (el.dataset.ohwEditable === "image") {
6778
7964
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -6780,6 +7966,9 @@ function OhhwellsBridge() {
6780
7966
  } else if (el.dataset.ohwEditable === "bg-image") {
6781
7967
  const next = `url('${val}')`;
6782
7968
  if (el.style.backgroundImage !== next) el.style.backgroundImage = next;
7969
+ } else if (el.dataset.ohwEditable === "video") {
7970
+ const video = getVideoEl(el);
7971
+ if (video && video.src !== val) applyVideoSrc(video, val);
6783
7972
  } else if (el.dataset.ohwEditable === "link") {
6784
7973
  applyLinkHref(el, val);
6785
7974
  } else if (el.innerHTML !== val) {
@@ -6788,6 +7977,7 @@ function OhhwellsBridge() {
6788
7977
  });
6789
7978
  applyLinkByKey(key, val);
6790
7979
  }
7980
+ reconcileNavbarItemsFromContent(content);
6791
7981
  } finally {
6792
7982
  observer?.observe(document.body, { childList: true, subtree: true });
6793
7983
  }
@@ -6811,20 +8001,38 @@ function OhhwellsBridge() {
6811
8001
  const visible = Boolean(subdomain) && fetchState !== "done";
6812
8002
  el.style.display = visible ? "flex" : "none";
6813
8003
  }, [subdomain, fetchState]);
6814
- useEffect3(() => {
6815
- postToParent({ type: "ow:navigation", path: pathname });
6816
- }, [pathname, postToParent]);
6817
- useEffect3(() => {
8004
+ useEffect7(() => {
8005
+ postToParent2({ type: "ow:navigation", path: pathname });
8006
+ }, [pathname, postToParent2]);
8007
+ useEffect7(() => {
6818
8008
  if (!isEditMode) return;
8009
+ if (linkPopoverSessionRef.current?.intent === "add-nav") return;
8010
+ if (document.querySelector("[data-ohw-section-picker]")) return;
6819
8011
  setLinkPopover(null);
6820
8012
  deselectRef.current();
6821
8013
  deactivateRef.current();
6822
8014
  }, [pathname, isEditMode]);
6823
- useEffect3(() => {
8015
+ useEffect7(() => {
8016
+ const contentForNav = () => {
8017
+ if (isEditMode) return editContentRef.current;
8018
+ if (!subdomain) return {};
8019
+ return contentCache.get(subdomain) ?? {};
8020
+ };
8021
+ const run = () => reconcileNavbarItemsFromContent(contentForNav());
8022
+ run();
8023
+ const nav = document.querySelector("nav");
8024
+ if (!nav) return;
8025
+ const observer = new MutationObserver(() => {
8026
+ requestAnimationFrame(run);
8027
+ });
8028
+ observer.observe(nav, { childList: true, subtree: true });
8029
+ return () => observer.disconnect();
8030
+ }, [isEditMode, pathname, subdomain, fetchState]);
8031
+ useEffect7(() => {
6824
8032
  if (!isEditMode) return;
6825
8033
  const measure = () => {
6826
8034
  const h = document.body.scrollHeight;
6827
- if (h > 50) postToParent({ type: "ow:height", height: h });
8035
+ if (h > 50) postToParent2({ type: "ow:height", height: h });
6828
8036
  };
6829
8037
  const t1 = setTimeout(measure, 50);
6830
8038
  const t2 = setTimeout(measure, 500);
@@ -6843,8 +8051,8 @@ function OhhwellsBridge() {
6843
8051
  if (resizeTimer) clearTimeout(resizeTimer);
6844
8052
  window.removeEventListener("resize", handleResize);
6845
8053
  };
6846
- }, [pathname, isEditMode, postToParent]);
6847
- useEffect3(() => {
8054
+ }, [pathname, isEditMode, postToParent2]);
8055
+ useEffect7(() => {
6848
8056
  if (!subdomainFromQuery || isEditMode) return;
6849
8057
  const handleClick = (e) => {
6850
8058
  const anchor = e.target.closest("a");
@@ -6860,7 +8068,7 @@ function OhhwellsBridge() {
6860
8068
  document.addEventListener("click", handleClick, true);
6861
8069
  return () => document.removeEventListener("click", handleClick, true);
6862
8070
  }, [subdomainFromQuery, isEditMode, router]);
6863
- useEffect3(() => {
8071
+ useEffect7(() => {
6864
8072
  if (!isEditMode) {
6865
8073
  editStylesRef.current?.base.remove();
6866
8074
  editStylesRef.current?.forceHover.remove();
@@ -6883,8 +8091,9 @@ function OhhwellsBridge() {
6883
8091
  [data-ohw-editable] {
6884
8092
  display: block;
6885
8093
  }
6886
- [data-ohw-editable]:not([contenteditable]):not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"]) { cursor: text !important; }
8094
+ [data-ohw-editable]:not([contenteditable]):not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"]):not([data-ohw-editable="video"]) { cursor: text !important; }
6887
8095
  [data-ohw-editable="image"], [data-ohw-editable="image"] *,
8096
+ [data-ohw-editable="video"], [data-ohw-editable="video"] *,
6888
8097
  [data-ohw-editable="bg-image"], [data-ohw-editable="bg-image"] * { cursor: pointer !important; }
6889
8098
  [data-ohw-editable="link"], [data-ohw-editable="link"] * { cursor: pointer !important; }
6890
8099
  [data-ohw-hovered]:not([contenteditable]):not([data-ohw-href-key]) {
@@ -6933,6 +8142,13 @@ function OhhwellsBridge() {
6933
8142
  if (target.closest("[data-ohw-max-badge]")) return;
6934
8143
  if (isInsideLinkEditor(target)) return;
6935
8144
  if (target.closest('[data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
8145
+ const navFromChrome = resolveNavContainerSelectionTarget(target, e.clientX, e.clientY);
8146
+ if (navFromChrome) {
8147
+ e.preventDefault();
8148
+ e.stopPropagation();
8149
+ selectFrameRef.current(navFromChrome);
8150
+ return;
8151
+ }
6936
8152
  e.preventDefault();
6937
8153
  e.stopPropagation();
6938
8154
  return;
@@ -6947,14 +8163,15 @@ function OhhwellsBridge() {
6947
8163
  bumpLinkPopoverGrace();
6948
8164
  setLinkPopoverRef.current({
6949
8165
  key: editable.dataset.ohwKey ?? "",
6950
- target: getLinkHref(editable)
8166
+ mode: "edit",
8167
+ target: getLinkHref2(editable)
6951
8168
  });
6952
8169
  return;
6953
8170
  }
6954
- if (editable.dataset.ohwEditable === "image" || editable.dataset.ohwEditable === "bg-image") {
8171
+ if (isMediaEditable(editable)) {
6955
8172
  e.preventDefault();
6956
8173
  e.stopPropagation();
6957
- postToParentRef.current({ type: "ow:image-pick", key: editable.dataset.ohwKey ?? "" });
8174
+ postToParentRef.current({ type: "ow:image-pick", key: editable.dataset.ohwKey ?? "", elementType: editable.dataset.ohwEditable ?? "image" });
6958
8175
  return;
6959
8176
  }
6960
8177
  const hrefCtx = getHrefKeyFromElement(editable);
@@ -6963,7 +8180,8 @@ function OhhwellsBridge() {
6963
8180
  e.preventDefault();
6964
8181
  e.stopPropagation();
6965
8182
  if (selectedElRef.current === navAnchor) {
6966
- activateRef.current(editable);
8183
+ if (e.detail >= 2) return;
8184
+ activateRef.current(editable, { caretX: e.clientX, caretY: e.clientY });
6967
8185
  return;
6968
8186
  }
6969
8187
  selectRef.current(navAnchor);
@@ -6982,6 +8200,22 @@ function OhhwellsBridge() {
6982
8200
  selectRef.current(hrefAnchor);
6983
8201
  return;
6984
8202
  }
8203
+ const navContainerToSelect = resolveNavContainerSelectionTarget(target, e.clientX, e.clientY);
8204
+ if (navContainerToSelect) {
8205
+ e.preventDefault();
8206
+ e.stopPropagation();
8207
+ selectFrameRef.current(navContainerToSelect);
8208
+ return;
8209
+ }
8210
+ const selectedContainer = selectedElRef.current;
8211
+ if (selectedContainer && isNavigationContainer(selectedContainer)) {
8212
+ const navItem = getNavigationItemAnchor(target);
8213
+ if (!navItem && (selectedContainer === target || selectedContainer.contains(target))) {
8214
+ e.preventDefault();
8215
+ e.stopPropagation();
8216
+ return;
8217
+ }
8218
+ }
6985
8219
  if (activeElRef.current) {
6986
8220
  const sel = window.getSelection();
6987
8221
  if (sel && !sel.isCollapsed) {
@@ -7007,10 +8241,48 @@ function OhhwellsBridge() {
7007
8241
  deselectRef.current();
7008
8242
  deactivateRef.current();
7009
8243
  };
8244
+ const handleDblClick = (e) => {
8245
+ const target = e.target;
8246
+ if (target.closest("[data-ohw-toolbar]")) return;
8247
+ if (target.closest("[data-ohw-state-toggle]")) return;
8248
+ if (target.closest("[data-ohw-max-badge]")) return;
8249
+ if (isInsideLinkEditor(target)) return;
8250
+ if (target.closest('[data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
8251
+ return;
8252
+ }
8253
+ const navLabel = getNavigationLabelEditable(target);
8254
+ if (!navLabel) return;
8255
+ const { editable } = navLabel;
8256
+ e.preventDefault();
8257
+ e.stopPropagation();
8258
+ if (activeElRef.current !== editable) {
8259
+ activateRef.current(editable);
8260
+ }
8261
+ requestAnimationFrame(() => {
8262
+ selectAllTextInEditable(editable);
8263
+ refreshActiveCommandsRef.current();
8264
+ });
8265
+ };
7010
8266
  const handleMouseOver = (e) => {
7011
8267
  const target = e.target;
8268
+ if (toolbarVariantRef.current !== "select-frame") {
8269
+ const navContainer = target.closest("[data-ohw-nav-container]");
8270
+ if (navContainer && !getNavigationItemAnchor(target)) {
8271
+ hoveredNavContainerRef.current = navContainer;
8272
+ setHoveredNavContainerRect(navContainer.getBoundingClientRect());
8273
+ hoveredItemElRef.current = null;
8274
+ setHoveredItemRect(null);
8275
+ return;
8276
+ }
8277
+ if (!target.closest("[data-ohw-nav-container]")) {
8278
+ hoveredNavContainerRef.current = null;
8279
+ setHoveredNavContainerRect(null);
8280
+ }
8281
+ }
7012
8282
  const navAnchor = getNavigationItemAnchor(target);
7013
8283
  if (navAnchor) {
8284
+ hoveredNavContainerRef.current = null;
8285
+ setHoveredNavContainerRect(null);
7014
8286
  const selected2 = selectedElRef.current;
7015
8287
  if (selected2 === navAnchor || selected2?.contains(navAnchor)) return;
7016
8288
  clearHrefKeyHover(navAnchor);
@@ -7022,7 +8294,7 @@ function OhhwellsBridge() {
7022
8294
  if (!editable) return;
7023
8295
  const selected = selectedElRef.current;
7024
8296
  if (selected && (selected === editable || selected.contains(editable))) return;
7025
- if (editable.dataset.ohwEditable !== "image" && editable.dataset.ohwEditable !== "bg-image" && !editable.hasAttribute("contenteditable")) {
8297
+ if (!isMediaEditable(editable) && !editable.hasAttribute("contenteditable")) {
7026
8298
  const hoverTarget = editable.closest("[data-ohw-href-key]") ?? editable;
7027
8299
  if (hoverTarget.hasAttribute("data-ohw-href-key")) {
7028
8300
  clearHrefKeyHover(hoverTarget);
@@ -7035,6 +8307,14 @@ function OhhwellsBridge() {
7035
8307
  };
7036
8308
  const handleMouseOut = (e) => {
7037
8309
  const target = e.target;
8310
+ if (target.closest("[data-ohw-nav-container]")) {
8311
+ const related2 = e.relatedTarget instanceof Element ? e.relatedTarget : null;
8312
+ if (related2?.closest("[data-ohw-nav-container], [data-ohw-navbar-container-chrome], [data-ohw-navbar-add-button]")) {
8313
+ return;
8314
+ }
8315
+ hoveredNavContainerRef.current = null;
8316
+ setHoveredNavContainerRect(null);
8317
+ }
7038
8318
  const navAnchor = getNavigationItemAnchor(target);
7039
8319
  if (navAnchor) {
7040
8320
  const related2 = e.relatedTarget instanceof Element ? e.relatedTarget : null;
@@ -7050,7 +8330,7 @@ function OhhwellsBridge() {
7050
8330
  if (!editable) return;
7051
8331
  const related = e.relatedTarget instanceof Element ? e.relatedTarget : null;
7052
8332
  if (related?.closest("[data-ohw-drag-handle-container], [data-ohw-item-interaction]")) return;
7053
- if (editable.dataset.ohwEditable !== "image" && editable.dataset.ohwEditable !== "bg-image") {
8333
+ if (!isMediaEditable(editable)) {
7054
8334
  const hoverTarget = editable.closest("[data-ohw-href-key]") ?? editable;
7055
8335
  if (hoverTarget.hasAttribute("data-ohw-href-key")) {
7056
8336
  if (!related?.closest("[data-ohw-href-key]")) {
@@ -7096,23 +8376,26 @@ function OhhwellsBridge() {
7096
8376
  }
7097
8377
  return { top, left, width: Math.max(0, right - left), height: Math.max(0, bottom - top) };
7098
8378
  };
7099
- const postImageHover = (imgEl, isDragOver = false, forceTextOverlap) => {
8379
+ const showImageHover = (imgEl, isDragOver = false, forceTextOverlap) => {
7100
8380
  const r2 = getVisibleRect(imgEl);
7101
8381
  const hasTextOverlap = forceTextOverlap ?? false;
7102
8382
  hoveredImageHasTextOverlapRef.current = hasTextOverlap;
7103
- postToParentRef.current({
7104
- type: "ow:image-hover",
8383
+ const video = imgEl.dataset.ohwEditable === "video" ? getVideoEl(imgEl) : null;
8384
+ setMediaHover({
7105
8385
  key: imgEl.dataset.ohwKey ?? "",
7106
8386
  rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height },
8387
+ elementType: imgEl.dataset.ohwEditable ?? "image",
7107
8388
  hasTextOverlap,
7108
- ...isDragOver ? { isDragOver: true } : {}
8389
+ isDragOver,
8390
+ ...video ? { videoAutoplay: video.autoplay, videoMuted: video.muted } : {}
7109
8391
  });
7110
8392
  const track = imgEl.closest("[data-ohw-hover-pause]");
7111
8393
  if (track) track.setAttribute("data-ohw-hover-paused", "");
7112
8394
  };
8395
+ const clearImageHover = () => setMediaHover(null);
7113
8396
  const findImageAtPoint = (clientX, clientY, fromParentViewport) => {
7114
8397
  const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
7115
- const images = Array.from(document.querySelectorAll('[data-ohw-editable="image"], [data-ohw-editable="bg-image"]'));
8398
+ const images = Array.from(document.querySelectorAll(MEDIA_SELECTOR));
7116
8399
  const matches = [];
7117
8400
  for (let i = images.length - 1; i >= 0; i--) {
7118
8401
  const el = images[i];
@@ -7128,7 +8411,9 @@ function OhhwellsBridge() {
7128
8411
  if (x >= r2.left && x <= r2.left + r2.width && y >= r2.top && y <= r2.top + r2.height) matches.push(el);
7129
8412
  }
7130
8413
  if (matches.length === 0) return null;
7131
- const imageTypeMatches = matches.filter((el) => el.dataset.ohwEditable === "image");
8414
+ const imageTypeMatches = matches.filter(
8415
+ (el) => el.dataset.ohwEditable === "image" || el.dataset.ohwEditable === "video"
8416
+ );
7132
8417
  const candidatePool = imageTypeMatches.length > 0 ? imageTypeMatches : matches;
7133
8418
  const smallest = candidatePool.reduce((best, el) => {
7134
8419
  const br = getVisibleRect(best);
@@ -7151,47 +8436,91 @@ function OhhwellsBridge() {
7151
8436
  }
7152
8437
  return smallest;
7153
8438
  };
8439
+ const dismissImageHover = () => {
8440
+ if (hoveredImageRef.current) {
8441
+ hoveredImageRef.current = null;
8442
+ hoveredImageHasTextOverlapRef.current = false;
8443
+ resumeAnimTracks();
8444
+ postToParentRef.current({ type: "ow:image-unhover" });
8445
+ }
8446
+ clearImageHover();
8447
+ };
8448
+ const probeNavigationHoverAt = (x, y) => {
8449
+ if (toolbarVariantRef.current === "select-frame") {
8450
+ hoveredNavContainerRef.current = null;
8451
+ setHoveredNavContainerRect(null);
8452
+ return;
8453
+ }
8454
+ const navContainer = document.querySelector("[data-ohw-nav-container]");
8455
+ if (!navContainer) {
8456
+ hoveredNavContainerRef.current = null;
8457
+ setHoveredNavContainerRect(null);
8458
+ return;
8459
+ }
8460
+ const containerRect = navContainer.getBoundingClientRect();
8461
+ const overContainer = x >= containerRect.left && x <= containerRect.right && y >= containerRect.top && y <= containerRect.bottom;
8462
+ if (!overContainer) {
8463
+ hoveredNavContainerRef.current = null;
8464
+ setHoveredNavContainerRect(null);
8465
+ return;
8466
+ }
8467
+ const navItemHit = Array.from(
8468
+ navContainer.querySelectorAll("[data-ohw-href-key]")
8469
+ ).find((el) => {
8470
+ if (!isNavigationItem(el) || isNavbarButton2(el)) return false;
8471
+ const itemRect = el.getBoundingClientRect();
8472
+ return x >= itemRect.left && x <= itemRect.right && y >= itemRect.top && y <= itemRect.bottom;
8473
+ });
8474
+ if (navItemHit) {
8475
+ hoveredNavContainerRef.current = null;
8476
+ setHoveredNavContainerRect(null);
8477
+ const selected = selectedElRef.current;
8478
+ if (selected !== navItemHit && !selected?.contains(navItemHit)) {
8479
+ clearHrefKeyHover(navItemHit);
8480
+ hoveredItemElRef.current = navItemHit;
8481
+ setHoveredItemRect(navItemHit.getBoundingClientRect());
8482
+ }
8483
+ return;
8484
+ }
8485
+ hoveredNavContainerRef.current = navContainer;
8486
+ setHoveredNavContainerRect(containerRect);
8487
+ hoveredItemElRef.current = null;
8488
+ setHoveredItemRect(null);
8489
+ };
7154
8490
  const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
7155
8491
  if (linkPopoverOpenRef.current) {
7156
- if (hoveredImageRef.current) {
7157
- hoveredImageRef.current = null;
7158
- hoveredImageHasTextOverlapRef.current = false;
7159
- resumeAnimTracks();
7160
- postToParentRef.current({ type: "ow:image-unhover" });
7161
- }
8492
+ dismissImageHover();
7162
8493
  return;
7163
8494
  }
8495
+ const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
8496
+ if (isPointOverNavigation(x, y)) {
8497
+ dismissImageHover();
8498
+ probeNavigationHoverAt(x, y);
8499
+ return;
8500
+ }
8501
+ hoveredNavContainerRef.current = null;
8502
+ setHoveredNavContainerRect(null);
7164
8503
  const toggleEl = document.querySelector("[data-ohw-state-toggle]");
7165
8504
  if (toggleEl) {
7166
- const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
7167
8505
  const tr = toggleEl.getBoundingClientRect();
7168
8506
  if (x >= tr.left && x <= tr.right && y >= tr.top && y <= tr.bottom) {
7169
- if (hoveredImageRef.current) {
7170
- hoveredImageRef.current = null;
7171
- resumeAnimTracks();
7172
- postToParentRef.current({ type: "ow:image-unhover" });
7173
- }
8507
+ dismissImageHover();
7174
8508
  return;
7175
8509
  }
7176
8510
  }
7177
- if (activeElRef.current) {
7178
- if (hoveredImageRef.current) {
7179
- hoveredImageRef.current = null;
7180
- hoveredImageHasTextOverlapRef.current = false;
7181
- resumeAnimTracks();
7182
- postToParentRef.current({ type: "ow:image-unhover" });
7183
- }
8511
+ const imgEl = findImageAtPoint(clientX, clientY, fromParentViewport);
8512
+ const activeEl = activeElRef.current;
8513
+ if (activeEl && (!imgEl || imgEl.contains(activeEl))) {
8514
+ dismissImageHover();
7184
8515
  return;
7185
8516
  }
7186
- const imgEl = findImageAtPoint(clientX, clientY, fromParentViewport);
7187
8517
  if (imgEl) {
7188
- const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
7189
8518
  const topEl = document.elementFromPoint(x, y);
7190
8519
  if (topEl?.closest('[data-ohw-toolbar], [data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
7191
8520
  if (hoveredImageRef.current) {
7192
8521
  hoveredImageRef.current = null;
7193
8522
  resumeAnimTracks();
7194
- postToParentRef.current({ type: "ow:image-unhover" });
8523
+ clearImageHover();
7195
8524
  }
7196
8525
  return;
7197
8526
  }
@@ -7200,13 +8529,13 @@ function OhhwellsBridge() {
7200
8529
  hoveredImageRef.current = null;
7201
8530
  hoveredImageHasTextOverlapRef.current = false;
7202
8531
  resumeAnimTracks();
7203
- postToParentRef.current({ type: "ow:image-unhover" });
8532
+ clearImageHover();
7204
8533
  }
7205
8534
  return;
7206
8535
  }
7207
8536
  const isStateCardImage = !!imgEl.closest("[data-ohw-editable-state]");
7208
8537
  const textEditable = Array.from(
7209
- document.querySelectorAll('[data-ohw-editable]:not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"])')
8538
+ document.querySelectorAll(NON_MEDIA_SELECTOR)
7210
8539
  ).find((el) => {
7211
8540
  const er = el.getBoundingClientRect();
7212
8541
  return x >= er.left && x <= er.right && y >= er.top && y <= er.bottom;
@@ -7225,14 +8554,14 @@ function OhhwellsBridge() {
7225
8554
  hoveredImageRef.current = null;
7226
8555
  hoveredImageHasTextOverlapRef.current = false;
7227
8556
  resumeAnimTracks();
7228
- postToParentRef.current({ type: "ow:image-unhover" });
8557
+ clearImageHover();
7229
8558
  }
7230
8559
  return;
7231
8560
  }
7232
8561
  if (isStateCardImage) {
7233
8562
  if (imgEl !== hoveredImageRef.current || !hoveredImageHasTextOverlapRef.current) {
7234
8563
  hoveredImageRef.current = imgEl;
7235
- postImageHover(imgEl, isDragOver, true);
8564
+ showImageHover(imgEl, isDragOver, true);
7236
8565
  }
7237
8566
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
7238
8567
  if (textEditable && !isInsideNavigationItem(textEditable)) {
@@ -7244,7 +8573,7 @@ function OhhwellsBridge() {
7244
8573
  hoveredImageRef.current = null;
7245
8574
  hoveredImageHasTextOverlapRef.current = false;
7246
8575
  resumeAnimTracks();
7247
- postToParentRef.current({ type: "ow:image-unhover" });
8576
+ clearImageHover();
7248
8577
  }
7249
8578
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
7250
8579
  if (textEditable && !isInsideNavigationItem(textEditable)) {
@@ -7256,7 +8585,7 @@ function OhhwellsBridge() {
7256
8585
  if (hoveredImageRef.current) {
7257
8586
  hoveredImageRef.current = null;
7258
8587
  resumeAnimTracks();
7259
- postToParentRef.current({ type: "ow:image-unhover" });
8588
+ clearImageHover();
7260
8589
  }
7261
8590
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
7262
8591
  return;
@@ -7264,9 +8593,9 @@ function OhhwellsBridge() {
7264
8593
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
7265
8594
  if (imgEl !== hoveredImageRef.current || hoveredImageHasTextOverlapRef.current) {
7266
8595
  hoveredImageRef.current = imgEl;
7267
- postImageHover(imgEl, isDragOver);
8596
+ showImageHover(imgEl, isDragOver);
7268
8597
  } else if (isDragOver) {
7269
- postImageHover(imgEl, true);
8598
+ showImageHover(imgEl, true);
7270
8599
  }
7271
8600
  } else {
7272
8601
  const inIframeView = clientX >= 0 && clientY >= 0 && clientX <= window.innerWidth && clientY <= window.innerHeight;
@@ -7283,14 +8612,14 @@ function OhhwellsBridge() {
7283
8612
  hoveredImageRef.current = null;
7284
8613
  hoveredImageHasTextOverlapRef.current = false;
7285
8614
  resumeAnimTracks();
7286
- postToParentRef.current({ type: "ow:image-unhover" });
8615
+ clearImageHover();
7287
8616
  }
7288
- const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
8617
+ const { x: x2, y: y2 } = toProbeCoords(clientX, clientY, fromParentViewport);
7289
8618
  const textEl = Array.from(
7290
- document.querySelectorAll('[data-ohw-editable]:not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"])')
8619
+ document.querySelectorAll(NON_MEDIA_SELECTOR)
7291
8620
  ).find((el) => {
7292
8621
  const er = el.getBoundingClientRect();
7293
- return x >= er.left && x <= er.right && y >= er.top && y <= er.bottom;
8622
+ return x2 >= er.left && x2 <= er.right && y2 >= er.top && y2 <= er.bottom;
7294
8623
  });
7295
8624
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
7296
8625
  if (textEl && !textEl.hasAttribute("contenteditable")) {
@@ -7393,18 +8722,20 @@ function OhhwellsBridge() {
7393
8722
  return;
7394
8723
  }
7395
8724
  e.dataTransfer.dropEffect = "copy";
7396
- if (hoveredImageRef.current !== el) {
8725
+ if (dragOverElRef.current !== el) {
8726
+ dragOverElRef.current = el;
7397
8727
  hoveredImageRef.current = el;
7398
8728
  const isStateCard = !!el.closest("[data-ohw-editable-state]");
7399
- postImageHover(el, true, isStateCard ? true : void 0);
8729
+ showImageHover(el, true, isStateCard ? true : void 0);
7400
8730
  }
7401
8731
  };
7402
8732
  const handleDragLeave = (e) => {
7403
8733
  const imgEl = findImageAtPoint(e.clientX, e.clientY, false);
7404
8734
  if (imgEl) return;
8735
+ dragOverElRef.current = null;
7405
8736
  hoveredImageRef.current = null;
7406
8737
  resumeAnimTracks();
7407
- postToParentRef.current({ type: "ow:image-unhover" });
8738
+ clearImageHover();
7408
8739
  };
7409
8740
  const handleDrop = (e) => {
7410
8741
  e.preventDefault();
@@ -7412,7 +8743,9 @@ function OhhwellsBridge() {
7412
8743
  if (!el) return;
7413
8744
  const file = e.dataTransfer?.files?.[0];
7414
8745
  if (file) {
7415
- if (file.type.startsWith("image/")) {
8746
+ const wantsVideo = el.dataset.ohwEditable === "video";
8747
+ const accepted = wantsVideo ? file.type.startsWith("video/") : file.type.startsWith("image/");
8748
+ if (accepted) {
7416
8749
  const key = el.dataset.ohwKey ?? "";
7417
8750
  const { name, type: mimeType } = file;
7418
8751
  const r2 = el.getBoundingClientRect();
@@ -7421,12 +8754,13 @@ function OhhwellsBridge() {
7421
8754
  window.parent.postMessage({ type: "ow:image-drop", key, name, mimeType, buf, rect }, "*", [buf]);
7422
8755
  });
7423
8756
  } else {
7424
- postToParentRef.current({ type: "ow:image-drop-invalid" });
8757
+ postToParentRef.current({ type: "ow:image-drop-invalid", expected: wantsVideo ? "video" : "image" });
7425
8758
  }
7426
8759
  }
8760
+ dragOverElRef.current = null;
7427
8761
  hoveredImageRef.current = null;
7428
8762
  resumeAnimTracks();
7429
- postToParentRef.current({ type: "ow:image-unhover" });
8763
+ clearImageHover();
7430
8764
  };
7431
8765
  const handleImageUrl = (e) => {
7432
8766
  if (e.data?.type !== "ow:image-url") return;
@@ -7444,7 +8778,11 @@ function OhhwellsBridge() {
7444
8778
  }
7445
8779
  });
7446
8780
  hoveredImageRef.current = null;
7447
- postToParentRef.current({ type: "ow:image-loaded", key });
8781
+ setUploadingRects((prev) => {
8782
+ const entry = prev[key];
8783
+ if (!entry || entry.fadingOut) return prev;
8784
+ return { ...prev, [key]: { ...entry, fadingOut: true } };
8785
+ });
7448
8786
  };
7449
8787
  let found = false;
7450
8788
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -7479,6 +8817,19 @@ function OhhwellsBridge() {
7479
8817
  requestAnimationFrame(() => onReady());
7480
8818
  }
7481
8819
  }
8820
+ } else if (el.dataset.ohwEditable === "video") {
8821
+ const video = getVideoEl(el);
8822
+ if (video) {
8823
+ found = true;
8824
+ const onReady = () => {
8825
+ video.onloadeddata = null;
8826
+ video.onerror = null;
8827
+ notify();
8828
+ };
8829
+ video.onloadeddata = onReady;
8830
+ video.onerror = onReady;
8831
+ applyVideoSrc(video, url);
8832
+ }
7482
8833
  }
7483
8834
  });
7484
8835
  if (!found) notify();
@@ -7545,12 +8896,16 @@ function OhhwellsBridge() {
7545
8896
  sectionsJson = val;
7546
8897
  continue;
7547
8898
  }
8899
+ if (applyVideoSettingNode(key, val)) continue;
7548
8900
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
7549
8901
  if (el.dataset.ohwEditable === "image") {
7550
8902
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
7551
8903
  if (img) applyEditableImageSrc(img, val);
7552
8904
  } else if (el.dataset.ohwEditable === "bg-image") {
7553
8905
  el.style.backgroundImage = `url('${val}')`;
8906
+ } else if (el.dataset.ohwEditable === "video") {
8907
+ const video = getVideoEl(el);
8908
+ if (video && video.src !== val) applyVideoSrc(video, val);
7554
8909
  } else if (el.dataset.ohwEditable === "link") {
7555
8910
  applyLinkHref(el, val);
7556
8911
  } else {
@@ -7564,6 +8919,8 @@ function OhhwellsBridge() {
7564
8919
  sectionsLoadedRef.current = true;
7565
8920
  pendingScheduleConfigRequests.current.splice(0).forEach(processConfigRequest);
7566
8921
  }
8922
+ editContentRef.current = { ...editContentRef.current, ...content };
8923
+ reconcileNavbarItemsFromContent(editContentRef.current);
7567
8924
  enforceLinkHrefs();
7568
8925
  postToParentRef.current({ type: "ow:hydrate-done" });
7569
8926
  };
@@ -7571,6 +8928,7 @@ function OhhwellsBridge() {
7571
8928
  const handleDeactivate = (e) => {
7572
8929
  if (e.data?.type !== "ow:deactivate") return;
7573
8930
  if (Date.now() < linkPopoverGraceUntilRef.current) return;
8931
+ if (document.querySelector("[data-ohw-section-picker]")) return;
7574
8932
  if (linkPopoverOpenRef.current) {
7575
8933
  setLinkPopoverRef.current(null);
7576
8934
  return;
@@ -7580,12 +8938,32 @@ function OhhwellsBridge() {
7580
8938
  };
7581
8939
  window.addEventListener("message", handleDeactivate);
7582
8940
  const handleKeyDown = (e) => {
8941
+ if (e.key === "Escape" && document.querySelector("[data-ohw-section-picker]")) return;
8942
+ if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "a" && activeElRef.current) {
8943
+ const navAnchor = getNavigationItemAnchor(activeElRef.current);
8944
+ if (navAnchor) {
8945
+ e.preventDefault();
8946
+ selectAllTextInEditable(activeElRef.current);
8947
+ refreshActiveCommandsRef.current();
8948
+ return;
8949
+ }
8950
+ }
7583
8951
  if (e.key === "Escape" && linkPopoverOpenRef.current) {
7584
8952
  setLinkPopoverRef.current(null);
7585
8953
  return;
7586
8954
  }
7587
8955
  if (e.key === "Escape" && selectedElRef.current && !activeElRef.current) {
7588
- deselectRef.current();
8956
+ if (toolbarVariantRef.current === "select-frame") {
8957
+ deselectRef.current();
8958
+ return;
8959
+ }
8960
+ const parent = getNavigationSelectionParent(selectedElRef.current);
8961
+ if (parent) {
8962
+ e.preventDefault();
8963
+ selectFrameRef.current(parent);
8964
+ } else {
8965
+ deselectRef.current();
8966
+ }
7589
8967
  return;
7590
8968
  }
7591
8969
  if (e.key === "Escape" && activeElRef.current) {
@@ -7643,18 +9021,18 @@ function OhhwellsBridge() {
7643
9021
  if (hoveredItemElRef.current) {
7644
9022
  setHoveredItemRect(hoveredItemElRef.current.getBoundingClientRect());
7645
9023
  }
9024
+ if (hoveredNavContainerRef.current) {
9025
+ setHoveredNavContainerRect(hoveredNavContainerRef.current.getBoundingClientRect());
9026
+ }
7646
9027
  if (siblingHintElRef.current) {
7647
9028
  setSiblingHintRect(siblingHintElRef.current.getBoundingClientRect());
7648
9029
  }
7649
9030
  if (hoveredImageRef.current) {
7650
9031
  const el = hoveredImageRef.current;
7651
9032
  const r2 = el.getBoundingClientRect();
7652
- postToParentRef.current({
7653
- type: "ow:image-hover",
7654
- key: el.dataset.ohwKey ?? "",
7655
- rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height },
7656
- hasTextOverlap: hoveredImageHasTextOverlapRef.current
7657
- });
9033
+ setMediaHover(
9034
+ (prev) => prev ? { ...prev, rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height } } : prev
9035
+ );
7658
9036
  }
7659
9037
  };
7660
9038
  const handleSave = (e) => {
@@ -7743,19 +9121,39 @@ function OhhwellsBridge() {
7743
9121
  refreshActiveCommandsRef.current = handleSelectionChange;
7744
9122
  const handleDocMouseLeave = () => {
7745
9123
  hoveredImageRef.current = null;
9124
+ setMediaHover(null);
7746
9125
  document.querySelectorAll("[data-ohw-state-hovered]").forEach((el) => el.removeAttribute("data-ohw-state-hovered"));
7747
9126
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
7748
9127
  activeStateElRef.current = null;
7749
9128
  setToggleState(null);
7750
9129
  };
7751
- const handleAnimLock = (e) => {
7752
- if (e.data?.type !== "ow:anim-lock") return;
7753
- const { key } = e.data;
9130
+ const handleImageUploading = (e) => {
9131
+ if (e.data?.type !== "ow:image-uploading") return;
9132
+ const { key, uploading } = e.data;
9133
+ setUploadingRects((prev) => {
9134
+ if (!uploading) {
9135
+ if (!(key in prev)) return prev;
9136
+ const next = { ...prev };
9137
+ delete next[key];
9138
+ return next;
9139
+ }
9140
+ const el = document.querySelector(`[data-ohw-key="${key}"]`);
9141
+ if (!el) return prev;
9142
+ const r2 = getVisibleRect(el);
9143
+ return {
9144
+ ...prev,
9145
+ [key]: { rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height } }
9146
+ };
9147
+ });
7754
9148
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
7755
9149
  const track = el.closest("[data-ohw-hover-pause]");
7756
- if (track) {
9150
+ if (!track) return;
9151
+ if (uploading) {
7757
9152
  uploadLockedTracks.add(track);
7758
9153
  track.setAttribute("data-ohw-hover-paused", "");
9154
+ } else {
9155
+ uploadLockedTracks.delete(track);
9156
+ track.removeAttribute("data-ohw-hover-paused");
7759
9157
  }
7760
9158
  });
7761
9159
  };
@@ -7797,7 +9195,7 @@ function OhhwellsBridge() {
7797
9195
  if (e.data?.type !== "ow:click-at") return;
7798
9196
  const { clientX, clientY } = e.data;
7799
9197
  const allImages = Array.from(
7800
- document.querySelectorAll('[data-ohw-editable="image"], [data-ohw-editable="bg-image"]')
9198
+ document.querySelectorAll(MEDIA_SELECTOR)
7801
9199
  ).filter((el) => !!el.closest("[data-ohw-editable-state]"));
7802
9200
  const stateCardImage = allImages.find((el) => {
7803
9201
  const ownerCard = el.closest("[data-ohw-editable-state]");
@@ -7812,21 +9210,55 @@ function OhhwellsBridge() {
7812
9210
  return clientX >= r2.left && clientX <= r2.right && clientY >= r2.top && clientY <= r2.bottom;
7813
9211
  });
7814
9212
  if (stateCardImage) {
7815
- postToParentRef.current({ type: "ow:image-pick", key: stateCardImage.dataset.ohwKey ?? "" });
9213
+ postToParentRef.current({ type: "ow:image-pick", key: stateCardImage.dataset.ohwKey ?? "", elementType: stateCardImage.dataset.ohwEditable ?? "image" });
7816
9214
  return;
7817
9215
  }
7818
9216
  const textEditable = Array.from(
7819
- document.querySelectorAll(
7820
- '[data-ohw-editable]:not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"])'
7821
- )
9217
+ document.querySelectorAll(NON_MEDIA_SELECTOR)
7822
9218
  ).find((el) => {
7823
9219
  const r2 = el.getBoundingClientRect();
7824
9220
  return clientX >= r2.left && clientX <= r2.right && clientY >= r2.top && clientY <= r2.bottom;
7825
9221
  });
7826
9222
  if (textEditable) {
7827
- activateRef.current(textEditable);
9223
+ const hrefCtx = getHrefKeyFromElement(textEditable);
9224
+ const navAnchor = hrefCtx ? getNavigationItemAnchor(hrefCtx.anchor) : null;
9225
+ if (navAnchor) {
9226
+ if (selectedElRef.current === navAnchor) {
9227
+ activateRef.current(textEditable, { caretX: clientX, caretY: clientY });
9228
+ } else {
9229
+ selectRef.current(navAnchor);
9230
+ }
9231
+ return;
9232
+ }
9233
+ activateRef.current(textEditable, { caretX: clientX, caretY: clientY });
7828
9234
  return;
7829
9235
  }
9236
+ const navContainer = document.querySelector("[data-ohw-nav-container]");
9237
+ if (navContainer) {
9238
+ const r2 = navContainer.getBoundingClientRect();
9239
+ const pad = 8;
9240
+ const over = clientX >= r2.left - pad && clientX <= r2.right + pad && clientY >= r2.top - pad && clientY <= r2.bottom + pad;
9241
+ if (over && !isPointOverNavItem(navContainer, clientX, clientY)) {
9242
+ selectFrameRef.current(navContainer);
9243
+ return;
9244
+ }
9245
+ }
9246
+ const navRoot = document.querySelector("[data-ohw-nav-root]");
9247
+ if (navRoot && navContainer) {
9248
+ const rr = navRoot.getBoundingClientRect();
9249
+ if (clientX >= rr.left && clientX <= rr.right && clientY >= rr.top && clientY <= rr.bottom && !isPointOverNavItem(navContainer, clientX, clientY)) {
9250
+ const book = navRoot.querySelector('[data-ohw-role="navbar-button"]');
9251
+ if (book) {
9252
+ const br = book.getBoundingClientRect();
9253
+ if (clientX >= br.left && clientX <= br.right && clientY >= br.top && clientY <= br.bottom) {
9254
+ deactivateRef.current();
9255
+ return;
9256
+ }
9257
+ }
9258
+ selectFrameRef.current(navContainer);
9259
+ return;
9260
+ }
9261
+ }
7830
9262
  deactivateRef.current();
7831
9263
  };
7832
9264
  window.addEventListener("message", handleSave);
@@ -7836,7 +9268,7 @@ function OhhwellsBridge() {
7836
9268
  window.addEventListener("message", handleClearSchedulingWidget);
7837
9269
  window.addEventListener("message", handleRemoveSchedulingSection);
7838
9270
  window.addEventListener("message", handleImageUrl);
7839
- window.addEventListener("message", handleAnimLock);
9271
+ window.addEventListener("message", handleImageUploading);
7840
9272
  window.addEventListener("message", handleCanvasHeight);
7841
9273
  window.addEventListener("message", handleParentScroll);
7842
9274
  window.addEventListener("message", handlePointerSync);
@@ -7848,6 +9280,7 @@ function OhhwellsBridge() {
7848
9280
  };
7849
9281
  window.addEventListener("resize", handleViewportResize, { passive: true });
7850
9282
  document.addEventListener("click", handleClick, true);
9283
+ document.addEventListener("dblclick", handleDblClick, true);
7851
9284
  document.addEventListener("paste", handlePaste, true);
7852
9285
  document.addEventListener("input", handleInput, true);
7853
9286
  document.addEventListener("mouseover", handleMouseOver, true);
@@ -7862,6 +9295,7 @@ function OhhwellsBridge() {
7862
9295
  window.addEventListener("scroll", handleScroll, true);
7863
9296
  return () => {
7864
9297
  document.removeEventListener("click", handleClick, true);
9298
+ document.removeEventListener("dblclick", handleDblClick, true);
7865
9299
  document.removeEventListener("paste", handlePaste, true);
7866
9300
  document.removeEventListener("input", handleInput, true);
7867
9301
  document.removeEventListener("mouseover", handleMouseOver, true);
@@ -7881,7 +9315,7 @@ function OhhwellsBridge() {
7881
9315
  window.removeEventListener("message", handleClearSchedulingWidget);
7882
9316
  window.removeEventListener("message", handleRemoveSchedulingSection);
7883
9317
  window.removeEventListener("message", handleImageUrl);
7884
- window.removeEventListener("message", handleAnimLock);
9318
+ window.removeEventListener("message", handleImageUploading);
7885
9319
  window.removeEventListener("message", handleCanvasHeight);
7886
9320
  window.removeEventListener("message", handleParentScroll);
7887
9321
  window.removeEventListener("resize", handleViewportResize);
@@ -7895,7 +9329,7 @@ function OhhwellsBridge() {
7895
9329
  if (imageShowTimerRef.current) clearTimeout(imageShowTimerRef.current);
7896
9330
  };
7897
9331
  }, [isEditMode, refreshStateRules]);
7898
- useEffect3(() => {
9332
+ useEffect7(() => {
7899
9333
  const handler = (e) => {
7900
9334
  if (e.data?.type !== "ow:request-schedule-config") return;
7901
9335
  const insertAfterVal = e.data.insertAfter;
@@ -7911,7 +9345,7 @@ function OhhwellsBridge() {
7911
9345
  window.addEventListener("message", handler);
7912
9346
  return () => window.removeEventListener("message", handler);
7913
9347
  }, [processConfigRequest]);
7914
- useEffect3(() => {
9348
+ useEffect7(() => {
7915
9349
  if (!isEditMode) return;
7916
9350
  document.querySelectorAll("[data-ohw-active-state]").forEach((el) => {
7917
9351
  el.removeAttribute("data-ohw-active-state");
@@ -7932,27 +9366,27 @@ function OhhwellsBridge() {
7932
9366
  const next = { ...prev, [pathKey]: sections };
7933
9367
  return shouldUseDevFixtures() ? { ...DEV_SECTIONS_BY_PATH, ...next } : next;
7934
9368
  });
7935
- postToParent({ type: "ow:ready", version: "1", path: pathname, nodes: collectEditableNodes(), sections });
7936
- postToParent({ type: "ow:request-site-pages" });
9369
+ postToParent2({ type: "ow:ready", version: "1", path: pathname, nodes: collectEditableNodes(), sections });
9370
+ postToParent2({ type: "ow:request-site-pages" });
7937
9371
  }, 150);
7938
9372
  return () => {
7939
9373
  cancelAnimationFrame(raf);
7940
9374
  clearTimeout(timer);
7941
9375
  };
7942
- }, [pathname, isEditMode, refreshStateRules, postToParent]);
7943
- useEffect3(() => {
9376
+ }, [pathname, isEditMode, refreshStateRules, postToParent2]);
9377
+ useEffect7(() => {
7944
9378
  scrollToHashSectionWhenReady();
7945
9379
  const onHashChange = () => scrollToHashSectionWhenReady();
7946
9380
  window.addEventListener("hashchange", onHashChange);
7947
9381
  return () => window.removeEventListener("hashchange", onHashChange);
7948
9382
  }, [pathname]);
7949
- const handleCommand = useCallback2((cmd) => {
9383
+ const handleCommand = useCallback4((cmd) => {
7950
9384
  document.execCommand(cmd, false);
7951
9385
  activeElRef.current?.focus();
7952
9386
  if (activeElRef.current) setToolbarRect(getEditMeasureEl(activeElRef.current).getBoundingClientRect());
7953
9387
  refreshActiveCommandsRef.current();
7954
9388
  }, []);
7955
- const handleStateChange = useCallback2((state) => {
9389
+ const handleStateChange = useCallback4((state) => {
7956
9390
  if (!activeStateElRef.current) return;
7957
9391
  const el = activeStateElRef.current;
7958
9392
  if (state === "Default") {
@@ -7965,18 +9399,22 @@ function OhhwellsBridge() {
7965
9399
  }
7966
9400
  setToggleState((prev) => prev ? { ...prev, activeState: state } : null);
7967
9401
  }, [deactivate]);
7968
- const closeLinkPopover = useCallback2(() => setLinkPopover(null), []);
7969
- const openLinkPopoverForActive = useCallback2(() => {
9402
+ const closeLinkPopover = useCallback4(() => {
9403
+ addNavAfterAnchorRef.current = null;
9404
+ setLinkPopover(null);
9405
+ }, []);
9406
+ const openLinkPopoverForActive = useCallback4(() => {
7970
9407
  const hrefCtx = getHrefKeyFromElement(activeElRef.current);
7971
9408
  if (!hrefCtx) return;
7972
9409
  bumpLinkPopoverGrace();
7973
9410
  setLinkPopover({
7974
9411
  key: hrefCtx.key,
7975
- target: getLinkHref(hrefCtx.anchor)
9412
+ mode: "edit",
9413
+ target: getLinkHref2(hrefCtx.anchor)
7976
9414
  });
7977
9415
  deactivate();
7978
9416
  }, [deactivate]);
7979
- const openLinkPopoverForSelected = useCallback2(() => {
9417
+ const openLinkPopoverForSelected = useCallback4(() => {
7980
9418
  const anchor = selectedElRef.current;
7981
9419
  if (!anchor) return;
7982
9420
  const key = anchor.getAttribute("data-ohw-href-key");
@@ -7984,51 +9422,163 @@ function OhhwellsBridge() {
7984
9422
  bumpLinkPopoverGrace();
7985
9423
  setLinkPopover({
7986
9424
  key,
7987
- target: getLinkHref(anchor)
9425
+ mode: "edit",
9426
+ target: getLinkHref2(anchor)
7988
9427
  });
7989
9428
  deselect();
7990
9429
  }, [deselect]);
7991
- const handleLinkPopoverSubmit = useCallback2(
9430
+ const handleLinkPopoverSubmit = useCallback4(
7992
9431
  (target) => {
7993
- if (!linkPopover) return;
7994
- const { key } = linkPopover;
9432
+ const session = linkPopoverSessionRef.current;
9433
+ if (!session) return;
9434
+ if (session.intent === "add-nav") {
9435
+ const trimmed = target.trim();
9436
+ let href = trimmed;
9437
+ let label = "Untitled";
9438
+ if (trimmed) {
9439
+ const { pageRoute, sectionId } = parseTarget(trimmed);
9440
+ const page = resolvePage(pageRoute, sitePages);
9441
+ const pageSections = getSectionsForPath(sectionsByPath, pageRoute);
9442
+ const section = sectionId ? pageSections.find((s) => s.id === sectionId) : void 0;
9443
+ label = section?.label ?? page.title;
9444
+ href = trimmed;
9445
+ }
9446
+ const afterAnchor = addNavAfterAnchorRef.current;
9447
+ const { anchor, hrefKey, labelKey, index, order } = insertNavbarItem(href, label, afterAnchor);
9448
+ applyLinkByKey(hrefKey, href);
9449
+ document.querySelectorAll(`[data-ohw-key="${labelKey}"]`).forEach((el) => {
9450
+ el.textContent = label;
9451
+ });
9452
+ const navCount = String(index + 1);
9453
+ const orderJson = JSON.stringify(order);
9454
+ editContentRef.current = {
9455
+ ...editContentRef.current,
9456
+ [hrefKey]: href,
9457
+ [labelKey]: label,
9458
+ [NAV_COUNT_KEY]: navCount,
9459
+ [NAV_ORDER_KEY]: orderJson
9460
+ };
9461
+ postToParent2({
9462
+ type: "ow:change",
9463
+ nodes: [
9464
+ { key: hrefKey, text: href },
9465
+ { key: labelKey, text: label },
9466
+ { key: NAV_COUNT_KEY, text: navCount },
9467
+ { key: NAV_ORDER_KEY, text: orderJson }
9468
+ ]
9469
+ });
9470
+ postToParent2({ type: "ow:toast", title: "Link added", toastType: "success" });
9471
+ addNavAfterAnchorRef.current = null;
9472
+ setLinkPopover(null);
9473
+ enforceLinkHrefs();
9474
+ requestAnimationFrame(() => {
9475
+ selectRef.current(anchor);
9476
+ });
9477
+ return;
9478
+ }
9479
+ const { key } = session;
7995
9480
  applyLinkByKey(key, target);
7996
- postToParent({ type: "ow:change", nodes: [{ key, text: target }] });
9481
+ postToParent2({ type: "ow:change", nodes: [{ key, text: target }] });
7997
9482
  setLinkPopover(null);
7998
9483
  },
7999
- [linkPopover, postToParent]
9484
+ [postToParent2, sitePages, sectionsByPath]
8000
9485
  );
8001
9486
  const showEditLink = toolbarShowEditLink;
8002
9487
  const currentSections = sectionsByPath[pathname] ?? [];
8003
9488
  linkPopoverOpenRef.current = linkPopover !== null;
8004
- return bridgeRoot ? createPortal(
8005
- /* @__PURE__ */ jsxs11(Fragment3, { children: [
8006
- /* @__PURE__ */ jsx21("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
8007
- siblingHintRect && !isItemDragging && /* @__PURE__ */ jsx21(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
8008
- hoveredItemRect && !siblingHintRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ jsx21(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
8009
- toolbarRect && toolbarVariant === "link-action" && /* @__PURE__ */ jsx21(
9489
+ const handleMediaReplace = useCallback4(
9490
+ (key) => {
9491
+ postToParent2({ type: "ow:image-pick", key, elementType: mediaHover?.elementType ?? "image" });
9492
+ },
9493
+ [postToParent2, mediaHover?.elementType]
9494
+ );
9495
+ const handleMediaFadeOutComplete = useCallback4((key) => {
9496
+ setUploadingRects((prev) => {
9497
+ if (!(key in prev)) return prev;
9498
+ const next = { ...prev };
9499
+ delete next[key];
9500
+ return next;
9501
+ });
9502
+ }, []);
9503
+ const handleVideoSettingsChange = useCallback4(
9504
+ (key, settings) => {
9505
+ document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
9506
+ const video = getVideoEl(el);
9507
+ if (!video) return;
9508
+ if (typeof settings.autoplay === "boolean") video.autoplay = settings.autoplay;
9509
+ if (typeof settings.muted === "boolean") video.muted = settings.muted;
9510
+ syncVideoPlayback(video);
9511
+ postToParent2({
9512
+ type: "ow:change",
9513
+ nodes: [
9514
+ { key: `${key}${VIDEO_AUTOPLAY_SUFFIX}`, text: String(video.autoplay) },
9515
+ { key: `${key}${VIDEO_MUTED_SUFFIX}`, text: String(video.muted) }
9516
+ ]
9517
+ });
9518
+ setMediaHover(
9519
+ (prev) => prev && prev.key === key ? { ...prev, videoAutoplay: video.autoplay, videoMuted: video.muted } : prev
9520
+ );
9521
+ });
9522
+ },
9523
+ [postToParent2]
9524
+ );
9525
+ return bridgeRoot ? createPortal2(
9526
+ /* @__PURE__ */ jsxs13(Fragment5, { children: [
9527
+ /* @__PURE__ */ jsx23("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
9528
+ Object.entries(uploadingRects).map(([key, { rect, fadingOut }]) => /* @__PURE__ */ jsx23(
9529
+ MediaOverlay,
9530
+ {
9531
+ hover: { key, rect, elementType: "image", isDragOver: false, hasTextOverlap: false },
9532
+ isUploading: true,
9533
+ fadingOut,
9534
+ onFadeOutComplete: handleMediaFadeOutComplete,
9535
+ onReplace: handleMediaReplace
9536
+ },
9537
+ `uploading-${key}`
9538
+ )),
9539
+ mediaHover && !(mediaHover.key in uploadingRects) && /* @__PURE__ */ jsx23(
9540
+ MediaOverlay,
9541
+ {
9542
+ hover: mediaHover,
9543
+ isUploading: false,
9544
+ onReplace: handleMediaReplace,
9545
+ onVideoSettingsChange: handleVideoSettingsChange
9546
+ }
9547
+ ),
9548
+ siblingHintRect && !isItemDragging && /* @__PURE__ */ jsx23(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
9549
+ hoveredNavContainerRect && toolbarVariant !== "select-frame" && !linkPopover && !isItemDragging && !siblingHintRect && /* @__PURE__ */ jsx23(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
9550
+ toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && /* @__PURE__ */ jsx23(
9551
+ NavbarContainerChrome,
9552
+ {
9553
+ rect: toolbarRect,
9554
+ onAdd: handleAddTopLevelNavItem
9555
+ }
9556
+ ),
9557
+ hoveredItemRect && !hoveredNavContainerRect && !siblingHintRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ jsx23(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
9558
+ toolbarRect && (toolbarVariant === "link-action" || toolbarVariant === "select-frame") && /* @__PURE__ */ jsx23(
8010
9559
  ItemInteractionLayer,
8011
9560
  {
8012
9561
  rect: toolbarRect,
8013
9562
  elRef: glowElRef,
8014
9563
  state: isItemDragging ? "dragging" : resolveItemInteractionState(toolbarRect, parentScrollRef.current),
8015
- showHandle: Boolean(reorderHrefKey),
9564
+ showHandle: toolbarVariant === "link-action" && Boolean(reorderHrefKey),
8016
9565
  dragDisabled: reorderDragDisabled,
8017
9566
  dragHandleLabel: reorderHrefKey ? `Reorder ${reorderHrefKey}` : "Reorder item",
8018
9567
  onDragHandleDragStart: handleItemDragStart,
8019
9568
  onDragHandleDragEnd: handleItemDragEnd,
8020
- toolbar: isItemDragging ? void 0 : /* @__PURE__ */ jsx21(
9569
+ toolbar: toolbarVariant === "link-action" && !isItemDragging ? /* @__PURE__ */ jsx23(
8021
9570
  ItemActionToolbar,
8022
9571
  {
8023
9572
  onEditLink: openLinkPopoverForSelected,
8024
- onAddItem: handleAddNavigationItem,
8025
- addItemDisabled: false
9573
+ addItemDisabled: true,
9574
+ editLinkDisabled: false,
9575
+ moreDisabled: true
8026
9576
  }
8027
- )
9577
+ ) : void 0
8028
9578
  }
8029
9579
  ),
8030
- toolbarRect && toolbarVariant === "rich-text" && /* @__PURE__ */ jsxs11(Fragment3, { children: [
8031
- /* @__PURE__ */ jsx21(
9580
+ toolbarRect && toolbarVariant === "rich-text" && /* @__PURE__ */ jsxs13(Fragment5, { children: [
9581
+ /* @__PURE__ */ jsx23(
8032
9582
  EditGlowChrome,
8033
9583
  {
8034
9584
  rect: toolbarRect,
@@ -8037,7 +9587,7 @@ function OhhwellsBridge() {
8037
9587
  dragDisabled: reorderDragDisabled
8038
9588
  }
8039
9589
  ),
8040
- /* @__PURE__ */ jsx21(
9590
+ /* @__PURE__ */ jsx23(
8041
9591
  FloatingToolbar,
8042
9592
  {
8043
9593
  rect: toolbarRect,
@@ -8050,7 +9600,7 @@ function OhhwellsBridge() {
8050
9600
  }
8051
9601
  )
8052
9602
  ] }),
8053
- maxBadge && /* @__PURE__ */ jsxs11(
9603
+ maxBadge && /* @__PURE__ */ jsxs13(
8054
9604
  "div",
8055
9605
  {
8056
9606
  "data-ohw-max-badge": "",
@@ -8076,7 +9626,7 @@ function OhhwellsBridge() {
8076
9626
  ]
8077
9627
  }
8078
9628
  ),
8079
- toggleState && /* @__PURE__ */ jsx21(
9629
+ toggleState && !linkPopover && /* @__PURE__ */ jsx23(
8080
9630
  StateToggle,
8081
9631
  {
8082
9632
  rect: toggleState.rect,
@@ -8085,15 +9635,15 @@ function OhhwellsBridge() {
8085
9635
  onStateChange: handleStateChange
8086
9636
  }
8087
9637
  ),
8088
- sectionGap && /* @__PURE__ */ jsxs11(
9638
+ sectionGap && !linkPopover && /* @__PURE__ */ jsxs13(
8089
9639
  "div",
8090
9640
  {
8091
9641
  "data-ohw-section-insert-line": "",
8092
9642
  className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
8093
9643
  style: { top: sectionGap.y, transform: "translateY(-50%)" },
8094
9644
  children: [
8095
- /* @__PURE__ */ jsx21("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
8096
- /* @__PURE__ */ jsx21(
9645
+ /* @__PURE__ */ jsx23("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
9646
+ /* @__PURE__ */ jsx23(
8097
9647
  Badge,
8098
9648
  {
8099
9649
  className: "px-8 py-1 bg-primary hover:bg-primary text-primary-foreground text-xs font-medium shrink-0 rounded-full cursor-pointer pointer-events-auto",
@@ -8106,26 +9656,52 @@ function OhhwellsBridge() {
8106
9656
  children: "Add Section"
8107
9657
  }
8108
9658
  ),
8109
- /* @__PURE__ */ jsx21("div", { className: "flex-1 bg-primary", style: { height: 3 } })
9659
+ /* @__PURE__ */ jsx23("div", { className: "flex-1 bg-primary", style: { height: 3 } })
8110
9660
  ]
8111
9661
  }
8112
9662
  ),
8113
- linkPopover && dialogPortalContainer ? /* @__PURE__ */ jsx21(
9663
+ linkPopover && dialogPortalContainer ? /* @__PURE__ */ jsx23(
8114
9664
  LinkPopover,
8115
9665
  {
8116
9666
  panelRef: linkPopoverPanelRef,
8117
9667
  portalContainer: dialogPortalContainer,
8118
9668
  open: true,
8119
- mode: "edit",
9669
+ mode: linkPopover.mode ?? "edit",
8120
9670
  pages: sitePages,
8121
9671
  sections: currentSections,
8122
9672
  sectionsByPath,
8123
9673
  initialTarget: linkPopover.target,
9674
+ existingTargets: linkPopover.intent === "add-nav" ? getNavbarExistingTargets() : [],
8124
9675
  onClose: closeLinkPopover,
8125
9676
  onSubmit: handleLinkPopoverSubmit
8126
9677
  },
8127
- `${linkPopover.key}-${pathname}`
8128
- ) : null
9678
+ linkPopover.key
9679
+ ) : null,
9680
+ sectionGap && /* @__PURE__ */ jsxs13(
9681
+ "div",
9682
+ {
9683
+ "data-ohw-section-insert-line": "",
9684
+ className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
9685
+ style: { top: sectionGap.y, transform: "translateY(-50%)" },
9686
+ children: [
9687
+ /* @__PURE__ */ jsx23("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
9688
+ /* @__PURE__ */ jsx23(
9689
+ Badge,
9690
+ {
9691
+ 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",
9692
+ onClick: () => {
9693
+ window.parent.postMessage(
9694
+ { type: "ow:add-section", insertAfter: sectionGap.insertAfter, insertBefore: sectionGap.insertBefore },
9695
+ "*"
9696
+ );
9697
+ },
9698
+ children: "Add Section"
9699
+ }
9700
+ ),
9701
+ /* @__PURE__ */ jsx23("div", { className: "flex-1 bg-primary", style: { height: 3 } })
9702
+ ]
9703
+ }
9704
+ )
8129
9705
  ] }),
8130
9706
  bridgeRoot
8131
9707
  ) : null;