@ohhwells/bridge 0.1.36 → 0.1.38-next.56

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -60,15 +60,18 @@ __export(index_exports, {
60
60
  module.exports = __toCommonJS(index_exports);
61
61
 
62
62
  // src/OhhwellsBridge.tsx
63
- var import_react6 = __toESM(require("react"), 1);
63
+ var import_react8 = __toESM(require("react"), 1);
64
64
  var import_client = require("react-dom/client");
65
- var import_react_dom = require("react-dom");
65
+ var import_react_dom2 = require("react-dom");
66
66
 
67
67
  // src/linkHrefStore.ts
68
68
  var linkHrefStore = /* @__PURE__ */ new Map();
69
69
  function setStoredLinkHref(key, href) {
70
70
  linkHrefStore.set(key, href);
71
71
  }
72
+ function getStoredLinkHref(key) {
73
+ return linkHrefStore.get(key);
74
+ }
72
75
  function enforceLinkHrefs() {
73
76
  document.querySelectorAll("[data-ohw-href-key]").forEach((el) => {
74
77
  const key = el.getAttribute("data-ohw-href-key") ?? "";
@@ -110,6 +113,7 @@ var import_react3 = require("react");
110
113
  var import_react2 = require("react");
111
114
  var import_radix_ui = require("radix-ui");
112
115
  var import_jsx_runtime = require("react/jsx-runtime");
116
+ var isValidEmail = (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
113
117
  function Spinner() {
114
118
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
115
119
  "svg",
@@ -143,12 +147,17 @@ function EmailCaptureModal({ title, subtitle, onSubmit, onClose }) {
143
147
  const [error, setError] = (0, import_react2.useState)(null);
144
148
  const isBook = title === "Confirm your spot";
145
149
  const handleSubmit = async () => {
146
- if (!email.trim()) return;
150
+ const trimmed = email.trim();
151
+ if (!trimmed) return;
152
+ if (!isValidEmail(trimmed)) {
153
+ setError("Please enter a valid email address.");
154
+ return;
155
+ }
147
156
  setLoading(true);
148
157
  setError(null);
149
158
  try {
150
- await onSubmit(email.trim());
151
- setSubmittedEmail(email.trim());
159
+ await onSubmit(trimmed);
160
+ setSubmittedEmail(trimmed);
152
161
  setSuccess(true);
153
162
  } catch (e) {
154
163
  setError(e instanceof Error ? e.message : "Something went wrong. Please try again.");
@@ -223,7 +232,10 @@ function EmailCaptureModal({ title, subtitle, onSubmit, onClose }) {
223
232
  {
224
233
  type: "email",
225
234
  value: email,
226
- onChange: (e) => setEmail(e.target.value),
235
+ onChange: (e) => {
236
+ setEmail(e.target.value);
237
+ if (error) setError(null);
238
+ },
227
239
  onKeyDown: handleKeyDown,
228
240
  placeholder: "hello@gmail.com",
229
241
  autoFocus: true,
@@ -280,12 +292,20 @@ function formatClassTime(cls) {
280
292
  const em = endTotal % 60;
281
293
  return `${h}:${cls.startTime.minutes}-${eh}:${String(em).padStart(2, "0")}`;
282
294
  }
295
+ function formatBookingLabel(cls) {
296
+ const price = cls.price;
297
+ const isFree = price === null || price === void 0 || `${price}` === "" || `${price}` === "0";
298
+ return isFree ? "Book for free" : `Book for ${cls.currency ?? ""} ${price}`.trim();
299
+ }
283
300
  function getBookingsOnDate(cls, date) {
284
301
  if (!cls.bookings?.length) return 0;
285
- const ds = date.toISOString().split("T")[0];
302
+ const target = new Date(date);
303
+ target.setHours(0, 0, 0, 0);
286
304
  return cls.bookings.filter((b) => {
287
305
  try {
288
- return new Date(b.classDate).toISOString().split("T")[0] === ds;
306
+ const bookingDate = new Date(b.classDate);
307
+ bookingDate.setHours(0, 0, 0, 0);
308
+ return bookingDate.getTime() === target.getTime();
289
309
  } catch {
290
310
  return false;
291
311
  }
@@ -589,7 +609,7 @@ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal
589
609
  if (!cls.id) return;
590
610
  onOpenModal?.(isFull ? "waitlist" : "book", cls.id, selectedDate);
591
611
  },
592
- children: isFull ? "Join Waitlist" : "Book Now"
612
+ children: isFull ? "Join Waitlist" : formatBookingLabel(cls)
593
613
  }
594
614
  )
595
615
  ] })
@@ -631,6 +651,17 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
631
651
  const [isHovered, setIsHovered] = (0, import_react3.useState)(false);
632
652
  const [modalState, setModalState] = (0, import_react3.useState)(null);
633
653
  const switchScheduleIdRef = (0, import_react3.useRef)(null);
654
+ const liveScheduleIdRef = (0, import_react3.useRef)(null);
655
+ const refetchLiveSchedule = (0, import_react3.useCallback)(async () => {
656
+ const id = liveScheduleIdRef.current;
657
+ if (!id) return;
658
+ try {
659
+ const res = await fetch(`${API_URL}/api/schedule/id/${id}`);
660
+ const data = await res.json();
661
+ if (data?.id) setSchedule(data);
662
+ } catch {
663
+ }
664
+ }, []);
634
665
  const dates = (0, import_react3.useMemo)(() => {
635
666
  const today = /* @__PURE__ */ new Date();
636
667
  today.setHours(0, 0, 0, 0);
@@ -650,6 +681,18 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
650
681
  }
651
682
  }
652
683
  }, [schedule, dates]);
684
+ (0, import_react3.useEffect)(() => {
685
+ if (typeof window === "undefined") return;
686
+ const onVisible = () => {
687
+ if (!document.hidden) void refetchLiveSchedule();
688
+ };
689
+ window.addEventListener("focus", refetchLiveSchedule);
690
+ document.addEventListener("visibilitychange", onVisible);
691
+ return () => {
692
+ window.removeEventListener("focus", refetchLiveSchedule);
693
+ document.removeEventListener("visibilitychange", onVisible);
694
+ };
695
+ }, [refetchLiveSchedule]);
653
696
  (0, import_react3.useEffect)(() => {
654
697
  if (typeof window === "undefined") return;
655
698
  const isInEditor = window.self !== window.top;
@@ -711,7 +754,7 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
711
754
  setLoading(false);
712
755
  return;
713
756
  }
714
- ;
757
+ liveScheduleIdRef.current = effectiveId;
715
758
  (async () => {
716
759
  try {
717
760
  const res = await fetch(`${API_URL}/api/schedule/id/${effectiveId}`);
@@ -768,18 +811,14 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
768
811
  const data = await res.json().catch(() => ({}));
769
812
  throw new Error(data?.message ?? "Something went wrong. Please try again.");
770
813
  }
814
+ await refetchLiveSchedule();
771
815
  };
772
816
  const handleReplaceSchedule = () => {
773
817
  setIsHovered(false);
774
- if (schedule) {
775
- window.parent.postMessage({
776
- type: "ow:schedule-connected",
777
- schedule: { id: schedule.id, name: schedule.name },
778
- insertAfter
779
- }, "*");
780
- } else {
781
- window.parent.postMessage({ type: "ow:scheduling-not-connected", insertAfter }, "*");
782
- }
818
+ window.parent.postMessage(
819
+ { type: "ow:scheduling-not-connected", insertAfter, currentScheduleId: schedule?.id },
820
+ "*"
821
+ );
783
822
  };
784
823
  if (!inEditor && !loading && !schedule) return null;
785
824
  const sectionId = `scheduling-${insertAfter}`;
@@ -822,7 +861,19 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
822
861
  ),
823
862
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "max-w-[1280px] mx-auto flex flex-col items-center gap-16", children: [
824
863
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col items-center gap-4", children: [
825
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h2", { className: "font-display text-4xl sm:text-5xl font-bold text-center m-0 text-(--color-dark,#200C02)", children: schedule?.name ?? "Book an appointment" }),
864
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
865
+ "h2",
866
+ {
867
+ className: "font-display text-center m-0 text-(--color-dark,#200C02)",
868
+ style: {
869
+ fontSize: "var(--fs-section-h2, clamp(40px, 4.4vw, 64px))",
870
+ fontWeight: "var(--font-weight-heading, 400)",
871
+ lineHeight: 1.05,
872
+ letterSpacing: "-0.02em"
873
+ },
874
+ children: schedule?.name ?? "Book an appointment"
875
+ }
876
+ ),
826
877
  schedule?.description && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "font-body text-body text-center m-0 text-(--color-accent,#A89B83)", children: schedule.description })
827
878
  ] }),
828
879
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "w-full flex flex-col items-end gap-3", children: [
@@ -4408,6 +4459,7 @@ function ItemActionToolbar({
4408
4459
  onEditLink,
4409
4460
  onAddItem,
4410
4461
  onMore,
4462
+ editLinkDisabled = false,
4411
4463
  addItemDisabled = true,
4412
4464
  moreDisabled = true,
4413
4465
  tooltipSide = "bottom"
@@ -4416,10 +4468,12 @@ function ItemActionToolbar({
4416
4468
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4417
4469
  ToolbarActionTooltip,
4418
4470
  {
4419
- label: "Add link",
4471
+ label: "Edit link",
4420
4472
  side: tooltipSide,
4473
+ disabled: editLinkDisabled,
4421
4474
  buttonProps: {
4422
4475
  onMouseDown: (e) => {
4476
+ if (editLinkDisabled) return;
4423
4477
  e.preventDefault();
4424
4478
  e.stopPropagation();
4425
4479
  onEditLink?.();
@@ -4591,9 +4645,223 @@ function ItemInteractionLayer({
4591
4645
  );
4592
4646
  }
4593
4647
 
4648
+ // src/ui/MediaOverlay.tsx
4649
+ var React6 = __toESM(require("react"), 1);
4650
+ var import_lucide_react3 = require("lucide-react");
4651
+
4652
+ // src/ui/button.tsx
4653
+ var React5 = __toESM(require("react"), 1);
4654
+ var import_radix_ui4 = require("radix-ui");
4655
+ var import_jsx_runtime10 = require("react/jsx-runtime");
4656
+ var buttonVariants = cva(
4657
+ "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",
4658
+ {
4659
+ variants: {
4660
+ variant: {
4661
+ default: "bg-primary text-primary-foreground hover:opacity-90",
4662
+ outline: "border border-border bg-background text-foreground shadow-sm hover:bg-muted/80",
4663
+ ghost: "min-w-0 px-3 py-2 text-foreground hover:bg-muted/50"
4664
+ },
4665
+ size: {
4666
+ default: "h-9",
4667
+ sm: "h-8 min-w-[64px] px-2 py-1.5 text-sm"
4668
+ }
4669
+ },
4670
+ defaultVariants: {
4671
+ variant: "default",
4672
+ size: "default"
4673
+ }
4674
+ }
4675
+ );
4676
+ var Button = React5.forwardRef(
4677
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
4678
+ const Comp = asChild ? import_radix_ui4.Slot.Root : "button";
4679
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4680
+ Comp,
4681
+ {
4682
+ ref,
4683
+ "data-slot": "button",
4684
+ className: cn(buttonVariants({ variant, size, className })),
4685
+ ...props
4686
+ }
4687
+ );
4688
+ }
4689
+ );
4690
+ Button.displayName = "Button";
4691
+
4692
+ // src/ui/MediaOverlay.tsx
4693
+ var import_jsx_runtime11 = require("react/jsx-runtime");
4694
+ var MEDIA_UPLOAD_FADE_MS = 300;
4695
+ var VIDEO_SETTINGS_BAR_INSET = 8;
4696
+ var OVERLAY_BUTTON_STYLE = {
4697
+ pointerEvents: "auto",
4698
+ fontFamily: "Inter, sans-serif",
4699
+ fontSize: 12,
4700
+ color: "#000"
4701
+ };
4702
+ var SKELETON_CSS = `
4703
+ @keyframes ohw-media-shimmer {
4704
+ 0% { transform: translateX(-100%); }
4705
+ 100% { transform: translateX(100%); }
4706
+ }
4707
+ [data-ohw-media-skeleton] {
4708
+ overflow: hidden;
4709
+ background-color: #d1d5db; /* tailwind gray-300 */
4710
+ }
4711
+ [data-ohw-media-skeleton]::after {
4712
+ content: "";
4713
+ position: absolute;
4714
+ inset: 0;
4715
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.6), transparent);
4716
+ animation: ohw-media-shimmer 1.4s infinite;
4717
+ }
4718
+ `;
4719
+ function MediaOverlay({
4720
+ hover,
4721
+ isUploading,
4722
+ fadingOut = false,
4723
+ onFadeOutComplete,
4724
+ onReplace,
4725
+ onVideoSettingsChange
4726
+ }) {
4727
+ const { rect } = hover;
4728
+ const skeletonRef = React6.useRef(null);
4729
+ const isVideo = hover.elementType === "video";
4730
+ const autoplay = hover.videoAutoplay ?? true;
4731
+ const muted = hover.videoMuted ?? true;
4732
+ const box = {
4733
+ position: "fixed",
4734
+ top: rect.top,
4735
+ left: rect.left,
4736
+ width: rect.width,
4737
+ height: rect.height,
4738
+ zIndex: 2147483646
4739
+ };
4740
+ React6.useEffect(() => {
4741
+ if (!isUploading || !fadingOut || !skeletonRef.current) return;
4742
+ const anim = skeletonRef.current.animate([{ opacity: 1 }, { opacity: 0 }], {
4743
+ duration: MEDIA_UPLOAD_FADE_MS,
4744
+ easing: "ease-out",
4745
+ fill: "forwards"
4746
+ });
4747
+ anim.finished.then(() => onFadeOutComplete?.(hover.key)).catch(() => onFadeOutComplete?.(hover.key));
4748
+ return () => anim.cancel();
4749
+ }, [isUploading, fadingOut, onFadeOutComplete, hover.key]);
4750
+ if (isUploading) {
4751
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
4752
+ "div",
4753
+ {
4754
+ ref: skeletonRef,
4755
+ "data-ohw-bridge": "",
4756
+ "data-ohw-media-overlay": "",
4757
+ "data-ohw-media-skeleton": "",
4758
+ "aria-hidden": true,
4759
+ style: { ...box, pointerEvents: "none" },
4760
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("style", { children: SKELETON_CSS })
4761
+ }
4762
+ );
4763
+ }
4764
+ const settingsBar = isVideo && !hover.isDragOver ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
4765
+ "div",
4766
+ {
4767
+ "data-ohw-bridge": "",
4768
+ "data-ohw-media-overlay": "",
4769
+ className: "flex items-center justify-center gap-1.5",
4770
+ style: {
4771
+ position: "fixed",
4772
+ top: rect.top + VIDEO_SETTINGS_BAR_INSET,
4773
+ left: rect.left,
4774
+ width: rect.width,
4775
+ zIndex: 2147483647,
4776
+ pointerEvents: "auto"
4777
+ },
4778
+ onClick: (e) => e.stopPropagation(),
4779
+ children: [
4780
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
4781
+ Button,
4782
+ {
4783
+ "data-ohw-media-overlay": "",
4784
+ variant: "outline",
4785
+ size: "sm",
4786
+ className: "cursor-pointer hover:bg-background",
4787
+ style: { ...OVERLAY_BUTTON_STYLE, minWidth: 0, padding: "0 8px" },
4788
+ "aria-label": autoplay ? "Disable autoplay" : "Enable autoplay",
4789
+ title: autoplay ? "Autoplay on \u2014 click to disable" : "Autoplay off \u2014 click to enable",
4790
+ onMouseDown: (e) => e.preventDefault(),
4791
+ onClick: (e) => {
4792
+ e.stopPropagation();
4793
+ onVideoSettingsChange?.(hover.key, { autoplay: !autoplay });
4794
+ },
4795
+ children: autoplay ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react3.Pause, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react3.Play, { size: 14 })
4796
+ }
4797
+ ),
4798
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
4799
+ Button,
4800
+ {
4801
+ "data-ohw-media-overlay": "",
4802
+ variant: "outline",
4803
+ size: "sm",
4804
+ className: "cursor-pointer hover:bg-background",
4805
+ style: { ...OVERLAY_BUTTON_STYLE, minWidth: 0, padding: "0 8px" },
4806
+ "aria-label": muted ? "Unmute video" : "Mute video",
4807
+ title: muted ? "Muted \u2014 click to unmute" : "Sound on \u2014 click to mute",
4808
+ onMouseDown: (e) => e.preventDefault(),
4809
+ onClick: (e) => {
4810
+ e.stopPropagation();
4811
+ onVideoSettingsChange?.(hover.key, { muted: !muted });
4812
+ },
4813
+ children: muted ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react3.VolumeX, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react3.Volume2, { size: 14 })
4814
+ }
4815
+ )
4816
+ ]
4817
+ }
4818
+ ) : null;
4819
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
4820
+ settingsBar,
4821
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
4822
+ "div",
4823
+ {
4824
+ "data-ohw-bridge": "",
4825
+ "data-ohw-media-overlay": "",
4826
+ className: "flex items-center justify-center cursor-pointer",
4827
+ style: {
4828
+ ...box,
4829
+ // When text overlays this media, let clicks fall THROUGH to the text underneath. In the
4830
+ // parent this needed a whole `ow:click-at` round-trip to re-hit-test inside the iframe;
4831
+ // in-document, pointer-events does it natively. The button below opts back in, so
4832
+ // Replace still works.
4833
+ pointerEvents: hover.hasTextOverlap ? "none" : "auto",
4834
+ boxShadow: "inset 0 0 0 1.5px var(--color-primary)",
4835
+ background: "color-mix(in srgb, var(--color-primary) 20%, transparent)"
4836
+ },
4837
+ onClick: () => onReplace(hover.key),
4838
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
4839
+ Button,
4840
+ {
4841
+ "data-ohw-media-overlay": "",
4842
+ variant: "outline",
4843
+ size: "sm",
4844
+ className: "gap-1.5 cursor-pointer hover:bg-background",
4845
+ style: OVERLAY_BUTTON_STYLE,
4846
+ onMouseDown: (e) => e.preventDefault(),
4847
+ onClick: (e) => {
4848
+ e.stopPropagation();
4849
+ onReplace(hover.key);
4850
+ },
4851
+ children: [
4852
+ isVideo ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react3.Film, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react3.ImageIcon, { size: 14 }),
4853
+ isVideo ? "Replace video" : "Replace image"
4854
+ ]
4855
+ }
4856
+ )
4857
+ }
4858
+ )
4859
+ ] });
4860
+ }
4861
+
4594
4862
  // src/OhhwellsBridge.tsx
4595
- var import_react_dom2 = require("react-dom");
4596
- var import_navigation = require("next/navigation");
4863
+ var import_react_dom3 = require("react-dom");
4864
+ var import_navigation2 = require("next/navigation");
4597
4865
 
4598
4866
  // src/lib/session-search.ts
4599
4867
  var STORAGE_KEY = "ohw-preserved-search";
@@ -4869,62 +5137,30 @@ function scrollToHashSectionWhenReady(behavior = "smooth") {
4869
5137
  tick();
4870
5138
  }
4871
5139
 
4872
- // src/ui/dialog.tsx
4873
- var React5 = __toESM(require("react"), 1);
4874
- var import_radix_ui4 = require("radix-ui");
4875
-
4876
- // src/ui/icons.tsx
4877
- var import_jsx_runtime10 = require("react/jsx-runtime");
4878
- function IconX({ className, ...props }) {
4879
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
4880
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d: "M18 6 6 18" }),
4881
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d: "m6 6 12 12" })
4882
- ] });
4883
- }
4884
- function IconChevronDown({ className, ...props }) {
4885
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d: "m6 9 6 6 6-6" }) });
4886
- }
4887
- function IconFile({ className, ...props }) {
4888
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
4889
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }),
4890
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d: "M14 2v4a2 2 0 0 0 2 2h4" })
4891
- ] });
4892
- }
4893
- function IconInfo({ className, ...props }) {
4894
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
4895
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
4896
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d: "M12 16v-4" }),
4897
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d: "M12 8h.01" })
4898
- ] });
4899
- }
4900
- function IconArrowRight({ className, ...props }) {
4901
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
4902
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d: "M5 12h14" }),
4903
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d: "m12 5 7 7-7 7" })
4904
- ] });
4905
- }
4906
- function IconSection({ className, ...props }) {
4907
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
4908
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("rect", { width: "18", height: "7", x: "3", y: "3", rx: "1" }),
4909
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("rect", { width: "9", height: "7", x: "3", y: "14", rx: "1" }),
4910
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("rect", { width: "5", height: "7", x: "16", y: "14", rx: "1" })
4911
- ] });
4912
- }
5140
+ // src/ui/link-modal/LinkPopover.tsx
5141
+ var import_react7 = require("react");
4913
5142
 
4914
5143
  // src/ui/dialog.tsx
4915
- var import_jsx_runtime11 = require("react/jsx-runtime");
4916
- function Dialog2({ ...props }) {
4917
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_radix_ui4.Dialog.Root, { "data-slot": "dialog", ...props });
5144
+ var React7 = __toESM(require("react"), 1);
5145
+ var import_radix_ui5 = require("radix-ui");
5146
+ var import_lucide_react4 = require("lucide-react");
5147
+ var import_jsx_runtime12 = require("react/jsx-runtime");
5148
+ function Dialog2({
5149
+ ...props
5150
+ }) {
5151
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_radix_ui5.Dialog.Root, { "data-slot": "dialog", ...props });
4918
5152
  }
4919
- function DialogPortal({ ...props }) {
4920
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_radix_ui4.Dialog.Portal, { ...props });
5153
+ function DialogPortal({
5154
+ ...props
5155
+ }) {
5156
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_radix_ui5.Dialog.Portal, { ...props });
4921
5157
  }
4922
5158
  function DialogOverlay({
4923
5159
  className,
4924
5160
  ...props
4925
5161
  }) {
4926
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
4927
- import_radix_ui4.Dialog.Overlay,
5162
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
5163
+ import_radix_ui5.Dialog.Overlay,
4928
5164
  {
4929
5165
  "data-slot": "dialog-overlay",
4930
5166
  "data-ohw-link-modal-root": "",
@@ -4933,120 +5169,118 @@ function DialogOverlay({
4933
5169
  }
4934
5170
  );
4935
5171
  }
4936
- var DialogContent = React5.forwardRef(({ className, children, showCloseButton = true, container, ...props }, ref) => {
4937
- const positionMode = container ? "absolute" : "fixed";
4938
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(DialogPortal, { container: container ?? void 0, children: [
4939
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(DialogOverlay, { className: cn(positionMode, "inset-0") }),
4940
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
4941
- import_radix_ui4.Dialog.Content,
4942
- {
4943
- ref,
4944
- "data-slot": "dialog-content",
4945
- className: cn(
4946
- positionMode,
4947
- "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",
4948
- "rounded-xl border border-border bg-background font-sans shadow-lg outline-none",
4949
- container ? "max-h-[calc(100%-32px)] max-w-[calc(100%-16px)]" : "max-h-[calc(100vh-32px)] max-w-[calc(100vw-16px)]",
4950
- className
4951
- ),
4952
- onMouseDown: (e) => e.stopPropagation(),
4953
- ...props,
4954
- children: [
4955
- children,
4956
- showCloseButton ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
4957
- import_radix_ui4.Dialog.Close,
4958
- {
4959
- type: "button",
4960
- className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
4961
- "aria-label": "Close",
4962
- children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(IconX, { "aria-hidden": true })
4963
- }
4964
- ) : null
4965
- ]
4966
- }
4967
- )
4968
- ] });
4969
- });
4970
- DialogContent.displayName = import_radix_ui4.Dialog.Content.displayName;
4971
- function DialogHeader({ className, ...props }) {
4972
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: cn("flex flex-col gap-1.5", className), ...props });
5172
+ var DialogContent = React7.forwardRef(
5173
+ ({ className, children, showCloseButton = true, container, ...props }, ref) => {
5174
+ const positionMode = container ? "absolute" : "fixed";
5175
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(DialogPortal, { container: container ?? void 0, children: [
5176
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(DialogOverlay, { className: cn(positionMode, "inset-0") }),
5177
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
5178
+ import_radix_ui5.Dialog.Content,
5179
+ {
5180
+ ref,
5181
+ "data-slot": "dialog-content",
5182
+ className: cn(
5183
+ positionMode,
5184
+ "left-1/2 top-1/2 z-[2147483647] flex w-full max-w-[483px] -translate-x-1/2 -translate-y-1/2 flex-col overflow-visible",
5185
+ "rounded-xl border border-border bg-background font-sans shadow-lg outline-none",
5186
+ container ? "max-h-[calc(100%-32px)] max-w-[calc(100%-16px)]" : "max-h-[calc(100vh-32px)] max-w-[calc(100vw-16px)]",
5187
+ className
5188
+ ),
5189
+ onMouseDown: (e) => e.stopPropagation(),
5190
+ ...props,
5191
+ children: [
5192
+ children,
5193
+ showCloseButton ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
5194
+ import_radix_ui5.Dialog.Close,
5195
+ {
5196
+ type: "button",
5197
+ className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
5198
+ "aria-label": "Close",
5199
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react4.X, { size: 16, "aria-hidden": true })
5200
+ }
5201
+ ) : null
5202
+ ]
5203
+ }
5204
+ )
5205
+ ] });
5206
+ }
5207
+ );
5208
+ DialogContent.displayName = import_radix_ui5.Dialog.Content.displayName;
5209
+ function DialogHeader({
5210
+ className,
5211
+ ...props
5212
+ }) {
5213
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: cn("flex flex-col gap-1.5", className), ...props });
4973
5214
  }
