@ohhwells/bridge 0.1.37 → 0.1.38-next.57

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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-[448px] -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-h-[calc(100vh-32px)]",
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);
@@ -5186,14 +5448,21 @@ function UrlOrPageInput({
5186
5448
  requestAnimationFrame(() => inputRef.current?.focus());
5187
5449
  };
5188
5450
  const fieldClassName = cn(
5189
- "data-ohw-link-field flex h-10 w-full items-center overflow-hidden rounded-md border bg-background pl-3 pr-3 py-2 outline-none transition-[border-color,box-shadow]",
5451
+ "data-ohw-link-field flex h-[36px] w-full items-center overflow-hidden rounded-md border bg-background pl-3 pr-3 py-2 outline-none transition-[border-color,box-shadow]",
5190
5452
  urlError ? "border-destructive shadow-[0_0_0_1px_var(--ohw-destructive)]" : isFocused ? "border-primary shadow-[0_0_0_1px_var(--ohw-primary)]" : "border-input"
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,444 @@ 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 readSectionRects(sectionIds) {
5638
+ const next = /* @__PURE__ */ new Map();
5639
+ for (const id of sectionIds) {
5640
+ const el = document.querySelector(
5641
+ `[data-ohw-section="${CSS.escape(id)}"]`
5642
+ );
5643
+ if (el) next.set(id, el.getBoundingClientRect());
5644
+ }
5645
+ return next;
5646
+ }
5647
+ function hitTestSectionId(x, y, sectionIds, rects) {
5648
+ for (const id of sectionIds) {
5649
+ const rect = rects.get(id);
5650
+ if (!rect || rect.width <= 0 || rect.height <= 0) continue;
5651
+ if (x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom) {
5652
+ return id;
5653
+ }
5654
+ }
5655
+ return null;
5656
+ }
5657
+ function useSectionRects(sectionIdsKey, enabled) {
5658
+ const [rects, setRects] = (0, import_react5.useState)(/* @__PURE__ */ new Map());
5659
+ const sectionIds = (0, import_react5.useMemo)(
5660
+ () => sectionIdsKey ? sectionIdsKey.split("\0") : [],
5661
+ [sectionIdsKey]
5662
+ );
5663
+ (0, import_react5.useEffect)(() => {
5664
+ if (!enabled || sectionIds.length === 0) {
5665
+ setRects((prev) => prev.size === 0 ? prev : /* @__PURE__ */ new Map());
5666
+ return;
5667
+ }
5668
+ const update = () => {
5669
+ const next = readSectionRects(sectionIds);
5670
+ setRects((prev) => rectsEqual(prev, next) ? prev : next);
5671
+ };
5672
+ update();
5673
+ const opts = { capture: true, passive: true };
5674
+ window.addEventListener("scroll", update, opts);
5675
+ window.addEventListener("resize", update);
5676
+ const vv = window.visualViewport;
5677
+ vv?.addEventListener("resize", update);
5678
+ vv?.addEventListener("scroll", update);
5679
+ const ro = new ResizeObserver(update);
5680
+ for (const id of sectionIds) {
5681
+ const el = document.querySelector(
5682
+ `[data-ohw-section="${CSS.escape(id)}"]`
5683
+ );
5684
+ if (el) ro.observe(el);
5685
+ }
5686
+ return () => {
5687
+ window.removeEventListener("scroll", update, opts);
5688
+ window.removeEventListener("resize", update);
5689
+ vv?.removeEventListener("resize", update);
5690
+ vv?.removeEventListener("scroll", update);
5691
+ ro.disconnect();
5692
+ };
5693
+ }, [sectionIds, enabled]);
5694
+ return rects;
5695
+ }
5696
+ function SectionPickerOverlay({
5697
+ pagePath,
5698
+ sections,
5699
+ onBack,
5700
+ onSelect
5701
+ }) {
5702
+ const router = (0, import_navigation.useRouter)();
5703
+ const pathname = (0, import_navigation.usePathname)();
5704
+ const [selectedId, setSelectedId] = (0, import_react5.useState)(null);
5705
+ const [hoveredId, setHoveredId] = (0, import_react5.useState)(null);
5706
+ const pointerRef = (0, import_react5.useRef)(null);
5707
+ const pointerScreenRef = (0, import_react5.useRef)(null);
5708
+ const iframeOffsetTopRef = (0, import_react5.useRef)(null);
5709
+ const sectionIdsRef = (0, import_react5.useRef)([]);
5710
+ const [chromeClip, setChromeClip] = (0, import_react5.useState)(
5711
+ () => ({
5712
+ top: 0,
5713
+ bottom: typeof window !== "undefined" ? window.innerHeight : 0
5714
+ })
5715
+ );
5716
+ const normalizedTarget = normalizePath(pagePath);
5717
+ const isOnTargetPage = normalizePath(pathname) === normalizedTarget;
5718
+ (0, import_react5.useEffect)(() => {
5719
+ if (isOnTargetPage) return;
5720
+ const search = readPreservedSearch();
5721
+ router.push(`${normalizedTarget}${search}`);
5722
+ }, [isOnTargetPage, normalizedTarget, router]);
5723
+ (0, import_react5.useEffect)(() => {
5724
+ document.documentElement.setAttribute("data-ohw-section-picking", "");
5725
+ document.querySelectorAll("[data-ohw-hovered]").forEach((el) => {
5726
+ el.removeAttribute("data-ohw-hovered");
5727
+ });
5728
+ return () => {
5729
+ document.documentElement.removeAttribute("data-ohw-section-picking");
5730
+ };
5731
+ }, []);
5732
+ const liveSections = (0, import_react5.useMemo)(() => {
5733
+ if (!isOnTargetPage) return sections;
5734
+ const live = collectSectionsFromDom();
5735
+ if (live.length === 0) return sections;
5736
+ const labels = new Map(sections.map((s) => [s.id, s.label]));
5737
+ return live.map((s) => ({ id: s.id, label: labels.get(s.id) ?? s.label }));
5738
+ }, [isOnTargetPage, sections, pathname]);
5739
+ const sectionIdsKey = (0, import_react5.useMemo)(
5740
+ () => liveSections.map((s) => s.id).join("\0"),
5741
+ [liveSections]
5742
+ );
5743
+ const sectionIds = (0, import_react5.useMemo)(
5744
+ () => liveSections.map((s) => s.id),
5745
+ [liveSections]
5746
+ );
5747
+ sectionIdsRef.current = sectionIds;
5748
+ const rects = useSectionRects(sectionIdsKey, isOnTargetPage);
5749
+ const applyHoverAt = (0, import_react5.useCallback)((x, y, liveRects) => {
5750
+ const ids = sectionIdsRef.current;
5751
+ const map = liveRects ?? readSectionRects(ids);
5752
+ const next = hitTestSectionId(x, y, ids, map);
5753
+ setHoveredId((prev) => prev === next ? prev : next);
5754
+ }, []);
5755
+ (0, import_react5.useEffect)(() => {
5756
+ const applyClip = (iframeOffsetTop, visibleCanvasTop, canvasH) => {
5757
+ const top = Math.max(0, visibleCanvasTop - iframeOffsetTop);
5758
+ const bottom = Math.min(
5759
+ window.innerHeight,
5760
+ visibleCanvasTop + canvasH - iframeOffsetTop
5761
+ );
5762
+ setChromeClip(
5763
+ (prev) => prev.top === top && prev.bottom === bottom ? prev : { top, bottom: Math.max(top, bottom) }
5764
+ );
5765
+ };
5766
+ const onParentScroll = (e) => {
5767
+ if (e.data?.type !== "ow:parent-scroll") return;
5768
+ const { iframeOffsetTop, headerH, canvasH } = e.data;
5769
+ applyClip(iframeOffsetTop, headerH, canvasH);
5770
+ iframeOffsetTopRef.current = iframeOffsetTop;
5771
+ if (!pointerScreenRef.current && pointerRef.current) {
5772
+ pointerScreenRef.current = {
5773
+ x: pointerRef.current.x,
5774
+ y: pointerRef.current.y + iframeOffsetTop
5775
+ };
5776
+ }
5777
+ const screen = pointerScreenRef.current;
5778
+ if (screen) {
5779
+ const next = { x: screen.x, y: screen.y - iframeOffsetTop };
5780
+ pointerRef.current = next;
5781
+ applyHoverAt(next.x, next.y);
5782
+ }
5783
+ };
5784
+ const onResize = () => {
5785
+ setChromeClip((prev) => {
5786
+ const bottom = Math.max(prev.top, window.innerHeight);
5787
+ return prev.bottom === bottom ? prev : { ...prev, bottom };
5788
+ });
5789
+ };
5790
+ window.addEventListener("message", onParentScroll);
5791
+ window.addEventListener("resize", onResize);
5792
+ return () => {
5793
+ window.removeEventListener("message", onParentScroll);
5794
+ window.removeEventListener("resize", onResize);
5795
+ };
5796
+ }, [applyHoverAt]);
5797
+ (0, import_react5.useEffect)(() => {
5798
+ const ptr = pointerRef.current;
5799
+ if (!ptr) return;
5800
+ applyHoverAt(ptr.x, ptr.y, rects);
5801
+ }, [rects, applyHoverAt]);
5802
+ (0, import_react5.useEffect)(() => {
5803
+ const rememberPointer = (clientX, clientY) => {
5804
+ pointerRef.current = { x: clientX, y: clientY };
5805
+ const top = iframeOffsetTopRef.current;
5806
+ if (top != null) {
5807
+ pointerScreenRef.current = { x: clientX, y: clientY + top };
5808
+ }
5809
+ applyHoverAt(clientX, clientY, rects);
5810
+ };
5811
+ const onPointerMove = (e) => {
5812
+ rememberPointer(e.clientX, e.clientY);
5813
+ };
5814
+ const onPointerSync = (e) => {
5815
+ if (e.data?.type !== "ow:pointer-sync") return;
5816
+ const { clientX, clientY } = e.data;
5817
+ rememberPointer(clientX, clientY);
5818
+ };
5819
+ window.addEventListener("pointermove", onPointerMove, { passive: true });
5820
+ window.addEventListener("message", onPointerSync);
5821
+ return () => {
5822
+ window.removeEventListener("pointermove", onPointerMove);
5823
+ window.removeEventListener("message", onPointerSync);
5824
+ };
5825
+ }, [rects, applyHoverAt]);
5826
+ const handleSelect = (0, import_react5.useCallback)(
5827
+ (section) => {
5828
+ if (selectedId) return;
5829
+ setSelectedId(section.id);
5830
+ onSelect(section);
5831
+ },
5832
+ [selectedId, onSelect]
5833
+ );
5834
+ (0, import_react5.useEffect)(() => {
5835
+ const onKeyDown = (e) => {
5836
+ if (e.key === "Escape") {
5837
+ e.preventDefault();
5838
+ e.stopPropagation();
5839
+ onBack();
5840
+ }
5841
+ };
5842
+ window.addEventListener("keydown", onKeyDown, true);
5843
+ return () => window.removeEventListener("keydown", onKeyDown, true);
5844
+ }, [onBack]);
5845
+ const portalRoot = typeof document !== "undefined" ? document.querySelector("[data-ohw-bridge-root]") ?? document.body : null;
5846
+ if (!portalRoot) return null;
5847
+ return (0, import_react_dom.createPortal)(
5848
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
5849
+ "div",
5850
+ {
5851
+ "data-ohw-section-picker": "",
5852
+ "data-ohw-bridge": "",
5853
+ className: `pointer-events-none fixed inset-0 z-[2147483647] transition-opacity duration-200 ${"opacity-100"}`,
5854
+ "aria-modal": true,
5855
+ role: "dialog",
5856
+ "aria-label": "Choose a section",
5857
+ children: [
5858
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5859
+ "div",
5860
+ {
5861
+ className: "pointer-events-auto fixed left-5 z-[2]",
5862
+ style: { top: chromeClip.top + 20 },
5863
+ children: /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
5864
+ Button,
5865
+ {
5866
+ type: "button",
5867
+ variant: "outline",
5868
+ size: "sm",
5869
+ className: "h-8 min-w-0 gap-1 border-border bg-background px-2 py-1.5 shadow-sm hover:bg-muted cursor-pointer",
5870
+ onClick: onBack,
5871
+ children: [
5872
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react9.ArrowLeft, { className: "size-4 shrink-0", "aria-hidden": true }),
5873
+ "Back"
5874
+ ]
5875
+ }
5876
+ )
5877
+ }
5878
+ ),
5879
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5880
+ "div",
5881
+ {
5882
+ 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",
5883
+ style: {
5884
+ top: chromeClip.bottom - 20,
5885
+ transform: "translate(-50%, -100%)",
5886
+ backgroundColor: "var(--ohw-popover-foreground, #0c0a09)"
5887
+ },
5888
+ "data-ohw-section-picker-hint": "",
5889
+ children: "Click on section to select"
5890
+ }
5891
+ ),
5892
+ !isOnTargetPage ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5893
+ "div",
5894
+ {
5895
+ 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",
5896
+ style: { top: chromeClip.top + 64 },
5897
+ children: "Loading page preview\u2026"
5898
+ }
5899
+ ) : null,
5900
+ 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,
5901
+ isOnTargetPage ? liveSections.map((section) => {
5902
+ const rect = rects.get(section.id);
5903
+ if (!rect || rect.width <= 0 || rect.height <= 0) return null;
5904
+ const isSelected = selectedId === section.id;
5905
+ const isLit = isSelected || hoveredId === section.id;
5906
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5907
+ "button",
5908
+ {
5909
+ type: "button",
5910
+ className: "pointer-events-auto fixed z-[1] cursor-pointer border-0 bg-transparent p-0 transition-[background-color] duration-150",
5911
+ style: {
5912
+ top: rect.top,
5913
+ left: rect.left,
5914
+ width: rect.width,
5915
+ height: rect.height,
5916
+ backgroundColor: isLit ? "transparent" : DIM_OVERLAY
5917
+ },
5918
+ "aria-label": `Select section ${section.label}`,
5919
+ onClick: () => handleSelect(section),
5920
+ children: isSelected ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5921
+ "span",
5922
+ {
5923
+ className: "absolute right-3 top-3 flex size-8 items-center justify-center rounded-full text-white",
5924
+ style: { backgroundColor: "var(--ohw-primary, #0885fe)" },
5925
+ "aria-hidden": true,
5926
+ children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react9.Check, { className: "size-5" })
5927
+ }
5928
+ ) : null
5929
+ },
5930
+ section.id
5931
+ );
5932
+ }) : null
5933
+ ]
5934
+ }
5935
+ ),
5936
+ portalRoot
5937
+ );
5938
+ }
5939
+
5940
+ // src/ui/link-modal/useLinkModalState.ts
5941
+ var import_react6 = require("react");
5942
+ function useLinkModalState({
5943
+ open,
5944
+ mode,
5945
+ pages,
5946
+ sections: _sections,
5947
+ sectionsByPath,
5948
+ initialTarget,
5949
+ existingTargets: _existingTargets,
5950
+ onClose,
5951
+ onSubmit
5952
+ }) {
5953
+ const availablePages = (0, import_react6.useMemo)(() => pages, [pages]);
5954
+ const [searchValue, setSearchValue] = (0, import_react6.useState)("");
5955
+ const [selectedPage, setSelectedPage] = (0, import_react6.useState)(null);
5956
+ const [selectedSection, setSelectedSection] = (0, import_react6.useState)(null);
5957
+ const [step, setStep] = (0, import_react6.useState)("input");
5958
+ const [dropdownOpen, setDropdownOpen] = (0, import_react6.useState)(false);
5959
+ const [urlError, setUrlError] = (0, import_react6.useState)("");
5960
+ const reset = (0, import_react6.useCallback)(() => {
5961
+ setSearchValue("");
5962
+ setSelectedPage(null);
5281
5963
  setSelectedSection(null);
5282
5964
  setStep("input");
5283
5965
  setDropdownOpen(false);
5284
5966
  setUrlError("");
5285
5967
  }, []);
5286
- (0, import_react5.useEffect)(() => {
5968
+ (0, import_react6.useEffect)(() => {
5287
5969
  if (!open) return;
5288
5970
  if (mode === "edit" && initialTarget) {
5289
5971
  const { pageRoute } = parseTarget(initialTarget);
@@ -5298,12 +5980,12 @@ function useLinkModalState({
5298
5980
  }
5299
5981
  setDropdownOpen(false);
5300
5982
  setUrlError("");
5301
- }, [open, mode, initialTarget, pages, sectionsByPath, reset]);
5302
- const filteredPages = (0, import_react5.useMemo)(() => {
5983
+ }, [open, mode, initialTarget, reset]);
5984
+ const filteredPages = (0, import_react6.useMemo)(() => {
5303
5985
  if (!searchValue.trim()) return availablePages;
5304
5986
  return availablePages.filter((p) => p.title.toLowerCase().startsWith(searchValue.toLowerCase()));
5305
5987
  }, [availablePages, searchValue]);
5306
- const activeSections = (0, import_react5.useMemo)(() => {
5988
+ const activeSections = (0, import_react6.useMemo)(() => {
5307
5989
  if (!selectedPage) return [];
5308
5990
  return getSectionsForPath(sectionsByPath, selectedPage.path);
5309
5991
  }, [selectedPage, sectionsByPath]);
@@ -5345,11 +6027,14 @@ function useLinkModalState({
5345
6027
  const handleBackToSections = () => {
5346
6028
  setStep("sectionPicker");
5347
6029
  };
6030
+ const handleSectionPickerBack = () => {
6031
+ setStep("input");
6032
+ };
5348
6033
  const handleClose = () => {
5349
6034
  reset();
5350
6035
  onClose();
5351
6036
  };
5352
- const isValid = (0, import_react5.useMemo)(() => {
6037
+ const isValid = (0, import_react6.useMemo)(() => {
5353
6038
  if (urlError) return false;
5354
6039
  if (showBreadcrumb) return true;
5355
6040
  if (selectedPage) return true;
@@ -5382,7 +6067,7 @@ function useLinkModalState({
5382
6067
  onSubmit(normalizeUrl(searchValue));
5383
6068
  handleClose();
5384
6069
  };
5385
- const submitLabel = mode === "edit" ? "Save" : "Create";
6070
+ const submitLabel = showBreadcrumb ? "Save" : mode === "edit" ? "Save" : "Create";
5386
6071
  const title = mode === "edit" ? "Edit navigation item" : "Create navigation item";
5387
6072
  const secondaryLabel = showBreadcrumb ? "Back to sections" : "Cancel";
5388
6073
  const handleSecondary = showBreadcrumb ? handleBackToSections : handleClose;
@@ -5409,23 +6094,29 @@ function useLinkModalState({
5409
6094
  handleChooseSection,
5410
6095
  handleSectionSelect,
5411
6096
  handleBackToSections,
6097
+ handleSectionPickerBack,
5412
6098
  handleClose,
5413
6099
  handleSecondary,
5414
6100
  handleSubmit
5415
6101
  };
5416
6102
  }
5417
6103
 
5418
- // src/ui/link-modal/LinkEditorPanel.tsx
5419
- var import_jsx_runtime18 = require("react/jsx-runtime");
5420
- function LinkEditorPanel({
6104
+ // src/ui/link-modal/LinkPopover.tsx
6105
+ var import_jsx_runtime20 = require("react/jsx-runtime");
6106
+ function postToParent(data) {
6107
+ window.parent?.postMessage(data, "*");
6108
+ }
6109
+ function LinkPopover({
5421
6110
  open = true,
6111
+ panelRef,
6112
+ portalContainer,
6113
+ onClose,
5422
6114
  mode = "create",
5423
6115
  pages,
5424
6116
  sections = [],
5425
6117
  sectionsByPath,
5426
6118
  initialTarget,
5427
6119
  existingTargets = [],
5428
- onClose,
5429
6120
  onSubmit
5430
6121
  }) {
5431
6122
  const state = useLinkModalState({
@@ -5439,115 +6130,109 @@ function LinkEditorPanel({
5439
6130
  onClose,
5440
6131
  onSubmit
5441
6132
  });
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",
6133
+ const sectionPickerActive = state.showSectionPicker && Boolean(state.selectedPage);
6134
+ (0, import_react7.useEffect)(() => {
6135
+ if (!open) return;
6136
+ if (sectionPickerActive) {
6137
+ postToParent({ type: "ow:link-modal-lock", locked: false });
6138
+ postToParent({ type: "ow:section-picker", active: true });
6139
+ document.documentElement.style.overflow = "";
6140
+ document.body.style.overflow = "";
6141
+ return () => {
6142
+ postToParent({ type: "ow:section-picker", active: false });
6143
+ };
6144
+ }
6145
+ postToParent({ type: "ow:section-picker", active: false });
6146
+ postToParent({ type: "ow:link-modal-lock", locked: true });
6147
+ const html = document.documentElement;
6148
+ const body = document.body;
6149
+ const prevHtmlOverflow = html.style.overflow;
6150
+ const prevBodyOverflow = body.style.overflow;
6151
+ const prevHtmlOverscroll = html.style.overscrollBehavior;
6152
+ const prevBodyOverscroll = body.style.overscrollBehavior;
6153
+ html.style.overflow = "hidden";
6154
+ body.style.overflow = "hidden";
6155
+ html.style.overscrollBehavior = "none";
6156
+ body.style.overscrollBehavior = "none";
6157
+ const canScrollElement = (el, deltaY) => {
6158
+ const { overflowY } = getComputedStyle(el);
6159
+ if (overflowY !== "auto" && overflowY !== "scroll") return false;
6160
+ if (el.scrollHeight <= el.clientHeight + 1) return false;
6161
+ if (deltaY < 0) return el.scrollTop > 0;
6162
+ if (deltaY > 0)
6163
+ return el.scrollTop + el.clientHeight < el.scrollHeight - 1;
6164
+ return true;
6165
+ };
6166
+ const allowsInternalScroll = (e) => {
6167
+ const target = e.target;
6168
+ if (!(target instanceof Element)) return false;
6169
+ const scrollRoot = target.closest(
6170
+ "[data-ohw-link-page-dropdown], [data-ohw-allow-scroll]"
6171
+ );
6172
+ if (!scrollRoot) return false;
6173
+ const deltaY = e instanceof WheelEvent ? e.deltaY : 0;
6174
+ return canScrollElement(scrollRoot, deltaY);
6175
+ };
6176
+ const preventBackgroundScroll = (e) => {
6177
+ if (allowsInternalScroll(e)) return;
6178
+ e.preventDefault();
6179
+ };
6180
+ const scrollOpts = { passive: false, capture: true };
6181
+ document.addEventListener("wheel", preventBackgroundScroll, scrollOpts);
6182
+ document.addEventListener("touchmove", preventBackgroundScroll, scrollOpts);
6183
+ return () => {
6184
+ postToParent({ type: "ow:link-modal-lock", locked: false });
6185
+ html.style.overflow = prevHtmlOverflow;
6186
+ body.style.overflow = prevBodyOverflow;
6187
+ html.style.overscrollBehavior = prevHtmlOverscroll;
6188
+ body.style.overscrollBehavior = prevBodyOverscroll;
6189
+ document.removeEventListener(
6190
+ "wheel",
6191
+ preventBackgroundScroll,
6192
+ scrollOpts
6193
+ );
6194
+ document.removeEventListener(
6195
+ "touchmove",
6196
+ preventBackgroundScroll,
6197
+ scrollOpts
6198
+ );
6199
+ };
6200
+ }, [open, sectionPickerActive]);
6201
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_jsx_runtime20.Fragment, { children: [
6202
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
6203
+ Dialog2,
5446
6204
  {
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 })
6205
+ open: open && !sectionPickerActive,
6206
+ onOpenChange: (next) => {
6207
+ if (!next) onClose?.();
6208
+ },
6209
+ children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
6210
+ DialogContent,
6211
+ {
6212
+ ref: panelRef,
6213
+ container: portalContainer,
6214
+ "data-ohw-link-popover-root": "",
6215
+ "data-ohw-link-modal-root": "",
6216
+ "data-ohw-bridge": "",
6217
+ showCloseButton: false,
6218
+ className: "gap-0 p-0 w-full max-w-[448px] pointer-events-auto z-[2147483646] overflow-visible",
6219
+ children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(LinkEditorPanel, { state, onClose })
6220
+ }
6221
+ )
5451
6222
  }
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
- ] })
6223
+ ),
6224
+ sectionPickerActive && state.selectedPage ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
6225
+ SectionPickerOverlay,
6226
+ {
6227
+ pagePath: state.selectedPage.path,
6228
+ sections: state.activeSections,
6229
+ onBack: state.handleSectionPickerBack,
6230
+ onSelect: state.handleSectionSelect
6231
+ }
6232
+ ) : null
5515
6233
  ] });
5516
6234
  }
5517
6235
 
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
6236
  // src/ui/link-modal/devFixtures.ts
5552
6237
  var DEV_SITE_PAGES = [
5553
6238
  { path: "/", title: "Home" },
@@ -5567,16 +6252,21 @@ var DEV_SECTIONS_BY_PATH = {
5567
6252
  { id: "founder-teaser", label: "Founder" },
5568
6253
  { id: "plan-form", label: "Plan Form" },
5569
6254
  { id: "testimonials", label: "Testimonials" },
5570
- { id: "wordmark", label: "Wordmark" }
6255
+ { id: "wordmark", label: "Wordmark" },
6256
+ { id: "footer", label: "Footer" }
5571
6257
  ],
5572
6258
  "/about": [
5573
6259
  { id: "manifesto", label: "Manifesto" },
5574
6260
  { id: "story-letter", label: "Our Story" },
5575
6261
  { id: "lagree-explainer", label: "Lagree Explainer" },
5576
6262
  { id: "waitlist-cta", label: "Waitlist" },
5577
- { id: "personal-training", label: "Personal training" }
6263
+ { id: "personal-training", label: "Personal training" },
6264
+ { id: "footer", label: "Footer" }
6265
+ ],
6266
+ "/classes": [
6267
+ { id: "class-library", label: "Class Library" },
6268
+ { id: "footer", label: "Footer" }
5578
6269
  ],
5579
- "/classes": [{ id: "class-library", label: "Class Library" }],
5580
6270
  "/pricing": [
5581
6271
  { id: "page-header", label: "Page Header" },
5582
6272
  { id: "free-class-cta", label: "Free Class" },
@@ -5584,19 +6274,25 @@ var DEV_SECTIONS_BY_PATH = {
5584
6274
  { id: "monthly-memberships", label: "Memberships" },
5585
6275
  { id: "specialty-programs", label: "Specialty Programs" },
5586
6276
  { id: "package-matcher", label: "Packages" },
5587
- { id: "wordmark", label: "Wordmark" }
6277
+ { id: "wordmark", label: "Wordmark" },
6278
+ { id: "footer", label: "Footer" }
5588
6279
  ],
5589
6280
  "/studio": [
5590
6281
  { id: "studio-intro", label: "Studio Intro" },
5591
6282
  { id: "studio-features", label: "Studio Features" },
5592
6283
  { id: "studio-gallery", label: "Studio Gallery" },
5593
- { id: "studio-visit", label: "Visit Studio" }
6284
+ { id: "studio-visit", label: "Visit Studio" },
6285
+ { id: "footer", label: "Footer" }
6286
+ ],
6287
+ "/instructors": [
6288
+ { id: "instructor-grid", label: "Instructors" },
6289
+ { id: "footer", label: "Footer" }
5594
6290
  ],
5595
- "/instructors": [{ id: "instructor-grid", label: "Instructors" }],
5596
6291
  "/contact": [
5597
6292
  { id: "hello-hero", label: "Hello" },
5598
6293
  { id: "contact-form", label: "Contact Form" },
5599
- { id: "faq", label: "FAQ" }
6294
+ { id: "faq", label: "FAQ" },
6295
+ { id: "footer", label: "Footer" }
5600
6296
  ]
5601
6297
  };
5602
6298
  function shouldUseDevFixtures() {
@@ -5606,8 +6302,277 @@ function shouldUseDevFixtures() {
5606
6302
  return new URLSearchParams(q).get("ohw-fixtures") === "1";
5607
6303
  }
5608
6304
 
6305
+ // src/lib/nav-items.ts
6306
+ var NAV_COUNT_KEY = "__ohw_nav_count";
6307
+ var NAV_ORDER_KEY = "__ohw_nav_order";
6308
+ function getLinkHref(el) {
6309
+ const key = el.getAttribute("data-ohw-href-key");
6310
+ if (key) {
6311
+ const stored = getStoredLinkHref(key);
6312
+ if (stored !== void 0) return stored;
6313
+ }
6314
+ return el.getAttribute("href") ?? "";
6315
+ }
6316
+ function isNavbarButton(el) {
6317
+ return el.hasAttribute("data-ohw-role") && el.getAttribute("data-ohw-role") === "navbar-button";
6318
+ }
6319
+ function isNavbarLinkItem(el) {
6320
+ if (!el.matches("[data-ohw-href-key]")) return false;
6321
+ if (isNavbarButton(el)) return false;
6322
+ if (!el.querySelector('[data-ohw-editable="text"]')) return false;
6323
+ return Boolean(el.closest("nav, [data-ohw-nav-container], [data-ohw-nav-drawer]"));
6324
+ }
6325
+ function getNavbarDesktopContainer() {
6326
+ return document.querySelector("[data-ohw-nav-container]");
6327
+ }
6328
+ function getNavbarDrawerContainer() {
6329
+ return document.querySelector("[data-ohw-nav-drawer]");
6330
+ }
6331
+ function listNavbarItems() {
6332
+ const desktop = getNavbarDesktopContainer();
6333
+ if (desktop) {
6334
+ return Array.from(desktop.querySelectorAll("[data-ohw-href-key]")).filter(isNavbarLinkItem);
6335
+ }
6336
+ const drawer = getNavbarDrawerContainer();
6337
+ if (drawer) {
6338
+ return Array.from(drawer.querySelectorAll("[data-ohw-href-key]")).filter(isNavbarLinkItem);
6339
+ }
6340
+ return Array.from(document.querySelectorAll("nav [data-ohw-href-key]")).filter(isNavbarLinkItem);
6341
+ }
6342
+ function parseNavIndexFromKey(key) {
6343
+ if (!key) return null;
6344
+ const match = key.match(/^nav-(\d+)-href$/);
6345
+ if (!match) return null;
6346
+ return parseInt(match[1], 10);
6347
+ }
6348
+ function getNavbarIndicesInDom() {
6349
+ const indices = /* @__PURE__ */ new Set();
6350
+ for (const item of listNavbarItems()) {
6351
+ const idx = parseNavIndexFromKey(item.getAttribute("data-ohw-href-key"));
6352
+ if (idx !== null) indices.add(idx);
6353
+ }
6354
+ return [...indices].sort((a, b) => a - b);
6355
+ }
6356
+ function getNextNavbarIndex() {
6357
+ const indices = getNavbarIndicesInDom();
6358
+ if (indices.length === 0) return 0;
6359
+ return Math.max(...indices) + 1;
6360
+ }
6361
+ function getNavbarExistingTargets() {
6362
+ return listNavbarItems().map((el) => getLinkHref(el)).filter(Boolean);
6363
+ }
6364
+ function getNavOrderFromDom() {
6365
+ return listNavbarItems().map((el) => el.getAttribute("data-ohw-href-key")).filter((key) => Boolean(key));
6366
+ }
6367
+ function parseNavOrder(content) {
6368
+ const raw = content[NAV_ORDER_KEY];
6369
+ if (!raw) return null;
6370
+ try {
6371
+ const parsed = JSON.parse(raw);
6372
+ if (!Array.isArray(parsed)) return null;
6373
+ return parsed.filter((key) => typeof key === "string" && key.length > 0);
6374
+ } catch {
6375
+ return null;
6376
+ }
6377
+ }
6378
+ function findCounterpartByHrefKey(hrefKey, root) {
6379
+ return root.querySelector(`[data-ohw-href-key="${CSS.escape(hrefKey)}"]`);
6380
+ }
6381
+ function cloneNavLinkShell(template) {
6382
+ const anchor = document.createElement("a");
6383
+ if (template) {
6384
+ anchor.style.cssText = template.style.cssText;
6385
+ if (template.className) anchor.className = template.className;
6386
+ }
6387
+ anchor.style.cursor = "pointer";
6388
+ anchor.style.textDecoration = "none";
6389
+ return anchor;
6390
+ }
6391
+ function buildNavLink(index, href, label, template) {
6392
+ const anchor = cloneNavLinkShell(template);
6393
+ anchor.href = href;
6394
+ anchor.setAttribute("data-ohw-href-key", `nav-${index}-href`);
6395
+ const span = document.createElement("span");
6396
+ span.setAttribute("data-ohw-editable", "text");
6397
+ span.setAttribute("data-ohw-key", `nav-${index}-label`);
6398
+ span.textContent = label;
6399
+ anchor.appendChild(span);
6400
+ return anchor;
6401
+ }
6402
+ function insertNavLinkInContainer(container, anchor, afterHrefKey) {
6403
+ if (!afterHrefKey) {
6404
+ container.appendChild(anchor);
6405
+ return;
6406
+ }
6407
+ const afterEl = findCounterpartByHrefKey(afterHrefKey, container);
6408
+ if (afterEl?.parentElement === container) {
6409
+ afterEl.insertAdjacentElement("afterend", anchor);
6410
+ return;
6411
+ }
6412
+ container.appendChild(anchor);
6413
+ }
6414
+ function insertNavbarItemDom(index, href, label, afterAnchor) {
6415
+ const afterHrefKey = afterAnchor?.getAttribute("data-ohw-href-key") ?? null;
6416
+ const desktopItems = getNavbarDesktopContainer()?.querySelectorAll("[data-ohw-href-key]") ?? [];
6417
+ const drawerItems = getNavbarDrawerContainer()?.querySelectorAll("[data-ohw-href-key]") ?? [];
6418
+ const desktopTemplate = Array.from(desktopItems).find(isNavbarLinkItem) ?? null;
6419
+ const drawerTemplate = Array.from(drawerItems).find(isNavbarLinkItem) ?? null;
6420
+ let primary = null;
6421
+ const desktopContainer = getNavbarDesktopContainer();
6422
+ if (desktopContainer) {
6423
+ const desktopAnchor = buildNavLink(index, href, label, desktopTemplate);
6424
+ insertNavLinkInContainer(desktopContainer, desktopAnchor, afterHrefKey);
6425
+ primary = desktopAnchor;
6426
+ }
6427
+ const drawerContainer = getNavbarDrawerContainer();
6428
+ if (drawerContainer) {
6429
+ const drawerAnchor = buildNavLink(index, href, label, drawerTemplate);
6430
+ insertNavLinkInContainer(drawerContainer, drawerAnchor, afterHrefKey);
6431
+ if (!primary) primary = drawerAnchor;
6432
+ }
6433
+ if (!primary) {
6434
+ const fallback = buildNavLink(index, href, label, listNavbarItems()[0] ?? null);
6435
+ const nav = document.querySelector("nav");
6436
+ nav?.appendChild(fallback);
6437
+ primary = fallback;
6438
+ }
6439
+ return primary;
6440
+ }
6441
+ function applyNavOrderToContainer(container, order) {
6442
+ const seen = /* @__PURE__ */ new Set();
6443
+ for (const hrefKey of order) {
6444
+ if (seen.has(hrefKey)) continue;
6445
+ seen.add(hrefKey);
6446
+ const el = findCounterpartByHrefKey(hrefKey, container);
6447
+ if (el) container.appendChild(el);
6448
+ }
6449
+ for (const el of container.querySelectorAll("[data-ohw-href-key]")) {
6450
+ if (!isNavbarLinkItem(el)) continue;
6451
+ const key = el.getAttribute("data-ohw-href-key");
6452
+ if (!key || seen.has(key)) continue;
6453
+ container.appendChild(el);
6454
+ }
6455
+ }
6456
+ function applyNavOrder(order) {
6457
+ const desktop = getNavbarDesktopContainer();
6458
+ if (desktop) applyNavOrderToContainer(desktop, order);
6459
+ const drawer = getNavbarDrawerContainer();
6460
+ if (drawer) applyNavOrderToContainer(drawer, order);
6461
+ }
6462
+ function collectNavbarIndicesFromContent(content) {
6463
+ const indices = /* @__PURE__ */ new Set();
6464
+ for (const key of Object.keys(content)) {
6465
+ const idx = parseNavIndexFromKey(key);
6466
+ if (idx !== null) indices.add(idx);
6467
+ }
6468
+ const countRaw = content[NAV_COUNT_KEY];
6469
+ if (countRaw) {
6470
+ const count = parseInt(countRaw, 10);
6471
+ if (Number.isFinite(count) && count > 0) {
6472
+ for (let i = 0; i < count; i++) indices.add(i);
6473
+ }
6474
+ }
6475
+ return [...indices].sort((a, b) => a - b);
6476
+ }
6477
+ function navbarIndexExistsInDom(index) {
6478
+ return document.querySelector(`[data-ohw-href-key="nav-${index}-href"]`) !== null;
6479
+ }
6480
+ function reconcileNavbarItemsFromContent(content) {
6481
+ const indices = collectNavbarIndicesFromContent(content);
6482
+ for (const index of indices) {
6483
+ if (navbarIndexExistsInDom(index)) continue;
6484
+ const href = content[`nav-${index}-href`];
6485
+ const label = content[`nav-${index}-label`];
6486
+ if (!href && !label) continue;
6487
+ insertNavbarItemDom(index, href ?? "/", label ?? "Untitled", null);
6488
+ }
6489
+ const order = parseNavOrder(content);
6490
+ if (order?.length) applyNavOrder(order);
6491
+ }
6492
+ function buildNavOrderAfterInsert(afterAnchor, newHrefKey) {
6493
+ const order = getNavOrderFromDom();
6494
+ if (!afterAnchor) {
6495
+ if (!order.includes(newHrefKey)) order.push(newHrefKey);
6496
+ return order;
6497
+ }
6498
+ const afterKey = afterAnchor.getAttribute("data-ohw-href-key");
6499
+ if (!afterKey) {
6500
+ if (!order.includes(newHrefKey)) order.push(newHrefKey);
6501
+ return order;
6502
+ }
6503
+ const withoutNew = order.filter((key) => key !== newHrefKey);
6504
+ const pos = withoutNew.indexOf(afterKey);
6505
+ if (pos >= 0) {
6506
+ withoutNew.splice(pos + 1, 0, newHrefKey);
6507
+ return withoutNew;
6508
+ }
6509
+ withoutNew.push(newHrefKey);
6510
+ return withoutNew;
6511
+ }
6512
+ function insertNavbarItem(href, label, afterAnchor = null) {
6513
+ const index = getNextNavbarIndex();
6514
+ const anchor = insertNavbarItemDom(index, href, label, afterAnchor);
6515
+ if (!anchor) throw new Error("Failed to insert navbar item");
6516
+ const hrefKey = `nav-${index}-href`;
6517
+ const order = buildNavOrderAfterInsert(afterAnchor, hrefKey);
6518
+ applyNavOrder(order);
6519
+ return {
6520
+ anchor,
6521
+ index,
6522
+ hrefKey,
6523
+ labelKey: `nav-${index}-label`,
6524
+ href,
6525
+ label,
6526
+ order
6527
+ };
6528
+ }
6529
+
6530
+ // src/ui/navbar-container-chrome.tsx
6531
+ var import_lucide_react10 = require("lucide-react");
6532
+ var import_jsx_runtime21 = require("react/jsx-runtime");
6533
+ function NavbarContainerChrome({
6534
+ rect,
6535
+ onAdd
6536
+ }) {
6537
+ const chromeGap = 6;
6538
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
6539
+ "div",
6540
+ {
6541
+ "data-ohw-navbar-container-chrome": "",
6542
+ "data-ohw-bridge": "",
6543
+ className: "pointer-events-none fixed z-[2147483647]",
6544
+ style: {
6545
+ top: rect.top - chromeGap,
6546
+ left: rect.left - chromeGap,
6547
+ width: rect.width + chromeGap * 2,
6548
+ height: rect.height + chromeGap * 2
6549
+ },
6550
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
6551
+ "button",
6552
+ {
6553
+ type: "button",
6554
+ "data-ohw-navbar-add-button": "",
6555
+ 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",
6556
+ style: { top: "70%" },
6557
+ "aria-label": "Add item",
6558
+ onMouseDown: (e) => {
6559
+ e.preventDefault();
6560
+ e.stopPropagation();
6561
+ },
6562
+ onClick: (e) => {
6563
+ e.preventDefault();
6564
+ e.stopPropagation();
6565
+ onAdd();
6566
+ },
6567
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react10.Plus, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
6568
+ }
6569
+ )
6570
+ }
6571
+ );
6572
+ }
6573
+
5609
6574
  // src/ui/badge.tsx
5610
- var import_jsx_runtime20 = require("react/jsx-runtime");
6575
+ var import_jsx_runtime22 = require("react/jsx-runtime");
5611
6576
  var badgeVariants = cva(
5612
6577
  "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
6578
  {
@@ -5625,12 +6590,12 @@ var badgeVariants = cva(
5625
6590
  }
5626
6591
  );
5627
6592
  function Badge({ className, variant, ...props }) {
5628
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: cn(badgeVariants({ variant }), className), ...props });
6593
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: cn(badgeVariants({ variant }), className), ...props });
5629
6594
  }
5630
6595
 
5631
6596
  // src/OhhwellsBridge.tsx
5632
- var import_lucide_react3 = require("lucide-react");
5633
- var import_jsx_runtime21 = require("react/jsx-runtime");
6597
+ var import_lucide_react11 = require("lucide-react");
6598
+ var import_jsx_runtime23 = require("react/jsx-runtime");
5634
6599
  var PRIMARY2 = "#0885FE";
5635
6600
  var IMAGE_FADE_MS = 300;
5636
6601
  function runOpacityFade(el, onDone) {
@@ -5807,9 +6772,9 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
5807
6772
  tail.insertAdjacentElement("afterend", container);
5808
6773
  }
5809
6774
  const root = (0, import_client.createRoot)(container);
5810
- (0, import_react_dom.flushSync)(() => {
6775
+ (0, import_react_dom2.flushSync)(() => {
5811
6776
  root.render(
5812
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
6777
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
5813
6778
  SchedulingWidget,
5814
6779
  {
5815
6780
  notifyOnConnect,
@@ -5849,7 +6814,7 @@ function mountSchedulingEntries(entries, notifyOnConnect = false) {
5849
6814
  }
5850
6815
  }
5851
6816
  }
5852
- function getLinkHref(el) {
6817
+ function getLinkHref2(el) {
5853
6818
  const anchor = el instanceof HTMLAnchorElement ? el : el.querySelector("a");
5854
6819
  return anchor?.getAttribute("href") ?? "";
5855
6820
  }
@@ -5882,8 +6847,11 @@ function collectEditableNodes() {
5882
6847
  const url = raw.replace(/^url\(['"]?/, "").replace(/['"]?\)$/, "");
5883
6848
  return { key: el.dataset.ohwKey ?? "", type: "bg-image", text: url };
5884
6849
  }
6850
+ if (el.dataset.ohwEditable === "video") {
6851
+ return { key: el.dataset.ohwKey ?? "", type: "video", text: getVideoEl(el)?.src ?? "" };
6852
+ }
5885
6853
  if (el.dataset.ohwEditable === "link") {
5886
- return { key: el.dataset.ohwKey ?? "", type: "link", text: getLinkHref(el) };
6854
+ return { key: el.dataset.ohwKey ?? "", type: "link", text: getLinkHref2(el) };
5887
6855
  }
5888
6856
  return {
5889
6857
  key: el.dataset.ohwKey ?? "",
@@ -5894,9 +6862,79 @@ function collectEditableNodes() {
5894
6862
  document.querySelectorAll("[data-ohw-href-key]").forEach((el) => {
5895
6863
  const key = el.getAttribute("data-ohw-href-key") ?? "";
5896
6864
  if (!key) return;
5897
- nodes.push({ key, type: "link", text: getLinkHref(el) });
6865
+ nodes.push({ key, type: "link", text: getLinkHref2(el) });
6866
+ });
6867
+ document.querySelectorAll('[data-ohw-editable="video"]').forEach((el) => {
6868
+ const key = el.dataset.ohwKey ?? "";
6869
+ const video = getVideoEl(el);
6870
+ if (!key || !video) return;
6871
+ nodes.push({ key: `${key}${VIDEO_AUTOPLAY_SUFFIX}`, type: "video-setting", text: String(video.autoplay) });
6872
+ nodes.push({ key: `${key}${VIDEO_MUTED_SUFFIX}`, type: "video-setting", text: String(video.muted) });
6873
+ });
6874
+ return nodes;
6875
+ }
6876
+ function isMediaEditable(el) {
6877
+ const t = el.dataset.ohwEditable;
6878
+ return t === "image" || t === "bg-image" || t === "video";
6879
+ }
6880
+ var MEDIA_SELECTOR = '[data-ohw-editable="image"], [data-ohw-editable="bg-image"], [data-ohw-editable="video"]';
6881
+ var NON_MEDIA_SELECTOR = '[data-ohw-editable]:not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"]):not([data-ohw-editable="video"])';
6882
+ function getVideoEl(el) {
6883
+ return el instanceof HTMLVideoElement ? el : el.querySelector("video");
6884
+ }
6885
+ var VIDEO_AUTOPLAY_SUFFIX = "__ohw_autoplay";
6886
+ var VIDEO_MUTED_SUFFIX = "__ohw_muted";
6887
+ function isBackgroundVideo(video) {
6888
+ return video.autoplay && video.muted;
6889
+ }
6890
+ function syncVideoPlayback(video) {
6891
+ const background = isBackgroundVideo(video);
6892
+ video.controls = !background;
6893
+ if (video.autoplay) void video.play().catch(() => {
6894
+ });
6895
+ else video.pause();
6896
+ }
6897
+ function lockVideoBox(video) {
6898
+ const { width, height } = video.getBoundingClientRect();
6899
+ if (width > 0 && height > 0 && !video.style.aspectRatio) {
6900
+ video.style.aspectRatio = `${width} / ${height}`;
6901
+ }
6902
+ if (getComputedStyle(video).objectFit === "fill") {
6903
+ video.style.objectFit = "contain";
6904
+ }
6905
+ const fit = video.style.objectFit || getComputedStyle(video).objectFit;
6906
+ const bg = getComputedStyle(video).backgroundColor;
6907
+ const isTransparent = bg === "rgba(0, 0, 0, 0)" || bg === "transparent" || !bg;
6908
+ if (fit === "contain" && isTransparent) {
6909
+ video.style.backgroundColor = "var(--color-dark, #000)";
6910
+ }
6911
+ }
6912
+ function applyVideoSrc(video, url) {
6913
+ const { autoplay, muted } = video;
6914
+ lockVideoBox(video);
6915
+ video.src = url;
6916
+ video.loop = true;
6917
+ video.playsInline = true;
6918
+ video.autoplay = autoplay;
6919
+ video.muted = muted;
6920
+ video.load();
6921
+ syncVideoPlayback(video);
6922
+ }
6923
+ function applyVideoSettingNode(key, val) {
6924
+ const isAutoplay = key.endsWith(VIDEO_AUTOPLAY_SUFFIX);
6925
+ const isMuted = key.endsWith(VIDEO_MUTED_SUFFIX);
6926
+ if (!isAutoplay && !isMuted) return false;
6927
+ const suffixLen = (isAutoplay ? VIDEO_AUTOPLAY_SUFFIX : VIDEO_MUTED_SUFFIX).length;
6928
+ const baseKey = key.slice(0, key.length - suffixLen);
6929
+ const on = val === "true";
6930
+ document.querySelectorAll(`[data-ohw-key="${baseKey}"]`).forEach((el) => {
6931
+ const video = getVideoEl(el);
6932
+ if (!video) return;
6933
+ if (isAutoplay) video.autoplay = on;
6934
+ else video.muted = on;
6935
+ syncVideoPlayback(video);
5898
6936
  });
5899
- return nodes;
6937
+ return true;
5900
6938
  }
5901
6939
  function applyLinkByKey(key, val) {
5902
6940
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -5910,7 +6948,7 @@ function applyLinkByKey(key, val) {
5910
6948
  }
5911
6949
  function isInsideLinkEditor(target) {
5912
6950
  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"]')
6951
+ 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
6952
  );
5915
6953
  }
5916
6954
  function getHrefKeyFromElement(el) {
@@ -5921,7 +6959,7 @@ function getHrefKeyFromElement(el) {
5921
6959
  if (!key) return null;
5922
6960
  return { anchor, key };
5923
6961
  }
5924
- function isNavbarButton(el) {
6962
+ function isNavbarButton2(el) {
5925
6963
  return Boolean(el.closest('[data-ohw-role="navbar-button"]'));
5926
6964
  }
5927
6965
  function getNavigationItemAnchor(el) {
@@ -5933,19 +6971,8 @@ function getNavigationItemAnchor(el) {
5933
6971
  function isNavigationItem(el) {
5934
6972
  return getNavigationItemAnchor(el) !== null;
5935
6973
  }
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
6974
  function getNavigationItemReorderState(anchor) {
5948
- if (isNavbarButton(anchor)) return { key: null, disabled: false };
6975
+ if (isNavbarButton2(anchor)) return { key: null, disabled: false };
5949
6976
  return getReorderHandleStateFromAnchor(anchor);
5950
6977
  }
5951
6978
  function getReorderHandleState(el) {
@@ -5967,6 +6994,120 @@ function clearHrefKeyHover(anchor) {
5967
6994
  function isInsideNavigationItem(el) {
5968
6995
  return getNavigationItemAnchor(el) !== null;
5969
6996
  }
6997
+ function getNavigationRoot(el) {
6998
+ const explicit = el.closest("[data-ohw-nav-root]");
6999
+ if (explicit) return explicit;
7000
+ return el.closest("nav, footer, aside");
7001
+ }
7002
+ function findFooterItemGroup(item) {
7003
+ const footer = item.closest("footer");
7004
+ if (!footer) return null;
7005
+ let node = item.parentElement;
7006
+ while (node && node !== footer) {
7007
+ const hasDirectNavItems = Array.from(
7008
+ node.querySelectorAll(":scope > [data-ohw-href-key]")
7009
+ ).some(isNavigationItem);
7010
+ if (hasDirectNavItems) return node;
7011
+ node = node.parentElement;
7012
+ }
7013
+ return null;
7014
+ }
7015
+ function isNavigationRoot(el) {
7016
+ return el.hasAttribute("data-ohw-nav-root") || el.matches("nav, footer, aside");
7017
+ }
7018
+ function isInferredFooterGroup(el) {
7019
+ const footer = el.closest("footer");
7020
+ if (!footer || el === footer) return false;
7021
+ return Array.from(el.querySelectorAll(":scope > [data-ohw-href-key]")).some(isNavigationItem);
7022
+ }
7023
+ function isNavigationContainer(el) {
7024
+ return el.hasAttribute("data-ohw-nav-container") || isNavigationRoot(el) || isInferredFooterGroup(el);
7025
+ }
7026
+ function isPointOverNavigation(x, y) {
7027
+ const navRoot = document.querySelector("[data-ohw-nav-root]") ?? document.querySelector("nav");
7028
+ if (!navRoot) return false;
7029
+ const r2 = navRoot.getBoundingClientRect();
7030
+ return x >= r2.left && x <= r2.right && y >= r2.top && y <= r2.bottom;
7031
+ }
7032
+ function isPointOverNavItem(container, x, y) {
7033
+ return Array.from(container.querySelectorAll("[data-ohw-href-key]")).some((el) => {
7034
+ if (!isNavigationItem(el) || isNavbarButton2(el)) return false;
7035
+ const itemRect = el.getBoundingClientRect();
7036
+ return x >= itemRect.left && x <= itemRect.right && y >= itemRect.top && y <= itemRect.bottom;
7037
+ });
7038
+ }
7039
+ function resolveNavContainerSelectionTarget(target, clientX, clientY) {
7040
+ if (getNavigationItemAnchor(target)) return null;
7041
+ if (target.closest('[data-ohw-role="navbar-button"]')) return null;
7042
+ const plainLink = target.closest("a");
7043
+ if (plainLink && !plainLink.hasAttribute("data-ohw-href-key") && !plainLink.closest("[data-ohw-nav-container]")) {
7044
+ return null;
7045
+ }
7046
+ const fromClosest = target.closest("[data-ohw-nav-container]");
7047
+ if (fromClosest && !isPointOverNavItem(fromClosest, clientX, clientY)) {
7048
+ return fromClosest;
7049
+ }
7050
+ const navRoot = target.closest("[data-ohw-nav-root]");
7051
+ if (!navRoot) return null;
7052
+ const container = navRoot.querySelector("[data-ohw-nav-container]");
7053
+ if (!container || isPointOverNavItem(container, clientX, clientY)) return null;
7054
+ if (target === navRoot || target.hasAttribute("data-ohw-nav-root")) {
7055
+ return container;
7056
+ }
7057
+ return null;
7058
+ }
7059
+ function getNavigationSelectionParent(el) {
7060
+ if (isNavigationItem(el)) {
7061
+ const explicit = el.closest("[data-ohw-nav-container]");
7062
+ if (explicit) return explicit;
7063
+ const footerGroup = findFooterItemGroup(el);
7064
+ if (footerGroup) return footerGroup;
7065
+ return getNavigationRoot(el);
7066
+ }
7067
+ if (el.hasAttribute("data-ohw-nav-container") || isInferredFooterGroup(el)) {
7068
+ return getNavigationRoot(el);
7069
+ }
7070
+ return null;
7071
+ }
7072
+ function placeCaretAtPoint(el, x, y) {
7073
+ const selection = window.getSelection();
7074
+ if (!selection) return;
7075
+ if (typeof document.caretRangeFromPoint === "function") {
7076
+ const range = document.caretRangeFromPoint(x, y);
7077
+ if (range && el.contains(range.startContainer)) {
7078
+ selection.removeAllRanges();
7079
+ selection.addRange(range);
7080
+ return;
7081
+ }
7082
+ }
7083
+ const caretPositionFromPoint = document.caretPositionFromPoint;
7084
+ if (typeof caretPositionFromPoint === "function") {
7085
+ const position = caretPositionFromPoint(x, y);
7086
+ if (position && el.contains(position.offsetNode)) {
7087
+ const range = document.createRange();
7088
+ range.setStart(position.offsetNode, position.offset);
7089
+ range.collapse(true);
7090
+ selection.removeAllRanges();
7091
+ selection.addRange(range);
7092
+ }
7093
+ }
7094
+ }
7095
+ function selectAllTextInEditable(el) {
7096
+ const selection = window.getSelection();
7097
+ if (!selection) return;
7098
+ const range = document.createRange();
7099
+ range.selectNodeContents(el);
7100
+ selection.removeAllRanges();
7101
+ selection.addRange(range);
7102
+ }
7103
+ function getNavigationLabelEditable(target) {
7104
+ const editable = target.closest('[data-ohw-editable="text"]');
7105
+ if (!editable) return null;
7106
+ const hrefCtx = getHrefKeyFromElement(editable);
7107
+ const navAnchor = hrefCtx ? getNavigationItemAnchor(hrefCtx.anchor) : null;
7108
+ if (!navAnchor) return null;
7109
+ return { editable, navAnchor };
7110
+ }
5970
7111
  function collectSections() {
5971
7112
  return collectSectionsFromDom();
5972
7113
  }
@@ -6090,6 +7231,8 @@ var ICONS = {
6090
7231
  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
7232
  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
7233
  };
7234
+ var TOOLBAR_PILL_PADDING = 2;
7235
+ var TOOLBAR_TOGGLE_GAP = 4;
6093
7236
  var TOOLBAR_GROUPS = [
6094
7237
  [
6095
7238
  { cmd: "bold", title: "Bold" },
@@ -6114,7 +7257,7 @@ function EditGlowChrome({
6114
7257
  dragDisabled = false
6115
7258
  }) {
6116
7259
  const GAP = 6;
6117
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
7260
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
6118
7261
  "div",
6119
7262
  {
6120
7263
  ref: elRef,
@@ -6129,7 +7272,7 @@ function EditGlowChrome({
6129
7272
  zIndex: 2147483646
6130
7273
  },
6131
7274
  children: [
6132
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7275
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6133
7276
  "div",
6134
7277
  {
6135
7278
  style: {
@@ -6142,7 +7285,7 @@ function EditGlowChrome({
6142
7285
  }
6143
7286
  }
6144
7287
  ),
6145
- reorderHrefKey && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7288
+ reorderHrefKey && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6146
7289
  "div",
6147
7290
  {
6148
7291
  "data-ohw-drag-handle-container": "",
@@ -6154,7 +7297,7 @@ function EditGlowChrome({
6154
7297
  transform: "translate(calc(-100% - 7px), -50%)",
6155
7298
  pointerEvents: dragDisabled ? "none" : "auto"
6156
7299
  },
6157
- children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7300
+ children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6158
7301
  DragHandle,
6159
7302
  {
6160
7303
  "aria-label": `Reorder ${reorderHrefKey}`,
@@ -6258,7 +7401,7 @@ function FloatingToolbar({
6258
7401
  onEditLink
6259
7402
  }) {
6260
7403
  const { top, left, transform } = calcToolbarPos(rect, parentScroll, 330);
6261
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7404
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6262
7405
  "div",
6263
7406
  {
6264
7407
  ref: elRef,
@@ -6268,14 +7411,23 @@ function FloatingToolbar({
6268
7411
  left,
6269
7412
  transform,
6270
7413
  zIndex: 2147483647,
7414
+ background: "#fff",
7415
+ border: "1px solid #E7E5E4",
7416
+ borderRadius: 6,
7417
+ boxShadow: "0px 2px 4px -2px rgba(0,0,0,.1),0px 4px 6px -1px rgba(0,0,0,.1)",
7418
+ display: "flex",
7419
+ alignItems: "center",
7420
+ padding: TOOLBAR_PILL_PADDING,
7421
+ gap: TOOLBAR_TOGGLE_GAP,
7422
+ fontFamily: "sans-serif",
6271
7423
  pointerEvents: "auto"
6272
7424
  },
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, {}),
7425
+ children: /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(CustomToolbar, { children: [
7426
+ TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_react8.default.Fragment, { children: [
7427
+ gi > 0 && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(CustomToolbarDivider, {}),
6276
7428
  btns.map((btn) => {
6277
7429
  const isActive = activeCommands.has(btn.cmd);
6278
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7430
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6279
7431
  CustomToolbarButton,
6280
7432
  {
6281
7433
  title: btn.title,
@@ -6284,7 +7436,7 @@ function FloatingToolbar({
6284
7436
  e.preventDefault();
6285
7437
  onCommand(btn.cmd);
6286
7438
  },
6287
- children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7439
+ children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6288
7440
  "svg",
6289
7441
  {
6290
7442
  width: "16",
@@ -6305,7 +7457,7 @@ function FloatingToolbar({
6305
7457
  );
6306
7458
  })
6307
7459
  ] }, gi)),
6308
- showEditLink ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7460
+ showEditLink ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6309
7461
  CustomToolbarButton,
6310
7462
  {
6311
7463
  type: "button",
@@ -6319,7 +7471,7 @@ function FloatingToolbar({
6319
7471
  e.preventDefault();
6320
7472
  e.stopPropagation();
6321
7473
  },
6322
- children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react3.Link, { className: "size-4 shrink-0", "aria-hidden": true })
7474
+ children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_lucide_react11.Link, { className: "size-4 shrink-0", "aria-hidden": true })
6323
7475
  }
6324
7476
  ) : null
6325
7477
  ] })
@@ -6336,7 +7488,7 @@ function StateToggle({
6336
7488
  states,
6337
7489
  onStateChange
6338
7490
  }) {
6339
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7491
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6340
7492
  ToggleGroup,
6341
7493
  {
6342
7494
  "data-ohw-state-toggle": "",
@@ -6350,7 +7502,7 @@ function StateToggle({
6350
7502
  left: rect.right - 8,
6351
7503
  transform: "translateX(-100%)"
6352
7504
  },
6353
- children: states.map((state) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
7505
+ children: states.map((state) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
6354
7506
  }
6355
7507
  );
6356
7508
  }
@@ -6373,12 +7525,12 @@ function resolveSubdomain(subdomainFromQuery) {
6373
7525
  return "";
6374
7526
  }
6375
7527
  function OhhwellsBridge() {
6376
- const pathname = (0, import_navigation.usePathname)();
6377
- const router = (0, import_navigation.useRouter)();
6378
- const searchParams = (0, import_navigation.useSearchParams)();
7528
+ const pathname = (0, import_navigation2.usePathname)();
7529
+ const router = (0, import_navigation2.useRouter)();
7530
+ const searchParams = (0, import_navigation2.useSearchParams)();
6379
7531
  const isEditMode = isEditSessionActive();
6380
- const [bridgeRoot, setBridgeRoot] = (0, import_react6.useState)(null);
6381
- (0, import_react6.useEffect)(() => {
7532
+ const [bridgeRoot, setBridgeRoot] = (0, import_react8.useState)(null);
7533
+ (0, import_react8.useEffect)(() => {
6382
7534
  const figtreeFontId = "ohw-figtree-font";
6383
7535
  if (!document.getElementById(figtreeFontId)) {
6384
7536
  const preconnect1 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.googleapis.com" });
@@ -6386,7 +7538,10 @@ function OhhwellsBridge() {
6386
7538
  const stylesheet = Object.assign(document.createElement("link"), {
6387
7539
  id: figtreeFontId,
6388
7540
  rel: "stylesheet",
6389
- href: "https://fonts.googleapis.com/css2?family=Figtree:ital,wght@0,300..900;1,300..900&display=swap"
7541
+ // Inter is loaded alongside Figtree for the media overlay's Replace button. The bridge
7542
+ // renders inside the customer's document, so it cannot assume any font is present — an
7543
+ // unloaded family would silently fall back to whatever the template happens to use.
7544
+ 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
7545
  });
6391
7546
  document.head.append(preconnect1, preconnect2, stylesheet);
6392
7547
  }
@@ -6403,80 +7558,91 @@ function OhhwellsBridge() {
6403
7558
  const subdomainFromQuery = searchParams.get("subdomain");
6404
7559
  const subdomain = resolveSubdomain(subdomainFromQuery);
6405
7560
  useLinkHrefGuardian(pathname, subdomain, isEditMode);
6406
- const postToParent = (0, import_react6.useCallback)((data) => {
7561
+ const postToParent2 = (0, import_react8.useCallback)((data) => {
6407
7562
  if (typeof window !== "undefined" && window.parent !== window) {
6408
7563
  window.parent.postMessage(data, "*");
6409
7564
  }
6410
7565
  }, []);
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) => {
7566
+ const [fetchState, setFetchState] = (0, import_react8.useState)("idle");
7567
+ const autoSaveTimers = (0, import_react8.useRef)(/* @__PURE__ */ new Map());
7568
+ const activeElRef = (0, import_react8.useRef)(null);
7569
+ const selectedElRef = (0, import_react8.useRef)(null);
7570
+ const originalContentRef = (0, import_react8.useRef)(null);
7571
+ const activeStateElRef = (0, import_react8.useRef)(null);
7572
+ const parentScrollRef = (0, import_react8.useRef)(null);
7573
+ const visibleViewportRef = (0, import_react8.useRef)(null);
7574
+ const [dialogPortalContainer, setDialogPortalContainer] = (0, import_react8.useState)(null);
7575
+ const attachVisibleViewport = (0, import_react8.useCallback)((node) => {
6421
7576
  visibleViewportRef.current = node;
6422
7577
  setDialogPortalContainer(node);
6423
7578
  if (node) applyVisibleViewport(node, parentScrollRef.current);
6424
7579
  }, []);
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)(() => {
7580
+ const toolbarElRef = (0, import_react8.useRef)(null);
7581
+ const glowElRef = (0, import_react8.useRef)(null);
7582
+ const hoveredImageRef = (0, import_react8.useRef)(null);
7583
+ const hoveredImageHasTextOverlapRef = (0, import_react8.useRef)(false);
7584
+ const dragOverElRef = (0, import_react8.useRef)(null);
7585
+ const [mediaHover, setMediaHover] = (0, import_react8.useState)(null);
7586
+ const [uploadingRects, setUploadingRects] = (0, import_react8.useState)({});
7587
+ const hoveredGapRef = (0, import_react8.useRef)(null);
7588
+ const imageUnhoverTimerRef = (0, import_react8.useRef)(null);
7589
+ const imageShowTimerRef = (0, import_react8.useRef)(null);
7590
+ const editStylesRef = (0, import_react8.useRef)(null);
7591
+ const activateRef = (0, import_react8.useRef)(() => {
6434
7592
  });
6435
- const deactivateRef = (0, import_react6.useRef)(() => {
7593
+ const deactivateRef = (0, import_react8.useRef)(() => {
6436
7594
  });
6437
- const selectRef = (0, import_react6.useRef)(() => {
7595
+ const selectRef = (0, import_react8.useRef)(() => {
6438
7596
  });
6439
- const deselectRef = (0, import_react6.useRef)(() => {
7597
+ const selectFrameRef = (0, import_react8.useRef)(() => {
6440
7598
  });
6441
- const reselectNavigationItemRef = (0, import_react6.useRef)(() => {
7599
+ const deselectRef = (0, import_react8.useRef)(() => {
6442
7600
  });
6443
- const commitNavigationTextEditRef = (0, import_react6.useRef)(() => {
7601
+ const reselectNavigationItemRef = (0, import_react8.useRef)(() => {
6444
7602
  });
6445
- const refreshActiveCommandsRef = (0, import_react6.useRef)(() => {
7603
+ const commitNavigationTextEditRef = (0, import_react8.useRef)(() => {
6446
7604
  });
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");
7605
+ const refreshActiveCommandsRef = (0, import_react8.useRef)(() => {
7606
+ });
7607
+ const postToParentRef = (0, import_react8.useRef)(postToParent2);
7608
+ postToParentRef.current = postToParent2;
7609
+ const sectionsLoadedRef = (0, import_react8.useRef)(false);
7610
+ const pendingScheduleConfigRequests = (0, import_react8.useRef)([]);
7611
+ const [toolbarRect, setToolbarRect] = (0, import_react8.useState)(null);
7612
+ const [toolbarVariant, setToolbarVariant] = (0, import_react8.useState)("none");
7613
+ const toolbarVariantRef = (0, import_react8.useRef)("none");
6454
7614
  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);
7615
+ const [reorderHrefKey, setReorderHrefKey] = (0, import_react8.useState)(null);
7616
+ const [reorderDragDisabled, setReorderDragDisabled] = (0, import_react8.useState)(false);
7617
+ const [toggleState, setToggleState] = (0, import_react8.useState)(null);
7618
+ const [maxBadge, setMaxBadge] = (0, import_react8.useState)(null);
7619
+ const [activeCommands, setActiveCommands] = (0, import_react8.useState)(/* @__PURE__ */ new Set());
7620
+ const [sectionGap, setSectionGap] = (0, import_react8.useState)(null);
7621
+ const [toolbarShowEditLink, setToolbarShowEditLink] = (0, import_react8.useState)(false);
7622
+ const hoveredNavContainerRef = (0, import_react8.useRef)(null);
7623
+ const [hoveredNavContainerRect, setHoveredNavContainerRect] = (0, import_react8.useState)(null);
7624
+ const hoveredItemElRef = (0, import_react8.useRef)(null);
7625
+ const [hoveredItemRect, setHoveredItemRect] = (0, import_react8.useState)(null);
7626
+ const siblingHintElRef = (0, import_react8.useRef)(null);
7627
+ const [siblingHintRect, setSiblingHintRect] = (0, import_react8.useState)(null);
7628
+ const [isItemDragging, setIsItemDragging] = (0, import_react8.useState)(false);
7629
+ const [linkPopover, setLinkPopover] = (0, import_react8.useState)(null);
7630
+ const linkPopoverSessionRef = (0, import_react8.useRef)(null);
7631
+ const addNavAfterAnchorRef = (0, import_react8.useRef)(null);
7632
+ const editContentRef = (0, import_react8.useRef)({});
7633
+ const [sitePages, setSitePages] = (0, import_react8.useState)([]);
7634
+ const [sectionsByPath, setSectionsByPath] = (0, import_react8.useState)({});
7635
+ const sectionsPrefetchGenRef = (0, import_react8.useRef)(0);
7636
+ const setLinkPopoverRef = (0, import_react8.useRef)(setLinkPopover);
7637
+ const linkPopoverPanelRef = (0, import_react8.useRef)(null);
7638
+ const linkPopoverOpenRef = (0, import_react8.useRef)(false);
7639
+ const linkPopoverGraceUntilRef = (0, import_react8.useRef)(0);
6475
7640
  setLinkPopoverRef.current = setLinkPopover;
7641
+ linkPopoverSessionRef.current = linkPopover;
6476
7642
  const bumpLinkPopoverGrace = () => {
6477
7643
  linkPopoverGraceUntilRef.current = Date.now() + 350;
6478
7644
  };
6479
- const runSectionsPrefetch = (0, import_react6.useCallback)((pages) => {
7645
+ const runSectionsPrefetch = (0, import_react8.useCallback)((pages) => {
6480
7646
  if (!isEditMode || shouldUseDevFixtures() || pages.length === 0) return;
6481
7647
  const gen = ++sectionsPrefetchGenRef.current;
6482
7648
  const paths = pages.map((p) => p.path);
@@ -6495,9 +7661,9 @@ function OhhwellsBridge() {
6495
7661
  );
6496
7662
  });
6497
7663
  }, [isEditMode, pathname]);
6498
- const runSectionsPrefetchRef = (0, import_react6.useRef)(runSectionsPrefetch);
7664
+ const runSectionsPrefetchRef = (0, import_react8.useRef)(runSectionsPrefetch);
6499
7665
  runSectionsPrefetchRef.current = runSectionsPrefetch;
6500
- (0, import_react6.useEffect)(() => {
7666
+ (0, import_react8.useEffect)(() => {
6501
7667
  if (!linkPopover) return;
6502
7668
  if (hoveredImageRef.current) {
6503
7669
  hoveredImageRef.current = null;
@@ -6505,33 +7671,9 @@ function OhhwellsBridge() {
6505
7671
  }
6506
7672
  hoveredGapRef.current = null;
6507
7673
  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)(() => {
7674
+ postToParent2({ type: "ow:image-unhover" });
7675
+ }, [linkPopover, postToParent2]);
7676
+ (0, import_react8.useEffect)(() => {
6535
7677
  if (!isEditMode) return;
6536
7678
  const useFixtures = shouldUseDevFixtures();
6537
7679
  if (useFixtures) {
@@ -6552,17 +7694,17 @@ function OhhwellsBridge() {
6552
7694
  runSectionsPrefetchRef.current(mapped);
6553
7695
  };
6554
7696
  window.addEventListener("message", onSitePages);
6555
- if (!useFixtures) postToParent({ type: "ow:request-site-pages" });
7697
+ if (!useFixtures) postToParent2({ type: "ow:request-site-pages" });
6556
7698
  return () => window.removeEventListener("message", onSitePages);
6557
- }, [isEditMode, postToParent]);
6558
- (0, import_react6.useEffect)(() => {
7699
+ }, [isEditMode, postToParent2]);
7700
+ (0, import_react8.useEffect)(() => {
6559
7701
  if (!isEditMode || shouldUseDevFixtures()) return;
6560
7702
  void loadAllSectionsManifest().then((manifest) => {
6561
7703
  if (Object.keys(manifest).length === 0) return;
6562
7704
  setSectionsByPath((prev) => ({ ...manifest, ...prev }));
6563
7705
  });
6564
7706
  }, [isEditMode]);
6565
- (0, import_react6.useEffect)(() => {
7707
+ (0, import_react8.useEffect)(() => {
6566
7708
  const update = () => {
6567
7709
  const el = activeElRef.current ?? selectedElRef.current;
6568
7710
  if (el) setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
@@ -6586,10 +7728,10 @@ function OhhwellsBridge() {
6586
7728
  vvp.removeEventListener("resize", update);
6587
7729
  };
6588
7730
  }, []);
6589
- const refreshStateRules = (0, import_react6.useCallback)(() => {
7731
+ const refreshStateRules = (0, import_react8.useCallback)(() => {
6590
7732
  editStylesRef.current?.forceHover && (editStylesRef.current.forceHover.textContent = collectStateRules());
6591
7733
  }, []);
6592
- const processConfigRequest = (0, import_react6.useCallback)((insertAfterVal) => {
7734
+ const processConfigRequest = (0, import_react8.useCallback)((insertAfterVal) => {
6593
7735
  const tracker = getSectionsTracker();
6594
7736
  let entries = [];
6595
7737
  try {
@@ -6612,7 +7754,7 @@ function OhhwellsBridge() {
6612
7754
  }
6613
7755
  window.postMessage({ type: "ow:schedule-config", insertAfter: insertAfterVal, scheduleId: null }, "*");
6614
7756
  }, [isEditMode]);
6615
- const deactivate = (0, import_react6.useCallback)(() => {
7757
+ const deactivate = (0, import_react8.useCallback)(() => {
6616
7758
  const el = activeElRef.current;
6617
7759
  if (!el) return;
6618
7760
  const key = el.dataset.ohwKey;
@@ -6641,21 +7783,23 @@ function OhhwellsBridge() {
6641
7783
  setMaxBadge(null);
6642
7784
  setActiveCommands(/* @__PURE__ */ new Set());
6643
7785
  setToolbarShowEditLink(false);
6644
- postToParent({ type: "ow:exit-edit" });
6645
- }, [postToParent]);
6646
- const deselect = (0, import_react6.useCallback)(() => {
7786
+ postToParent2({ type: "ow:exit-edit" });
7787
+ }, [postToParent2]);
7788
+ const deselect = (0, import_react8.useCallback)(() => {
6647
7789
  selectedElRef.current = null;
6648
7790
  setReorderHrefKey(null);
6649
7791
  setReorderDragDisabled(false);
6650
7792
  siblingHintElRef.current = null;
6651
7793
  setSiblingHintRect(null);
6652
7794
  setIsItemDragging(false);
7795
+ hoveredNavContainerRef.current = null;
7796
+ setHoveredNavContainerRect(null);
6653
7797
  if (!activeElRef.current) {
6654
7798
  setToolbarRect(null);
6655
7799
  setToolbarVariant("none");
6656
7800
  }
6657
7801
  }, []);
6658
- const reselectNavigationItem = (0, import_react6.useCallback)((navAnchor) => {
7802
+ const reselectNavigationItem = (0, import_react8.useCallback)((navAnchor) => {
6659
7803
  selectedElRef.current = navAnchor;
6660
7804
  const { key, disabled } = getNavigationItemReorderState(navAnchor);
6661
7805
  setReorderHrefKey(key);
@@ -6665,7 +7809,7 @@ function OhhwellsBridge() {
6665
7809
  setToolbarShowEditLink(false);
6666
7810
  setActiveCommands(/* @__PURE__ */ new Set());
6667
7811
  }, []);
6668
- const commitNavigationTextEdit = (0, import_react6.useCallback)((navAnchor) => {
7812
+ const commitNavigationTextEdit = (0, import_react8.useCallback)((navAnchor) => {
6669
7813
  const el = activeElRef.current;
6670
7814
  if (!el) return;
6671
7815
  const key = el.dataset.ohwKey;
@@ -6678,9 +7822,9 @@ function OhhwellsBridge() {
6678
7822
  const html = sanitizeHtml(el.innerHTML);
6679
7823
  const original = originalContentRef.current ?? "";
6680
7824
  if (html !== sanitizeHtml(original)) {
6681
- postToParent({ type: "ow:change", nodes: [{ key, text: html }] });
7825
+ postToParent2({ type: "ow:change", nodes: [{ key, text: html }] });
6682
7826
  const h = document.documentElement.scrollHeight;
6683
- if (h > 50) postToParent({ type: "ow:height", height: h });
7827
+ if (h > 50) postToParent2({ type: "ow:height", height: h });
6684
7828
  }
6685
7829
  }
6686
7830
  el.removeAttribute("contenteditable");
@@ -6688,31 +7832,38 @@ function OhhwellsBridge() {
6688
7832
  setMaxBadge(null);
6689
7833
  setActiveCommands(/* @__PURE__ */ new Set());
6690
7834
  setToolbarShowEditLink(false);
6691
- postToParent({ type: "ow:exit-edit" });
7835
+ postToParent2({ type: "ow:exit-edit" });
6692
7836
  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());
7837
+ }, [postToParent2, reselectNavigationItem]);
7838
+ const handleAddTopLevelNavItem = (0, import_react8.useCallback)(() => {
7839
+ const items = listNavbarItems();
7840
+ addNavAfterAnchorRef.current = items[items.length - 1] ?? null;
7841
+ deselectRef.current();
7842
+ deactivateRef.current();
7843
+ bumpLinkPopoverGrace();
7844
+ setLinkPopover({
7845
+ key: "__add-nav__",
7846
+ mode: "create",
7847
+ intent: "add-nav"
7848
+ });
6700
7849
  }, []);
6701
- const handleItemDragStart = (0, import_react6.useCallback)(() => {
7850
+ const handleItemDragStart = (0, import_react8.useCallback)(() => {
6702
7851
  siblingHintElRef.current = null;
6703
7852
  setSiblingHintRect(null);
6704
7853
  setIsItemDragging(true);
6705
7854
  }, []);
6706
- const handleItemDragEnd = (0, import_react6.useCallback)(() => {
7855
+ const handleItemDragEnd = (0, import_react8.useCallback)(() => {
6707
7856
  setIsItemDragging(false);
6708
7857
  }, []);
6709
7858
  reselectNavigationItemRef.current = reselectNavigationItem;
6710
7859
  commitNavigationTextEditRef.current = commitNavigationTextEdit;
6711
- const select = (0, import_react6.useCallback)((anchor) => {
7860
+ const select = (0, import_react8.useCallback)((anchor) => {
6712
7861
  if (!isNavigationItem(anchor)) return;
6713
7862
  if (activeElRef.current) deactivate();
6714
7863
  selectedElRef.current = anchor;
6715
7864
  clearHrefKeyHover(anchor);
7865
+ hoveredNavContainerRef.current = null;
7866
+ setHoveredNavContainerRect(null);
6716
7867
  setHoveredItemRect(null);
6717
7868
  hoveredItemElRef.current = null;
6718
7869
  siblingHintElRef.current = null;
@@ -6726,13 +7877,32 @@ function OhhwellsBridge() {
6726
7877
  setToolbarShowEditLink(false);
6727
7878
  setActiveCommands(/* @__PURE__ */ new Set());
6728
7879
  }, [deactivate]);
6729
- const activate = (0, import_react6.useCallback)((el) => {
7880
+ const selectFrame = (0, import_react8.useCallback)((el) => {
7881
+ if (!isNavigationContainer(el)) return;
7882
+ if (activeElRef.current) deactivate();
7883
+ selectedElRef.current = el;
7884
+ clearHrefKeyHover(el);
7885
+ hoveredNavContainerRef.current = null;
7886
+ setHoveredNavContainerRect(null);
7887
+ setHoveredItemRect(null);
7888
+ hoveredItemElRef.current = null;
7889
+ siblingHintElRef.current = null;
7890
+ setSiblingHintRect(null);
7891
+ setIsItemDragging(false);
7892
+ setReorderHrefKey(null);
7893
+ setReorderDragDisabled(false);
7894
+ setToolbarVariant("select-frame");
7895
+ setToolbarRect(el.getBoundingClientRect());
7896
+ setToolbarShowEditLink(false);
7897
+ setActiveCommands(/* @__PURE__ */ new Set());
7898
+ }, [deactivate]);
7899
+ const activate = (0, import_react8.useCallback)((el, options) => {
6730
7900
  if (activeElRef.current === el) return;
6731
7901
  selectedElRef.current = null;
6732
7902
  deactivate();
6733
7903
  if (hoveredImageRef.current) {
6734
7904
  hoveredImageRef.current = null;
6735
- postToParentRef.current({ type: "ow:image-unhover" });
7905
+ setMediaHover(null);
6736
7906
  }
6737
7907
  setToolbarVariant("rich-text");
6738
7908
  siblingHintElRef.current = null;
@@ -6744,6 +7914,9 @@ function OhhwellsBridge() {
6744
7914
  activeElRef.current = el;
6745
7915
  originalContentRef.current = el.innerHTML;
6746
7916
  el.focus();
7917
+ if (options?.caretX !== void 0 && options?.caretY !== void 0) {
7918
+ placeCaretAtPoint(el, options.caretX, options.caretY);
7919
+ }
6747
7920
  setToolbarShowEditLink(Boolean(getHrefKeyFromElement(el)));
6748
7921
  const navAnchor = getNavigationItemAnchor(el);
6749
7922
  if (navAnchor) {
@@ -6755,14 +7928,15 @@ function OhhwellsBridge() {
6755
7928
  setReorderDragDisabled(reorderDisabled);
6756
7929
  }
6757
7930
  setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
6758
- postToParent({ type: "ow:enter-edit", key: el.dataset.ohwKey });
7931
+ postToParent2({ type: "ow:enter-edit", key: el.dataset.ohwKey });
6759
7932
  requestAnimationFrame(() => refreshActiveCommandsRef.current());
6760
- }, [deactivate, postToParent]);
7933
+ }, [deactivate, postToParent2]);
6761
7934
  activateRef.current = activate;
6762
7935
  deactivateRef.current = deactivate;
6763
7936
  selectRef.current = select;
7937
+ selectFrameRef.current = selectFrame;
6764
7938
  deselectRef.current = deselect;
6765
- (0, import_react6.useLayoutEffect)(() => {
7939
+ (0, import_react8.useLayoutEffect)(() => {
6766
7940
  if (!subdomain || isEditMode) {
6767
7941
  setFetchState("done");
6768
7942
  return;
@@ -6771,6 +7945,7 @@ function OhhwellsBridge() {
6771
7945
  const imageLoads = [];
6772
7946
  for (const [key, val] of Object.entries(content)) {
6773
7947
  if (key === "__ohw_sections") continue;
7948
+ if (applyVideoSettingNode(key, val)) continue;
6774
7949
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
6775
7950
  if (el.dataset.ohwEditable === "image") {
6776
7951
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -6784,6 +7959,15 @@ function OhhwellsBridge() {
6784
7959
  } else if (el.dataset.ohwEditable === "bg-image") {
6785
7960
  const next = `url('${val}')`;
6786
7961
  if (el.style.backgroundImage !== next) el.style.backgroundImage = next;
7962
+ } else if (el.dataset.ohwEditable === "video") {
7963
+ const video = getVideoEl(el);
7964
+ if (video && video.src !== val) {
7965
+ applyVideoSrc(video, val);
7966
+ imageLoads.push(new Promise((resolve) => {
7967
+ video.onloadeddata = () => resolve();
7968
+ video.onerror = () => resolve();
7969
+ }));
7970
+ }
6787
7971
  } else if (el.dataset.ohwEditable === "link") {
6788
7972
  applyLinkHref(el, val);
6789
7973
  } else if (el.innerHTML !== val) {
@@ -6792,6 +7976,7 @@ function OhhwellsBridge() {
6792
7976
  });
6793
7977
  applyLinkByKey(key, val);
6794
7978
  }
7979
+ reconcileNavbarItemsFromContent(content);
6795
7980
  enforceLinkHrefs();
6796
7981
  initSectionsFromContent(content, true);
6797
7982
  sectionsLoadedRef.current = true;
@@ -6820,7 +8005,7 @@ function OhhwellsBridge() {
6820
8005
  cancelled = true;
6821
8006
  };
6822
8007
  }, [subdomain, isEditMode]);
6823
- (0, import_react6.useEffect)(() => {
8008
+ (0, import_react8.useEffect)(() => {
6824
8009
  if (!subdomain || isEditMode) return;
6825
8010
  let debounceTimer = null;
6826
8011
  let observer = null;
@@ -6832,6 +8017,7 @@ function OhhwellsBridge() {
6832
8017
  try {
6833
8018
  for (const [key, val] of Object.entries(content)) {
6834
8019
  if (key === "__ohw_sections") continue;
8020
+ if (applyVideoSettingNode(key, val)) continue;
6835
8021
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
6836
8022
  if (el.dataset.ohwEditable === "image") {
6837
8023
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -6839,6 +8025,9 @@ function OhhwellsBridge() {
6839
8025
  } else if (el.dataset.ohwEditable === "bg-image") {
6840
8026
  const next = `url('${val}')`;
6841
8027
  if (el.style.backgroundImage !== next) el.style.backgroundImage = next;
8028
+ } else if (el.dataset.ohwEditable === "video") {
8029
+ const video = getVideoEl(el);
8030
+ if (video && video.src !== val) applyVideoSrc(video, val);
6842
8031
  } else if (el.dataset.ohwEditable === "link") {
6843
8032
  applyLinkHref(el, val);
6844
8033
  } else if (el.innerHTML !== val) {
@@ -6847,6 +8036,7 @@ function OhhwellsBridge() {
6847
8036
  });
6848
8037
  applyLinkByKey(key, val);
6849
8038
  }
8039
+ reconcileNavbarItemsFromContent(content);
6850
8040
  } finally {
6851
8041
  observer?.observe(document.body, { childList: true, subtree: true });
6852
8042
  }
@@ -6864,26 +8054,44 @@ function OhhwellsBridge() {
6864
8054
  if (debounceTimer) clearTimeout(debounceTimer);
6865
8055
  };
6866
8056
  }, [subdomain, isEditMode, pathname]);
6867
- (0, import_react6.useLayoutEffect)(() => {
8057
+ (0, import_react8.useLayoutEffect)(() => {
6868
8058
  const el = document.getElementById("ohw-loader");
6869
8059
  if (!el) return;
6870
8060
  const visible = Boolean(subdomain) && fetchState !== "done";
6871
8061
  el.style.display = visible ? "flex" : "none";
6872
8062
  }, [subdomain, fetchState]);
6873
- (0, import_react6.useEffect)(() => {
6874
- postToParent({ type: "ow:navigation", path: pathname });
6875
- }, [pathname, postToParent]);
6876
- (0, import_react6.useEffect)(() => {
8063
+ (0, import_react8.useEffect)(() => {
8064
+ postToParent2({ type: "ow:navigation", path: pathname });
8065
+ }, [pathname, postToParent2]);
8066
+ (0, import_react8.useEffect)(() => {
6877
8067
  if (!isEditMode) return;
8068
+ if (linkPopoverSessionRef.current?.intent === "add-nav") return;
8069
+ if (document.querySelector("[data-ohw-section-picker]")) return;
6878
8070
  setLinkPopover(null);
6879
8071
  deselectRef.current();
6880
8072
  deactivateRef.current();
6881
8073
  }, [pathname, isEditMode]);
6882
- (0, import_react6.useEffect)(() => {
8074
+ (0, import_react8.useEffect)(() => {
8075
+ const contentForNav = () => {
8076
+ if (isEditMode) return editContentRef.current;
8077
+ if (!subdomain) return {};
8078
+ return contentCache.get(subdomain) ?? {};
8079
+ };
8080
+ const run = () => reconcileNavbarItemsFromContent(contentForNav());
8081
+ run();
8082
+ const nav = document.querySelector("nav");
8083
+ if (!nav) return;
8084
+ const observer = new MutationObserver(() => {
8085
+ requestAnimationFrame(run);
8086
+ });
8087
+ observer.observe(nav, { childList: true, subtree: true });
8088
+ return () => observer.disconnect();
8089
+ }, [isEditMode, pathname, subdomain, fetchState]);
8090
+ (0, import_react8.useEffect)(() => {
6883
8091
  if (!isEditMode) return;
6884
8092
  const measure = () => {
6885
8093
  const h = document.body.scrollHeight;
6886
- if (h > 50) postToParent({ type: "ow:height", height: h });
8094
+ if (h > 50) postToParent2({ type: "ow:height", height: h });
6887
8095
  };
6888
8096
  const t1 = setTimeout(measure, 50);
6889
8097
  const t2 = setTimeout(measure, 500);
@@ -6902,8 +8110,8 @@ function OhhwellsBridge() {
6902
8110
  if (resizeTimer) clearTimeout(resizeTimer);
6903
8111
  window.removeEventListener("resize", handleResize);
6904
8112
  };
6905
- }, [pathname, isEditMode, postToParent]);
6906
- (0, import_react6.useEffect)(() => {
8113
+ }, [pathname, isEditMode, postToParent2]);
8114
+ (0, import_react8.useEffect)(() => {
6907
8115
  if (!subdomainFromQuery || isEditMode) return;
6908
8116
  const handleClick = (e) => {
6909
8117
  const anchor = e.target.closest("a");
@@ -6919,7 +8127,7 @@ function OhhwellsBridge() {
6919
8127
  document.addEventListener("click", handleClick, true);
6920
8128
  return () => document.removeEventListener("click", handleClick, true);
6921
8129
  }, [subdomainFromQuery, isEditMode, router]);
6922
- (0, import_react6.useEffect)(() => {
8130
+ (0, import_react8.useEffect)(() => {
6923
8131
  if (!isEditMode) {
6924
8132
  editStylesRef.current?.base.remove();
6925
8133
  editStylesRef.current?.forceHover.remove();
@@ -6942,8 +8150,9 @@ function OhhwellsBridge() {
6942
8150
  [data-ohw-editable] {
6943
8151
  display: block;
6944
8152
  }
6945
- [data-ohw-editable]:not([contenteditable]):not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"]) { cursor: text !important; }
8153
+ [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
8154
  [data-ohw-editable="image"], [data-ohw-editable="image"] *,
8155
+ [data-ohw-editable="video"], [data-ohw-editable="video"] *,
6947
8156
  [data-ohw-editable="bg-image"], [data-ohw-editable="bg-image"] * { cursor: pointer !important; }
6948
8157
  [data-ohw-editable="link"], [data-ohw-editable="link"] * { cursor: pointer !important; }
6949
8158
  [data-ohw-hovered]:not([contenteditable]):not([data-ohw-href-key]) {
@@ -6992,6 +8201,13 @@ function OhhwellsBridge() {
6992
8201
  if (target.closest("[data-ohw-max-badge]")) return;
6993
8202
  if (isInsideLinkEditor(target)) return;
6994
8203
  if (target.closest('[data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
8204
+ const navFromChrome = resolveNavContainerSelectionTarget(target, e.clientX, e.clientY);
8205
+ if (navFromChrome) {
8206
+ e.preventDefault();
8207
+ e.stopPropagation();
8208
+ selectFrameRef.current(navFromChrome);
8209
+ return;
8210
+ }
6995
8211
  e.preventDefault();
6996
8212
  e.stopPropagation();
6997
8213
  return;
@@ -7006,14 +8222,15 @@ function OhhwellsBridge() {
7006
8222
  bumpLinkPopoverGrace();
7007
8223
  setLinkPopoverRef.current({
7008
8224
  key: editable.dataset.ohwKey ?? "",
7009
- target: getLinkHref(editable)
8225
+ mode: "edit",
8226
+ target: getLinkHref2(editable)
7010
8227
  });
7011
8228
  return;
7012
8229
  }
7013
- if (editable.dataset.ohwEditable === "image" || editable.dataset.ohwEditable === "bg-image") {
8230
+ if (isMediaEditable(editable)) {
7014
8231
  e.preventDefault();
7015
8232
  e.stopPropagation();
7016
- postToParentRef.current({ type: "ow:image-pick", key: editable.dataset.ohwKey ?? "" });
8233
+ postToParentRef.current({ type: "ow:image-pick", key: editable.dataset.ohwKey ?? "", elementType: editable.dataset.ohwEditable ?? "image" });
7017
8234
  return;
7018
8235
  }
7019
8236
  const hrefCtx = getHrefKeyFromElement(editable);
@@ -7022,7 +8239,8 @@ function OhhwellsBridge() {
7022
8239
  e.preventDefault();
7023
8240
  e.stopPropagation();
7024
8241
  if (selectedElRef.current === navAnchor) {
7025
- activateRef.current(editable);
8242
+ if (e.detail >= 2) return;
8243
+ activateRef.current(editable, { caretX: e.clientX, caretY: e.clientY });
7026
8244
  return;
7027
8245
  }
7028
8246
  selectRef.current(navAnchor);
@@ -7041,6 +8259,22 @@ function OhhwellsBridge() {
7041
8259
  selectRef.current(hrefAnchor);
7042
8260
  return;
7043
8261
  }
8262
+ const navContainerToSelect = resolveNavContainerSelectionTarget(target, e.clientX, e.clientY);
8263
+ if (navContainerToSelect) {
8264
+ e.preventDefault();
8265
+ e.stopPropagation();
8266
+ selectFrameRef.current(navContainerToSelect);
8267
+ return;
8268
+ }
8269
+ const selectedContainer = selectedElRef.current;
8270
+ if (selectedContainer && isNavigationContainer(selectedContainer)) {
8271
+ const navItem = getNavigationItemAnchor(target);
8272
+ if (!navItem && (selectedContainer === target || selectedContainer.contains(target))) {
8273
+ e.preventDefault();
8274
+ e.stopPropagation();
8275
+ return;
8276
+ }
8277
+ }
7044
8278
  if (activeElRef.current) {
7045
8279
  const sel = window.getSelection();
7046
8280
  if (sel && !sel.isCollapsed) {
@@ -7066,10 +8300,48 @@ function OhhwellsBridge() {
7066
8300
  deselectRef.current();
7067
8301
  deactivateRef.current();
7068
8302
  };
8303
+ const handleDblClick = (e) => {
8304
+ const target = e.target;
8305
+ if (target.closest("[data-ohw-toolbar]")) return;
8306
+ if (target.closest("[data-ohw-state-toggle]")) return;
8307
+ if (target.closest("[data-ohw-max-badge]")) return;
8308
+ if (isInsideLinkEditor(target)) return;
8309
+ if (target.closest('[data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
8310
+ return;
8311
+ }
8312
+ const navLabel = getNavigationLabelEditable(target);
8313
+ if (!navLabel) return;
8314
+ const { editable } = navLabel;
8315
+ e.preventDefault();
8316
+ e.stopPropagation();
8317
+ if (activeElRef.current !== editable) {
8318
+ activateRef.current(editable);
8319
+ }
8320
+ requestAnimationFrame(() => {
8321
+ selectAllTextInEditable(editable);
8322
+ refreshActiveCommandsRef.current();
8323
+ });
8324
+ };
7069
8325
  const handleMouseOver = (e) => {
7070
8326
  const target = e.target;
8327
+ if (toolbarVariantRef.current !== "select-frame") {
8328
+ const navContainer = target.closest("[data-ohw-nav-container]");
8329
+ if (navContainer && !getNavigationItemAnchor(target)) {
8330
+ hoveredNavContainerRef.current = navContainer;
8331
+ setHoveredNavContainerRect(navContainer.getBoundingClientRect());
8332
+ hoveredItemElRef.current = null;
8333
+ setHoveredItemRect(null);
8334
+ return;
8335
+ }
8336
+ if (!target.closest("[data-ohw-nav-container]")) {
8337
+ hoveredNavContainerRef.current = null;
8338
+ setHoveredNavContainerRect(null);
8339
+ }
8340
+ }
7071
8341
  const navAnchor = getNavigationItemAnchor(target);
7072
8342
  if (navAnchor) {
8343
+ hoveredNavContainerRef.current = null;
8344
+ setHoveredNavContainerRect(null);
7073
8345
  const selected2 = selectedElRef.current;
7074
8346
  if (selected2 === navAnchor || selected2?.contains(navAnchor)) return;
7075
8347
  clearHrefKeyHover(navAnchor);
@@ -7081,7 +8353,7 @@ function OhhwellsBridge() {
7081
8353
  if (!editable) return;
7082
8354
  const selected = selectedElRef.current;
7083
8355
  if (selected && (selected === editable || selected.contains(editable))) return;
7084
- if (editable.dataset.ohwEditable !== "image" && editable.dataset.ohwEditable !== "bg-image" && !editable.hasAttribute("contenteditable")) {
8356
+ if (!isMediaEditable(editable) && !editable.hasAttribute("contenteditable")) {
7085
8357
  const hoverTarget = editable.closest("[data-ohw-href-key]") ?? editable;
7086
8358
  if (hoverTarget.hasAttribute("data-ohw-href-key")) {
7087
8359
  clearHrefKeyHover(hoverTarget);
@@ -7094,6 +8366,14 @@ function OhhwellsBridge() {
7094
8366
  };
7095
8367
  const handleMouseOut = (e) => {
7096
8368
  const target = e.target;
8369
+ if (target.closest("[data-ohw-nav-container]")) {
8370
+ const related2 = e.relatedTarget instanceof Element ? e.relatedTarget : null;
8371
+ if (related2?.closest("[data-ohw-nav-container], [data-ohw-navbar-container-chrome], [data-ohw-navbar-add-button]")) {
8372
+ return;
8373
+ }
8374
+ hoveredNavContainerRef.current = null;
8375
+ setHoveredNavContainerRect(null);
8376
+ }
7097
8377
  const navAnchor = getNavigationItemAnchor(target);
7098
8378
  if (navAnchor) {
7099
8379
  const related2 = e.relatedTarget instanceof Element ? e.relatedTarget : null;
@@ -7109,7 +8389,7 @@ function OhhwellsBridge() {
7109
8389
  if (!editable) return;
7110
8390
  const related = e.relatedTarget instanceof Element ? e.relatedTarget : null;
7111
8391
  if (related?.closest("[data-ohw-drag-handle-container], [data-ohw-item-interaction]")) return;
7112
- if (editable.dataset.ohwEditable !== "image" && editable.dataset.ohwEditable !== "bg-image") {
8392
+ if (!isMediaEditable(editable)) {
7113
8393
  const hoverTarget = editable.closest("[data-ohw-href-key]") ?? editable;
7114
8394
  if (hoverTarget.hasAttribute("data-ohw-href-key")) {
7115
8395
  if (!related?.closest("[data-ohw-href-key]")) {
@@ -7155,23 +8435,26 @@ function OhhwellsBridge() {
7155
8435
  }
7156
8436
  return { top, left, width: Math.max(0, right - left), height: Math.max(0, bottom - top) };
7157
8437
  };
7158
- const postImageHover = (imgEl, isDragOver = false, forceTextOverlap) => {
8438
+ const showImageHover = (imgEl, isDragOver = false, forceTextOverlap) => {
7159
8439
  const r2 = getVisibleRect(imgEl);
7160
8440
  const hasTextOverlap = forceTextOverlap ?? false;
7161
8441
  hoveredImageHasTextOverlapRef.current = hasTextOverlap;
7162
- postToParentRef.current({
7163
- type: "ow:image-hover",
8442
+ const video = imgEl.dataset.ohwEditable === "video" ? getVideoEl(imgEl) : null;
8443
+ setMediaHover({
7164
8444
  key: imgEl.dataset.ohwKey ?? "",
7165
8445
  rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height },
8446
+ elementType: imgEl.dataset.ohwEditable ?? "image",
7166
8447
  hasTextOverlap,
7167
- ...isDragOver ? { isDragOver: true } : {}
8448
+ isDragOver,
8449
+ ...video ? { videoAutoplay: video.autoplay, videoMuted: video.muted } : {}
7168
8450
  });
7169
8451
  const track = imgEl.closest("[data-ohw-hover-pause]");
7170
8452
  if (track) track.setAttribute("data-ohw-hover-paused", "");
7171
8453
  };
8454
+ const clearImageHover = () => setMediaHover(null);
7172
8455
  const findImageAtPoint = (clientX, clientY, fromParentViewport) => {
7173
8456
  const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
7174
- const images = Array.from(document.querySelectorAll('[data-ohw-editable="image"], [data-ohw-editable="bg-image"]'));
8457
+ const images = Array.from(document.querySelectorAll(MEDIA_SELECTOR));
7175
8458
  const matches = [];
7176
8459
  for (let i = images.length - 1; i >= 0; i--) {
7177
8460
  const el = images[i];
@@ -7187,7 +8470,9 @@ function OhhwellsBridge() {
7187
8470
  if (x >= r2.left && x <= r2.left + r2.width && y >= r2.top && y <= r2.top + r2.height) matches.push(el);
7188
8471
  }
7189
8472
  if (matches.length === 0) return null;
7190
- const imageTypeMatches = matches.filter((el) => el.dataset.ohwEditable === "image");
8473
+ const imageTypeMatches = matches.filter(
8474
+ (el) => el.dataset.ohwEditable === "image" || el.dataset.ohwEditable === "video"
8475
+ );
7191
8476
  const candidatePool = imageTypeMatches.length > 0 ? imageTypeMatches : matches;
7192
8477
  const smallest = candidatePool.reduce((best, el) => {
7193
8478
  const br = getVisibleRect(best);
@@ -7210,47 +8495,91 @@ function OhhwellsBridge() {
7210
8495
  }
7211
8496
  return smallest;
7212
8497
  };
8498
+ const dismissImageHover = () => {
8499
+ if (hoveredImageRef.current) {
8500
+ hoveredImageRef.current = null;
8501
+ hoveredImageHasTextOverlapRef.current = false;
8502
+ resumeAnimTracks();
8503
+ postToParentRef.current({ type: "ow:image-unhover" });
8504
+ }
8505
+ clearImageHover();
8506
+ };
8507
+ const probeNavigationHoverAt = (x, y) => {
8508
+ if (toolbarVariantRef.current === "select-frame") {
8509
+ hoveredNavContainerRef.current = null;
8510
+ setHoveredNavContainerRect(null);
8511
+ return;
8512
+ }
8513
+ const navContainer = document.querySelector("[data-ohw-nav-container]");
8514
+ if (!navContainer) {
8515
+ hoveredNavContainerRef.current = null;
8516
+ setHoveredNavContainerRect(null);
8517
+ return;
8518
+ }
8519
+ const containerRect = navContainer.getBoundingClientRect();
8520
+ const overContainer = x >= containerRect.left && x <= containerRect.right && y >= containerRect.top && y <= containerRect.bottom;
8521
+ if (!overContainer) {
8522
+ hoveredNavContainerRef.current = null;
8523
+ setHoveredNavContainerRect(null);
8524
+ return;
8525
+ }
8526
+ const navItemHit = Array.from(
8527
+ navContainer.querySelectorAll("[data-ohw-href-key]")
8528
+ ).find((el) => {
8529
+ if (!isNavigationItem(el) || isNavbarButton2(el)) return false;
8530
+ const itemRect = el.getBoundingClientRect();
8531
+ return x >= itemRect.left && x <= itemRect.right && y >= itemRect.top && y <= itemRect.bottom;
8532
+ });
8533
+ if (navItemHit) {
8534
+ hoveredNavContainerRef.current = null;
8535
+ setHoveredNavContainerRect(null);
8536
+ const selected = selectedElRef.current;
8537
+ if (selected !== navItemHit && !selected?.contains(navItemHit)) {
8538
+ clearHrefKeyHover(navItemHit);
8539
+ hoveredItemElRef.current = navItemHit;
8540
+ setHoveredItemRect(navItemHit.getBoundingClientRect());
8541
+ }
8542
+ return;
8543
+ }
8544
+ hoveredNavContainerRef.current = navContainer;
8545
+ setHoveredNavContainerRect(containerRect);
8546
+ hoveredItemElRef.current = null;
8547
+ setHoveredItemRect(null);
8548
+ };
7213
8549
  const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
7214
8550
  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
- }
8551
+ dismissImageHover();
7221
8552
  return;
7222
8553
  }
8554
+ const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
8555
+ if (isPointOverNavigation(x, y)) {
8556
+ dismissImageHover();
8557
+ probeNavigationHoverAt(x, y);
8558
+ return;
8559
+ }
8560
+ hoveredNavContainerRef.current = null;
8561
+ setHoveredNavContainerRect(null);
7223
8562
  const toggleEl = document.querySelector("[data-ohw-state-toggle]");
7224
8563
  if (toggleEl) {
7225
- const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
7226
8564
  const tr = toggleEl.getBoundingClientRect();
7227
8565
  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
- }
8566
+ dismissImageHover();
7233
8567
  return;
7234
8568
  }
7235
8569
  }
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
- }
8570
+ const imgEl = findImageAtPoint(clientX, clientY, fromParentViewport);
8571
+ const activeEl = activeElRef.current;
8572
+ if (activeEl && (!imgEl || imgEl.contains(activeEl))) {
8573
+ dismissImageHover();
7243
8574
  return;
7244
8575
  }
7245
- const imgEl = findImageAtPoint(clientX, clientY, fromParentViewport);
7246
8576
  if (imgEl) {
7247
- const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
7248
8577
  const topEl = document.elementFromPoint(x, y);
7249
8578
  if (topEl?.closest('[data-ohw-toolbar], [data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
7250
8579
  if (hoveredImageRef.current) {
7251
8580
  hoveredImageRef.current = null;
7252
8581
  resumeAnimTracks();
7253
- postToParentRef.current({ type: "ow:image-unhover" });
8582
+ clearImageHover();
7254
8583
  }
7255
8584
  return;
7256
8585
  }
@@ -7259,13 +8588,13 @@ function OhhwellsBridge() {
7259
8588
  hoveredImageRef.current = null;
7260
8589
  hoveredImageHasTextOverlapRef.current = false;
7261
8590
  resumeAnimTracks();
7262
- postToParentRef.current({ type: "ow:image-unhover" });
8591
+ clearImageHover();
7263
8592
  }
7264
8593
  return;
7265
8594
  }
7266
8595
  const isStateCardImage = !!imgEl.closest("[data-ohw-editable-state]");
7267
8596
  const textEditable = Array.from(
7268
- document.querySelectorAll('[data-ohw-editable]:not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"])')
8597
+ document.querySelectorAll(NON_MEDIA_SELECTOR)
7269
8598
  ).find((el) => {
7270
8599
  const er = el.getBoundingClientRect();
7271
8600
  return x >= er.left && x <= er.right && y >= er.top && y <= er.bottom;
@@ -7284,14 +8613,14 @@ function OhhwellsBridge() {
7284
8613
  hoveredImageRef.current = null;
7285
8614
  hoveredImageHasTextOverlapRef.current = false;
7286
8615
  resumeAnimTracks();
7287
- postToParentRef.current({ type: "ow:image-unhover" });
8616
+ clearImageHover();
7288
8617
  }
7289
8618
  return;
7290
8619
  }
7291
8620
  if (isStateCardImage) {
7292
8621
  if (imgEl !== hoveredImageRef.current || !hoveredImageHasTextOverlapRef.current) {
7293
8622
  hoveredImageRef.current = imgEl;
7294
- postImageHover(imgEl, isDragOver, true);
8623
+ showImageHover(imgEl, isDragOver, true);
7295
8624
  }
7296
8625
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
7297
8626
  if (textEditable && !isInsideNavigationItem(textEditable)) {
@@ -7303,7 +8632,7 @@ function OhhwellsBridge() {
7303
8632
  hoveredImageRef.current = null;
7304
8633
  hoveredImageHasTextOverlapRef.current = false;
7305
8634
  resumeAnimTracks();
7306
- postToParentRef.current({ type: "ow:image-unhover" });
8635
+ clearImageHover();
7307
8636
  }
7308
8637
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
7309
8638
  if (textEditable && !isInsideNavigationItem(textEditable)) {
@@ -7315,7 +8644,7 @@ function OhhwellsBridge() {
7315
8644
  if (hoveredImageRef.current) {
7316
8645
  hoveredImageRef.current = null;
7317
8646
  resumeAnimTracks();
7318
- postToParentRef.current({ type: "ow:image-unhover" });
8647
+ clearImageHover();
7319
8648
  }
7320
8649
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
7321
8650
  return;
@@ -7323,9 +8652,9 @@ function OhhwellsBridge() {
7323
8652
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
7324
8653
  if (imgEl !== hoveredImageRef.current || hoveredImageHasTextOverlapRef.current) {
7325
8654
  hoveredImageRef.current = imgEl;
7326
- postImageHover(imgEl, isDragOver);
8655
+ showImageHover(imgEl, isDragOver);
7327
8656
  } else if (isDragOver) {
7328
- postImageHover(imgEl, true);
8657
+ showImageHover(imgEl, true);
7329
8658
  }
7330
8659
  } else {
7331
8660
  const inIframeView = clientX >= 0 && clientY >= 0 && clientX <= window.innerWidth && clientY <= window.innerHeight;
@@ -7342,14 +8671,14 @@ function OhhwellsBridge() {
7342
8671
  hoveredImageRef.current = null;
7343
8672
  hoveredImageHasTextOverlapRef.current = false;
7344
8673
  resumeAnimTracks();
7345
- postToParentRef.current({ type: "ow:image-unhover" });
8674
+ clearImageHover();
7346
8675
  }
7347
- const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
8676
+ const { x: x2, y: y2 } = toProbeCoords(clientX, clientY, fromParentViewport);
7348
8677
  const textEl = Array.from(
7349
- document.querySelectorAll('[data-ohw-editable]:not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"])')
8678
+ document.querySelectorAll(NON_MEDIA_SELECTOR)
7350
8679
  ).find((el) => {
7351
8680
  const er = el.getBoundingClientRect();
7352
- return x >= er.left && x <= er.right && y >= er.top && y <= er.bottom;
8681
+ return x2 >= er.left && x2 <= er.right && y2 >= er.top && y2 <= er.bottom;
7353
8682
  });
7354
8683
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
7355
8684
  if (textEl && !textEl.hasAttribute("contenteditable")) {
@@ -7452,18 +8781,20 @@ function OhhwellsBridge() {
7452
8781
  return;
7453
8782
  }
7454
8783
  e.dataTransfer.dropEffect = "copy";
7455
- if (hoveredImageRef.current !== el) {
8784
+ if (dragOverElRef.current !== el) {
8785
+ dragOverElRef.current = el;
7456
8786
  hoveredImageRef.current = el;
7457
8787
  const isStateCard = !!el.closest("[data-ohw-editable-state]");
7458
- postImageHover(el, true, isStateCard ? true : void 0);
8788
+ showImageHover(el, true, isStateCard ? true : void 0);
7459
8789
  }
7460
8790
  };
7461
8791
  const handleDragLeave = (e) => {
7462
8792
  const imgEl = findImageAtPoint(e.clientX, e.clientY, false);
7463
8793
  if (imgEl) return;
8794
+ dragOverElRef.current = null;
7464
8795
  hoveredImageRef.current = null;
7465
8796
  resumeAnimTracks();
7466
- postToParentRef.current({ type: "ow:image-unhover" });
8797
+ clearImageHover();
7467
8798
  };
7468
8799
  const handleDrop = (e) => {
7469
8800
  e.preventDefault();
@@ -7471,7 +8802,9 @@ function OhhwellsBridge() {
7471
8802
  if (!el) return;
7472
8803
  const file = e.dataTransfer?.files?.[0];
7473
8804
  if (file) {
7474
- if (file.type.startsWith("image/")) {
8805
+ const wantsVideo = el.dataset.ohwEditable === "video";
8806
+ const accepted = wantsVideo ? file.type.startsWith("video/") : file.type.startsWith("image/");
8807
+ if (accepted) {
7475
8808
  const key = el.dataset.ohwKey ?? "";
7476
8809
  const { name, type: mimeType } = file;
7477
8810
  const r2 = el.getBoundingClientRect();
@@ -7480,12 +8813,13 @@ function OhhwellsBridge() {
7480
8813
  window.parent.postMessage({ type: "ow:image-drop", key, name, mimeType, buf, rect }, "*", [buf]);
7481
8814
  });
7482
8815
  } else {
7483
- postToParentRef.current({ type: "ow:image-drop-invalid" });
8816
+ postToParentRef.current({ type: "ow:image-drop-invalid", expected: wantsVideo ? "video" : "image" });
7484
8817
  }
7485
8818
  }
8819
+ dragOverElRef.current = null;
7486
8820
  hoveredImageRef.current = null;
7487
8821
  resumeAnimTracks();
7488
- postToParentRef.current({ type: "ow:image-unhover" });
8822
+ clearImageHover();
7489
8823
  };
7490
8824
  const handleImageUrl = (e) => {
7491
8825
  if (e.data?.type !== "ow:image-url") return;
@@ -7503,7 +8837,11 @@ function OhhwellsBridge() {
7503
8837
  }
7504
8838
  });
7505
8839
  hoveredImageRef.current = null;
7506
- postToParentRef.current({ type: "ow:image-loaded", key });
8840
+ setUploadingRects((prev) => {
8841
+ const entry = prev[key];
8842
+ if (!entry || entry.fadingOut) return prev;
8843
+ return { ...prev, [key]: { ...entry, fadingOut: true } };
8844
+ });
7507
8845
  };
7508
8846
  let found = false;
7509
8847
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -7538,6 +8876,19 @@ function OhhwellsBridge() {
7538
8876
  requestAnimationFrame(() => onReady());
7539
8877
  }
7540
8878
  }
8879
+ } else if (el.dataset.ohwEditable === "video") {
8880
+ const video = getVideoEl(el);
8881
+ if (video) {
8882
+ found = true;
8883
+ const onReady = () => {
8884
+ video.onloadeddata = null;
8885
+ video.onerror = null;
8886
+ notify();
8887
+ };
8888
+ video.onloadeddata = onReady;
8889
+ video.onerror = onReady;
8890
+ applyVideoSrc(video, url);
8891
+ }
7541
8892
  }
7542
8893
  });
7543
8894
  if (!found) notify();
@@ -7604,12 +8955,16 @@ function OhhwellsBridge() {
7604
8955
  sectionsJson = val;
7605
8956
  continue;
7606
8957
  }
8958
+ if (applyVideoSettingNode(key, val)) continue;
7607
8959
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
7608
8960
  if (el.dataset.ohwEditable === "image") {
7609
8961
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
7610
8962
  if (img) applyEditableImageSrc(img, val);
7611
8963
  } else if (el.dataset.ohwEditable === "bg-image") {
7612
8964
  el.style.backgroundImage = `url('${val}')`;
8965
+ } else if (el.dataset.ohwEditable === "video") {
8966
+ const video = getVideoEl(el);
8967
+ if (video && video.src !== val) applyVideoSrc(video, val);
7613
8968
  } else if (el.dataset.ohwEditable === "link") {
7614
8969
  applyLinkHref(el, val);
7615
8970
  } else {
@@ -7623,6 +8978,8 @@ function OhhwellsBridge() {
7623
8978
  sectionsLoadedRef.current = true;
7624
8979
  pendingScheduleConfigRequests.current.splice(0).forEach(processConfigRequest);
7625
8980
  }
8981
+ editContentRef.current = { ...editContentRef.current, ...content };
8982
+ reconcileNavbarItemsFromContent(editContentRef.current);
7626
8983
  enforceLinkHrefs();
7627
8984
  postToParentRef.current({ type: "ow:hydrate-done" });
7628
8985
  };
@@ -7630,6 +8987,7 @@ function OhhwellsBridge() {
7630
8987
  const handleDeactivate = (e) => {
7631
8988
  if (e.data?.type !== "ow:deactivate") return;
7632
8989
  if (Date.now() < linkPopoverGraceUntilRef.current) return;
8990
+ if (document.querySelector("[data-ohw-section-picker]")) return;
7633
8991
  if (linkPopoverOpenRef.current) {
7634
8992
  setLinkPopoverRef.current(null);
7635
8993
  return;
@@ -7639,12 +8997,32 @@ function OhhwellsBridge() {
7639
8997
  };
7640
8998
  window.addEventListener("message", handleDeactivate);
7641
8999
  const handleKeyDown = (e) => {
9000
+ if (e.key === "Escape" && document.querySelector("[data-ohw-section-picker]")) return;
9001
+ if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "a" && activeElRef.current) {
9002
+ const navAnchor = getNavigationItemAnchor(activeElRef.current);
9003
+ if (navAnchor) {
9004
+ e.preventDefault();
9005
+ selectAllTextInEditable(activeElRef.current);
9006
+ refreshActiveCommandsRef.current();
9007
+ return;
9008
+ }
9009
+ }
7642
9010
  if (e.key === "Escape" && linkPopoverOpenRef.current) {
7643
9011
  setLinkPopoverRef.current(null);
7644
9012
  return;
7645
9013
  }
7646
9014
  if (e.key === "Escape" && selectedElRef.current && !activeElRef.current) {
7647
- deselectRef.current();
9015
+ if (toolbarVariantRef.current === "select-frame") {
9016
+ deselectRef.current();
9017
+ return;
9018
+ }
9019
+ const parent = getNavigationSelectionParent(selectedElRef.current);
9020
+ if (parent) {
9021
+ e.preventDefault();
9022
+ selectFrameRef.current(parent);
9023
+ } else {
9024
+ deselectRef.current();
9025
+ }
7648
9026
  return;
7649
9027
  }
7650
9028
  if (e.key === "Escape" && activeElRef.current) {
@@ -7702,18 +9080,18 @@ function OhhwellsBridge() {
7702
9080
  if (hoveredItemElRef.current) {
7703
9081
  setHoveredItemRect(hoveredItemElRef.current.getBoundingClientRect());
7704
9082
  }
9083
+ if (hoveredNavContainerRef.current) {
9084
+ setHoveredNavContainerRect(hoveredNavContainerRef.current.getBoundingClientRect());
9085
+ }
7705
9086
  if (siblingHintElRef.current) {
7706
9087
  setSiblingHintRect(siblingHintElRef.current.getBoundingClientRect());
7707
9088
  }
7708
9089
  if (hoveredImageRef.current) {
7709
9090
  const el = hoveredImageRef.current;
7710
9091
  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
- });
9092
+ setMediaHover(
9093
+ (prev) => prev ? { ...prev, rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height } } : prev
9094
+ );
7717
9095
  }
7718
9096
  };
7719
9097
  const handleSave = (e) => {
@@ -7802,19 +9180,39 @@ function OhhwellsBridge() {
7802
9180
  refreshActiveCommandsRef.current = handleSelectionChange;
7803
9181
  const handleDocMouseLeave = () => {
7804
9182
  hoveredImageRef.current = null;
9183
+ setMediaHover(null);
7805
9184
  document.querySelectorAll("[data-ohw-state-hovered]").forEach((el) => el.removeAttribute("data-ohw-state-hovered"));
7806
9185
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
7807
9186
  activeStateElRef.current = null;
7808
9187
  setToggleState(null);
7809
9188
  };
7810
- const handleAnimLock = (e) => {
7811
- if (e.data?.type !== "ow:anim-lock") return;
7812
- const { key } = e.data;
9189
+ const handleImageUploading = (e) => {
9190
+ if (e.data?.type !== "ow:image-uploading") return;
9191
+ const { key, uploading } = e.data;
9192
+ setUploadingRects((prev) => {
9193
+ if (!uploading) {
9194
+ if (!(key in prev)) return prev;
9195
+ const next = { ...prev };
9196
+ delete next[key];
9197
+ return next;
9198
+ }
9199
+ const el = document.querySelector(`[data-ohw-key="${key}"]`);
9200
+ if (!el) return prev;
9201
+ const r2 = getVisibleRect(el);
9202
+ return {
9203
+ ...prev,
9204
+ [key]: { rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height } }
9205
+ };
9206
+ });
7813
9207
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
7814
9208
  const track = el.closest("[data-ohw-hover-pause]");
7815
- if (track) {
9209
+ if (!track) return;
9210
+ if (uploading) {
7816
9211
  uploadLockedTracks.add(track);
7817
9212
  track.setAttribute("data-ohw-hover-paused", "");
9213
+ } else {
9214
+ uploadLockedTracks.delete(track);
9215
+ track.removeAttribute("data-ohw-hover-paused");
7818
9216
  }
7819
9217
  });
7820
9218
  };
@@ -7856,7 +9254,7 @@ function OhhwellsBridge() {
7856
9254
  if (e.data?.type !== "ow:click-at") return;
7857
9255
  const { clientX, clientY } = e.data;
7858
9256
  const allImages = Array.from(
7859
- document.querySelectorAll('[data-ohw-editable="image"], [data-ohw-editable="bg-image"]')
9257
+ document.querySelectorAll(MEDIA_SELECTOR)
7860
9258
  ).filter((el) => !!el.closest("[data-ohw-editable-state]"));
7861
9259
  const stateCardImage = allImages.find((el) => {
7862
9260
  const ownerCard = el.closest("[data-ohw-editable-state]");
@@ -7871,21 +9269,55 @@ function OhhwellsBridge() {
7871
9269
  return clientX >= r2.left && clientX <= r2.right && clientY >= r2.top && clientY <= r2.bottom;
7872
9270
  });
7873
9271
  if (stateCardImage) {
7874
- postToParentRef.current({ type: "ow:image-pick", key: stateCardImage.dataset.ohwKey ?? "" });
9272
+ postToParentRef.current({ type: "ow:image-pick", key: stateCardImage.dataset.ohwKey ?? "", elementType: stateCardImage.dataset.ohwEditable ?? "image" });
7875
9273
  return;
7876
9274
  }
7877
9275
  const textEditable = Array.from(
7878
- document.querySelectorAll(
7879
- '[data-ohw-editable]:not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"])'
7880
- )
9276
+ document.querySelectorAll(NON_MEDIA_SELECTOR)
7881
9277
  ).find((el) => {
7882
9278
  const r2 = el.getBoundingClientRect();
7883
9279
  return clientX >= r2.left && clientX <= r2.right && clientY >= r2.top && clientY <= r2.bottom;
7884
9280
  });
7885
9281
  if (textEditable) {
7886
- activateRef.current(textEditable);
9282
+ const hrefCtx = getHrefKeyFromElement(textEditable);
9283
+ const navAnchor = hrefCtx ? getNavigationItemAnchor(hrefCtx.anchor) : null;
9284
+ if (navAnchor) {
9285
+ if (selectedElRef.current === navAnchor) {
9286
+ activateRef.current(textEditable, { caretX: clientX, caretY: clientY });
9287
+ } else {
9288
+ selectRef.current(navAnchor);
9289
+ }
9290
+ return;
9291
+ }
9292
+ activateRef.current(textEditable, { caretX: clientX, caretY: clientY });
7887
9293
  return;
7888
9294
  }
9295
+ const navContainer = document.querySelector("[data-ohw-nav-container]");
9296
+ if (navContainer) {
9297
+ const r2 = navContainer.getBoundingClientRect();
9298
+ const pad = 8;
9299
+ const over = clientX >= r2.left - pad && clientX <= r2.right + pad && clientY >= r2.top - pad && clientY <= r2.bottom + pad;
9300
+ if (over && !isPointOverNavItem(navContainer, clientX, clientY)) {
9301
+ selectFrameRef.current(navContainer);
9302
+ return;
9303
+ }
9304
+ }
9305
+ const navRoot = document.querySelector("[data-ohw-nav-root]");
9306
+ if (navRoot && navContainer) {
9307
+ const rr = navRoot.getBoundingClientRect();
9308
+ if (clientX >= rr.left && clientX <= rr.right && clientY >= rr.top && clientY <= rr.bottom && !isPointOverNavItem(navContainer, clientX, clientY)) {
9309
+ const book = navRoot.querySelector('[data-ohw-role="navbar-button"]');
9310
+ if (book) {
9311
+ const br = book.getBoundingClientRect();
9312
+ if (clientX >= br.left && clientX <= br.right && clientY >= br.top && clientY <= br.bottom) {
9313
+ deactivateRef.current();
9314
+ return;
9315
+ }
9316
+ }
9317
+ selectFrameRef.current(navContainer);
9318
+ return;
9319
+ }
9320
+ }
7889
9321
  deactivateRef.current();
7890
9322
  };
7891
9323
  window.addEventListener("message", handleSave);
@@ -7895,7 +9327,7 @@ function OhhwellsBridge() {
7895
9327
  window.addEventListener("message", handleClearSchedulingWidget);
7896
9328
  window.addEventListener("message", handleRemoveSchedulingSection);
7897
9329
  window.addEventListener("message", handleImageUrl);
7898
- window.addEventListener("message", handleAnimLock);
9330
+ window.addEventListener("message", handleImageUploading);
7899
9331
  window.addEventListener("message", handleCanvasHeight);
7900
9332
  window.addEventListener("message", handleParentScroll);
7901
9333
  window.addEventListener("message", handlePointerSync);
@@ -7907,6 +9339,7 @@ function OhhwellsBridge() {
7907
9339
  };
7908
9340
  window.addEventListener("resize", handleViewportResize, { passive: true });
7909
9341
  document.addEventListener("click", handleClick, true);
9342
+ document.addEventListener("dblclick", handleDblClick, true);
7910
9343
  document.addEventListener("paste", handlePaste, true);
7911
9344
  document.addEventListener("input", handleInput, true);
7912
9345
  document.addEventListener("mouseover", handleMouseOver, true);
@@ -7921,6 +9354,7 @@ function OhhwellsBridge() {
7921
9354
  window.addEventListener("scroll", handleScroll, true);
7922
9355
  return () => {
7923
9356
  document.removeEventListener("click", handleClick, true);
9357
+ document.removeEventListener("dblclick", handleDblClick, true);
7924
9358
  document.removeEventListener("paste", handlePaste, true);
7925
9359
  document.removeEventListener("input", handleInput, true);
7926
9360
  document.removeEventListener("mouseover", handleMouseOver, true);
@@ -7940,7 +9374,7 @@ function OhhwellsBridge() {
7940
9374
  window.removeEventListener("message", handleClearSchedulingWidget);
7941
9375
  window.removeEventListener("message", handleRemoveSchedulingSection);
7942
9376
  window.removeEventListener("message", handleImageUrl);
7943
- window.removeEventListener("message", handleAnimLock);
9377
+ window.removeEventListener("message", handleImageUploading);
7944
9378
  window.removeEventListener("message", handleCanvasHeight);
7945
9379
  window.removeEventListener("message", handleParentScroll);
7946
9380
  window.removeEventListener("resize", handleViewportResize);
@@ -7954,7 +9388,7 @@ function OhhwellsBridge() {
7954
9388
  if (imageShowTimerRef.current) clearTimeout(imageShowTimerRef.current);
7955
9389
  };
7956
9390
  }, [isEditMode, refreshStateRules]);
7957
- (0, import_react6.useEffect)(() => {
9391
+ (0, import_react8.useEffect)(() => {
7958
9392
  const handler = (e) => {
7959
9393
  if (e.data?.type !== "ow:request-schedule-config") return;
7960
9394
  const insertAfterVal = e.data.insertAfter;
@@ -7970,7 +9404,7 @@ function OhhwellsBridge() {
7970
9404
  window.addEventListener("message", handler);
7971
9405
  return () => window.removeEventListener("message", handler);
7972
9406
  }, [processConfigRequest]);
7973
- (0, import_react6.useEffect)(() => {
9407
+ (0, import_react8.useEffect)(() => {
7974
9408
  if (!isEditMode) return;
7975
9409
  document.querySelectorAll("[data-ohw-active-state]").forEach((el) => {
7976
9410
  el.removeAttribute("data-ohw-active-state");
@@ -7991,27 +9425,27 @@ function OhhwellsBridge() {
7991
9425
  const next = { ...prev, [pathKey]: sections };
7992
9426
  return shouldUseDevFixtures() ? { ...DEV_SECTIONS_BY_PATH, ...next } : next;
7993
9427
  });
7994
- postToParent({ type: "ow:ready", version: "1", path: pathname, nodes: collectEditableNodes(), sections });
7995
- postToParent({ type: "ow:request-site-pages" });
9428
+ postToParent2({ type: "ow:ready", version: "1", path: pathname, nodes: collectEditableNodes(), sections });
9429
+ postToParent2({ type: "ow:request-site-pages" });
7996
9430
  }, 150);
7997
9431
  return () => {
7998
9432
  cancelAnimationFrame(raf);
7999
9433
  clearTimeout(timer);
8000
9434
  };
8001
- }, [pathname, isEditMode, refreshStateRules, postToParent]);
8002
- (0, import_react6.useEffect)(() => {
9435
+ }, [pathname, isEditMode, refreshStateRules, postToParent2]);
9436
+ (0, import_react8.useEffect)(() => {
8003
9437
  scrollToHashSectionWhenReady();
8004
9438
  const onHashChange = () => scrollToHashSectionWhenReady();
8005
9439
  window.addEventListener("hashchange", onHashChange);
8006
9440
  return () => window.removeEventListener("hashchange", onHashChange);
8007
9441
  }, [pathname]);
8008
- const handleCommand = (0, import_react6.useCallback)((cmd) => {
9442
+ const handleCommand = (0, import_react8.useCallback)((cmd) => {
8009
9443
  document.execCommand(cmd, false);
8010
9444
  activeElRef.current?.focus();
8011
9445
  if (activeElRef.current) setToolbarRect(getEditMeasureEl(activeElRef.current).getBoundingClientRect());
8012
9446
  refreshActiveCommandsRef.current();
8013
9447
  }, []);
8014
- const handleStateChange = (0, import_react6.useCallback)((state) => {
9448
+ const handleStateChange = (0, import_react8.useCallback)((state) => {
8015
9449
  if (!activeStateElRef.current) return;
8016
9450
  const el = activeStateElRef.current;
8017
9451
  if (state === "Default") {
@@ -8024,18 +9458,22 @@ function OhhwellsBridge() {
8024
9458
  }
8025
9459
  setToggleState((prev) => prev ? { ...prev, activeState: state } : null);
8026
9460
  }, [deactivate]);
8027
- const closeLinkPopover = (0, import_react6.useCallback)(() => setLinkPopover(null), []);
8028
- const openLinkPopoverForActive = (0, import_react6.useCallback)(() => {
9461
+ const closeLinkPopover = (0, import_react8.useCallback)(() => {
9462
+ addNavAfterAnchorRef.current = null;
9463
+ setLinkPopover(null);
9464
+ }, []);
9465
+ const openLinkPopoverForActive = (0, import_react8.useCallback)(() => {
8029
9466
  const hrefCtx = getHrefKeyFromElement(activeElRef.current);
8030
9467
  if (!hrefCtx) return;
8031
9468
  bumpLinkPopoverGrace();
8032
9469
  setLinkPopover({
8033
9470
  key: hrefCtx.key,
8034
- target: getLinkHref(hrefCtx.anchor)
9471
+ mode: "edit",
9472
+ target: getLinkHref2(hrefCtx.anchor)
8035
9473
  });
8036
9474
  deactivate();
8037
9475
  }, [deactivate]);
8038
- const openLinkPopoverForSelected = (0, import_react6.useCallback)(() => {
9476
+ const openLinkPopoverForSelected = (0, import_react8.useCallback)(() => {
8039
9477
  const anchor = selectedElRef.current;
8040
9478
  if (!anchor) return;
8041
9479
  const key = anchor.getAttribute("data-ohw-href-key");
@@ -8043,51 +9481,163 @@ function OhhwellsBridge() {
8043
9481
  bumpLinkPopoverGrace();
8044
9482
  setLinkPopover({
8045
9483
  key,
8046
- target: getLinkHref(anchor)
9484
+ mode: "edit",
9485
+ target: getLinkHref2(anchor)
8047
9486
  });
8048
9487
  deselect();
8049
9488
  }, [deselect]);
8050
- const handleLinkPopoverSubmit = (0, import_react6.useCallback)(
9489
+ const handleLinkPopoverSubmit = (0, import_react8.useCallback)(
8051
9490
  (target) => {
8052
- if (!linkPopover) return;
8053
- const { key } = linkPopover;
9491
+ const session = linkPopoverSessionRef.current;
9492
+ if (!session) return;
9493
+ if (session.intent === "add-nav") {
9494
+ const trimmed = target.trim();
9495
+ let href = trimmed;
9496
+ let label = "Untitled";
9497
+ if (trimmed) {
9498
+ const { pageRoute, sectionId } = parseTarget(trimmed);
9499
+ const page = resolvePage(pageRoute, sitePages);
9500
+ const pageSections = getSectionsForPath(sectionsByPath, pageRoute);
9501
+ const section = sectionId ? pageSections.find((s) => s.id === sectionId) : void 0;
9502
+ label = section?.label ?? page.title;
9503
+ href = trimmed;
9504
+ }
9505
+ const afterAnchor = addNavAfterAnchorRef.current;
9506
+ const { anchor, hrefKey, labelKey, index, order } = insertNavbarItem(href, label, afterAnchor);
9507
+ applyLinkByKey(hrefKey, href);
9508
+ document.querySelectorAll(`[data-ohw-key="${labelKey}"]`).forEach((el) => {
9509
+ el.textContent = label;
9510
+ });
9511
+ const navCount = String(index + 1);
9512
+ const orderJson = JSON.stringify(order);
9513
+ editContentRef.current = {
9514
+ ...editContentRef.current,
9515
+ [hrefKey]: href,
9516
+ [labelKey]: label,
9517
+ [NAV_COUNT_KEY]: navCount,
9518
+ [NAV_ORDER_KEY]: orderJson
9519
+ };
9520
+ postToParent2({
9521
+ type: "ow:change",
9522
+ nodes: [
9523
+ { key: hrefKey, text: href },
9524
+ { key: labelKey, text: label },
9525
+ { key: NAV_COUNT_KEY, text: navCount },
9526
+ { key: NAV_ORDER_KEY, text: orderJson }
9527
+ ]
9528
+ });
9529
+ postToParent2({ type: "ow:toast", title: "Link added", toastType: "success" });
9530
+ addNavAfterAnchorRef.current = null;
9531
+ setLinkPopover(null);
9532
+ enforceLinkHrefs();
9533
+ requestAnimationFrame(() => {
9534
+ selectRef.current(anchor);
9535
+ });
9536
+ return;
9537
+ }
9538
+ const { key } = session;
8054
9539
  applyLinkByKey(key, target);
8055
- postToParent({ type: "ow:change", nodes: [{ key, text: target }] });
9540
+ postToParent2({ type: "ow:change", nodes: [{ key, text: target }] });
8056
9541
  setLinkPopover(null);
8057
9542
  },
8058
- [linkPopover, postToParent]
9543
+ [postToParent2, sitePages, sectionsByPath]
8059
9544
  );
8060
9545
  const showEditLink = toolbarShowEditLink;
8061
9546
  const currentSections = sectionsByPath[pathname] ?? [];
8062
9547
  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)(
9548
+ const handleMediaReplace = (0, import_react8.useCallback)(
9549
+ (key) => {
9550
+ postToParent2({ type: "ow:image-pick", key, elementType: mediaHover?.elementType ?? "image" });
9551
+ },
9552
+ [postToParent2, mediaHover?.elementType]
9553
+ );
9554
+ const handleMediaFadeOutComplete = (0, import_react8.useCallback)((key) => {
9555
+ setUploadingRects((prev) => {
9556
+ if (!(key in prev)) return prev;
9557
+ const next = { ...prev };
9558
+ delete next[key];
9559
+ return next;
9560
+ });
9561
+ }, []);
9562
+ const handleVideoSettingsChange = (0, import_react8.useCallback)(
9563
+ (key, settings) => {
9564
+ document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
9565
+ const video = getVideoEl(el);
9566
+ if (!video) return;
9567
+ if (typeof settings.autoplay === "boolean") video.autoplay = settings.autoplay;
9568
+ if (typeof settings.muted === "boolean") video.muted = settings.muted;
9569
+ syncVideoPlayback(video);
9570
+ postToParent2({
9571
+ type: "ow:change",
9572
+ nodes: [
9573
+ { key: `${key}${VIDEO_AUTOPLAY_SUFFIX}`, text: String(video.autoplay) },
9574
+ { key: `${key}${VIDEO_MUTED_SUFFIX}`, text: String(video.muted) }
9575
+ ]
9576
+ });
9577
+ setMediaHover(
9578
+ (prev) => prev && prev.key === key ? { ...prev, videoAutoplay: video.autoplay, videoMuted: video.muted } : prev
9579
+ );
9580
+ });
9581
+ },
9582
+ [postToParent2]
9583
+ );
9584
+ return bridgeRoot ? (0, import_react_dom3.createPortal)(
9585
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
9586
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
9587
+ Object.entries(uploadingRects).map(([key, { rect, fadingOut }]) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
9588
+ MediaOverlay,
9589
+ {
9590
+ hover: { key, rect, elementType: "image", isDragOver: false, hasTextOverlap: false },
9591
+ isUploading: true,
9592
+ fadingOut,
9593
+ onFadeOutComplete: handleMediaFadeOutComplete,
9594
+ onReplace: handleMediaReplace
9595
+ },
9596
+ `uploading-${key}`
9597
+ )),
9598
+ mediaHover && !(mediaHover.key in uploadingRects) && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
9599
+ MediaOverlay,
9600
+ {
9601
+ hover: mediaHover,
9602
+ isUploading: false,
9603
+ onReplace: handleMediaReplace,
9604
+ onVideoSettingsChange: handleVideoSettingsChange
9605
+ }
9606
+ ),
9607
+ siblingHintRect && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
9608
+ hoveredNavContainerRect && toolbarVariant !== "select-frame" && !linkPopover && !isItemDragging && !siblingHintRect && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
9609
+ toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
9610
+ NavbarContainerChrome,
9611
+ {
9612
+ rect: toolbarRect,
9613
+ onAdd: handleAddTopLevelNavItem
9614
+ }
9615
+ ),
9616
+ hoveredItemRect && !hoveredNavContainerRect && !siblingHintRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
9617
+ toolbarRect && (toolbarVariant === "link-action" || toolbarVariant === "select-frame") && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8069
9618
  ItemInteractionLayer,
8070
9619
  {
8071
9620
  rect: toolbarRect,
8072
9621
  elRef: glowElRef,
8073
9622
  state: isItemDragging ? "dragging" : resolveItemInteractionState(toolbarRect, parentScrollRef.current),
8074
- showHandle: Boolean(reorderHrefKey),
9623
+ showHandle: toolbarVariant === "link-action" && Boolean(reorderHrefKey),
8075
9624
  dragDisabled: reorderDragDisabled,
8076
9625
  dragHandleLabel: reorderHrefKey ? `Reorder ${reorderHrefKey}` : "Reorder item",
8077
9626
  onDragHandleDragStart: handleItemDragStart,
8078
9627
  onDragHandleDragEnd: handleItemDragEnd,
8079
- toolbar: isItemDragging ? void 0 : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
9628
+ toolbar: toolbarVariant === "link-action" && !isItemDragging ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8080
9629
  ItemActionToolbar,
8081
9630
  {
8082
9631
  onEditLink: openLinkPopoverForSelected,
8083
- onAddItem: handleAddNavigationItem,
8084
- addItemDisabled: false
9632
+ addItemDisabled: true,
9633
+ editLinkDisabled: false,
9634
+ moreDisabled: true
8085
9635
  }
8086
- )
9636
+ ) : void 0
8087
9637
  }
8088
9638
  ),
8089
- toolbarRect && toolbarVariant === "rich-text" && /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
8090
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
9639
+ toolbarRect && toolbarVariant === "rich-text" && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
9640
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8091
9641
  EditGlowChrome,
8092
9642
  {
8093
9643
  rect: toolbarRect,
@@ -8096,7 +9646,7 @@ function OhhwellsBridge() {
8096
9646
  dragDisabled: reorderDragDisabled
8097
9647
  }
8098
9648
  ),
8099
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
9649
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8100
9650
  FloatingToolbar,
8101
9651
  {
8102
9652
  rect: toolbarRect,
@@ -8109,7 +9659,7 @@ function OhhwellsBridge() {
8109
9659
  }
8110
9660
  )
8111
9661
  ] }),
8112
- maxBadge && /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
9662
+ maxBadge && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
8113
9663
  "div",
8114
9664
  {
8115
9665
  "data-ohw-max-badge": "",
@@ -8135,7 +9685,7 @@ function OhhwellsBridge() {
8135
9685
  ]
8136
9686
  }
8137
9687
  ),
8138
- toggleState && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
9688
+ toggleState && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8139
9689
  StateToggle,
8140
9690
  {
8141
9691
  rect: toggleState.rect,
@@ -8144,15 +9694,15 @@ function OhhwellsBridge() {
8144
9694
  onStateChange: handleStateChange
8145
9695
  }
8146
9696
  ),
8147
- sectionGap && /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
9697
+ sectionGap && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
8148
9698
  "div",
8149
9699
  {
8150
9700
  "data-ohw-section-insert-line": "",
8151
9701
  className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
8152
9702
  style: { top: sectionGap.y, transform: "translateY(-50%)" },
8153
9703
  children: [
8154
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
8155
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
9704
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
9705
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8156
9706
  Badge,
8157
9707
  {
8158
9708
  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 +9715,52 @@ function OhhwellsBridge() {
8165
9715
  children: "Add Section"
8166
9716
  }
8167
9717
  ),
8168
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
9718
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
8169
9719
  ]
8170
9720
  }
8171
9721
  ),
8172
- linkPopover && dialogPortalContainer ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
9722
+ linkPopover && dialogPortalContainer ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8173
9723
  LinkPopover,
8174
9724
  {
8175
9725
  panelRef: linkPopoverPanelRef,
8176
9726
  portalContainer: dialogPortalContainer,
8177
9727
  open: true,
8178
- mode: "edit",
9728
+ mode: linkPopover.mode ?? "edit",
8179
9729
  pages: sitePages,
8180
9730
  sections: currentSections,
8181
9731
  sectionsByPath,
8182
9732
  initialTarget: linkPopover.target,
9733
+ existingTargets: linkPopover.intent === "add-nav" ? getNavbarExistingTargets() : [],
8183
9734
  onClose: closeLinkPopover,
8184
9735
  onSubmit: handleLinkPopoverSubmit
8185
9736
  },
8186
- `${linkPopover.key}-${pathname}`
8187
- ) : null
9737
+ linkPopover.key
9738
+ ) : null,
9739
+ sectionGap && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
9740
+ "div",
9741
+ {
9742
+ "data-ohw-section-insert-line": "",
9743
+ className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
9744
+ style: { top: sectionGap.y, transform: "translateY(-50%)" },
9745
+ children: [
9746
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
9747
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
9748
+ Badge,
9749
+ {
9750
+ 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",
9751
+ onClick: () => {
9752
+ window.parent.postMessage(
9753
+ { type: "ow:add-section", insertAfter: sectionGap.insertAfter, insertBefore: sectionGap.insertBefore },
9754
+ "*"
9755
+ );
9756
+ },
9757
+ children: "Add Section"
9758
+ }
9759
+ ),
9760
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
9761
+ ]
9762
+ }
9763
+ )
8188
9764
  ] }),
8189
9765
  bridgeRoot
8190
9766
  ) : null;