@ohhwells/bridge 0.1.37 → 0.1.38-next.56

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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) {
@@ -5807,9 +6694,9 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
5807
6694
  tail.insertAdjacentElement("afterend", container);
5808
6695
  }
5809
6696
  const root = (0, import_client.createRoot)(container);
5810
- (0, import_react_dom.flushSync)(() => {
6697
+ (0, import_react_dom2.flushSync)(() => {
5811
6698
  root.render(
5812
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
6699
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
5813
6700
  SchedulingWidget,
5814
6701
  {
5815
6702
  notifyOnConnect,
@@ -5849,7 +6736,7 @@ function mountSchedulingEntries(entries, notifyOnConnect = false) {
5849
6736
  }
5850
6737
  }
5851
6738
  }
5852
- function getLinkHref(el) {
6739
+ function getLinkHref2(el) {
5853
6740
  const anchor = el instanceof HTMLAnchorElement ? el : el.querySelector("a");
5854
6741
  return anchor?.getAttribute("href") ?? "";
5855
6742
  }
@@ -5882,8 +6769,11 @@ function collectEditableNodes() {
5882
6769
  const url = raw.replace(/^url\(['"]?/, "").replace(/['"]?\)$/, "");
5883
6770
  return { key: el.dataset.ohwKey ?? "", type: "bg-image", text: url };
5884
6771
  }
6772
+ if (el.dataset.ohwEditable === "video") {
6773
+ return { key: el.dataset.ohwKey ?? "", type: "video", text: getVideoEl(el)?.src ?? "" };
6774
+ }
5885
6775
  if (el.dataset.ohwEditable === "link") {
5886
- return { key: el.dataset.ohwKey ?? "", type: "link", text: getLinkHref(el) };
6776
+ return { key: el.dataset.ohwKey ?? "", type: "link", text: getLinkHref2(el) };
5887
6777
  }
5888
6778
  return {
5889
6779
  key: el.dataset.ohwKey ?? "",
@@ -5894,9 +6784,79 @@ function collectEditableNodes() {
5894
6784
  document.querySelectorAll("[data-ohw-href-key]").forEach((el) => {
5895
6785
  const key = el.getAttribute("data-ohw-href-key") ?? "";
5896
6786
  if (!key) return;
5897
- 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);
5898
6858
  });
5899
- return nodes;
6859
+ return true;
5900
6860
  }
5901
6861
  function applyLinkByKey(key, val) {
5902
6862
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -5910,7 +6870,7 @@ function applyLinkByKey(key, val) {
5910
6870
  }
5911
6871
  function isInsideLinkEditor(target) {
5912
6872
  return Boolean(
5913
- 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"]')
5914
6874
  );
5915
6875
  }
5916
6876
  function getHrefKeyFromElement(el) {
@@ -5921,7 +6881,7 @@ function getHrefKeyFromElement(el) {
5921
6881
  if (!key) return null;
5922
6882
  return { anchor, key };
5923
6883
  }
5924
- function isNavbarButton(el) {
6884
+ function isNavbarButton2(el) {
5925
6885
  return Boolean(el.closest('[data-ohw-role="navbar-button"]'));
5926
6886
  }
5927
6887
  function getNavigationItemAnchor(el) {
@@ -5933,19 +6893,8 @@ function getNavigationItemAnchor(el) {
5933
6893
  function isNavigationItem(el) {
5934
6894
  return getNavigationItemAnchor(el) !== null;
5935
6895
  }
5936
- function getNavigationItemAddHintTarget(anchor) {
5937
- const items = listNavigationItems();
5938
- const idx = items.indexOf(anchor);
5939
- if (idx >= 0 && idx < items.length - 1) return items[idx + 1];
5940
- return anchor;
5941
- }
5942
- function listNavigationItems() {
5943
- return Array.from(
5944
- document.querySelectorAll("nav [data-ohw-href-key], footer [data-ohw-href-key]")
5945
- ).filter((el) => isNavigationItem(el));
5946
- }
5947
6896
  function getNavigationItemReorderState(anchor) {
5948
- if (isNavbarButton(anchor)) return { key: null, disabled: false };
6897
+ if (isNavbarButton2(anchor)) return { key: null, disabled: false };
5949
6898
  return getReorderHandleStateFromAnchor(anchor);
5950
6899
  }
5951
6900
  function getReorderHandleState(el) {
@@ -5967,6 +6916,120 @@ function clearHrefKeyHover(anchor) {
5967
6916
  function isInsideNavigationItem(el) {
5968
6917
  return getNavigationItemAnchor(el) !== null;
5969
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
+ }
5970
7033
  function collectSections() {
5971
7034
  return collectSectionsFromDom();
5972
7035
  }
@@ -6090,6 +7153,8 @@ var ICONS = {
6090
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"/>',
6091
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"/>'
6092
7155
  };
7156
+ var TOOLBAR_PILL_PADDING = 2;
7157
+ var TOOLBAR_TOGGLE_GAP = 4;
6093
7158
  var TOOLBAR_GROUPS = [
6094
7159
  [
6095
7160
  { cmd: "bold", title: "Bold" },
@@ -6114,7 +7179,7 @@ function EditGlowChrome({
6114
7179
  dragDisabled = false
6115
7180
  }) {
6116
7181
  const GAP = 6;
6117
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
7182
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
6118
7183
  "div",
6119
7184
  {
6120
7185
  ref: elRef,
@@ -6129,7 +7194,7 @@ function EditGlowChrome({
6129
7194
  zIndex: 2147483646
6130
7195
  },
6131
7196
  children: [
6132
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7197
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6133
7198
  "div",
6134
7199
  {
6135
7200
  style: {
@@ -6142,7 +7207,7 @@ function EditGlowChrome({
6142
7207
  }
6143
7208
  }
6144
7209
  ),
6145
- reorderHrefKey && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7210
+ reorderHrefKey && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6146
7211
  "div",
6147
7212
  {
6148
7213
  "data-ohw-drag-handle-container": "",
@@ -6154,7 +7219,7 @@ function EditGlowChrome({
6154
7219
  transform: "translate(calc(-100% - 7px), -50%)",
6155
7220
  pointerEvents: dragDisabled ? "none" : "auto"
6156
7221
  },
6157
- children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7222
+ children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6158
7223
  DragHandle,
6159
7224
  {
6160
7225
  "aria-label": `Reorder ${reorderHrefKey}`,
@@ -6258,7 +7323,7 @@ function FloatingToolbar({
6258
7323
  onEditLink
6259
7324
  }) {
6260
7325
  const { top, left, transform } = calcToolbarPos(rect, parentScroll, 330);
6261
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7326
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6262
7327
  "div",
6263
7328
  {
6264
7329
  ref: elRef,
@@ -6268,14 +7333,23 @@ function FloatingToolbar({
6268
7333
  left,
6269
7334
  transform,
6270
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",
6271
7345
  pointerEvents: "auto"
6272
7346
  },
6273
- children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(CustomToolbar, { children: [
6274
- TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_react6.default.Fragment, { children: [
6275
- 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, {}),
6276
7350
  btns.map((btn) => {
6277
7351
  const isActive = activeCommands.has(btn.cmd);
6278
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7352
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6279
7353
  CustomToolbarButton,
6280
7354
  {
6281
7355
  title: btn.title,
@@ -6284,7 +7358,7 @@ function FloatingToolbar({
6284
7358
  e.preventDefault();
6285
7359
  onCommand(btn.cmd);
6286
7360
  },
6287
- children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7361
+ children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6288
7362
  "svg",
6289
7363
  {
6290
7364
  width: "16",
@@ -6305,7 +7379,7 @@ function FloatingToolbar({
6305
7379
  );
6306
7380
  })
6307
7381
  ] }, gi)),
6308
- showEditLink ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7382
+ showEditLink ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6309
7383
  CustomToolbarButton,
6310
7384
  {
6311
7385
  type: "button",
@@ -6319,7 +7393,7 @@ function FloatingToolbar({
6319
7393
  e.preventDefault();
6320
7394
  e.stopPropagation();
6321
7395
  },
6322
- 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 })
6323
7397
  }
6324
7398
  ) : null
6325
7399
  ] })
@@ -6336,7 +7410,7 @@ function StateToggle({
6336
7410
  states,
6337
7411
  onStateChange
6338
7412
  }) {
6339
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7413
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6340
7414
  ToggleGroup,
6341
7415
  {
6342
7416
  "data-ohw-state-toggle": "",
@@ -6350,7 +7424,7 @@ function StateToggle({
6350
7424
  left: rect.right - 8,
6351
7425
  transform: "translateX(-100%)"
6352
7426
  },
6353
- 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))
6354
7428
  }
6355
7429
  );
6356
7430
  }
@@ -6373,12 +7447,12 @@ function resolveSubdomain(subdomainFromQuery) {
6373
7447
  return "";
6374
7448
  }
6375
7449
  function OhhwellsBridge() {
6376
- const pathname = (0, import_navigation.usePathname)();
6377
- const router = (0, import_navigation.useRouter)();
6378
- 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)();
6379
7453
  const isEditMode = isEditSessionActive();
6380
- const [bridgeRoot, setBridgeRoot] = (0, import_react6.useState)(null);
6381
- (0, import_react6.useEffect)(() => {
7454
+ const [bridgeRoot, setBridgeRoot] = (0, import_react8.useState)(null);
7455
+ (0, import_react8.useEffect)(() => {
6382
7456
  const figtreeFontId = "ohw-figtree-font";
6383
7457
  if (!document.getElementById(figtreeFontId)) {
6384
7458
  const preconnect1 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.googleapis.com" });
@@ -6386,7 +7460,10 @@ function OhhwellsBridge() {
6386
7460
  const stylesheet = Object.assign(document.createElement("link"), {
6387
7461
  id: figtreeFontId,
6388
7462
  rel: "stylesheet",
6389
- 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"
6390
7467
  });
6391
7468
  document.head.append(preconnect1, preconnect2, stylesheet);
6392
7469
  }
@@ -6403,80 +7480,91 @@ function OhhwellsBridge() {
6403
7480
  const subdomainFromQuery = searchParams.get("subdomain");
6404
7481
  const subdomain = resolveSubdomain(subdomainFromQuery);
6405
7482
  useLinkHrefGuardian(pathname, subdomain, isEditMode);
6406
- const postToParent = (0, import_react6.useCallback)((data) => {
7483
+ const postToParent2 = (0, import_react8.useCallback)((data) => {
6407
7484
  if (typeof window !== "undefined" && window.parent !== window) {
6408
7485
  window.parent.postMessage(data, "*");
6409
7486
  }
6410
7487
  }, []);
6411
- const [fetchState, setFetchState] = (0, import_react6.useState)("idle");
6412
- const autoSaveTimers = (0, import_react6.useRef)(/* @__PURE__ */ new Map());
6413
- const activeElRef = (0, import_react6.useRef)(null);
6414
- const selectedElRef = (0, import_react6.useRef)(null);
6415
- const originalContentRef = (0, import_react6.useRef)(null);
6416
- const activeStateElRef = (0, import_react6.useRef)(null);
6417
- const parentScrollRef = (0, import_react6.useRef)(null);
6418
- const visibleViewportRef = (0, import_react6.useRef)(null);
6419
- const [dialogPortalContainer, setDialogPortalContainer] = (0, import_react6.useState)(null);
6420
- 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) => {
6421
7498
  visibleViewportRef.current = node;
6422
7499
  setDialogPortalContainer(node);
6423
7500
  if (node) applyVisibleViewport(node, parentScrollRef.current);
6424
7501
  }, []);
6425
- const toolbarElRef = (0, import_react6.useRef)(null);
6426
- const glowElRef = (0, import_react6.useRef)(null);
6427
- const hoveredImageRef = (0, import_react6.useRef)(null);
6428
- const hoveredImageHasTextOverlapRef = (0, import_react6.useRef)(false);
6429
- const hoveredGapRef = (0, import_react6.useRef)(null);
6430
- const imageUnhoverTimerRef = (0, import_react6.useRef)(null);
6431
- const imageShowTimerRef = (0, import_react6.useRef)(null);
6432
- const editStylesRef = (0, import_react6.useRef)(null);
6433
- 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)(() => {
6434
7516
  });
6435
- const deactivateRef = (0, import_react6.useRef)(() => {
7517
+ const selectRef = (0, import_react8.useRef)(() => {
6436
7518
  });
6437
- const selectRef = (0, import_react6.useRef)(() => {
7519
+ const selectFrameRef = (0, import_react8.useRef)(() => {
6438
7520
  });
6439
- const deselectRef = (0, import_react6.useRef)(() => {
7521
+ const deselectRef = (0, import_react8.useRef)(() => {
6440
7522
  });
6441
- const reselectNavigationItemRef = (0, import_react6.useRef)(() => {
7523
+ const reselectNavigationItemRef = (0, import_react8.useRef)(() => {
6442
7524
  });
6443
- const commitNavigationTextEditRef = (0, import_react6.useRef)(() => {
7525
+ const commitNavigationTextEditRef = (0, import_react8.useRef)(() => {
6444
7526
  });
6445
- const refreshActiveCommandsRef = (0, import_react6.useRef)(() => {
7527
+ const refreshActiveCommandsRef = (0, import_react8.useRef)(() => {
6446
7528
  });
6447
- const postToParentRef = (0, import_react6.useRef)(postToParent);
6448
- postToParentRef.current = postToParent;
6449
- const sectionsLoadedRef = (0, import_react6.useRef)(false);
6450
- const pendingScheduleConfigRequests = (0, import_react6.useRef)([]);
6451
- const [toolbarRect, setToolbarRect] = (0, import_react6.useState)(null);
6452
- const [toolbarVariant, setToolbarVariant] = (0, import_react6.useState)("none");
6453
- 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");
6454
7536
  toolbarVariantRef.current = toolbarVariant;
6455
- const [reorderHrefKey, setReorderHrefKey] = (0, import_react6.useState)(null);
6456
- const [reorderDragDisabled, setReorderDragDisabled] = (0, import_react6.useState)(false);
6457
- const [toggleState, setToggleState] = (0, import_react6.useState)(null);
6458
- const [maxBadge, setMaxBadge] = (0, import_react6.useState)(null);
6459
- const [activeCommands, setActiveCommands] = (0, import_react6.useState)(/* @__PURE__ */ new Set());
6460
- const [sectionGap, setSectionGap] = (0, import_react6.useState)(null);
6461
- const [toolbarShowEditLink, setToolbarShowEditLink] = (0, import_react6.useState)(false);
6462
- const hoveredItemElRef = (0, import_react6.useRef)(null);
6463
- const [hoveredItemRect, setHoveredItemRect] = (0, import_react6.useState)(null);
6464
- const siblingHintElRef = (0, import_react6.useRef)(null);
6465
- const [siblingHintRect, setSiblingHintRect] = (0, import_react6.useState)(null);
6466
- const [isItemDragging, setIsItemDragging] = (0, import_react6.useState)(false);
6467
- const [linkPopover, setLinkPopover] = (0, import_react6.useState)(null);
6468
- const [sitePages, setSitePages] = (0, import_react6.useState)([]);
6469
- const [sectionsByPath, setSectionsByPath] = (0, import_react6.useState)({});
6470
- const sectionsPrefetchGenRef = (0, import_react6.useRef)(0);
6471
- const setLinkPopoverRef = (0, import_react6.useRef)(setLinkPopover);
6472
- const linkPopoverPanelRef = (0, import_react6.useRef)(null);
6473
- const linkPopoverOpenRef = (0, import_react6.useRef)(false);
6474
- 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);
6475
7562
  setLinkPopoverRef.current = setLinkPopover;
7563
+ linkPopoverSessionRef.current = linkPopover;
6476
7564
  const bumpLinkPopoverGrace = () => {
6477
7565
  linkPopoverGraceUntilRef.current = Date.now() + 350;
6478
7566
  };
6479
- const runSectionsPrefetch = (0, import_react6.useCallback)((pages) => {
7567
+ const runSectionsPrefetch = (0, import_react8.useCallback)((pages) => {
6480
7568
  if (!isEditMode || shouldUseDevFixtures() || pages.length === 0) return;
6481
7569
  const gen = ++sectionsPrefetchGenRef.current;
6482
7570
  const paths = pages.map((p) => p.path);
@@ -6495,9 +7583,9 @@ function OhhwellsBridge() {
6495
7583
  );
6496
7584
  });
6497
7585
  }, [isEditMode, pathname]);
6498
- const runSectionsPrefetchRef = (0, import_react6.useRef)(runSectionsPrefetch);
7586
+ const runSectionsPrefetchRef = (0, import_react8.useRef)(runSectionsPrefetch);
6499
7587
  runSectionsPrefetchRef.current = runSectionsPrefetch;
6500
- (0, import_react6.useEffect)(() => {
7588
+ (0, import_react8.useEffect)(() => {
6501
7589
  if (!linkPopover) return;
6502
7590
  if (hoveredImageRef.current) {
6503
7591
  hoveredImageRef.current = null;
@@ -6505,33 +7593,9 @@ function OhhwellsBridge() {
6505
7593
  }
6506
7594
  hoveredGapRef.current = null;
6507
7595
  setSectionGap(null);
6508
- postToParent({ type: "ow:image-unhover" });
6509
- postToParent({ type: "ow:link-modal-lock", locked: true });
6510
- const html = document.documentElement;
6511
- const body = document.body;
6512
- const prevHtmlOverflow = html.style.overflow;
6513
- const prevBodyOverflow = body.style.overflow;
6514
- html.style.overflow = "hidden";
6515
- body.style.overflow = "hidden";
6516
- const preventBackgroundScroll = (e) => {
6517
- const target = e.target;
6518
- if (target instanceof Element && target.closest('[data-ohw-link-modal-root], [data-slot="dialog-overlay"]')) {
6519
- return;
6520
- }
6521
- e.preventDefault();
6522
- };
6523
- const scrollOpts = { passive: false, capture: true };
6524
- document.addEventListener("wheel", preventBackgroundScroll, scrollOpts);
6525
- document.addEventListener("touchmove", preventBackgroundScroll, scrollOpts);
6526
- return () => {
6527
- postToParent({ type: "ow:link-modal-lock", locked: false });
6528
- html.style.overflow = prevHtmlOverflow;
6529
- body.style.overflow = prevBodyOverflow;
6530
- document.removeEventListener("wheel", preventBackgroundScroll, scrollOpts);
6531
- document.removeEventListener("touchmove", preventBackgroundScroll, scrollOpts);
6532
- };
6533
- }, [linkPopover, postToParent]);
6534
- (0, import_react6.useEffect)(() => {
7596
+ postToParent2({ type: "ow:image-unhover" });
7597
+ }, [linkPopover, postToParent2]);
7598
+ (0, import_react8.useEffect)(() => {
6535
7599
  if (!isEditMode) return;
6536
7600
  const useFixtures = shouldUseDevFixtures();
6537
7601
  if (useFixtures) {
@@ -6552,17 +7616,17 @@ function OhhwellsBridge() {
6552
7616
  runSectionsPrefetchRef.current(mapped);
6553
7617
  };
6554
7618
  window.addEventListener("message", onSitePages);
6555
- if (!useFixtures) postToParent({ type: "ow:request-site-pages" });
7619
+ if (!useFixtures) postToParent2({ type: "ow:request-site-pages" });
6556
7620
  return () => window.removeEventListener("message", onSitePages);
6557
- }, [isEditMode, postToParent]);
6558
- (0, import_react6.useEffect)(() => {
7621
+ }, [isEditMode, postToParent2]);
7622
+ (0, import_react8.useEffect)(() => {
6559
7623
  if (!isEditMode || shouldUseDevFixtures()) return;
6560
7624
  void loadAllSectionsManifest().then((manifest) => {
6561
7625
  if (Object.keys(manifest).length === 0) return;
6562
7626
  setSectionsByPath((prev) => ({ ...manifest, ...prev }));
6563
7627
  });
6564
7628
  }, [isEditMode]);
6565
- (0, import_react6.useEffect)(() => {
7629
+ (0, import_react8.useEffect)(() => {
6566
7630
  const update = () => {
6567
7631
  const el = activeElRef.current ?? selectedElRef.current;
6568
7632
  if (el) setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
@@ -6586,10 +7650,10 @@ function OhhwellsBridge() {
6586
7650
  vvp.removeEventListener("resize", update);
6587
7651
  };
6588
7652
  }, []);
6589
- const refreshStateRules = (0, import_react6.useCallback)(() => {
7653
+ const refreshStateRules = (0, import_react8.useCallback)(() => {
6590
7654
  editStylesRef.current?.forceHover && (editStylesRef.current.forceHover.textContent = collectStateRules());
6591
7655
  }, []);
6592
- const processConfigRequest = (0, import_react6.useCallback)((insertAfterVal) => {
7656
+ const processConfigRequest = (0, import_react8.useCallback)((insertAfterVal) => {
6593
7657
  const tracker = getSectionsTracker();
6594
7658
  let entries = [];
6595
7659
  try {
@@ -6612,7 +7676,7 @@ function OhhwellsBridge() {
6612
7676
  }
6613
7677
  window.postMessage({ type: "ow:schedule-config", insertAfter: insertAfterVal, scheduleId: null }, "*");
6614
7678
  }, [isEditMode]);
6615
- const deactivate = (0, import_react6.useCallback)(() => {
7679
+ const deactivate = (0, import_react8.useCallback)(() => {
6616
7680
  const el = activeElRef.current;
6617
7681
  if (!el) return;
6618
7682
  const key = el.dataset.ohwKey;
@@ -6641,21 +7705,23 @@ function OhhwellsBridge() {
6641
7705
  setMaxBadge(null);
6642
7706
  setActiveCommands(/* @__PURE__ */ new Set());
6643
7707
  setToolbarShowEditLink(false);
6644
- postToParent({ type: "ow:exit-edit" });
6645
- }, [postToParent]);
6646
- const deselect = (0, import_react6.useCallback)(() => {
7708
+ postToParent2({ type: "ow:exit-edit" });
7709
+ }, [postToParent2]);
7710
+ const deselect = (0, import_react8.useCallback)(() => {
6647
7711
  selectedElRef.current = null;
6648
7712
  setReorderHrefKey(null);
6649
7713
  setReorderDragDisabled(false);
6650
7714
  siblingHintElRef.current = null;
6651
7715
  setSiblingHintRect(null);
6652
7716
  setIsItemDragging(false);
7717
+ hoveredNavContainerRef.current = null;
7718
+ setHoveredNavContainerRect(null);
6653
7719
  if (!activeElRef.current) {
6654
7720
  setToolbarRect(null);
6655
7721
  setToolbarVariant("none");
6656
7722
  }
6657
7723
  }, []);
6658
- const reselectNavigationItem = (0, import_react6.useCallback)((navAnchor) => {
7724
+ const reselectNavigationItem = (0, import_react8.useCallback)((navAnchor) => {
6659
7725
  selectedElRef.current = navAnchor;
6660
7726
  const { key, disabled } = getNavigationItemReorderState(navAnchor);
6661
7727
  setReorderHrefKey(key);
@@ -6665,7 +7731,7 @@ function OhhwellsBridge() {
6665
7731
  setToolbarShowEditLink(false);
6666
7732
  setActiveCommands(/* @__PURE__ */ new Set());
6667
7733
  }, []);
6668
- const commitNavigationTextEdit = (0, import_react6.useCallback)((navAnchor) => {
7734
+ const commitNavigationTextEdit = (0, import_react8.useCallback)((navAnchor) => {
6669
7735
  const el = activeElRef.current;
6670
7736
  if (!el) return;
6671
7737
  const key = el.dataset.ohwKey;
@@ -6678,9 +7744,9 @@ function OhhwellsBridge() {
6678
7744
  const html = sanitizeHtml(el.innerHTML);
6679
7745
  const original = originalContentRef.current ?? "";
6680
7746
  if (html !== sanitizeHtml(original)) {
6681
- postToParent({ type: "ow:change", nodes: [{ key, text: html }] });
7747
+ postToParent2({ type: "ow:change", nodes: [{ key, text: html }] });
6682
7748
  const h = document.documentElement.scrollHeight;
6683
- if (h > 50) postToParent({ type: "ow:height", height: h });
7749
+ if (h > 50) postToParent2({ type: "ow:height", height: h });
6684
7750
  }
6685
7751
  }
6686
7752
  el.removeAttribute("contenteditable");
@@ -6688,31 +7754,38 @@ function OhhwellsBridge() {
6688
7754
  setMaxBadge(null);
6689
7755
  setActiveCommands(/* @__PURE__ */ new Set());
6690
7756
  setToolbarShowEditLink(false);
6691
- postToParent({ type: "ow:exit-edit" });
7757
+ postToParent2({ type: "ow:exit-edit" });
6692
7758
  reselectNavigationItem(navAnchor);
6693
- }, [postToParent, reselectNavigationItem]);
6694
- const handleAddNavigationItem = (0, import_react6.useCallback)(() => {
6695
- const selected = selectedElRef.current;
6696
- if (!selected) return;
6697
- const target = getNavigationItemAddHintTarget(selected);
6698
- siblingHintElRef.current = target;
6699
- 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
+ });
6700
7771
  }, []);
6701
- const handleItemDragStart = (0, import_react6.useCallback)(() => {
7772
+ const handleItemDragStart = (0, import_react8.useCallback)(() => {
6702
7773
  siblingHintElRef.current = null;
6703
7774
  setSiblingHintRect(null);
6704
7775
  setIsItemDragging(true);
6705
7776
  }, []);
6706
- const handleItemDragEnd = (0, import_react6.useCallback)(() => {
7777
+ const handleItemDragEnd = (0, import_react8.useCallback)(() => {
6707
7778
  setIsItemDragging(false);
6708
7779
  }, []);
6709
7780
  reselectNavigationItemRef.current = reselectNavigationItem;
6710
7781
  commitNavigationTextEditRef.current = commitNavigationTextEdit;
6711
- const select = (0, import_react6.useCallback)((anchor) => {
7782
+ const select = (0, import_react8.useCallback)((anchor) => {
6712
7783
  if (!isNavigationItem(anchor)) return;
6713
7784
  if (activeElRef.current) deactivate();
6714
7785
  selectedElRef.current = anchor;
6715
7786
  clearHrefKeyHover(anchor);
7787
+ hoveredNavContainerRef.current = null;
7788
+ setHoveredNavContainerRect(null);
6716
7789
  setHoveredItemRect(null);
6717
7790
  hoveredItemElRef.current = null;
6718
7791
  siblingHintElRef.current = null;
@@ -6726,13 +7799,32 @@ function OhhwellsBridge() {
6726
7799
  setToolbarShowEditLink(false);
6727
7800
  setActiveCommands(/* @__PURE__ */ new Set());
6728
7801
  }, [deactivate]);
6729
- 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) => {
6730
7822
  if (activeElRef.current === el) return;
6731
7823
  selectedElRef.current = null;
6732
7824
  deactivate();
6733
7825
  if (hoveredImageRef.current) {
6734
7826
  hoveredImageRef.current = null;
6735
- postToParentRef.current({ type: "ow:image-unhover" });
7827
+ setMediaHover(null);
6736
7828
  }
6737
7829
  setToolbarVariant("rich-text");
6738
7830
  siblingHintElRef.current = null;
@@ -6744,6 +7836,9 @@ function OhhwellsBridge() {
6744
7836
  activeElRef.current = el;
6745
7837
  originalContentRef.current = el.innerHTML;
6746
7838
  el.focus();
7839
+ if (options?.caretX !== void 0 && options?.caretY !== void 0) {
7840
+ placeCaretAtPoint(el, options.caretX, options.caretY);
7841
+ }
6747
7842
  setToolbarShowEditLink(Boolean(getHrefKeyFromElement(el)));
6748
7843
  const navAnchor = getNavigationItemAnchor(el);
6749
7844
  if (navAnchor) {
@@ -6755,14 +7850,15 @@ function OhhwellsBridge() {
6755
7850
  setReorderDragDisabled(reorderDisabled);
6756
7851
  }
6757
7852
  setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
6758
- postToParent({ type: "ow:enter-edit", key: el.dataset.ohwKey });
7853
+ postToParent2({ type: "ow:enter-edit", key: el.dataset.ohwKey });
6759
7854
  requestAnimationFrame(() => refreshActiveCommandsRef.current());
6760
- }, [deactivate, postToParent]);
7855
+ }, [deactivate, postToParent2]);
6761
7856
  activateRef.current = activate;
6762
7857
  deactivateRef.current = deactivate;
6763
7858
  selectRef.current = select;
7859
+ selectFrameRef.current = selectFrame;
6764
7860
  deselectRef.current = deselect;
6765
- (0, import_react6.useLayoutEffect)(() => {
7861
+ (0, import_react8.useLayoutEffect)(() => {
6766
7862
  if (!subdomain || isEditMode) {
6767
7863
  setFetchState("done");
6768
7864
  return;
@@ -6771,6 +7867,7 @@ function OhhwellsBridge() {
6771
7867
  const imageLoads = [];
6772
7868
  for (const [key, val] of Object.entries(content)) {
6773
7869
  if (key === "__ohw_sections") continue;
7870
+ if (applyVideoSettingNode(key, val)) continue;
6774
7871
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
6775
7872
  if (el.dataset.ohwEditable === "image") {
6776
7873
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -6784,6 +7881,15 @@ function OhhwellsBridge() {
6784
7881
  } else if (el.dataset.ohwEditable === "bg-image") {
6785
7882
  const next = `url('${val}')`;
6786
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
+ }
6787
7893
  } else if (el.dataset.ohwEditable === "link") {
6788
7894
  applyLinkHref(el, val);
6789
7895
  } else if (el.innerHTML !== val) {
@@ -6792,6 +7898,7 @@ function OhhwellsBridge() {
6792
7898
  });
6793
7899
  applyLinkByKey(key, val);
6794
7900
  }
7901
+ reconcileNavbarItemsFromContent(content);
6795
7902
  enforceLinkHrefs();
6796
7903
  initSectionsFromContent(content, true);
6797
7904
  sectionsLoadedRef.current = true;
@@ -6820,7 +7927,7 @@ function OhhwellsBridge() {
6820
7927
  cancelled = true;
6821
7928
  };
6822
7929
  }, [subdomain, isEditMode]);
6823
- (0, import_react6.useEffect)(() => {
7930
+ (0, import_react8.useEffect)(() => {
6824
7931
  if (!subdomain || isEditMode) return;
6825
7932
  let debounceTimer = null;
6826
7933
  let observer = null;
@@ -6832,6 +7939,7 @@ function OhhwellsBridge() {
6832
7939
  try {
6833
7940
  for (const [key, val] of Object.entries(content)) {
6834
7941
  if (key === "__ohw_sections") continue;
7942
+ if (applyVideoSettingNode(key, val)) continue;
6835
7943
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
6836
7944
  if (el.dataset.ohwEditable === "image") {
6837
7945
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -6839,6 +7947,9 @@ function OhhwellsBridge() {
6839
7947
  } else if (el.dataset.ohwEditable === "bg-image") {
6840
7948
  const next = `url('${val}')`;
6841
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);
6842
7953
  } else if (el.dataset.ohwEditable === "link") {
6843
7954
  applyLinkHref(el, val);
6844
7955
  } else if (el.innerHTML !== val) {
@@ -6847,6 +7958,7 @@ function OhhwellsBridge() {
6847
7958
  });
6848
7959
  applyLinkByKey(key, val);
6849
7960
  }
7961
+ reconcileNavbarItemsFromContent(content);
6850
7962
  } finally {
6851
7963
  observer?.observe(document.body, { childList: true, subtree: true });
6852
7964
  }
@@ -6864,26 +7976,44 @@ function OhhwellsBridge() {
6864
7976
  if (debounceTimer) clearTimeout(debounceTimer);
6865
7977
  };
6866
7978
  }, [subdomain, isEditMode, pathname]);
6867
- (0, import_react6.useLayoutEffect)(() => {
7979
+ (0, import_react8.useLayoutEffect)(() => {
6868
7980
  const el = document.getElementById("ohw-loader");
6869
7981
  if (!el) return;
6870
7982
  const visible = Boolean(subdomain) && fetchState !== "done";
6871
7983
  el.style.display = visible ? "flex" : "none";
6872
7984
  }, [subdomain, fetchState]);
6873
- (0, import_react6.useEffect)(() => {
6874
- postToParent({ type: "ow:navigation", path: pathname });
6875
- }, [pathname, postToParent]);
6876
- (0, import_react6.useEffect)(() => {
7985
+ (0, import_react8.useEffect)(() => {
7986
+ postToParent2({ type: "ow:navigation", path: pathname });
7987
+ }, [pathname, postToParent2]);
7988
+ (0, import_react8.useEffect)(() => {
6877
7989
  if (!isEditMode) return;
7990
+ if (linkPopoverSessionRef.current?.intent === "add-nav") return;
7991
+ if (document.querySelector("[data-ohw-section-picker]")) return;
6878
7992
  setLinkPopover(null);
6879
7993
  deselectRef.current();
6880
7994
  deactivateRef.current();
6881
7995
  }, [pathname, isEditMode]);
6882
- (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)(() => {
6883
8013
  if (!isEditMode) return;
6884
8014
  const measure = () => {
6885
8015
  const h = document.body.scrollHeight;
6886
- if (h > 50) postToParent({ type: "ow:height", height: h });
8016
+ if (h > 50) postToParent2({ type: "ow:height", height: h });
6887
8017
  };
6888
8018
  const t1 = setTimeout(measure, 50);
6889
8019
  const t2 = setTimeout(measure, 500);
@@ -6902,8 +8032,8 @@ function OhhwellsBridge() {
6902
8032
  if (resizeTimer) clearTimeout(resizeTimer);
6903
8033
  window.removeEventListener("resize", handleResize);
6904
8034
  };
6905
- }, [pathname, isEditMode, postToParent]);
6906
- (0, import_react6.useEffect)(() => {
8035
+ }, [pathname, isEditMode, postToParent2]);
8036
+ (0, import_react8.useEffect)(() => {
6907
8037
  if (!subdomainFromQuery || isEditMode) return;
6908
8038
  const handleClick = (e) => {
6909
8039
  const anchor = e.target.closest("a");
@@ -6919,7 +8049,7 @@ function OhhwellsBridge() {
6919
8049
  document.addEventListener("click", handleClick, true);
6920
8050
  return () => document.removeEventListener("click", handleClick, true);
6921
8051
  }, [subdomainFromQuery, isEditMode, router]);
6922
- (0, import_react6.useEffect)(() => {
8052
+ (0, import_react8.useEffect)(() => {
6923
8053
  if (!isEditMode) {
6924
8054
  editStylesRef.current?.base.remove();
6925
8055
  editStylesRef.current?.forceHover.remove();
@@ -6942,8 +8072,9 @@ function OhhwellsBridge() {
6942
8072
  [data-ohw-editable] {
6943
8073
  display: block;
6944
8074
  }
6945
- [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; }
6946
8076
  [data-ohw-editable="image"], [data-ohw-editable="image"] *,
8077
+ [data-ohw-editable="video"], [data-ohw-editable="video"] *,
6947
8078
  [data-ohw-editable="bg-image"], [data-ohw-editable="bg-image"] * { cursor: pointer !important; }
6948
8079
  [data-ohw-editable="link"], [data-ohw-editable="link"] * { cursor: pointer !important; }
6949
8080
  [data-ohw-hovered]:not([contenteditable]):not([data-ohw-href-key]) {
@@ -6992,6 +8123,13 @@ function OhhwellsBridge() {
6992
8123
  if (target.closest("[data-ohw-max-badge]")) return;
6993
8124
  if (isInsideLinkEditor(target)) return;
6994
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
+ }
6995
8133
  e.preventDefault();
6996
8134
  e.stopPropagation();
6997
8135
  return;
@@ -7006,14 +8144,15 @@ function OhhwellsBridge() {
7006
8144
  bumpLinkPopoverGrace();
7007
8145
  setLinkPopoverRef.current({
7008
8146
  key: editable.dataset.ohwKey ?? "",
7009
- target: getLinkHref(editable)
8147
+ mode: "edit",
8148
+ target: getLinkHref2(editable)
7010
8149
  });
7011
8150
  return;
7012
8151
  }
7013
- if (editable.dataset.ohwEditable === "image" || editable.dataset.ohwEditable === "bg-image") {
8152
+ if (isMediaEditable(editable)) {
7014
8153
  e.preventDefault();
7015
8154
  e.stopPropagation();
7016
- 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" });
7017
8156
  return;
7018
8157
  }
7019
8158
  const hrefCtx = getHrefKeyFromElement(editable);
@@ -7022,7 +8161,8 @@ function OhhwellsBridge() {
7022
8161
  e.preventDefault();
7023
8162
  e.stopPropagation();
7024
8163
  if (selectedElRef.current === navAnchor) {
7025
- activateRef.current(editable);
8164
+ if (e.detail >= 2) return;
8165
+ activateRef.current(editable, { caretX: e.clientX, caretY: e.clientY });
7026
8166
  return;
7027
8167
  }
7028
8168
  selectRef.current(navAnchor);
@@ -7041,6 +8181,22 @@ function OhhwellsBridge() {
7041
8181
  selectRef.current(hrefAnchor);
7042
8182
  return;
7043
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
+ }
7044
8200
  if (activeElRef.current) {
7045
8201
  const sel = window.getSelection();
7046
8202
  if (sel && !sel.isCollapsed) {
@@ -7066,10 +8222,48 @@ function OhhwellsBridge() {
7066
8222
  deselectRef.current();
7067
8223
  deactivateRef.current();
7068
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
+ };
7069
8247
  const handleMouseOver = (e) => {
7070
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
+ }
7071
8263
  const navAnchor = getNavigationItemAnchor(target);
7072
8264
  if (navAnchor) {
8265
+ hoveredNavContainerRef.current = null;
8266
+ setHoveredNavContainerRect(null);
7073
8267
  const selected2 = selectedElRef.current;
7074
8268
  if (selected2 === navAnchor || selected2?.contains(navAnchor)) return;
7075
8269
  clearHrefKeyHover(navAnchor);
@@ -7081,7 +8275,7 @@ function OhhwellsBridge() {
7081
8275
  if (!editable) return;
7082
8276
  const selected = selectedElRef.current;
7083
8277
  if (selected && (selected === editable || selected.contains(editable))) return;
7084
- if (editable.dataset.ohwEditable !== "image" && editable.dataset.ohwEditable !== "bg-image" && !editable.hasAttribute("contenteditable")) {
8278
+ if (!isMediaEditable(editable) && !editable.hasAttribute("contenteditable")) {
7085
8279
  const hoverTarget = editable.closest("[data-ohw-href-key]") ?? editable;
7086
8280
  if (hoverTarget.hasAttribute("data-ohw-href-key")) {
7087
8281
  clearHrefKeyHover(hoverTarget);
@@ -7094,6 +8288,14 @@ function OhhwellsBridge() {
7094
8288
  };
7095
8289
  const handleMouseOut = (e) => {
7096
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
+ }
7097
8299
  const navAnchor = getNavigationItemAnchor(target);
7098
8300
  if (navAnchor) {
7099
8301
  const related2 = e.relatedTarget instanceof Element ? e.relatedTarget : null;
@@ -7109,7 +8311,7 @@ function OhhwellsBridge() {
7109
8311
  if (!editable) return;
7110
8312
  const related = e.relatedTarget instanceof Element ? e.relatedTarget : null;
7111
8313
  if (related?.closest("[data-ohw-drag-handle-container], [data-ohw-item-interaction]")) return;
7112
- if (editable.dataset.ohwEditable !== "image" && editable.dataset.ohwEditable !== "bg-image") {
8314
+ if (!isMediaEditable(editable)) {
7113
8315
  const hoverTarget = editable.closest("[data-ohw-href-key]") ?? editable;
7114
8316
  if (hoverTarget.hasAttribute("data-ohw-href-key")) {
7115
8317
  if (!related?.closest("[data-ohw-href-key]")) {
@@ -7155,23 +8357,26 @@ function OhhwellsBridge() {
7155
8357
  }
7156
8358
  return { top, left, width: Math.max(0, right - left), height: Math.max(0, bottom - top) };
7157
8359
  };
7158
- const postImageHover = (imgEl, isDragOver = false, forceTextOverlap) => {
8360
+ const showImageHover = (imgEl, isDragOver = false, forceTextOverlap) => {
7159
8361
  const r2 = getVisibleRect(imgEl);
7160
8362
  const hasTextOverlap = forceTextOverlap ?? false;
7161
8363
  hoveredImageHasTextOverlapRef.current = hasTextOverlap;
7162
- postToParentRef.current({
7163
- type: "ow:image-hover",
8364
+ const video = imgEl.dataset.ohwEditable === "video" ? getVideoEl(imgEl) : null;
8365
+ setMediaHover({
7164
8366
  key: imgEl.dataset.ohwKey ?? "",
7165
8367
  rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height },
8368
+ elementType: imgEl.dataset.ohwEditable ?? "image",
7166
8369
  hasTextOverlap,
7167
- ...isDragOver ? { isDragOver: true } : {}
8370
+ isDragOver,
8371
+ ...video ? { videoAutoplay: video.autoplay, videoMuted: video.muted } : {}
7168
8372
  });
7169
8373
  const track = imgEl.closest("[data-ohw-hover-pause]");
7170
8374
  if (track) track.setAttribute("data-ohw-hover-paused", "");
7171
8375
  };
8376
+ const clearImageHover = () => setMediaHover(null);
7172
8377
  const findImageAtPoint = (clientX, clientY, fromParentViewport) => {
7173
8378
  const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
7174
- const images = Array.from(document.querySelectorAll('[data-ohw-editable="image"], [data-ohw-editable="bg-image"]'));
8379
+ const images = Array.from(document.querySelectorAll(MEDIA_SELECTOR));
7175
8380
  const matches = [];
7176
8381
  for (let i = images.length - 1; i >= 0; i--) {
7177
8382
  const el = images[i];
@@ -7187,7 +8392,9 @@ function OhhwellsBridge() {
7187
8392
  if (x >= r2.left && x <= r2.left + r2.width && y >= r2.top && y <= r2.top + r2.height) matches.push(el);
7188
8393
  }
7189
8394
  if (matches.length === 0) return null;
7190
- 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
+ );
7191
8398
  const candidatePool = imageTypeMatches.length > 0 ? imageTypeMatches : matches;
7192
8399
  const smallest = candidatePool.reduce((best, el) => {
7193
8400
  const br = getVisibleRect(best);
@@ -7210,47 +8417,91 @@ function OhhwellsBridge() {
7210
8417
  }
7211
8418
  return smallest;
7212
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
+ };
7213
8471
  const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
7214
8472
  if (linkPopoverOpenRef.current) {
7215
- if (hoveredImageRef.current) {
7216
- hoveredImageRef.current = null;
7217
- hoveredImageHasTextOverlapRef.current = false;
7218
- resumeAnimTracks();
7219
- postToParentRef.current({ type: "ow:image-unhover" });
7220
- }
8473
+ dismissImageHover();
8474
+ return;
8475
+ }
8476
+ const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
8477
+ if (isPointOverNavigation(x, y)) {
8478
+ dismissImageHover();
8479
+ probeNavigationHoverAt(x, y);
7221
8480
  return;
7222
8481
  }
8482
+ hoveredNavContainerRef.current = null;
8483
+ setHoveredNavContainerRect(null);
7223
8484
  const toggleEl = document.querySelector("[data-ohw-state-toggle]");
7224
8485
  if (toggleEl) {
7225
- const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
7226
8486
  const tr = toggleEl.getBoundingClientRect();
7227
8487
  if (x >= tr.left && x <= tr.right && y >= tr.top && y <= tr.bottom) {
7228
- if (hoveredImageRef.current) {
7229
- hoveredImageRef.current = null;
7230
- resumeAnimTracks();
7231
- postToParentRef.current({ type: "ow:image-unhover" });
7232
- }
8488
+ dismissImageHover();
7233
8489
  return;
7234
8490
  }
7235
8491
  }
7236
- if (activeElRef.current) {
7237
- if (hoveredImageRef.current) {
7238
- hoveredImageRef.current = null;
7239
- hoveredImageHasTextOverlapRef.current = false;
7240
- resumeAnimTracks();
7241
- postToParentRef.current({ type: "ow:image-unhover" });
7242
- }
8492
+ const imgEl = findImageAtPoint(clientX, clientY, fromParentViewport);
8493
+ const activeEl = activeElRef.current;
8494
+ if (activeEl && (!imgEl || imgEl.contains(activeEl))) {
8495
+ dismissImageHover();
7243
8496
  return;
7244
8497
  }
7245
- const imgEl = findImageAtPoint(clientX, clientY, fromParentViewport);
7246
8498
  if (imgEl) {
7247
- const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
7248
8499
  const topEl = document.elementFromPoint(x, y);
7249
8500
  if (topEl?.closest('[data-ohw-toolbar], [data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
7250
8501
  if (hoveredImageRef.current) {
7251
8502
  hoveredImageRef.current = null;
7252
8503
  resumeAnimTracks();
7253
- postToParentRef.current({ type: "ow:image-unhover" });
8504
+ clearImageHover();
7254
8505
  }
7255
8506
  return;
7256
8507
  }
@@ -7259,13 +8510,13 @@ function OhhwellsBridge() {
7259
8510
  hoveredImageRef.current = null;
7260
8511
  hoveredImageHasTextOverlapRef.current = false;
7261
8512
  resumeAnimTracks();
7262
- postToParentRef.current({ type: "ow:image-unhover" });
8513
+ clearImageHover();
7263
8514
  }
7264
8515
  return;
7265
8516
  }
7266
8517
  const isStateCardImage = !!imgEl.closest("[data-ohw-editable-state]");
7267
8518
  const textEditable = Array.from(
7268
- document.querySelectorAll('[data-ohw-editable]:not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"])')
8519
+ document.querySelectorAll(NON_MEDIA_SELECTOR)
7269
8520
  ).find((el) => {
7270
8521
  const er = el.getBoundingClientRect();
7271
8522
  return x >= er.left && x <= er.right && y >= er.top && y <= er.bottom;
@@ -7284,14 +8535,14 @@ function OhhwellsBridge() {
7284
8535
  hoveredImageRef.current = null;
7285
8536
  hoveredImageHasTextOverlapRef.current = false;
7286
8537
  resumeAnimTracks();
7287
- postToParentRef.current({ type: "ow:image-unhover" });
8538
+ clearImageHover();
7288
8539
  }
7289
8540
  return;
7290
8541
  }
7291
8542
  if (isStateCardImage) {
7292
8543
  if (imgEl !== hoveredImageRef.current || !hoveredImageHasTextOverlapRef.current) {
7293
8544
  hoveredImageRef.current = imgEl;
7294
- postImageHover(imgEl, isDragOver, true);
8545
+ showImageHover(imgEl, isDragOver, true);
7295
8546
  }
7296
8547
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
7297
8548
  if (textEditable && !isInsideNavigationItem(textEditable)) {
@@ -7303,7 +8554,7 @@ function OhhwellsBridge() {
7303
8554
  hoveredImageRef.current = null;
7304
8555
  hoveredImageHasTextOverlapRef.current = false;
7305
8556
  resumeAnimTracks();
7306
- postToParentRef.current({ type: "ow:image-unhover" });
8557
+ clearImageHover();
7307
8558
  }
7308
8559
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
7309
8560
  if (textEditable && !isInsideNavigationItem(textEditable)) {
@@ -7315,7 +8566,7 @@ function OhhwellsBridge() {
7315
8566
  if (hoveredImageRef.current) {
7316
8567
  hoveredImageRef.current = null;
7317
8568
  resumeAnimTracks();
7318
- postToParentRef.current({ type: "ow:image-unhover" });
8569
+ clearImageHover();
7319
8570
  }
7320
8571
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
7321
8572
  return;
@@ -7323,9 +8574,9 @@ function OhhwellsBridge() {
7323
8574
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
7324
8575
  if (imgEl !== hoveredImageRef.current || hoveredImageHasTextOverlapRef.current) {
7325
8576
  hoveredImageRef.current = imgEl;
7326
- postImageHover(imgEl, isDragOver);
8577
+ showImageHover(imgEl, isDragOver);
7327
8578
  } else if (isDragOver) {
7328
- postImageHover(imgEl, true);
8579
+ showImageHover(imgEl, true);
7329
8580
  }
7330
8581
  } else {
7331
8582
  const inIframeView = clientX >= 0 && clientY >= 0 && clientX <= window.innerWidth && clientY <= window.innerHeight;
@@ -7342,14 +8593,14 @@ function OhhwellsBridge() {
7342
8593
  hoveredImageRef.current = null;
7343
8594
  hoveredImageHasTextOverlapRef.current = false;
7344
8595
  resumeAnimTracks();
7345
- postToParentRef.current({ type: "ow:image-unhover" });
8596
+ clearImageHover();
7346
8597
  }
7347
- const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
8598
+ const { x: x2, y: y2 } = toProbeCoords(clientX, clientY, fromParentViewport);
7348
8599
  const textEl = Array.from(
7349
- document.querySelectorAll('[data-ohw-editable]:not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"])')
8600
+ document.querySelectorAll(NON_MEDIA_SELECTOR)
7350
8601
  ).find((el) => {
7351
8602
  const er = el.getBoundingClientRect();
7352
- 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;
7353
8604
  });
7354
8605
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
7355
8606
  if (textEl && !textEl.hasAttribute("contenteditable")) {
@@ -7452,18 +8703,20 @@ function OhhwellsBridge() {
7452
8703
  return;
7453
8704
  }
7454
8705
  e.dataTransfer.dropEffect = "copy";
7455
- if (hoveredImageRef.current !== el) {
8706
+ if (dragOverElRef.current !== el) {
8707
+ dragOverElRef.current = el;
7456
8708
  hoveredImageRef.current = el;
7457
8709
  const isStateCard = !!el.closest("[data-ohw-editable-state]");
7458
- postImageHover(el, true, isStateCard ? true : void 0);
8710
+ showImageHover(el, true, isStateCard ? true : void 0);
7459
8711
  }
7460
8712
  };
7461
8713
  const handleDragLeave = (e) => {
7462
8714
  const imgEl = findImageAtPoint(e.clientX, e.clientY, false);
7463
8715
  if (imgEl) return;
8716
+ dragOverElRef.current = null;
7464
8717
  hoveredImageRef.current = null;
7465
8718
  resumeAnimTracks();
7466
- postToParentRef.current({ type: "ow:image-unhover" });
8719
+ clearImageHover();
7467
8720
  };
7468
8721
  const handleDrop = (e) => {
7469
8722
  e.preventDefault();
@@ -7471,7 +8724,9 @@ function OhhwellsBridge() {
7471
8724
  if (!el) return;
7472
8725
  const file = e.dataTransfer?.files?.[0];
7473
8726
  if (file) {
7474
- 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) {
7475
8730
  const key = el.dataset.ohwKey ?? "";
7476
8731
  const { name, type: mimeType } = file;
7477
8732
  const r2 = el.getBoundingClientRect();
@@ -7480,12 +8735,13 @@ function OhhwellsBridge() {
7480
8735
  window.parent.postMessage({ type: "ow:image-drop", key, name, mimeType, buf, rect }, "*", [buf]);
7481
8736
  });
7482
8737
  } else {
7483
- postToParentRef.current({ type: "ow:image-drop-invalid" });
8738
+ postToParentRef.current({ type: "ow:image-drop-invalid", expected: wantsVideo ? "video" : "image" });
7484
8739
  }
7485
8740
  }
8741
+ dragOverElRef.current = null;
7486
8742
  hoveredImageRef.current = null;
7487
8743
  resumeAnimTracks();
7488
- postToParentRef.current({ type: "ow:image-unhover" });
8744
+ clearImageHover();
7489
8745
  };
7490
8746
  const handleImageUrl = (e) => {
7491
8747
  if (e.data?.type !== "ow:image-url") return;
@@ -7503,7 +8759,11 @@ function OhhwellsBridge() {
7503
8759
  }
7504
8760
  });
7505
8761
  hoveredImageRef.current = null;
7506
- 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
+ });
7507
8767
  };
7508
8768
  let found = false;
7509
8769
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -7538,6 +8798,19 @@ function OhhwellsBridge() {
7538
8798
  requestAnimationFrame(() => onReady());
7539
8799
  }
7540
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
+ }
7541
8814
  }
7542
8815
  });
7543
8816
  if (!found) notify();
@@ -7604,12 +8877,16 @@ function OhhwellsBridge() {
7604
8877
  sectionsJson = val;
7605
8878
  continue;
7606
8879
  }
8880
+ if (applyVideoSettingNode(key, val)) continue;
7607
8881
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
7608
8882
  if (el.dataset.ohwEditable === "image") {
7609
8883
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
7610
8884
  if (img) applyEditableImageSrc(img, val);
7611
8885
  } else if (el.dataset.ohwEditable === "bg-image") {
7612
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);
7613
8890
  } else if (el.dataset.ohwEditable === "link") {
7614
8891
  applyLinkHref(el, val);
7615
8892
  } else {
@@ -7623,6 +8900,8 @@ function OhhwellsBridge() {
7623
8900
  sectionsLoadedRef.current = true;
7624
8901
  pendingScheduleConfigRequests.current.splice(0).forEach(processConfigRequest);
7625
8902
  }
8903
+ editContentRef.current = { ...editContentRef.current, ...content };
8904
+ reconcileNavbarItemsFromContent(editContentRef.current);
7626
8905
  enforceLinkHrefs();
7627
8906
  postToParentRef.current({ type: "ow:hydrate-done" });
7628
8907
  };
@@ -7630,6 +8909,7 @@ function OhhwellsBridge() {
7630
8909
  const handleDeactivate = (e) => {
7631
8910
  if (e.data?.type !== "ow:deactivate") return;
7632
8911
  if (Date.now() < linkPopoverGraceUntilRef.current) return;
8912
+ if (document.querySelector("[data-ohw-section-picker]")) return;
7633
8913
  if (linkPopoverOpenRef.current) {
7634
8914
  setLinkPopoverRef.current(null);
7635
8915
  return;
@@ -7639,12 +8919,32 @@ function OhhwellsBridge() {
7639
8919
  };
7640
8920
  window.addEventListener("message", handleDeactivate);
7641
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
+ }
7642
8932
  if (e.key === "Escape" && linkPopoverOpenRef.current) {
7643
8933
  setLinkPopoverRef.current(null);
7644
8934
  return;
7645
8935
  }
7646
8936
  if (e.key === "Escape" && selectedElRef.current && !activeElRef.current) {
7647
- 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
+ }
7648
8948
  return;
7649
8949
  }
7650
8950
  if (e.key === "Escape" && activeElRef.current) {
@@ -7702,18 +9002,18 @@ function OhhwellsBridge() {
7702
9002
  if (hoveredItemElRef.current) {
7703
9003
  setHoveredItemRect(hoveredItemElRef.current.getBoundingClientRect());
7704
9004
  }
9005
+ if (hoveredNavContainerRef.current) {
9006
+ setHoveredNavContainerRect(hoveredNavContainerRef.current.getBoundingClientRect());
9007
+ }
7705
9008
  if (siblingHintElRef.current) {
7706
9009
  setSiblingHintRect(siblingHintElRef.current.getBoundingClientRect());
7707
9010
  }
7708
9011
  if (hoveredImageRef.current) {
7709
9012
  const el = hoveredImageRef.current;
7710
9013
  const r2 = el.getBoundingClientRect();
7711
- postToParentRef.current({
7712
- type: "ow:image-hover",
7713
- key: el.dataset.ohwKey ?? "",
7714
- rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height },
7715
- hasTextOverlap: hoveredImageHasTextOverlapRef.current
7716
- });
9014
+ setMediaHover(
9015
+ (prev) => prev ? { ...prev, rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height } } : prev
9016
+ );
7717
9017
  }
7718
9018
  };
7719
9019
  const handleSave = (e) => {
@@ -7802,19 +9102,39 @@ function OhhwellsBridge() {
7802
9102
  refreshActiveCommandsRef.current = handleSelectionChange;
7803
9103
  const handleDocMouseLeave = () => {
7804
9104
  hoveredImageRef.current = null;
9105
+ setMediaHover(null);
7805
9106
  document.querySelectorAll("[data-ohw-state-hovered]").forEach((el) => el.removeAttribute("data-ohw-state-hovered"));
7806
9107
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
7807
9108
  activeStateElRef.current = null;
7808
9109
  setToggleState(null);
7809
9110
  };
7810
- const handleAnimLock = (e) => {
7811
- if (e.data?.type !== "ow:anim-lock") return;
7812
- 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
+ });
7813
9129
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
7814
9130
  const track = el.closest("[data-ohw-hover-pause]");
7815
- if (track) {
9131
+ if (!track) return;
9132
+ if (uploading) {
7816
9133
  uploadLockedTracks.add(track);
7817
9134
  track.setAttribute("data-ohw-hover-paused", "");
9135
+ } else {
9136
+ uploadLockedTracks.delete(track);
9137
+ track.removeAttribute("data-ohw-hover-paused");
7818
9138
  }
7819
9139
  });
7820
9140
  };
@@ -7856,7 +9176,7 @@ function OhhwellsBridge() {
7856
9176
  if (e.data?.type !== "ow:click-at") return;
7857
9177
  const { clientX, clientY } = e.data;
7858
9178
  const allImages = Array.from(
7859
- document.querySelectorAll('[data-ohw-editable="image"], [data-ohw-editable="bg-image"]')
9179
+ document.querySelectorAll(MEDIA_SELECTOR)
7860
9180
  ).filter((el) => !!el.closest("[data-ohw-editable-state]"));
7861
9181
  const stateCardImage = allImages.find((el) => {
7862
9182
  const ownerCard = el.closest("[data-ohw-editable-state]");
@@ -7871,21 +9191,55 @@ function OhhwellsBridge() {
7871
9191
  return clientX >= r2.left && clientX <= r2.right && clientY >= r2.top && clientY <= r2.bottom;
7872
9192
  });
7873
9193
  if (stateCardImage) {
7874
- 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" });
7875
9195
  return;
7876
9196
  }
7877
9197
  const textEditable = Array.from(
7878
- document.querySelectorAll(
7879
- '[data-ohw-editable]:not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"])'
7880
- )
9198
+ document.querySelectorAll(NON_MEDIA_SELECTOR)
7881
9199
  ).find((el) => {
7882
9200
  const r2 = el.getBoundingClientRect();
7883
9201
  return clientX >= r2.left && clientX <= r2.right && clientY >= r2.top && clientY <= r2.bottom;
7884
9202
  });
7885
9203
  if (textEditable) {
7886
- 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 });
7887
9215
  return;
7888
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
+ }
7889
9243
  deactivateRef.current();
7890
9244
  };
7891
9245
  window.addEventListener("message", handleSave);
@@ -7895,7 +9249,7 @@ function OhhwellsBridge() {
7895
9249
  window.addEventListener("message", handleClearSchedulingWidget);
7896
9250
  window.addEventListener("message", handleRemoveSchedulingSection);
7897
9251
  window.addEventListener("message", handleImageUrl);
7898
- window.addEventListener("message", handleAnimLock);
9252
+ window.addEventListener("message", handleImageUploading);
7899
9253
  window.addEventListener("message", handleCanvasHeight);
7900
9254
  window.addEventListener("message", handleParentScroll);
7901
9255
  window.addEventListener("message", handlePointerSync);
@@ -7907,6 +9261,7 @@ function OhhwellsBridge() {
7907
9261
  };
7908
9262
  window.addEventListener("resize", handleViewportResize, { passive: true });
7909
9263
  document.addEventListener("click", handleClick, true);
9264
+ document.addEventListener("dblclick", handleDblClick, true);
7910
9265
  document.addEventListener("paste", handlePaste, true);
7911
9266
  document.addEventListener("input", handleInput, true);
7912
9267
  document.addEventListener("mouseover", handleMouseOver, true);
@@ -7921,6 +9276,7 @@ function OhhwellsBridge() {
7921
9276
  window.addEventListener("scroll", handleScroll, true);
7922
9277
  return () => {
7923
9278
  document.removeEventListener("click", handleClick, true);
9279
+ document.removeEventListener("dblclick", handleDblClick, true);
7924
9280
  document.removeEventListener("paste", handlePaste, true);
7925
9281
  document.removeEventListener("input", handleInput, true);
7926
9282
  document.removeEventListener("mouseover", handleMouseOver, true);
@@ -7940,7 +9296,7 @@ function OhhwellsBridge() {
7940
9296
  window.removeEventListener("message", handleClearSchedulingWidget);
7941
9297
  window.removeEventListener("message", handleRemoveSchedulingSection);
7942
9298
  window.removeEventListener("message", handleImageUrl);
7943
- window.removeEventListener("message", handleAnimLock);
9299
+ window.removeEventListener("message", handleImageUploading);
7944
9300
  window.removeEventListener("message", handleCanvasHeight);
7945
9301
  window.removeEventListener("message", handleParentScroll);
7946
9302
  window.removeEventListener("resize", handleViewportResize);
@@ -7954,7 +9310,7 @@ function OhhwellsBridge() {
7954
9310
  if (imageShowTimerRef.current) clearTimeout(imageShowTimerRef.current);
7955
9311
  };
7956
9312
  }, [isEditMode, refreshStateRules]);
7957
- (0, import_react6.useEffect)(() => {
9313
+ (0, import_react8.useEffect)(() => {
7958
9314
  const handler = (e) => {
7959
9315
  if (e.data?.type !== "ow:request-schedule-config") return;
7960
9316
  const insertAfterVal = e.data.insertAfter;
@@ -7970,7 +9326,7 @@ function OhhwellsBridge() {
7970
9326
  window.addEventListener("message", handler);
7971
9327
  return () => window.removeEventListener("message", handler);
7972
9328
  }, [processConfigRequest]);
7973
- (0, import_react6.useEffect)(() => {
9329
+ (0, import_react8.useEffect)(() => {
7974
9330
  if (!isEditMode) return;
7975
9331
  document.querySelectorAll("[data-ohw-active-state]").forEach((el) => {
7976
9332
  el.removeAttribute("data-ohw-active-state");
@@ -7991,27 +9347,27 @@ function OhhwellsBridge() {
7991
9347
  const next = { ...prev, [pathKey]: sections };
7992
9348
  return shouldUseDevFixtures() ? { ...DEV_SECTIONS_BY_PATH, ...next } : next;
7993
9349
  });
7994
- postToParent({ type: "ow:ready", version: "1", path: pathname, nodes: collectEditableNodes(), sections });
7995
- 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" });
7996
9352
  }, 150);
7997
9353
  return () => {
7998
9354
  cancelAnimationFrame(raf);
7999
9355
  clearTimeout(timer);
8000
9356
  };
8001
- }, [pathname, isEditMode, refreshStateRules, postToParent]);
8002
- (0, import_react6.useEffect)(() => {
9357
+ }, [pathname, isEditMode, refreshStateRules, postToParent2]);
9358
+ (0, import_react8.useEffect)(() => {
8003
9359
  scrollToHashSectionWhenReady();
8004
9360
  const onHashChange = () => scrollToHashSectionWhenReady();
8005
9361
  window.addEventListener("hashchange", onHashChange);
8006
9362
  return () => window.removeEventListener("hashchange", onHashChange);
8007
9363
  }, [pathname]);
8008
- const handleCommand = (0, import_react6.useCallback)((cmd) => {
9364
+ const handleCommand = (0, import_react8.useCallback)((cmd) => {
8009
9365
  document.execCommand(cmd, false);
8010
9366
  activeElRef.current?.focus();
8011
9367
  if (activeElRef.current) setToolbarRect(getEditMeasureEl(activeElRef.current).getBoundingClientRect());
8012
9368
  refreshActiveCommandsRef.current();
8013
9369
  }, []);
8014
- const handleStateChange = (0, import_react6.useCallback)((state) => {
9370
+ const handleStateChange = (0, import_react8.useCallback)((state) => {
8015
9371
  if (!activeStateElRef.current) return;
8016
9372
  const el = activeStateElRef.current;
8017
9373
  if (state === "Default") {
@@ -8024,18 +9380,22 @@ function OhhwellsBridge() {
8024
9380
  }
8025
9381
  setToggleState((prev) => prev ? { ...prev, activeState: state } : null);
8026
9382
  }, [deactivate]);
8027
- const closeLinkPopover = (0, import_react6.useCallback)(() => setLinkPopover(null), []);
8028
- 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)(() => {
8029
9388
  const hrefCtx = getHrefKeyFromElement(activeElRef.current);
8030
9389
  if (!hrefCtx) return;
8031
9390
  bumpLinkPopoverGrace();
8032
9391
  setLinkPopover({
8033
9392
  key: hrefCtx.key,
8034
- target: getLinkHref(hrefCtx.anchor)
9393
+ mode: "edit",
9394
+ target: getLinkHref2(hrefCtx.anchor)
8035
9395
  });
8036
9396
  deactivate();
8037
9397
  }, [deactivate]);
8038
- const openLinkPopoverForSelected = (0, import_react6.useCallback)(() => {
9398
+ const openLinkPopoverForSelected = (0, import_react8.useCallback)(() => {
8039
9399
  const anchor = selectedElRef.current;
8040
9400
  if (!anchor) return;
8041
9401
  const key = anchor.getAttribute("data-ohw-href-key");
@@ -8043,51 +9403,156 @@ function OhhwellsBridge() {
8043
9403
  bumpLinkPopoverGrace();
8044
9404
  setLinkPopover({
8045
9405
  key,
8046
- target: getLinkHref(anchor)
9406
+ mode: "edit",
9407
+ target: getLinkHref2(anchor)
8047
9408
  });
8048
9409
  deselect();
8049
9410
  }, [deselect]);
8050
- const handleLinkPopoverSubmit = (0, import_react6.useCallback)(
9411
+ const handleLinkPopoverSubmit = (0, import_react8.useCallback)(
8051
9412
  (target) => {
8052
- if (!linkPopover) return;
8053
- 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;
8054
9454
  applyLinkByKey(key, target);
8055
- postToParent({ type: "ow:change", nodes: [{ key, text: target }] });
9455
+ postToParent2({ type: "ow:change", nodes: [{ key, text: target }] });
8056
9456
  setLinkPopover(null);
8057
9457
  },
8058
- [linkPopover, postToParent]
9458
+ [postToParent2, sitePages, sectionsByPath]
8059
9459
  );
8060
9460
  const showEditLink = toolbarShowEditLink;
8061
9461
  const currentSections = sectionsByPath[pathname] ?? [];
8062
9462
  linkPopoverOpenRef.current = linkPopover !== null;
8063
- return bridgeRoot ? (0, import_react_dom2.createPortal)(
8064
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
8065
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
8066
- siblingHintRect && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
8067
- hoveredItemRect && !siblingHintRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
8068
- 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)(
8069
9533
  ItemInteractionLayer,
8070
9534
  {
8071
9535
  rect: toolbarRect,
8072
9536
  elRef: glowElRef,
8073
9537
  state: isItemDragging ? "dragging" : resolveItemInteractionState(toolbarRect, parentScrollRef.current),
8074
- showHandle: Boolean(reorderHrefKey),
9538
+ showHandle: toolbarVariant === "link-action" && Boolean(reorderHrefKey),
8075
9539
  dragDisabled: reorderDragDisabled,
8076
9540
  dragHandleLabel: reorderHrefKey ? `Reorder ${reorderHrefKey}` : "Reorder item",
8077
9541
  onDragHandleDragStart: handleItemDragStart,
8078
9542
  onDragHandleDragEnd: handleItemDragEnd,
8079
- toolbar: isItemDragging ? void 0 : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
9543
+ toolbar: toolbarVariant === "link-action" && !isItemDragging ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8080
9544
  ItemActionToolbar,
8081
9545
  {
8082
9546
  onEditLink: openLinkPopoverForSelected,
8083
- onAddItem: handleAddNavigationItem,
8084
- addItemDisabled: false
9547
+ addItemDisabled: true,
9548
+ editLinkDisabled: false,
9549
+ moreDisabled: true
8085
9550
  }
8086
- )
9551
+ ) : void 0
8087
9552
  }
8088
9553
  ),
8089
- toolbarRect && toolbarVariant === "rich-text" && /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
8090
- /* @__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)(
8091
9556
  EditGlowChrome,
8092
9557
  {
8093
9558
  rect: toolbarRect,
@@ -8096,7 +9561,7 @@ function OhhwellsBridge() {
8096
9561
  dragDisabled: reorderDragDisabled
8097
9562
  }
8098
9563
  ),
8099
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
9564
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8100
9565
  FloatingToolbar,
8101
9566
  {
8102
9567
  rect: toolbarRect,
@@ -8109,7 +9574,7 @@ function OhhwellsBridge() {
8109
9574
  }
8110
9575
  )
8111
9576
  ] }),
8112
- maxBadge && /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
9577
+ maxBadge && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
8113
9578
  "div",
8114
9579
  {
8115
9580
  "data-ohw-max-badge": "",
@@ -8135,7 +9600,7 @@ function OhhwellsBridge() {
8135
9600
  ]
8136
9601
  }
8137
9602
  ),
8138
- toggleState && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
9603
+ toggleState && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8139
9604
  StateToggle,
8140
9605
  {
8141
9606
  rect: toggleState.rect,
@@ -8144,15 +9609,15 @@ function OhhwellsBridge() {
8144
9609
  onStateChange: handleStateChange
8145
9610
  }
8146
9611
  ),
8147
- sectionGap && /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
9612
+ sectionGap && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
8148
9613
  "div",
8149
9614
  {
8150
9615
  "data-ohw-section-insert-line": "",
8151
9616
  className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
8152
9617
  style: { top: sectionGap.y, transform: "translateY(-50%)" },
8153
9618
  children: [
8154
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
8155
- /* @__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)(
8156
9621
  Badge,
8157
9622
  {
8158
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",
@@ -8165,26 +9630,52 @@ function OhhwellsBridge() {
8165
9630
  children: "Add Section"
8166
9631
  }
8167
9632
  ),
8168
- /* @__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 } })
8169
9634
  ]
8170
9635
  }
8171
9636
  ),
8172
- linkPopover && dialogPortalContainer ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
9637
+ linkPopover && dialogPortalContainer ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8173
9638
  LinkPopover,
8174
9639
  {
8175
9640
  panelRef: linkPopoverPanelRef,
8176
9641
  portalContainer: dialogPortalContainer,
8177
9642
  open: true,
8178
- mode: "edit",
9643
+ mode: linkPopover.mode ?? "edit",
8179
9644
  pages: sitePages,
8180
9645
  sections: currentSections,
8181
9646
  sectionsByPath,
8182
9647
  initialTarget: linkPopover.target,
9648
+ existingTargets: linkPopover.intent === "add-nav" ? getNavbarExistingTargets() : [],
8183
9649
  onClose: closeLinkPopover,
8184
9650
  onSubmit: handleLinkPopoverSubmit
8185
9651
  },
8186
- `${linkPopover.key}-${pathname}`
8187
- ) : 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
+ )
8188
9679
  ] }),
8189
9680
  bridgeRoot
8190
9681
  ) : null;