4974
- function DialogFooter({ className, ...props }) {
4975
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: cn("flex items-center justify-end gap-2", className), ...props });
5215
+ function DialogFooter({
5216
+ className,
5217
+ ...props
5218
+ }) {
5219
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
5220
+ "div",
5221
+ {
5222
+ className: cn("flex items-center justify-end gap-2", className),
5223
+ ...props
5224
+ }
5225
+ );
4976
5226
  }
4977
- var DialogTitle = React5.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
4978
- import_radix_ui4.Dialog.Title,
5227
+ var DialogTitle = React7.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
5228
+ import_radix_ui5.Dialog.Title,
4979
5229
  {
4980
5230
  ref,
4981
- className: cn("text-lg font-semibold leading-none tracking-tight text-card-foreground", className),
5231
+ className: cn(
5232
+ "text-lg font-semibold leading-none tracking-tight text-card-foreground",
5233
+ className
5234
+ ),
4982
5235
  ...props
4983
5236
  }
4984
5237
  ));
4985
- DialogTitle.displayName = import_radix_ui4.Dialog.Title.displayName;
4986
- var DialogDescription = React5.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
4987
- import_radix_ui4.Dialog.Description,
5238
+ DialogTitle.displayName = import_radix_ui5.Dialog.Title.displayName;
5239
+ var DialogDescription = React7.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
5240
+ import_radix_ui5.Dialog.Description,
4988
5241
  {
4989
5242
  ref,
4990
5243
  className: cn("text-sm text-muted-foreground", className),
4991
5244
  ...props
4992
5245
  }
4993
5246
  ));
4994
- DialogDescription.displayName = import_radix_ui4.Dialog.Description.displayName;
4995
- var DialogClose = import_radix_ui4.Dialog.Close;
5247
+ DialogDescription.displayName = import_radix_ui5.Dialog.Description.displayName;
5248
+ var DialogClose = import_radix_ui5.Dialog.Close;
4996
5249
 
4997
- // src/ui/button.tsx
4998
- var React6 = __toESM(require("react"), 1);
4999
- var import_radix_ui5 = require("radix-ui");
5000
- var import_jsx_runtime12 = require("react/jsx-runtime");
5001
- var buttonVariants = cva(
5002
- "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",
5003
- {
5004
- variants: {
5005
- variant: {
5006
- default: "bg-primary text-primary-foreground hover:opacity-90",
5007
- outline: "border border-border bg-background text-foreground shadow-sm hover:bg-muted/80",
5008
- ghost: "min-w-0 px-3 py-2 text-foreground hover:bg-muted/50"
5009
- },
5010
- size: {
5011
- default: "h-9",
5012
- sm: "h-8 min-w-[64px] px-2 py-1.5 text-sm"
5013
- }
5014
- },
5015
- defaultVariants: {
5016
- variant: "default",
5017
- size: "default"
5018
- }
5019
- }
5020
- );
5021
- var Button = React6.forwardRef(
5022
- ({ className, variant, size, asChild = false, ...props }, ref) => {
5023
- const Comp = asChild ? import_radix_ui5.Slot.Root : "button";
5024
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
5025
- Comp,
5026
- {
5027
- ref,
5028
- "data-slot": "button",
5029
- className: cn(buttonVariants({ variant, size, className })),
5030
- ...props
5031
- }
5032
- );
5033
- }
5034
- );
5035
- Button.displayName = "Button";
5250
+ // src/ui/link-modal/LinkEditorPanel.tsx
5251
+ var import_lucide_react8 = require("lucide-react");
5036
5252
 
5037
5253
  // src/ui/link-modal/DestinationBreadcrumb.tsx
5254
+ var import_lucide_react5 = require("lucide-react");
5038
5255
  var import_jsx_runtime13 = require("react/jsx-runtime");
5039
- function DestinationBreadcrumb({ pageTitle, sectionLabel }) {
5256
+ function DestinationBreadcrumb({
5257
+ pageTitle,
5258
+ sectionLabel
5259
+ }) {
5040
5260
  return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex w-full flex-col gap-2", children: [
5041
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { className: "text-sm font-medium text-foreground", children: "Destination" }),
5261
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { className: "text-sm font-medium! text-foreground m-0", children: "Destination" }),
5042
5262
  /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-center gap-3", children: [
5043
5263
  /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-center gap-2", children: [
5044
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(IconFile, { className: "shrink-0 text-foreground", "aria-hidden": true }),
5045
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "text-sm font-medium leading-none text-foreground", children: pageTitle })
5264
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react5.File, { size: 16, className: "shrink-0 text-foreground", "aria-hidden": true }),
5265
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "text-sm font-medium leading-none text-foreground!", children: pageTitle })
5046
5266
  ] }),
5047
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(IconArrowRight, { className: "shrink-0 text-muted-foreground", "aria-hidden": true }),
5267
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5268
+ import_lucide_react5.ArrowRight,
5269
+ {
5270
+ size: 16,
5271
+ className: "shrink-0 text-muted-foreground",
5272
+ "aria-hidden": true
5273
+ }
5274
+ ),
5048
5275
  /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [
5049
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(IconSection, { className: "shrink-0 text-foreground", "aria-hidden": true }),
5276
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5277
+ import_lucide_react5.GalleryVertical,
5278
+ {
5279
+ size: 16,
5280
+ className: "shrink-0 text-foreground",
5281
+ "aria-hidden": true
5282
+ }
5283
+ ),
5050
5284
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "truncate text-sm font-medium leading-none text-foreground", children: sectionLabel })
5051
5285
  ] })
5052
5286
  ] })
@@ -5054,8 +5288,13 @@ function DestinationBreadcrumb({ pageTitle, sectionLabel }) {
5054
5288
  }
5055
5289
 
5056
5290
  // src/ui/link-modal/SectionTreeItem.tsx
5291
+ var import_lucide_react6 = require("lucide-react");
5057
5292
  var import_jsx_runtime14 = require("react/jsx-runtime");
5058
- function SectionTreeItem({ section, onSelect, selected }) {
5293
+ function SectionTreeItem({
5294
+ section,
5295
+ onSelect,
5296
+ selected
5297
+ }) {
5059
5298
  const interactive = Boolean(onSelect);
5060
5299
  return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex h-9 w-full items-end pl-3", children: [
5061
5300
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
@@ -5083,30 +5322,28 @@ function SectionTreeItem({ section, onSelect, selected }) {
5083
5322
  interactive && selected && "border-primary"
5084
5323
  ),
5085
5324
  children: [
5086
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(IconSection, { className: "shrink-0 text-foreground", "aria-hidden": true }),
5325
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
5326
+ import_lucide_react6.GalleryVertical,
5327
+ {
5328
+ size: 16,
5329
+ className: "shrink-0 text-foreground",
5330
+ "aria-hidden": true
5331
+ }
5332
+ ),
5087
5333
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "truncate text-sm font-normal leading-5 text-foreground", children: section.label })
5088
5334
  ]
5089
5335
  }
5090
5336
  )
5091
5337
  ] });
5092
5338
  }
5093
- function SectionPickerList({ sections, onSelect }) {
5094
- if (sections.length === 0) {
5095
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { className: "text-sm text-muted-foreground", children: "No sections found on this page." });
5096
- }
5097
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex flex-col gap-1", children: [
5098
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { className: "text-sm text-muted-foreground", children: "Pick a section this link should scroll to." }),
5099
- sections.map((section) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(SectionTreeItem, { section, onSelect }, section.id))
5100
- ] });
5101
- }
5102
5339
 
5103
5340
  // src/ui/link-modal/UrlOrPageInput.tsx
5104
5341
  var import_react4 = require("react");
5105
5342
 
5106
5343
  // src/ui/input.tsx
5107
- var React7 = __toESM(require("react"), 1);
5344
+ var React8 = __toESM(require("react"), 1);
5108
5345
  var import_jsx_runtime15 = require("react/jsx-runtime");
