@ohhwells/bridge 0.1.36-next.49 → 0.1.36-next.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  // src/OhhwellsBridge.tsx
4
- import React8, { useCallback as useCallback3, useEffect as useEffect3, useLayoutEffect as useLayoutEffect2, useRef as useRef3, useState as useState5 } from "react";
4
+ import React9, { useCallback as useCallback4, useEffect as useEffect7, useLayoutEffect as useLayoutEffect2, useRef as useRef4, useState as useState6 } from "react";
5
5
  import { createRoot } from "react-dom/client";
6
6
  import { flushSync } from "react-dom";
7
7
 
@@ -10,6 +10,9 @@ var linkHrefStore = /* @__PURE__ */ new Map();
10
10
  function setStoredLinkHref(key, href) {
11
11
  linkHrefStore.set(key, href);
12
12
  }
13
+ function getStoredLinkHref(key) {
14
+ return linkHrefStore.get(key);
15
+ }
13
16
  function enforceLinkHrefs() {
14
17
  document.querySelectorAll("[data-ohw-href-key]").forEach((el) => {
15
18
  const key = el.getAttribute("data-ohw-href-key") ?? "";
@@ -4397,6 +4400,7 @@ function ItemActionToolbar({
4397
4400
  onEditLink,
4398
4401
  onAddItem,
4399
4402
  onMore,
4403
+ editLinkDisabled = false,
4400
4404
  addItemDisabled = true,
4401
4405
  moreDisabled = true,
4402
4406
  tooltipSide = "bottom"
@@ -4407,8 +4411,10 @@ function ItemActionToolbar({
4407
4411
  {
4408
4412
  label: "Edit link",
4409
4413
  side: tooltipSide,
4414
+ disabled: editLinkDisabled,
4410
4415
  buttonProps: {
4411
4416
  onMouseDown: (e) => {
4417
+ if (editLinkDisabled) return;
4412
4418
  e.preventDefault();
4413
4419
  e.stopPropagation();
4414
4420
  onEditLink?.();
@@ -4580,9 +4586,223 @@ function ItemInteractionLayer({
4580
4586
  );
4581
4587
  }
4582
4588
 
4589
+ // src/ui/MediaOverlay.tsx
4590
+ import * as React6 from "react";
4591
+ import { Film, ImageIcon, Pause, Play, Volume2, VolumeX } from "lucide-react";
4592
+
4593
+ // src/ui/button.tsx
4594
+ import * as React5 from "react";
4595
+ import { Slot } from "radix-ui";
4596
+ import { jsx as jsx10 } from "react/jsx-runtime";
4597
+ var buttonVariants = cva(
4598
+ "inline-flex items-center justify-center gap-1 whitespace-nowrap rounded-md text-sm font-medium transition-colors outline-none disabled:pointer-events-none disabled:opacity-50 min-w-[80px] px-3 py-2",
4599
+ {
4600
+ variants: {
4601
+ variant: {
4602
+ default: "bg-primary text-primary-foreground hover:opacity-90",
4603
+ outline: "border border-border bg-background text-foreground shadow-sm hover:bg-muted/80",
4604
+ ghost: "min-w-0 px-3 py-2 text-foreground hover:bg-muted/50"
4605
+ },
4606
+ size: {
4607
+ default: "h-9",
4608
+ sm: "h-8 min-w-[64px] px-2 py-1.5 text-sm"
4609
+ }
4610
+ },
4611
+ defaultVariants: {
4612
+ variant: "default",
4613
+ size: "default"
4614
+ }
4615
+ }
4616
+ );
4617
+ var Button = React5.forwardRef(
4618
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
4619
+ const Comp = asChild ? Slot.Root : "button";
4620
+ return /* @__PURE__ */ jsx10(
4621
+ Comp,
4622
+ {
4623
+ ref,
4624
+ "data-slot": "button",
4625
+ className: cn(buttonVariants({ variant, size, className })),
4626
+ ...props
4627
+ }
4628
+ );
4629
+ }
4630
+ );
4631
+ Button.displayName = "Button";
4632
+
4633
+ // src/ui/MediaOverlay.tsx
4634
+ import { Fragment as Fragment2, jsx as jsx11, jsxs as jsxs5 } from "react/jsx-runtime";
4635
+ var MEDIA_UPLOAD_FADE_MS = 300;
4636
+ var VIDEO_SETTINGS_BAR_INSET = 8;
4637
+ var OVERLAY_BUTTON_STYLE = {
4638
+ pointerEvents: "auto",
4639
+ fontFamily: "Inter, sans-serif",
4640
+ fontSize: 12,
4641
+ color: "#000"
4642
+ };
4643
+ var SKELETON_CSS = `
4644
+ @keyframes ohw-media-shimmer {
4645
+ 0% { transform: translateX(-100%); }
4646
+ 100% { transform: translateX(100%); }
4647
+ }
4648
+ [data-ohw-media-skeleton] {
4649
+ overflow: hidden;
4650
+ background-color: #d1d5db; /* tailwind gray-300 */
4651
+ }
4652
+ [data-ohw-media-skeleton]::after {
4653
+ content: "";
4654
+ position: absolute;
4655
+ inset: 0;
4656
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.6), transparent);
4657
+ animation: ohw-media-shimmer 1.4s infinite;
4658
+ }
4659
+ `;
4660
+ function MediaOverlay({
4661
+ hover,
4662
+ isUploading,
4663
+ fadingOut = false,
4664
+ onFadeOutComplete,
4665
+ onReplace,
4666
+ onVideoSettingsChange
4667
+ }) {
4668
+ const { rect } = hover;
4669
+ const skeletonRef = React6.useRef(null);
4670
+ const isVideo = hover.elementType === "video";
4671
+ const autoplay = hover.videoAutoplay ?? true;
4672
+ const muted = hover.videoMuted ?? true;
4673
+ const box = {
4674
+ position: "fixed",
4675
+ top: rect.top,
4676
+ left: rect.left,
4677
+ width: rect.width,
4678
+ height: rect.height,
4679
+ zIndex: 2147483646
4680
+ };
4681
+ React6.useEffect(() => {
4682
+ if (!isUploading || !fadingOut || !skeletonRef.current) return;
4683
+ const anim = skeletonRef.current.animate([{ opacity: 1 }, { opacity: 0 }], {
4684
+ duration: MEDIA_UPLOAD_FADE_MS,
4685
+ easing: "ease-out",
4686
+ fill: "forwards"
4687
+ });
4688
+ anim.finished.then(() => onFadeOutComplete?.(hover.key)).catch(() => onFadeOutComplete?.(hover.key));
4689
+ return () => anim.cancel();
4690
+ }, [isUploading, fadingOut, onFadeOutComplete, hover.key]);
4691
+ if (isUploading) {
4692
+ return /* @__PURE__ */ jsx11(
4693
+ "div",
4694
+ {
4695
+ ref: skeletonRef,
4696
+ "data-ohw-bridge": "",
4697
+ "data-ohw-media-overlay": "",
4698
+ "data-ohw-media-skeleton": "",
4699
+ "aria-hidden": true,
4700
+ style: { ...box, pointerEvents: "none" },
4701
+ children: /* @__PURE__ */ jsx11("style", { children: SKELETON_CSS })
4702
+ }
4703
+ );
4704
+ }
4705
+ const settingsBar = isVideo && !hover.isDragOver ? /* @__PURE__ */ jsxs5(
4706
+ "div",
4707
+ {
4708
+ "data-ohw-bridge": "",
4709
+ "data-ohw-media-overlay": "",
4710
+ className: "flex items-center justify-center gap-1.5",
4711
+ style: {
4712
+ position: "fixed",
4713
+ top: rect.top + VIDEO_SETTINGS_BAR_INSET,
4714
+ left: rect.left,
4715
+ width: rect.width,
4716
+ zIndex: 2147483647,
4717
+ pointerEvents: "auto"
4718
+ },
4719
+ onClick: (e) => e.stopPropagation(),
4720
+ children: [
4721
+ /* @__PURE__ */ jsx11(
4722
+ Button,
4723
+ {
4724
+ "data-ohw-media-overlay": "",
4725
+ variant: "outline",
4726
+ size: "sm",
4727
+ className: "cursor-pointer hover:bg-background",
4728
+ style: { ...OVERLAY_BUTTON_STYLE, minWidth: 0, padding: "0 8px" },
4729
+ "aria-label": autoplay ? "Disable autoplay" : "Enable autoplay",
4730
+ title: autoplay ? "Autoplay on \u2014 click to disable" : "Autoplay off \u2014 click to enable",
4731
+ onMouseDown: (e) => e.preventDefault(),
4732
+ onClick: (e) => {
4733
+ e.stopPropagation();
4734
+ onVideoSettingsChange?.(hover.key, { autoplay: !autoplay });
4735
+ },
4736
+ children: autoplay ? /* @__PURE__ */ jsx11(Pause, { size: 14 }) : /* @__PURE__ */ jsx11(Play, { size: 14 })
4737
+ }
4738
+ ),
4739
+ /* @__PURE__ */ jsx11(
4740
+ Button,
4741
+ {
4742
+ "data-ohw-media-overlay": "",
4743
+ variant: "outline",
4744
+ size: "sm",
4745
+ className: "cursor-pointer hover:bg-background",
4746
+ style: { ...OVERLAY_BUTTON_STYLE, minWidth: 0, padding: "0 8px" },
4747
+ "aria-label": muted ? "Unmute video" : "Mute video",
4748
+ title: muted ? "Muted \u2014 click to unmute" : "Sound on \u2014 click to mute",
4749
+ onMouseDown: (e) => e.preventDefault(),
4750
+ onClick: (e) => {
4751
+ e.stopPropagation();
4752
+ onVideoSettingsChange?.(hover.key, { muted: !muted });
4753
+ },
4754
+ children: muted ? /* @__PURE__ */ jsx11(VolumeX, { size: 14 }) : /* @__PURE__ */ jsx11(Volume2, { size: 14 })
4755
+ }
4756
+ )
4757
+ ]
4758
+ }
4759
+ ) : null;
4760
+ return /* @__PURE__ */ jsxs5(Fragment2, { children: [
4761
+ settingsBar,
4762
+ /* @__PURE__ */ jsx11(
4763
+ "div",
4764
+ {
4765
+ "data-ohw-bridge": "",
4766
+ "data-ohw-media-overlay": "",
4767
+ className: "flex items-center justify-center cursor-pointer",
4768
+ style: {
4769
+ ...box,
4770
+ // When text overlays this media, let clicks fall THROUGH to the text underneath. In the
4771
+ // parent this needed a whole `ow:click-at` round-trip to re-hit-test inside the iframe;
4772
+ // in-document, pointer-events does it natively. The button below opts back in, so
4773
+ // Replace still works.
4774
+ pointerEvents: hover.hasTextOverlap ? "none" : "auto",
4775
+ boxShadow: "inset 0 0 0 1.5px var(--color-primary)",
4776
+ background: "color-mix(in srgb, var(--color-primary) 20%, transparent)"
4777
+ },
4778
+ onClick: () => onReplace(hover.key),
4779
+ children: /* @__PURE__ */ jsxs5(
4780
+ Button,
4781
+ {
4782
+ "data-ohw-media-overlay": "",
4783
+ variant: "outline",
4784
+ size: "sm",
4785
+ className: "gap-1.5 cursor-pointer hover:bg-background",
4786
+ style: OVERLAY_BUTTON_STYLE,
4787
+ onMouseDown: (e) => e.preventDefault(),
4788
+ onClick: (e) => {
4789
+ e.stopPropagation();
4790
+ onReplace(hover.key);
4791
+ },
4792
+ children: [
4793
+ isVideo ? /* @__PURE__ */ jsx11(Film, { size: 14 }) : /* @__PURE__ */ jsx11(ImageIcon, { size: 14 }),
4794
+ isVideo ? "Replace video" : "Replace image"
4795
+ ]
4796
+ }
4797
+ )
4798
+ }
4799
+ )
4800
+ ] });
4801
+ }
4802
+
4583
4803
  // src/OhhwellsBridge.tsx
4584
- import { createPortal } from "react-dom";
4585
- import { usePathname, useRouter, useSearchParams } from "next/navigation";
4804
+ import { createPortal as createPortal2 } from "react-dom";
4805
+ import { usePathname as usePathname2, useRouter as useRouter2, useSearchParams } from "next/navigation";
4586
4806
 
4587
4807
  // src/lib/session-search.ts
4588
4808
  var STORAGE_KEY = "ohw-preserved-search";
@@ -4858,61 +5078,29 @@ function scrollToHashSectionWhenReady(behavior = "smooth") {
4858
5078
  tick();
4859
5079
  }
4860
5080
 
4861
- // src/ui/dialog.tsx
4862
- import * as React5 from "react";
4863
- import { Dialog as DialogPrimitive } from "radix-ui";
4864
-
4865
- // src/ui/icons.tsx
4866
- import { jsx as jsx10, jsxs as jsxs5 } from "react/jsx-runtime";
4867
- function IconX({ className, ...props }) {
4868
- return /* @__PURE__ */ jsxs5("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
4869
- /* @__PURE__ */ jsx10("path", { d: "M18 6 6 18" }),
4870
- /* @__PURE__ */ jsx10("path", { d: "m6 6 12 12" })
4871
- ] });
4872
- }
4873
- function IconChevronDown({ className, ...props }) {
4874
- return /* @__PURE__ */ jsx10("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: /* @__PURE__ */ jsx10("path", { d: "m6 9 6 6 6-6" }) });
4875
- }
4876
- function IconFile({ className, ...props }) {
4877
- return /* @__PURE__ */ jsxs5("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
4878
- /* @__PURE__ */ jsx10("path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }),
4879
- /* @__PURE__ */ jsx10("path", { d: "M14 2v4a2 2 0 0 0 2 2h4" })
4880
- ] });
4881
- }
4882
- function IconInfo({ className, ...props }) {
4883
- return /* @__PURE__ */ jsxs5("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
4884
- /* @__PURE__ */ jsx10("circle", { cx: "12", cy: "12", r: "10" }),
4885
- /* @__PURE__ */ jsx10("path", { d: "M12 16v-4" }),
4886
- /* @__PURE__ */ jsx10("path", { d: "M12 8h.01" })
4887
- ] });
4888
- }
4889
- function IconArrowRight({ className, ...props }) {
4890
- return /* @__PURE__ */ jsxs5("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
4891
- /* @__PURE__ */ jsx10("path", { d: "M5 12h14" }),
4892
- /* @__PURE__ */ jsx10("path", { d: "m12 5 7 7-7 7" })
4893
- ] });
4894
- }
4895
- function IconSection({ className, ...props }) {
4896
- return /* @__PURE__ */ jsxs5("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
4897
- /* @__PURE__ */ jsx10("rect", { width: "18", height: "7", x: "3", y: "3", rx: "1" }),
4898
- /* @__PURE__ */ jsx10("rect", { width: "9", height: "7", x: "3", y: "14", rx: "1" }),
4899
- /* @__PURE__ */ jsx10("rect", { width: "5", height: "7", x: "16", y: "14", rx: "1" })
4900
- ] });
4901
- }
5081
+ // src/ui/link-modal/LinkPopover.tsx
5082
+ import { useEffect as useEffect6 } from "react";
4902
5083
 
4903
5084
  // src/ui/dialog.tsx
4904
- import { jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
4905
- function Dialog2({ ...props }) {
4906
- return /* @__PURE__ */ jsx11(DialogPrimitive.Root, { "data-slot": "dialog", ...props });
5085
+ import * as React7 from "react";
5086
+ import { Dialog as DialogPrimitive } from "radix-ui";
5087
+ import { X } from "lucide-react";
5088
+ import { jsx as jsx12, jsxs as jsxs6 } from "react/jsx-runtime";
5089
+ function Dialog2({
5090
+ ...props
5091
+ }) {
5092
+ return /* @__PURE__ */ jsx12(DialogPrimitive.Root, { "data-slot": "dialog", ...props });
4907
5093
  }
4908
- function DialogPortal({ ...props }) {
4909
- return /* @__PURE__ */ jsx11(DialogPrimitive.Portal, { ...props });
5094
+ function DialogPortal({
5095
+ ...props
5096
+ }) {
5097
+ return /* @__PURE__ */ jsx12(DialogPrimitive.Portal, { ...props });
4910
5098
  }
4911
5099
  function DialogOverlay({
4912
5100
  className,
4913
5101
  ...props
4914
5102
  }) {
4915
- return /* @__PURE__ */ jsx11(
5103
+ return /* @__PURE__ */ jsx12(
4916
5104
  DialogPrimitive.Overlay,
4917
5105
  {
4918
5106
  "data-slot": "dialog-overlay",
@@ -4922,57 +5110,74 @@ function DialogOverlay({
4922
5110
  }
4923
5111
  );
4924
5112
  }
4925
- var DialogContent = React5.forwardRef(({ className, children, showCloseButton = true, container, ...props }, ref) => {
4926
- const positionMode = container ? "absolute" : "fixed";
4927
- return /* @__PURE__ */ jsxs6(DialogPortal, { container: container ?? void 0, children: [
4928
- /* @__PURE__ */ jsx11(DialogOverlay, { className: cn(positionMode, "inset-0") }),
4929
- /* @__PURE__ */ jsxs6(
4930
- DialogPrimitive.Content,
4931
- {
4932
- ref,
4933
- "data-slot": "dialog-content",
4934
- className: cn(
4935
- positionMode,
4936
- "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",
4937
- "rounded-xl border border-border bg-background font-sans shadow-lg outline-none",
4938
- container ? "max-h-[calc(100%-32px)] max-w-[calc(100%-16px)]" : "max-h-[calc(100vh-32px)] max-w-[calc(100vw-16px)]",
4939
- className
4940
- ),
4941
- onMouseDown: (e) => e.stopPropagation(),
4942
- ...props,
4943
- children: [
4944
- children,
4945
- showCloseButton ? /* @__PURE__ */ jsx11(
4946
- DialogPrimitive.Close,
4947
- {
4948
- type: "button",
4949
- className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
4950
- "aria-label": "Close",
4951
- children: /* @__PURE__ */ jsx11(IconX, { "aria-hidden": true })
4952
- }
4953
- ) : null
4954
- ]
4955
- }
4956
- )
4957
- ] });
4958
- });
5113
+ var DialogContent = React7.forwardRef(
5114
+ ({ className, children, showCloseButton = true, container, ...props }, ref) => {
5115
+ const positionMode = container ? "absolute" : "fixed";
5116
+ return /* @__PURE__ */ jsxs6(DialogPortal, { container: container ?? void 0, children: [
5117
+ /* @__PURE__ */ jsx12(DialogOverlay, { className: cn(positionMode, "inset-0") }),
5118
+ /* @__PURE__ */ jsxs6(
5119
+ DialogPrimitive.Content,
5120
+ {
5121
+ ref,
5122
+ "data-slot": "dialog-content",
5123
+ className: cn(
5124
+ positionMode,
5125
+ "left-1/2 top-1/2 z-[2147483647] flex w-full max-w-[483px] -translate-x-1/2 -translate-y-1/2 flex-col overflow-visible",
5126
+ "rounded-xl border border-border bg-background font-sans shadow-lg outline-none",
5127
+ container ? "max-h-[calc(100%-32px)] max-w-[calc(100%-16px)]" : "max-h-[calc(100vh-32px)] max-w-[calc(100vw-16px)]",
5128
+ className
5129
+ ),
5130
+ onMouseDown: (e) => e.stopPropagation(),
5131
+ ...props,
5132
+ children: [
5133
+ children,
5134
+ showCloseButton ? /* @__PURE__ */ jsx12(
5135
+ DialogPrimitive.Close,
5136
+ {
5137
+ type: "button",
5138
+ className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
5139
+ "aria-label": "Close",
5140
+ children: /* @__PURE__ */ jsx12(X, { size: 16, "aria-hidden": true })
5141
+ }
5142
+ ) : null
5143
+ ]
5144
+ }
5145
+ )
5146
+ ] });
5147
+ }
5148
+ );
4959
5149
  DialogContent.displayName = DialogPrimitive.Content.displayName;
4960
- function DialogHeader({ className, ...props }) {
4961
- return /* @__PURE__ */ jsx11("div", { className: cn("flex flex-col gap-1.5", className), ...props });
5150
+ function DialogHeader({
5151
+ className,
5152
+ ...props
5153
+ }) {
5154
+ return /* @__PURE__ */ jsx12("div", { className: cn("flex flex-col gap-1.5", className), ...props });
4962
5155
  }
4963
- function DialogFooter({ className, ...props }) {
4964
- return /* @__PURE__ */ jsx11("div", { className: cn("flex items-center justify-end gap-2", className), ...props });
5156
+ function DialogFooter({
5157
+ className,
5158
+ ...props
5159
+ }) {
5160
+ return /* @__PURE__ */ jsx12(
5161
+ "div",
5162
+ {
5163
+ className: cn("flex items-center justify-end gap-2", className),
5164
+ ...props
5165
+ }
5166
+ );
4965
5167
  }
4966
- var DialogTitle = React5.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx11(
5168
+ var DialogTitle = React7.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx12(
4967
5169
  DialogPrimitive.Title,
4968
5170
  {
4969
5171
  ref,
4970
- className: cn("text-lg font-semibold leading-none tracking-tight text-card-foreground", className),
5172
+ className: cn(
5173
+ "text-lg font-semibold leading-none tracking-tight text-card-foreground",
5174
+ className
5175
+ ),
4971
5176
  ...props
4972
5177
  }
4973
5178
  ));
4974
5179
  DialogTitle.displayName = DialogPrimitive.Title.displayName;
4975
- var DialogDescription = React5.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx11(
5180
+ var DialogDescription = React7.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx12(
4976
5181
  DialogPrimitive.Description,
4977
5182
  {
4978
5183
  ref,
@@ -4983,59 +5188,40 @@ var DialogDescription = React5.forwardRef(({ className, ...props }, ref) => /* @
4983
5188
  DialogDescription.displayName = DialogPrimitive.Description.displayName;
4984
5189
  var DialogClose = DialogPrimitive.Close;
4985
5190
 
4986
- // src/ui/button.tsx
4987
- import * as React6 from "react";
4988
- import { Slot } from "radix-ui";
4989
- import { jsx as jsx12 } from "react/jsx-runtime";
4990
- var buttonVariants = cva(
4991
- "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",
4992
- {
4993
- variants: {
4994
- variant: {
4995
- default: "bg-primary text-primary-foreground hover:opacity-90",
4996
- outline: "border border-border bg-background text-foreground shadow-sm hover:bg-muted/80",
4997
- ghost: "min-w-0 px-3 py-2 text-foreground hover:bg-muted/50"
4998
- },
4999
- size: {
5000
- default: "h-9",
5001
- sm: "h-8 min-w-[64px] px-2 py-1.5 text-sm"
5002
- }
5003
- },
5004
- defaultVariants: {
5005
- variant: "default",
5006
- size: "default"
5007
- }
5008
- }
5009
- );
5010
- var Button = React6.forwardRef(
5011
- ({ className, variant, size, asChild = false, ...props }, ref) => {
5012
- const Comp = asChild ? Slot.Root : "button";
5013
- return /* @__PURE__ */ jsx12(
5014
- Comp,
5015
- {
5016
- ref,
5017
- "data-slot": "button",
5018
- className: cn(buttonVariants({ variant, size, className })),
5019
- ...props
5020
- }
5021
- );
5022
- }
5023
- );
5024
- Button.displayName = "Button";
5191
+ // src/ui/link-modal/LinkEditorPanel.tsx
5192
+ import { Info, X as X3 } from "lucide-react";
5025
5193
 
5026
5194
  // src/ui/link-modal/DestinationBreadcrumb.tsx
5195
+ import { ArrowRight, File, GalleryVertical } from "lucide-react";
5027
5196
  import { jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
5028
- function DestinationBreadcrumb({ pageTitle, sectionLabel }) {
5197
+ function DestinationBreadcrumb({
5198
+ pageTitle,
5199
+ sectionLabel
5200
+ }) {
5029
5201
  return /* @__PURE__ */ jsxs7("div", { className: "flex w-full flex-col gap-2", children: [
5030
- /* @__PURE__ */ jsx13("p", { className: "text-sm font-medium text-foreground", children: "Destination" }),
5202
+ /* @__PURE__ */ jsx13("p", { className: "text-sm font-medium! text-foreground m-0", children: "Destination" }),
5031
5203
  /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-3", children: [
5032
5204
  /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-2", children: [
5033
- /* @__PURE__ */ jsx13(IconFile, { className: "shrink-0 text-foreground", "aria-hidden": true }),
5034
- /* @__PURE__ */ jsx13("span", { className: "text-sm font-medium leading-none text-foreground", children: pageTitle })
5205
+ /* @__PURE__ */ jsx13(File, { size: 16, className: "shrink-0 text-foreground", "aria-hidden": true }),
5206
+ /* @__PURE__ */ jsx13("span", { className: "text-sm font-medium leading-none text-foreground!", children: pageTitle })
5035
5207
  ] }),
5036
- /* @__PURE__ */ jsx13(IconArrowRight, { className: "shrink-0 text-muted-foreground", "aria-hidden": true }),
5208
+ /* @__PURE__ */ jsx13(
5209
+ ArrowRight,
5210
+ {
5211
+ size: 16,
5212
+ className: "shrink-0 text-muted-foreground",
5213
+ "aria-hidden": true
5214
+ }
5215
+ ),
5037
5216
  /* @__PURE__ */ jsxs7("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [
5038
- /* @__PURE__ */ jsx13(IconSection, { className: "shrink-0 text-foreground", "aria-hidden": true }),
5217
+ /* @__PURE__ */ jsx13(
5218
+ GalleryVertical,
5219
+ {
5220
+ size: 16,
5221
+ className: "shrink-0 text-foreground",
5222
+ "aria-hidden": true
5223
+ }
5224
+ ),
5039
5225
  /* @__PURE__ */ jsx13("span", { className: "truncate text-sm font-medium leading-none text-foreground", children: sectionLabel })
5040
5226
  ] })
5041
5227
  ] })
@@ -5043,8 +5229,13 @@ function DestinationBreadcrumb({ pageTitle, sectionLabel }) {
5043
5229
  }
5044
5230
 
5045
5231
  // src/ui/link-modal/SectionTreeItem.tsx
5232
+ import { GalleryVertical as GalleryVertical2 } from "lucide-react";
5046
5233
  import { jsx as jsx14, jsxs as jsxs8 } from "react/jsx-runtime";
5047
- function SectionTreeItem({ section, onSelect, selected }) {
5234
+ function SectionTreeItem({
5235
+ section,
5236
+ onSelect,
5237
+ selected
5238
+ }) {
5048
5239
  const interactive = Boolean(onSelect);
5049
5240
  return /* @__PURE__ */ jsxs8("div", { className: "flex h-9 w-full items-end pl-3", children: [
5050
5241
  /* @__PURE__ */ jsx14(
@@ -5072,30 +5263,28 @@ function SectionTreeItem({ section, onSelect, selected }) {
5072
5263
  interactive && selected && "border-primary"
5073
5264
  ),
5074
5265
  children: [
5075
- /* @__PURE__ */ jsx14(IconSection, { className: "shrink-0 text-foreground", "aria-hidden": true }),
5266
+ /* @__PURE__ */ jsx14(
5267
+ GalleryVertical2,
5268
+ {
5269
+ size: 16,
5270
+ className: "shrink-0 text-foreground",
5271
+ "aria-hidden": true
5272
+ }
5273
+ ),
5076
5274
  /* @__PURE__ */ jsx14("span", { className: "truncate text-sm font-normal leading-5 text-foreground", children: section.label })
5077
5275
  ]
5078
5276
  }
5079
5277
  )
5080
5278
  ] });
5081
5279
  }
5082
- function SectionPickerList({ sections, onSelect }) {
5083
- if (sections.length === 0) {
5084
- return /* @__PURE__ */ jsx14("p", { className: "text-sm text-muted-foreground", children: "No sections found on this page." });
5085
- }
5086
- return /* @__PURE__ */ jsxs8("div", { className: "flex flex-col gap-1", children: [
5087
- /* @__PURE__ */ jsx14("p", { className: "text-sm text-muted-foreground", children: "Pick a section this link should scroll to." }),
5088
- sections.map((section) => /* @__PURE__ */ jsx14(SectionTreeItem, { section, onSelect }, section.id))
5089
- ] });
5090
- }
5091
5280
 
5092
5281
  // src/ui/link-modal/UrlOrPageInput.tsx
5093
- import { useId as useId2, useRef as useRef2, useState as useState3 } from "react";
5282
+ import { useEffect as useEffect3, useId as useId2, useRef as useRef3, useState as useState3 } from "react";
5094
5283
 
5095
5284
  // src/ui/input.tsx
5096
- import * as React7 from "react";
5285
+ import * as React8 from "react";
5097
5286
  import { jsx as jsx15 } from "react/jsx-runtime";
5098
- var Input = React7.forwardRef(
5287
+ var Input = React8.forwardRef(
5099
5288
  ({ className, type, ...props }, ref) => {
5100
5289
  return /* @__PURE__ */ jsx15(
5101
5290
  "input",
@@ -5129,8 +5318,11 @@ function Label({ className, ...props }) {
5129
5318
  }
5130
5319
 
5131
5320
  // src/ui/link-modal/UrlOrPageInput.tsx
5321
+ import { ChevronDown, File as File2, X as X2 } from "lucide-react";
5132
5322
  import { jsx as jsx17, jsxs as jsxs9 } from "react/jsx-runtime";
5133
- function FieldChevron({ onClick }) {
5323
+ function FieldChevron({
5324
+ onClick
5325
+ }) {
5134
5326
  return /* @__PURE__ */ jsx17(
5135
5327
  "button",
5136
5328
  {
@@ -5139,7 +5331,7 @@ function FieldChevron({ onClick }) {
5139
5331
  onClick,
5140
5332
  "aria-label": "Open page list",
5141
5333
  tabIndex: -1,
5142
- children: /* @__PURE__ */ jsx17(IconChevronDown, {})
5334
+ children: /* @__PURE__ */ jsx17(ChevronDown, { size: 16 })
5143
5335
  }
5144
5336
  );
5145
5337
  }
@@ -5156,8 +5348,30 @@ function UrlOrPageInput({
5156
5348
  urlError
5157
5349
  }) {
5158
5350
  const inputId = useId2();
5159
- const inputRef = useRef2(null);
5351
+ const inputRef = useRef3(null);
5352
+ const rootRef = useRef3(null);
5160
5353
  const [isFocused, setIsFocused] = useState3(false);
5354
+ useEffect3(() => {
5355
+ if (!dropdownOpen) return;
5356
+ const isOutside = (e) => {
5357
+ const root = rootRef.current;
5358
+ if (!root) return true;
5359
+ const path = typeof e.composedPath === "function" ? e.composedPath() : [];
5360
+ if (path.includes(root)) return false;
5361
+ const target = e.target;
5362
+ return !(target instanceof Node && root.contains(target));
5363
+ };
5364
+ const closeIfOutside = (e) => {
5365
+ if (!isOutside(e)) return;
5366
+ onDropdownOpenChange(false);
5367
+ };
5368
+ document.addEventListener("pointerdown", closeIfOutside, true);
5369
+ document.addEventListener("mousedown", closeIfOutside, true);
5370
+ return () => {
5371
+ document.removeEventListener("pointerdown", closeIfOutside, true);
5372
+ document.removeEventListener("mousedown", closeIfOutside, true);
5373
+ };
5374
+ }, [dropdownOpen, onDropdownOpenChange]);
5161
5375
  const openDropdown = () => {
5162
5376
  if (readOnly || filteredPages.length === 0) return;
5163
5377
  onDropdownOpenChange(true);
@@ -5180,9 +5394,16 @@ function UrlOrPageInput({
5180
5394
  );
5181
5395
  return /* @__PURE__ */ jsxs9("div", { className: "flex w-full flex-col gap-2 p-0", children: [
5182
5396
  /* @__PURE__ */ jsx17(Label, { htmlFor: inputId, className: cn(urlError && "text-destructive"), children: "Destination" }),
5183
- /* @__PURE__ */ jsxs9("div", { className: "relative w-full", children: [
5397
+ /* @__PURE__ */ jsxs9("div", { ref: rootRef, className: "relative w-full", children: [
5184
5398
  /* @__PURE__ */ jsxs9("div", { "data-ohw-link-field": true, className: fieldClassName, children: [
5185
- selectedPage ? /* @__PURE__ */ jsx17("div", { className: "flex shrink-0 items-center pr-2", children: /* @__PURE__ */ jsx17(IconFile, { className: "text-foreground", "aria-hidden": true }) }) : null,
5399
+ selectedPage ? /* @__PURE__ */ jsx17("div", { className: "flex shrink-0 items-center pr-2", children: /* @__PURE__ */ jsx17(
5400
+ File2,
5401
+ {
5402
+ size: 16,
5403
+ className: "shrink-0 text-foreground",
5404
+ "aria-hidden": true
5405
+ }
5406
+ ) }) : null,
5186
5407
  readOnly ? /* @__PURE__ */ jsx17("span", { className: "min-w-0 flex-1 truncate text-sm leading-5 text-foreground", children: selectedPage?.title ?? value }) : /* @__PURE__ */ jsx17(
5187
5408
  Input,
5188
5409
  {
@@ -5194,7 +5415,14 @@ function UrlOrPageInput({
5194
5415
  setIsFocused(true);
5195
5416
  if (!selectedPage) openDropdown();
5196
5417
  },
5197
- onBlur: () => setIsFocused(false),
5418
+ onBlur: () => {
5419
+ setIsFocused(false);
5420
+ requestAnimationFrame(() => {
5421
+ if (!rootRef.current?.contains(document.activeElement)) {
5422
+ onDropdownOpenChange(false);
5423
+ }
5424
+ });
5425
+ },
5198
5426
  placeholder,
5199
5427
  className: cn(
5200
5428
  "min-w-0 flex-1 truncate border-0 bg-transparent p-0 text-sm font-normal leading-5 shadow-none outline-none ring-0 caret-foreground",
@@ -5210,7 +5438,7 @@ function UrlOrPageInput({
5210
5438
  onMouseDown: clearSelection,
5211
5439
  "aria-label": "Clear selected page",
5212
5440
  tabIndex: -1,
5213
- children: /* @__PURE__ */ jsx17(IconX, { "aria-hidden": true })
5441
+ children: /* @__PURE__ */ jsx17(X2, { size: 16, "aria-hidden": true })
5214
5442
  }
5215
5443
  ) : null,
5216
5444
  !readOnly ? /* @__PURE__ */ jsx17(FieldChevron, { onClick: toggleDropdown }) : null
@@ -5219,7 +5447,7 @@ function UrlOrPageInput({
5219
5447
  "div",
5220
5448
  {
5221
5449
  "data-ohw-link-page-dropdown": "",
5222
- className: "absolute left-0 right-0 h-20 top-[calc(100%+4px)] z-10 max-h-48 overflow-auto rounded-lg border border-border bg-popover py-1 shadow-lg",
5450
+ className: "absolute left-0 right-0 top-[calc(100%+4px)] z-50 max-h-48 overflow-auto rounded-lg border border-border bg-popover py-1 shadow-lg",
5223
5451
  onMouseDown: (e) => e.preventDefault(),
5224
5452
  children: filteredPages.map((page) => /* @__PURE__ */ jsxs9(
5225
5453
  "button",
@@ -5228,7 +5456,7 @@ function UrlOrPageInput({
5228
5456
  className: "flex h-9 w-full items-center gap-2 border-0 bg-transparent px-3 text-left text-sm leading-5 text-foreground outline-none hover:bg-muted",
5229
5457
  onClick: () => onPageSelect(page),
5230
5458
  children: [
5231
- /* @__PURE__ */ jsx17(IconFile, { className: "shrink-0", "aria-hidden": true }),
5459
+ /* @__PURE__ */ jsx17(File2, { size: 16, className: "shrink-0", "aria-hidden": true }),
5232
5460
  /* @__PURE__ */ jsx17("span", { className: "truncate", children: page.title })
5233
5461
  ]
5234
5462
  },
@@ -5241,65 +5469,402 @@ function UrlOrPageInput({
5241
5469
  ] });
5242
5470
  }
5243
5471
 
5244
- // src/ui/link-modal/useLinkModalState.ts
5245
- import { useCallback as useCallback2, useEffect as useEffect2, useMemo as useMemo2, useState as useState4 } from "react";
5246
- function useLinkModalState({
5247
- open,
5248
- mode,
5249
- pages,
5250
- sections: _sections,
5251
- sectionsByPath,
5252
- initialTarget,
5253
- existingTargets,
5254
- onClose,
5255
- onSubmit
5256
- }) {
5257
- const availablePages = useMemo2(
5258
- () => mode === "create" ? filterAvailablePages(pages, existingTargets) : pages,
5259
- [mode, pages, existingTargets]
5260
- );
5261
- const [searchValue, setSearchValue] = useState4("");
5262
- const [selectedPage, setSelectedPage] = useState4(null);
5263
- const [selectedSection, setSelectedSection] = useState4(null);
5264
- const [step, setStep] = useState4("input");
5265
- const [dropdownOpen, setDropdownOpen] = useState4(false);
5266
- const [urlError, setUrlError] = useState4("");
5267
- const reset = useCallback2(() => {
5268
- setSearchValue("");
5269
- setSelectedPage(null);
5270
- setSelectedSection(null);
5271
- setStep("input");
5272
- setDropdownOpen(false);
5273
- setUrlError("");
5274
- }, []);
5275
- useEffect2(() => {
5276
- if (!open) return;
5277
- if (mode === "edit" && initialTarget) {
5278
- const { pageRoute } = parseTarget(initialTarget);
5279
- const targetSections = getSectionsForPath(sectionsByPath, pageRoute);
5280
- const init = getEditModeInitialState(initialTarget, pages, targetSections);
5281
- setSearchValue(init.searchValue);
5282
- setSelectedPage(init.selectedPage);
5283
- setSelectedSection(init.selectedSection);
5284
- setStep(init.step);
5285
- } else {
5286
- reset();
5287
- }
5288
- setDropdownOpen(false);
5289
- setUrlError("");
5290
- }, [open, mode, initialTarget, pages, sectionsByPath, reset]);
5291
- const filteredPages = useMemo2(() => {
5292
- if (!searchValue.trim()) return availablePages;
5293
- return availablePages.filter((p) => p.title.toLowerCase().startsWith(searchValue.toLowerCase()));
5294
- }, [availablePages, searchValue]);
5295
- const activeSections = useMemo2(() => {
5296
- if (!selectedPage) return [];
5297
- return getSectionsForPath(sectionsByPath, selectedPage.path);
5298
- }, [selectedPage, sectionsByPath]);
5299
- const showBreadcrumb = step === "confirmed" && selectedPage && selectedSection;
5300
- const showSectionPicker = step === "sectionPicker" && selectedPage;
5301
- const showSectionRow = mode === "edit" && selectedPage && selectedSection && step === "input";
5302
- const showChooseSection = step === "input" && selectedPage && !selectedSection && !showSectionRow && activeSections.length > 0;
5472
+ // src/ui/link-modal/LinkEditorPanel.tsx
5473
+ import { Fragment as Fragment3, jsx as jsx18, jsxs as jsxs10 } from "react/jsx-runtime";
5474
+ function LinkEditorPanel({ state, onClose }) {
5475
+ const isCancel = state.secondaryLabel === "Cancel" || state.secondaryLabel === "Back to sections";
5476
+ return /* @__PURE__ */ jsxs10(Fragment3, { children: [
5477
+ /* @__PURE__ */ jsx18(DialogClose, { asChild: true, children: /* @__PURE__ */ jsx18(
5478
+ "button",
5479
+ {
5480
+ type: "button",
5481
+ className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50 h-7",
5482
+ "aria-label": "Close",
5483
+ onClick: onClose,
5484
+ children: /* @__PURE__ */ jsx18(X3, { size: 16, "aria-hidden": true })
5485
+ }
5486
+ ) }),
5487
+ /* @__PURE__ */ jsx18(DialogHeader, { className: "w-full gap-1.5 p-6 pr-12", children: /* @__PURE__ */ jsx18(DialogTitle, { className: "m-0 w-full break-words", children: state.title }) }),
5488
+ /* @__PURE__ */ jsxs10("div", { className: "flex w-full flex-col gap-3 px-6 pb-8 pt-1", children: [
5489
+ state.showBreadcrumb && state.selectedPage && state.selectedSection ? /* @__PURE__ */ jsx18(
5490
+ DestinationBreadcrumb,
5491
+ {
5492
+ pageTitle: state.selectedPage.title,
5493
+ sectionLabel: state.selectedSection.label
5494
+ }
5495
+ ) : /* @__PURE__ */ jsx18(
5496
+ UrlOrPageInput,
5497
+ {
5498
+ value: state.searchValue,
5499
+ selectedPage: state.selectedPage,
5500
+ dropdownOpen: state.dropdownOpen,
5501
+ onDropdownOpenChange: state.setDropdownOpen,
5502
+ filteredPages: state.filteredPages,
5503
+ onInputChange: state.handleInputChange,
5504
+ onPageSelect: state.handlePageSelect,
5505
+ urlError: state.urlError
5506
+ }
5507
+ ),
5508
+ state.showChooseSection ? /* @__PURE__ */ jsxs10("div", { className: "flex flex-col justify-center gap-2", children: [
5509
+ /* @__PURE__ */ jsx18(
5510
+ Button,
5511
+ {
5512
+ type: "button",
5513
+ variant: "outline",
5514
+ className: "w-fit cursor-pointer",
5515
+ size: "sm",
5516
+ onClick: state.handleChooseSection,
5517
+ children: "Choose a section"
5518
+ }
5519
+ ),
5520
+ /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-1 text-sm text-muted-foreground", children: [
5521
+ /* @__PURE__ */ jsx18(Info, { size: 16, className: "shrink-0", "aria-hidden": true }),
5522
+ /* @__PURE__ */ jsx18("span", { children: "Pick a section this link should scroll to." })
5523
+ ] })
5524
+ ] }) : null,
5525
+ state.showSectionRow && state.selectedSection ? /* @__PURE__ */ jsx18(SectionTreeItem, { section: state.selectedSection, selected: true }) : null
5526
+ ] }),
5527
+ /* @__PURE__ */ jsxs10(DialogFooter, { className: "w-full px-6 pb-6", children: [
5528
+ /* @__PURE__ */ jsx18(
5529
+ Button,
5530
+ {
5531
+ type: "button",
5532
+ variant: isCancel ? "outline" : "ghost",
5533
+ className: "leading-6 shadow-none cursor-pointer",
5534
+ style: isCancel ? {
5535
+ backgroundColor: "var(--ohw-background)",
5536
+ borderColor: "var(--ohw-border)",
5537
+ color: "var(--ohw-foreground)"
5538
+ } : void 0,
5539
+ onClick: state.handleSecondary,
5540
+ children: state.secondaryLabel
5541
+ }
5542
+ ),
5543
+ /* @__PURE__ */ jsx18(
5544
+ Button,
5545
+ {
5546
+ type: "button",
5547
+ className: "border-0 leading-6 shadow-none hover:opacity-90 disabled:opacity-50 cursor-pointer",
5548
+ style: {
5549
+ backgroundColor: "var(--ohw-primary)",
5550
+ color: "var(--ohw-primary-foreground)"
5551
+ },
5552
+ disabled: !state.isValid,
5553
+ onClick: state.handleSubmit,
5554
+ children: state.submitLabel
5555
+ }
5556
+ )
5557
+ ] })
5558
+ ] });
5559
+ }
5560
+
5561
+ // src/ui/link-modal/SectionPickerOverlay.tsx
5562
+ import { useCallback as useCallback2, useEffect as useEffect4, useMemo as useMemo2, useState as useState4 } from "react";
5563
+ import { createPortal } from "react-dom";
5564
+ import { ArrowLeft, Check } from "lucide-react";
5565
+ import { usePathname, useRouter } from "next/navigation";
5566
+ import { jsx as jsx19, jsxs as jsxs11 } from "react/jsx-runtime";
5567
+ var DIM_OVERLAY = "rgba(0, 0, 0, 0.45)";
5568
+ function rectsEqual(a, b) {
5569
+ if (a.size !== b.size) return false;
5570
+ for (const [id, rect] of a) {
5571
+ const other = b.get(id);
5572
+ if (!other || rect.top !== other.top || rect.left !== other.left || rect.width !== other.width || rect.height !== other.height) {
5573
+ return false;
5574
+ }
5575
+ }
5576
+ return true;
5577
+ }
5578
+ function useSectionRects(sectionIdsKey, enabled) {
5579
+ const [rects, setRects] = useState4(/* @__PURE__ */ new Map());
5580
+ const sectionIds = useMemo2(
5581
+ () => sectionIdsKey ? sectionIdsKey.split("\0") : [],
5582
+ [sectionIdsKey]
5583
+ );
5584
+ useEffect4(() => {
5585
+ if (!enabled || sectionIds.length === 0) {
5586
+ setRects((prev) => prev.size === 0 ? prev : /* @__PURE__ */ new Map());
5587
+ return;
5588
+ }
5589
+ const update = () => {
5590
+ const next = /* @__PURE__ */ new Map();
5591
+ for (const id of sectionIds) {
5592
+ const el = document.querySelector(
5593
+ `[data-ohw-section="${CSS.escape(id)}"]`
5594
+ );
5595
+ if (el) next.set(id, el.getBoundingClientRect());
5596
+ }
5597
+ setRects((prev) => rectsEqual(prev, next) ? prev : next);
5598
+ };
5599
+ update();
5600
+ const opts = { capture: true, passive: true };
5601
+ window.addEventListener("scroll", update, opts);
5602
+ window.addEventListener("resize", update);
5603
+ const vv = window.visualViewport;
5604
+ vv?.addEventListener("resize", update);
5605
+ vv?.addEventListener("scroll", update);
5606
+ const ro = new ResizeObserver(update);
5607
+ for (const id of sectionIds) {
5608
+ const el = document.querySelector(
5609
+ `[data-ohw-section="${CSS.escape(id)}"]`
5610
+ );
5611
+ if (el) ro.observe(el);
5612
+ }
5613
+ return () => {
5614
+ window.removeEventListener("scroll", update, opts);
5615
+ window.removeEventListener("resize", update);
5616
+ vv?.removeEventListener("resize", update);
5617
+ vv?.removeEventListener("scroll", update);
5618
+ ro.disconnect();
5619
+ };
5620
+ }, [sectionIds, enabled]);
5621
+ return rects;
5622
+ }
5623
+ function SectionPickerOverlay({
5624
+ pagePath,
5625
+ sections,
5626
+ onBack,
5627
+ onSelect
5628
+ }) {
5629
+ const router = useRouter();
5630
+ const pathname = usePathname();
5631
+ const [selectedId, setSelectedId] = useState4(null);
5632
+ const [hoveredId, setHoveredId] = useState4(null);
5633
+ const [chromeClip, setChromeClip] = useState4(
5634
+ () => ({
5635
+ top: 0,
5636
+ bottom: typeof window !== "undefined" ? window.innerHeight : 0
5637
+ })
5638
+ );
5639
+ const normalizedTarget = normalizePath(pagePath);
5640
+ const isOnTargetPage = normalizePath(pathname) === normalizedTarget;
5641
+ useEffect4(() => {
5642
+ if (isOnTargetPage) return;
5643
+ const search = readPreservedSearch();
5644
+ router.push(`${normalizedTarget}${search}`);
5645
+ }, [isOnTargetPage, normalizedTarget, router]);
5646
+ useEffect4(() => {
5647
+ document.documentElement.setAttribute("data-ohw-section-picking", "");
5648
+ document.querySelectorAll("[data-ohw-hovered]").forEach((el) => {
5649
+ el.removeAttribute("data-ohw-hovered");
5650
+ });
5651
+ return () => {
5652
+ document.documentElement.removeAttribute("data-ohw-section-picking");
5653
+ };
5654
+ }, []);
5655
+ useEffect4(() => {
5656
+ const applyClip = (iframeOffsetTop, visibleCanvasTop, canvasH) => {
5657
+ const top = Math.max(0, visibleCanvasTop - iframeOffsetTop);
5658
+ const bottom = Math.min(
5659
+ window.innerHeight,
5660
+ visibleCanvasTop + canvasH - iframeOffsetTop
5661
+ );
5662
+ setChromeClip(
5663
+ (prev) => prev.top === top && prev.bottom === bottom ? prev : { top, bottom: Math.max(top, bottom) }
5664
+ );
5665
+ };
5666
+ const onParentScroll = (e) => {
5667
+ if (e.data?.type !== "ow:parent-scroll") return;
5668
+ const { iframeOffsetTop, headerH, canvasH } = e.data;
5669
+ applyClip(iframeOffsetTop, headerH, canvasH);
5670
+ };
5671
+ const onResize = () => {
5672
+ setChromeClip((prev) => {
5673
+ const bottom = Math.max(prev.top, window.innerHeight);
5674
+ return prev.bottom === bottom ? prev : { ...prev, bottom };
5675
+ });
5676
+ };
5677
+ window.addEventListener("message", onParentScroll);
5678
+ window.addEventListener("resize", onResize);
5679
+ return () => {
5680
+ window.removeEventListener("message", onParentScroll);
5681
+ window.removeEventListener("resize", onResize);
5682
+ };
5683
+ }, []);
5684
+ const liveSections = useMemo2(() => {
5685
+ if (!isOnTargetPage) return sections;
5686
+ const live = collectSectionsFromDom();
5687
+ if (live.length === 0) return sections;
5688
+ const labels = new Map(sections.map((s) => [s.id, s.label]));
5689
+ return live.map((s) => ({ id: s.id, label: labels.get(s.id) ?? s.label }));
5690
+ }, [isOnTargetPage, sections, pathname]);
5691
+ const sectionIdsKey = useMemo2(
5692
+ () => liveSections.map((s) => s.id).join("\0"),
5693
+ [liveSections]
5694
+ );
5695
+ const rects = useSectionRects(sectionIdsKey, isOnTargetPage);
5696
+ const handleSelect = useCallback2(
5697
+ (section) => {
5698
+ if (selectedId) return;
5699
+ setSelectedId(section.id);
5700
+ onSelect(section);
5701
+ },
5702
+ [selectedId, onSelect]
5703
+ );
5704
+ useEffect4(() => {
5705
+ const onKeyDown = (e) => {
5706
+ if (e.key === "Escape") {
5707
+ e.preventDefault();
5708
+ e.stopPropagation();
5709
+ onBack();
5710
+ }
5711
+ };
5712
+ window.addEventListener("keydown", onKeyDown, true);
5713
+ return () => window.removeEventListener("keydown", onKeyDown, true);
5714
+ }, [onBack]);
5715
+ const portalRoot = typeof document !== "undefined" ? document.querySelector("[data-ohw-bridge-root]") ?? document.body : null;
5716
+ if (!portalRoot) return null;
5717
+ return createPortal(
5718
+ /* @__PURE__ */ jsxs11(
5719
+ "div",
5720
+ {
5721
+ "data-ohw-section-picker": "",
5722
+ "data-ohw-bridge": "",
5723
+ className: `pointer-events-none fixed inset-0 z-[2147483647] transition-opacity duration-200 ${"opacity-100"}`,
5724
+ "aria-modal": true,
5725
+ role: "dialog",
5726
+ "aria-label": "Choose a section",
5727
+ children: [
5728
+ /* @__PURE__ */ jsx19(
5729
+ "div",
5730
+ {
5731
+ className: "pointer-events-auto fixed left-5 z-[2]",
5732
+ style: { top: chromeClip.top + 20 },
5733
+ children: /* @__PURE__ */ jsxs11(
5734
+ Button,
5735
+ {
5736
+ type: "button",
5737
+ variant: "outline",
5738
+ size: "sm",
5739
+ className: "h-8 min-w-0 gap-1 border-border bg-background px-2 py-1.5 shadow-sm hover:bg-muted cursor-pointer",
5740
+ onClick: onBack,
5741
+ children: [
5742
+ /* @__PURE__ */ jsx19(ArrowLeft, { className: "size-4 shrink-0", "aria-hidden": true }),
5743
+ "Back"
5744
+ ]
5745
+ }
5746
+ )
5747
+ }
5748
+ ),
5749
+ /* @__PURE__ */ jsx19(
5750
+ "div",
5751
+ {
5752
+ className: "pointer-events-none fixed left-1/2 z-[2] rounded-lg px-4 py-3 text-xs leading-4 tracking-[0.18px] text-white shadow-md",
5753
+ style: {
5754
+ top: chromeClip.bottom - 20,
5755
+ transform: "translate(-50%, -100%)",
5756
+ backgroundColor: "var(--ohw-popover-foreground, #0c0a09)"
5757
+ },
5758
+ "data-ohw-section-picker-hint": "",
5759
+ children: "Click on section to select"
5760
+ }
5761
+ ),
5762
+ !isOnTargetPage ? /* @__PURE__ */ jsx19(
5763
+ "div",
5764
+ {
5765
+ className: "pointer-events-none fixed left-1/2 z-[1] -translate-x-1/2 rounded-md bg-background/90 px-3 py-2 text-sm text-muted-foreground shadow-sm",
5766
+ style: { top: chromeClip.top + 64 },
5767
+ children: "Loading page preview\u2026"
5768
+ }
5769
+ ) : null,
5770
+ isOnTargetPage && liveSections.length === 0 ? /* @__PURE__ */ jsx19("div", { className: "pointer-events-auto fixed inset-0 z-[1] flex items-center justify-center bg-muted/40", children: /* @__PURE__ */ jsx19("p", { className: "text-sm text-muted-foreground", children: "No sections found on this page." }) }) : null,
5771
+ isOnTargetPage ? liveSections.map((section) => {
5772
+ const rect = rects.get(section.id);
5773
+ if (!rect || rect.width <= 0 || rect.height <= 0) return null;
5774
+ const isSelected = selectedId === section.id;
5775
+ const isLit = isSelected || hoveredId === section.id;
5776
+ return /* @__PURE__ */ jsx19(
5777
+ "button",
5778
+ {
5779
+ type: "button",
5780
+ className: "pointer-events-auto fixed z-[1] cursor-pointer border-0 bg-transparent p-0 transition-[background-color] duration-150",
5781
+ style: {
5782
+ top: rect.top,
5783
+ left: rect.left,
5784
+ width: rect.width,
5785
+ height: rect.height,
5786
+ backgroundColor: isLit ? "transparent" : DIM_OVERLAY
5787
+ },
5788
+ "aria-label": `Select section ${section.label}`,
5789
+ onMouseEnter: () => setHoveredId(section.id),
5790
+ onMouseLeave: () => setHoveredId((prev) => prev === section.id ? null : prev),
5791
+ onClick: () => handleSelect(section),
5792
+ children: isSelected ? /* @__PURE__ */ jsx19(
5793
+ "span",
5794
+ {
5795
+ className: "absolute right-3 top-3 flex size-8 items-center justify-center rounded-full text-white",
5796
+ style: { backgroundColor: "var(--ohw-primary, #0885fe)" },
5797
+ "aria-hidden": true,
5798
+ children: /* @__PURE__ */ jsx19(Check, { className: "size-5" })
5799
+ }
5800
+ ) : null
5801
+ },
5802
+ section.id
5803
+ );
5804
+ }) : null
5805
+ ]
5806
+ }
5807
+ ),
5808
+ portalRoot
5809
+ );
5810
+ }
5811
+
5812
+ // src/ui/link-modal/useLinkModalState.ts
5813
+ import { useCallback as useCallback3, useEffect as useEffect5, useMemo as useMemo3, useState as useState5 } from "react";
5814
+ function useLinkModalState({
5815
+ open,
5816
+ mode,
5817
+ pages,
5818
+ sections: _sections,
5819
+ sectionsByPath,
5820
+ initialTarget,
5821
+ existingTargets: _existingTargets,
5822
+ onClose,
5823
+ onSubmit
5824
+ }) {
5825
+ const availablePages = useMemo3(() => pages, [pages]);
5826
+ const [searchValue, setSearchValue] = useState5("");
5827
+ const [selectedPage, setSelectedPage] = useState5(null);
5828
+ const [selectedSection, setSelectedSection] = useState5(null);
5829
+ const [step, setStep] = useState5("input");
5830
+ const [dropdownOpen, setDropdownOpen] = useState5(false);
5831
+ const [urlError, setUrlError] = useState5("");
5832
+ const reset = useCallback3(() => {
5833
+ setSearchValue("");
5834
+ setSelectedPage(null);
5835
+ setSelectedSection(null);
5836
+ setStep("input");
5837
+ setDropdownOpen(false);
5838
+ setUrlError("");
5839
+ }, []);
5840
+ useEffect5(() => {
5841
+ if (!open) return;
5842
+ if (mode === "edit" && initialTarget) {
5843
+ const { pageRoute } = parseTarget(initialTarget);
5844
+ const targetSections = getSectionsForPath(sectionsByPath, pageRoute);
5845
+ const init = getEditModeInitialState(initialTarget, pages, targetSections);
5846
+ setSearchValue(init.searchValue);
5847
+ setSelectedPage(init.selectedPage);
5848
+ setSelectedSection(init.selectedSection);
5849
+ setStep(init.step);
5850
+ } else {
5851
+ reset();
5852
+ }
5853
+ setDropdownOpen(false);
5854
+ setUrlError("");
5855
+ }, [open, mode, initialTarget, reset]);
5856
+ const filteredPages = useMemo3(() => {
5857
+ if (!searchValue.trim()) return availablePages;
5858
+ return availablePages.filter((p) => p.title.toLowerCase().startsWith(searchValue.toLowerCase()));
5859
+ }, [availablePages, searchValue]);
5860
+ const activeSections = useMemo3(() => {
5861
+ if (!selectedPage) return [];
5862
+ return getSectionsForPath(sectionsByPath, selectedPage.path);
5863
+ }, [selectedPage, sectionsByPath]);
5864
+ const showBreadcrumb = step === "confirmed" && selectedPage && selectedSection;
5865
+ const showSectionPicker = step === "sectionPicker" && selectedPage;
5866
+ const showSectionRow = mode === "edit" && selectedPage && selectedSection && step === "input";
5867
+ const showChooseSection = step === "input" && selectedPage && !selectedSection && !showSectionRow && activeSections.length > 0;
5303
5868
  const handleInputChange = (value) => {
5304
5869
  setSearchValue(value);
5305
5870
  setSelectedPage(null);
@@ -5334,11 +5899,14 @@ function useLinkModalState({
5334
5899
  const handleBackToSections = () => {
5335
5900
  setStep("sectionPicker");
5336
5901
  };
5902
+ const handleSectionPickerBack = () => {
5903
+ setStep("input");
5904
+ };
5337
5905
  const handleClose = () => {
5338
5906
  reset();
5339
5907
  onClose();
5340
5908
  };
5341
- const isValid = useMemo2(() => {
5909
+ const isValid = useMemo3(() => {
5342
5910
  if (urlError) return false;
5343
5911
  if (showBreadcrumb) return true;
5344
5912
  if (selectedPage) return true;
@@ -5371,7 +5939,7 @@ function useLinkModalState({
5371
5939
  onSubmit(normalizeUrl(searchValue));
5372
5940
  handleClose();
5373
5941
  };
5374
- const submitLabel = mode === "edit" ? "Save" : "Create";
5942
+ const submitLabel = showBreadcrumb ? "Save" : mode === "edit" ? "Save" : "Create";
5375
5943
  const title = mode === "edit" ? "Edit navigation item" : "Create navigation item";
5376
5944
  const secondaryLabel = showBreadcrumb ? "Back to sections" : "Cancel";
5377
5945
  const handleSecondary = showBreadcrumb ? handleBackToSections : handleClose;
@@ -5398,23 +5966,29 @@ function useLinkModalState({
5398
5966
  handleChooseSection,
5399
5967
  handleSectionSelect,
5400
5968
  handleBackToSections,
5969
+ handleSectionPickerBack,
5401
5970
  handleClose,
5402
5971
  handleSecondary,
5403
5972
  handleSubmit
5404
5973
  };
5405
5974
  }
5406
5975
 
5407
- // src/ui/link-modal/LinkEditorPanel.tsx
5408
- import { Fragment as Fragment2, jsx as jsx18, jsxs as jsxs10 } from "react/jsx-runtime";
5409
- function LinkEditorPanel({
5976
+ // src/ui/link-modal/LinkPopover.tsx
5977
+ import { Fragment as Fragment4, jsx as jsx20, jsxs as jsxs12 } from "react/jsx-runtime";
5978
+ function postToParent(data) {
5979
+ window.parent?.postMessage(data, "*");
5980
+ }
5981
+ function LinkPopover({
5410
5982
  open = true,
5983
+ panelRef,
5984
+ portalContainer,
5985
+ onClose,
5411
5986
  mode = "create",
5412
5987
  pages,
5413
5988
  sections = [],
5414
5989
  sectionsByPath,
5415
5990
  initialTarget,
5416
5991
  existingTargets = [],
5417
- onClose,
5418
5992
  onSubmit
5419
5993
  }) {
5420
5994
  const state = useLinkModalState({
@@ -5428,175 +6002,440 @@ function LinkEditorPanel({
5428
6002
  onClose,
5429
6003
  onSubmit
5430
6004
  });
5431
- const isCancel = state.secondaryLabel === "Cancel";
5432
- return /* @__PURE__ */ jsxs10(Fragment2, { children: [
5433
- /* @__PURE__ */ jsx18(DialogClose, { asChild: true, children: /* @__PURE__ */ jsx18(
5434
- "button",
6005
+ const sectionPickerActive = state.showSectionPicker && Boolean(state.selectedPage);
6006
+ useEffect6(() => {
6007
+ if (!open) return;
6008
+ if (sectionPickerActive) {
6009
+ postToParent({ type: "ow:link-modal-lock", locked: false });
6010
+ postToParent({ type: "ow:section-picker", active: true });
6011
+ document.documentElement.style.overflow = "";
6012
+ document.body.style.overflow = "";
6013
+ return () => {
6014
+ postToParent({ type: "ow:section-picker", active: false });
6015
+ };
6016
+ }
6017
+ postToParent({ type: "ow:section-picker", active: false });
6018
+ postToParent({ type: "ow:link-modal-lock", locked: true });
6019
+ const html = document.documentElement;
6020
+ const body = document.body;
6021
+ const prevHtmlOverflow = html.style.overflow;
6022
+ const prevBodyOverflow = body.style.overflow;
6023
+ const prevHtmlOverscroll = html.style.overscrollBehavior;
6024
+ const prevBodyOverscroll = body.style.overscrollBehavior;
6025
+ html.style.overflow = "hidden";
6026
+ body.style.overflow = "hidden";
6027
+ html.style.overscrollBehavior = "none";
6028
+ body.style.overscrollBehavior = "none";
6029
+ const canScrollElement = (el, deltaY) => {
6030
+ const { overflowY } = getComputedStyle(el);
6031
+ if (overflowY !== "auto" && overflowY !== "scroll") return false;
6032
+ if (el.scrollHeight <= el.clientHeight + 1) return false;
6033
+ if (deltaY < 0) return el.scrollTop > 0;
6034
+ if (deltaY > 0) return el.scrollTop + el.clientHeight < el.scrollHeight - 1;
6035
+ return true;
6036
+ };
6037
+ const allowsInternalScroll = (e) => {
6038
+ const target = e.target;
6039
+ if (!(target instanceof Element)) return false;
6040
+ const scrollRoot = target.closest(
6041
+ "[data-ohw-link-page-dropdown], [data-ohw-allow-scroll]"
6042
+ );
6043
+ if (!scrollRoot) return false;
6044
+ const deltaY = e instanceof WheelEvent ? e.deltaY : 0;
6045
+ return canScrollElement(scrollRoot, deltaY);
6046
+ };
6047
+ const preventBackgroundScroll = (e) => {
6048
+ if (allowsInternalScroll(e)) return;
6049
+ e.preventDefault();
6050
+ };
6051
+ const scrollOpts = { passive: false, capture: true };
6052
+ document.addEventListener("wheel", preventBackgroundScroll, scrollOpts);
6053
+ document.addEventListener("touchmove", preventBackgroundScroll, scrollOpts);
6054
+ return () => {
6055
+ postToParent({ type: "ow:link-modal-lock", locked: false });
6056
+ html.style.overflow = prevHtmlOverflow;
6057
+ body.style.overflow = prevBodyOverflow;
6058
+ html.style.overscrollBehavior = prevHtmlOverscroll;
6059
+ body.style.overscrollBehavior = prevBodyOverscroll;
6060
+ document.removeEventListener("wheel", preventBackgroundScroll, scrollOpts);
6061
+ document.removeEventListener("touchmove", preventBackgroundScroll, scrollOpts);
6062
+ };
6063
+ }, [open, sectionPickerActive]);
6064
+ return /* @__PURE__ */ jsxs12(Fragment4, { children: [
6065
+ /* @__PURE__ */ jsx20(
6066
+ Dialog2,
5435
6067
  {
5436
- type: "button",
5437
- className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
5438
- "aria-label": "Close",
5439
- children: /* @__PURE__ */ jsx18(IconX, { "aria-hidden": true })
6068
+ open: open && !sectionPickerActive,
6069
+ onOpenChange: (next) => {
6070
+ if (!next) onClose?.();
6071
+ },
6072
+ children: /* @__PURE__ */ jsx20(
6073
+ DialogContent,
6074
+ {
6075
+ ref: panelRef,
6076
+ container: portalContainer,
6077
+ "data-ohw-link-popover-root": "",
6078
+ "data-ohw-link-modal-root": "",
6079
+ "data-ohw-bridge": "",
6080
+ showCloseButton: false,
6081
+ className: "gap-0 p-0 w-full md:w-md pointer-events-auto z-[2147483646] overflow-visible",
6082
+ children: /* @__PURE__ */ jsx20(LinkEditorPanel, { state, onClose })
6083
+ }
6084
+ )
5440
6085
  }
5441
- ) }),
5442
- /* @__PURE__ */ jsx18(DialogHeader, { className: "w-full gap-1.5 p-6 pr-12", children: /* @__PURE__ */ jsx18(DialogTitle, { className: "m-0 w-full break-words", children: state.title }) }),
5443
- /* @__PURE__ */ jsxs10("div", { className: "flex w-full flex-col gap-3 px-6 pb-8 pt-1", children: [
5444
- state.showBreadcrumb && state.selectedPage && state.selectedSection ? /* @__PURE__ */ jsx18(
5445
- DestinationBreadcrumb,
5446
- {
5447
- pageTitle: state.selectedPage.title,
5448
- sectionLabel: state.selectedSection.label
5449
- }
5450
- ) : /* @__PURE__ */ jsx18(
5451
- UrlOrPageInput,
5452
- {
5453
- value: state.searchValue,
5454
- selectedPage: state.selectedPage,
5455
- dropdownOpen: state.dropdownOpen,
5456
- onDropdownOpenChange: state.setDropdownOpen,
5457
- filteredPages: state.filteredPages,
5458
- onInputChange: state.handleInputChange,
5459
- onPageSelect: state.handlePageSelect,
5460
- urlError: state.urlError
5461
- }
5462
- ),
5463
- state.showChooseSection ? /* @__PURE__ */ jsxs10("div", { className: "flex flex-col justify-center gap-2", children: [
5464
- /* @__PURE__ */ jsx18(Button, { type: "button", variant: "outline", className: "w-fit cursor-pointer", size: "sm", onClick: state.handleChooseSection, children: "Choose a section" }),
5465
- /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-1 text-sm text-muted-foreground", children: [
5466
- /* @__PURE__ */ jsx18(IconInfo, { className: "shrink-0", "aria-hidden": true }),
5467
- /* @__PURE__ */ jsx18("span", { children: "Pick a section this link should scroll to." })
5468
- ] })
5469
- ] }) : null,
5470
- state.showSectionPicker ? /* @__PURE__ */ jsx18(SectionPickerList, { sections: state.activeSections, onSelect: state.handleSectionSelect }) : null,
5471
- state.showSectionRow && state.selectedSection ? /* @__PURE__ */ jsx18(SectionTreeItem, { section: state.selectedSection, selected: true }) : null
5472
- ] }),
5473
- /* @__PURE__ */ jsxs10(DialogFooter, { className: "w-full px-6 pb-6", children: [
5474
- /* @__PURE__ */ jsx18(
5475
- Button,
5476
- {
5477
- type: "button",
5478
- variant: isCancel ? "outline" : "ghost",
5479
- className: "leading-6 shadow-none cursor-pointer",
5480
- style: isCancel ? {
5481
- backgroundColor: "var(--ohw-background)",
5482
- borderColor: "var(--ohw-border)",
5483
- color: "var(--ohw-foreground)"
5484
- } : void 0,
5485
- onClick: state.handleSecondary,
5486
- children: state.secondaryLabel
5487
- }
5488
- ),
5489
- /* @__PURE__ */ jsx18(
5490
- Button,
5491
- {
5492
- type: "button",
5493
- className: "border-0 leading-6 shadow-none hover:opacity-90 disabled:opacity-50 cursor-pointer",
5494
- style: {
5495
- backgroundColor: "var(--ohw-primary)",
5496
- color: "var(--ohw-primary-foreground)"
5497
- },
5498
- disabled: !state.isValid,
5499
- onClick: state.handleSubmit,
5500
- children: state.submitLabel
5501
- }
5502
- )
5503
- ] })
6086
+ ),
6087
+ sectionPickerActive && state.selectedPage ? /* @__PURE__ */ jsx20(
6088
+ SectionPickerOverlay,
6089
+ {
6090
+ pagePath: state.selectedPage.path,
6091
+ sections: state.activeSections,
6092
+ onBack: state.handleSectionPickerBack,
6093
+ onSelect: state.handleSectionSelect
6094
+ }
6095
+ ) : null
5504
6096
  ] });
5505
6097
  }
5506
6098
 
5507
- // src/ui/link-modal/LinkPopover.tsx
5508
- import { jsx as jsx19 } from "react/jsx-runtime";
5509
- function LinkPopover({
5510
- open = true,
5511
- panelRef,
5512
- portalContainer,
5513
- onClose,
5514
- ...editorProps
6099
+ // src/ui/link-modal/devFixtures.ts
6100
+ var DEV_SITE_PAGES = [
6101
+ { path: "/", title: "Home" },
6102
+ { path: "/about", title: "About" },
6103
+ { path: "/classes", title: "Classes" },
6104
+ { path: "/pricing", title: "Pricing" },
6105
+ { path: "/studio", title: "Studio" },
6106
+ { path: "/instructors", title: "Instructors" },
6107
+ { path: "/contact", title: "Contact" }
6108
+ ];
6109
+ var DEV_SECTIONS_BY_PATH = {
6110
+ "/": [
6111
+ { id: "hero", label: "Hero" },
6112
+ { id: "lagree-intro", label: "Lagree Intro" },
6113
+ { id: "classes-strip", label: "Classes" },
6114
+ { id: "feel-split", label: "Feel Split" },
6115
+ { id: "founder-teaser", label: "Founder" },
6116
+ { id: "plan-form", label: "Plan Form" },
6117
+ { id: "testimonials", label: "Testimonials" },
6118
+ { id: "wordmark", label: "Wordmark" },
6119
+ { id: "footer", label: "Footer" }
6120
+ ],
6121
+ "/about": [
6122
+ { id: "manifesto", label: "Manifesto" },
6123
+ { id: "story-letter", label: "Our Story" },
6124
+ { id: "lagree-explainer", label: "Lagree Explainer" },
6125
+ { id: "waitlist-cta", label: "Waitlist" },
6126
+ { id: "personal-training", label: "Personal training" },
6127
+ { id: "footer", label: "Footer" }
6128
+ ],
6129
+ "/classes": [
6130
+ { id: "class-library", label: "Class Library" },
6131
+ { id: "footer", label: "Footer" }
6132
+ ],
6133
+ "/pricing": [
6134
+ { id: "page-header", label: "Page Header" },
6135
+ { id: "free-class-cta", label: "Free Class" },
6136
+ { id: "benefits-marquee", label: "Benefits" },
6137
+ { id: "monthly-memberships", label: "Memberships" },
6138
+ { id: "specialty-programs", label: "Specialty Programs" },
6139
+ { id: "package-matcher", label: "Packages" },
6140
+ { id: "wordmark", label: "Wordmark" },
6141
+ { id: "footer", label: "Footer" }
6142
+ ],
6143
+ "/studio": [
6144
+ { id: "studio-intro", label: "Studio Intro" },
6145
+ { id: "studio-features", label: "Studio Features" },
6146
+ { id: "studio-gallery", label: "Studio Gallery" },
6147
+ { id: "studio-visit", label: "Visit Studio" },
6148
+ { id: "footer", label: "Footer" }
6149
+ ],
6150
+ "/instructors": [
6151
+ { id: "instructor-grid", label: "Instructors" },
6152
+ { id: "footer", label: "Footer" }
6153
+ ],
6154
+ "/contact": [
6155
+ { id: "hello-hero", label: "Hello" },
6156
+ { id: "contact-form", label: "Contact Form" },
6157
+ { id: "faq", label: "FAQ" },
6158
+ { id: "footer", label: "Footer" }
6159
+ ]
6160
+ };
6161
+ function shouldUseDevFixtures() {
6162
+ if (typeof window === "undefined") return false;
6163
+ const raw = readPreservedSearch();
6164
+ const q = raw.startsWith("?") ? raw.slice(1) : raw;
6165
+ return new URLSearchParams(q).get("ohw-fixtures") === "1";
6166
+ }
6167
+
6168
+ // src/lib/nav-items.ts
6169
+ var NAV_COUNT_KEY = "__ohw_nav_count";
6170
+ var NAV_ORDER_KEY = "__ohw_nav_order";
6171
+ function getLinkHref(el) {
6172
+ const key = el.getAttribute("data-ohw-href-key");
6173
+ if (key) {
6174
+ const stored = getStoredLinkHref(key);
6175
+ if (stored !== void 0) return stored;
6176
+ }
6177
+ return el.getAttribute("href") ?? "";
6178
+ }
6179
+ function isNavbarButton(el) {
6180
+ return el.hasAttribute("data-ohw-role") && el.getAttribute("data-ohw-role") === "navbar-button";
6181
+ }
6182
+ function isNavbarLinkItem(el) {
6183
+ if (!el.matches("[data-ohw-href-key]")) return false;
6184
+ if (isNavbarButton(el)) return false;
6185
+ if (!el.querySelector('[data-ohw-editable="text"]')) return false;
6186
+ return Boolean(el.closest("nav, [data-ohw-nav-container], [data-ohw-nav-drawer]"));
6187
+ }
6188
+ function getNavbarDesktopContainer() {
6189
+ return document.querySelector("[data-ohw-nav-container]");
6190
+ }
6191
+ function getNavbarDrawerContainer() {
6192
+ return document.querySelector("[data-ohw-nav-drawer]");
6193
+ }
6194
+ function listNavbarItems() {
6195
+ const desktop = getNavbarDesktopContainer();
6196
+ if (desktop) {
6197
+ return Array.from(desktop.querySelectorAll("[data-ohw-href-key]")).filter(isNavbarLinkItem);
6198
+ }
6199
+ const drawer = getNavbarDrawerContainer();
6200
+ if (drawer) {
6201
+ return Array.from(drawer.querySelectorAll("[data-ohw-href-key]")).filter(isNavbarLinkItem);
6202
+ }
6203
+ return Array.from(document.querySelectorAll("nav [data-ohw-href-key]")).filter(isNavbarLinkItem);
6204
+ }
6205
+ function parseNavIndexFromKey(key) {
6206
+ if (!key) return null;
6207
+ const match = key.match(/^nav-(\d+)-href$/);
6208
+ if (!match) return null;
6209
+ return parseInt(match[1], 10);
6210
+ }
6211
+ function getNavbarIndicesInDom() {
6212
+ const indices = /* @__PURE__ */ new Set();
6213
+ for (const item of listNavbarItems()) {
6214
+ const idx = parseNavIndexFromKey(item.getAttribute("data-ohw-href-key"));
6215
+ if (idx !== null) indices.add(idx);
6216
+ }
6217
+ return [...indices].sort((a, b) => a - b);
6218
+ }
6219
+ function getNextNavbarIndex() {
6220
+ const indices = getNavbarIndicesInDom();
6221
+ if (indices.length === 0) return 0;
6222
+ return Math.max(...indices) + 1;
6223
+ }
6224
+ function getNavbarExistingTargets() {
6225
+ return listNavbarItems().map((el) => getLinkHref(el)).filter(Boolean);
6226
+ }
6227
+ function getNavOrderFromDom() {
6228
+ return listNavbarItems().map((el) => el.getAttribute("data-ohw-href-key")).filter((key) => Boolean(key));
6229
+ }
6230
+ function parseNavOrder(content) {
6231
+ const raw = content[NAV_ORDER_KEY];
6232
+ if (!raw) return null;
6233
+ try {
6234
+ const parsed = JSON.parse(raw);
6235
+ if (!Array.isArray(parsed)) return null;
6236
+ return parsed.filter((key) => typeof key === "string" && key.length > 0);
6237
+ } catch {
6238
+ return null;
6239
+ }
6240
+ }
6241
+ function findCounterpartByHrefKey(hrefKey, root) {
6242
+ return root.querySelector(`[data-ohw-href-key="${CSS.escape(hrefKey)}"]`);
6243
+ }
6244
+ function cloneNavLinkShell(template) {
6245
+ const anchor = document.createElement("a");
6246
+ if (template) {
6247
+ anchor.style.cssText = template.style.cssText;
6248
+ if (template.className) anchor.className = template.className;
6249
+ }
6250
+ anchor.style.cursor = "pointer";
6251
+ anchor.style.textDecoration = "none";
6252
+ return anchor;
6253
+ }
6254
+ function buildNavLink(index, href, label, template) {
6255
+ const anchor = cloneNavLinkShell(template);
6256
+ anchor.href = href;
6257
+ anchor.setAttribute("data-ohw-href-key", `nav-${index}-href`);
6258
+ const span = document.createElement("span");
6259
+ span.setAttribute("data-ohw-editable", "text");
6260
+ span.setAttribute("data-ohw-key", `nav-${index}-label`);
6261
+ span.textContent = label;
6262
+ anchor.appendChild(span);
6263
+ return anchor;
6264
+ }
6265
+ function insertNavLinkInContainer(container, anchor, afterHrefKey) {
6266
+ if (!afterHrefKey) {
6267
+ container.appendChild(anchor);
6268
+ return;
6269
+ }
6270
+ const afterEl = findCounterpartByHrefKey(afterHrefKey, container);
6271
+ if (afterEl?.parentElement === container) {
6272
+ afterEl.insertAdjacentElement("afterend", anchor);
6273
+ return;
6274
+ }
6275
+ container.appendChild(anchor);
6276
+ }
6277
+ function insertNavbarItemDom(index, href, label, afterAnchor) {
6278
+ const afterHrefKey = afterAnchor?.getAttribute("data-ohw-href-key") ?? null;
6279
+ const desktopItems = getNavbarDesktopContainer()?.querySelectorAll("[data-ohw-href-key]") ?? [];
6280
+ const drawerItems = getNavbarDrawerContainer()?.querySelectorAll("[data-ohw-href-key]") ?? [];
6281
+ const desktopTemplate = Array.from(desktopItems).find(isNavbarLinkItem) ?? null;
6282
+ const drawerTemplate = Array.from(drawerItems).find(isNavbarLinkItem) ?? null;
6283
+ let primary = null;
6284
+ const desktopContainer = getNavbarDesktopContainer();
6285
+ if (desktopContainer) {
6286
+ const desktopAnchor = buildNavLink(index, href, label, desktopTemplate);
6287
+ insertNavLinkInContainer(desktopContainer, desktopAnchor, afterHrefKey);
6288
+ primary = desktopAnchor;
6289
+ }
6290
+ const drawerContainer = getNavbarDrawerContainer();
6291
+ if (drawerContainer) {
6292
+ const drawerAnchor = buildNavLink(index, href, label, drawerTemplate);
6293
+ insertNavLinkInContainer(drawerContainer, drawerAnchor, afterHrefKey);
6294
+ if (!primary) primary = drawerAnchor;
6295
+ }
6296
+ if (!primary) {
6297
+ const fallback = buildNavLink(index, href, label, listNavbarItems()[0] ?? null);
6298
+ const nav = document.querySelector("nav");
6299
+ nav?.appendChild(fallback);
6300
+ primary = fallback;
6301
+ }
6302
+ return primary;
6303
+ }
6304
+ function applyNavOrderToContainer(container, order) {
6305
+ const seen = /* @__PURE__ */ new Set();
6306
+ for (const hrefKey of order) {
6307
+ if (seen.has(hrefKey)) continue;
6308
+ seen.add(hrefKey);
6309
+ const el = findCounterpartByHrefKey(hrefKey, container);
6310
+ if (el) container.appendChild(el);
6311
+ }
6312
+ for (const el of container.querySelectorAll("[data-ohw-href-key]")) {
6313
+ if (!isNavbarLinkItem(el)) continue;
6314
+ const key = el.getAttribute("data-ohw-href-key");
6315
+ if (!key || seen.has(key)) continue;
6316
+ container.appendChild(el);
6317
+ }
6318
+ }
6319
+ function applyNavOrder(order) {
6320
+ const desktop = getNavbarDesktopContainer();
6321
+ if (desktop) applyNavOrderToContainer(desktop, order);
6322
+ const drawer = getNavbarDrawerContainer();
6323
+ if (drawer) applyNavOrderToContainer(drawer, order);
6324
+ }
6325
+ function collectNavbarIndicesFromContent(content) {
6326
+ const indices = /* @__PURE__ */ new Set();
6327
+ for (const key of Object.keys(content)) {
6328
+ const idx = parseNavIndexFromKey(key);
6329
+ if (idx !== null) indices.add(idx);
6330
+ }
6331
+ const countRaw = content[NAV_COUNT_KEY];
6332
+ if (countRaw) {
6333
+ const count = parseInt(countRaw, 10);
6334
+ if (Number.isFinite(count) && count > 0) {
6335
+ for (let i = 0; i < count; i++) indices.add(i);
6336
+ }
6337
+ }
6338
+ return [...indices].sort((a, b) => a - b);
6339
+ }
6340
+ function navbarIndexExistsInDom(index) {
6341
+ return document.querySelector(`[data-ohw-href-key="nav-${index}-href"]`) !== null;
6342
+ }
6343
+ function reconcileNavbarItemsFromContent(content) {
6344
+ const indices = collectNavbarIndicesFromContent(content);
6345
+ for (const index of indices) {
6346
+ if (navbarIndexExistsInDom(index)) continue;
6347
+ const href = content[`nav-${index}-href`];
6348
+ const label = content[`nav-${index}-label`];
6349
+ if (!href && !label) continue;
6350
+ insertNavbarItemDom(index, href ?? "/", label ?? "Untitled", null);
6351
+ }
6352
+ const order = parseNavOrder(content);
6353
+ if (order?.length) applyNavOrder(order);
6354
+ }
6355
+ function buildNavOrderAfterInsert(afterAnchor, newHrefKey) {
6356
+ const order = getNavOrderFromDom();
6357
+ if (!afterAnchor) {
6358
+ if (!order.includes(newHrefKey)) order.push(newHrefKey);
6359
+ return order;
6360
+ }
6361
+ const afterKey = afterAnchor.getAttribute("data-ohw-href-key");
6362
+ if (!afterKey) {
6363
+ if (!order.includes(newHrefKey)) order.push(newHrefKey);
6364
+ return order;
6365
+ }
6366
+ const withoutNew = order.filter((key) => key !== newHrefKey);
6367
+ const pos = withoutNew.indexOf(afterKey);
6368
+ if (pos >= 0) {
6369
+ withoutNew.splice(pos + 1, 0, newHrefKey);
6370
+ return withoutNew;
6371
+ }
6372
+ withoutNew.push(newHrefKey);
6373
+ return withoutNew;
6374
+ }
6375
+ function insertNavbarItem(href, label, afterAnchor = null) {
6376
+ const index = getNextNavbarIndex();
6377
+ const anchor = insertNavbarItemDom(index, href, label, afterAnchor);
6378
+ if (!anchor) throw new Error("Failed to insert navbar item");
6379
+ const hrefKey = `nav-${index}-href`;
6380
+ const order = buildNavOrderAfterInsert(afterAnchor, hrefKey);
6381
+ applyNavOrder(order);
6382
+ return {
6383
+ anchor,
6384
+ index,
6385
+ hrefKey,
6386
+ labelKey: `nav-${index}-label`,
6387
+ href,
6388
+ label,
6389
+ order
6390
+ };
6391
+ }
6392
+
6393
+ // src/ui/navbar-container-chrome.tsx
6394
+ import { Plus as Plus2 } from "lucide-react";
6395
+ import { jsx as jsx21 } from "react/jsx-runtime";
6396
+ function NavbarContainerChrome({
6397
+ rect,
6398
+ onAdd
5515
6399
  }) {
5516
- return /* @__PURE__ */ jsx19(
5517
- Dialog2,
6400
+ const chromeGap = 6;
6401
+ return /* @__PURE__ */ jsx21(
6402
+ "div",
5518
6403
  {
5519
- open,
5520
- onOpenChange: (next) => {
5521
- if (!next) onClose?.();
6404
+ "data-ohw-navbar-container-chrome": "",
6405
+ "data-ohw-bridge": "",
6406
+ className: "pointer-events-none fixed z-[2147483647]",
6407
+ style: {
6408
+ top: rect.top - chromeGap,
6409
+ left: rect.left - chromeGap,
6410
+ width: rect.width + chromeGap * 2,
6411
+ height: rect.height + chromeGap * 2
5522
6412
  },
5523
- children: /* @__PURE__ */ jsx19(
5524
- DialogContent,
6413
+ children: /* @__PURE__ */ jsx21(
6414
+ "button",
5525
6415
  {
5526
- ref: panelRef,
5527
- container: portalContainer,
5528
- "data-ohw-link-popover-root": "",
5529
- "data-ohw-link-modal-root": "",
5530
- "data-ohw-bridge": "",
5531
- showCloseButton: false,
5532
- className: "gap-0 p-0 w-full md:w-md pointer-events-auto",
5533
- children: /* @__PURE__ */ jsx19(LinkEditorPanel, { ...editorProps, open, onClose })
6416
+ type: "button",
6417
+ "data-ohw-navbar-add-button": "",
6418
+ className: "pointer-events-auto absolute left-1/2 flex p-0.5 rounded-[10px] size-7 -translate-x-1/2 translate-y-1/2 items-center justify-center border border-border bg-background shadow-sm transition-colors hover:bg-muted/80",
6419
+ style: { top: "70%" },
6420
+ "aria-label": "Add item",
6421
+ onMouseDown: (e) => {
6422
+ e.preventDefault();
6423
+ e.stopPropagation();
6424
+ },
6425
+ onClick: (e) => {
6426
+ e.preventDefault();
6427
+ e.stopPropagation();
6428
+ onAdd();
6429
+ },
6430
+ children: /* @__PURE__ */ jsx21(Plus2, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
5534
6431
  }
5535
6432
  )
5536
6433
  }
5537
6434
  );
5538
6435
  }
5539
6436
 
5540
- // src/ui/link-modal/devFixtures.ts
5541
- var DEV_SITE_PAGES = [
5542
- { path: "/", title: "Home" },
5543
- { path: "/about", title: "About" },
5544
- { path: "/classes", title: "Classes" },
5545
- { path: "/pricing", title: "Pricing" },
5546
- { path: "/studio", title: "Studio" },
5547
- { path: "/instructors", title: "Instructors" },
5548
- { path: "/contact", title: "Contact" }
5549
- ];
5550
- var DEV_SECTIONS_BY_PATH = {
5551
- "/": [
5552
- { id: "hero", label: "Hero" },
5553
- { id: "lagree-intro", label: "Lagree Intro" },
5554
- { id: "classes-strip", label: "Classes" },
5555
- { id: "feel-split", label: "Feel Split" },
5556
- { id: "founder-teaser", label: "Founder" },
5557
- { id: "plan-form", label: "Plan Form" },
5558
- { id: "testimonials", label: "Testimonials" },
5559
- { id: "wordmark", label: "Wordmark" }
5560
- ],
5561
- "/about": [
5562
- { id: "manifesto", label: "Manifesto" },
5563
- { id: "story-letter", label: "Our Story" },
5564
- { id: "lagree-explainer", label: "Lagree Explainer" },
5565
- { id: "waitlist-cta", label: "Waitlist" },
5566
- { id: "personal-training", label: "Personal training" }
5567
- ],
5568
- "/classes": [{ id: "class-library", label: "Class Library" }],
5569
- "/pricing": [
5570
- { id: "page-header", label: "Page Header" },
5571
- { id: "free-class-cta", label: "Free Class" },
5572
- { id: "benefits-marquee", label: "Benefits" },
5573
- { id: "monthly-memberships", label: "Memberships" },
5574
- { id: "specialty-programs", label: "Specialty Programs" },
5575
- { id: "package-matcher", label: "Packages" },
5576
- { id: "wordmark", label: "Wordmark" }
5577
- ],
5578
- "/studio": [
5579
- { id: "studio-intro", label: "Studio Intro" },
5580
- { id: "studio-features", label: "Studio Features" },
5581
- { id: "studio-gallery", label: "Studio Gallery" },
5582
- { id: "studio-visit", label: "Visit Studio" }
5583
- ],
5584
- "/instructors": [{ id: "instructor-grid", label: "Instructors" }],
5585
- "/contact": [
5586
- { id: "hello-hero", label: "Hello" },
5587
- { id: "contact-form", label: "Contact Form" },
5588
- { id: "faq", label: "FAQ" }
5589
- ]
5590
- };
5591
- function shouldUseDevFixtures() {
5592
- if (typeof window === "undefined") return false;
5593
- const raw = readPreservedSearch();
5594
- const q = raw.startsWith("?") ? raw.slice(1) : raw;
5595
- return new URLSearchParams(q).get("ohw-fixtures") === "1";
5596
- }
5597
-
5598
6437
  // src/ui/badge.tsx
5599
- import { jsx as jsx20 } from "react/jsx-runtime";
6438
+ import { jsx as jsx22 } from "react/jsx-runtime";
5600
6439
  var badgeVariants = cva(
5601
6440
  "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
5602
6441
  {
@@ -5614,12 +6453,12 @@ var badgeVariants = cva(
5614
6453
  }
5615
6454
  );
5616
6455
  function Badge({ className, variant, ...props }) {
5617
- return /* @__PURE__ */ jsx20("div", { className: cn(badgeVariants({ variant }), className), ...props });
6456
+ return /* @__PURE__ */ jsx22("div", { className: cn(badgeVariants({ variant }), className), ...props });
5618
6457
  }
5619
6458
 
5620
6459
  // src/OhhwellsBridge.tsx
5621
6460
  import { Link as Link2 } from "lucide-react";
5622
- import { Fragment as Fragment3, jsx as jsx21, jsxs as jsxs11 } from "react/jsx-runtime";
6461
+ import { Fragment as Fragment5, jsx as jsx23, jsxs as jsxs13 } from "react/jsx-runtime";
5623
6462
  var PRIMARY2 = "#0885FE";
5624
6463
  var IMAGE_FADE_MS = 300;
5625
6464
  function runOpacityFade(el, onDone) {
@@ -5793,7 +6632,7 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
5793
6632
  const root = createRoot(container);
5794
6633
  flushSync(() => {
5795
6634
  root.render(
5796
- /* @__PURE__ */ jsx21(
6635
+ /* @__PURE__ */ jsx23(
5797
6636
  SchedulingWidget,
5798
6637
  {
5799
6638
  notifyOnConnect,
@@ -5833,7 +6672,7 @@ function mountSchedulingEntries(entries, notifyOnConnect = false) {
5833
6672
  }
5834
6673
  }
5835
6674
  }
5836
- function getLinkHref(el) {
6675
+ function getLinkHref2(el) {
5837
6676
  const anchor = el instanceof HTMLAnchorElement ? el : el.querySelector("a");
5838
6677
  return anchor?.getAttribute("href") ?? "";
5839
6678
  }
@@ -5870,7 +6709,7 @@ function collectEditableNodes() {
5870
6709
  return { key: el.dataset.ohwKey ?? "", type: "video", text: getVideoEl(el)?.src ?? "" };
5871
6710
  }
5872
6711
  if (el.dataset.ohwEditable === "link") {
5873
- return { key: el.dataset.ohwKey ?? "", type: "link", text: getLinkHref(el) };
6712
+ return { key: el.dataset.ohwKey ?? "", type: "link", text: getLinkHref2(el) };
5874
6713
  }
5875
6714
  return {
5876
6715
  key: el.dataset.ohwKey ?? "",
@@ -5881,7 +6720,7 @@ function collectEditableNodes() {
5881
6720
  document.querySelectorAll("[data-ohw-href-key]").forEach((el) => {
5882
6721
  const key = el.getAttribute("data-ohw-href-key") ?? "";
5883
6722
  if (!key) return;
5884
- nodes.push({ key, type: "link", text: getLinkHref(el) });
6723
+ nodes.push({ key, type: "link", text: getLinkHref2(el) });
5885
6724
  });
5886
6725
  document.querySelectorAll('[data-ohw-editable="video"]').forEach((el) => {
5887
6726
  const key = el.dataset.ohwKey ?? "";
@@ -5951,7 +6790,7 @@ function applyLinkByKey(key, val) {
5951
6790
  }
5952
6791
  function isInsideLinkEditor(target) {
5953
6792
  return Boolean(
5954
- 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"]')
6793
+ 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"]')
5955
6794
  );
5956
6795
  }
5957
6796
  function getHrefKeyFromElement(el) {
@@ -5962,7 +6801,7 @@ function getHrefKeyFromElement(el) {
5962
6801
  if (!key) return null;
5963
6802
  return { anchor, key };
5964
6803
  }
5965
- function isNavbarButton(el) {
6804
+ function isNavbarButton2(el) {
5966
6805
  return Boolean(el.closest('[data-ohw-role="navbar-button"]'));
5967
6806
  }
5968
6807
  function getNavigationItemAnchor(el) {
@@ -5974,19 +6813,8 @@ function getNavigationItemAnchor(el) {
5974
6813
  function isNavigationItem(el) {
5975
6814
  return getNavigationItemAnchor(el) !== null;
5976
6815
  }
5977
- function getNavigationItemAddHintTarget(anchor) {
5978
- const items = listNavigationItems();
5979
- const idx = items.indexOf(anchor);
5980
- if (idx >= 0 && idx < items.length - 1) return items[idx + 1];
5981
- return anchor;
5982
- }
5983
- function listNavigationItems() {
5984
- return Array.from(
5985
- document.querySelectorAll("nav [data-ohw-href-key], footer [data-ohw-href-key]")
5986
- ).filter((el) => isNavigationItem(el));
5987
- }
5988
6816
  function getNavigationItemReorderState(anchor) {
5989
- if (isNavbarButton(anchor)) return { key: null, disabled: false };
6817
+ if (isNavbarButton2(anchor)) return { key: null, disabled: false };
5990
6818
  return getReorderHandleStateFromAnchor(anchor);
5991
6819
  }
5992
6820
  function getReorderHandleState(el) {
@@ -6037,6 +6865,39 @@ function isInferredFooterGroup(el) {
6037
6865
  function isNavigationContainer(el) {
6038
6866
  return el.hasAttribute("data-ohw-nav-container") || isNavigationRoot(el) || isInferredFooterGroup(el);
6039
6867
  }
6868
+ function isPointOverNavigation(x, y) {
6869
+ const navRoot = document.querySelector("[data-ohw-nav-root]") ?? document.querySelector("nav");
6870
+ if (!navRoot) return false;
6871
+ const r2 = navRoot.getBoundingClientRect();
6872
+ return x >= r2.left && x <= r2.right && y >= r2.top && y <= r2.bottom;
6873
+ }
6874
+ function isPointOverNavItem(container, x, y) {
6875
+ return Array.from(container.querySelectorAll("[data-ohw-href-key]")).some((el) => {
6876
+ if (!isNavigationItem(el) || isNavbarButton2(el)) return false;
6877
+ const itemRect = el.getBoundingClientRect();
6878
+ return x >= itemRect.left && x <= itemRect.right && y >= itemRect.top && y <= itemRect.bottom;
6879
+ });
6880
+ }
6881
+ function resolveNavContainerSelectionTarget(target, clientX, clientY) {
6882
+ if (getNavigationItemAnchor(target)) return null;
6883
+ if (target.closest('[data-ohw-role="navbar-button"]')) return null;
6884
+ const plainLink = target.closest("a");
6885
+ if (plainLink && !plainLink.hasAttribute("data-ohw-href-key") && !plainLink.closest("[data-ohw-nav-container]")) {
6886
+ return null;
6887
+ }
6888
+ const fromClosest = target.closest("[data-ohw-nav-container]");
6889
+ if (fromClosest && !isPointOverNavItem(fromClosest, clientX, clientY)) {
6890
+ return fromClosest;
6891
+ }
6892
+ const navRoot = target.closest("[data-ohw-nav-root]");
6893
+ if (!navRoot) return null;
6894
+ const container = navRoot.querySelector("[data-ohw-nav-container]");
6895
+ if (!container || isPointOverNavItem(container, clientX, clientY)) return null;
6896
+ if (target === navRoot || target.hasAttribute("data-ohw-nav-root")) {
6897
+ return container;
6898
+ }
6899
+ return null;
6900
+ }
6040
6901
  function getNavigationSelectionParent(el) {
6041
6902
  if (isNavigationItem(el)) {
6042
6903
  const explicit = el.closest("[data-ohw-nav-container]");
@@ -6238,7 +7099,7 @@ function EditGlowChrome({
6238
7099
  dragDisabled = false
6239
7100
  }) {
6240
7101
  const GAP = 6;
6241
- return /* @__PURE__ */ jsxs11(
7102
+ return /* @__PURE__ */ jsxs13(
6242
7103
  "div",
6243
7104
  {
6244
7105
  ref: elRef,
@@ -6253,7 +7114,7 @@ function EditGlowChrome({
6253
7114
  zIndex: 2147483646
6254
7115
  },
6255
7116
  children: [
6256
- /* @__PURE__ */ jsx21(
7117
+ /* @__PURE__ */ jsx23(
6257
7118
  "div",
6258
7119
  {
6259
7120
  style: {
@@ -6266,7 +7127,7 @@ function EditGlowChrome({
6266
7127
  }
6267
7128
  }
6268
7129
  ),
6269
- reorderHrefKey && /* @__PURE__ */ jsx21(
7130
+ reorderHrefKey && /* @__PURE__ */ jsx23(
6270
7131
  "div",
6271
7132
  {
6272
7133
  "data-ohw-drag-handle-container": "",
@@ -6278,7 +7139,7 @@ function EditGlowChrome({
6278
7139
  transform: "translate(calc(-100% - 7px), -50%)",
6279
7140
  pointerEvents: dragDisabled ? "none" : "auto"
6280
7141
  },
6281
- children: /* @__PURE__ */ jsx21(
7142
+ children: /* @__PURE__ */ jsx23(
6282
7143
  DragHandle,
6283
7144
  {
6284
7145
  "aria-label": `Reorder ${reorderHrefKey}`,
@@ -6382,7 +7243,7 @@ function FloatingToolbar({
6382
7243
  onEditLink
6383
7244
  }) {
6384
7245
  const { top, left, transform } = calcToolbarPos(rect, parentScroll, 330);
6385
- return /* @__PURE__ */ jsx21(
7246
+ return /* @__PURE__ */ jsx23(
6386
7247
  "div",
6387
7248
  {
6388
7249
  ref: elRef,
@@ -6403,12 +7264,12 @@ function FloatingToolbar({
6403
7264
  fontFamily: "sans-serif",
6404
7265
  pointerEvents: "auto"
6405
7266
  },
6406
- children: /* @__PURE__ */ jsxs11(CustomToolbar, { children: [
6407
- TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ jsxs11(React8.Fragment, { children: [
6408
- gi > 0 && /* @__PURE__ */ jsx21(CustomToolbarDivider, {}),
7267
+ children: /* @__PURE__ */ jsxs13(CustomToolbar, { children: [
7268
+ TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ jsxs13(React9.Fragment, { children: [
7269
+ gi > 0 && /* @__PURE__ */ jsx23(CustomToolbarDivider, {}),
6409
7270
  btns.map((btn) => {
6410
7271
  const isActive = activeCommands.has(btn.cmd);
6411
- return /* @__PURE__ */ jsx21(
7272
+ return /* @__PURE__ */ jsx23(
6412
7273
  CustomToolbarButton,
6413
7274
  {
6414
7275
  title: btn.title,
@@ -6417,7 +7278,7 @@ function FloatingToolbar({
6417
7278
  e.preventDefault();
6418
7279
  onCommand(btn.cmd);
6419
7280
  },
6420
- children: /* @__PURE__ */ jsx21(
7281
+ children: /* @__PURE__ */ jsx23(
6421
7282
  "svg",
6422
7283
  {
6423
7284
  width: "16",
@@ -6438,7 +7299,7 @@ function FloatingToolbar({
6438
7299
  );
6439
7300
  })
6440
7301
  ] }, gi)),
6441
- showEditLink ? /* @__PURE__ */ jsx21(
7302
+ showEditLink ? /* @__PURE__ */ jsx23(
6442
7303
  CustomToolbarButton,
6443
7304
  {
6444
7305
  type: "button",
@@ -6452,7 +7313,7 @@ function FloatingToolbar({
6452
7313
  e.preventDefault();
6453
7314
  e.stopPropagation();
6454
7315
  },
6455
- children: /* @__PURE__ */ jsx21(Link2, { className: "size-4 shrink-0", "aria-hidden": true })
7316
+ children: /* @__PURE__ */ jsx23(Link2, { className: "size-4 shrink-0", "aria-hidden": true })
6456
7317
  }
6457
7318
  ) : null
6458
7319
  ] })
@@ -6469,7 +7330,7 @@ function StateToggle({
6469
7330
  states,
6470
7331
  onStateChange
6471
7332
  }) {
6472
- return /* @__PURE__ */ jsx21(
7333
+ return /* @__PURE__ */ jsx23(
6473
7334
  ToggleGroup,
6474
7335
  {
6475
7336
  "data-ohw-state-toggle": "",
@@ -6483,7 +7344,7 @@ function StateToggle({
6483
7344
  left: rect.right - 8,
6484
7345
  transform: "translateX(-100%)"
6485
7346
  },
6486
- children: states.map((state) => /* @__PURE__ */ jsx21(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
7347
+ children: states.map((state) => /* @__PURE__ */ jsx23(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
6487
7348
  }
6488
7349
  );
6489
7350
  }
@@ -6506,12 +7367,12 @@ function resolveSubdomain(subdomainFromQuery) {
6506
7367
  return "";
6507
7368
  }
6508
7369
  function OhhwellsBridge() {
6509
- const pathname = usePathname();
6510
- const router = useRouter();
7370
+ const pathname = usePathname2();
7371
+ const router = useRouter2();
6511
7372
  const searchParams = useSearchParams();
6512
7373
  const isEditMode = isEditSessionActive();
6513
- const [bridgeRoot, setBridgeRoot] = useState5(null);
6514
- useEffect3(() => {
7374
+ const [bridgeRoot, setBridgeRoot] = useState6(null);
7375
+ useEffect7(() => {
6515
7376
  const figtreeFontId = "ohw-figtree-font";
6516
7377
  if (!document.getElementById(figtreeFontId)) {
6517
7378
  const preconnect1 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.googleapis.com" });
@@ -6519,7 +7380,10 @@ function OhhwellsBridge() {
6519
7380
  const stylesheet = Object.assign(document.createElement("link"), {
6520
7381
  id: figtreeFontId,
6521
7382
  rel: "stylesheet",
6522
- href: "https://fonts.googleapis.com/css2?family=Figtree:ital,wght@0,300..900;1,300..900&display=swap"
7383
+ // Inter is loaded alongside Figtree for the media overlay's Replace button. The bridge
7384
+ // renders inside the customer's document, so it cannot assume any font is present — an
7385
+ // unloaded family would silently fall back to whatever the template happens to use.
7386
+ href: "https://fonts.googleapis.com/css2?family=Figtree:ital,wght@0,300..900;1,300..900&family=Inter:wght@400;500;600&display=swap"
6523
7387
  });
6524
7388
  document.head.append(preconnect1, preconnect2, stylesheet);
6525
7389
  }
@@ -6536,82 +7400,90 @@ function OhhwellsBridge() {
6536
7400
  const subdomainFromQuery = searchParams.get("subdomain");
6537
7401
  const subdomain = resolveSubdomain(subdomainFromQuery);
6538
7402
  useLinkHrefGuardian(pathname, subdomain, isEditMode);
6539
- const postToParent = useCallback3((data) => {
7403
+ const postToParent2 = useCallback4((data) => {
6540
7404
  if (typeof window !== "undefined" && window.parent !== window) {
6541
7405
  window.parent.postMessage(data, "*");
6542
7406
  }
6543
7407
  }, []);
6544
- const [fetchState, setFetchState] = useState5("idle");
6545
- const autoSaveTimers = useRef3(/* @__PURE__ */ new Map());
6546
- const activeElRef = useRef3(null);
6547
- const selectedElRef = useRef3(null);
6548
- const originalContentRef = useRef3(null);
6549
- const activeStateElRef = useRef3(null);
6550
- const parentScrollRef = useRef3(null);
6551
- const visibleViewportRef = useRef3(null);
6552
- const [dialogPortalContainer, setDialogPortalContainer] = useState5(null);
6553
- const attachVisibleViewport = useCallback3((node) => {
7408
+ const [fetchState, setFetchState] = useState6("idle");
7409
+ const autoSaveTimers = useRef4(/* @__PURE__ */ new Map());
7410
+ const activeElRef = useRef4(null);
7411
+ const selectedElRef = useRef4(null);
7412
+ const originalContentRef = useRef4(null);
7413
+ const activeStateElRef = useRef4(null);
7414
+ const parentScrollRef = useRef4(null);
7415
+ const visibleViewportRef = useRef4(null);
7416
+ const [dialogPortalContainer, setDialogPortalContainer] = useState6(null);
7417
+ const attachVisibleViewport = useCallback4((node) => {
6554
7418
  visibleViewportRef.current = node;
6555
7419
  setDialogPortalContainer(node);
6556
7420
  if (node) applyVisibleViewport(node, parentScrollRef.current);
6557
7421
  }, []);
6558
- const toolbarElRef = useRef3(null);
6559
- const glowElRef = useRef3(null);
6560
- const hoveredImageRef = useRef3(null);
6561
- const hoveredImageHasTextOverlapRef = useRef3(false);
6562
- const hoveredGapRef = useRef3(null);
6563
- const imageUnhoverTimerRef = useRef3(null);
6564
- const imageShowTimerRef = useRef3(null);
6565
- const editStylesRef = useRef3(null);
6566
- const activateRef = useRef3(() => {
7422
+ const toolbarElRef = useRef4(null);
7423
+ const glowElRef = useRef4(null);
7424
+ const hoveredImageRef = useRef4(null);
7425
+ const hoveredImageHasTextOverlapRef = useRef4(false);
7426
+ const [mediaHover, setMediaHover] = useState6(null);
7427
+ const [uploadingRects, setUploadingRects] = useState6({});
7428
+ const hoveredGapRef = useRef4(null);
7429
+ const imageUnhoverTimerRef = useRef4(null);
7430
+ const imageShowTimerRef = useRef4(null);
7431
+ const editStylesRef = useRef4(null);
7432
+ const activateRef = useRef4(() => {
6567
7433
  });
6568
- const deactivateRef = useRef3(() => {
7434
+ const deactivateRef = useRef4(() => {
6569
7435
  });
6570
- const selectRef = useRef3(() => {
7436
+ const selectRef = useRef4(() => {
6571
7437
  });
6572
- const selectFrameRef = useRef3(() => {
7438
+ const selectFrameRef = useRef4(() => {
6573
7439
  });
6574
- const deselectRef = useRef3(() => {
7440
+ const deselectRef = useRef4(() => {
6575
7441
  });
6576
- const reselectNavigationItemRef = useRef3(() => {
7442
+ const reselectNavigationItemRef = useRef4(() => {
6577
7443
  });
6578
- const commitNavigationTextEditRef = useRef3(() => {
7444
+ const commitNavigationTextEditRef = useRef4(() => {
6579
7445
  });
6580
- const refreshActiveCommandsRef = useRef3(() => {
7446
+ const refreshActiveCommandsRef = useRef4(() => {
6581
7447
  });
6582
- const postToParentRef = useRef3(postToParent);
6583
- postToParentRef.current = postToParent;
6584
- const sectionsLoadedRef = useRef3(false);
6585
- const pendingScheduleConfigRequests = useRef3([]);
6586
- const [toolbarRect, setToolbarRect] = useState5(null);
6587
- const [toolbarVariant, setToolbarVariant] = useState5("none");
6588
- const toolbarVariantRef = useRef3("none");
7448
+ const postToParentRef = useRef4(postToParent2);
7449
+ postToParentRef.current = postToParent2;
7450
+ const sectionsLoadedRef = useRef4(false);
7451
+ const pendingScheduleConfigRequests = useRef4([]);
7452
+ const [toolbarRect, setToolbarRect] = useState6(null);
7453
+ const [toolbarVariant, setToolbarVariant] = useState6("none");
7454
+ const toolbarVariantRef = useRef4("none");
6589
7455
  toolbarVariantRef.current = toolbarVariant;
6590
- const [reorderHrefKey, setReorderHrefKey] = useState5(null);
6591
- const [reorderDragDisabled, setReorderDragDisabled] = useState5(false);
6592
- const [toggleState, setToggleState] = useState5(null);
6593
- const [maxBadge, setMaxBadge] = useState5(null);
6594
- const [activeCommands, setActiveCommands] = useState5(/* @__PURE__ */ new Set());
6595
- const [sectionGap, setSectionGap] = useState5(null);
6596
- const [toolbarShowEditLink, setToolbarShowEditLink] = useState5(false);
6597
- const hoveredItemElRef = useRef3(null);
6598
- const [hoveredItemRect, setHoveredItemRect] = useState5(null);
6599
- const siblingHintElRef = useRef3(null);
6600
- const [siblingHintRect, setSiblingHintRect] = useState5(null);
6601
- const [isItemDragging, setIsItemDragging] = useState5(false);
6602
- const [linkPopover, setLinkPopover] = useState5(null);
6603
- const [sitePages, setSitePages] = useState5([]);
6604
- const [sectionsByPath, setSectionsByPath] = useState5({});
6605
- const sectionsPrefetchGenRef = useRef3(0);
6606
- const setLinkPopoverRef = useRef3(setLinkPopover);
6607
- const linkPopoverPanelRef = useRef3(null);
6608
- const linkPopoverOpenRef = useRef3(false);
6609
- const linkPopoverGraceUntilRef = useRef3(0);
7456
+ const [reorderHrefKey, setReorderHrefKey] = useState6(null);
7457
+ const [reorderDragDisabled, setReorderDragDisabled] = useState6(false);
7458
+ const [toggleState, setToggleState] = useState6(null);
7459
+ const [maxBadge, setMaxBadge] = useState6(null);
7460
+ const [activeCommands, setActiveCommands] = useState6(/* @__PURE__ */ new Set());
7461
+ const [sectionGap, setSectionGap] = useState6(null);
7462
+ const [toolbarShowEditLink, setToolbarShowEditLink] = useState6(false);
7463
+ const hoveredNavContainerRef = useRef4(null);
7464
+ const [hoveredNavContainerRect, setHoveredNavContainerRect] = useState6(null);
7465
+ const hoveredItemElRef = useRef4(null);
7466
+ const [hoveredItemRect, setHoveredItemRect] = useState6(null);
7467
+ const siblingHintElRef = useRef4(null);
7468
+ const [siblingHintRect, setSiblingHintRect] = useState6(null);
7469
+ const [isItemDragging, setIsItemDragging] = useState6(false);
7470
+ const [linkPopover, setLinkPopover] = useState6(null);
7471
+ const linkPopoverSessionRef = useRef4(null);
7472
+ const addNavAfterAnchorRef = useRef4(null);
7473
+ const editContentRef = useRef4({});
7474
+ const [sitePages, setSitePages] = useState6([]);
7475
+ const [sectionsByPath, setSectionsByPath] = useState6({});
7476
+ const sectionsPrefetchGenRef = useRef4(0);
7477
+ const setLinkPopoverRef = useRef4(setLinkPopover);
7478
+ const linkPopoverPanelRef = useRef4(null);
7479
+ const linkPopoverOpenRef = useRef4(false);
7480
+ const linkPopoverGraceUntilRef = useRef4(0);
6610
7481
  setLinkPopoverRef.current = setLinkPopover;
7482
+ linkPopoverSessionRef.current = linkPopover;
6611
7483
  const bumpLinkPopoverGrace = () => {
6612
7484
  linkPopoverGraceUntilRef.current = Date.now() + 350;
6613
7485
  };
6614
- const runSectionsPrefetch = useCallback3((pages) => {
7486
+ const runSectionsPrefetch = useCallback4((pages) => {
6615
7487
  if (!isEditMode || shouldUseDevFixtures() || pages.length === 0) return;
6616
7488
  const gen = ++sectionsPrefetchGenRef.current;
6617
7489
  const paths = pages.map((p) => p.path);
@@ -6630,9 +7502,9 @@ function OhhwellsBridge() {
6630
7502
  );
6631
7503
  });
6632
7504
  }, [isEditMode, pathname]);
6633
- const runSectionsPrefetchRef = useRef3(runSectionsPrefetch);
7505
+ const runSectionsPrefetchRef = useRef4(runSectionsPrefetch);
6634
7506
  runSectionsPrefetchRef.current = runSectionsPrefetch;
6635
- useEffect3(() => {
7507
+ useEffect7(() => {
6636
7508
  if (!linkPopover) return;
6637
7509
  if (hoveredImageRef.current) {
6638
7510
  hoveredImageRef.current = null;
@@ -6640,33 +7512,9 @@ function OhhwellsBridge() {
6640
7512
  }
6641
7513
  hoveredGapRef.current = null;
6642
7514
  setSectionGap(null);
6643
- postToParent({ type: "ow:image-unhover" });
6644
- postToParent({ type: "ow:link-modal-lock", locked: true });
6645
- const html = document.documentElement;
6646
- const body = document.body;
6647
- const prevHtmlOverflow = html.style.overflow;
6648
- const prevBodyOverflow = body.style.overflow;
6649
- html.style.overflow = "hidden";
6650
- body.style.overflow = "hidden";
6651
- const preventBackgroundScroll = (e) => {
6652
- const target = e.target;
6653
- if (target instanceof Element && target.closest('[data-ohw-link-modal-root], [data-slot="dialog-overlay"]')) {
6654
- return;
6655
- }
6656
- e.preventDefault();
6657
- };
6658
- const scrollOpts = { passive: false, capture: true };
6659
- document.addEventListener("wheel", preventBackgroundScroll, scrollOpts);
6660
- document.addEventListener("touchmove", preventBackgroundScroll, scrollOpts);
6661
- return () => {
6662
- postToParent({ type: "ow:link-modal-lock", locked: false });
6663
- html.style.overflow = prevHtmlOverflow;
6664
- body.style.overflow = prevBodyOverflow;
6665
- document.removeEventListener("wheel", preventBackgroundScroll, scrollOpts);
6666
- document.removeEventListener("touchmove", preventBackgroundScroll, scrollOpts);
6667
- };
6668
- }, [linkPopover, postToParent]);
6669
- useEffect3(() => {
7515
+ postToParent2({ type: "ow:image-unhover" });
7516
+ }, [linkPopover, postToParent2]);
7517
+ useEffect7(() => {
6670
7518
  if (!isEditMode) return;
6671
7519
  const useFixtures = shouldUseDevFixtures();
6672
7520
  if (useFixtures) {
@@ -6687,17 +7535,17 @@ function OhhwellsBridge() {
6687
7535
  runSectionsPrefetchRef.current(mapped);
6688
7536
  };
6689
7537
  window.addEventListener("message", onSitePages);
6690
- if (!useFixtures) postToParent({ type: "ow:request-site-pages" });
7538
+ if (!useFixtures) postToParent2({ type: "ow:request-site-pages" });
6691
7539
  return () => window.removeEventListener("message", onSitePages);
6692
- }, [isEditMode, postToParent]);
6693
- useEffect3(() => {
7540
+ }, [isEditMode, postToParent2]);
7541
+ useEffect7(() => {
6694
7542
  if (!isEditMode || shouldUseDevFixtures()) return;
6695
7543
  void loadAllSectionsManifest().then((manifest) => {
6696
7544
  if (Object.keys(manifest).length === 0) return;
6697
7545
  setSectionsByPath((prev) => ({ ...manifest, ...prev }));
6698
7546
  });
6699
7547
  }, [isEditMode]);
6700
- useEffect3(() => {
7548
+ useEffect7(() => {
6701
7549
  const update = () => {
6702
7550
  const el = activeElRef.current ?? selectedElRef.current;
6703
7551
  if (el) setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
@@ -6721,10 +7569,10 @@ function OhhwellsBridge() {
6721
7569
  vvp.removeEventListener("resize", update);
6722
7570
  };
6723
7571
  }, []);
6724
- const refreshStateRules = useCallback3(() => {
7572
+ const refreshStateRules = useCallback4(() => {
6725
7573
  editStylesRef.current?.forceHover && (editStylesRef.current.forceHover.textContent = collectStateRules());
6726
7574
  }, []);
6727
- const processConfigRequest = useCallback3((insertAfterVal) => {
7575
+ const processConfigRequest = useCallback4((insertAfterVal) => {
6728
7576
  const tracker = getSectionsTracker();
6729
7577
  let entries = [];
6730
7578
  try {
@@ -6747,7 +7595,7 @@ function OhhwellsBridge() {
6747
7595
  }
6748
7596
  window.postMessage({ type: "ow:schedule-config", insertAfter: insertAfterVal, scheduleId: null }, "*");
6749
7597
  }, [isEditMode]);
6750
- const deactivate = useCallback3(() => {
7598
+ const deactivate = useCallback4(() => {
6751
7599
  const el = activeElRef.current;
6752
7600
  if (!el) return;
6753
7601
  const key = el.dataset.ohwKey;
@@ -6776,21 +7624,23 @@ function OhhwellsBridge() {
6776
7624
  setMaxBadge(null);
6777
7625
  setActiveCommands(/* @__PURE__ */ new Set());
6778
7626
  setToolbarShowEditLink(false);
6779
- postToParent({ type: "ow:exit-edit" });
6780
- }, [postToParent]);
6781
- const deselect = useCallback3(() => {
7627
+ postToParent2({ type: "ow:exit-edit" });
7628
+ }, [postToParent2]);
7629
+ const deselect = useCallback4(() => {
6782
7630
  selectedElRef.current = null;
6783
7631
  setReorderHrefKey(null);
6784
7632
  setReorderDragDisabled(false);
6785
7633
  siblingHintElRef.current = null;
6786
7634
  setSiblingHintRect(null);
6787
7635
  setIsItemDragging(false);
7636
+ hoveredNavContainerRef.current = null;
7637
+ setHoveredNavContainerRect(null);
6788
7638
  if (!activeElRef.current) {
6789
7639
  setToolbarRect(null);
6790
7640
  setToolbarVariant("none");
6791
7641
  }
6792
7642
  }, []);
6793
- const reselectNavigationItem = useCallback3((navAnchor) => {
7643
+ const reselectNavigationItem = useCallback4((navAnchor) => {
6794
7644
  selectedElRef.current = navAnchor;
6795
7645
  const { key, disabled } = getNavigationItemReorderState(navAnchor);
6796
7646
  setReorderHrefKey(key);
@@ -6800,7 +7650,7 @@ function OhhwellsBridge() {
6800
7650
  setToolbarShowEditLink(false);
6801
7651
  setActiveCommands(/* @__PURE__ */ new Set());
6802
7652
  }, []);
6803
- const commitNavigationTextEdit = useCallback3((navAnchor) => {
7653
+ const commitNavigationTextEdit = useCallback4((navAnchor) => {
6804
7654
  const el = activeElRef.current;
6805
7655
  if (!el) return;
6806
7656
  const key = el.dataset.ohwKey;
@@ -6813,9 +7663,9 @@ function OhhwellsBridge() {
6813
7663
  const html = sanitizeHtml(el.innerHTML);
6814
7664
  const original = originalContentRef.current ?? "";
6815
7665
  if (html !== sanitizeHtml(original)) {
6816
- postToParent({ type: "ow:change", nodes: [{ key, text: html }] });
7666
+ postToParent2({ type: "ow:change", nodes: [{ key, text: html }] });
6817
7667
  const h = document.documentElement.scrollHeight;
6818
- if (h > 50) postToParent({ type: "ow:height", height: h });
7668
+ if (h > 50) postToParent2({ type: "ow:height", height: h });
6819
7669
  }
6820
7670
  }
6821
7671
  el.removeAttribute("contenteditable");
@@ -6823,31 +7673,38 @@ function OhhwellsBridge() {
6823
7673
  setMaxBadge(null);
6824
7674
  setActiveCommands(/* @__PURE__ */ new Set());
6825
7675
  setToolbarShowEditLink(false);
6826
- postToParent({ type: "ow:exit-edit" });
7676
+ postToParent2({ type: "ow:exit-edit" });
6827
7677
  reselectNavigationItem(navAnchor);
6828
- }, [postToParent, reselectNavigationItem]);
6829
- const handleAddNavigationItem = useCallback3(() => {
6830
- const selected = selectedElRef.current;
6831
- if (!selected) return;
6832
- const target = getNavigationItemAddHintTarget(selected);
6833
- siblingHintElRef.current = target;
6834
- setSiblingHintRect(target.getBoundingClientRect());
7678
+ }, [postToParent2, reselectNavigationItem]);
7679
+ const handleAddTopLevelNavItem = useCallback4(() => {
7680
+ const items = listNavbarItems();
7681
+ addNavAfterAnchorRef.current = items[items.length - 1] ?? null;
7682
+ deselectRef.current();
7683
+ deactivateRef.current();
7684
+ bumpLinkPopoverGrace();
7685
+ setLinkPopover({
7686
+ key: "__add-nav__",
7687
+ mode: "create",
7688
+ intent: "add-nav"
7689
+ });
6835
7690
  }, []);
6836
- const handleItemDragStart = useCallback3(() => {
7691
+ const handleItemDragStart = useCallback4(() => {
6837
7692
  siblingHintElRef.current = null;
6838
7693
  setSiblingHintRect(null);
6839
7694
  setIsItemDragging(true);
6840
7695
  }, []);
6841
- const handleItemDragEnd = useCallback3(() => {
7696
+ const handleItemDragEnd = useCallback4(() => {
6842
7697
  setIsItemDragging(false);
6843
7698
  }, []);
6844
7699
  reselectNavigationItemRef.current = reselectNavigationItem;
6845
7700
  commitNavigationTextEditRef.current = commitNavigationTextEdit;
6846
- const select = useCallback3((anchor) => {
7701
+ const select = useCallback4((anchor) => {
6847
7702
  if (!isNavigationItem(anchor)) return;
6848
7703
  if (activeElRef.current) deactivate();
6849
7704
  selectedElRef.current = anchor;
6850
7705
  clearHrefKeyHover(anchor);
7706
+ hoveredNavContainerRef.current = null;
7707
+ setHoveredNavContainerRect(null);
6851
7708
  setHoveredItemRect(null);
6852
7709
  hoveredItemElRef.current = null;
6853
7710
  siblingHintElRef.current = null;
@@ -6861,11 +7718,13 @@ function OhhwellsBridge() {
6861
7718
  setToolbarShowEditLink(false);
6862
7719
  setActiveCommands(/* @__PURE__ */ new Set());
6863
7720
  }, [deactivate]);
6864
- const selectFrame = useCallback3((el) => {
7721
+ const selectFrame = useCallback4((el) => {
6865
7722
  if (!isNavigationContainer(el)) return;
6866
7723
  if (activeElRef.current) deactivate();
6867
7724
  selectedElRef.current = el;
6868
7725
  clearHrefKeyHover(el);
7726
+ hoveredNavContainerRef.current = null;
7727
+ setHoveredNavContainerRect(null);
6869
7728
  setHoveredItemRect(null);
6870
7729
  hoveredItemElRef.current = null;
6871
7730
  siblingHintElRef.current = null;
@@ -6878,13 +7737,13 @@ function OhhwellsBridge() {
6878
7737
  setToolbarShowEditLink(false);
6879
7738
  setActiveCommands(/* @__PURE__ */ new Set());
6880
7739
  }, [deactivate]);
6881
- const activate = useCallback3((el, options) => {
7740
+ const activate = useCallback4((el, options) => {
6882
7741
  if (activeElRef.current === el) return;
6883
7742
  selectedElRef.current = null;
6884
7743
  deactivate();
6885
7744
  if (hoveredImageRef.current) {
6886
7745
  hoveredImageRef.current = null;
6887
- postToParentRef.current({ type: "ow:image-unhover" });
7746
+ setMediaHover(null);
6888
7747
  }
6889
7748
  setToolbarVariant("rich-text");
6890
7749
  siblingHintElRef.current = null;
@@ -6910,9 +7769,9 @@ function OhhwellsBridge() {
6910
7769
  setReorderDragDisabled(reorderDisabled);
6911
7770
  }
6912
7771
  setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
6913
- postToParent({ type: "ow:enter-edit", key: el.dataset.ohwKey });
7772
+ postToParent2({ type: "ow:enter-edit", key: el.dataset.ohwKey });
6914
7773
  requestAnimationFrame(() => refreshActiveCommandsRef.current());
6915
- }, [deactivate, postToParent]);
7774
+ }, [deactivate, postToParent2]);
6916
7775
  activateRef.current = activate;
6917
7776
  deactivateRef.current = deactivate;
6918
7777
  selectRef.current = select;
@@ -6986,7 +7845,7 @@ function OhhwellsBridge() {
6986
7845
  cancelled = true;
6987
7846
  };
6988
7847
  }, [subdomain, isEditMode]);
6989
- useEffect3(() => {
7848
+ useEffect7(() => {
6990
7849
  if (!subdomain || isEditMode) return;
6991
7850
  let debounceTimer = null;
6992
7851
  let observer = null;
@@ -7040,20 +7899,34 @@ function OhhwellsBridge() {
7040
7899
  const visible = Boolean(subdomain) && fetchState !== "done";
7041
7900
  el.style.display = visible ? "flex" : "none";
7042
7901
  }, [subdomain, fetchState]);
7043
- useEffect3(() => {
7044
- postToParent({ type: "ow:navigation", path: pathname });
7045
- }, [pathname, postToParent]);
7046
- useEffect3(() => {
7902
+ useEffect7(() => {
7903
+ postToParent2({ type: "ow:navigation", path: pathname });
7904
+ }, [pathname, postToParent2]);
7905
+ useEffect7(() => {
7047
7906
  if (!isEditMode) return;
7907
+ if (linkPopoverSessionRef.current?.intent === "add-nav") return;
7908
+ if (document.querySelector("[data-ohw-section-picker]")) return;
7048
7909
  setLinkPopover(null);
7049
7910
  deselectRef.current();
7050
7911
  deactivateRef.current();
7051
7912
  }, [pathname, isEditMode]);
7052
- useEffect3(() => {
7913
+ useEffect7(() => {
7914
+ if (!isEditMode) return;
7915
+ const run = () => reconcileNavbarItemsFromContent(editContentRef.current);
7916
+ run();
7917
+ const nav = document.querySelector("nav");
7918
+ if (!nav) return;
7919
+ const observer = new MutationObserver(() => {
7920
+ requestAnimationFrame(run);
7921
+ });
7922
+ observer.observe(nav, { childList: true, subtree: true });
7923
+ return () => observer.disconnect();
7924
+ }, [isEditMode, pathname]);
7925
+ useEffect7(() => {
7053
7926
  if (!isEditMode) return;
7054
7927
  const measure = () => {
7055
7928
  const h = document.body.scrollHeight;
7056
- if (h > 50) postToParent({ type: "ow:height", height: h });
7929
+ if (h > 50) postToParent2({ type: "ow:height", height: h });
7057
7930
  };
7058
7931
  const t1 = setTimeout(measure, 50);
7059
7932
  const t2 = setTimeout(measure, 500);
@@ -7072,8 +7945,8 @@ function OhhwellsBridge() {
7072
7945
  if (resizeTimer) clearTimeout(resizeTimer);
7073
7946
  window.removeEventListener("resize", handleResize);
7074
7947
  };
7075
- }, [pathname, isEditMode, postToParent]);
7076
- useEffect3(() => {
7948
+ }, [pathname, isEditMode, postToParent2]);
7949
+ useEffect7(() => {
7077
7950
  if (!subdomainFromQuery || isEditMode) return;
7078
7951
  const handleClick = (e) => {
7079
7952
  const anchor = e.target.closest("a");
@@ -7089,7 +7962,7 @@ function OhhwellsBridge() {
7089
7962
  document.addEventListener("click", handleClick, true);
7090
7963
  return () => document.removeEventListener("click", handleClick, true);
7091
7964
  }, [subdomainFromQuery, isEditMode, router]);
7092
- useEffect3(() => {
7965
+ useEffect7(() => {
7093
7966
  if (!isEditMode) {
7094
7967
  editStylesRef.current?.base.remove();
7095
7968
  editStylesRef.current?.forceHover.remove();
@@ -7163,6 +8036,13 @@ function OhhwellsBridge() {
7163
8036
  if (target.closest("[data-ohw-max-badge]")) return;
7164
8037
  if (isInsideLinkEditor(target)) return;
7165
8038
  if (target.closest('[data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
8039
+ const navFromChrome = resolveNavContainerSelectionTarget(target, e.clientX, e.clientY);
8040
+ if (navFromChrome) {
8041
+ e.preventDefault();
8042
+ e.stopPropagation();
8043
+ selectFrameRef.current(navFromChrome);
8044
+ return;
8045
+ }
7166
8046
  e.preventDefault();
7167
8047
  e.stopPropagation();
7168
8048
  return;
@@ -7177,14 +8057,15 @@ function OhhwellsBridge() {
7177
8057
  bumpLinkPopoverGrace();
7178
8058
  setLinkPopoverRef.current({
7179
8059
  key: editable.dataset.ohwKey ?? "",
7180
- target: getLinkHref(editable)
8060
+ mode: "edit",
8061
+ target: getLinkHref2(editable)
7181
8062
  });
7182
8063
  return;
7183
8064
  }
7184
8065
  if (isMediaEditable(editable)) {
7185
8066
  e.preventDefault();
7186
8067
  e.stopPropagation();
7187
- postToParentRef.current({ type: "ow:image-pick", key: editable.dataset.ohwKey ?? "" });
8068
+ postToParentRef.current({ type: "ow:image-pick", key: editable.dataset.ohwKey ?? "", elementType: editable.dataset.ohwEditable ?? "image" });
7188
8069
  return;
7189
8070
  }
7190
8071
  const hrefCtx = getHrefKeyFromElement(editable);
@@ -7213,6 +8094,13 @@ function OhhwellsBridge() {
7213
8094
  selectRef.current(hrefAnchor);
7214
8095
  return;
7215
8096
  }
8097
+ const navContainerToSelect = resolveNavContainerSelectionTarget(target, e.clientX, e.clientY);
8098
+ if (navContainerToSelect) {
8099
+ e.preventDefault();
8100
+ e.stopPropagation();
8101
+ selectFrameRef.current(navContainerToSelect);
8102
+ return;
8103
+ }
7216
8104
  const selectedContainer = selectedElRef.current;
7217
8105
  if (selectedContainer && isNavigationContainer(selectedContainer)) {
7218
8106
  const navItem = getNavigationItemAnchor(target);
@@ -7271,8 +8159,24 @@ function OhhwellsBridge() {
7271
8159
  };
7272
8160
  const handleMouseOver = (e) => {
7273
8161
  const target = e.target;
8162
+ if (toolbarVariantRef.current !== "select-frame") {
8163
+ const navContainer = target.closest("[data-ohw-nav-container]");
8164
+ if (navContainer && !getNavigationItemAnchor(target)) {
8165
+ hoveredNavContainerRef.current = navContainer;
8166
+ setHoveredNavContainerRect(navContainer.getBoundingClientRect());
8167
+ hoveredItemElRef.current = null;
8168
+ setHoveredItemRect(null);
8169
+ return;
8170
+ }
8171
+ if (!target.closest("[data-ohw-nav-container]")) {
8172
+ hoveredNavContainerRef.current = null;
8173
+ setHoveredNavContainerRect(null);
8174
+ }
8175
+ }
7274
8176
  const navAnchor = getNavigationItemAnchor(target);
7275
8177
  if (navAnchor) {
8178
+ hoveredNavContainerRef.current = null;
8179
+ setHoveredNavContainerRect(null);
7276
8180
  const selected2 = selectedElRef.current;
7277
8181
  if (selected2 === navAnchor || selected2?.contains(navAnchor)) return;
7278
8182
  clearHrefKeyHover(navAnchor);
@@ -7297,6 +8201,14 @@ function OhhwellsBridge() {
7297
8201
  };
7298
8202
  const handleMouseOut = (e) => {
7299
8203
  const target = e.target;
8204
+ if (target.closest("[data-ohw-nav-container]")) {
8205
+ const related2 = e.relatedTarget instanceof Element ? e.relatedTarget : null;
8206
+ if (related2?.closest("[data-ohw-nav-container], [data-ohw-navbar-container-chrome], [data-ohw-navbar-add-button]")) {
8207
+ return;
8208
+ }
8209
+ hoveredNavContainerRef.current = null;
8210
+ setHoveredNavContainerRect(null);
8211
+ }
7300
8212
  const navAnchor = getNavigationItemAnchor(target);
7301
8213
  if (navAnchor) {
7302
8214
  const related2 = e.relatedTarget instanceof Element ? e.relatedTarget : null;
@@ -7358,25 +8270,23 @@ function OhhwellsBridge() {
7358
8270
  }
7359
8271
  return { top, left, width: Math.max(0, right - left), height: Math.max(0, bottom - top) };
7360
8272
  };
7361
- const postImageHover = (imgEl, isDragOver = false, forceTextOverlap) => {
8273
+ const showImageHover = (imgEl, isDragOver = false, forceTextOverlap) => {
7362
8274
  const r2 = getVisibleRect(imgEl);
7363
8275
  const hasTextOverlap = forceTextOverlap ?? false;
7364
8276
  hoveredImageHasTextOverlapRef.current = hasTextOverlap;
7365
8277
  const video = imgEl.dataset.ohwEditable === "video" ? getVideoEl(imgEl) : null;
7366
- postToParentRef.current({
7367
- type: "ow:image-hover",
8278
+ setMediaHover({
7368
8279
  key: imgEl.dataset.ohwKey ?? "",
7369
- // Lets the parent pick the file-picker `accept`, overlay label/icon and drop
7370
- // mime-check without a parallel ow:video-* message channel.
7371
- elementType: imgEl.dataset.ohwEditable ?? "image",
7372
8280
  rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height },
8281
+ elementType: imgEl.dataset.ohwEditable ?? "image",
7373
8282
  hasTextOverlap,
7374
- ...video ? { videoAutoplay: video.autoplay, videoMuted: video.muted } : {},
7375
- ...isDragOver ? { isDragOver: true } : {}
8283
+ isDragOver,
8284
+ ...video ? { videoAutoplay: video.autoplay, videoMuted: video.muted } : {}
7376
8285
  });
7377
8286
  const track = imgEl.closest("[data-ohw-hover-pause]");
7378
8287
  if (track) track.setAttribute("data-ohw-hover-paused", "");
7379
8288
  };
8289
+ const clearImageHover = () => setMediaHover(null);
7380
8290
  const findImageAtPoint = (clientX, clientY, fromParentViewport) => {
7381
8291
  const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
7382
8292
  const images = Array.from(document.querySelectorAll(MEDIA_SELECTOR));
@@ -7420,47 +8330,91 @@ function OhhwellsBridge() {
7420
8330
  }
7421
8331
  return smallest;
7422
8332
  };
8333
+ const dismissImageHover = () => {
8334
+ if (hoveredImageRef.current) {
8335
+ hoveredImageRef.current = null;
8336
+ hoveredImageHasTextOverlapRef.current = false;
8337
+ resumeAnimTracks();
8338
+ postToParentRef.current({ type: "ow:image-unhover" });
8339
+ }
8340
+ clearImageHover();
8341
+ };
8342
+ const probeNavigationHoverAt = (x, y) => {
8343
+ if (toolbarVariantRef.current === "select-frame") {
8344
+ hoveredNavContainerRef.current = null;
8345
+ setHoveredNavContainerRect(null);
8346
+ return;
8347
+ }
8348
+ const navContainer = document.querySelector("[data-ohw-nav-container]");
8349
+ if (!navContainer) {
8350
+ hoveredNavContainerRef.current = null;
8351
+ setHoveredNavContainerRect(null);
8352
+ return;
8353
+ }
8354
+ const containerRect = navContainer.getBoundingClientRect();
8355
+ const overContainer = x >= containerRect.left && x <= containerRect.right && y >= containerRect.top && y <= containerRect.bottom;
8356
+ if (!overContainer) {
8357
+ hoveredNavContainerRef.current = null;
8358
+ setHoveredNavContainerRect(null);
8359
+ return;
8360
+ }
8361
+ const navItemHit = Array.from(
8362
+ navContainer.querySelectorAll("[data-ohw-href-key]")
8363
+ ).find((el) => {
8364
+ if (!isNavigationItem(el) || isNavbarButton2(el)) return false;
8365
+ const itemRect = el.getBoundingClientRect();
8366
+ return x >= itemRect.left && x <= itemRect.right && y >= itemRect.top && y <= itemRect.bottom;
8367
+ });
8368
+ if (navItemHit) {
8369
+ hoveredNavContainerRef.current = null;
8370
+ setHoveredNavContainerRect(null);
8371
+ const selected = selectedElRef.current;
8372
+ if (selected !== navItemHit && !selected?.contains(navItemHit)) {
8373
+ clearHrefKeyHover(navItemHit);
8374
+ hoveredItemElRef.current = navItemHit;
8375
+ setHoveredItemRect(navItemHit.getBoundingClientRect());
8376
+ }
8377
+ return;
8378
+ }
8379
+ hoveredNavContainerRef.current = navContainer;
8380
+ setHoveredNavContainerRect(containerRect);
8381
+ hoveredItemElRef.current = null;
8382
+ setHoveredItemRect(null);
8383
+ };
7423
8384
  const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
7424
8385
  if (linkPopoverOpenRef.current) {
7425
- if (hoveredImageRef.current) {
7426
- hoveredImageRef.current = null;
7427
- hoveredImageHasTextOverlapRef.current = false;
7428
- resumeAnimTracks();
7429
- postToParentRef.current({ type: "ow:image-unhover" });
7430
- }
8386
+ dismissImageHover();
8387
+ return;
8388
+ }
8389
+ const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
8390
+ if (isPointOverNavigation(x, y)) {
8391
+ dismissImageHover();
8392
+ probeNavigationHoverAt(x, y);
7431
8393
  return;
7432
8394
  }
8395
+ hoveredNavContainerRef.current = null;
8396
+ setHoveredNavContainerRect(null);
7433
8397
  const toggleEl = document.querySelector("[data-ohw-state-toggle]");
7434
8398
  if (toggleEl) {
7435
- const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
7436
8399
  const tr = toggleEl.getBoundingClientRect();
7437
8400
  if (x >= tr.left && x <= tr.right && y >= tr.top && y <= tr.bottom) {
7438
- if (hoveredImageRef.current) {
7439
- hoveredImageRef.current = null;
7440
- resumeAnimTracks();
7441
- postToParentRef.current({ type: "ow:image-unhover" });
7442
- }
8401
+ dismissImageHover();
7443
8402
  return;
7444
8403
  }
7445
8404
  }
7446
- if (activeElRef.current) {
7447
- if (hoveredImageRef.current) {
7448
- hoveredImageRef.current = null;
7449
- hoveredImageHasTextOverlapRef.current = false;
7450
- resumeAnimTracks();
7451
- postToParentRef.current({ type: "ow:image-unhover" });
7452
- }
8405
+ const imgEl = findImageAtPoint(clientX, clientY, fromParentViewport);
8406
+ const activeEl = activeElRef.current;
8407
+ if (activeEl && (!imgEl || imgEl.contains(activeEl))) {
8408
+ dismissImageHover();
7453
8409
  return;
7454
8410
  }
7455
- const imgEl = findImageAtPoint(clientX, clientY, fromParentViewport);
7456
8411
  if (imgEl) {
7457
- const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
7458
8412
  const topEl = document.elementFromPoint(x, y);
7459
8413
  if (topEl?.closest('[data-ohw-toolbar], [data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
7460
8414
  if (hoveredImageRef.current) {
7461
8415
  hoveredImageRef.current = null;
7462
8416
  resumeAnimTracks();
7463
- postToParentRef.current({ type: "ow:image-unhover" });
8417
+ clearImageHover();
7464
8418
  }
7465
8419
  return;
7466
8420
  }
@@ -7469,7 +8423,7 @@ function OhhwellsBridge() {
7469
8423
  hoveredImageRef.current = null;
7470
8424
  hoveredImageHasTextOverlapRef.current = false;
7471
8425
  resumeAnimTracks();
7472
- postToParentRef.current({ type: "ow:image-unhover" });
8426
+ clearImageHover();
7473
8427
  }
7474
8428
  return;
7475
8429
  }
@@ -7494,14 +8448,14 @@ function OhhwellsBridge() {
7494
8448
  hoveredImageRef.current = null;
7495
8449
  hoveredImageHasTextOverlapRef.current = false;
7496
8450
  resumeAnimTracks();
7497
- postToParentRef.current({ type: "ow:image-unhover" });
8451
+ clearImageHover();
7498
8452
  }
7499
8453
  return;
7500
8454
  }
7501
8455
  if (isStateCardImage) {
7502
8456
  if (imgEl !== hoveredImageRef.current || !hoveredImageHasTextOverlapRef.current) {
7503
8457
  hoveredImageRef.current = imgEl;
7504
- postImageHover(imgEl, isDragOver, true);
8458
+ showImageHover(imgEl, isDragOver, true);
7505
8459
  }
7506
8460
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
7507
8461
  if (textEditable && !isInsideNavigationItem(textEditable)) {
@@ -7513,7 +8467,7 @@ function OhhwellsBridge() {
7513
8467
  hoveredImageRef.current = null;
7514
8468
  hoveredImageHasTextOverlapRef.current = false;
7515
8469
  resumeAnimTracks();
7516
- postToParentRef.current({ type: "ow:image-unhover" });
8470
+ clearImageHover();
7517
8471
  }
7518
8472
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
7519
8473
  if (textEditable && !isInsideNavigationItem(textEditable)) {
@@ -7525,7 +8479,7 @@ function OhhwellsBridge() {
7525
8479
  if (hoveredImageRef.current) {
7526
8480
  hoveredImageRef.current = null;
7527
8481
  resumeAnimTracks();
7528
- postToParentRef.current({ type: "ow:image-unhover" });
8482
+ clearImageHover();
7529
8483
  }
7530
8484
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
7531
8485
  return;
@@ -7533,9 +8487,9 @@ function OhhwellsBridge() {
7533
8487
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
7534
8488
  if (imgEl !== hoveredImageRef.current || hoveredImageHasTextOverlapRef.current) {
7535
8489
  hoveredImageRef.current = imgEl;
7536
- postImageHover(imgEl, isDragOver);
8490
+ showImageHover(imgEl, isDragOver);
7537
8491
  } else if (isDragOver) {
7538
- postImageHover(imgEl, true);
8492
+ showImageHover(imgEl, true);
7539
8493
  }
7540
8494
  } else {
7541
8495
  const inIframeView = clientX >= 0 && clientY >= 0 && clientX <= window.innerWidth && clientY <= window.innerHeight;
@@ -7552,14 +8506,14 @@ function OhhwellsBridge() {
7552
8506
  hoveredImageRef.current = null;
7553
8507
  hoveredImageHasTextOverlapRef.current = false;
7554
8508
  resumeAnimTracks();
7555
- postToParentRef.current({ type: "ow:image-unhover" });
8509
+ clearImageHover();
7556
8510
  }
7557
- const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
8511
+ const { x: x2, y: y2 } = toProbeCoords(clientX, clientY, fromParentViewport);
7558
8512
  const textEl = Array.from(
7559
8513
  document.querySelectorAll(NON_MEDIA_SELECTOR)
7560
8514
  ).find((el) => {
7561
8515
  const er = el.getBoundingClientRect();
7562
- return x >= er.left && x <= er.right && y >= er.top && y <= er.bottom;
8516
+ return x2 >= er.left && x2 <= er.right && y2 >= er.top && y2 <= er.bottom;
7563
8517
  });
7564
8518
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
7565
8519
  if (textEl && !textEl.hasAttribute("contenteditable")) {
@@ -7665,7 +8619,7 @@ function OhhwellsBridge() {
7665
8619
  if (hoveredImageRef.current !== el) {
7666
8620
  hoveredImageRef.current = el;
7667
8621
  const isStateCard = !!el.closest("[data-ohw-editable-state]");
7668
- postImageHover(el, true, isStateCard ? true : void 0);
8622
+ showImageHover(el, true, isStateCard ? true : void 0);
7669
8623
  }
7670
8624
  };
7671
8625
  const handleDragLeave = (e) => {
@@ -7673,7 +8627,7 @@ function OhhwellsBridge() {
7673
8627
  if (imgEl) return;
7674
8628
  hoveredImageRef.current = null;
7675
8629
  resumeAnimTracks();
7676
- postToParentRef.current({ type: "ow:image-unhover" });
8630
+ clearImageHover();
7677
8631
  };
7678
8632
  const handleDrop = (e) => {
7679
8633
  e.preventDefault();
@@ -7697,31 +8651,7 @@ function OhhwellsBridge() {
7697
8651
  }
7698
8652
  hoveredImageRef.current = null;
7699
8653
  resumeAnimTracks();
7700
- postToParentRef.current({ type: "ow:image-unhover" });
7701
- };
7702
- const handleVideoSettings = (e) => {
7703
- if (e.data?.type !== "ow:video-settings") return;
7704
- const { key, autoplay, muted } = e.data;
7705
- document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
7706
- const video = getVideoEl(el);
7707
- if (!video) return;
7708
- if (typeof autoplay === "boolean") video.autoplay = autoplay;
7709
- if (typeof muted === "boolean") video.muted = muted;
7710
- syncVideoPlayback(video);
7711
- postToParentRef.current({
7712
- type: "ow:change",
7713
- nodes: [
7714
- { key: `${key}${VIDEO_AUTOPLAY_SUFFIX}`, text: String(video.autoplay) },
7715
- { key: `${key}${VIDEO_MUTED_SUFFIX}`, text: String(video.muted) }
7716
- ]
7717
- });
7718
- postToParentRef.current({
7719
- type: "ow:video-settings-applied",
7720
- key,
7721
- autoplay: video.autoplay,
7722
- muted: video.muted
7723
- });
7724
- });
8654
+ clearImageHover();
7725
8655
  };
7726
8656
  const handleImageUrl = (e) => {
7727
8657
  if (e.data?.type !== "ow:image-url") return;
@@ -7739,7 +8669,11 @@ function OhhwellsBridge() {
7739
8669
  }
7740
8670
  });
7741
8671
  hoveredImageRef.current = null;
7742
- postToParentRef.current({ type: "ow:image-loaded", key });
8672
+ setUploadingRects((prev) => {
8673
+ const entry = prev[key];
8674
+ if (!entry || entry.fadingOut) return prev;
8675
+ return { ...prev, [key]: { ...entry, fadingOut: true } };
8676
+ });
7743
8677
  };
7744
8678
  let found = false;
7745
8679
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -7876,6 +8810,8 @@ function OhhwellsBridge() {
7876
8810
  sectionsLoadedRef.current = true;
7877
8811
  pendingScheduleConfigRequests.current.splice(0).forEach(processConfigRequest);
7878
8812
  }
8813
+ editContentRef.current = { ...editContentRef.current, ...content };
8814
+ reconcileNavbarItemsFromContent(editContentRef.current);
7879
8815
  enforceLinkHrefs();
7880
8816
  postToParentRef.current({ type: "ow:hydrate-done" });
7881
8817
  };
@@ -7883,6 +8819,7 @@ function OhhwellsBridge() {
7883
8819
  const handleDeactivate = (e) => {
7884
8820
  if (e.data?.type !== "ow:deactivate") return;
7885
8821
  if (Date.now() < linkPopoverGraceUntilRef.current) return;
8822
+ if (document.querySelector("[data-ohw-section-picker]")) return;
7886
8823
  if (linkPopoverOpenRef.current) {
7887
8824
  setLinkPopoverRef.current(null);
7888
8825
  return;
@@ -7892,6 +8829,7 @@ function OhhwellsBridge() {
7892
8829
  };
7893
8830
  window.addEventListener("message", handleDeactivate);
7894
8831
  const handleKeyDown = (e) => {
8832
+ if (e.key === "Escape" && document.querySelector("[data-ohw-section-picker]")) return;
7895
8833
  if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "a" && activeElRef.current) {
7896
8834
  const navAnchor = getNavigationItemAnchor(activeElRef.current);
7897
8835
  if (navAnchor) {
@@ -7906,8 +8844,13 @@ function OhhwellsBridge() {
7906
8844
  return;
7907
8845
  }
7908
8846
  if (e.key === "Escape" && selectedElRef.current && !activeElRef.current) {
8847
+ if (toolbarVariantRef.current === "select-frame") {
8848
+ deselectRef.current();
8849
+ return;
8850
+ }
7909
8851
  const parent = getNavigationSelectionParent(selectedElRef.current);
7910
8852
  if (parent) {
8853
+ e.preventDefault();
7911
8854
  selectFrameRef.current(parent);
7912
8855
  } else {
7913
8856
  deselectRef.current();
@@ -7969,18 +8912,18 @@ function OhhwellsBridge() {
7969
8912
  if (hoveredItemElRef.current) {
7970
8913
  setHoveredItemRect(hoveredItemElRef.current.getBoundingClientRect());
7971
8914
  }
8915
+ if (hoveredNavContainerRef.current) {
8916
+ setHoveredNavContainerRect(hoveredNavContainerRef.current.getBoundingClientRect());
8917
+ }
7972
8918
  if (siblingHintElRef.current) {
7973
8919
  setSiblingHintRect(siblingHintElRef.current.getBoundingClientRect());
7974
8920
  }
7975
8921
  if (hoveredImageRef.current) {
7976
8922
  const el = hoveredImageRef.current;
7977
8923
  const r2 = el.getBoundingClientRect();
7978
- postToParentRef.current({
7979
- type: "ow:image-hover",
7980
- key: el.dataset.ohwKey ?? "",
7981
- rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height },
7982
- hasTextOverlap: hoveredImageHasTextOverlapRef.current
7983
- });
8924
+ setMediaHover(
8925
+ (prev) => prev ? { ...prev, rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height } } : prev
8926
+ );
7984
8927
  }
7985
8928
  };
7986
8929
  const handleSave = (e) => {
@@ -8069,19 +9012,39 @@ function OhhwellsBridge() {
8069
9012
  refreshActiveCommandsRef.current = handleSelectionChange;
8070
9013
  const handleDocMouseLeave = () => {
8071
9014
  hoveredImageRef.current = null;
9015
+ setMediaHover(null);
8072
9016
  document.querySelectorAll("[data-ohw-state-hovered]").forEach((el) => el.removeAttribute("data-ohw-state-hovered"));
8073
9017
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
8074
9018
  activeStateElRef.current = null;
8075
9019
  setToggleState(null);
8076
9020
  };
8077
- const handleAnimLock = (e) => {
8078
- if (e.data?.type !== "ow:anim-lock") return;
8079
- const { key } = e.data;
9021
+ const handleImageUploading = (e) => {
9022
+ if (e.data?.type !== "ow:image-uploading") return;
9023
+ const { key, uploading } = e.data;
9024
+ setUploadingRects((prev) => {
9025
+ if (!uploading) {
9026
+ if (!(key in prev)) return prev;
9027
+ const next = { ...prev };
9028
+ delete next[key];
9029
+ return next;
9030
+ }
9031
+ const el = document.querySelector(`[data-ohw-key="${key}"]`);
9032
+ if (!el) return prev;
9033
+ const r2 = getVisibleRect(el);
9034
+ return {
9035
+ ...prev,
9036
+ [key]: { rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height } }
9037
+ };
9038
+ });
8080
9039
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
8081
9040
  const track = el.closest("[data-ohw-hover-pause]");
8082
- if (track) {
9041
+ if (!track) return;
9042
+ if (uploading) {
8083
9043
  uploadLockedTracks.add(track);
8084
9044
  track.setAttribute("data-ohw-hover-paused", "");
9045
+ } else {
9046
+ uploadLockedTracks.delete(track);
9047
+ track.removeAttribute("data-ohw-hover-paused");
8085
9048
  }
8086
9049
  });
8087
9050
  };
@@ -8138,7 +9101,7 @@ function OhhwellsBridge() {
8138
9101
  return clientX >= r2.left && clientX <= r2.right && clientY >= r2.top && clientY <= r2.bottom;
8139
9102
  });
8140
9103
  if (stateCardImage) {
8141
- postToParentRef.current({ type: "ow:image-pick", key: stateCardImage.dataset.ohwKey ?? "" });
9104
+ postToParentRef.current({ type: "ow:image-pick", key: stateCardImage.dataset.ohwKey ?? "", elementType: stateCardImage.dataset.ohwEditable ?? "image" });
8142
9105
  return;
8143
9106
  }
8144
9107
  const textEditable = Array.from(
@@ -8161,6 +9124,32 @@ function OhhwellsBridge() {
8161
9124
  activateRef.current(textEditable, { caretX: clientX, caretY: clientY });
8162
9125
  return;
8163
9126
  }
9127
+ const navContainer = document.querySelector("[data-ohw-nav-container]");
9128
+ if (navContainer) {
9129
+ const r2 = navContainer.getBoundingClientRect();
9130
+ const pad = 8;
9131
+ const over = clientX >= r2.left - pad && clientX <= r2.right + pad && clientY >= r2.top - pad && clientY <= r2.bottom + pad;
9132
+ if (over && !isPointOverNavItem(navContainer, clientX, clientY)) {
9133
+ selectFrameRef.current(navContainer);
9134
+ return;
9135
+ }
9136
+ }
9137
+ const navRoot = document.querySelector("[data-ohw-nav-root]");
9138
+ if (navRoot && navContainer) {
9139
+ const rr = navRoot.getBoundingClientRect();
9140
+ if (clientX >= rr.left && clientX <= rr.right && clientY >= rr.top && clientY <= rr.bottom && !isPointOverNavItem(navContainer, clientX, clientY)) {
9141
+ const book = navRoot.querySelector('[data-ohw-role="navbar-button"]');
9142
+ if (book) {
9143
+ const br = book.getBoundingClientRect();
9144
+ if (clientX >= br.left && clientX <= br.right && clientY >= br.top && clientY <= br.bottom) {
9145
+ deactivateRef.current();
9146
+ return;
9147
+ }
9148
+ }
9149
+ selectFrameRef.current(navContainer);
9150
+ return;
9151
+ }
9152
+ }
8164
9153
  deactivateRef.current();
8165
9154
  };
8166
9155
  window.addEventListener("message", handleSave);
@@ -8170,8 +9159,7 @@ function OhhwellsBridge() {
8170
9159
  window.addEventListener("message", handleClearSchedulingWidget);
8171
9160
  window.addEventListener("message", handleRemoveSchedulingSection);
8172
9161
  window.addEventListener("message", handleImageUrl);
8173
- window.addEventListener("message", handleVideoSettings);
8174
- window.addEventListener("message", handleAnimLock);
9162
+ window.addEventListener("message", handleImageUploading);
8175
9163
  window.addEventListener("message", handleCanvasHeight);
8176
9164
  window.addEventListener("message", handleParentScroll);
8177
9165
  window.addEventListener("message", handlePointerSync);
@@ -8218,8 +9206,7 @@ function OhhwellsBridge() {
8218
9206
  window.removeEventListener("message", handleClearSchedulingWidget);
8219
9207
  window.removeEventListener("message", handleRemoveSchedulingSection);
8220
9208
  window.removeEventListener("message", handleImageUrl);
8221
- window.removeEventListener("message", handleVideoSettings);
8222
- window.removeEventListener("message", handleAnimLock);
9209
+ window.removeEventListener("message", handleImageUploading);
8223
9210
  window.removeEventListener("message", handleCanvasHeight);
8224
9211
  window.removeEventListener("message", handleParentScroll);
8225
9212
  window.removeEventListener("resize", handleViewportResize);
@@ -8233,7 +9220,7 @@ function OhhwellsBridge() {
8233
9220
  if (imageShowTimerRef.current) clearTimeout(imageShowTimerRef.current);
8234
9221
  };
8235
9222
  }, [isEditMode, refreshStateRules]);
8236
- useEffect3(() => {
9223
+ useEffect7(() => {
8237
9224
  const handler = (e) => {
8238
9225
  if (e.data?.type !== "ow:request-schedule-config") return;
8239
9226
  const insertAfterVal = e.data.insertAfter;
@@ -8249,7 +9236,7 @@ function OhhwellsBridge() {
8249
9236
  window.addEventListener("message", handler);
8250
9237
  return () => window.removeEventListener("message", handler);
8251
9238
  }, [processConfigRequest]);
8252
- useEffect3(() => {
9239
+ useEffect7(() => {
8253
9240
  if (!isEditMode) return;
8254
9241
  document.querySelectorAll("[data-ohw-active-state]").forEach((el) => {
8255
9242
  el.removeAttribute("data-ohw-active-state");
@@ -8270,27 +9257,27 @@ function OhhwellsBridge() {
8270
9257
  const next = { ...prev, [pathKey]: sections };
8271
9258
  return shouldUseDevFixtures() ? { ...DEV_SECTIONS_BY_PATH, ...next } : next;
8272
9259
  });
8273
- postToParent({ type: "ow:ready", version: "1", path: pathname, nodes: collectEditableNodes(), sections });
8274
- postToParent({ type: "ow:request-site-pages" });
9260
+ postToParent2({ type: "ow:ready", version: "1", path: pathname, nodes: collectEditableNodes(), sections });
9261
+ postToParent2({ type: "ow:request-site-pages" });
8275
9262
  }, 150);
8276
9263
  return () => {
8277
9264
  cancelAnimationFrame(raf);
8278
9265
  clearTimeout(timer);
8279
9266
  };
8280
- }, [pathname, isEditMode, refreshStateRules, postToParent]);
8281
- useEffect3(() => {
9267
+ }, [pathname, isEditMode, refreshStateRules, postToParent2]);
9268
+ useEffect7(() => {
8282
9269
  scrollToHashSectionWhenReady();
8283
9270
  const onHashChange = () => scrollToHashSectionWhenReady();
8284
9271
  window.addEventListener("hashchange", onHashChange);
8285
9272
  return () => window.removeEventListener("hashchange", onHashChange);
8286
9273
  }, [pathname]);
8287
- const handleCommand = useCallback3((cmd) => {
9274
+ const handleCommand = useCallback4((cmd) => {
8288
9275
  document.execCommand(cmd, false);
8289
9276
  activeElRef.current?.focus();
8290
9277
  if (activeElRef.current) setToolbarRect(getEditMeasureEl(activeElRef.current).getBoundingClientRect());
8291
9278
  refreshActiveCommandsRef.current();
8292
9279
  }, []);
8293
- const handleStateChange = useCallback3((state) => {
9280
+ const handleStateChange = useCallback4((state) => {
8294
9281
  if (!activeStateElRef.current) return;
8295
9282
  const el = activeStateElRef.current;
8296
9283
  if (state === "Default") {
@@ -8303,18 +9290,22 @@ function OhhwellsBridge() {
8303
9290
  }
8304
9291
  setToggleState((prev) => prev ? { ...prev, activeState: state } : null);
8305
9292
  }, [deactivate]);
8306
- const closeLinkPopover = useCallback3(() => setLinkPopover(null), []);
8307
- const openLinkPopoverForActive = useCallback3(() => {
9293
+ const closeLinkPopover = useCallback4(() => {
9294
+ addNavAfterAnchorRef.current = null;
9295
+ setLinkPopover(null);
9296
+ }, []);
9297
+ const openLinkPopoverForActive = useCallback4(() => {
8308
9298
  const hrefCtx = getHrefKeyFromElement(activeElRef.current);
8309
9299
  if (!hrefCtx) return;
8310
9300
  bumpLinkPopoverGrace();
8311
9301
  setLinkPopover({
8312
9302
  key: hrefCtx.key,
8313
- target: getLinkHref(hrefCtx.anchor)
9303
+ mode: "edit",
9304
+ target: getLinkHref2(hrefCtx.anchor)
8314
9305
  });
8315
9306
  deactivate();
8316
9307
  }, [deactivate]);
8317
- const openLinkPopoverForSelected = useCallback3(() => {
9308
+ const openLinkPopoverForSelected = useCallback4(() => {
8318
9309
  const anchor = selectedElRef.current;
8319
9310
  if (!anchor) return;
8320
9311
  const key = anchor.getAttribute("data-ohw-href-key");
@@ -8322,29 +9313,133 @@ function OhhwellsBridge() {
8322
9313
  bumpLinkPopoverGrace();
8323
9314
  setLinkPopover({
8324
9315
  key,
8325
- target: getLinkHref(anchor)
9316
+ mode: "edit",
9317
+ target: getLinkHref2(anchor)
8326
9318
  });
8327
9319
  deselect();
8328
9320
  }, [deselect]);
8329
- const handleLinkPopoverSubmit = useCallback3(
9321
+ const handleLinkPopoverSubmit = useCallback4(
8330
9322
  (target) => {
8331
- if (!linkPopover) return;
8332
- const { key } = linkPopover;
9323
+ const session = linkPopoverSessionRef.current;
9324
+ if (!session) return;
9325
+ if (session.intent === "add-nav") {
9326
+ const { pageRoute, sectionId } = parseTarget(target);
9327
+ const page = resolvePage(pageRoute, sitePages);
9328
+ const pageSections = getSectionsForPath(sectionsByPath, pageRoute);
9329
+ const section = sectionId ? pageSections.find((s) => s.id === sectionId) : void 0;
9330
+ const label = section?.label ?? page.title;
9331
+ const afterAnchor = addNavAfterAnchorRef.current;
9332
+ const { anchor, hrefKey, labelKey, index, order } = insertNavbarItem(target, label, afterAnchor);
9333
+ applyLinkByKey(hrefKey, target);
9334
+ document.querySelectorAll(`[data-ohw-key="${labelKey}"]`).forEach((el) => {
9335
+ el.textContent = label;
9336
+ });
9337
+ const navCount = String(index + 1);
9338
+ const orderJson = JSON.stringify(order);
9339
+ editContentRef.current = {
9340
+ ...editContentRef.current,
9341
+ [hrefKey]: target,
9342
+ [labelKey]: label,
9343
+ [NAV_COUNT_KEY]: navCount,
9344
+ [NAV_ORDER_KEY]: orderJson
9345
+ };
9346
+ postToParent2({
9347
+ type: "ow:change",
9348
+ nodes: [
9349
+ { key: hrefKey, text: target },
9350
+ { key: labelKey, text: label },
9351
+ { key: NAV_COUNT_KEY, text: navCount },
9352
+ { key: NAV_ORDER_KEY, text: orderJson }
9353
+ ]
9354
+ });
9355
+ addNavAfterAnchorRef.current = null;
9356
+ setLinkPopover(null);
9357
+ enforceLinkHrefs();
9358
+ requestAnimationFrame(() => {
9359
+ selectRef.current(anchor);
9360
+ });
9361
+ return;
9362
+ }
9363
+ const { key } = session;
8333
9364
  applyLinkByKey(key, target);
8334
- postToParent({ type: "ow:change", nodes: [{ key, text: target }] });
9365
+ postToParent2({ type: "ow:change", nodes: [{ key, text: target }] });
8335
9366
  setLinkPopover(null);
8336
9367
  },
8337
- [linkPopover, postToParent]
9368
+ [postToParent2, sitePages, sectionsByPath]
8338
9369
  );
8339
9370
  const showEditLink = toolbarShowEditLink;
8340
9371
  const currentSections = sectionsByPath[pathname] ?? [];
8341
9372
  linkPopoverOpenRef.current = linkPopover !== null;
8342
- return bridgeRoot ? createPortal(
8343
- /* @__PURE__ */ jsxs11(Fragment3, { children: [
8344
- /* @__PURE__ */ jsx21("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
8345
- siblingHintRect && !isItemDragging && /* @__PURE__ */ jsx21(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
8346
- hoveredItemRect && !siblingHintRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ jsx21(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
8347
- toolbarRect && (toolbarVariant === "link-action" || toolbarVariant === "select-frame") && /* @__PURE__ */ jsx21(
9373
+ const handleMediaReplace = useCallback4(
9374
+ (key) => {
9375
+ postToParent2({ type: "ow:image-pick", key, elementType: mediaHover?.elementType ?? "image" });
9376
+ },
9377
+ [postToParent2, mediaHover?.elementType]
9378
+ );
9379
+ const handleVideoSettingsChange = useCallback4(
9380
+ (key, settings) => {
9381
+ document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
9382
+ const video = getVideoEl(el);
9383
+ if (!video) return;
9384
+ if (typeof settings.autoplay === "boolean") video.autoplay = settings.autoplay;
9385
+ if (typeof settings.muted === "boolean") video.muted = settings.muted;
9386
+ syncVideoPlayback(video);
9387
+ postToParent2({
9388
+ type: "ow:change",
9389
+ nodes: [
9390
+ { key: `${key}${VIDEO_AUTOPLAY_SUFFIX}`, text: String(video.autoplay) },
9391
+ { key: `${key}${VIDEO_MUTED_SUFFIX}`, text: String(video.muted) }
9392
+ ]
9393
+ });
9394
+ setMediaHover(
9395
+ (prev) => prev && prev.key === key ? { ...prev, videoAutoplay: video.autoplay, videoMuted: video.muted } : prev
9396
+ );
9397
+ });
9398
+ },
9399
+ [postToParent2]
9400
+ );
9401
+ const handleMediaFadeOutComplete = useCallback4((key) => {
9402
+ setUploadingRects((prev) => {
9403
+ if (!(key in prev)) return prev;
9404
+ const next = { ...prev };
9405
+ delete next[key];
9406
+ return next;
9407
+ });
9408
+ }, []);
9409
+ return bridgeRoot ? createPortal2(
9410
+ /* @__PURE__ */ jsxs13(Fragment5, { children: [
9411
+ /* @__PURE__ */ jsx23("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
9412
+ Object.entries(uploadingRects).map(([key, { rect, fadingOut }]) => /* @__PURE__ */ jsx23(
9413
+ MediaOverlay,
9414
+ {
9415
+ hover: { key, rect, elementType: "image", isDragOver: false, hasTextOverlap: false },
9416
+ isUploading: true,
9417
+ fadingOut,
9418
+ onFadeOutComplete: handleMediaFadeOutComplete,
9419
+ onReplace: handleMediaReplace
9420
+ },
9421
+ `uploading-${key}`
9422
+ )),
9423
+ mediaHover && !(mediaHover.key in uploadingRects) && /* @__PURE__ */ jsx23(
9424
+ MediaOverlay,
9425
+ {
9426
+ hover: mediaHover,
9427
+ isUploading: false,
9428
+ onReplace: handleMediaReplace,
9429
+ onVideoSettingsChange: handleVideoSettingsChange
9430
+ }
9431
+ ),
9432
+ siblingHintRect && !isItemDragging && /* @__PURE__ */ jsx23(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
9433
+ hoveredNavContainerRect && toolbarVariant !== "select-frame" && !linkPopover && !isItemDragging && !siblingHintRect && /* @__PURE__ */ jsx23(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
9434
+ toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && /* @__PURE__ */ jsx23(
9435
+ NavbarContainerChrome,
9436
+ {
9437
+ rect: toolbarRect,
9438
+ onAdd: handleAddTopLevelNavItem
9439
+ }
9440
+ ),
9441
+ hoveredItemRect && !hoveredNavContainerRect && !siblingHintRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ jsx23(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
9442
+ toolbarRect && (toolbarVariant === "link-action" || toolbarVariant === "select-frame") && /* @__PURE__ */ jsx23(
8348
9443
  ItemInteractionLayer,
8349
9444
  {
8350
9445
  rect: toolbarRect,
@@ -8355,18 +9450,19 @@ function OhhwellsBridge() {
8355
9450
  dragHandleLabel: reorderHrefKey ? `Reorder ${reorderHrefKey}` : "Reorder item",
8356
9451
  onDragHandleDragStart: handleItemDragStart,
8357
9452
  onDragHandleDragEnd: handleItemDragEnd,
8358
- toolbar: toolbarVariant === "link-action" && !isItemDragging ? /* @__PURE__ */ jsx21(
9453
+ toolbar: toolbarVariant === "link-action" && !isItemDragging ? /* @__PURE__ */ jsx23(
8359
9454
  ItemActionToolbar,
8360
9455
  {
8361
9456
  onEditLink: openLinkPopoverForSelected,
8362
- onAddItem: handleAddNavigationItem,
8363
- addItemDisabled: false
9457
+ addItemDisabled: true,
9458
+ editLinkDisabled: false,
9459
+ moreDisabled: true
8364
9460
  }
8365
9461
  ) : void 0
8366
9462
  }
8367
9463
  ),
8368
- toolbarRect && toolbarVariant === "rich-text" && /* @__PURE__ */ jsxs11(Fragment3, { children: [
8369
- /* @__PURE__ */ jsx21(
9464
+ toolbarRect && toolbarVariant === "rich-text" && /* @__PURE__ */ jsxs13(Fragment5, { children: [
9465
+ /* @__PURE__ */ jsx23(
8370
9466
  EditGlowChrome,
8371
9467
  {
8372
9468
  rect: toolbarRect,
@@ -8375,7 +9471,7 @@ function OhhwellsBridge() {
8375
9471
  dragDisabled: reorderDragDisabled
8376
9472
  }
8377
9473
  ),
8378
- /* @__PURE__ */ jsx21(
9474
+ /* @__PURE__ */ jsx23(
8379
9475
  FloatingToolbar,
8380
9476
  {
8381
9477
  rect: toolbarRect,
@@ -8388,7 +9484,7 @@ function OhhwellsBridge() {
8388
9484
  }
8389
9485
  )
8390
9486
  ] }),
8391
- maxBadge && /* @__PURE__ */ jsxs11(
9487
+ maxBadge && /* @__PURE__ */ jsxs13(
8392
9488
  "div",
8393
9489
  {
8394
9490
  "data-ohw-max-badge": "",
@@ -8414,7 +9510,7 @@ function OhhwellsBridge() {
8414
9510
  ]
8415
9511
  }
8416
9512
  ),
8417
- toggleState && /* @__PURE__ */ jsx21(
9513
+ toggleState && !linkPopover && /* @__PURE__ */ jsx23(
8418
9514
  StateToggle,
8419
9515
  {
8420
9516
  rect: toggleState.rect,
@@ -8423,15 +9519,15 @@ function OhhwellsBridge() {
8423
9519
  onStateChange: handleStateChange
8424
9520
  }
8425
9521
  ),
8426
- sectionGap && /* @__PURE__ */ jsxs11(
9522
+ sectionGap && !linkPopover && /* @__PURE__ */ jsxs13(
8427
9523
  "div",
8428
9524
  {
8429
9525
  "data-ohw-section-insert-line": "",
8430
9526
  className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
8431
9527
  style: { top: sectionGap.y, transform: "translateY(-50%)" },
8432
9528
  children: [
8433
- /* @__PURE__ */ jsx21("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
8434
- /* @__PURE__ */ jsx21(
9529
+ /* @__PURE__ */ jsx23("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
9530
+ /* @__PURE__ */ jsx23(
8435
9531
  Badge,
8436
9532
  {
8437
9533
  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",
@@ -8444,35 +9540,36 @@ function OhhwellsBridge() {
8444
9540
  children: "Add Section"
8445
9541
  }
8446
9542
  ),
8447
- /* @__PURE__ */ jsx21("div", { className: "flex-1 bg-primary", style: { height: 3 } })
9543
+ /* @__PURE__ */ jsx23("div", { className: "flex-1 bg-primary", style: { height: 3 } })
8448
9544
  ]
8449
9545
  }
8450
9546
  ),
8451
- linkPopover && dialogPortalContainer ? /* @__PURE__ */ jsx21(
9547
+ linkPopover && dialogPortalContainer ? /* @__PURE__ */ jsx23(
8452
9548
  LinkPopover,
8453
9549
  {
8454
9550
  panelRef: linkPopoverPanelRef,
8455
9551
  portalContainer: dialogPortalContainer,
8456
9552
  open: true,
8457
- mode: "edit",
9553
+ mode: linkPopover.mode ?? "edit",
8458
9554
  pages: sitePages,
8459
9555
  sections: currentSections,
8460
9556
  sectionsByPath,
8461
9557
  initialTarget: linkPopover.target,
9558
+ existingTargets: linkPopover.intent === "add-nav" ? getNavbarExistingTargets() : [],
8462
9559
  onClose: closeLinkPopover,
8463
9560
  onSubmit: handleLinkPopoverSubmit
8464
9561
  },
8465
- `${linkPopover.key}-${pathname}`
9562
+ linkPopover.key
8466
9563
  ) : null,
8467
- sectionGap && /* @__PURE__ */ jsxs11(
9564
+ sectionGap && /* @__PURE__ */ jsxs13(
8468
9565
  "div",
8469
9566
  {
8470
9567
  "data-ohw-section-insert-line": "",
8471
9568
  className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
8472
9569
  style: { top: sectionGap.y, transform: "translateY(-50%)" },
8473
9570
  children: [
8474
- /* @__PURE__ */ jsx21("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
8475
- /* @__PURE__ */ jsx21(
9571
+ /* @__PURE__ */ jsx23("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
9572
+ /* @__PURE__ */ jsx23(
8476
9573
  Badge,
8477
9574
  {
8478
9575
  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",
@@ -8485,7 +9582,7 @@ function OhhwellsBridge() {
8485
9582
  children: "Add Section"
8486
9583
  }
8487
9584
  ),
8488
- /* @__PURE__ */ jsx21("div", { className: "flex-1 bg-primary", style: { height: 3 } })
9585
+ /* @__PURE__ */ jsx23("div", { className: "flex-1 bg-primary", style: { height: 3 } })
8489
9586
  ]
8490
9587
  }
8491
9588
  )