5109
- var Input = React7.forwardRef(
5346
+ var Input = React8.forwardRef(
5110
5347
  ({ className, type, ...props }, ref) => {
5111
5348
  return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
5112
5349
  "input",
@@ -5140,8 +5377,11 @@ function Label({ className, ...props }) {
5140
5377
  }
5141
5378
 
5142
5379
  // src/ui/link-modal/UrlOrPageInput.tsx
5380
+ var import_lucide_react7 = require("lucide-react");
5143
5381
  var import_jsx_runtime17 = require("react/jsx-runtime");
5144
- function FieldChevron({ onClick }) {
5382
+ function FieldChevron({
5383
+ onClick
5384
+ }) {
5145
5385
  return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5146
5386
  "button",
5147
5387
  {
@@ -5150,7 +5390,7 @@ function FieldChevron({ onClick }) {
5150
5390
  onClick,
5151
5391
  "aria-label": "Open page list",
5152
5392
  tabIndex: -1,
5153
- children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(IconChevronDown, {})
5393
+ children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react7.ChevronDown, { size: 16 })
5154
5394
  }
5155
5395
  );
5156
5396
  }
@@ -5168,7 +5408,29 @@ function UrlOrPageInput({
5168
5408
  }) {
5169
5409
  const inputId = (0, import_react4.useId)();
5170
5410
  const inputRef = (0, import_react4.useRef)(null);
5411
+ const rootRef = (0, import_react4.useRef)(null);
5171
5412
  const [isFocused, setIsFocused] = (0, import_react4.useState)(false);
5413
+ (0, import_react4.useEffect)(() => {
5414
+ if (!dropdownOpen) return;
5415
+ const isOutside = (e) => {
5416
+ const root = rootRef.current;
5417
+ if (!root) return true;
5418
+ const path = typeof e.composedPath === "function" ? e.composedPath() : [];
5419
+ if (path.includes(root)) return false;
5420
+ const target = e.target;
5421
+ return !(target instanceof Node && root.contains(target));
5422
+ };
5423
+ const closeIfOutside = (e) => {
5424
+ if (!isOutside(e)) return;
5425
+ onDropdownOpenChange(false);
5426
+ };
5427
+ document.addEventListener("pointerdown", closeIfOutside, true);
5428
+ document.addEventListener("mousedown", closeIfOutside, true);
5429
+ return () => {
5430
+ document.removeEventListener("pointerdown", closeIfOutside, true);
5431
+ document.removeEventListener("mousedown", closeIfOutside, true);
5432
+ };
5433
+ }, [dropdownOpen, onDropdownOpenChange]);
5172
5434
  const openDropdown = () => {
5173
5435
  if (readOnly || filteredPages.length === 0) return;
5174
5436
  onDropdownOpenChange(true);
@@ -5191,9 +5453,16 @@ function UrlOrPageInput({
5191
5453
  );
5192
5454
  return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex w-full flex-col gap-2 p-0", children: [
5193
5455
  /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Label, { htmlFor: inputId, className: cn(urlError && "text-destructive"), children: "Destination" }),
5194
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "relative w-full", children: [
5456
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { ref: rootRef, className: "relative w-full", children: [
5195
5457
  /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { "data-ohw-link-field": true, className: fieldClassName, children: [
5196
- selectedPage ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "flex shrink-0 items-center pr-2", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(IconFile, { className: "text-foreground", "aria-hidden": true }) }) : null,
5458
+ selectedPage ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "flex shrink-0 items-center pr-2", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5459
+ import_lucide_react7.File,
5460
+ {
5461
+ size: 16,
5462
+ className: "shrink-0 text-foreground",
5463
+ "aria-hidden": true
5464
+ }
5465
+ ) }) : null,
5197
5466
  readOnly ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "min-w-0 flex-1 truncate text-sm leading-5 text-foreground", children: selectedPage?.title ?? value }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5198
5467
  Input,
5199
5468
  {
@@ -5205,7 +5474,14 @@ function UrlOrPageInput({
5205
5474
  setIsFocused(true);
5206
5475
  if (!selectedPage) openDropdown();
5207
5476
  },
5208
- onBlur: () => setIsFocused(false),
5477
+ onBlur: () => {
5478
+ setIsFocused(false);
5479
+ requestAnimationFrame(() => {
5480
+ if (!rootRef.current?.contains(document.activeElement)) {
5481
+ onDropdownOpenChange(false);
5482
+ }
5483
+ });
5484
+ },
5209
5485
  placeholder,
5210
5486
  className: cn(
5211
5487
  "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",
@@ -5221,7 +5497,7 @@ function UrlOrPageInput({
5221
5497
  onMouseDown: clearSelection,
5222
5498
  "aria-label": "Clear selected page",
5223
5499
  tabIndex: -1,
5224
- children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(IconX, { "aria-hidden": true })
5500
+ children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react7.X, { size: 16, "aria-hidden": true })
5225
5501
  }
5226
5502
  ) : null,
5227
5503
  !readOnly ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(FieldChevron, { onClick: toggleDropdown }) : null
@@ -5230,7 +5506,7 @@ function UrlOrPageInput({
5230
5506
  "div",
5231
5507
  {
5232
5508
  "data-ohw-link-page-dropdown": "",
5233
- 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",
5509
+ 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",
5234
5510
  onMouseDown: (e) => e.preventDefault(),
5235
5511
  children: filteredPages.map((page) => /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
5236
5512
  "button",
@@ -5239,7 +5515,7 @@ function UrlOrPageInput({
5239
5515
  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",
5240
5516
  onClick: () => onPageSelect(page),
5241
5517
  children: [
5242
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(IconFile, { className: "shrink-0", "aria-hidden": true }),
5518
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react7.File, { size: 16, className: "shrink-0", "aria-hidden": true }),
5243
5519
  /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "truncate", children: page.title })
5244
5520
  ]
5245
5521
  },
@@ -5252,38 +5528,375 @@ function UrlOrPageInput({
5252
5528
  ] });
5253
5529
  }
5254
5530
 
5255
- // src/ui/link-modal/useLinkModalState.ts
5256
- var import_react5 = require("react");
5257
- function useLinkModalState({
5258
- open,
5259
- mode,
5260
- pages,
5261
- sections: _sections,
5262
- sectionsByPath,
5263
- initialTarget,
5264
- existingTargets,
5265
- onClose,
5266
- onSubmit
5267
- }) {
5268
- const availablePages = (0, import_react5.useMemo)(
5269
- () => mode === "create" ? filterAvailablePages(pages, existingTargets) : pages,
5270
- [mode, pages, existingTargets]
5271
- );
5272
- const [searchValue, setSearchValue] = (0, import_react5.useState)("");
5273
- const [selectedPage, setSelectedPage] = (0, import_react5.useState)(null);
5274
- const [selectedSection, setSelectedSection] = (0, import_react5.useState)(null);
5275
- const [step, setStep] = (0, import_react5.useState)("input");
5276
- const [dropdownOpen, setDropdownOpen] = (0, import_react5.useState)(false);
5277
- const [urlError, setUrlError] = (0, import_react5.useState)("");
5278
- const reset = (0, import_react5.useCallback)(() => {
5279
- setSearchValue("");
5280
- setSelectedPage(null);
5531
+ // src/ui/link-modal/LinkEditorPanel.tsx
5532
+ var import_jsx_runtime18 = require("react/jsx-runtime");
5533
+ function LinkEditorPanel({ state, onClose }) {
5534
+ const isCancel = state.secondaryLabel === "Cancel" || state.secondaryLabel === "Back to sections";
5535
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [
5536
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(DialogClose, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5537
+ "button",
5538
+ {
5539
+ type: "button",
5540
+ className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50 h-7",
5541
+ "aria-label": "Close",
5542
+ onClick: onClose,
5543
+ children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_lucide_react8.X, { size: 16, "aria-hidden": true })
5544
+ }
5545
+ ) }),
5546
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(DialogHeader, { className: "w-full gap-1.5 p-6 pr-12", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(DialogTitle, { className: "m-0 w-full break-words", children: state.title }) }),
5547
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex w-full flex-col gap-3 px-6 pb-8 pt-1", children: [
5548
+ state.showBreadcrumb && state.selectedPage && state.selectedSection ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5549
+ DestinationBreadcrumb,
5550
+ {
5551
+ pageTitle: state.selectedPage.title,
5552
+ sectionLabel: state.selectedSection.label
5553
+ }
5554
+ ) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5555
+ UrlOrPageInput,
5556
+ {
5557
+ value: state.searchValue,
5558
+ selectedPage: state.selectedPage,
5559
+ dropdownOpen: state.dropdownOpen,
5560
+ onDropdownOpenChange: state.setDropdownOpen,
5561
+ filteredPages: state.filteredPages,
5562
+ onInputChange: state.handleInputChange,
5563
+ onPageSelect: state.handlePageSelect,
5564
+ urlError: state.urlError
5565
+ }
5566
+ ),
5567
+ state.showChooseSection ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex flex-col justify-center gap-2", children: [
5568
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5569
+ Button,
5570
+ {
5571
+ type: "button",
5572
+ variant: "outline",
5573
+ className: "w-fit cursor-pointer",
5574
+ size: "sm",
5575
+ onClick: state.handleChooseSection,
5576
+ children: "Choose a section"
5577
+ }
5578
+ ),
5579
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex items-center gap-1 text-sm text-muted-foreground", children: [
5580
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_lucide_react8.Info, { size: 16, className: "shrink-0", "aria-hidden": true }),
5581
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { children: "Pick a section this link should scroll to." })
5582
+ ] })
5583
+ ] }) : null,
5584
+ state.showSectionRow && state.selectedSection ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(SectionTreeItem, { section: state.selectedSection, selected: true }) : null
5585
+ ] }),
5586
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(DialogFooter, { className: "w-full px-6 pb-6", children: [
5587
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5588
+ Button,
5589
+ {
5590
+ type: "button",
5591
+ variant: isCancel ? "outline" : "ghost",
5592
+ className: "leading-6 shadow-none cursor-pointer",
5593
+ style: isCancel ? {
5594
+ backgroundColor: "var(--ohw-background)",
5595
+ borderColor: "var(--ohw-border)",
5596
+ color: "var(--ohw-foreground)"
5597
+ } : void 0,
5598
+ onClick: state.handleSecondary,
5599
+ children: state.secondaryLabel
5600
+ }
5601
+ ),
5602
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5603
+ Button,
5604
+ {
5605
+ type: "button",
5606
+ className: "border-0 leading-6 shadow-none hover:opacity-90 disabled:opacity-50 cursor-pointer",
5607
+ style: {
5608
+ backgroundColor: "var(--ohw-primary)",
5609
+ color: "var(--ohw-primary-foreground)"
5610
+ },
5611
+ disabled: !state.isValid,
5612
+ onClick: state.handleSubmit,
5613
+ children: state.submitLabel
5614
+ }
5615
+ )
5616
+ ] })
5617
+ ] });
5618
+ }
5619
+
5620
+ // src/ui/link-modal/SectionPickerOverlay.tsx
5621
+ var import_react5 = require("react");
5622
+ var import_react_dom = require("react-dom");
5623
+ var import_lucide_react9 = require("lucide-react");
5624
+ var import_navigation = require("next/navigation");
5625
+ var import_jsx_runtime19 = require("react/jsx-runtime");
5626
+ var DIM_OVERLAY = "rgba(0, 0, 0, 0.45)";
5627
+ function rectsEqual(a, b) {
5628
+ if (a.size !== b.size) return false;
5629
+ for (const [id, rect] of a) {
5630
+ const other = b.get(id);
5631
+ if (!other || rect.top !== other.top || rect.left !== other.left || rect.width !== other.width || rect.height !== other.height) {
5632
+ return false;
5633
+ }
5634
+ }
5635
+ return true;
5636
+ }
5637
+ function useSectionRects(sectionIdsKey, enabled) {
5638
+ const [rects, setRects] = (0, import_react5.useState)(/* @__PURE__ */ new Map());
5639
+ const sectionIds = (0, import_react5.useMemo)(
5640
+ () => sectionIdsKey ? sectionIdsKey.split("\0") : [],
5641
+ [sectionIdsKey]
5642
+ );
5643
+ (0, import_react5.useEffect)(() => {
5644
+ if (!enabled || sectionIds.length === 0) {
5645
+ setRects((prev) => prev.size === 0 ? prev : /* @__PURE__ */ new Map());
5646
+ return;
5647
+ }
5648
+ const update = () => {
5649
+ const next = /* @__PURE__ */ new Map();
5650
+ for (const id of sectionIds) {
5651
+ const el = document.querySelector(
5652
+ `[data-ohw-section="${CSS.escape(id)}"]`
5653
+ );
5654
+ if (el) next.set(id, el.getBoundingClientRect());
5655
+ }
5656
+ setRects((prev) => rectsEqual(prev, next) ? prev : next);
5657
+ };
5658
+ update();
5659
+ const opts = { capture: true, passive: true };
5660
+ window.addEventListener("scroll", update, opts);
5661
+ window.addEventListener("resize", update);
5662
+ const vv = window.visualViewport;
5663
+ vv?.addEventListener("resize", update);
5664
+ vv?.addEventListener("scroll", update);
5665
+ const ro = new ResizeObserver(update);
5666
+ for (const id of sectionIds) {
5667
+ const el = document.querySelector(
5668
+ `[data-ohw-section="${CSS.escape(id)}"]`
5669
+ );
5670
+ if (el) ro.observe(el);
5671
+ }
5672
+ return () => {
5673
+ window.removeEventListener("scroll", update, opts);
5674
+ window.removeEventListener("resize", update);
5675
+ vv?.removeEventListener("resize", update);
5676
+ vv?.removeEventListener("scroll", update);
5677
+ ro.disconnect();
5678
+ };
5679
+ }, [sectionIds, enabled]);
5680
+ return rects;
5681
+ }
5682
+ function SectionPickerOverlay({
5683
+ pagePath,
5684
+ sections,
5685
+ onBack,
5686
+ onSelect
5687
+ }) {
5688
+ const router = (0, import_navigation.useRouter)();
5689
+ const pathname = (0, import_navigation.usePathname)();
5690
+ const [selectedId, setSelectedId] = (0, import_react5.useState)(null);
5691
+ const [hoveredId, setHoveredId] = (0, import_react5.useState)(null);
5692
+ const [chromeClip, setChromeClip] = (0, import_react5.useState)(
5693
+ () => ({
5694
+ top: 0,
5695
+ bottom: typeof window !== "undefined" ? window.innerHeight : 0
5696
+ })
5697
+ );
5698
+ const normalizedTarget = normalizePath(pagePath);
5699
+ const isOnTargetPage = normalizePath(pathname) === normalizedTarget;
5700
+ (0, import_react5.useEffect)(() => {
5701
+ if (isOnTargetPage) return;
5702
+ const search = readPreservedSearch();
5703
+ router.push(`${normalizedTarget}${search}`);
5704
+ }, [isOnTargetPage, normalizedTarget, router]);
5705
+ (0, import_react5.useEffect)(() => {
5706
+ document.documentElement.setAttribute("data-ohw-section-picking", "");
5707
+ document.querySelectorAll("[data-ohw-hovered]").forEach((el) => {
5708
+ el.removeAttribute("data-ohw-hovered");
5709
+ });
5710
+ return () => {
5711
+ document.documentElement.removeAttribute("data-ohw-section-picking");
5712
+ };
5713
+ }, []);
5714
+ (0, import_react5.useEffect)(() => {
5715
+ const applyClip = (iframeOffsetTop, visibleCanvasTop, canvasH) => {
5716
+ const top = Math.max(0, visibleCanvasTop - iframeOffsetTop);
5717
+ const bottom = Math.min(
5718
+ window.innerHeight,
5719
+ visibleCanvasTop + canvasH - iframeOffsetTop
5720
+ );
5721
+ setChromeClip(
5722
+ (prev) => prev.top === top && prev.bottom === bottom ? prev : { top, bottom: Math.max(top, bottom) }
5723
+ );
5724
+ };
5725
+ const onParentScroll = (e) => {
5726
+ if (e.data?.type !== "ow:parent-scroll") return;
5727
+ const { iframeOffsetTop, headerH, canvasH } = e.data;
5728
+ applyClip(iframeOffsetTop, headerH, canvasH);
5729
+ };
5730
+ const onResize = () => {
5731
+ setChromeClip((prev) => {
5732
+ const bottom = Math.max(prev.top, window.innerHeight);
5733
+ return prev.bottom === bottom ? prev : { ...prev, bottom };
5734
+ });
5735
+ };
5736
+ window.addEventListener("message", onParentScroll);
5737
+ window.addEventListener("resize", onResize);
5738
+ return () => {
5739
+ window.removeEventListener("message", onParentScroll);
5740
+ window.removeEventListener("resize", onResize);
5741
+ };
5742
+ }, []);
5743
+ const liveSections = (0, import_react5.useMemo)(() => {
5744
+ if (!isOnTargetPage) return sections;
5745
+ const live = collectSectionsFromDom();
5746
+ if (live.length === 0) return sections;
5747
+ const labels = new Map(sections.map((s) => [s.id, s.label]));
5748
+ return live.map((s) => ({ id: s.id, label: labels.get(s.id) ?? s.label }));
5749
+ }, [isOnTargetPage, sections, pathname]);
5750
+ const sectionIdsKey = (0, import_react5.useMemo)(
5751
+ () => liveSections.map((s) => s.id).join("\0"),
5752
+ [liveSections]
5753
+ );
5754
+ const rects = useSectionRects(sectionIdsKey, isOnTargetPage);
5755
+ const handleSelect = (0, import_react5.useCallback)(
5756
+ (section) => {
5757
+ if (selectedId) return;
5758
+ setSelectedId(section.id);
5759
+ onSelect(section);
5760
+ },
5761
+ [selectedId, onSelect]
5762
+ );
5763
+ (0, import_react5.useEffect)(() => {
5764
+ const onKeyDown = (e) => {
5765
+ if (e.key === "Escape") {
5766
+ e.preventDefault();
5767
+ e.stopPropagation();
5768
+ onBack();
5769
+ }
5770
+ };
5771
+ window.addEventListener("keydown", onKeyDown, true);
5772
+ return () => window.removeEventListener("keydown", onKeyDown, true);
5773
+ }, [onBack]);
5774
+ const portalRoot = typeof document !== "undefined" ? document.querySelector("[data-ohw-bridge-root]") ?? document.body : null;
5775
+ if (!portalRoot) return null;
5776
+ return (0, import_react_dom.createPortal)(
5777
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
5778
+ "div",
5779
+ {
5780
+ "data-ohw-section-picker": "",
5781
+ "data-ohw-bridge": "",
5782
+ className: `pointer-events-none fixed inset-0 z-[2147483647] transition-opacity duration-200 ${"opacity-100"}`,
5783
+ "aria-modal": true,
5784
+ role: "dialog",
5785
+ "aria-label": "Choose a section",
5786
+ children: [
5787
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5788
+ "div",
5789
+ {
5790
+ className: "pointer-events-auto fixed left-5 z-[2]",
5791
+ style: { top: chromeClip.top + 20 },
5792
+ children: /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
5793
+ Button,
5794
+ {
5795
+ type: "button",
5796
+ variant: "outline",
5797
+ size: "sm",
5798
+ className: "h-8 min-w-0 gap-1 border-border bg-background px-2 py-1.5 shadow-sm hover:bg-muted cursor-pointer",
5799
+ onClick: onBack,
5800
+ children: [
5801
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react9.ArrowLeft, { className: "size-4 shrink-0", "aria-hidden": true }),
5802
+ "Back"
5803
+ ]
5804
+ }
5805
+ )
5806
+ }
5807
+ ),
5808
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5809
+ "div",
5810
+ {
5811
+ 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",
5812
+ style: {
5813
+ top: chromeClip.bottom - 20,
5814
+ transform: "translate(-50%, -100%)",
5815
+ backgroundColor: "var(--ohw-popover-foreground, #0c0a09)"
5816
+ },
5817
+ "data-ohw-section-picker-hint": "",
5818
+ children: "Click on section to select"
5819
+ }
5820
+ ),
5821
+ !isOnTargetPage ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5822
+ "div",
5823
+ {
5824
+ 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",
5825
+ style: { top: chromeClip.top + 64 },
5826
+ children: "Loading page preview\u2026"
5827
+ }
5828
+ ) : null,
5829
+ isOnTargetPage && liveSections.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "pointer-events-auto fixed inset-0 z-[1] flex items-center justify-center bg-muted/40", children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("p", { className: "text-sm text-muted-foreground", children: "No sections found on this page." }) }) : null,
5830
+ isOnTargetPage ? liveSections.map((section) => {
5831
+ const rect = rects.get(section.id);
5832
+ if (!rect || rect.width <= 0 || rect.height <= 0) return null;
5833
+ const isSelected = selectedId === section.id;
5834
+ const isLit = isSelected || hoveredId === section.id;
5835
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5836
+ "button",
5837
+ {
5838
+ type: "button",
5839
+ className: "pointer-events-auto fixed z-[1] cursor-pointer border-0 bg-transparent p-0 transition-[background-color] duration-150",
5840
+ style: {
5841
+ top: rect.top,
5842
+ left: rect.left,
5843
+ width: rect.width,
5844
+ height: rect.height,
5845
+ backgroundColor: isLit ? "transparent" : DIM_OVERLAY
5846
+ },
5847
+ "aria-label": `Select section ${section.label}`,
5848
+ onMouseEnter: () => setHoveredId(section.id),
5849
+ onMouseLeave: () => setHoveredId((prev) => prev === section.id ? null : prev),
5850
+ onClick: () => handleSelect(section),
5851
+ children: isSelected ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5852
+ "span",
5853
+ {
5854
+ className: "absolute right-3 top-3 flex size-8 items-center justify-center rounded-full text-white",
5855
+ style: { backgroundColor: "var(--ohw-primary, #0885fe)" },
5856
+ "aria-hidden": true,
5857
+ children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react9.Check, { className: "size-5" })
5858
+ }
5859
+ ) : null
5860
+ },
5861
+ section.id
5862
+ );
5863
+ }) : null
5864
+ ]
5865
+ }
5866
+ ),
5867
+ portalRoot
5868
+ );
5869
+ }
5870
+
5871
+ // src/ui/link-modal/useLinkModalState.ts
5872
+ var import_react6 = require("react");
5873
+ function useLinkModalState({
5874
+ open,
5875
+ mode,
5876
+ pages,
5877
+ sections: _sections,
5878
+ sectionsByPath,
5879
+ initialTarget,
5880
+ existingTargets: _existingTargets,
5881
+ onClose,
5882
+ onSubmit
5883
+ }) {
5884
+ const availablePages = (0, import_react6.useMemo)(() => pages, [pages]);
5885
+ const [searchValue, setSearchValue] = (0, import_react6.useState)("");
5886
+ const [selectedPage, setSelectedPage] = (0, import_react6.useState)(null);
5887
+ const [selectedSection, setSelectedSection] = (0, import_react6.useState)(null);
5888
+ const [step, setStep] = (0, import_react6.useState)("input");
5889
+ const [dropdownOpen, setDropdownOpen] = (0, import_react6.useState)(false);
5890
+ const [urlError, setUrlError] = (0, import_react6.useState)("");
5891
+ const reset = (0, import_react6.useCallback)(() => {
5892
+ setSearchValue("");
5893
+ setSelectedPage(null);
5281
5894
  setSelectedSection(null);
5282
5895
  setStep("input");
5283
5896
  setDropdownOpen(false);
5284
5897
  setUrlError("");
5285
5898
  }, []);
5286
- (0, import_react5.useEffect)(() => {
5899
+ (0, import_react6.useEffect)(() => {
5287
5900
  if (!open) return;
5288
5901
  if (mode === "edit" && initialTarget) {
5289
5902
  const { pageRoute } = parseTarget(initialTarget);
@@ -5298,12 +5911,12 @@ function useLinkModalState({
5298
5911
  }
5299
5912
  setDropdownOpen(false);
5300
5913
  setUrlError("");
5301
- }, [open, mode, initialTarget, pages, sectionsByPath, reset]);
5302
- const filteredPages = (0, import_react5.useMemo)(() => {
5914
+ }, [open, mode, initialTarget, reset]);
5915
+ const filteredPages = (0, import_react6.useMemo)(() => {
5303
5916
  if (!searchValue.trim()) return availablePages;
5304
5917
  return availablePages.filter((p) => p.title.toLowerCase().startsWith(searchValue.toLowerCase()));
5305
5918
  }, [availablePages, searchValue]);
5306
- const activeSections = (0, import_react5.useMemo)(() => {
5919
+ const activeSections = (0, import_react6.useMemo)(() => {
5307
5920
  if (!selectedPage) return [];
5308
5921
  return getSectionsForPath(sectionsByPath, selectedPage.path);
5309
5922
  }, [selectedPage, sectionsByPath]);
@@ -5345,11 +5958,14 @@ function useLinkModalState({
5345
5958
  const handleBackToSections = () => {
5346
5959
  setStep("sectionPicker");
5347
5960
  };
5961
+ const handleSectionPickerBack = () => {
5962
+ setStep("input");
5963
+ };
5348
5964
  const handleClose = () => {
5349
5965
  reset();
5350
5966
  onClose();
5351
5967
  };
5352
- const isValid = (0, import_react5.useMemo)(() => {
5968
+ const isValid = (0, import_react6.useMemo)(() => {
5353
5969
  if (urlError) return false;
5354
5970
  if (showBreadcrumb) return true;
5355
5971
  if (selectedPage) return true;
@@ -5382,7 +5998,7 @@ function useLinkModalState({
5382
5998
  onSubmit(normalizeUrl(searchValue));
5383
5999
  handleClose();
5384
6000
  };
5385
- const submitLabel = mode === "edit" ? "Save" : "Create";
6001
+ const submitLabel = showBreadcrumb ? "Save" : mode === "edit" ? "Save" : "Create";
5386
6002
  const title = mode === "edit" ? "Edit navigation item" : "Create navigation item";
5387
6003
  const secondaryLabel = showBreadcrumb ? "Back to sections" : "Cancel";
5388
6004
  const handleSecondary = showBreadcrumb ? handleBackToSections : handleClose;
@@ -5409,23 +6025,29 @@ function useLinkModalState({
5409
6025
  handleChooseSection,
5410
6026
  handleSectionSelect,
5411
6027
  handleBackToSections,
6028
+ handleSectionPickerBack,
5412
6029
  handleClose,
5413
6030
  handleSecondary,
5414
6031
  handleSubmit
5415
6032
  };
5416
6033
  }
5417
6034
 
5418
- // src/ui/link-modal/LinkEditorPanel.tsx
5419
- var import_jsx_runtime18 = require("react/jsx-runtime");
5420
- function LinkEditorPanel({
6035
+ // src/ui/link-modal/LinkPopover.tsx
6036
+ var import_jsx_runtime20 = require("react/jsx-runtime");
6037
+ function postToParent(data) {
6038
+ window.parent?.postMessage(data, "*");
6039
+ }
6040
+ function LinkPopover({
5421
6041
  open = true,
6042
+ panelRef,
6043
+ portalContainer,
6044
+ onClose,
5422
6045
  mode = "create",
5423
6046
  pages,
5424
6047
  sections = [],
5425
6048
  sectionsByPath,
5426
6049
  initialTarget,
5427
6050
  existingTargets = [],
5428
- onClose,
5429
6051
  onSubmit
5430
6052
  }) {
5431
6053
  const state = useLinkModalState({
@@ -5439,115 +6061,100 @@ function LinkEditorPanel({
5439
6061
  onClose,
5440
6062
  onSubmit
5441
6063
  });
5442
- const isCancel = state.secondaryLabel === "Cancel";
5443
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [
5444
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(DialogClose, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5445
- "button",
6064
+ const sectionPickerActive = state.showSectionPicker && Boolean(state.selectedPage);
6065
+ (0, import_react7.useEffect)(() => {
6066
+ if (!open) return;
6067
+ if (sectionPickerActive) {
6068
+ postToParent({ type: "ow:link-modal-lock", locked: false });
6069
+ postToParent({ type: "ow:section-picker", active: true });
6070
+ document.documentElement.style.overflow = "";
6071
+ document.body.style.overflow = "";
6072
+ return () => {
6073
+ postToParent({ type: "ow:section-picker", active: false });
6074
+ };
6075
+ }
6076
+ postToParent({ type: "ow:section-picker", active: false });
6077
+ postToParent({ type: "ow:link-modal-lock", locked: true });
6078
+ const html = document.documentElement;
6079
+ const body = document.body;
6080
+ const prevHtmlOverflow = html.style.overflow;
6081
+ const prevBodyOverflow = body.style.overflow;
6082
+ const prevHtmlOverscroll = html.style.overscrollBehavior;
6083
+ const prevBodyOverscroll = body.style.overscrollBehavior;
6084
+ html.style.overflow = "hidden";
6085
+ body.style.overflow = "hidden";
6086
+ html.style.overscrollBehavior = "none";
6087
+ body.style.overscrollBehavior = "none";
6088
+ const canScrollElement = (el, deltaY) => {
6089
+ const { overflowY } = getComputedStyle(el);
6090
+ if (overflowY !== "auto" && overflowY !== "scroll") return false;
6091
+ if (el.scrollHeight <= el.clientHeight + 1) return false;
6092
+ if (deltaY < 0) return el.scrollTop > 0;
6093
+ if (deltaY > 0) return el.scrollTop + el.clientHeight < el.scrollHeight - 1;
6094
+ return true;
6095
+ };
6096
+ const allowsInternalScroll = (e) => {
6097
+ const target = e.target;
6098
+ if (!(target instanceof Element)) return false;
6099
+ const scrollRoot = target.closest(
6100
+ "[data-ohw-link-page-dropdown], [data-ohw-allow-scroll]"
6101
+ );
6102
+ if (!scrollRoot) return false;
6103
+ const deltaY = e instanceof WheelEvent ? e.deltaY : 0;
6104
+ return canScrollElement(scrollRoot, deltaY);
6105
+ };
6106
+ const preventBackgroundScroll = (e) => {
6107
+ if (allowsInternalScroll(e)) return;
6108
+ e.preventDefault();
6109
+ };
6110
+ const scrollOpts = { passive: false, capture: true };
6111
+ document.addEventListener("wheel", preventBackgroundScroll, scrollOpts);
6112
+ document.addEventListener("touchmove", preventBackgroundScroll, scrollOpts);
6113
+ return () => {
6114
+ postToParent({ type: "ow:link-modal-lock", locked: false });
6115
+ html.style.overflow = prevHtmlOverflow;
6116
+ body.style.overflow = prevBodyOverflow;
6117
+ html.style.overscrollBehavior = prevHtmlOverscroll;
6118
+ body.style.overscrollBehavior = prevBodyOverscroll;
6119
+ document.removeEventListener("wheel", preventBackgroundScroll, scrollOpts);
6120
+ document.removeEventListener("touchmove", preventBackgroundScroll, scrollOpts);
6121
+ };
6122
+ }, [open, sectionPickerActive]);
6123
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_jsx_runtime20.Fragment, { children: [
6124
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
6125
+ Dialog2,
5446
6126
  {
5447
- type: "button",
5448
- className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
5449
- "aria-label": "Close",
5450
- children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(IconX, { "aria-hidden": true })
6127
+ open: open && !sectionPickerActive,
6128
+ onOpenChange: (next) => {
6129
+ if (!next) onClose?.();
6130
+ },
6131
+ children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
6132
+ DialogContent,
6133
+ {
6134
+ ref: panelRef,
6135
+ container: portalContainer,
6136
+ "data-ohw-link-popover-root": "",
6137
+ "data-ohw-link-modal-root": "",
6138
+ "data-ohw-bridge": "",
6139
+ showCloseButton: false,
6140
+ className: "gap-0 p-0 w-full md:w-md pointer-events-auto z-[2147483646] overflow-visible",
6141
+ children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(LinkEditorPanel, { state, onClose })
6142
+ }
6143
+ )
5451
6144
  }
5452
- ) }),
5453
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(DialogHeader, { className: "w-full gap-1.5 p-6 pr-12", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(DialogTitle, { className: "m-0 w-full break-words", children: state.title }) }),
5454
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex w-full flex-col gap-3 px-6 pb-8 pt-1", children: [
5455
- state.showBreadcrumb && state.selectedPage && state.selectedSection ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5456
- DestinationBreadcrumb,
5457
- {
5458
- pageTitle: state.selectedPage.title,
5459
- sectionLabel: state.selectedSection.label
5460
- }
5461
- ) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5462
- UrlOrPageInput,
5463
- {
5464
- value: state.searchValue,
5465
- selectedPage: state.selectedPage,
5466
- dropdownOpen: state.dropdownOpen,
5467
- onDropdownOpenChange: state.setDropdownOpen,
5468
- filteredPages: state.filteredPages,
5469
- onInputChange: state.handleInputChange,
5470
- onPageSelect: state.handlePageSelect,
5471
- urlError: state.urlError
5472
- }
5473
- ),
5474
- state.showChooseSection ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex flex-col justify-center gap-2", children: [
5475
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Button, { type: "button", variant: "outline", className: "w-fit cursor-pointer", size: "sm", onClick: state.handleChooseSection, children: "Choose a section" }),
5476
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex items-center gap-1 text-sm text-muted-foreground", children: [
5477
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(IconInfo, { className: "shrink-0", "aria-hidden": true }),
5478
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { children: "Pick a section this link should scroll to." })
5479
- ] })
5480
- ] }) : null,
5481
- state.showSectionPicker ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(SectionPickerList, { sections: state.activeSections, onSelect: state.handleSectionSelect }) : null,
5482
- state.showSectionRow && state.selectedSection ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(SectionTreeItem, { section: state.selectedSection, selected: true }) : null
5483
- ] }),
5484
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(DialogFooter, { className: "w-full px-6 pb-6", children: [
5485
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5486
- Button,
5487
- {
5488
- type: "button",
5489
- variant: isCancel ? "outline" : "ghost",
5490
- className: "leading-6 shadow-none cursor-pointer",
5491
- style: isCancel ? {
5492
- backgroundColor: "var(--ohw-background)",
5493
- borderColor: "var(--ohw-border)",
5494
- color: "var(--ohw-foreground)"
5495
- } : void 0,
5496
- onClick: state.handleSecondary,
5497
- children: state.secondaryLabel
5498
- }
5499
- ),
5500
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5501
- Button,
5502
- {
5503
- type: "button",
5504
- className: "border-0 leading-6 shadow-none hover:opacity-90 disabled:opacity-50 cursor-pointer",
5505
- style: {
5506
- backgroundColor: "var(--ohw-primary)",
5507
- color: "var(--ohw-primary-foreground)"
5508
- },
5509
- disabled: !state.isValid,
5510
- onClick: state.handleSubmit,
5511
- children: state.submitLabel
5512
- }
5513
- )
5514
- ] })
6145
+ ),
6146
+ sectionPickerActive && state.selectedPage ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
6147
+ SectionPickerOverlay,
6148
+ {
6149
+ pagePath: state.selectedPage.path,
6150
+ sections: state.activeSections,
6151
+ onBack: state.handleSectionPickerBack,
6152
+ onSelect: state.handleSectionSelect
6153
+ }
6154
+ ) : null
5515
6155
  ] });
5516
6156
  }
5517
6157
 
5518
- // src/ui/link-modal/LinkPopover.tsx
5519
- var import_jsx_runtime19 = require("react/jsx-runtime");
5520
- function LinkPopover({
5521
- open = true,
5522
- panelRef,
5523
- portalContainer,
5524
- onClose,
5525
- ...editorProps
5526
- }) {
5527
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5528
- Dialog2,
5529
- {
5530
- open,
5531
- onOpenChange: (next) => {
5532
- if (!next) onClose?.();
5533
- },
5534
- children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5535
- DialogContent,
5536
- {
5537
- ref: panelRef,
5538
- container: portalContainer,
5539
- "data-ohw-link-popover-root": "",
5540
- "data-ohw-link-modal-root": "",
5541
- "data-ohw-bridge": "",
5542
- showCloseButton: false,
5543
- className: "gap-0 p-0 w-full md:w-md pointer-events-auto",
5544
- children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(LinkEditorPanel, { ...editorProps, open, onClose })
5545
- }
5546
- )
5547
- }
5548
- );
5549
- }
5550
-
5551
6158
  // src/ui/link-modal/devFixtures.ts
5552
6159
  var DEV_SITE_PAGES = [
5553
6160
  { path: "/", title: "Home" },
@@ -5567,16 +6174,21 @@ var DEV_SECTIONS_BY_PATH = {
5567
6174
  { id: "founder-teaser", label: "Founder" },
5568
6175
  { id: "plan-form", label: "Plan Form" },
5569
6176
  { id: "testimonials", label: "Testimonials" },
5570
- { id: "wordmark", label: "Wordmark" }
6177
+ { id: "wordmark", label: "Wordmark" },
6178
+ { id: "footer", label: "Footer" }
5571
6179
  ],
5572
6180
  "/about": [
5573
6181
  { id: "manifesto", label: "Manifesto" },
5574
6182
  { id: "story-letter", label: "Our Story" },
5575
6183
  { id: "lagree-explainer", label: "Lagree Explainer" },
5576
6184
  { id: "waitlist-cta", label: "Waitlist" },
5577
- { id: "personal-training", label: "Personal training" }
6185
+ { id: "personal-training", label: "Personal training" },
6186
+ { id: "footer", label: "Footer" }
6187
+ ],
6188
+ "/classes": [
6189
+ { id: "class-library", label: "Class Library" },
6190
+ { id: "footer", label: "Footer" }
5578
6191
  ],
5579
- "/classes": [{ id: "class-library", label: "Class Library" }],
5580
6192
  "/pricing": [
5581
6193
  { id: "page-header", label: "Page Header" },
5582
6194
  { id: "free-class-cta", label: "Free Class" },
@@ -5584,19 +6196,25 @@ var DEV_SECTIONS_BY_PATH = {
5584
6196
  { id: "monthly-memberships", label: "Memberships" },
5585
6197
  { id: "specialty-programs", label: "Specialty Programs" },
5586
6198
  { id: "package-matcher", label: "Packages" },
5587
- { id: "wordmark", label: "Wordmark" }
6199
+ { id: "wordmark", label: "Wordmark" },
6200
+ { id: "footer", label: "Footer" }
5588
6201
  ],
5589
6202
  "/studio": [
5590
6203
  { id: "studio-intro", label: "Studio Intro" },
5591
6204
  { id: "studio-features", label: "Studio Features" },
5592
6205
  { id: "studio-gallery", label: "Studio Gallery" },
5593
- { id: "studio-visit", label: "Visit Studio" }
6206
+ { id: "studio-visit", label: "Visit Studio" },
6207
+ { id: "footer", label: "Footer" }
6208
+ ],
6209
+ "/instructors": [
6210
+ { id: "instructor-grid", label: "Instructors" },
6211
+ { id: "footer", label: "Footer" }
5594
6212
  ],
5595
- "/instructors": [{ id: "instructor-grid", label: "Instructors" }],
5596
6213
  "/contact": [
5597
6214
  { id: "hello-hero", label: "Hello" },
5598
6215
  { id: "contact-form", label: "Contact Form" },
5599
- { id: "faq", label: "FAQ" }
6216
+ { id: "faq", label: "FAQ" },
6217
+ { id: "footer", label: "Footer" }
5600
6218
  ]
5601
6219
  };
5602
6220
  function shouldUseDevFixtures() {
@@ -5606,8 +6224,277 @@ function shouldUseDevFixtures() {
5606
6224
  return new URLSearchParams(q).get("ohw-fixtures") === "1";
5607
6225
  }
5608
6226
 
6227
+ // src/lib/nav-items.ts
6228
+ var NAV_COUNT_KEY = "__ohw_nav_count";
6229
+ var NAV_ORDER_KEY = "__ohw_nav_order";
6230
+ function getLinkHref(el) {
6231
+ const key = el.getAttribute("data-ohw-href-key");
6232
+ if (key) {
6233
+ const stored = getStoredLinkHref(key);
6234
+ if (stored !== void 0) return stored;
6235
+ }
6236
+ return el.getAttribute("href") ?? "";
6237
+ }
6238
+ function isNavbarButton(el) {
6239
+ return el.hasAttribute("data-ohw-role") && el.getAttribute("data-ohw-role") === "navbar-button";
6240
+ }
6241
+ function isNavbarLinkItem(el) {
6242
+ if (!el.matches("[data-ohw-href-key]")) return false;
6243
+ if (isNavbarButton(el)) return false;
6244
+ if (!el.querySelector('[data-ohw-editable="text"]')) return false;
6245
+ return Boolean(el.closest("nav, [data-ohw-nav-container], [data-ohw-nav-drawer]"));
6246
+ }
6247
+ function getNavbarDesktopContainer() {
6248
+ return document.querySelector("[data-ohw-nav-container]");
6249
+ }
6250
+ function getNavbarDrawerContainer() {
6251
+ return document.querySelector("[data-ohw-nav-drawer]");
6252
+ }
6253
+ function listNavbarItems() {
6254
+ const desktop = getNavbarDesktopContainer();
6255
+ if (desktop) {
6256
+ return Array.from(desktop.querySelectorAll("[data-ohw-href-key]")).filter(isNavbarLinkItem);
6257
+ }
6258
+ const drawer = getNavbarDrawerContainer();
6259
+ if (drawer) {
6260
+ return Array.from(drawer.querySelectorAll("[data-ohw-href-key]")).filter(isNavbarLinkItem);
6261
+ }
6262
+ return Array.from(document.querySelectorAll("nav [data-ohw-href-key]")).filter(isNavbarLinkItem);
6263
+ }
6264
+ function parseNavIndexFromKey(key) {
6265
+ if (!key) return null;
6266
+ const match = key.match(/^nav-(\d+)-href$/);
6267
+ if (!match) return null;
6268
+ return parseInt(match[1], 10);
6269
+ }
6270
+ function getNavbarIndicesInDom() {
6271
+ const indices = /* @__PURE__ */ new Set();
6272
+ for (const item of listNavbarItems()) {
6273
+ const idx = parseNavIndexFromKey(item.getAttribute("data-ohw-href-key"));
6274
+ if (idx !== null) indices.add(idx);
6275
+ }
6276
+ return [...indices].sort((a, b) => a - b);
6277
+ }
6278
+ function getNextNavbarIndex() {
6279
+ const indices = getNavbarIndicesInDom();
6280
+ if (indices.length === 0) return 0;
6281
+ return Math.max(...indices) + 1;
6282
+ }
6283
+ function getNavbarExistingTargets() {
6284
+ return listNavbarItems().map((el) => getLinkHref(el)).filter(Boolean);
6285
+ }
6286
+ function getNavOrderFromDom() {
6287
+ return listNavbarItems().map((el) => el.getAttribute("data-ohw-href-key")).filter((key) => Boolean(key));
6288
+ }
6289
+ function parseNavOrder(content) {
6290
+ const raw = content[NAV_ORDER_KEY];
6291
+ if (!raw) return null;
6292
+ try {
6293
+ const parsed = JSON.parse(raw);
6294
+ if (!Array.isArray(parsed)) return null;
6295
+ return parsed.filter((key) => typeof key === "string" && key.length > 0);
6296
+ } catch {
6297
+ return null;
6298
+ }
6299
+ }
6300
+ function findCounterpartByHrefKey(hrefKey, root) {
6301
+ return root.querySelector(`[data-ohw-href-key="${CSS.escape(hrefKey)}"]`);
6302
+ }
6303
+ function cloneNavLinkShell(template) {
6304
+ const anchor = document.createElement("a");
6305
+ if (template) {
6306
+ anchor.style.cssText = template.style.cssText;
6307
+ if (template.className) anchor.className = template.className;
6308
+ }
6309
+ anchor.style.cursor = "pointer";
6310
+ anchor.style.textDecoration = "none";
6311
+ return anchor;
6312
+ }
6313
+ function buildNavLink(index, href, label, template) {
6314
+ const anchor = cloneNavLinkShell(template);
6315
+ anchor.href = href;
6316
+ anchor.setAttribute("data-ohw-href-key", `nav-${index}-href`);
6317
+ const span = document.createElement("span");
6318
+ span.setAttribute("data-ohw-editable", "text");
6319
+ span.setAttribute("data-ohw-key", `nav-${index}-label`);
6320
+ span.textContent = label;
6321
+ anchor.appendChild(span);
6322
+ return anchor;
6323
+ }
6324
+ function insertNavLinkInContainer(container, anchor, afterHrefKey) {
6325
+ if (!afterHrefKey) {
6326
+ container.appendChild(anchor);
6327
+ return;
6328
+ }
6329
+ const afterEl = findCounterpartByHrefKey(afterHrefKey, container);
6330
+ if (afterEl?.parentElement === container) {
6331
+ afterEl.insertAdjacentElement("afterend", anchor);
6332
+ return;
6333
+ }
6334
+ container.appendChild(anchor);
6335
+ }
6336
+ function insertNavbarItemDom(index, href, label, afterAnchor) {
6337
+ const afterHrefKey = afterAnchor?.getAttribute("data-ohw-href-key") ?? null;
6338
+ const desktopItems = getNavbarDesktopContainer()?.querySelectorAll("[data-ohw-href-key]") ?? [];
6339
+ const drawerItems = getNavbarDrawerContainer()?.querySelectorAll("[data-ohw-href-key]") ?? [];
6340
+ const desktopTemplate = Array.from(desktopItems).find(isNavbarLinkItem) ?? null;
6341
+ const drawerTemplate = Array.from(drawerItems).find(isNavbarLinkItem) ?? null;
6342
+ let primary = null;
6343
+ const desktopContainer = getNavbarDesktopContainer();
6344
+ if (desktopContainer) {
6345
+ const desktopAnchor = buildNavLink(index, href, label, desktopTemplate);
6346
+ insertNavLinkInContainer(desktopContainer, desktopAnchor, afterHrefKey);
6347
+ primary = desktopAnchor;
6348
+ }
6349
+ const drawerContainer = getNavbarDrawerContainer();
6350
+ if (drawerContainer) {
6351
+ const drawerAnchor = buildNavLink(index, href, label, drawerTemplate);
6352
+ insertNavLinkInContainer(drawerContainer, drawerAnchor, afterHrefKey);
6353
+ if (!primary) primary = drawerAnchor;
6354
+ }
6355
+ if (!primary) {
6356
+ const fallback = buildNavLink(index, href, label, listNavbarItems()[0] ?? null);
6357
+ const nav = document.querySelector("nav");
6358
+ nav?.appendChild(fallback);
6359
+ primary = fallback;
6360
+ }
6361
+ return primary;
6362
+ }
6363
+ function applyNavOrderToContainer(container, order) {
6364
+ const seen = /* @__PURE__ */ new Set();
6365
+ for (const hrefKey of order) {
6366
+ if (seen.has(hrefKey)) continue;
6367
+ seen.add(hrefKey);
6368
+ const el = findCounterpartByHrefKey(hrefKey, container);
6369
+ if (el) container.appendChild(el);
6370
+ }
6371
+ for (const el of container.querySelectorAll("[data-ohw-href-key]")) {
6372
+ if (!isNavbarLinkItem(el)) continue;
6373
+ const key = el.getAttribute("data-ohw-href-key");
6374
+ if (!key || seen.has(key)) continue;
6375
+ container.appendChild(el);
6376
+ }
6377
+ }
6378
+ function applyNavOrder(order) {
6379
+ const desktop = getNavbarDesktopContainer();
6380
+ if (desktop) applyNavOrderToContainer(desktop, order);
6381
+ const drawer = getNavbarDrawerContainer();
6382
+ if (drawer) applyNavOrderToContainer(drawer, order);
6383
+ }
6384
+ function collectNavbarIndicesFromContent(content) {
6385
+ const indices = /* @__PURE__ */ new Set();
6386
+ for (const key of Object.keys(content)) {
6387
+ const idx = parseNavIndexFromKey(key);
6388
+ if (idx !== null) indices.add(idx);
6389
+ }
6390
+ const countRaw = content[NAV_COUNT_KEY];
6391
+ if (countRaw) {
6392
+ const count = parseInt(countRaw, 10);
6393
+ if (Number.isFinite(count) && count > 0) {
6394
+ for (let i = 0; i < count; i++) indices.add(i);
6395
+ }
6396
+ }
6397
+ return [...indices].sort((a, b) => a - b);
6398
+ }
6399
+ function navbarIndexExistsInDom(index) {
6400
+ return document.querySelector(`[data-ohw-href-key="nav-${index}-href"]`) !== null;
6401
+ }
6402
+ function reconcileNavbarItemsFromContent(content) {
6403
+ const indices = collectNavbarIndicesFromContent(content);
6404
+ for (const index of indices) {
6405
+ if (navbarIndexExistsInDom(index)) continue;
6406
+ const href = content[`nav-${index}-href`];
6407
+ const label = content[`nav-${index}-label`];
6408
+ if (!href && !label) continue;
6409
+ insertNavbarItemDom(index, href ?? "/", label ?? "Untitled", null);
6410
+ }
6411
+ const order = parseNavOrder(content);
6412
+ if (order?.length) applyNavOrder(order);
6413
+ }
6414
+ function buildNavOrderAfterInsert(afterAnchor, newHrefKey) {
6415
+ const order = getNavOrderFromDom();
6416
+ if (!afterAnchor) {
6417
+ if (!order.includes(newHrefKey)) order.push(newHrefKey);
6418
+ return order;
6419
+ }
6420
+ const afterKey = afterAnchor.getAttribute("data-ohw-href-key");
6421
+ if (!afterKey) {
6422
+ if (!order.includes(newHrefKey)) order.push(newHrefKey);
6423
+ return order;
6424
+ }
6425
+ const withoutNew = order.filter((key) => key !== newHrefKey);
6426
+ const pos = withoutNew.indexOf(afterKey);
6427
+ if (pos >= 0) {
6428
+ withoutNew.splice(pos + 1, 0, newHrefKey);
6429
+ return withoutNew;
6430
+ }
6431
+ withoutNew.push(newHrefKey);
6432
+ return withoutNew;
6433
+ }
6434
+ function insertNavbarItem(href, label, afterAnchor = null) {
6435
+ const index = getNextNavbarIndex();
6436
+ const anchor = insertNavbarItemDom(index, href, label, afterAnchor);
6437
+ if (!anchor) throw new Error("Failed to insert navbar item");
6438
+ const hrefKey = `nav-${index}-href`;
6439
+ const order = buildNavOrderAfterInsert(afterAnchor, hrefKey);
6440
+ applyNavOrder(order);
6441
+ return {
6442
+ anchor,
6443
+ index,
6444
+ hrefKey,
6445
+ labelKey: `nav-${index}-label`,
6446
+ href,
6447
+ label,
6448
+ order
6449
+ };
6450
+ }
6451
+
6452
+ // src/ui/navbar-container-chrome.tsx
6453
+ var import_lucide_react10 = require("lucide-react");
6454
+ var import_jsx_runtime21 = require("react/jsx-runtime");
6455
+ function NavbarContainerChrome({
6456
+ rect,
6457
+ onAdd
6458
+ }) {
6459
+ const chromeGap = 6;
6460
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
6461
+ "div",
6462
+ {
6463
+ "data-ohw-navbar-container-chrome": "",
6464
+ "data-ohw-bridge": "",
6465
+ className: "pointer-events-none fixed z-[2147483647]",
6466
+ style: {
6467
+ top: rect.top - chromeGap,
6468
+ left: rect.left - chromeGap,
6469
+ width: rect.width + chromeGap * 2,
6470
+ height: rect.height + chromeGap * 2
6471
+ },
6472
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
6473
+ "button",
6474
+ {
6475
+ type: "button",
6476
+ "data-ohw-navbar-add-button": "",
6477
+ 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",
6478
+ style: { top: "70%" },
6479
+ "aria-label": "Add item",
6480
+ onMouseDown: (e) => {
6481
+ e.preventDefault();
6482
+ e.stopPropagation();
6483
+ },
6484
+ onClick: (e) => {
6485
+ e.preventDefault();
6486
+ e.stopPropagation();
6487
+ onAdd();
6488
+ },
6489
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react10.Plus, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
6490
+ }
6491
+ )
6492
+ }
6493
+ );
6494
+ }
6495
+
5609
6496
  // src/ui/badge.tsx
5610
- var import_jsx_runtime20 = require("react/jsx-runtime");
6497
+ var import_jsx_runtime22 = require("react/jsx-runtime");
5611
6498
  var badgeVariants = cva(
5612
6499
  "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",
5613
6500
  {
@@ -5625,12 +6512,12 @@ var badgeVariants = cva(
5625
6512
  }
5626
6513
  );
5627
6514
  function Badge({ className, variant, ...props }) {
5628
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: cn(badgeVariants({ variant }), className), ...props });
6515
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: cn(badgeVariants({ variant }), className), ...props });
5629
6516
  }
5630
6517
 
5631
6518
  // src/OhhwellsBridge.tsx
5632
- var import_lucide_react3 = require("lucide-react");
5633
- var import_jsx_runtime21 = require("react/jsx-runtime");
6519
+ var import_lucide_react11 = require("lucide-react");
6520
+ var import_jsx_runtime23 = require("react/jsx-runtime");
5634
6521
  var PRIMARY2 = "#0885FE";
5635
6522
  var IMAGE_FADE_MS = 300;
5636
6523
  function runOpacityFade(el, onDone) {
@@ -5657,6 +6544,11 @@ function fadeInImageElement(img, onReady) {
5657
6544
  img.style.opacity = "";
5658
6545
  });
5659
6546
  }
6547
+ function applyEditableImageSrc(img, url) {
6548
+ img.removeAttribute("srcset");
6549
+ img.removeAttribute("sizes");
6550
+ img.src = url;
6551
+ }
5660
6552
  function fadeInBgImage(el, url, onReady) {
5661
6553
  const prevPos = el.style.position;
5662
6554
  if (!prevPos || prevPos === "static") el.style.position = "relative";
@@ -5802,9 +6694,9 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
5802
6694
  tail.insertAdjacentElement("afterend", container);
5803
6695
  }
5804
6696
  const root = (0, import_client.createRoot)(container);
5805
- (0, import_react_dom.flushSync)(() => {
6697
+ (0, import_react_dom2.flushSync)(() => {
5806
6698
  root.render(
5807
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
6699
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
5808
6700
  SchedulingWidget,
5809
6701
  {
5810
6702
  notifyOnConnect,
@@ -5844,7 +6736,7 @@ function mountSchedulingEntries(entries, notifyOnConnect = false) {
5844
6736
  }
5845
6737
  }
5846
6738
  }
5847
- function getLinkHref(el) {
6739
+ function getLinkHref2(el) {
5848
6740
  const anchor = el instanceof HTMLAnchorElement ? el : el.querySelector("a");
5849
6741
  return anchor?.getAttribute("href") ?? "";
5850
6742
  }
@@ -5877,8 +6769,11 @@ function collectEditableNodes() {
5877
6769
  const url = raw.replace(/^url\(['"]?/, "").replace(/['"]?\)$/, "");
5878
6770
  return { key: el.dataset.ohwKey ?? "", type: "bg-image", text: url };
5879
6771
  }
6772
+ if (el.dataset.ohwEditable === "video") {
6773
+ return { key: el.dataset.ohwKey ?? "", type: "video", text: getVideoEl(el)?.src ?? "" };
6774
+ }
5880
6775
  if (el.dataset.ohwEditable === "link") {
5881
- return { key: el.dataset.ohwKey ?? "", type: "link", text: getLinkHref(el) };
6776
+ return { key: el.dataset.ohwKey ?? "", type: "link", text: getLinkHref2(el) };
5882
6777
  }
5883
6778
  return {
5884
6779
  key: el.dataset.ohwKey ?? "",
@@ -5889,9 +6784,79 @@ function collectEditableNodes() {
5889
6784
  document.querySelectorAll("[data-ohw-href-key]").forEach((el) => {
5890
6785
  const key = el.getAttribute("data-ohw-href-key") ?? "";
5891
6786
  if (!key) return;
5892
- nodes.push({ key, type: "link", text: getLinkHref(el) });
6787
+ nodes.push({ key, type: "link", text: getLinkHref2(el) });
6788
+ });
6789
+ document.querySelectorAll('[data-ohw-editable="video"]').forEach((el) => {
6790
+ const key = el.dataset.ohwKey ?? "";
6791
+ const video = getVideoEl(el);
6792
+ if (!key || !video) return;
6793
+ nodes.push({ key: `${key}${VIDEO_AUTOPLAY_SUFFIX}`, type: "video-setting", text: String(video.autoplay) });
6794
+ nodes.push({ key: `${key}${VIDEO_MUTED_SUFFIX}`, type: "video-setting", text: String(video.muted) });
6795
+ });
6796
+ return nodes;
6797
+ }
6798
+ function isMediaEditable(el) {
6799
+ const t = el.dataset.ohwEditable;
6800
+ return t === "image" || t === "bg-image" || t === "video";
6801
+ }
6802
+ var MEDIA_SELECTOR = '[data-ohw-editable="image"], [data-ohw-editable="bg-image"], [data-ohw-editable="video"]';
6803
+ var NON_MEDIA_SELECTOR = '[data-ohw-editable]:not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"]):not([data-ohw-editable="video"])';
6804
+ function getVideoEl(el) {
6805
+ return el instanceof HTMLVideoElement ? el : el.querySelector("video");
6806
+ }
6807
+ var VIDEO_AUTOPLAY_SUFFIX = "__ohw_autoplay";
6808
+ var VIDEO_MUTED_SUFFIX = "__ohw_muted";
6809
+ function isBackgroundVideo(video) {
6810
+ return video.autoplay && video.muted;
6811
+ }
6812
+ function syncVideoPlayback(video) {
6813
+ const background = isBackgroundVideo(video);
6814
+ video.controls = !background;
6815
+ if (video.autoplay) void video.play().catch(() => {
6816
+ });
6817
+ else video.pause();
6818
+ }
6819
+ function lockVideoBox(video) {
6820
+ const { width, height } = video.getBoundingClientRect();
6821
+ if (width > 0 && height > 0 && !video.style.aspectRatio) {
6822
+ video.style.aspectRatio = `${width} / ${height}`;
6823
+ }
6824
+ if (getComputedStyle(video).objectFit === "fill") {
6825
+ video.style.objectFit = "contain";
6826
+ }
6827
+ const fit = video.style.objectFit || getComputedStyle(video).objectFit;
6828
+ const bg = getComputedStyle(video).backgroundColor;
6829
+ const isTransparent = bg === "rgba(0, 0, 0, 0)" || bg === "transparent" || !bg;
6830
+ if (fit === "contain" && isTransparent) {
6831
+ video.style.backgroundColor = "var(--color-dark, #000)";
6832
+ }
6833
+ }
6834
+ function applyVideoSrc(video, url) {
6835
+ const { autoplay, muted } = video;
6836
+ lockVideoBox(video);
6837
+ video.src = url;
6838
+ video.loop = true;
6839
+ video.playsInline = true;
6840
+ video.autoplay = autoplay;
6841
+ video.muted = muted;
6842
+ video.load();
6843
+ syncVideoPlayback(video);
6844
+ }
6845
+ function applyVideoSettingNode(key, val) {
6846
+ const isAutoplay = key.endsWith(VIDEO_AUTOPLAY_SUFFIX);
6847
+ const isMuted = key.endsWith(VIDEO_MUTED_SUFFIX);
6848
+ if (!isAutoplay && !isMuted) return false;
6849
+ const suffixLen = (isAutoplay ? VIDEO_AUTOPLAY_SUFFIX : VIDEO_MUTED_SUFFIX).length;
6850
+ const baseKey = key.slice(0, key.length - suffixLen);
6851
+ const on = val === "true";
6852
+ document.querySelectorAll(`[data-ohw-key="${baseKey}"]`).forEach((el) => {
6853
+ const video = getVideoEl(el);
6854
+ if (!video) return;
6855
+ if (isAutoplay) video.autoplay = on;
6856
+ else video.muted = on;
6857
+ syncVideoPlayback(video);
5893
6858
  });
5894
- return nodes;
6859
+ return true;
5895
6860
  }
5896
6861
  function applyLinkByKey(key, val) {
5897
6862
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -5905,7 +6870,7 @@ function applyLinkByKey(key, val) {
5905
6870
  }
5906
6871
  function isInsideLinkEditor(target) {
5907
6872
  return Boolean(
5908
- 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"]')
6873
+ 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"]')
5909
6874
  );
5910
6875
  }
5911
6876
  function getHrefKeyFromElement(el) {
@@ -5916,7 +6881,7 @@ function getHrefKeyFromElement(el) {
5916
6881
  if (!key) return null;
5917
6882
  return { anchor, key };
5918
6883
  }
5919
- function isNavbarButton(el) {
6884
+ function isNavbarButton2(el) {
5920
6885
  return Boolean(el.closest('[data-ohw-role="navbar-button"]'));
5921
6886
  }
5922
6887
  function getNavigationItemAnchor(el) {
@@ -5928,19 +6893,8 @@ function getNavigationItemAnchor(el) {
5928
6893
  function isNavigationItem(el) {
5929
6894
  return getNavigationItemAnchor(el) !== null;
5930
6895
  }
5931
- function getNavigationItemAddHintTarget(anchor) {
5932
- const items = listNavigationItems();
5933
- const idx = items.indexOf(anchor);
5934
- if (idx >= 0 && idx < items.length - 1) return items[idx + 1];
5935
- return anchor;
5936
- }
5937
- function listNavigationItems() {
5938
- return Array.from(
5939
- document.querySelectorAll("nav [data-ohw-href-key], footer [data-ohw-href-key]")
5940
- ).filter((el) => isNavigationItem(el));
5941
- }
5942
6896
  function getNavigationItemReorderState(anchor) {
5943
- if (isNavbarButton(anchor)) return { key: null, disabled: false };
6897
+ if (isNavbarButton2(anchor)) return { key: null, disabled: false };
5944
6898
  return getReorderHandleStateFromAnchor(anchor);
5945
6899
  }
5946
6900
  function getReorderHandleState(el) {
@@ -5962,6 +6916,120 @@ function clearHrefKeyHover(anchor) {
5962
6916
  function isInsideNavigationItem(el) {
5963
6917
  return getNavigationItemAnchor(el) !== null;
5964
6918
  }
6919
+ function getNavigationRoot(el) {
6920
+ const explicit = el.closest("[data-ohw-nav-root]");
6921
+ if (explicit) return explicit;
6922
+ return el.closest("nav, footer, aside");
6923
+ }
6924
+ function findFooterItemGroup(item) {
6925
+ const footer = item.closest("footer");
6926
+ if (!footer) return null;
6927
+ let node = item.parentElement;
6928
+ while (node && node !== footer) {
6929
+ const hasDirectNavItems = Array.from(
6930
+ node.querySelectorAll(":scope > [data-ohw-href-key]")
6931
+ ).some(isNavigationItem);
6932
+ if (hasDirectNavItems) return node;
6933
+ node = node.parentElement;
6934
+ }
6935
+ return null;
6936
+ }
6937
+ function isNavigationRoot(el) {
6938
+ return el.hasAttribute("data-ohw-nav-root") || el.matches("nav, footer, aside");
6939
+ }
6940
+ function isInferredFooterGroup(el) {
6941
+ const footer = el.closest("footer");
6942
+ if (!footer || el === footer) return false;
6943
+ return Array.from(el.querySelectorAll(":scope > [data-ohw-href-key]")).some(isNavigationItem);
6944
+ }
6945
+ function isNavigationContainer(el) {
6946
+ return el.hasAttribute("data-ohw-nav-container") || isNavigationRoot(el) || isInferredFooterGroup(el);
6947
+ }
6948
+ function isPointOverNavigation(x, y) {
6949
+ const navRoot = document.querySelector("[data-ohw-nav-root]") ?? document.querySelector("nav");
6950
+ if (!navRoot) return false;
6951
+ const r2 = navRoot.getBoundingClientRect();
6952
+ return x >= r2.left && x <= r2.right && y >= r2.top && y <= r2.bottom;
6953
+ }
6954
+ function isPointOverNavItem(container, x, y) {
6955
+ return Array.from(container.querySelectorAll("[data-ohw-href-key]")).some((el) => {
6956
+ if (!isNavigationItem(el) || isNavbarButton2(el)) return false;
6957
+ const itemRect = el.getBoundingClientRect();
6958
+ return x >= itemRect.left && x <= itemRect.right && y >= itemRect.top && y <= itemRect.bottom;
6959
+ });
6960
+ }
6961
+ function resolveNavContainerSelectionTarget(target, clientX, clientY) {
6962
+ if (getNavigationItemAnchor(target)) return null;
6963
+ if (target.closest('[data-ohw-role="navbar-button"]')) return null;
6964
+ const plainLink = target.closest("a");
6965
+ if (plainLink && !plainLink.hasAttribute("data-ohw-href-key") && !plainLink.closest("[data-ohw-nav-container]")) {
6966
+ return null;
6967
+ }
6968
+ const fromClosest = target.closest("[data-ohw-nav-container]");
6969
+ if (fromClosest && !isPointOverNavItem(fromClosest, clientX, clientY)) {
6970
+ return fromClosest;
6971
+ }
6972
+ const navRoot = target.closest("[data-ohw-nav-root]");
6973
+ if (!navRoot) return null;
6974
+ const container = navRoot.querySelector("[data-ohw-nav-container]");
6975
+ if (!container || isPointOverNavItem(container, clientX, clientY)) return null;
6976
+ if (target === navRoot || target.hasAttribute("data-ohw-nav-root")) {
6977
+ return container;
6978
+ }
6979
+ return null;
6980
+ }
6981
+ function getNavigationSelectionParent(el) {
6982
+ if (isNavigationItem(el)) {
6983
+ const explicit = el.closest("[data-ohw-nav-container]");
6984
+ if (explicit) return explicit;
6985
+ const footerGroup = findFooterItemGroup(el);
6986
+ if (footerGroup) return footerGroup;
6987
+ return getNavigationRoot(el);
6988
+ }
6989
+ if (el.hasAttribute("data-ohw-nav-container") || isInferredFooterGroup(el)) {
6990
+ return getNavigationRoot(el);
6991
+ }
6992
+ return null;
6993
+ }
6994
+ function placeCaretAtPoint(el, x, y) {
6995
+ const selection = window.getSelection();
6996
+ if (!selection) return;
6997
+ if (typeof document.caretRangeFromPoint === "function") {
6998
+ const range = document.caretRangeFromPoint(x, y);
6999
+ if (range && el.contains(range.startContainer)) {
7000
+ selection.removeAllRanges();
7001
+ selection.addRange(range);
7002
+ return;
7003
+ }
7004
+ }
7005
+ const caretPositionFromPoint = document.caretPositionFromPoint;
7006
+ if (typeof caretPositionFromPoint === "function") {
7007
+ const position = caretPositionFromPoint(x, y);
7008
+ if (position && el.contains(position.offsetNode)) {
7009
+ const range = document.createRange();
7010
+ range.setStart(position.offsetNode, position.offset);
7011
+ range.collapse(true);
7012
+ selection.removeAllRanges();
7013
+ selection.addRange(range);
7014
+ }
7015
+ }
7016
+ }
7017
+ function selectAllTextInEditable(el) {
7018
+ const selection = window.getSelection();
7019
+ if (!selection) return;
7020
+ const range = document.createRange();
7021
+ range.selectNodeContents(el);
7022
+ selection.removeAllRanges();
7023
+ selection.addRange(range);
7024
+ }
7025
+ function getNavigationLabelEditable(target) {
7026
+ const editable = target.closest('[data-ohw-editable="text"]');
7027
+ if (!editable) return null;
7028
+ const hrefCtx = getHrefKeyFromElement(editable);
7029
+ const navAnchor = hrefCtx ? getNavigationItemAnchor(hrefCtx.anchor) : null;
7030
+ if (!navAnchor) return null;
7031
+ return { editable, navAnchor };
7032
+ }
5965
7033
  function collectSections() {
5966
7034
  return collectSectionsFromDom();
5967
7035
  }
@@ -6085,6 +7153,8 @@ var ICONS = {
6085
7153
  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"/>',
6086
7154
  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"/>'
6087
7155
  };
7156
+ var TOOLBAR_PILL_PADDING = 2;
7157
+ var TOOLBAR_TOGGLE_GAP = 4;
6088
7158
  var TOOLBAR_GROUPS = [
6089
7159
  [
6090
7160
  { cmd: "bold", title: "Bold" },
@@ -6109,7 +7179,7 @@ function EditGlowChrome({
6109
7179
  dragDisabled = false
6110
7180
  }) {
6111
7181
  const GAP = 6;
6112
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
7182
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
6113
7183
  "div",
6114
7184
  {
6115
7185
  ref: elRef,
@@ -6124,7 +7194,7 @@ function EditGlowChrome({
6124
7194
  zIndex: 2147483646
6125
7195
  },
6126
7196
  children: [
6127
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7197
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6128
7198
  "div",
6129
7199
  {
6130
7200
  style: {
@@ -6137,7 +7207,7 @@ function EditGlowChrome({
6137
7207
  }
6138
7208
  }
6139
7209
  ),
6140
- reorderHrefKey && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7210
+ reorderHrefKey && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6141
7211
  "div",
6142
7212
  {
6143
7213
  "data-ohw-drag-handle-container": "",
@@ -6149,7 +7219,7 @@ function EditGlowChrome({
6149
7219
  transform: "translate(calc(-100% - 7px), -50%)",
6150
7220
  pointerEvents: dragDisabled ? "none" : "auto"
6151
7221
  },
6152
- children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7222
+ children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6153
7223
  DragHandle,
6154
7224
  {
6155
7225
  "aria-label": `Reorder ${reorderHrefKey}`,
@@ -6253,7 +7323,7 @@ function FloatingToolbar({
6253
7323
  onEditLink
6254
7324
  }) {
6255
7325
  const { top, left, transform } = calcToolbarPos(rect, parentScroll, 330);
6256
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7326
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6257
7327
  "div",
6258
7328
  {
6259
7329
  ref: elRef,
@@ -6263,14 +7333,23 @@ function FloatingToolbar({
6263
7333
  left,
6264
7334
  transform,
6265
7335
  zIndex: 2147483647,
7336
+ background: "#fff",
7337
+ border: "1px solid #E7E5E4",
7338
+ borderRadius: 6,
7339
+ boxShadow: "0px 2px 4px -2px rgba(0,0,0,.1),0px 4px 6px -1px rgba(0,0,0,.1)",
7340
+ display: "flex",
7341
+ alignItems: "center",
7342
+ padding: TOOLBAR_PILL_PADDING,
7343
+ gap: TOOLBAR_TOGGLE_GAP,
7344
+ fontFamily: "sans-serif",
6266
7345
  pointerEvents: "auto"
6267
7346
  },
6268
- children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(CustomToolbar, { children: [
6269
- TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_react6.default.Fragment, { children: [
6270
- gi > 0 && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(CustomToolbarDivider, {}),
7347
+ children: /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(CustomToolbar, { children: [
7348
+ TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_react8.default.Fragment, { children: [
7349
+ gi > 0 && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(CustomToolbarDivider, {}),
6271
7350
  btns.map((btn) => {
6272
7351
  const isActive = activeCommands.has(btn.cmd);
6273
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7352
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6274
7353
  CustomToolbarButton,
6275
7354
  {
6276
7355
  title: btn.title,
@@ -6279,7 +7358,7 @@ function FloatingToolbar({
6279
7358
  e.preventDefault();
6280
7359
  onCommand(btn.cmd);
6281
7360
  },
6282
- children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7361
+ children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6283
7362
  "svg",
6284
7363
  {
6285
7364
  width: "16",
@@ -6300,7 +7379,7 @@ function FloatingToolbar({
6300
7379
  );
6301
7380
  })
6302
7381
  ] }, gi)),
6303
- showEditLink ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7382
+ showEditLink ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6304
7383
  CustomToolbarButton,
6305
7384
  {
6306
7385
  type: "button",
@@ -6314,7 +7393,7 @@ function FloatingToolbar({
6314
7393
  e.preventDefault();
6315
7394
  e.stopPropagation();
6316
7395
  },
6317
- children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react3.Link, { className: "size-4 shrink-0", "aria-hidden": true })
7396
+ children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_lucide_react11.Link, { className: "size-4 shrink-0", "aria-hidden": true })
6318
7397
  }
6319
7398
  ) : null
6320
7399
  ] })
@@ -6331,7 +7410,7 @@ function StateToggle({
6331
7410
  states,
6332
7411
  onStateChange
6333
7412
  }) {
6334
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7413
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6335
7414
  ToggleGroup,
6336
7415
  {
6337
7416
  "data-ohw-state-toggle": "",
@@ -6345,7 +7424,7 @@ function StateToggle({
6345
7424
  left: rect.right - 8,
6346
7425
  transform: "translateX(-100%)"
6347
7426
  },
6348
- children: states.map((state) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
7427
+ children: states.map((state) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
6349
7428
  }
6350
7429
  );
6351
7430
  }
@@ -6368,12 +7447,12 @@ function resolveSubdomain(subdomainFromQuery) {
6368
7447
  return "";
6369
7448
  }
6370
7449
  function OhhwellsBridge() {
6371
- const pathname = (0, import_navigation.usePathname)();
6372
- const router = (0, import_navigation.useRouter)();
6373
- const searchParams = (0, import_navigation.useSearchParams)();
7450
+ const pathname = (0, import_navigation2.usePathname)();
7451
+ const router = (0, import_navigation2.useRouter)();
7452
+ const searchParams = (0, import_navigation2.useSearchParams)();
6374
7453
  const isEditMode = isEditSessionActive();
6375
- const [bridgeRoot, setBridgeRoot] = (0, import_react6.useState)(null);
6376
- (0, import_react6.useEffect)(() => {
7454
+ const [bridgeRoot, setBridgeRoot] = (0, import_react8.useState)(null);
7455
+ (0, import_react8.useEffect)(() => {
6377
7456
  const figtreeFontId = "ohw-figtree-font";
6378
7457
  if (!document.getElementById(figtreeFontId)) {
6379
7458
  const preconnect1 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.googleapis.com" });
@@ -6381,7 +7460,10 @@ function OhhwellsBridge() {
6381
7460
  const stylesheet = Object.assign(document.createElement("link"), {
6382
7461
  id: figtreeFontId,
6383
7462
  rel: "stylesheet",
6384
- href: "https://fonts.googleapis.com/css2?family=Figtree:ital,wght@0,300..900;1,300..900&display=swap"
7463
+ // Inter is loaded alongside Figtree for the media overlay's Replace button. The bridge
7464
+ // renders inside the customer's document, so it cannot assume any font is present — an
7465
+ // unloaded family would silently fall back to whatever the template happens to use.
7466
+ href: "https://fonts.googleapis.com/css2?family=Figtree:ital,wght@0,300..900;1,300..900&family=Inter:wght@400;500;600&display=swap"
6385
7467
  });
6386
7468
  document.head.append(preconnect1, preconnect2, stylesheet);
6387
7469
  }
@@ -6398,80 +7480,91 @@ function OhhwellsBridge() {
6398
7480
  const subdomainFromQuery = searchParams.get("subdomain");
6399
7481
  const subdomain = resolveSubdomain(subdomainFromQuery);
6400
7482
  useLinkHrefGuardian(pathname, subdomain, isEditMode);
6401
- const postToParent = (0, import_react6.useCallback)((data) => {
7483
+ const postToParent2 = (0, import_react8.useCallback)((data) => {
6402
7484
  if (typeof window !== "undefined" && window.parent !== window) {
6403
7485
  window.parent.postMessage(data, "*");
6404
7486
  }
6405
7487
  }, []);
6406
- const [fetchState, setFetchState] = (0, import_react6.useState)("idle");
6407
- const autoSaveTimers = (0, import_react6.useRef)(/* @__PURE__ */ new Map());
6408
- const activeElRef = (0, import_react6.useRef)(null);
6409
- const selectedElRef = (0, import_react6.useRef)(null);
6410
- const originalContentRef = (0, import_react6.useRef)(null);
6411
- const activeStateElRef = (0, import_react6.useRef)(null);
6412
- const parentScrollRef = (0, import_react6.useRef)(null);
6413
- const visibleViewportRef = (0, import_react6.useRef)(null);
6414
- const [dialogPortalContainer, setDialogPortalContainer] = (0, import_react6.useState)(null);
6415
- const attachVisibleViewport = (0, import_react6.useCallback)((node) => {
7488
+ const [fetchState, setFetchState] = (0, import_react8.useState)("idle");
7489
+ const autoSaveTimers = (0, import_react8.useRef)(/* @__PURE__ */ new Map());
7490
+ const activeElRef = (0, import_react8.useRef)(null);
7491
+ const selectedElRef = (0, import_react8.useRef)(null);
7492
+ const originalContentRef = (0, import_react8.useRef)(null);
7493
+ const activeStateElRef = (0, import_react8.useRef)(null);
7494
+ const parentScrollRef = (0, import_react8.useRef)(null);
7495
+ const visibleViewportRef = (0, import_react8.useRef)(null);
7496
+ const [dialogPortalContainer, setDialogPortalContainer] = (0, import_react8.useState)(null);
7497
+ const attachVisibleViewport = (0, import_react8.useCallback)((node) => {
6416
7498
  visibleViewportRef.current = node;
6417
7499
  setDialogPortalContainer(node);
6418
7500
  if (node) applyVisibleViewport(node, parentScrollRef.current);
6419
7501
  }, []);
6420
- const toolbarElRef = (0, import_react6.useRef)(null);
6421
- const glowElRef = (0, import_react6.useRef)(null);
6422
- const hoveredImageRef = (0, import_react6.useRef)(null);
6423
- const hoveredImageHasTextOverlapRef = (0, import_react6.useRef)(false);
6424
- const hoveredGapRef = (0, import_react6.useRef)(null);
6425
- const imageUnhoverTimerRef = (0, import_react6.useRef)(null);
6426
- const imageShowTimerRef = (0, import_react6.useRef)(null);
6427
- const editStylesRef = (0, import_react6.useRef)(null);
6428
- const activateRef = (0, import_react6.useRef)(() => {
7502
+ const toolbarElRef = (0, import_react8.useRef)(null);
7503
+ const glowElRef = (0, import_react8.useRef)(null);
7504
+ const hoveredImageRef = (0, import_react8.useRef)(null);
7505
+ const hoveredImageHasTextOverlapRef = (0, import_react8.useRef)(false);
7506
+ const dragOverElRef = (0, import_react8.useRef)(null);
7507
+ const [mediaHover, setMediaHover] = (0, import_react8.useState)(null);
7508
+ const [uploadingRects, setUploadingRects] = (0, import_react8.useState)({});
7509
+ const hoveredGapRef = (0, import_react8.useRef)(null);
7510
+ const imageUnhoverTimerRef = (0, import_react8.useRef)(null);
7511
+ const imageShowTimerRef = (0, import_react8.useRef)(null);
7512
+ const editStylesRef = (0, import_react8.useRef)(null);
7513
+ const activateRef = (0, import_react8.useRef)(() => {
7514
+ });
7515
+ const deactivateRef = (0, import_react8.useRef)(() => {
6429
7516
  });
6430
- const deactivateRef = (0, import_react6.useRef)(() => {
7517
+ const selectRef = (0, import_react8.useRef)(() => {
6431
7518
  });
6432
- const selectRef = (0, import_react6.useRef)(() => {
7519
+ const selectFrameRef = (0, import_react8.useRef)(() => {
6433
7520
  });
6434
- const deselectRef = (0, import_react6.useRef)(() => {
7521
+ const deselectRef = (0, import_react8.useRef)(() => {
6435
7522
  });
6436
- const reselectNavigationItemRef = (0, import_react6.useRef)(() => {
7523
+ const reselectNavigationItemRef = (0, import_react8.useRef)(() => {
6437
7524
  });
6438
- const commitNavigationTextEditRef = (0, import_react6.useRef)(() => {
7525
+ const commitNavigationTextEditRef = (0, import_react8.useRef)(() => {
6439
7526
  });
6440
- const refreshActiveCommandsRef = (0, import_react6.useRef)(() => {
7527
+ const refreshActiveCommandsRef = (0, import_react8.useRef)(() => {
6441
7528
  });
6442
- const postToParentRef = (0, import_react6.useRef)(postToParent);
6443
- postToParentRef.current = postToParent;
6444
- const sectionsLoadedRef = (0, import_react6.useRef)(false);
6445
- const pendingScheduleConfigRequests = (0, import_react6.useRef)([]);
6446
- const [toolbarRect, setToolbarRect] = (0, import_react6.useState)(null);
6447
- const [toolbarVariant, setToolbarVariant] = (0, import_react6.useState)("none");
6448
- const toolbarVariantRef = (0, import_react6.useRef)("none");
7529
+ const postToParentRef = (0, import_react8.useRef)(postToParent2);
7530
+ postToParentRef.current = postToParent2;
7531
+ const sectionsLoadedRef = (0, import_react8.useRef)(false);
7532
+ const pendingScheduleConfigRequests = (0, import_react8.useRef)([]);
7533
+ const [toolbarRect, setToolbarRect] = (0, import_react8.useState)(null);
7534
+ const [toolbarVariant, setToolbarVariant] = (0, import_react8.useState)("none");
7535
+ const toolbarVariantRef = (0, import_react8.useRef)("none");
6449
7536
  toolbarVariantRef.current = toolbarVariant;
6450
- const [reorderHrefKey, setReorderHrefKey] = (0, import_react6.useState)(null);
6451
- const [reorderDragDisabled, setReorderDragDisabled] = (0, import_react6.useState)(false);
6452
- const [toggleState, setToggleState] = (0, import_react6.useState)(null);
6453
- const [maxBadge, setMaxBadge] = (0, import_react6.useState)(null);
6454
- const [activeCommands, setActiveCommands] = (0, import_react6.useState)(/* @__PURE__ */ new Set());
6455
- const [sectionGap, setSectionGap] = (0, import_react6.useState)(null);
6456
- const [toolbarShowEditLink, setToolbarShowEditLink] = (0, import_react6.useState)(false);
6457
- const hoveredItemElRef = (0, import_react6.useRef)(null);
6458
- const [hoveredItemRect, setHoveredItemRect] = (0, import_react6.useState)(null);
6459
- const siblingHintElRef = (0, import_react6.useRef)(null);
6460
- const [siblingHintRect, setSiblingHintRect] = (0, import_react6.useState)(null);
6461
- const [isItemDragging, setIsItemDragging] = (0, import_react6.useState)(false);
6462
- const [linkPopover, setLinkPopover] = (0, import_react6.useState)(null);
6463
- const [sitePages, setSitePages] = (0, import_react6.useState)([]);
6464
- const [sectionsByPath, setSectionsByPath] = (0, import_react6.useState)({});
6465
- const sectionsPrefetchGenRef = (0, import_react6.useRef)(0);
6466
- const setLinkPopoverRef = (0, import_react6.useRef)(setLinkPopover);
6467
- const linkPopoverPanelRef = (0, import_react6.useRef)(null);
6468
- const linkPopoverOpenRef = (0, import_react6.useRef)(false);
6469
- const linkPopoverGraceUntilRef = (0, import_react6.useRef)(0);
7537
+ const [reorderHrefKey, setReorderHrefKey] = (0, import_react8.useState)(null);
7538
+ const [reorderDragDisabled, setReorderDragDisabled] = (0, import_react8.useState)(false);
7539
+ const [toggleState, setToggleState] = (0, import_react8.useState)(null);
7540
+ const [maxBadge, setMaxBadge] = (0, import_react8.useState)(null);
7541
+ const [activeCommands, setActiveCommands] = (0, import_react8.useState)(/* @__PURE__ */ new Set());
7542
+ const [sectionGap, setSectionGap] = (0, import_react8.useState)(null);
7543
+ const [toolbarShowEditLink, setToolbarShowEditLink] = (0, import_react8.useState)(false);
7544
+ const hoveredNavContainerRef = (0, import_react8.useRef)(null);
7545
+ const [hoveredNavContainerRect, setHoveredNavContainerRect] = (0, import_react8.useState)(null);
7546
+ const hoveredItemElRef = (0, import_react8.useRef)(null);
7547
+ const [hoveredItemRect, setHoveredItemRect] = (0, import_react8.useState)(null);
7548
+ const siblingHintElRef = (0, import_react8.useRef)(null);
7549
+ const [siblingHintRect, setSiblingHintRect] = (0, import_react8.useState)(null);
7550
+ const [isItemDragging, setIsItemDragging] = (0, import_react8.useState)(false);
7551
+ const [linkPopover, setLinkPopover] = (0, import_react8.useState)(null);
7552
+ const linkPopoverSessionRef = (0, import_react8.useRef)(null);
7553
+ const addNavAfterAnchorRef = (0, import_react8.useRef)(null);
7554
+ const editContentRef = (0, import_react8.useRef)({});
7555
+ const [sitePages, setSitePages] = (0, import_react8.useState)([]);
7556
+ const [sectionsByPath, setSectionsByPath] = (0, import_react8.useState)({});
7557
+ const sectionsPrefetchGenRef = (0, import_react8.useRef)(0);
7558
+ const setLinkPopoverRef = (0, import_react8.useRef)(setLinkPopover);
7559
+ const linkPopoverPanelRef = (0, import_react8.useRef)(null);
7560
+ const linkPopoverOpenRef = (0, import_react8.useRef)(false);
7561
+ const linkPopoverGraceUntilRef = (0, import_react8.useRef)(0);
6470
7562
  setLinkPopoverRef.current = setLinkPopover;
7563
+ linkPopoverSessionRef.current = linkPopover;
6471
7564
  const bumpLinkPopoverGrace = () => {
6472
7565
  linkPopoverGraceUntilRef.current = Date.now() + 350;
6473
7566
  };
6474
- const runSectionsPrefetch = (0, import_react6.useCallback)((pages) => {
7567
+ const runSectionsPrefetch = (0, import_react8.useCallback)((pages) => {
6475
7568
  if (!isEditMode || shouldUseDevFixtures() || pages.length === 0) return;
6476
7569
  const gen = ++sectionsPrefetchGenRef.current;
6477
7570
  const paths = pages.map((p) => p.path);
@@ -6490,9 +7583,9 @@ function OhhwellsBridge() {
6490
7583
  );
6491
7584
  });
6492
7585
  }, [isEditMode, pathname]);
6493
- const runSectionsPrefetchRef = (0, import_react6.useRef)(runSectionsPrefetch);
7586
+ const runSectionsPrefetchRef = (0, import_react8.useRef)(runSectionsPrefetch);
6494
7587
  runSectionsPrefetchRef.current = runSectionsPrefetch;
6495
- (0, import_react6.useEffect)(() => {
7588
+ (0, import_react8.useEffect)(() => {
6496
7589
  if (!linkPopover) return;
6497
7590
  if (hoveredImageRef.current) {
6498
7591
  hoveredImageRef.current = null;
@@ -6500,33 +7593,9 @@ function OhhwellsBridge() {
6500
7593
  }
6501
7594
  hoveredGapRef.current = null;
6502
7595
  setSectionGap(null);
6503
- postToParent({ type: "ow:image-unhover" });
6504
- postToParent({ type: "ow:link-modal-lock", locked: true });
6505
- const html = document.documentElement;
6506
- const body = document.body;
6507
- const prevHtmlOverflow = html.style.overflow;
6508
- const prevBodyOverflow = body.style.overflow;
6509
- html.style.overflow = "hidden";
6510
- body.style.overflow = "hidden";
6511
- const preventBackgroundScroll = (e) => {
6512
- const target = e.target;
6513
- if (target instanceof Element && target.closest('[data-ohw-link-modal-root], [data-slot="dialog-overlay"]')) {
6514
- return;
6515
- }
6516
- e.preventDefault();
6517
- };
6518
- const scrollOpts = { passive: false, capture: true };
6519
- document.addEventListener("wheel", preventBackgroundScroll, scrollOpts);
6520
- document.addEventListener("touchmove", preventBackgroundScroll, scrollOpts);
6521
- return () => {
6522
- postToParent({ type: "ow:link-modal-lock", locked: false });
6523
- html.style.overflow = prevHtmlOverflow;
6524
- body.style.overflow = prevBodyOverflow;
6525
- document.removeEventListener("wheel", preventBackgroundScroll, scrollOpts);
6526
- document.removeEventListener("touchmove", preventBackgroundScroll, scrollOpts);
6527
- };
6528
- }, [linkPopover, postToParent]);
6529
- (0, import_react6.useEffect)(() => {
7596
+ postToParent2({ type: "ow:image-unhover" });
7597
+ }, [linkPopover, postToParent2]);
7598
+ (0, import_react8.useEffect)(() => {
6530
7599
  if (!isEditMode) return;
6531
7600
  const useFixtures = shouldUseDevFixtures();
6532
7601
  if (useFixtures) {
@@ -6547,17 +7616,17 @@ function OhhwellsBridge() {
6547
7616
  runSectionsPrefetchRef.current(mapped);
6548
7617
  };
6549
7618
  window.addEventListener("message", onSitePages);
6550
- if (!useFixtures) postToParent({ type: "ow:request-site-pages" });
7619
+ if (!useFixtures) postToParent2({ type: "ow:request-site-pages" });
6551
7620
  return () => window.removeEventListener("message", onSitePages);
6552
- }, [isEditMode, postToParent]);
6553
- (0, import_react6.useEffect)(() => {
7621
+ }, [isEditMode, postToParent2]);
7622
+ (0, import_react8.useEffect)(() => {
6554
7623
  if (!isEditMode || shouldUseDevFixtures()) return;
6555
7624
  void loadAllSectionsManifest().then((manifest) => {
6556
7625
  if (Object.keys(manifest).length === 0) return;
6557
7626
  setSectionsByPath((prev) => ({ ...manifest, ...prev }));
6558
7627
  });
6559
7628
  }, [isEditMode]);
6560
- (0, import_react6.useEffect)(() => {
7629
+ (0, import_react8.useEffect)(() => {
6561
7630
  const update = () => {
6562
7631
  const el = activeElRef.current ?? selectedElRef.current;
6563
7632
  if (el) setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
@@ -6581,10 +7650,10 @@ function OhhwellsBridge() {
6581
7650
  vvp.removeEventListener("resize", update);
6582
7651
  };
6583
7652
  }, []);
6584
- const refreshStateRules = (0, import_react6.useCallback)(() => {
7653
+ const refreshStateRules = (0, import_react8.useCallback)(() => {
6585
7654
  editStylesRef.current?.forceHover && (editStylesRef.current.forceHover.textContent = collectStateRules());
6586
7655
  }, []);
6587
- const processConfigRequest = (0, import_react6.useCallback)((insertAfterVal) => {
7656
+ const processConfigRequest = (0, import_react8.useCallback)((insertAfterVal) => {
6588
7657
  const tracker = getSectionsTracker();
6589
7658
  let entries = [];
6590
7659
  try {
@@ -6607,7 +7676,7 @@ function OhhwellsBridge() {
6607
7676
  }
6608
7677
  window.postMessage({ type: "ow:schedule-config", insertAfter: insertAfterVal, scheduleId: null }, "*");
6609
7678
  }, [isEditMode]);
6610
- const deactivate = (0, import_react6.useCallback)(() => {
7679
+ const deactivate = (0, import_react8.useCallback)(() => {
6611
7680
  const el = activeElRef.current;
6612
7681
  if (!el) return;
6613
7682
  const key = el.dataset.ohwKey;
@@ -6636,21 +7705,23 @@ function OhhwellsBridge() {
6636
7705
  setMaxBadge(null);
6637
7706
  setActiveCommands(/* @__PURE__ */ new Set());
6638
7707
  setToolbarShowEditLink(false);
6639
- postToParent({ type: "ow:exit-edit" });
6640
- }, [postToParent]);
6641
- const deselect = (0, import_react6.useCallback)(() => {
7708
+ postToParent2({ type: "ow:exit-edit" });
7709
+ }, [postToParent2]);
7710
+ const deselect = (0, import_react8.useCallback)(() => {
6642
7711
  selectedElRef.current = null;
6643
7712
  setReorderHrefKey(null);
6644
7713
  setReorderDragDisabled(false);
6645
7714
  siblingHintElRef.current = null;
6646
7715
  setSiblingHintRect(null);
6647
7716
  setIsItemDragging(false);
7717
+ hoveredNavContainerRef.current = null;
7718
+ setHoveredNavContainerRect(null);
6648
7719
  if (!activeElRef.current) {
6649
7720
  setToolbarRect(null);
6650
7721
  setToolbarVariant("none");
6651
7722
  }
6652
7723
  }, []);
6653
- const reselectNavigationItem = (0, import_react6.useCallback)((navAnchor) => {
7724
+ const reselectNavigationItem = (0, import_react8.useCallback)((navAnchor) => {
6654
7725
  selectedElRef.current = navAnchor;
6655
7726
  const { key, disabled } = getNavigationItemReorderState(navAnchor);
6656
7727
  setReorderHrefKey(key);
@@ -6660,7 +7731,7 @@ function OhhwellsBridge() {
6660
7731
  setToolbarShowEditLink(false);
6661
7732
  setActiveCommands(/* @__PURE__ */ new Set());
6662
7733
  }, []);
6663
- const commitNavigationTextEdit = (0, import_react6.useCallback)((navAnchor) => {
7734
+ const commitNavigationTextEdit = (0, import_react8.useCallback)((navAnchor) => {
6664
7735
  const el = activeElRef.current;
6665
7736
  if (!el) return;
6666
7737
  const key = el.dataset.ohwKey;
@@ -6673,9 +7744,9 @@ function OhhwellsBridge() {
6673
7744
  const html = sanitizeHtml(el.innerHTML);
6674
7745
  const original = originalContentRef.current ?? "";
6675
7746
  if (html !== sanitizeHtml(original)) {
6676
- postToParent({ type: "ow:change", nodes: [{ key, text: html }] });
7747
+ postToParent2({ type: "ow:change", nodes: [{ key, text: html }] });
6677
7748
  const h = document.documentElement.scrollHeight;
6678
- if (h > 50) postToParent({ type: "ow:height", height: h });
7749
+ if (h > 50) postToParent2({ type: "ow:height", height: h });
6679
7750
  }
6680
7751
  }
6681
7752
  el.removeAttribute("contenteditable");
@@ -6683,31 +7754,38 @@ function OhhwellsBridge() {
6683
7754
  setMaxBadge(null);
6684
7755
  setActiveCommands(/* @__PURE__ */ new Set());
6685
7756
  setToolbarShowEditLink(false);
6686
- postToParent({ type: "ow:exit-edit" });
7757
+ postToParent2({ type: "ow:exit-edit" });
6687
7758
  reselectNavigationItem(navAnchor);
6688
- }, [postToParent, reselectNavigationItem]);
6689
- const handleAddNavigationItem = (0, import_react6.useCallback)(() => {
6690
- const selected = selectedElRef.current;
6691
- if (!selected) return;
6692
- const target = getNavigationItemAddHintTarget(selected);
6693
- siblingHintElRef.current = target;
6694
- setSiblingHintRect(target.getBoundingClientRect());
7759
+ }, [postToParent2, reselectNavigationItem]);
7760
+ const handleAddTopLevelNavItem = (0, import_react8.useCallback)(() => {
7761
+ const items = listNavbarItems();
7762
+ addNavAfterAnchorRef.current = items[items.length - 1] ?? null;
7763
+ deselectRef.current();
7764
+ deactivateRef.current();
7765
+ bumpLinkPopoverGrace();
7766
+ setLinkPopover({
7767
+ key: "__add-nav__",
7768
+ mode: "create",
7769
+ intent: "add-nav"
7770
+ });
6695
7771
  }, []);
6696
- const handleItemDragStart = (0, import_react6.useCallback)(() => {
7772
+ const handleItemDragStart = (0, import_react8.useCallback)(() => {
6697
7773
  siblingHintElRef.current = null;
6698
7774
  setSiblingHintRect(null);
6699
7775
  setIsItemDragging(true);
6700
7776
  }, []);
6701
- const handleItemDragEnd = (0, import_react6.useCallback)(() => {
7777
+ const handleItemDragEnd = (0, import_react8.useCallback)(() => {
6702
7778
  setIsItemDragging(false);
6703
7779
  }, []);
6704
7780
  reselectNavigationItemRef.current = reselectNavigationItem;
6705
7781
  commitNavigationTextEditRef.current = commitNavigationTextEdit;
6706
- const select = (0, import_react6.useCallback)((anchor) => {
7782
+ const select = (0, import_react8.useCallback)((anchor) => {
6707
7783
  if (!isNavigationItem(anchor)) return;
6708
7784
  if (activeElRef.current) deactivate();
6709
7785
  selectedElRef.current = anchor;
6710
7786
  clearHrefKeyHover(anchor);
7787
+ hoveredNavContainerRef.current = null;
7788
+ setHoveredNavContainerRect(null);
6711
7789
  setHoveredItemRect(null);
6712
7790
  hoveredItemElRef.current = null;
6713
7791
  siblingHintElRef.current = null;
@@ -6721,13 +7799,32 @@ function OhhwellsBridge() {
6721
7799
  setToolbarShowEditLink(false);
6722
7800
  setActiveCommands(/* @__PURE__ */ new Set());
6723
7801
  }, [deactivate]);
6724
- const activate = (0, import_react6.useCallback)((el) => {
7802
+ const selectFrame = (0, import_react8.useCallback)((el) => {
7803
+ if (!isNavigationContainer(el)) return;
7804
+ if (activeElRef.current) deactivate();
7805
+ selectedElRef.current = el;
7806
+ clearHrefKeyHover(el);
7807
+ hoveredNavContainerRef.current = null;
7808
+ setHoveredNavContainerRect(null);
7809
+ setHoveredItemRect(null);
7810
+ hoveredItemElRef.current = null;
7811
+ siblingHintElRef.current = null;
7812
+ setSiblingHintRect(null);
7813
+ setIsItemDragging(false);
7814
+ setReorderHrefKey(null);
7815
+ setReorderDragDisabled(false);
7816
+ setToolbarVariant("select-frame");
7817
+ setToolbarRect(el.getBoundingClientRect());
7818
+ setToolbarShowEditLink(false);
7819
+ setActiveCommands(/* @__PURE__ */ new Set());
7820
+ }, [deactivate]);
7821
+ const activate = (0, import_react8.useCallback)((el, options) => {
6725
7822
  if (activeElRef.current === el) return;
6726
7823
  selectedElRef.current = null;
6727
7824
  deactivate();
6728
7825
  if (hoveredImageRef.current) {
6729
7826
  hoveredImageRef.current = null;
6730
- postToParentRef.current({ type: "ow:image-unhover" });
7827
+ setMediaHover(null);
6731
7828
  }
6732
7829
  setToolbarVariant("rich-text");
6733
7830
  siblingHintElRef.current = null;
@@ -6739,6 +7836,9 @@ function OhhwellsBridge() {
6739
7836
  activeElRef.current = el;
6740
7837
  originalContentRef.current = el.innerHTML;
6741
7838
  el.focus();
7839
+ if (options?.caretX !== void 0 && options?.caretY !== void 0) {
7840
+ placeCaretAtPoint(el, options.caretX, options.caretY);
7841
+ }
6742
7842
  setToolbarShowEditLink(Boolean(getHrefKeyFromElement(el)));
6743
7843
  const navAnchor = getNavigationItemAnchor(el);
6744
7844
  if (navAnchor) {
@@ -6750,14 +7850,15 @@ function OhhwellsBridge() {
6750
7850
  setReorderDragDisabled(reorderDisabled);
6751
7851
  }
6752
7852
  setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
6753
- postToParent({ type: "ow:enter-edit", key: el.dataset.ohwKey });
7853
+ postToParent2({ type: "ow:enter-edit", key: el.dataset.ohwKey });
6754
7854
  requestAnimationFrame(() => refreshActiveCommandsRef.current());
6755
- }, [deactivate, postToParent]);
7855
+ }, [deactivate, postToParent2]);
6756
7856
  activateRef.current = activate;
6757
7857
  deactivateRef.current = deactivate;
6758
7858
  selectRef.current = select;
7859
+ selectFrameRef.current = selectFrame;
6759
7860
  deselectRef.current = deselect;
6760
- (0, import_react6.useLayoutEffect)(() => {
7861
+ (0, import_react8.useLayoutEffect)(() => {
6761
7862
  if (!subdomain || isEditMode) {
6762
7863
  setFetchState("done");
6763
7864
  return;
@@ -6766,11 +7867,12 @@ function OhhwellsBridge() {
6766
7867
  const imageLoads = [];
6767
7868
  for (const [key, val] of Object.entries(content)) {
6768
7869
  if (key === "__ohw_sections") continue;
7870
+ if (applyVideoSettingNode(key, val)) continue;
6769
7871
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
6770
7872
  if (el.dataset.ohwEditable === "image") {
6771
7873
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
6772
7874
  if (img && img.src !== val) {
6773
- img.src = val;
7875
+ applyEditableImageSrc(img, val);
6774
7876
  imageLoads.push(new Promise((resolve) => {
6775
7877
  img.onload = () => resolve();
6776
7878
  img.onerror = () => resolve();
@@ -6779,6 +7881,15 @@ function OhhwellsBridge() {
6779
7881
  } else if (el.dataset.ohwEditable === "bg-image") {
6780
7882
  const next = `url('${val}')`;
6781
7883
  if (el.style.backgroundImage !== next) el.style.backgroundImage = next;
7884
+ } else if (el.dataset.ohwEditable === "video") {
7885
+ const video = getVideoEl(el);
7886
+ if (video && video.src !== val) {
7887
+ applyVideoSrc(video, val);
7888
+ imageLoads.push(new Promise((resolve) => {
7889
+ video.onloadeddata = () => resolve();
7890
+ video.onerror = () => resolve();
7891
+ }));
7892
+ }
6782
7893
  } else if (el.dataset.ohwEditable === "link") {
6783
7894
  applyLinkHref(el, val);
6784
7895
  } else if (el.innerHTML !== val) {
@@ -6787,6 +7898,7 @@ function OhhwellsBridge() {
6787
7898
  });
6788
7899
  applyLinkByKey(key, val);
6789
7900
  }
7901
+ reconcileNavbarItemsFromContent(content);
6790
7902
  enforceLinkHrefs();
6791
7903
  initSectionsFromContent(content, true);
6792
7904
  sectionsLoadedRef.current = true;
@@ -6815,7 +7927,7 @@ function OhhwellsBridge() {
6815
7927
  cancelled = true;
6816
7928
  };
6817
7929
  }, [subdomain, isEditMode]);
6818
- (0, import_react6.useEffect)(() => {
7930
+ (0, import_react8.useEffect)(() => {
6819
7931
  if (!subdomain || isEditMode) return;
6820
7932
  let debounceTimer = null;
6821
7933
  let observer = null;
@@ -6827,13 +7939,17 @@ function OhhwellsBridge() {
6827
7939
  try {
6828
7940
  for (const [key, val] of Object.entries(content)) {
6829
7941
  if (key === "__ohw_sections") continue;
7942
+ if (applyVideoSettingNode(key, val)) continue;
6830
7943
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
6831
7944
  if (el.dataset.ohwEditable === "image") {
6832
7945
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
6833
- if (img && img.src !== val) img.src = val;
7946
+ if (img && img.src !== val) applyEditableImageSrc(img, val);
6834
7947
  } else if (el.dataset.ohwEditable === "bg-image") {
6835
7948
  const next = `url('${val}')`;
6836
7949
  if (el.style.backgroundImage !== next) el.style.backgroundImage = next;
7950
+ } else if (el.dataset.ohwEditable === "video") {
7951
+ const video = getVideoEl(el);
7952
+ if (video && video.src !== val) applyVideoSrc(video, val);
6837
7953
  } else if (el.dataset.ohwEditable === "link") {
6838
7954
  applyLinkHref(el, val);
6839
7955
  } else if (el.innerHTML !== val) {
@@ -6842,6 +7958,7 @@ function OhhwellsBridge() {
6842
7958
  });
6843
7959
  applyLinkByKey(key, val);
6844
7960
  }
7961
+ reconcileNavbarItemsFromContent(content);
6845
7962
  } finally {
6846
7963
  observer?.observe(document.body, { childList: true, subtree: true });
6847
7964
  }
@@ -6859,26 +7976,44 @@ function OhhwellsBridge() {
6859
7976
  if (debounceTimer) clearTimeout(debounceTimer);
6860
7977
  };
6861
7978
  }, [subdomain, isEditMode, pathname]);
6862
- (0, import_react6.useLayoutEffect)(() => {
7979
+ (0, import_react8.useLayoutEffect)(() => {
6863
7980
  const el = document.getElementById("ohw-loader");
6864
7981
  if (!el) return;
6865
7982
  const visible = Boolean(subdomain) && fetchState !== "done";
6866
7983
  el.style.display = visible ? "flex" : "none";
6867
7984
  }, [subdomain, fetchState]);
6868
- (0, import_react6.useEffect)(() => {
6869
- postToParent({ type: "ow:navigation", path: pathname });
6870
- }, [pathname, postToParent]);
6871
- (0, import_react6.useEffect)(() => {
7985
+ (0, import_react8.useEffect)(() => {
7986
+ postToParent2({ type: "ow:navigation", path: pathname });
7987
+ }, [pathname, postToParent2]);
7988
+ (0, import_react8.useEffect)(() => {
6872
7989
  if (!isEditMode) return;
7990
+ if (linkPopoverSessionRef.current?.intent === "add-nav") return;
7991
+ if (document.querySelector("[data-ohw-section-picker]")) return;
6873
7992
  setLinkPopover(null);
6874
7993
  deselectRef.current();
6875
7994
  deactivateRef.current();
6876
7995
  }, [pathname, isEditMode]);
6877
- (0, import_react6.useEffect)(() => {
7996
+ (0, import_react8.useEffect)(() => {
7997
+ const contentForNav = () => {
7998
+ if (isEditMode) return editContentRef.current;
7999
+ if (!subdomain) return {};
8000
+ return contentCache.get(subdomain) ?? {};
8001
+ };
8002
+ const run = () => reconcileNavbarItemsFromContent(contentForNav());
8003
+ run();
8004
+ const nav = document.querySelector("nav");
8005
+ if (!nav) return;
8006
+ const observer = new MutationObserver(() => {
8007
+ requestAnimationFrame(run);
8008
+ });
8009
+ observer.observe(nav, { childList: true, subtree: true });
8010
+ return () => observer.disconnect();
8011
+ }, [isEditMode, pathname, subdomain, fetchState]);
8012
+ (0, import_react8.useEffect)(() => {
6878
8013
  if (!isEditMode) return;
6879
8014
  const measure = () => {
6880
8015
  const h = document.body.scrollHeight;
6881
- if (h > 50) postToParent({ type: "ow:height", height: h });
8016
+ if (h > 50) postToParent2({ type: "ow:height", height: h });
6882
8017
  };
6883
8018
  const t1 = setTimeout(measure, 50);
6884
8019
  const t2 = setTimeout(measure, 500);
@@ -6897,8 +8032,8 @@ function OhhwellsBridge() {
6897
8032
  if (resizeTimer) clearTimeout(resizeTimer);
6898
8033
  window.removeEventListener("resize", handleResize);
6899
8034
  };
6900
- }, [pathname, isEditMode, postToParent]);
6901
- (0, import_react6.useEffect)(() => {
8035
+ }, [pathname, isEditMode, postToParent2]);
8036
+ (0, import_react8.useEffect)(() => {
6902
8037
  if (!subdomainFromQuery || isEditMode) return;
6903
8038
  const handleClick = (e) => {
6904
8039
  const anchor = e.target.closest("a");
@@ -6914,7 +8049,7 @@ function OhhwellsBridge() {
6914
8049
  document.addEventListener("click", handleClick, true);
6915
8050
  return () => document.removeEventListener("click", handleClick, true);
6916
8051
  }, [subdomainFromQuery, isEditMode, router]);
6917
- (0, import_react6.useEffect)(() => {
8052
+ (0, import_react8.useEffect)(() => {
6918
8053
  if (!isEditMode) {
6919
8054
  editStylesRef.current?.base.remove();
6920
8055
  editStylesRef.current?.forceHover.remove();
@@ -6937,8 +8072,9 @@ function OhhwellsBridge() {
6937
8072
  [data-ohw-editable] {
6938
8073
  display: block;
6939
8074
  }
6940
- [data-ohw-editable]:not([contenteditable]):not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"]) { cursor: text !important; }
8075
+ [data-ohw-editable]:not([contenteditable]):not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"]):not([data-ohw-editable="video"]) { cursor: text !important; }
6941
8076
  [data-ohw-editable="image"], [data-ohw-editable="image"] *,
8077
+ [data-ohw-editable="video"], [data-ohw-editable="video"] *,
6942
8078
  [data-ohw-editable="bg-image"], [data-ohw-editable="bg-image"] * { cursor: pointer !important; }
6943
8079
  [data-ohw-editable="link"], [data-ohw-editable="link"] * { cursor: pointer !important; }
6944
8080
  [data-ohw-hovered]:not([contenteditable]):not([data-ohw-href-key]) {
@@ -6987,6 +8123,13 @@ function OhhwellsBridge() {
6987
8123
  if (target.closest("[data-ohw-max-badge]")) return;
6988
8124
  if (isInsideLinkEditor(target)) return;
6989
8125
  if (target.closest('[data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
8126
+ const navFromChrome = resolveNavContainerSelectionTarget(target, e.clientX, e.clientY);
8127
+ if (navFromChrome) {
8128
+ e.preventDefault();
8129
+ e.stopPropagation();
8130
+ selectFrameRef.current(navFromChrome);
8131
+ return;
8132
+ }
6990
8133
  e.preventDefault();
6991
8134
  e.stopPropagation();
6992
8135
  return;
@@ -7001,14 +8144,15 @@ function OhhwellsBridge() {
7001
8144
  bumpLinkPopoverGrace();
7002
8145
  setLinkPopoverRef.current({
7003
8146
  key: editable.dataset.ohwKey ?? "",
7004
- target: getLinkHref(editable)
8147
+ mode: "edit",
8148
+ target: getLinkHref2(editable)
7005
8149
  });
7006
8150
  return;
7007
8151
  }
7008
- if (editable.dataset.ohwEditable === "image" || editable.dataset.ohwEditable === "bg-image") {
8152
+ if (isMediaEditable(editable)) {
7009
8153
  e.preventDefault();
7010
8154
  e.stopPropagation();
7011
- postToParentRef.current({ type: "ow:image-pick", key: editable.dataset.ohwKey ?? "" });
8155
+ postToParentRef.current({ type: "ow:image-pick", key: editable.dataset.ohwKey ?? "", elementType: editable.dataset.ohwEditable ?? "image" });
7012
8156
  return;
7013
8157
  }
7014
8158
  const hrefCtx = getHrefKeyFromElement(editable);
@@ -7017,7 +8161,8 @@ function OhhwellsBridge() {
7017
8161
  e.preventDefault();
7018
8162
  e.stopPropagation();
7019
8163
  if (selectedElRef.current === navAnchor) {
7020
- activateRef.current(editable);
8164
+ if (e.detail >= 2) return;
8165
+ activateRef.current(editable, { caretX: e.clientX, caretY: e.clientY });
7021
8166
  return;
7022
8167
  }
7023
8168
  selectRef.current(navAnchor);
@@ -7036,6 +8181,22 @@ function OhhwellsBridge() {
7036
8181
  selectRef.current(hrefAnchor);
7037
8182
  return;
7038
8183
  }
8184
+ const navContainerToSelect = resolveNavContainerSelectionTarget(target, e.clientX, e.clientY);
8185
+ if (navContainerToSelect) {
8186
+ e.preventDefault();
8187
+ e.stopPropagation();
8188
+ selectFrameRef.current(navContainerToSelect);
8189
+ return;
8190
+ }
8191
+ const selectedContainer = selectedElRef.current;
8192
+ if (selectedContainer && isNavigationContainer(selectedContainer)) {
8193
+ const navItem = getNavigationItemAnchor(target);
8194
+ if (!navItem && (selectedContainer === target || selectedContainer.contains(target))) {
8195
+ e.preventDefault();
8196
+ e.stopPropagation();
8197
+ return;
8198
+ }
8199
+ }
7039
8200
  if (activeElRef.current) {
7040
8201
  const sel = window.getSelection();
7041
8202
  if (sel && !sel.isCollapsed) {
@@ -7061,10 +8222,48 @@ function OhhwellsBridge() {
7061
8222
  deselectRef.current();
7062
8223
  deactivateRef.current();
7063
8224
  };
8225
+ const handleDblClick = (e) => {
8226
+ const target = e.target;
8227
+ if (target.closest("[data-ohw-toolbar]")) return;
8228
+ if (target.closest("[data-ohw-state-toggle]")) return;
8229
+ if (target.closest("[data-ohw-max-badge]")) return;
8230
+ if (isInsideLinkEditor(target)) return;
8231
+ if (target.closest('[data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
8232
+ return;
8233
+ }
8234
+ const navLabel = getNavigationLabelEditable(target);
8235
+ if (!navLabel) return;
8236
+ const { editable } = navLabel;
8237
+ e.preventDefault();
8238
+ e.stopPropagation();
8239
+ if (activeElRef.current !== editable) {
8240
+ activateRef.current(editable);
8241
+ }
8242
+ requestAnimationFrame(() => {
8243
+ selectAllTextInEditable(editable);
8244
+ refreshActiveCommandsRef.current();
8245
+ });
8246
+ };
7064
8247
  const handleMouseOver = (e) => {
7065
8248
  const target = e.target;
8249
+ if (toolbarVariantRef.current !== "select-frame") {
8250
+ const navContainer = target.closest("[data-ohw-nav-container]");
8251
+ if (navContainer && !getNavigationItemAnchor(target)) {
8252
+ hoveredNavContainerRef.current = navContainer;
8253
+ setHoveredNavContainerRect(navContainer.getBoundingClientRect());
8254
+ hoveredItemElRef.current = null;
8255
+ setHoveredItemRect(null);
8256
+ return;
8257
+ }
8258
+ if (!target.closest("[data-ohw-nav-container]")) {
8259
+ hoveredNavContainerRef.current = null;
8260
+ setHoveredNavContainerRect(null);
8261
+ }
8262
+ }
7066
8263
  const navAnchor = getNavigationItemAnchor(target);
7067
8264
  if (navAnchor) {
8265
+ hoveredNavContainerRef.current = null;
8266
+ setHoveredNavContainerRect(null);
7068
8267
  const selected2 = selectedElRef.current;
7069
8268
  if (selected2 === navAnchor || selected2?.contains(navAnchor)) return;
7070
8269
  clearHrefKeyHover(navAnchor);
@@ -7076,7 +8275,7 @@ function OhhwellsBridge() {
7076
8275
  if (!editable) return;
7077
8276
  const selected = selectedElRef.current;
7078
8277
  if (selected && (selected === editable || selected.contains(editable))) return;
7079
- if (editable.dataset.ohwEditable !== "image" && editable.dataset.ohwEditable !== "bg-image" && !editable.hasAttribute("contenteditable")) {
8278
+ if (!isMediaEditable(editable) && !editable.hasAttribute("contenteditable")) {
7080
8279
  const hoverTarget = editable.closest("[data-ohw-href-key]") ?? editable;
7081
8280
  if (hoverTarget.hasAttribute("data-ohw-href-key")) {
7082
8281
  clearHrefKeyHover(hoverTarget);
@@ -7089,6 +8288,14 @@ function OhhwellsBridge() {
7089
8288
  };
7090
8289
  const handleMouseOut = (e) => {
7091
8290
  const target = e.target;
8291
+ if (target.closest("[data-ohw-nav-container]")) {
8292
+ const related2 = e.relatedTarget instanceof Element ? e.relatedTarget : null;
8293
+ if (related2?.closest("[data-ohw-nav-container], [data-ohw-navbar-container-chrome], [data-ohw-navbar-add-button]")) {
8294
+ return;
8295
+ }
8296
+ hoveredNavContainerRef.current = null;
8297
+ setHoveredNavContainerRect(null);
8298
+ }
7092
8299
  const navAnchor = getNavigationItemAnchor(target);
7093
8300
  if (navAnchor) {
7094
8301
  const related2 = e.relatedTarget instanceof Element ? e.relatedTarget : null;
@@ -7104,7 +8311,7 @@ function OhhwellsBridge() {
7104
8311
  if (!editable) return;
7105
8312
  const related = e.relatedTarget instanceof Element ? e.relatedTarget : null;
7106
8313
  if (related?.closest("[data-ohw-drag-handle-container], [data-ohw-item-interaction]")) return;
7107
- if (editable.dataset.ohwEditable !== "image" && editable.dataset.ohwEditable !== "bg-image") {
8314
+ if (!isMediaEditable(editable)) {
7108
8315
  const hoverTarget = editable.closest("[data-ohw-href-key]") ?? editable;
7109
8316
  if (hoverTarget.hasAttribute("data-ohw-href-key")) {
7110
8317
  if (!related?.closest("[data-ohw-href-key]")) {
@@ -7150,23 +8357,26 @@ function OhhwellsBridge() {
7150
8357
  }
7151
8358
  return { top, left, width: Math.max(0, right - left), height: Math.max(0, bottom - top) };
7152
8359
  };
7153
- const postImageHover = (imgEl, isDragOver = false, forceTextOverlap) => {
8360
+ const showImageHover = (imgEl, isDragOver = false, forceTextOverlap) => {
7154
8361
  const r2 = getVisibleRect(imgEl);
7155
8362
  const hasTextOverlap = forceTextOverlap ?? false;
7156
8363
  hoveredImageHasTextOverlapRef.current = hasTextOverlap;
7157
- postToParentRef.current({
7158
- type: "ow:image-hover",
8364
+ const video = imgEl.dataset.ohwEditable === "video" ? getVideoEl(imgEl) : null;
8365
+ setMediaHover({
7159
8366
  key: imgEl.dataset.ohwKey ?? "",
7160
8367
  rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height },
8368
+ elementType: imgEl.dataset.ohwEditable ?? "image",
7161
8369
  hasTextOverlap,
7162
- ...isDragOver ? { isDragOver: true } : {}
8370
+ isDragOver,
8371
+ ...video ? { videoAutoplay: video.autoplay, videoMuted: video.muted } : {}
7163
8372
  });
7164
8373
  const track = imgEl.closest("[data-ohw-hover-pause]");
7165
8374
  if (track) track.setAttribute("data-ohw-hover-paused", "");
7166
8375
  };
8376
+ const clearImageHover = () => setMediaHover(null);
7167
8377
  const findImageAtPoint = (clientX, clientY, fromParentViewport) => {
7168
8378
  const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
7169
- const images = Array.from(document.querySelectorAll('[data-ohw-editable="image"], [data-ohw-editable="bg-image"]'));
8379
+ const images = Array.from(document.querySelectorAll(MEDIA_SELECTOR));
7170
8380
  const matches = [];
7171
8381
  for (let i = images.length - 1; i >= 0; i--) {
7172
8382
  const el = images[i];
@@ -7182,7 +8392,9 @@ function OhhwellsBridge() {
7182
8392
  if (x >= r2.left && x <= r2.left + r2.width && y >= r2.top && y <= r2.top + r2.height) matches.push(el);
7183
8393
  }
7184
8394
  if (matches.length === 0) return null;
7185
- const imageTypeMatches = matches.filter((el) => el.dataset.ohwEditable === "image");
8395
+ const imageTypeMatches = matches.filter(
8396
+ (el) => el.dataset.ohwEditable === "image" || el.dataset.ohwEditable === "video"
8397
+ );
7186
8398
  const candidatePool = imageTypeMatches.length > 0 ? imageTypeMatches : matches;
7187
8399
  const smallest = candidatePool.reduce((best, el) => {
7188
8400
  const br = getVisibleRect(best);
@@ -7205,47 +8417,91 @@ function OhhwellsBridge() {
7205
8417
  }
7206
8418
  return smallest;
7207
8419
  };
8420
+ const dismissImageHover = () => {
8421
+ if (hoveredImageRef.current) {
8422
+ hoveredImageRef.current = null;
8423
+ hoveredImageHasTextOverlapRef.current = false;
8424
+ resumeAnimTracks();
8425
+ postToParentRef.current({ type: "ow:image-unhover" });
8426
+ }
8427
+ clearImageHover();
8428
+ };
8429
+ const probeNavigationHoverAt = (x, y) => {
8430
+ if (toolbarVariantRef.current === "select-frame") {
8431
+ hoveredNavContainerRef.current = null;
8432
+ setHoveredNavContainerRect(null);
8433
+ return;
8434
+ }
8435
+ const navContainer = document.querySelector("[data-ohw-nav-container]");
8436
+ if (!navContainer) {
8437
+ hoveredNavContainerRef.current = null;
8438
+ setHoveredNavContainerRect(null);
8439
+ return;
8440
+ }
8441
+ const containerRect = navContainer.getBoundingClientRect();
8442
+ const overContainer = x >= containerRect.left && x <= containerRect.right && y >= containerRect.top && y <= containerRect.bottom;
8443
+ if (!overContainer) {
8444
+ hoveredNavContainerRef.current = null;
8445
+ setHoveredNavContainerRect(null);
8446
+ return;
8447
+ }
8448
+ const navItemHit = Array.from(
8449
+ navContainer.querySelectorAll("[data-ohw-href-key]")
8450
+ ).find((el) => {
8451
+ if (!isNavigationItem(el) || isNavbarButton2(el)) return false;
8452
+ const itemRect = el.getBoundingClientRect();
8453
+ return x >= itemRect.left && x <= itemRect.right && y >= itemRect.top && y <= itemRect.bottom;
8454
+ });
8455
+ if (navItemHit) {
8456
+ hoveredNavContainerRef.current = null;
8457
+ setHoveredNavContainerRect(null);
8458
+ const selected = selectedElRef.current;
8459
+ if (selected !== navItemHit && !selected?.contains(navItemHit)) {
8460
+ clearHrefKeyHover(navItemHit);
8461
+ hoveredItemElRef.current = navItemHit;
8462
+ setHoveredItemRect(navItemHit.getBoundingClientRect());
8463
+ }
8464
+ return;
8465
+ }
8466
+ hoveredNavContainerRef.current = navContainer;
8467
+ setHoveredNavContainerRect(containerRect);
8468
+ hoveredItemElRef.current = null;
8469
+ setHoveredItemRect(null);
8470
+ };
7208
8471
  const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
7209
8472
  if (linkPopoverOpenRef.current) {
7210
- if (hoveredImageRef.current) {
7211
- hoveredImageRef.current = null;
7212
- hoveredImageHasTextOverlapRef.current = false;
7213
- resumeAnimTracks();
7214
- postToParentRef.current({ type: "ow:image-unhover" });
7215
- }
8473
+ dismissImageHover();
8474
+ return;
8475
+ }
8476
+ const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
8477
+ if (isPointOverNavigation(x, y)) {
8478
+ dismissImageHover();
8479
+ probeNavigationHoverAt(x, y);
7216
8480
  return;
7217
8481
  }
8482
+ hoveredNavContainerRef.current = null;
8483
+ setHoveredNavContainerRect(null);
7218
8484
  const toggleEl = document.querySelector("[data-ohw-state-toggle]");
7219
8485
  if (toggleEl) {
7220
- const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
7221
8486
  const tr = toggleEl.getBoundingClientRect();
7222
8487
  if (x >= tr.left && x <= tr.right && y >= tr.top && y <= tr.bottom) {
7223
- if (hoveredImageRef.current) {
7224
- hoveredImageRef.current = null;
7225
- resumeAnimTracks();
7226
- postToParentRef.current({ type: "ow:image-unhover" });
7227
- }
8488
+ dismissImageHover();
7228
8489
  return;
7229
8490
  }
7230
8491
  }
7231
- if (activeElRef.current) {
7232
- if (hoveredImageRef.current) {
7233
- hoveredImageRef.current = null;
7234
- hoveredImageHasTextOverlapRef.current = false;
7235
- resumeAnimTracks();
7236
- postToParentRef.current({ type: "ow:image-unhover" });
7237
- }
8492
+ const imgEl = findImageAtPoint(clientX, clientY, fromParentViewport);
8493
+ const activeEl = activeElRef.current;
8494
+ if (activeEl && (!imgEl || imgEl.contains(activeEl))) {
8495
+ dismissImageHover();
7238
8496
  return;
7239
8497
  }
7240
- const imgEl = findImageAtPoint(clientX, clientY, fromParentViewport);
7241
8498
  if (imgEl) {
7242
- const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
7243
8499
  const topEl = document.elementFromPoint(x, y);
7244
8500
  if (topEl?.closest('[data-ohw-toolbar], [data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
7245
8501
  if (hoveredImageRef.current) {
7246
8502
  hoveredImageRef.current = null;
7247
8503
  resumeAnimTracks();
7248
- postToParentRef.current({ type: "ow:image-unhover" });
8504
+ clearImageHover();
7249
8505
  }
7250
8506
  return;
7251
8507
  }
@@ -7254,13 +8510,13 @@ function OhhwellsBridge() {
7254
8510
  hoveredImageRef.current = null;
7255
8511
  hoveredImageHasTextOverlapRef.current = false;
7256
8512
  resumeAnimTracks();
7257
- postToParentRef.current({ type: "ow:image-unhover" });
8513
+ clearImageHover();
7258
8514
  }
7259
8515
  return;
7260
8516
  }
7261
8517
  const isStateCardImage = !!imgEl.closest("[data-ohw-editable-state]");
7262
8518
  const textEditable = Array.from(
7263
- document.querySelectorAll('[data-ohw-editable]:not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"])')
8519
+ document.querySelectorAll(NON_MEDIA_SELECTOR)
7264
8520
  ).find((el) => {
7265
8521
  const er = el.getBoundingClientRect();
7266
8522
  return x >= er.left && x <= er.right && y >= er.top && y <= er.bottom;
@@ -7279,14 +8535,14 @@ function OhhwellsBridge() {
7279
8535
  hoveredImageRef.current = null;
7280
8536
  hoveredImageHasTextOverlapRef.current = false;
7281
8537
  resumeAnimTracks();
7282
- postToParentRef.current({ type: "ow:image-unhover" });
8538
+ clearImageHover();
7283
8539
  }
7284
8540
  return;
7285
8541
  }
7286
8542
  if (isStateCardImage) {
7287
8543
  if (imgEl !== hoveredImageRef.current || !hoveredImageHasTextOverlapRef.current) {
7288
8544
  hoveredImageRef.current = imgEl;
7289
- postImageHover(imgEl, isDragOver, true);
8545
+ showImageHover(imgEl, isDragOver, true);
7290
8546
  }
7291
8547
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
7292
8548
  if (textEditable && !isInsideNavigationItem(textEditable)) {
@@ -7298,7 +8554,7 @@ function OhhwellsBridge() {
7298
8554
  hoveredImageRef.current = null;
7299
8555
  hoveredImageHasTextOverlapRef.current = false;
7300
8556
  resumeAnimTracks();
7301
- postToParentRef.current({ type: "ow:image-unhover" });
8557
+ clearImageHover();
7302
8558
  }
7303
8559
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
7304
8560
  if (textEditable && !isInsideNavigationItem(textEditable)) {
@@ -7310,7 +8566,7 @@ function OhhwellsBridge() {
7310
8566
  if (hoveredImageRef.current) {
7311
8567
  hoveredImageRef.current = null;
7312
8568
  resumeAnimTracks();
7313
- postToParentRef.current({ type: "ow:image-unhover" });
8569
+ clearImageHover();
7314
8570
  }
7315
8571
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
7316
8572
  return;
@@ -7318,9 +8574,9 @@ function OhhwellsBridge() {
7318
8574
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
7319
8575
  if (imgEl !== hoveredImageRef.current || hoveredImageHasTextOverlapRef.current) {
7320
8576
  hoveredImageRef.current = imgEl;
7321
- postImageHover(imgEl, isDragOver);
8577
+ showImageHover(imgEl, isDragOver);
7322
8578
  } else if (isDragOver) {
7323
- postImageHover(imgEl, true);
8579
+ showImageHover(imgEl, true);
7324
8580
  }
7325
8581
  } else {
7326
8582
  const inIframeView = clientX >= 0 && clientY >= 0 && clientX <= window.innerWidth && clientY <= window.innerHeight;
@@ -7337,14 +8593,14 @@ function OhhwellsBridge() {
7337
8593
  hoveredImageRef.current = null;
7338
8594
  hoveredImageHasTextOverlapRef.current = false;
7339
8595
  resumeAnimTracks();
7340
- postToParentRef.current({ type: "ow:image-unhover" });
8596
+ clearImageHover();
7341
8597
  }
7342
- const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
8598
+ const { x: x2, y: y2 } = toProbeCoords(clientX, clientY, fromParentViewport);
7343
8599
  const textEl = Array.from(
7344
- document.querySelectorAll('[data-ohw-editable]:not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"])')
8600
+ document.querySelectorAll(NON_MEDIA_SELECTOR)
7345
8601
  ).find((el) => {
7346
8602
  const er = el.getBoundingClientRect();
7347
- return x >= er.left && x <= er.right && y >= er.top && y <= er.bottom;
8603
+ return x2 >= er.left && x2 <= er.right && y2 >= er.top && y2 <= er.bottom;
7348
8604
  });
7349
8605
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
7350
8606
  if (textEl && !textEl.hasAttribute("contenteditable")) {
@@ -7447,18 +8703,20 @@ function OhhwellsBridge() {
7447
8703
  return;
7448
8704
  }
7449
8705
  e.dataTransfer.dropEffect = "copy";
7450
- if (hoveredImageRef.current !== el) {
8706
+ if (dragOverElRef.current !== el) {
8707
+ dragOverElRef.current = el;
7451
8708
  hoveredImageRef.current = el;
7452
8709
  const isStateCard = !!el.closest("[data-ohw-editable-state]");
7453
- postImageHover(el, true, isStateCard ? true : void 0);
8710
+ showImageHover(el, true, isStateCard ? true : void 0);
7454
8711
  }
7455
8712
  };
7456
8713
  const handleDragLeave = (e) => {
7457
8714
  const imgEl = findImageAtPoint(e.clientX, e.clientY, false);
7458
8715
  if (imgEl) return;
8716
+ dragOverElRef.current = null;
7459
8717
  hoveredImageRef.current = null;
7460
8718
  resumeAnimTracks();
7461
- postToParentRef.current({ type: "ow:image-unhover" });
8719
+ clearImageHover();
7462
8720
  };
7463
8721
  const handleDrop = (e) => {
7464
8722
  e.preventDefault();
@@ -7466,7 +8724,9 @@ function OhhwellsBridge() {
7466
8724
  if (!el) return;
7467
8725
  const file = e.dataTransfer?.files?.[0];
7468
8726
  if (file) {
7469
- if (file.type.startsWith("image/")) {
8727
+ const wantsVideo = el.dataset.ohwEditable === "video";
8728
+ const accepted = wantsVideo ? file.type.startsWith("video/") : file.type.startsWith("image/");
8729
+ if (accepted) {
7470
8730
  const key = el.dataset.ohwKey ?? "";
7471
8731
  const { name, type: mimeType } = file;
7472
8732
  const r2 = el.getBoundingClientRect();
@@ -7475,12 +8735,13 @@ function OhhwellsBridge() {
7475
8735
  window.parent.postMessage({ type: "ow:image-drop", key, name, mimeType, buf, rect }, "*", [buf]);
7476
8736
  });
7477
8737
  } else {
7478
- postToParentRef.current({ type: "ow:image-drop-invalid" });
8738
+ postToParentRef.current({ type: "ow:image-drop-invalid", expected: wantsVideo ? "video" : "image" });
7479
8739
  }
7480
8740
  }
8741
+ dragOverElRef.current = null;
7481
8742
  hoveredImageRef.current = null;
7482
8743
  resumeAnimTracks();
7483
- postToParentRef.current({ type: "ow:image-unhover" });
8744
+ clearImageHover();
7484
8745
  };
7485
8746
  const handleImageUrl = (e) => {
7486
8747
  if (e.data?.type !== "ow:image-url") return;
@@ -7498,7 +8759,11 @@ function OhhwellsBridge() {
7498
8759
  }
7499
8760
  });
7500
8761
  hoveredImageRef.current = null;
7501
- postToParentRef.current({ type: "ow:image-loaded", key });
8762
+ setUploadingRects((prev) => {
8763
+ const entry = prev[key];
8764
+ if (!entry || entry.fadingOut) return prev;
8765
+ return { ...prev, [key]: { ...entry, fadingOut: true } };
8766
+ });
7502
8767
  };
7503
8768
  let found = false;
7504
8769
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -7528,11 +8793,24 @@ function OhhwellsBridge() {
7528
8793
  };
7529
8794
  img.onload = onReady;
7530
8795
  img.onerror = onReady;
7531
- img.src = url;
8796
+ applyEditableImageSrc(img, url);
7532
8797
  if (img.complete && img.naturalWidth > 0) {
7533
8798
  requestAnimationFrame(() => onReady());
7534
8799
  }
7535
8800
  }
8801
+ } else if (el.dataset.ohwEditable === "video") {
8802
+ const video = getVideoEl(el);
8803
+ if (video) {
8804
+ found = true;
8805
+ const onReady = () => {
8806
+ video.onloadeddata = null;
8807
+ video.onerror = null;
8808
+ notify();
8809
+ };
8810
+ video.onloadeddata = onReady;
8811
+ video.onerror = onReady;
8812
+ applyVideoSrc(video, url);
8813
+ }
7536
8814
  }
7537
8815
  });
7538
8816
  if (!found) notify();
@@ -7599,12 +8877,16 @@ function OhhwellsBridge() {
7599
8877
  sectionsJson = val;
7600
8878
  continue;
7601
8879
  }
8880
+ if (applyVideoSettingNode(key, val)) continue;
7602
8881
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
7603
8882
  if (el.dataset.ohwEditable === "image") {
7604
8883
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
7605
- if (img) img.src = val;
8884
+ if (img) applyEditableImageSrc(img, val);
7606
8885
  } else if (el.dataset.ohwEditable === "bg-image") {
7607
8886
  el.style.backgroundImage = `url('${val}')`;
8887
+ } else if (el.dataset.ohwEditable === "video") {
8888
+ const video = getVideoEl(el);
8889
+ if (video && video.src !== val) applyVideoSrc(video, val);
7608
8890
  } else if (el.dataset.ohwEditable === "link") {
7609
8891
  applyLinkHref(el, val);
7610
8892
  } else {
@@ -7618,6 +8900,8 @@ function OhhwellsBridge() {
7618
8900
  sectionsLoadedRef.current = true;
7619
8901
  pendingScheduleConfigRequests.current.splice(0).forEach(processConfigRequest);
7620
8902
  }
8903
+ editContentRef.current = { ...editContentRef.current, ...content };
8904
+ reconcileNavbarItemsFromContent(editContentRef.current);
7621
8905
  enforceLinkHrefs();
7622
8906
  postToParentRef.current({ type: "ow:hydrate-done" });
7623
8907
  };
@@ -7625,6 +8909,7 @@ function OhhwellsBridge() {
7625
8909
  const handleDeactivate = (e) => {
7626
8910
  if (e.data?.type !== "ow:deactivate") return;
7627
8911
  if (Date.now() < linkPopoverGraceUntilRef.current) return;
8912
+ if (document.querySelector("[data-ohw-section-picker]")) return;
7628
8913
  if (linkPopoverOpenRef.current) {
7629
8914
  setLinkPopoverRef.current(null);
7630
8915
  return;
@@ -7634,12 +8919,32 @@ function OhhwellsBridge() {
7634
8919
  };
7635
8920
  window.addEventListener("message", handleDeactivate);
7636
8921
  const handleKeyDown = (e) => {
8922
+ if (e.key === "Escape" && document.querySelector("[data-ohw-section-picker]")) return;
8923
+ if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "a" && activeElRef.current) {
8924
+ const navAnchor = getNavigationItemAnchor(activeElRef.current);
8925
+ if (navAnchor) {
8926
+ e.preventDefault();
8927
+ selectAllTextInEditable(activeElRef.current);
8928
+ refreshActiveCommandsRef.current();
8929
+ return;
8930
+ }
8931
+ }
7637
8932
  if (e.key === "Escape" && linkPopoverOpenRef.current) {
7638
8933
  setLinkPopoverRef.current(null);
7639
8934
  return;
7640
8935
  }
7641
8936
  if (e.key === "Escape" && selectedElRef.current && !activeElRef.current) {
7642
- deselectRef.current();
8937
+ if (toolbarVariantRef.current === "select-frame") {
8938
+ deselectRef.current();
8939
+ return;
8940
+ }
8941
+ const parent = getNavigationSelectionParent(selectedElRef.current);
8942
+ if (parent) {
8943
+ e.preventDefault();
8944
+ selectFrameRef.current(parent);
8945
+ } else {
8946
+ deselectRef.current();
8947
+ }
7643
8948
  return;
7644
8949
  }
7645
8950
  if (e.key === "Escape" && activeElRef.current) {
@@ -7697,18 +9002,18 @@ function OhhwellsBridge() {
7697
9002
  if (hoveredItemElRef.current) {
7698
9003
  setHoveredItemRect(hoveredItemElRef.current.getBoundingClientRect());
7699
9004
  }
9005
+ if (hoveredNavContainerRef.current) {
9006
+ setHoveredNavContainerRect(hoveredNavContainerRef.current.getBoundingClientRect());
9007
+ }
7700
9008
  if (siblingHintElRef.current) {
7701
9009
  setSiblingHintRect(siblingHintElRef.current.getBoundingClientRect());
7702
9010
  }
7703
9011
  if (hoveredImageRef.current) {
7704
9012
  const el = hoveredImageRef.current;
7705
9013
  const r2 = el.getBoundingClientRect();
7706
- postToParentRef.current({
7707
- type: "ow:image-hover",
7708
- key: el.dataset.ohwKey ?? "",
7709
- rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height },
7710
- hasTextOverlap: hoveredImageHasTextOverlapRef.current
7711
- });
9014
+ setMediaHover(
9015
+ (prev) => prev ? { ...prev, rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height } } : prev
9016
+ );
7712
9017
  }
7713
9018
  };
7714
9019
  const handleSave = (e) => {
@@ -7797,19 +9102,39 @@ function OhhwellsBridge() {
7797
9102
  refreshActiveCommandsRef.current = handleSelectionChange;
7798
9103
  const handleDocMouseLeave = () => {
7799
9104
  hoveredImageRef.current = null;
9105
+ setMediaHover(null);
7800
9106
  document.querySelectorAll("[data-ohw-state-hovered]").forEach((el) => el.removeAttribute("data-ohw-state-hovered"));
7801
9107
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
7802
9108
  activeStateElRef.current = null;
7803
9109
  setToggleState(null);
7804
9110
  };
7805
- const handleAnimLock = (e) => {
7806
- if (e.data?.type !== "ow:anim-lock") return;
7807
- const { key } = e.data;
9111
+ const handleImageUploading = (e) => {
9112
+ if (e.data?.type !== "ow:image-uploading") return;
9113
+ const { key, uploading } = e.data;
9114
+ setUploadingRects((prev) => {
9115
+ if (!uploading) {
9116
+ if (!(key in prev)) return prev;
9117
+ const next = { ...prev };
9118
+ delete next[key];
9119
+ return next;
9120
+ }
9121
+ const el = document.querySelector(`[data-ohw-key="${key}"]`);
9122
+ if (!el) return prev;
9123
+ const r2 = getVisibleRect(el);
9124
+ return {
9125
+ ...prev,
9126
+ [key]: { rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height } }
9127
+ };
9128
+ });
7808
9129
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
7809
9130
  const track = el.closest("[data-ohw-hover-pause]");
7810
- if (track) {
9131
+ if (!track) return;
9132
+ if (uploading) {
7811
9133
  uploadLockedTracks.add(track);
7812
9134
  track.setAttribute("data-ohw-hover-paused", "");
9135
+ } else {
9136
+ uploadLockedTracks.delete(track);
9137
+ track.removeAttribute("data-ohw-hover-paused");
7813
9138
  }
7814
9139
  });
7815
9140
  };
@@ -7851,7 +9176,7 @@ function OhhwellsBridge() {
7851
9176
  if (e.data?.type !== "ow:click-at") return;
7852
9177
  const { clientX, clientY } = e.data;
7853
9178
  const allImages = Array.from(
7854
- document.querySelectorAll('[data-ohw-editable="image"], [data-ohw-editable="bg-image"]')
9179
+ document.querySelectorAll(MEDIA_SELECTOR)
7855
9180
  ).filter((el) => !!el.closest("[data-ohw-editable-state]"));
7856
9181
  const stateCardImage = allImages.find((el) => {
7857
9182
  const ownerCard = el.closest("[data-ohw-editable-state]");
@@ -7866,21 +9191,55 @@ function OhhwellsBridge() {
7866
9191
  return clientX >= r2.left && clientX <= r2.right && clientY >= r2.top && clientY <= r2.bottom;
7867
9192
  });
7868
9193
  if (stateCardImage) {
7869
- postToParentRef.current({ type: "ow:image-pick", key: stateCardImage.dataset.ohwKey ?? "" });
9194
+ postToParentRef.current({ type: "ow:image-pick", key: stateCardImage.dataset.ohwKey ?? "", elementType: stateCardImage.dataset.ohwEditable ?? "image" });
7870
9195
  return;
7871
9196
  }
7872
9197
  const textEditable = Array.from(
7873
- document.querySelectorAll(
7874
- '[data-ohw-editable]:not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"])'
7875
- )
9198
+ document.querySelectorAll(NON_MEDIA_SELECTOR)
7876
9199
  ).find((el) => {
7877
9200
  const r2 = el.getBoundingClientRect();
7878
9201
  return clientX >= r2.left && clientX <= r2.right && clientY >= r2.top && clientY <= r2.bottom;
7879
9202
  });
7880
9203
  if (textEditable) {
7881
- activateRef.current(textEditable);
9204
+ const hrefCtx = getHrefKeyFromElement(textEditable);
9205
+ const navAnchor = hrefCtx ? getNavigationItemAnchor(hrefCtx.anchor) : null;
9206
+ if (navAnchor) {
9207
+ if (selectedElRef.current === navAnchor) {
9208
+ activateRef.current(textEditable, { caretX: clientX, caretY: clientY });
9209
+ } else {
9210
+ selectRef.current(navAnchor);
9211
+ }
9212
+ return;
9213
+ }
9214
+ activateRef.current(textEditable, { caretX: clientX, caretY: clientY });
7882
9215
  return;
7883
9216
  }
9217
+ const navContainer = document.querySelector("[data-ohw-nav-container]");
9218
+ if (navContainer) {
9219
+ const r2 = navContainer.getBoundingClientRect();
9220
+ const pad = 8;
9221
+ const over = clientX >= r2.left - pad && clientX <= r2.right + pad && clientY >= r2.top - pad && clientY <= r2.bottom + pad;
9222
+ if (over && !isPointOverNavItem(navContainer, clientX, clientY)) {
9223
+ selectFrameRef.current(navContainer);
9224
+ return;
9225
+ }
9226
+ }
9227
+ const navRoot = document.querySelector("[data-ohw-nav-root]");
9228
+ if (navRoot && navContainer) {
9229
+ const rr = navRoot.getBoundingClientRect();
9230
+ if (clientX >= rr.left && clientX <= rr.right && clientY >= rr.top && clientY <= rr.bottom && !isPointOverNavItem(navContainer, clientX, clientY)) {
9231
+ const book = navRoot.querySelector('[data-ohw-role="navbar-button"]');
9232
+ if (book) {
9233
+ const br = book.getBoundingClientRect();
9234
+ if (clientX >= br.left && clientX <= br.right && clientY >= br.top && clientY <= br.bottom) {
9235
+ deactivateRef.current();
9236
+ return;
9237
+ }
9238
+ }
9239
+ selectFrameRef.current(navContainer);
9240
+ return;
9241
+ }
9242
+ }
7884
9243
  deactivateRef.current();
7885
9244
  };
7886
9245
  window.addEventListener("message", handleSave);
@@ -7890,7 +9249,7 @@ function OhhwellsBridge() {
7890
9249
  window.addEventListener("message", handleClearSchedulingWidget);
7891
9250
  window.addEventListener("message", handleRemoveSchedulingSection);
7892
9251
  window.addEventListener("message", handleImageUrl);
7893
- window.addEventListener("message", handleAnimLock);
9252
+ window.addEventListener("message", handleImageUploading);
7894
9253
  window.addEventListener("message", handleCanvasHeight);
7895
9254
  window.addEventListener("message", handleParentScroll);
7896
9255
  window.addEventListener("message", handlePointerSync);
@@ -7902,6 +9261,7 @@ function OhhwellsBridge() {
7902
9261
  };
7903
9262
  window.addEventListener("resize", handleViewportResize, { passive: true });
7904
9263
  document.addEventListener("click", handleClick, true);
9264
+ document.addEventListener("dblclick", handleDblClick, true);
7905
9265
  document.addEventListener("paste", handlePaste, true);
7906
9266
  document.addEventListener("input", handleInput, true);
7907
9267
  document.addEventListener("mouseover", handleMouseOver, true);
@@ -7916,6 +9276,7 @@ function OhhwellsBridge() {
7916
9276
  window.addEventListener("scroll", handleScroll, true);
7917
9277
  return () => {
7918
9278
  document.removeEventListener("click", handleClick, true);
9279
+ document.removeEventListener("dblclick", handleDblClick, true);
7919
9280
  document.removeEventListener("paste", handlePaste, true);
7920
9281
  document.removeEventListener("input", handleInput, true);
7921
9282
  document.removeEventListener("mouseover", handleMouseOver, true);
@@ -7935,7 +9296,7 @@ function OhhwellsBridge() {
7935
9296
  window.removeEventListener("message", handleClearSchedulingWidget);
7936
9297
  window.removeEventListener("message", handleRemoveSchedulingSection);
7937
9298
  window.removeEventListener("message", handleImageUrl);
7938
- window.removeEventListener("message", handleAnimLock);
9299
+ window.removeEventListener("message", handleImageUploading);
7939
9300
  window.removeEventListener("message", handleCanvasHeight);
7940
9301
  window.removeEventListener("message", handleParentScroll);
7941
9302
  window.removeEventListener("resize", handleViewportResize);
@@ -7949,7 +9310,7 @@ function OhhwellsBridge() {
7949
9310
  if (imageShowTimerRef.current) clearTimeout(imageShowTimerRef.current);
7950
9311
  };
7951
9312
  }, [isEditMode, refreshStateRules]);
7952
- (0, import_react6.useEffect)(() => {
9313
+ (0, import_react8.useEffect)(() => {
7953
9314
  const handler = (e) => {
7954
9315
  if (e.data?.type !== "ow:request-schedule-config") return;
7955
9316
  const insertAfterVal = e.data.insertAfter;
@@ -7965,7 +9326,7 @@ function OhhwellsBridge() {
7965
9326
  window.addEventListener("message", handler);
7966
9327
  return () => window.removeEventListener("message", handler);
7967
9328
  }, [processConfigRequest]);
7968
- (0, import_react6.useEffect)(() => {
9329
+ (0, import_react8.useEffect)(() => {
7969
9330
  if (!isEditMode) return;
7970
9331
  document.querySelectorAll("[data-ohw-active-state]").forEach((el) => {
7971
9332
  el.removeAttribute("data-ohw-active-state");
@@ -7986,27 +9347,27 @@ function OhhwellsBridge() {
7986
9347
  const next = { ...prev, [pathKey]: sections };
7987
9348
  return shouldUseDevFixtures() ? { ...DEV_SECTIONS_BY_PATH, ...next } : next;
7988
9349
  });
7989
- postToParent({ type: "ow:ready", version: "1", path: pathname, nodes: collectEditableNodes(), sections });
7990
- postToParent({ type: "ow:request-site-pages" });
9350
+ postToParent2({ type: "ow:ready", version: "1", path: pathname, nodes: collectEditableNodes(), sections });
9351
+ postToParent2({ type: "ow:request-site-pages" });
7991
9352
  }, 150);
7992
9353
  return () => {
7993
9354
  cancelAnimationFrame(raf);
7994
9355
  clearTimeout(timer);
7995
9356
  };
7996
- }, [pathname, isEditMode, refreshStateRules, postToParent]);
7997
- (0, import_react6.useEffect)(() => {
9357
+ }, [pathname, isEditMode, refreshStateRules, postToParent2]);
9358
+ (0, import_react8.useEffect)(() => {
7998
9359
  scrollToHashSectionWhenReady();
7999
9360
  const onHashChange = () => scrollToHashSectionWhenReady();
8000
9361
  window.addEventListener("hashchange", onHashChange);
8001
9362
  return () => window.removeEventListener("hashchange", onHashChange);
8002
9363
  }, [pathname]);
8003
- const handleCommand = (0, import_react6.useCallback)((cmd) => {
9364
+ const handleCommand = (0, import_react8.useCallback)((cmd) => {
8004
9365
  document.execCommand(cmd, false);
8005
9366
  activeElRef.current?.focus();
8006
9367
  if (activeElRef.current) setToolbarRect(getEditMeasureEl(activeElRef.current).getBoundingClientRect());
8007
9368
  refreshActiveCommandsRef.current();
8008
9369
  }, []);
8009
- const handleStateChange = (0, import_react6.useCallback)((state) => {
9370
+ const handleStateChange = (0, import_react8.useCallback)((state) => {
8010
9371
  if (!activeStateElRef.current) return;
8011
9372
  const el = activeStateElRef.current;
8012
9373
  if (state === "Default") {
@@ -8019,18 +9380,22 @@ function OhhwellsBridge() {
8019
9380
  }
8020
9381
  setToggleState((prev) => prev ? { ...prev, activeState: state } : null);
8021
9382
  }, [deactivate]);
8022
- const closeLinkPopover = (0, import_react6.useCallback)(() => setLinkPopover(null), []);
8023
- const openLinkPopoverForActive = (0, import_react6.useCallback)(() => {
9383
+ const closeLinkPopover = (0, import_react8.useCallback)(() => {
9384
+ addNavAfterAnchorRef.current = null;
9385
+ setLinkPopover(null);
9386
+ }, []);
9387
+ const openLinkPopoverForActive = (0, import_react8.useCallback)(() => {
8024
9388
  const hrefCtx = getHrefKeyFromElement(activeElRef.current);
8025
9389
  if (!hrefCtx) return;
8026
9390
  bumpLinkPopoverGrace();
8027
9391
  setLinkPopover({
8028
9392
  key: hrefCtx.key,
8029
- target: getLinkHref(hrefCtx.anchor)
9393
+ mode: "edit",
9394
+ target: getLinkHref2(hrefCtx.anchor)
8030
9395
  });
8031
9396
  deactivate();
8032
9397
  }, [deactivate]);
8033
- const openLinkPopoverForSelected = (0, import_react6.useCallback)(() => {
9398
+ const openLinkPopoverForSelected = (0, import_react8.useCallback)(() => {
8034
9399
  const anchor = selectedElRef.current;
8035
9400
  if (!anchor) return;
8036
9401
  const key = anchor.getAttribute("data-ohw-href-key");
@@ -8038,51 +9403,156 @@ function OhhwellsBridge() {
8038
9403
  bumpLinkPopoverGrace();
8039
9404
  setLinkPopover({
8040
9405
  key,
8041
- target: getLinkHref(anchor)
9406
+ mode: "edit",
9407
+ target: getLinkHref2(anchor)
8042
9408
  });
8043
9409
  deselect();
8044
9410
  }, [deselect]);
8045
- const handleLinkPopoverSubmit = (0, import_react6.useCallback)(
9411
+ const handleLinkPopoverSubmit = (0, import_react8.useCallback)(
8046
9412
  (target) => {
8047
- if (!linkPopover) return;
8048
- const { key } = linkPopover;
9413
+ const session = linkPopoverSessionRef.current;
9414
+ if (!session) return;
9415
+ if (session.intent === "add-nav") {
9416
+ const { pageRoute, sectionId } = parseTarget(target);
9417
+ const page = resolvePage(pageRoute, sitePages);
9418
+ const pageSections = getSectionsForPath(sectionsByPath, pageRoute);
9419
+ const section = sectionId ? pageSections.find((s) => s.id === sectionId) : void 0;
9420
+ const label = section?.label ?? page.title;
9421
+ const afterAnchor = addNavAfterAnchorRef.current;
9422
+ const { anchor, hrefKey, labelKey, index, order } = insertNavbarItem(target, label, afterAnchor);
9423
+ applyLinkByKey(hrefKey, target);
9424
+ document.querySelectorAll(`[data-ohw-key="${labelKey}"]`).forEach((el) => {
9425
+ el.textContent = label;
9426
+ });
9427
+ const navCount = String(index + 1);
9428
+ const orderJson = JSON.stringify(order);
9429
+ editContentRef.current = {
9430
+ ...editContentRef.current,
9431
+ [hrefKey]: target,
9432
+ [labelKey]: label,
9433
+ [NAV_COUNT_KEY]: navCount,
9434
+ [NAV_ORDER_KEY]: orderJson
9435
+ };
9436
+ postToParent2({
9437
+ type: "ow:change",
9438
+ nodes: [
9439
+ { key: hrefKey, text: target },
9440
+ { key: labelKey, text: label },
9441
+ { key: NAV_COUNT_KEY, text: navCount },
9442
+ { key: NAV_ORDER_KEY, text: orderJson }
9443
+ ]
9444
+ });
9445
+ addNavAfterAnchorRef.current = null;
9446
+ setLinkPopover(null);
9447
+ enforceLinkHrefs();
9448
+ requestAnimationFrame(() => {
9449
+ selectRef.current(anchor);
9450
+ });
9451
+ return;
9452
+ }
9453
+ const { key } = session;
8049
9454
  applyLinkByKey(key, target);
8050
- postToParent({ type: "ow:change", nodes: [{ key, text: target }] });
9455
+ postToParent2({ type: "ow:change", nodes: [{ key, text: target }] });
8051
9456
  setLinkPopover(null);
8052
9457
  },
8053
- [linkPopover, postToParent]
9458
+ [postToParent2, sitePages, sectionsByPath]
8054
9459
  );
8055
9460
  const showEditLink = toolbarShowEditLink;
8056
9461
  const currentSections = sectionsByPath[pathname] ?? [];
8057
9462
  linkPopoverOpenRef.current = linkPopover !== null;
8058
- return bridgeRoot ? (0, import_react_dom2.createPortal)(
8059
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
8060
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
8061
- siblingHintRect && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
8062
- hoveredItemRect && !siblingHintRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
8063
- toolbarRect && toolbarVariant === "link-action" && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
9463
+ const handleMediaReplace = (0, import_react8.useCallback)(
9464
+ (key) => {
9465
+ postToParent2({ type: "ow:image-pick", key, elementType: mediaHover?.elementType ?? "image" });
9466
+ },
9467
+ [postToParent2, mediaHover?.elementType]
9468
+ );
9469
+ const handleMediaFadeOutComplete = (0, import_react8.useCallback)((key) => {
9470
+ setUploadingRects((prev) => {
9471
+ if (!(key in prev)) return prev;
9472
+ const next = { ...prev };
9473
+ delete next[key];
9474
+ return next;
9475
+ });
9476
+ }, []);
9477
+ const handleVideoSettingsChange = (0, import_react8.useCallback)(
9478
+ (key, settings) => {
9479
+ document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
9480
+ const video = getVideoEl(el);
9481
+ if (!video) return;
9482
+ if (typeof settings.autoplay === "boolean") video.autoplay = settings.autoplay;
9483
+ if (typeof settings.muted === "boolean") video.muted = settings.muted;
9484
+ syncVideoPlayback(video);
9485
+ postToParent2({
9486
+ type: "ow:change",
9487
+ nodes: [
9488
+ { key: `${key}${VIDEO_AUTOPLAY_SUFFIX}`, text: String(video.autoplay) },
9489
+ { key: `${key}${VIDEO_MUTED_SUFFIX}`, text: String(video.muted) }
9490
+ ]
9491
+ });
9492
+ setMediaHover(
9493
+ (prev) => prev && prev.key === key ? { ...prev, videoAutoplay: video.autoplay, videoMuted: video.muted } : prev
9494
+ );
9495
+ });
9496
+ },
9497
+ [postToParent2]
9498
+ );
9499
+ return bridgeRoot ? (0, import_react_dom3.createPortal)(
9500
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
9501
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
9502
+ Object.entries(uploadingRects).map(([key, { rect, fadingOut }]) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
9503
+ MediaOverlay,
9504
+ {
9505
+ hover: { key, rect, elementType: "image", isDragOver: false, hasTextOverlap: false },
9506
+ isUploading: true,
9507
+ fadingOut,
9508
+ onFadeOutComplete: handleMediaFadeOutComplete,
9509
+ onReplace: handleMediaReplace
9510
+ },
9511
+ `uploading-${key}`
9512
+ )),
9513
+ mediaHover && !(mediaHover.key in uploadingRects) && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
9514
+ MediaOverlay,
9515
+ {
9516
+ hover: mediaHover,
9517
+ isUploading: false,
9518
+ onReplace: handleMediaReplace,
9519
+ onVideoSettingsChange: handleVideoSettingsChange
9520
+ }
9521
+ ),
9522
+ siblingHintRect && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
9523
+ hoveredNavContainerRect && toolbarVariant !== "select-frame" && !linkPopover && !isItemDragging && !siblingHintRect && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
9524
+ toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
9525
+ NavbarContainerChrome,
9526
+ {
9527
+ rect: toolbarRect,
9528
+ onAdd: handleAddTopLevelNavItem
9529
+ }
9530
+ ),
9531
+ hoveredItemRect && !hoveredNavContainerRect && !siblingHintRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
9532
+ toolbarRect && (toolbarVariant === "link-action" || toolbarVariant === "select-frame") && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8064
9533
  ItemInteractionLayer,
8065
9534
  {
8066
9535
  rect: toolbarRect,
8067
9536
  elRef: glowElRef,
8068
9537
  state: isItemDragging ? "dragging" : resolveItemInteractionState(toolbarRect, parentScrollRef.current),
8069
- showHandle: Boolean(reorderHrefKey),
9538
+ showHandle: toolbarVariant === "link-action" && Boolean(reorderHrefKey),
8070
9539
  dragDisabled: reorderDragDisabled,
8071
9540
  dragHandleLabel: reorderHrefKey ? `Reorder ${reorderHrefKey}` : "Reorder item",
8072
9541
  onDragHandleDragStart: handleItemDragStart,
8073
9542
  onDragHandleDragEnd: handleItemDragEnd,
8074
- toolbar: isItemDragging ? void 0 : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
9543
+ toolbar: toolbarVariant === "link-action" && !isItemDragging ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8075
9544
  ItemActionToolbar,
8076
9545
  {
8077
9546
  onEditLink: openLinkPopoverForSelected,
8078
- onAddItem: handleAddNavigationItem,
8079
- addItemDisabled: false
9547
+ addItemDisabled: true,
9548
+ editLinkDisabled: false,
9549
+ moreDisabled: true
8080
9550
  }
8081
- )
9551
+ ) : void 0
8082
9552
  }
8083
9553
  ),
8084
- toolbarRect && toolbarVariant === "rich-text" && /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
8085
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
9554
+ toolbarRect && toolbarVariant === "rich-text" && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
9555
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8086
9556
  EditGlowChrome,
8087
9557
  {
8088
9558
  rect: toolbarRect,
@@ -8091,7 +9561,7 @@ function OhhwellsBridge() {
8091
9561
  dragDisabled: reorderDragDisabled
8092
9562
  }
8093
9563
  ),
8094
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
9564
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8095
9565
  FloatingToolbar,
8096
9566
  {
8097
9567
  rect: toolbarRect,
@@ -8104,7 +9574,7 @@ function OhhwellsBridge() {
8104
9574
  }
8105
9575
  )
8106
9576
  ] }),
8107
- maxBadge && /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
9577
+ maxBadge && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
8108
9578
  "div",
8109
9579
  {
8110
9580
  "data-ohw-max-badge": "",
@@ -8130,7 +9600,7 @@ function OhhwellsBridge() {
8130
9600
  ]
8131
9601
  }
8132
9602
  ),
8133
- toggleState && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
9603
+ toggleState && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8134
9604
  StateToggle,
8135
9605
  {
8136
9606
  rect: toggleState.rect,
@@ -8139,15 +9609,15 @@ function OhhwellsBridge() {
8139
9609
  onStateChange: handleStateChange
8140
9610
  }
8141
9611
  ),
8142
- sectionGap && /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
9612
+ sectionGap && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
8143
9613
  "div",
8144
9614
  {
8145
9615
  "data-ohw-section-insert-line": "",
8146
9616
  className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
8147
9617
  style: { top: sectionGap.y, transform: "translateY(-50%)" },
8148
9618
  children: [
8149
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
8150
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
9619
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
9620
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8151
9621
  Badge,
8152
9622
  {
8153
9623
  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",
@@ -8160,26 +9630,52 @@ function OhhwellsBridge() {
8160
9630
  children: "Add Section"
8161
9631
  }
8162
9632
  ),
8163
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
9633
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
8164
9634
  ]
8165
9635
  }
8166
9636
  ),
8167
- linkPopover && dialogPortalContainer ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
9637
+ linkPopover && dialogPortalContainer ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8168
9638
  LinkPopover,
8169
9639
  {
8170
9640
  panelRef: linkPopoverPanelRef,
8171
9641
  portalContainer: dialogPortalContainer,
8172
9642
  open: true,
8173
- mode: "edit",
9643
+ mode: linkPopover.mode ?? "edit",
8174
9644
  pages: sitePages,
8175
9645
  sections: currentSections,
8176
9646
  sectionsByPath,
8177
9647
  initialTarget: linkPopover.target,
9648
+ existingTargets: linkPopover.intent === "add-nav" ? getNavbarExistingTargets() : [],
8178
9649
  onClose: closeLinkPopover,
8179
9650
  onSubmit: handleLinkPopoverSubmit
8180
9651
  },
8181
- `${linkPopover.key}-${pathname}`
8182
- ) : null
9652
+ linkPopover.key
9653
+ ) : null,
9654
+ sectionGap && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
9655
+ "div",
9656
+ {
9657
+ "data-ohw-section-insert-line": "",
9658
+ className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
9659
+ style: { top: sectionGap.y, transform: "translateY(-50%)" },
9660
+ children: [
9661
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
9662
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
9663
+ Badge,
9664
+ {
9665
+ 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",
9666
+ onClick: () => {
9667
+ window.parent.postMessage(
9668
+ { type: "ow:add-section", insertAfter: sectionGap.insertAfter, insertBefore: sectionGap.insertBefore },
9669
+ "*"
9670
+ );
9671
+ },
9672
+ children: "Add Section"
9673
+ }
9674
+ ),
9675
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
9676
+ ]
9677
+ }
9678
+ )
8183
9679
  ] }),
8184
9680
  bridgeRoot
8185
9681
  ) : null;