@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.cjs CHANGED
@@ -60,15 +60,18 @@ __export(index_exports, {
60
60
  module.exports = __toCommonJS(index_exports);
61
61
 
62
62
  // src/OhhwellsBridge.tsx
63
- var import_react6 = __toESM(require("react"), 1);
63
+ var import_react8 = __toESM(require("react"), 1);
64
64
  var import_client = require("react-dom/client");
65
- var import_react_dom = require("react-dom");
65
+ var import_react_dom2 = require("react-dom");
66
66
 
67
67
  // src/linkHrefStore.ts
68
68
  var linkHrefStore = /* @__PURE__ */ new Map();
69
69
  function setStoredLinkHref(key, href) {
70
70
  linkHrefStore.set(key, href);
71
71
  }
72
+ function getStoredLinkHref(key) {
73
+ return linkHrefStore.get(key);
74
+ }
72
75
  function enforceLinkHrefs() {
73
76
  document.querySelectorAll("[data-ohw-href-key]").forEach((el) => {
74
77
  const key = el.getAttribute("data-ohw-href-key") ?? "";
@@ -4456,6 +4459,7 @@ function ItemActionToolbar({
4456
4459
  onEditLink,
4457
4460
  onAddItem,
4458
4461
  onMore,
4462
+ editLinkDisabled = false,
4459
4463
  addItemDisabled = true,
4460
4464
  moreDisabled = true,
4461
4465
  tooltipSide = "bottom"
@@ -4466,8 +4470,10 @@ function ItemActionToolbar({
4466
4470
  {
4467
4471
  label: "Edit link",
4468
4472
  side: tooltipSide,
4473
+ disabled: editLinkDisabled,
4469
4474
  buttonProps: {
4470
4475
  onMouseDown: (e) => {
4476
+ if (editLinkDisabled) return;
4471
4477
  e.preventDefault();
4472
4478
  e.stopPropagation();
4473
4479
  onEditLink?.();
@@ -4639,9 +4645,223 @@ function ItemInteractionLayer({
4639
4645
  );
4640
4646
  }
4641
4647
 
4648
+ // src/ui/MediaOverlay.tsx
4649
+ var React6 = __toESM(require("react"), 1);
4650
+ var import_lucide_react3 = require("lucide-react");
4651
+
4652
+ // src/ui/button.tsx
4653
+ var React5 = __toESM(require("react"), 1);
4654
+ var import_radix_ui4 = require("radix-ui");
4655
+ var import_jsx_runtime10 = require("react/jsx-runtime");
4656
+ var buttonVariants = cva(
4657
+ "inline-flex items-center justify-center gap-1 whitespace-nowrap rounded-md text-sm font-medium transition-colors outline-none disabled:pointer-events-none disabled:opacity-50 min-w-[80px] px-3 py-2",
4658
+ {
4659
+ variants: {
4660
+ variant: {
4661
+ default: "bg-primary text-primary-foreground hover:opacity-90",
4662
+ outline: "border border-border bg-background text-foreground shadow-sm hover:bg-muted/80",
4663
+ ghost: "min-w-0 px-3 py-2 text-foreground hover:bg-muted/50"
4664
+ },
4665
+ size: {
4666
+ default: "h-9",
4667
+ sm: "h-8 min-w-[64px] px-2 py-1.5 text-sm"
4668
+ }
4669
+ },
4670
+ defaultVariants: {
4671
+ variant: "default",
4672
+ size: "default"
4673
+ }
4674
+ }
4675
+ );
4676
+ var Button = React5.forwardRef(
4677
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
4678
+ const Comp = asChild ? import_radix_ui4.Slot.Root : "button";
4679
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4680
+ Comp,
4681
+ {
4682
+ ref,
4683
+ "data-slot": "button",
4684
+ className: cn(buttonVariants({ variant, size, className })),
4685
+ ...props
4686
+ }
4687
+ );
4688
+ }
4689
+ );
4690
+ Button.displayName = "Button";
4691
+
4692
+ // src/ui/MediaOverlay.tsx
4693
+ var import_jsx_runtime11 = require("react/jsx-runtime");
4694
+ var MEDIA_UPLOAD_FADE_MS = 300;
4695
+ var VIDEO_SETTINGS_BAR_INSET = 8;
4696
+ var OVERLAY_BUTTON_STYLE = {
4697
+ pointerEvents: "auto",
4698
+ fontFamily: "Inter, sans-serif",
4699
+ fontSize: 12,
4700
+ color: "#000"
4701
+ };
4702
+ var SKELETON_CSS = `
4703
+ @keyframes ohw-media-shimmer {
4704
+ 0% { transform: translateX(-100%); }
4705
+ 100% { transform: translateX(100%); }
4706
+ }
4707
+ [data-ohw-media-skeleton] {
4708
+ overflow: hidden;
4709
+ background-color: #d1d5db; /* tailwind gray-300 */
4710
+ }
4711
+ [data-ohw-media-skeleton]::after {
4712
+ content: "";
4713
+ position: absolute;
4714
+ inset: 0;
4715
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.6), transparent);
4716
+ animation: ohw-media-shimmer 1.4s infinite;
4717
+ }
4718
+ `;
4719
+ function MediaOverlay({
4720
+ hover,
4721
+ isUploading,
4722
+ fadingOut = false,
4723
+ onFadeOutComplete,
4724
+ onReplace,
4725
+ onVideoSettingsChange
4726
+ }) {
4727
+ const { rect } = hover;
4728
+ const skeletonRef = React6.useRef(null);
4729
+ const isVideo = hover.elementType === "video";
4730
+ const autoplay = hover.videoAutoplay ?? true;
4731
+ const muted = hover.videoMuted ?? true;
4732
+ const box = {
4733
+ position: "fixed",
4734
+ top: rect.top,
4735
+ left: rect.left,
4736
+ width: rect.width,
4737
+ height: rect.height,
4738
+ zIndex: 2147483646
4739
+ };
4740
+ React6.useEffect(() => {
4741
+ if (!isUploading || !fadingOut || !skeletonRef.current) return;
4742
+ const anim = skeletonRef.current.animate([{ opacity: 1 }, { opacity: 0 }], {
4743
+ duration: MEDIA_UPLOAD_FADE_MS,
4744
+ easing: "ease-out",
4745
+ fill: "forwards"
4746
+ });
4747
+ anim.finished.then(() => onFadeOutComplete?.(hover.key)).catch(() => onFadeOutComplete?.(hover.key));
4748
+ return () => anim.cancel();
4749
+ }, [isUploading, fadingOut, onFadeOutComplete, hover.key]);
4750
+ if (isUploading) {
4751
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
4752
+ "div",
4753
+ {
4754
+ ref: skeletonRef,
4755
+ "data-ohw-bridge": "",
4756
+ "data-ohw-media-overlay": "",
4757
+ "data-ohw-media-skeleton": "",
4758
+ "aria-hidden": true,
4759
+ style: { ...box, pointerEvents: "none" },
4760
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("style", { children: SKELETON_CSS })
4761
+ }
4762
+ );
4763
+ }
4764
+ const settingsBar = isVideo && !hover.isDragOver ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
4765
+ "div",
4766
+ {
4767
+ "data-ohw-bridge": "",
4768
+ "data-ohw-media-overlay": "",
4769
+ className: "flex items-center justify-center gap-1.5",
4770
+ style: {
4771
+ position: "fixed",
4772
+ top: rect.top + VIDEO_SETTINGS_BAR_INSET,
4773
+ left: rect.left,
4774
+ width: rect.width,
4775
+ zIndex: 2147483647,
4776
+ pointerEvents: "auto"
4777
+ },
4778
+ onClick: (e) => e.stopPropagation(),
4779
+ children: [
4780
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
4781
+ Button,
4782
+ {
4783
+ "data-ohw-media-overlay": "",
4784
+ variant: "outline",
4785
+ size: "sm",
4786
+ className: "cursor-pointer hover:bg-background",
4787
+ style: { ...OVERLAY_BUTTON_STYLE, minWidth: 0, padding: "0 8px" },
4788
+ "aria-label": autoplay ? "Disable autoplay" : "Enable autoplay",
4789
+ title: autoplay ? "Autoplay on \u2014 click to disable" : "Autoplay off \u2014 click to enable",
4790
+ onMouseDown: (e) => e.preventDefault(),
4791
+ onClick: (e) => {
4792
+ e.stopPropagation();
4793
+ onVideoSettingsChange?.(hover.key, { autoplay: !autoplay });
4794
+ },
4795
+ children: autoplay ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react3.Pause, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react3.Play, { size: 14 })
4796
+ }
4797
+ ),
4798
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
4799
+ Button,
4800
+ {
4801
+ "data-ohw-media-overlay": "",
4802
+ variant: "outline",
4803
+ size: "sm",
4804
+ className: "cursor-pointer hover:bg-background",
4805
+ style: { ...OVERLAY_BUTTON_STYLE, minWidth: 0, padding: "0 8px" },
4806
+ "aria-label": muted ? "Unmute video" : "Mute video",
4807
+ title: muted ? "Muted \u2014 click to unmute" : "Sound on \u2014 click to mute",
4808
+ onMouseDown: (e) => e.preventDefault(),
4809
+ onClick: (e) => {
4810
+ e.stopPropagation();
4811
+ onVideoSettingsChange?.(hover.key, { muted: !muted });
4812
+ },
4813
+ children: muted ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react3.VolumeX, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react3.Volume2, { size: 14 })
4814
+ }
4815
+ )
4816
+ ]
4817
+ }
4818
+ ) : null;
4819
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
4820
+ settingsBar,
4821
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
4822
+ "div",
4823
+ {
4824
+ "data-ohw-bridge": "",
4825
+ "data-ohw-media-overlay": "",
4826
+ className: "flex items-center justify-center cursor-pointer",
4827
+ style: {
4828
+ ...box,
4829
+ // When text overlays this media, let clicks fall THROUGH to the text underneath. In the
4830
+ // parent this needed a whole `ow:click-at` round-trip to re-hit-test inside the iframe;
4831
+ // in-document, pointer-events does it natively. The button below opts back in, so
4832
+ // Replace still works.
4833
+ pointerEvents: hover.hasTextOverlap ? "none" : "auto",
4834
+ boxShadow: "inset 0 0 0 1.5px var(--color-primary)",
4835
+ background: "color-mix(in srgb, var(--color-primary) 20%, transparent)"
4836
+ },
4837
+ onClick: () => onReplace(hover.key),
4838
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
4839
+ Button,
4840
+ {
4841
+ "data-ohw-media-overlay": "",
4842
+ variant: "outline",
4843
+ size: "sm",
4844
+ className: "gap-1.5 cursor-pointer hover:bg-background",
4845
+ style: OVERLAY_BUTTON_STYLE,
4846
+ onMouseDown: (e) => e.preventDefault(),
4847
+ onClick: (e) => {
4848
+ e.stopPropagation();
4849
+ onReplace(hover.key);
4850
+ },
4851
+ children: [
4852
+ isVideo ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react3.Film, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react3.ImageIcon, { size: 14 }),
4853
+ isVideo ? "Replace video" : "Replace image"
4854
+ ]
4855
+ }
4856
+ )
4857
+ }
4858
+ )
4859
+ ] });
4860
+ }
4861
+
4642
4862
  // src/OhhwellsBridge.tsx
4643
- var import_react_dom2 = require("react-dom");
4644
- var import_navigation = require("next/navigation");
4863
+ var import_react_dom3 = require("react-dom");
4864
+ var import_navigation2 = require("next/navigation");
4645
4865
 
4646
4866
  // src/lib/session-search.ts
4647
4867
  var STORAGE_KEY = "ohw-preserved-search";
@@ -4917,62 +5137,30 @@ function scrollToHashSectionWhenReady(behavior = "smooth") {
4917
5137
  tick();
4918
5138
  }
4919
5139
 
4920
- // src/ui/dialog.tsx
4921
- var React5 = __toESM(require("react"), 1);
4922
- var import_radix_ui4 = require("radix-ui");
4923
-
4924
- // src/ui/icons.tsx
4925
- var import_jsx_runtime10 = require("react/jsx-runtime");
4926
- function IconX({ className, ...props }) {
4927
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
4928
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d: "M18 6 6 18" }),
4929
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d: "m6 6 12 12" })
4930
- ] });
4931
- }
4932
- function IconChevronDown({ className, ...props }) {
4933
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d: "m6 9 6 6 6-6" }) });
4934
- }
4935
- function IconFile({ className, ...props }) {
4936
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
4937
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }),
4938
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d: "M14 2v4a2 2 0 0 0 2 2h4" })
4939
- ] });
4940
- }
4941
- function IconInfo({ className, ...props }) {
4942
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
4943
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
4944
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d: "M12 16v-4" }),
4945
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d: "M12 8h.01" })
4946
- ] });
4947
- }
4948
- function IconArrowRight({ className, ...props }) {
4949
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
4950
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d: "M5 12h14" }),
4951
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d: "m12 5 7 7-7 7" })
4952
- ] });
4953
- }
4954
- function IconSection({ className, ...props }) {
4955
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
4956
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("rect", { width: "18", height: "7", x: "3", y: "3", rx: "1" }),
4957
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("rect", { width: "9", height: "7", x: "3", y: "14", rx: "1" }),
4958
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("rect", { width: "5", height: "7", x: "16", y: "14", rx: "1" })
4959
- ] });
4960
- }
5140
+ // src/ui/link-modal/LinkPopover.tsx
5141
+ var import_react7 = require("react");
4961
5142
 
4962
5143
  // src/ui/dialog.tsx
4963
- var import_jsx_runtime11 = require("react/jsx-runtime");
4964
- function Dialog2({ ...props }) {
4965
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_radix_ui4.Dialog.Root, { "data-slot": "dialog", ...props });
5144
+ var React7 = __toESM(require("react"), 1);
5145
+ var import_radix_ui5 = require("radix-ui");
5146
+ var import_lucide_react4 = require("lucide-react");
5147
+ var import_jsx_runtime12 = require("react/jsx-runtime");
5148
+ function Dialog2({
5149
+ ...props
5150
+ }) {
5151
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_radix_ui5.Dialog.Root, { "data-slot": "dialog", ...props });
4966
5152
  }
4967
- function DialogPortal({ ...props }) {
4968
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_radix_ui4.Dialog.Portal, { ...props });
5153
+ function DialogPortal({
5154
+ ...props
5155
+ }) {
5156
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_radix_ui5.Dialog.Portal, { ...props });
4969
5157
  }
4970
5158
  function DialogOverlay({
4971
5159
  className,
4972
5160
  ...props
4973
5161
  }) {
4974
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
4975
- import_radix_ui4.Dialog.Overlay,
5162
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
5163
+ import_radix_ui5.Dialog.Overlay,
4976
5164
  {
4977
5165
  "data-slot": "dialog-overlay",
4978
5166
  "data-ohw-link-modal-root": "",
@@ -4981,120 +5169,118 @@ function DialogOverlay({
4981
5169
  }
4982
5170
  );
4983
5171
  }
4984
- var DialogContent = React5.forwardRef(({ className, children, showCloseButton = true, container, ...props }, ref) => {
4985
- const positionMode = container ? "absolute" : "fixed";
4986
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(DialogPortal, { container: container ?? void 0, children: [
4987
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(DialogOverlay, { className: cn(positionMode, "inset-0") }),
4988
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
4989
- import_radix_ui4.Dialog.Content,
4990
- {
4991
- ref,
4992
- "data-slot": "dialog-content",
4993
- className: cn(
4994
- positionMode,
4995
- "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",
4996
- "rounded-xl border border-border bg-background font-sans shadow-lg outline-none",
4997
- container ? "max-h-[calc(100%-32px)] max-w-[calc(100%-16px)]" : "max-h-[calc(100vh-32px)] max-w-[calc(100vw-16px)]",
4998
- className
4999
- ),
5000
- onMouseDown: (e) => e.stopPropagation(),
5001
- ...props,
5002
- children: [
5003
- children,
5004
- showCloseButton ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
5005
- import_radix_ui4.Dialog.Close,
5006
- {
5007
- type: "button",
5008
- className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
5009
- "aria-label": "Close",
5010
- children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(IconX, { "aria-hidden": true })
5011
- }
5012
- ) : null
5013
- ]
5014
- }
5015
- )
5016
- ] });
5017
- });
5018
- DialogContent.displayName = import_radix_ui4.Dialog.Content.displayName;
5019
- function DialogHeader({ className, ...props }) {
5020
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: cn("flex flex-col gap-1.5", className), ...props });
5172
+ var DialogContent = React7.forwardRef(
5173
+ ({ className, children, showCloseButton = true, container, ...props }, ref) => {
5174
+ const positionMode = container ? "absolute" : "fixed";
5175
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(DialogPortal, { container: container ?? void 0, children: [
5176
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(DialogOverlay, { className: cn(positionMode, "inset-0") }),
5177
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
5178
+ import_radix_ui5.Dialog.Content,
5179
+ {
5180
+ ref,
5181
+ "data-slot": "dialog-content",
5182
+ className: cn(
5183
+ positionMode,
5184
+ "left-1/2 top-1/2 z-[2147483647] flex w-full max-w-[483px] -translate-x-1/2 -translate-y-1/2 flex-col overflow-visible",
5185
+ "rounded-xl border border-border bg-background font-sans shadow-lg outline-none",
5186
+ container ? "max-h-[calc(100%-32px)] max-w-[calc(100%-16px)]" : "max-h-[calc(100vh-32px)] max-w-[calc(100vw-16px)]",
5187
+ className
5188
+ ),
5189
+ onMouseDown: (e) => e.stopPropagation(),
5190
+ ...props,
5191
+ children: [
5192
+ children,
5193
+ showCloseButton ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
5194
+ import_radix_ui5.Dialog.Close,
5195
+ {
5196
+ type: "button",
5197
+ className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
5198
+ "aria-label": "Close",
5199
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react4.X, { size: 16, "aria-hidden": true })
5200
+ }
5201
+ ) : null
5202
+ ]
5203
+ }
5204
+ )
5205
+ ] });
5206
+ }
5207
+ );
5208
+ DialogContent.displayName = import_radix_ui5.Dialog.Content.displayName;
5209
+ function DialogHeader({
5210
+ className,
5211
+ ...props
5212
+ }) {
5213
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: cn("flex flex-col gap-1.5", className), ...props });
5021
5214
  }
5022
- function DialogFooter({ className, ...props }) {
5023
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: cn("flex items-center justify-end gap-2", className), ...props });
5215
+ function DialogFooter({
5216
+ className,
5217
+ ...props
5218
+ }) {
5219
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
5220
+ "div",
5221
+ {
5222
+ className: cn("flex items-center justify-end gap-2", className),
5223
+ ...props
5224
+ }
5225
+ );
5024
5226
  }
5025
- var DialogTitle = React5.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
5026
- import_radix_ui4.Dialog.Title,
5227
+ var DialogTitle = React7.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
5228
+ import_radix_ui5.Dialog.Title,
5027
5229
  {
5028
5230
  ref,
5029
- className: cn("text-lg font-semibold leading-none tracking-tight text-card-foreground", className),
5231
+ className: cn(
5232
+ "text-lg font-semibold leading-none tracking-tight text-card-foreground",
5233
+ className
5234
+ ),
5030
5235
  ...props
5031
5236
  }
5032
5237
  ));
5033
- DialogTitle.displayName = import_radix_ui4.Dialog.Title.displayName;
5034
- var DialogDescription = React5.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
5035
- import_radix_ui4.Dialog.Description,
5238
+ DialogTitle.displayName = import_radix_ui5.Dialog.Title.displayName;
5239
+ var DialogDescription = React7.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
5240
+ import_radix_ui5.Dialog.Description,
5036
5241
  {
5037
5242
  ref,
5038
5243
  className: cn("text-sm text-muted-foreground", className),
5039
5244
  ...props
5040
5245
  }
5041
5246
  ));
5042
- DialogDescription.displayName = import_radix_ui4.Dialog.Description.displayName;
5043
- var DialogClose = import_radix_ui4.Dialog.Close;
5247
+ DialogDescription.displayName = import_radix_ui5.Dialog.Description.displayName;
5248
+ var DialogClose = import_radix_ui5.Dialog.Close;
5044
5249
 
5045
- // src/ui/button.tsx
5046
- var React6 = __toESM(require("react"), 1);
5047
- var import_radix_ui5 = require("radix-ui");
5048
- var import_jsx_runtime12 = require("react/jsx-runtime");
5049
- var buttonVariants = cva(
5050
- "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",
5051
- {
5052
- variants: {
5053
- variant: {
5054
- default: "bg-primary text-primary-foreground hover:opacity-90",
5055
- outline: "border border-border bg-background text-foreground shadow-sm hover:bg-muted/80",
5056
- ghost: "min-w-0 px-3 py-2 text-foreground hover:bg-muted/50"
5057
- },
5058
- size: {
5059
- default: "h-9",
5060
- sm: "h-8 min-w-[64px] px-2 py-1.5 text-sm"
5061
- }
5062
- },
5063
- defaultVariants: {
5064
- variant: "default",
5065
- size: "default"
5066
- }
5067
- }
5068
- );
5069
- var Button = React6.forwardRef(
5070
- ({ className, variant, size, asChild = false, ...props }, ref) => {
5071
- const Comp = asChild ? import_radix_ui5.Slot.Root : "button";
5072
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
5073
- Comp,
5074
- {
5075
- ref,
5076
- "data-slot": "button",
5077
- className: cn(buttonVariants({ variant, size, className })),
5078
- ...props
5079
- }
5080
- );
5081
- }
5082
- );
5083
- Button.displayName = "Button";
5250
+ // src/ui/link-modal/LinkEditorPanel.tsx
5251
+ var import_lucide_react8 = require("lucide-react");
5084
5252
 
5085
5253
  // src/ui/link-modal/DestinationBreadcrumb.tsx
5254
+ var import_lucide_react5 = require("lucide-react");
5086
5255
  var import_jsx_runtime13 = require("react/jsx-runtime");
5087
- function DestinationBreadcrumb({ pageTitle, sectionLabel }) {
5256
+ function DestinationBreadcrumb({
5257
+ pageTitle,
5258
+ sectionLabel
5259
+ }) {
5088
5260
  return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex w-full flex-col gap-2", children: [
5089
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { className: "text-sm font-medium text-foreground", children: "Destination" }),
5261
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { className: "text-sm font-medium! text-foreground m-0", children: "Destination" }),
5090
5262
  /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-center gap-3", children: [
5091
5263
  /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-center gap-2", children: [
5092
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(IconFile, { className: "shrink-0 text-foreground", "aria-hidden": true }),
5093
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "text-sm font-medium leading-none text-foreground", children: pageTitle })
5264
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react5.File, { size: 16, className: "shrink-0 text-foreground", "aria-hidden": true }),
5265
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "text-sm font-medium leading-none text-foreground!", children: pageTitle })
5094
5266
  ] }),
5095
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(IconArrowRight, { className: "shrink-0 text-muted-foreground", "aria-hidden": true }),
5267
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5268
+ import_lucide_react5.ArrowRight,
5269
+ {
5270
+ size: 16,
5271
+ className: "shrink-0 text-muted-foreground",
5272
+ "aria-hidden": true
5273
+ }
5274
+ ),
5096
5275
  /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [
5097
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(IconSection, { className: "shrink-0 text-foreground", "aria-hidden": true }),
5276
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5277
+ import_lucide_react5.GalleryVertical,
5278
+ {
5279
+ size: 16,
5280
+ className: "shrink-0 text-foreground",
5281
+ "aria-hidden": true
5282
+ }
5283
+ ),
5098
5284
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "truncate text-sm font-medium leading-none text-foreground", children: sectionLabel })
5099
5285
  ] })
5100
5286
  ] })
@@ -5102,8 +5288,13 @@ function DestinationBreadcrumb({ pageTitle, sectionLabel }) {
5102
5288
  }
5103
5289
 
5104
5290
  // src/ui/link-modal/SectionTreeItem.tsx
5291
+ var import_lucide_react6 = require("lucide-react");
5105
5292
  var import_jsx_runtime14 = require("react/jsx-runtime");
5106
- function SectionTreeItem({ section, onSelect, selected }) {
5293
+ function SectionTreeItem({
5294
+ section,
5295
+ onSelect,
5296
+ selected
5297
+ }) {
5107
5298
  const interactive = Boolean(onSelect);
5108
5299
  return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex h-9 w-full items-end pl-3", children: [
5109
5300
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
@@ -5131,30 +5322,28 @@ function SectionTreeItem({ section, onSelect, selected }) {
5131
5322
  interactive && selected && "border-primary"
5132
5323
  ),
5133
5324
  children: [
5134
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(IconSection, { className: "shrink-0 text-foreground", "aria-hidden": true }),
5325
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
5326
+ import_lucide_react6.GalleryVertical,
5327
+ {
5328
+ size: 16,
5329
+ className: "shrink-0 text-foreground",
5330
+ "aria-hidden": true
5331
+ }
5332
+ ),
5135
5333
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "truncate text-sm font-normal leading-5 text-foreground", children: section.label })
5136
5334
  ]
5137
5335
  }
5138
5336
  )
5139
5337
  ] });
5140
5338
  }
5141
- function SectionPickerList({ sections, onSelect }) {
5142
- if (sections.length === 0) {
5143
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { className: "text-sm text-muted-foreground", children: "No sections found on this page." });
5144
- }
5145
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex flex-col gap-1", children: [
5146
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { className: "text-sm text-muted-foreground", children: "Pick a section this link should scroll to." }),
5147
- sections.map((section) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(SectionTreeItem, { section, onSelect }, section.id))
5148
- ] });
5149
- }
5150
5339
 
5151
5340
  // src/ui/link-modal/UrlOrPageInput.tsx
5152
5341
  var import_react4 = require("react");
5153
5342
 
5154
5343
  // src/ui/input.tsx
5155
- var React7 = __toESM(require("react"), 1);
5344
+ var React8 = __toESM(require("react"), 1);
5156
5345
  var import_jsx_runtime15 = require("react/jsx-runtime");
5157
- var Input = React7.forwardRef(
5346
+ var Input = React8.forwardRef(
5158
5347
  ({ className, type, ...props }, ref) => {
5159
5348
  return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
5160
5349
  "input",
@@ -5188,8 +5377,11 @@ function Label({ className, ...props }) {
5188
5377
  }
5189
5378
 
5190
5379
  // src/ui/link-modal/UrlOrPageInput.tsx
5380
+ var import_lucide_react7 = require("lucide-react");
5191
5381
  var import_jsx_runtime17 = require("react/jsx-runtime");
5192
- function FieldChevron({ onClick }) {
5382
+ function FieldChevron({
5383
+ onClick
5384
+ }) {
5193
5385
  return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5194
5386
  "button",
5195
5387
  {
@@ -5198,7 +5390,7 @@ function FieldChevron({ onClick }) {
5198
5390
  onClick,
5199
5391
  "aria-label": "Open page list",
5200
5392
  tabIndex: -1,
5201
- children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(IconChevronDown, {})
5393
+ children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react7.ChevronDown, { size: 16 })
5202
5394
  }
5203
5395
  );
5204
5396
  }
@@ -5216,7 +5408,29 @@ function UrlOrPageInput({
5216
5408
  }) {
5217
5409
  const inputId = (0, import_react4.useId)();
5218
5410
  const inputRef = (0, import_react4.useRef)(null);
5411
+ const rootRef = (0, import_react4.useRef)(null);
5219
5412
  const [isFocused, setIsFocused] = (0, import_react4.useState)(false);
5413
+ (0, import_react4.useEffect)(() => {
5414
+ if (!dropdownOpen) return;
5415
+ const isOutside = (e) => {
5416
+ const root = rootRef.current;
5417
+ if (!root) return true;
5418
+ const path = typeof e.composedPath === "function" ? e.composedPath() : [];
5419
+ if (path.includes(root)) return false;
5420
+ const target = e.target;
5421
+ return !(target instanceof Node && root.contains(target));
5422
+ };
5423
+ const closeIfOutside = (e) => {
5424
+ if (!isOutside(e)) return;
5425
+ onDropdownOpenChange(false);
5426
+ };
5427
+ document.addEventListener("pointerdown", closeIfOutside, true);
5428
+ document.addEventListener("mousedown", closeIfOutside, true);
5429
+ return () => {
5430
+ document.removeEventListener("pointerdown", closeIfOutside, true);
5431
+ document.removeEventListener("mousedown", closeIfOutside, true);
5432
+ };
5433
+ }, [dropdownOpen, onDropdownOpenChange]);
5220
5434
  const openDropdown = () => {
5221
5435
  if (readOnly || filteredPages.length === 0) return;
5222
5436
  onDropdownOpenChange(true);
@@ -5239,9 +5453,16 @@ function UrlOrPageInput({
5239
5453
  );
5240
5454
  return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex w-full flex-col gap-2 p-0", children: [
5241
5455
  /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Label, { htmlFor: inputId, className: cn(urlError && "text-destructive"), children: "Destination" }),
5242
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "relative w-full", children: [
5456
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { ref: rootRef, className: "relative w-full", children: [
5243
5457
  /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { "data-ohw-link-field": true, className: fieldClassName, children: [
5244
- selectedPage ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "flex shrink-0 items-center pr-2", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(IconFile, { className: "text-foreground", "aria-hidden": true }) }) : null,
5458
+ selectedPage ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "flex shrink-0 items-center pr-2", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5459
+ import_lucide_react7.File,
5460
+ {
5461
+ size: 16,
5462
+ className: "shrink-0 text-foreground",
5463
+ "aria-hidden": true
5464
+ }
5465
+ ) }) : null,
5245
5466
  readOnly ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "min-w-0 flex-1 truncate text-sm leading-5 text-foreground", children: selectedPage?.title ?? value }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5246
5467
  Input,
5247
5468
  {
@@ -5253,7 +5474,14 @@ function UrlOrPageInput({
5253
5474
  setIsFocused(true);
5254
5475
  if (!selectedPage) openDropdown();
5255
5476
  },
5256
- onBlur: () => setIsFocused(false),
5477
+ onBlur: () => {
5478
+ setIsFocused(false);
5479
+ requestAnimationFrame(() => {
5480
+ if (!rootRef.current?.contains(document.activeElement)) {
5481
+ onDropdownOpenChange(false);
5482
+ }
5483
+ });
5484
+ },
5257
5485
  placeholder,
5258
5486
  className: cn(
5259
5487
  "min-w-0 flex-1 truncate border-0 bg-transparent p-0 text-sm font-normal leading-5 shadow-none outline-none ring-0 caret-foreground",
@@ -5269,7 +5497,7 @@ function UrlOrPageInput({
5269
5497
  onMouseDown: clearSelection,
5270
5498
  "aria-label": "Clear selected page",
5271
5499
  tabIndex: -1,
5272
- children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(IconX, { "aria-hidden": true })
5500
+ children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react7.X, { size: 16, "aria-hidden": true })
5273
5501
  }
5274
5502
  ) : null,
5275
5503
  !readOnly ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(FieldChevron, { onClick: toggleDropdown }) : null
@@ -5278,7 +5506,7 @@ function UrlOrPageInput({
5278
5506
  "div",
5279
5507
  {
5280
5508
  "data-ohw-link-page-dropdown": "",
5281
- className: "absolute left-0 right-0 h-20 top-[calc(100%+4px)] z-10 max-h-48 overflow-auto rounded-lg border border-border bg-popover py-1 shadow-lg",
5509
+ className: "absolute left-0 right-0 top-[calc(100%+4px)] z-50 max-h-48 overflow-auto rounded-lg border border-border bg-popover py-1 shadow-lg",
5282
5510
  onMouseDown: (e) => e.preventDefault(),
5283
5511
  children: filteredPages.map((page) => /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
5284
5512
  "button",
@@ -5287,7 +5515,7 @@ function UrlOrPageInput({
5287
5515
  className: "flex h-9 w-full items-center gap-2 border-0 bg-transparent px-3 text-left text-sm leading-5 text-foreground outline-none hover:bg-muted",
5288
5516
  onClick: () => onPageSelect(page),
5289
5517
  children: [
5290
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(IconFile, { className: "shrink-0", "aria-hidden": true }),
5518
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react7.File, { size: 16, className: "shrink-0", "aria-hidden": true }),
5291
5519
  /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "truncate", children: page.title })
5292
5520
  ]
5293
5521
  },
@@ -5300,58 +5528,395 @@ function UrlOrPageInput({
5300
5528
  ] });
5301
5529
  }
5302
5530
 
5303
- // src/ui/link-modal/useLinkModalState.ts
5304
- var import_react5 = require("react");
5305
- function useLinkModalState({
5306
- open,
5307
- mode,
5308
- pages,
5309
- sections: _sections,
5310
- sectionsByPath,
5311
- initialTarget,
5312
- existingTargets,
5313
- onClose,
5314
- onSubmit
5315
- }) {
5316
- const availablePages = (0, import_react5.useMemo)(
5317
- () => mode === "create" ? filterAvailablePages(pages, existingTargets) : pages,
5318
- [mode, pages, existingTargets]
5319
- );
5320
- const [searchValue, setSearchValue] = (0, import_react5.useState)("");
5321
- const [selectedPage, setSelectedPage] = (0, import_react5.useState)(null);
5322
- const [selectedSection, setSelectedSection] = (0, import_react5.useState)(null);
5323
- const [step, setStep] = (0, import_react5.useState)("input");
5324
- const [dropdownOpen, setDropdownOpen] = (0, import_react5.useState)(false);
5325
- const [urlError, setUrlError] = (0, import_react5.useState)("");
5326
- const reset = (0, import_react5.useCallback)(() => {
5327
- setSearchValue("");
5328
- setSelectedPage(null);
5329
- setSelectedSection(null);
5330
- setStep("input");
5331
- setDropdownOpen(false);
5332
- setUrlError("");
5333
- }, []);
5334
- (0, import_react5.useEffect)(() => {
5335
- if (!open) return;
5336
- if (mode === "edit" && initialTarget) {
5337
- const { pageRoute } = parseTarget(initialTarget);
5338
- const targetSections = getSectionsForPath(sectionsByPath, pageRoute);
5339
- const init = getEditModeInitialState(initialTarget, pages, targetSections);
5340
- setSearchValue(init.searchValue);
5341
- setSelectedPage(init.selectedPage);
5342
- setSelectedSection(init.selectedSection);
5343
- setStep(init.step);
5344
- } else {
5345
- reset();
5346
- }
5347
- setDropdownOpen(false);
5348
- setUrlError("");
5349
- }, [open, mode, initialTarget, pages, sectionsByPath, reset]);
5350
- const filteredPages = (0, import_react5.useMemo)(() => {
5351
- if (!searchValue.trim()) return availablePages;
5531
+ // src/ui/link-modal/LinkEditorPanel.tsx
5532
+ var import_jsx_runtime18 = require("react/jsx-runtime");
5533
+ function LinkEditorPanel({ state, onClose }) {
5534
+ const isCancel = state.secondaryLabel === "Cancel" || state.secondaryLabel === "Back to sections";
5535
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [
5536
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(DialogClose, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5537
+ "button",
5538
+ {
5539
+ type: "button",
5540
+ className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50 h-7",
5541
+ "aria-label": "Close",
5542
+ onClick: onClose,
5543
+ children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_lucide_react8.X, { size: 16, "aria-hidden": true })
5544
+ }
5545
+ ) }),
5546
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(DialogHeader, { className: "w-full gap-1.5 p-6 pr-12", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(DialogTitle, { className: "m-0 w-full break-words", children: state.title }) }),
5547
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex w-full flex-col gap-3 px-6 pb-8 pt-1", children: [
5548
+ state.showBreadcrumb && state.selectedPage && state.selectedSection ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5549
+ DestinationBreadcrumb,
5550
+ {
5551
+ pageTitle: state.selectedPage.title,
5552
+ sectionLabel: state.selectedSection.label
5553
+ }
5554
+ ) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5555
+ UrlOrPageInput,
5556
+ {
5557
+ value: state.searchValue,
5558
+ selectedPage: state.selectedPage,
5559
+ dropdownOpen: state.dropdownOpen,
5560
+ onDropdownOpenChange: state.setDropdownOpen,
5561
+ filteredPages: state.filteredPages,
5562
+ onInputChange: state.handleInputChange,
5563
+ onPageSelect: state.handlePageSelect,
5564
+ urlError: state.urlError
5565
+ }
5566
+ ),
5567
+ state.showChooseSection ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex flex-col justify-center gap-2", children: [
5568
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5569
+ Button,
5570
+ {
5571
+ type: "button",
5572
+ variant: "outline",
5573
+ className: "w-fit cursor-pointer",
5574
+ size: "sm",
5575
+ onClick: state.handleChooseSection,
5576
+ children: "Choose a section"
5577
+ }
5578
+ ),
5579
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex items-center gap-1 text-sm text-muted-foreground", children: [
5580
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_lucide_react8.Info, { size: 16, className: "shrink-0", "aria-hidden": true }),
5581
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { children: "Pick a section this link should scroll to." })
5582
+ ] })
5583
+ ] }) : null,
5584
+ state.showSectionRow && state.selectedSection ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(SectionTreeItem, { section: state.selectedSection, selected: true }) : null
5585
+ ] }),
5586
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(DialogFooter, { className: "w-full px-6 pb-6", children: [
5587
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5588
+ Button,
5589
+ {
5590
+ type: "button",
5591
+ variant: isCancel ? "outline" : "ghost",
5592
+ className: "leading-6 shadow-none cursor-pointer",
5593
+ style: isCancel ? {
5594
+ backgroundColor: "var(--ohw-background)",
5595
+ borderColor: "var(--ohw-border)",
5596
+ color: "var(--ohw-foreground)"
5597
+ } : void 0,
5598
+ onClick: state.handleSecondary,
5599
+ children: state.secondaryLabel
5600
+ }
5601
+ ),
5602
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5603
+ Button,
5604
+ {
5605
+ type: "button",
5606
+ className: "border-0 leading-6 shadow-none hover:opacity-90 disabled:opacity-50 cursor-pointer",
5607
+ style: {
5608
+ backgroundColor: "var(--ohw-primary)",
5609
+ color: "var(--ohw-primary-foreground)"
5610
+ },
5611
+ disabled: !state.isValid,
5612
+ onClick: state.handleSubmit,
5613
+ children: state.submitLabel
5614
+ }
5615
+ )
5616
+ ] })
5617
+ ] });
5618
+ }
5619
+
5620
+ // src/ui/link-modal/SectionPickerOverlay.tsx
5621
+ var import_react5 = require("react");
5622
+ var import_react_dom = require("react-dom");
5623
+ var import_lucide_react9 = require("lucide-react");
5624
+ var import_navigation = require("next/navigation");
5625
+ var import_jsx_runtime19 = require("react/jsx-runtime");
5626
+ var DIM_OVERLAY = "rgba(0, 0, 0, 0.45)";
5627
+ function rectsEqual(a, b) {
5628
+ if (a.size !== b.size) return false;
5629
+ for (const [id, rect] of a) {
5630
+ const other = b.get(id);
5631
+ if (!other || rect.top !== other.top || rect.left !== other.left || rect.width !== other.width || rect.height !== other.height) {
5632
+ return false;
5633
+ }
5634
+ }
5635
+ return true;
5636
+ }
5637
+ function useSectionRects(sectionIdsKey, enabled) {
5638
+ const [rects, setRects] = (0, import_react5.useState)(/* @__PURE__ */ new Map());
5639
+ const sectionIds = (0, import_react5.useMemo)(
5640
+ () => sectionIdsKey ? sectionIdsKey.split("\0") : [],
5641
+ [sectionIdsKey]
5642
+ );
5643
+ (0, import_react5.useEffect)(() => {
5644
+ if (!enabled || sectionIds.length === 0) {
5645
+ setRects((prev) => prev.size === 0 ? prev : /* @__PURE__ */ new Map());
5646
+ return;
5647
+ }
5648
+ const update = () => {
5649
+ const next = /* @__PURE__ */ new Map();
5650
+ for (const id of sectionIds) {
5651
+ const el = document.querySelector(
5652
+ `[data-ohw-section="${CSS.escape(id)}"]`
5653
+ );
5654
+ if (el) next.set(id, el.getBoundingClientRect());
5655
+ }
5656
+ setRects((prev) => rectsEqual(prev, next) ? prev : next);
5657
+ };
5658
+ update();
5659
+ const opts = { capture: true, passive: true };
5660
+ window.addEventListener("scroll", update, opts);
5661
+ window.addEventListener("resize", update);
5662
+ const vv = window.visualViewport;
5663
+ vv?.addEventListener("resize", update);
5664
+ vv?.addEventListener("scroll", update);
5665
+ const ro = new ResizeObserver(update);
5666
+ for (const id of sectionIds) {
5667
+ const el = document.querySelector(
5668
+ `[data-ohw-section="${CSS.escape(id)}"]`
5669
+ );
5670
+ if (el) ro.observe(el);
5671
+ }
5672
+ return () => {
5673
+ window.removeEventListener("scroll", update, opts);
5674
+ window.removeEventListener("resize", update);
5675
+ vv?.removeEventListener("resize", update);
5676
+ vv?.removeEventListener("scroll", update);
5677
+ ro.disconnect();
5678
+ };
5679
+ }, [sectionIds, enabled]);
5680
+ return rects;
5681
+ }
5682
+ function SectionPickerOverlay({
5683
+ pagePath,
5684
+ sections,
5685
+ onBack,
5686
+ onSelect
5687
+ }) {
5688
+ const router = (0, import_navigation.useRouter)();
5689
+ const pathname = (0, import_navigation.usePathname)();
5690
+ const [selectedId, setSelectedId] = (0, import_react5.useState)(null);
5691
+ const [hoveredId, setHoveredId] = (0, import_react5.useState)(null);
5692
+ const [chromeClip, setChromeClip] = (0, import_react5.useState)(
5693
+ () => ({
5694
+ top: 0,
5695
+ bottom: typeof window !== "undefined" ? window.innerHeight : 0
5696
+ })
5697
+ );
5698
+ const normalizedTarget = normalizePath(pagePath);
5699
+ const isOnTargetPage = normalizePath(pathname) === normalizedTarget;
5700
+ (0, import_react5.useEffect)(() => {
5701
+ if (isOnTargetPage) return;
5702
+ const search = readPreservedSearch();
5703
+ router.push(`${normalizedTarget}${search}`);
5704
+ }, [isOnTargetPage, normalizedTarget, router]);
5705
+ (0, import_react5.useEffect)(() => {
5706
+ document.documentElement.setAttribute("data-ohw-section-picking", "");
5707
+ document.querySelectorAll("[data-ohw-hovered]").forEach((el) => {
5708
+ el.removeAttribute("data-ohw-hovered");
5709
+ });
5710
+ return () => {
5711
+ document.documentElement.removeAttribute("data-ohw-section-picking");
5712
+ };
5713
+ }, []);
5714
+ (0, import_react5.useEffect)(() => {
5715
+ const applyClip = (iframeOffsetTop, visibleCanvasTop, canvasH) => {
5716
+ const top = Math.max(0, visibleCanvasTop - iframeOffsetTop);
5717
+ const bottom = Math.min(
5718
+ window.innerHeight,
5719
+ visibleCanvasTop + canvasH - iframeOffsetTop
5720
+ );
5721
+ setChromeClip(
5722
+ (prev) => prev.top === top && prev.bottom === bottom ? prev : { top, bottom: Math.max(top, bottom) }
5723
+ );
5724
+ };
5725
+ const onParentScroll = (e) => {
5726
+ if (e.data?.type !== "ow:parent-scroll") return;
5727
+ const { iframeOffsetTop, headerH, canvasH } = e.data;
5728
+ applyClip(iframeOffsetTop, headerH, canvasH);
5729
+ };
5730
+ const onResize = () => {
5731
+ setChromeClip((prev) => {
5732
+ const bottom = Math.max(prev.top, window.innerHeight);
5733
+ return prev.bottom === bottom ? prev : { ...prev, bottom };
5734
+ });
5735
+ };
5736
+ window.addEventListener("message", onParentScroll);
5737
+ window.addEventListener("resize", onResize);
5738
+ return () => {
5739
+ window.removeEventListener("message", onParentScroll);
5740
+ window.removeEventListener("resize", onResize);
5741
+ };
5742
+ }, []);
5743
+ const liveSections = (0, import_react5.useMemo)(() => {
5744
+ if (!isOnTargetPage) return sections;
5745
+ const live = collectSectionsFromDom();
5746
+ if (live.length === 0) return sections;
5747
+ const labels = new Map(sections.map((s) => [s.id, s.label]));
5748
+ return live.map((s) => ({ id: s.id, label: labels.get(s.id) ?? s.label }));
5749
+ }, [isOnTargetPage, sections, pathname]);
5750
+ const sectionIdsKey = (0, import_react5.useMemo)(
5751
+ () => liveSections.map((s) => s.id).join("\0"),
5752
+ [liveSections]
5753
+ );
5754
+ const rects = useSectionRects(sectionIdsKey, isOnTargetPage);
5755
+ const handleSelect = (0, import_react5.useCallback)(
5756
+ (section) => {
5757
+ if (selectedId) return;
5758
+ setSelectedId(section.id);
5759
+ onSelect(section);
5760
+ },
5761
+ [selectedId, onSelect]
5762
+ );
5763
+ (0, import_react5.useEffect)(() => {
5764
+ const onKeyDown = (e) => {
5765
+ if (e.key === "Escape") {
5766
+ e.preventDefault();
5767
+ e.stopPropagation();
5768
+ onBack();
5769
+ }
5770
+ };
5771
+ window.addEventListener("keydown", onKeyDown, true);
5772
+ return () => window.removeEventListener("keydown", onKeyDown, true);
5773
+ }, [onBack]);
5774
+ const portalRoot = typeof document !== "undefined" ? document.querySelector("[data-ohw-bridge-root]") ?? document.body : null;
5775
+ if (!portalRoot) return null;
5776
+ return (0, import_react_dom.createPortal)(
5777
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
5778
+ "div",
5779
+ {
5780
+ "data-ohw-section-picker": "",
5781
+ "data-ohw-bridge": "",
5782
+ className: `pointer-events-none fixed inset-0 z-[2147483647] transition-opacity duration-200 ${"opacity-100"}`,
5783
+ "aria-modal": true,
5784
+ role: "dialog",
5785
+ "aria-label": "Choose a section",
5786
+ children: [
5787
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5788
+ "div",
5789
+ {
5790
+ className: "pointer-events-auto fixed left-5 z-[2]",
5791
+ style: { top: chromeClip.top + 20 },
5792
+ children: /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
5793
+ Button,
5794
+ {
5795
+ type: "button",
5796
+ variant: "outline",
5797
+ size: "sm",
5798
+ className: "h-8 min-w-0 gap-1 border-border bg-background px-2 py-1.5 shadow-sm hover:bg-muted cursor-pointer",
5799
+ onClick: onBack,
5800
+ children: [
5801
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react9.ArrowLeft, { className: "size-4 shrink-0", "aria-hidden": true }),
5802
+ "Back"
5803
+ ]
5804
+ }
5805
+ )
5806
+ }
5807
+ ),
5808
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5809
+ "div",
5810
+ {
5811
+ className: "pointer-events-none fixed left-1/2 z-[2] rounded-lg px-4 py-3 text-xs leading-4 tracking-[0.18px] text-white shadow-md",
5812
+ style: {
5813
+ top: chromeClip.bottom - 20,
5814
+ transform: "translate(-50%, -100%)",
5815
+ backgroundColor: "var(--ohw-popover-foreground, #0c0a09)"
5816
+ },
5817
+ "data-ohw-section-picker-hint": "",
5818
+ children: "Click on section to select"
5819
+ }
5820
+ ),
5821
+ !isOnTargetPage ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5822
+ "div",
5823
+ {
5824
+ className: "pointer-events-none fixed left-1/2 z-[1] -translate-x-1/2 rounded-md bg-background/90 px-3 py-2 text-sm text-muted-foreground shadow-sm",
5825
+ style: { top: chromeClip.top + 64 },
5826
+ children: "Loading page preview\u2026"
5827
+ }
5828
+ ) : null,
5829
+ isOnTargetPage && liveSections.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "pointer-events-auto fixed inset-0 z-[1] flex items-center justify-center bg-muted/40", children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("p", { className: "text-sm text-muted-foreground", children: "No sections found on this page." }) }) : null,
5830
+ isOnTargetPage ? liveSections.map((section) => {
5831
+ const rect = rects.get(section.id);
5832
+ if (!rect || rect.width <= 0 || rect.height <= 0) return null;
5833
+ const isSelected = selectedId === section.id;
5834
+ const isLit = isSelected || hoveredId === section.id;
5835
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5836
+ "button",
5837
+ {
5838
+ type: "button",
5839
+ className: "pointer-events-auto fixed z-[1] cursor-pointer border-0 bg-transparent p-0 transition-[background-color] duration-150",
5840
+ style: {
5841
+ top: rect.top,
5842
+ left: rect.left,
5843
+ width: rect.width,
5844
+ height: rect.height,
5845
+ backgroundColor: isLit ? "transparent" : DIM_OVERLAY
5846
+ },
5847
+ "aria-label": `Select section ${section.label}`,
5848
+ onMouseEnter: () => setHoveredId(section.id),
5849
+ onMouseLeave: () => setHoveredId((prev) => prev === section.id ? null : prev),
5850
+ onClick: () => handleSelect(section),
5851
+ children: isSelected ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5852
+ "span",
5853
+ {
5854
+ className: "absolute right-3 top-3 flex size-8 items-center justify-center rounded-full text-white",
5855
+ style: { backgroundColor: "var(--ohw-primary, #0885fe)" },
5856
+ "aria-hidden": true,
5857
+ children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react9.Check, { className: "size-5" })
5858
+ }
5859
+ ) : null
5860
+ },
5861
+ section.id
5862
+ );
5863
+ }) : null
5864
+ ]
5865
+ }
5866
+ ),
5867
+ portalRoot
5868
+ );
5869
+ }
5870
+
5871
+ // src/ui/link-modal/useLinkModalState.ts
5872
+ var import_react6 = require("react");
5873
+ function useLinkModalState({
5874
+ open,
5875
+ mode,
5876
+ pages,
5877
+ sections: _sections,
5878
+ sectionsByPath,
5879
+ initialTarget,
5880
+ existingTargets: _existingTargets,
5881
+ onClose,
5882
+ onSubmit
5883
+ }) {
5884
+ const availablePages = (0, import_react6.useMemo)(() => pages, [pages]);
5885
+ const [searchValue, setSearchValue] = (0, import_react6.useState)("");
5886
+ const [selectedPage, setSelectedPage] = (0, import_react6.useState)(null);
5887
+ const [selectedSection, setSelectedSection] = (0, import_react6.useState)(null);
5888
+ const [step, setStep] = (0, import_react6.useState)("input");
5889
+ const [dropdownOpen, setDropdownOpen] = (0, import_react6.useState)(false);
5890
+ const [urlError, setUrlError] = (0, import_react6.useState)("");
5891
+ const reset = (0, import_react6.useCallback)(() => {
5892
+ setSearchValue("");
5893
+ setSelectedPage(null);
5894
+ setSelectedSection(null);
5895
+ setStep("input");
5896
+ setDropdownOpen(false);
5897
+ setUrlError("");
5898
+ }, []);
5899
+ (0, import_react6.useEffect)(() => {
5900
+ if (!open) return;
5901
+ if (mode === "edit" && initialTarget) {
5902
+ const { pageRoute } = parseTarget(initialTarget);
5903
+ const targetSections = getSectionsForPath(sectionsByPath, pageRoute);
5904
+ const init = getEditModeInitialState(initialTarget, pages, targetSections);
5905
+ setSearchValue(init.searchValue);
5906
+ setSelectedPage(init.selectedPage);
5907
+ setSelectedSection(init.selectedSection);
5908
+ setStep(init.step);
5909
+ } else {
5910
+ reset();
5911
+ }
5912
+ setDropdownOpen(false);
5913
+ setUrlError("");
5914
+ }, [open, mode, initialTarget, reset]);
5915
+ const filteredPages = (0, import_react6.useMemo)(() => {
5916
+ if (!searchValue.trim()) return availablePages;
5352
5917
  return availablePages.filter((p) => p.title.toLowerCase().startsWith(searchValue.toLowerCase()));
5353
5918
  }, [availablePages, searchValue]);
5354
- const activeSections = (0, import_react5.useMemo)(() => {
5919
+ const activeSections = (0, import_react6.useMemo)(() => {
5355
5920
  if (!selectedPage) return [];
5356
5921
  return getSectionsForPath(sectionsByPath, selectedPage.path);
5357
5922
  }, [selectedPage, sectionsByPath]);
@@ -5393,11 +5958,14 @@ function useLinkModalState({
5393
5958
  const handleBackToSections = () => {
5394
5959
  setStep("sectionPicker");
5395
5960
  };
5961
+ const handleSectionPickerBack = () => {
5962
+ setStep("input");
5963
+ };
5396
5964
  const handleClose = () => {
5397
5965
  reset();
5398
5966
  onClose();
5399
5967
  };
5400
- const isValid = (0, import_react5.useMemo)(() => {
5968
+ const isValid = (0, import_react6.useMemo)(() => {
5401
5969
  if (urlError) return false;
5402
5970
  if (showBreadcrumb) return true;
5403
5971
  if (selectedPage) return true;
@@ -5430,7 +5998,7 @@ function useLinkModalState({
5430
5998
  onSubmit(normalizeUrl(searchValue));
5431
5999
  handleClose();
5432
6000
  };
5433
- const submitLabel = mode === "edit" ? "Save" : "Create";
6001
+ const submitLabel = showBreadcrumb ? "Save" : mode === "edit" ? "Save" : "Create";
5434
6002
  const title = mode === "edit" ? "Edit navigation item" : "Create navigation item";
5435
6003
  const secondaryLabel = showBreadcrumb ? "Back to sections" : "Cancel";
5436
6004
  const handleSecondary = showBreadcrumb ? handleBackToSections : handleClose;
@@ -5457,23 +6025,29 @@ function useLinkModalState({
5457
6025
  handleChooseSection,
5458
6026
  handleSectionSelect,
5459
6027
  handleBackToSections,
6028
+ handleSectionPickerBack,
5460
6029
  handleClose,
5461
6030
  handleSecondary,
5462
6031
  handleSubmit
5463
6032
  };
5464
6033
  }
5465
6034
 
5466
- // src/ui/link-modal/LinkEditorPanel.tsx
5467
- var import_jsx_runtime18 = require("react/jsx-runtime");
5468
- function LinkEditorPanel({
6035
+ // src/ui/link-modal/LinkPopover.tsx
6036
+ var import_jsx_runtime20 = require("react/jsx-runtime");
6037
+ function postToParent(data) {
6038
+ window.parent?.postMessage(data, "*");
6039
+ }
6040
+ function LinkPopover({
5469
6041
  open = true,
6042
+ panelRef,
6043
+ portalContainer,
6044
+ onClose,
5470
6045
  mode = "create",
5471
6046
  pages,
5472
6047
  sections = [],
5473
6048
  sectionsByPath,
5474
6049
  initialTarget,
5475
6050
  existingTargets = [],
5476
- onClose,
5477
6051
  onSubmit
5478
6052
  }) {
5479
6053
  const state = useLinkModalState({
@@ -5487,175 +6061,440 @@ function LinkEditorPanel({
5487
6061
  onClose,
5488
6062
  onSubmit
5489
6063
  });
5490
- const isCancel = state.secondaryLabel === "Cancel";
5491
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [
5492
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(DialogClose, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5493
- "button",
6064
+ const sectionPickerActive = state.showSectionPicker && Boolean(state.selectedPage);
6065
+ (0, import_react7.useEffect)(() => {
6066
+ if (!open) return;
6067
+ if (sectionPickerActive) {
6068
+ postToParent({ type: "ow:link-modal-lock", locked: false });
6069
+ postToParent({ type: "ow:section-picker", active: true });
6070
+ document.documentElement.style.overflow = "";
6071
+ document.body.style.overflow = "";
6072
+ return () => {
6073
+ postToParent({ type: "ow:section-picker", active: false });
6074
+ };
6075
+ }
6076
+ postToParent({ type: "ow:section-picker", active: false });
6077
+ postToParent({ type: "ow:link-modal-lock", locked: true });
6078
+ const html = document.documentElement;
6079
+ const body = document.body;
6080
+ const prevHtmlOverflow = html.style.overflow;
6081
+ const prevBodyOverflow = body.style.overflow;
6082
+ const prevHtmlOverscroll = html.style.overscrollBehavior;
6083
+ const prevBodyOverscroll = body.style.overscrollBehavior;
6084
+ html.style.overflow = "hidden";
6085
+ body.style.overflow = "hidden";
6086
+ html.style.overscrollBehavior = "none";
6087
+ body.style.overscrollBehavior = "none";
6088
+ const canScrollElement = (el, deltaY) => {
6089
+ const { overflowY } = getComputedStyle(el);
6090
+ if (overflowY !== "auto" && overflowY !== "scroll") return false;
6091
+ if (el.scrollHeight <= el.clientHeight + 1) return false;
6092
+ if (deltaY < 0) return el.scrollTop > 0;
6093
+ if (deltaY > 0) return el.scrollTop + el.clientHeight < el.scrollHeight - 1;
6094
+ return true;
6095
+ };
6096
+ const allowsInternalScroll = (e) => {
6097
+ const target = e.target;
6098
+ if (!(target instanceof Element)) return false;
6099
+ const scrollRoot = target.closest(
6100
+ "[data-ohw-link-page-dropdown], [data-ohw-allow-scroll]"
6101
+ );
6102
+ if (!scrollRoot) return false;
6103
+ const deltaY = e instanceof WheelEvent ? e.deltaY : 0;
6104
+ return canScrollElement(scrollRoot, deltaY);
6105
+ };
6106
+ const preventBackgroundScroll = (e) => {
6107
+ if (allowsInternalScroll(e)) return;
6108
+ e.preventDefault();
6109
+ };
6110
+ const scrollOpts = { passive: false, capture: true };
6111
+ document.addEventListener("wheel", preventBackgroundScroll, scrollOpts);
6112
+ document.addEventListener("touchmove", preventBackgroundScroll, scrollOpts);
6113
+ return () => {
6114
+ postToParent({ type: "ow:link-modal-lock", locked: false });
6115
+ html.style.overflow = prevHtmlOverflow;
6116
+ body.style.overflow = prevBodyOverflow;
6117
+ html.style.overscrollBehavior = prevHtmlOverscroll;
6118
+ body.style.overscrollBehavior = prevBodyOverscroll;
6119
+ document.removeEventListener("wheel", preventBackgroundScroll, scrollOpts);
6120
+ document.removeEventListener("touchmove", preventBackgroundScroll, scrollOpts);
6121
+ };
6122
+ }, [open, sectionPickerActive]);
6123
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_jsx_runtime20.Fragment, { children: [
6124
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
6125
+ Dialog2,
5494
6126
  {
5495
- type: "button",
5496
- className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
5497
- "aria-label": "Close",
5498
- children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(IconX, { "aria-hidden": true })
6127
+ open: open && !sectionPickerActive,
6128
+ onOpenChange: (next) => {
6129
+ if (!next) onClose?.();
6130
+ },
6131
+ children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
6132
+ DialogContent,
6133
+ {
6134
+ ref: panelRef,
6135
+ container: portalContainer,
6136
+ "data-ohw-link-popover-root": "",
6137
+ "data-ohw-link-modal-root": "",
6138
+ "data-ohw-bridge": "",
6139
+ showCloseButton: false,
6140
+ className: "gap-0 p-0 w-full md:w-md pointer-events-auto z-[2147483646] overflow-visible",
6141
+ children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(LinkEditorPanel, { state, onClose })
6142
+ }
6143
+ )
5499
6144
  }
5500
- ) }),
5501
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(DialogHeader, { className: "w-full gap-1.5 p-6 pr-12", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(DialogTitle, { className: "m-0 w-full break-words", children: state.title }) }),
5502
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex w-full flex-col gap-3 px-6 pb-8 pt-1", children: [
5503
- state.showBreadcrumb && state.selectedPage && state.selectedSection ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5504
- DestinationBreadcrumb,
5505
- {
5506
- pageTitle: state.selectedPage.title,
5507
- sectionLabel: state.selectedSection.label
5508
- }
5509
- ) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5510
- UrlOrPageInput,
5511
- {
5512
- value: state.searchValue,
5513
- selectedPage: state.selectedPage,
5514
- dropdownOpen: state.dropdownOpen,
5515
- onDropdownOpenChange: state.setDropdownOpen,
5516
- filteredPages: state.filteredPages,
5517
- onInputChange: state.handleInputChange,
5518
- onPageSelect: state.handlePageSelect,
5519
- urlError: state.urlError
5520
- }
5521
- ),
5522
- state.showChooseSection ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex flex-col justify-center gap-2", children: [
5523
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Button, { type: "button", variant: "outline", className: "w-fit cursor-pointer", size: "sm", onClick: state.handleChooseSection, children: "Choose a section" }),
5524
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex items-center gap-1 text-sm text-muted-foreground", children: [
5525
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(IconInfo, { className: "shrink-0", "aria-hidden": true }),
5526
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { children: "Pick a section this link should scroll to." })
5527
- ] })
5528
- ] }) : null,
5529
- state.showSectionPicker ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(SectionPickerList, { sections: state.activeSections, onSelect: state.handleSectionSelect }) : null,
5530
- state.showSectionRow && state.selectedSection ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(SectionTreeItem, { section: state.selectedSection, selected: true }) : null
5531
- ] }),
5532
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(DialogFooter, { className: "w-full px-6 pb-6", children: [
5533
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5534
- Button,
5535
- {
5536
- type: "button",
5537
- variant: isCancel ? "outline" : "ghost",
5538
- className: "leading-6 shadow-none cursor-pointer",
5539
- style: isCancel ? {
5540
- backgroundColor: "var(--ohw-background)",
5541
- borderColor: "var(--ohw-border)",
5542
- color: "var(--ohw-foreground)"
5543
- } : void 0,
5544
- onClick: state.handleSecondary,
5545
- children: state.secondaryLabel
5546
- }
5547
- ),
5548
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5549
- Button,
5550
- {
5551
- type: "button",
5552
- className: "border-0 leading-6 shadow-none hover:opacity-90 disabled:opacity-50 cursor-pointer",
5553
- style: {
5554
- backgroundColor: "var(--ohw-primary)",
5555
- color: "var(--ohw-primary-foreground)"
5556
- },
5557
- disabled: !state.isValid,
5558
- onClick: state.handleSubmit,
5559
- children: state.submitLabel
5560
- }
5561
- )
5562
- ] })
6145
+ ),
6146
+ sectionPickerActive && state.selectedPage ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
6147
+ SectionPickerOverlay,
6148
+ {
6149
+ pagePath: state.selectedPage.path,
6150
+ sections: state.activeSections,
6151
+ onBack: state.handleSectionPickerBack,
6152
+ onSelect: state.handleSectionSelect
6153
+ }
6154
+ ) : null
5563
6155
  ] });
5564
6156
  }
5565
6157
 
5566
- // src/ui/link-modal/LinkPopover.tsx
5567
- var import_jsx_runtime19 = require("react/jsx-runtime");
5568
- function LinkPopover({
5569
- open = true,
5570
- panelRef,
5571
- portalContainer,
5572
- onClose,
5573
- ...editorProps
6158
+ // src/ui/link-modal/devFixtures.ts
6159
+ var DEV_SITE_PAGES = [
6160
+ { path: "/", title: "Home" },
6161
+ { path: "/about", title: "About" },
6162
+ { path: "/classes", title: "Classes" },
6163
+ { path: "/pricing", title: "Pricing" },
6164
+ { path: "/studio", title: "Studio" },
6165
+ { path: "/instructors", title: "Instructors" },
6166
+ { path: "/contact", title: "Contact" }
6167
+ ];
6168
+ var DEV_SECTIONS_BY_PATH = {
6169
+ "/": [
6170
+ { id: "hero", label: "Hero" },
6171
+ { id: "lagree-intro", label: "Lagree Intro" },
6172
+ { id: "classes-strip", label: "Classes" },
6173
+ { id: "feel-split", label: "Feel Split" },
6174
+ { id: "founder-teaser", label: "Founder" },
6175
+ { id: "plan-form", label: "Plan Form" },
6176
+ { id: "testimonials", label: "Testimonials" },
6177
+ { id: "wordmark", label: "Wordmark" },
6178
+ { id: "footer", label: "Footer" }
6179
+ ],
6180
+ "/about": [
6181
+ { id: "manifesto", label: "Manifesto" },
6182
+ { id: "story-letter", label: "Our Story" },
6183
+ { id: "lagree-explainer", label: "Lagree Explainer" },
6184
+ { id: "waitlist-cta", label: "Waitlist" },
6185
+ { id: "personal-training", label: "Personal training" },
6186
+ { id: "footer", label: "Footer" }
6187
+ ],
6188
+ "/classes": [
6189
+ { id: "class-library", label: "Class Library" },
6190
+ { id: "footer", label: "Footer" }
6191
+ ],
6192
+ "/pricing": [
6193
+ { id: "page-header", label: "Page Header" },
6194
+ { id: "free-class-cta", label: "Free Class" },
6195
+ { id: "benefits-marquee", label: "Benefits" },
6196
+ { id: "monthly-memberships", label: "Memberships" },
6197
+ { id: "specialty-programs", label: "Specialty Programs" },
6198
+ { id: "package-matcher", label: "Packages" },
6199
+ { id: "wordmark", label: "Wordmark" },
6200
+ { id: "footer", label: "Footer" }
6201
+ ],
6202
+ "/studio": [
6203
+ { id: "studio-intro", label: "Studio Intro" },
6204
+ { id: "studio-features", label: "Studio Features" },
6205
+ { id: "studio-gallery", label: "Studio Gallery" },
6206
+ { id: "studio-visit", label: "Visit Studio" },
6207
+ { id: "footer", label: "Footer" }
6208
+ ],
6209
+ "/instructors": [
6210
+ { id: "instructor-grid", label: "Instructors" },
6211
+ { id: "footer", label: "Footer" }
6212
+ ],
6213
+ "/contact": [
6214
+ { id: "hello-hero", label: "Hello" },
6215
+ { id: "contact-form", label: "Contact Form" },
6216
+ { id: "faq", label: "FAQ" },
6217
+ { id: "footer", label: "Footer" }
6218
+ ]
6219
+ };
6220
+ function shouldUseDevFixtures() {
6221
+ if (typeof window === "undefined") return false;
6222
+ const raw = readPreservedSearch();
6223
+ const q = raw.startsWith("?") ? raw.slice(1) : raw;
6224
+ return new URLSearchParams(q).get("ohw-fixtures") === "1";
6225
+ }
6226
+
6227
+ // src/lib/nav-items.ts
6228
+ var NAV_COUNT_KEY = "__ohw_nav_count";
6229
+ var NAV_ORDER_KEY = "__ohw_nav_order";
6230
+ function getLinkHref(el) {
6231
+ const key = el.getAttribute("data-ohw-href-key");
6232
+ if (key) {
6233
+ const stored = getStoredLinkHref(key);
6234
+ if (stored !== void 0) return stored;
6235
+ }
6236
+ return el.getAttribute("href") ?? "";
6237
+ }
6238
+ function isNavbarButton(el) {
6239
+ return el.hasAttribute("data-ohw-role") && el.getAttribute("data-ohw-role") === "navbar-button";
6240
+ }
6241
+ function isNavbarLinkItem(el) {
6242
+ if (!el.matches("[data-ohw-href-key]")) return false;
6243
+ if (isNavbarButton(el)) return false;
6244
+ if (!el.querySelector('[data-ohw-editable="text"]')) return false;
6245
+ return Boolean(el.closest("nav, [data-ohw-nav-container], [data-ohw-nav-drawer]"));
6246
+ }
6247
+ function getNavbarDesktopContainer() {
6248
+ return document.querySelector("[data-ohw-nav-container]");
6249
+ }
6250
+ function getNavbarDrawerContainer() {
6251
+ return document.querySelector("[data-ohw-nav-drawer]");
6252
+ }
6253
+ function listNavbarItems() {
6254
+ const desktop = getNavbarDesktopContainer();
6255
+ if (desktop) {
6256
+ return Array.from(desktop.querySelectorAll("[data-ohw-href-key]")).filter(isNavbarLinkItem);
6257
+ }
6258
+ const drawer = getNavbarDrawerContainer();
6259
+ if (drawer) {
6260
+ return Array.from(drawer.querySelectorAll("[data-ohw-href-key]")).filter(isNavbarLinkItem);
6261
+ }
6262
+ return Array.from(document.querySelectorAll("nav [data-ohw-href-key]")).filter(isNavbarLinkItem);
6263
+ }
6264
+ function parseNavIndexFromKey(key) {
6265
+ if (!key) return null;
6266
+ const match = key.match(/^nav-(\d+)-href$/);
6267
+ if (!match) return null;
6268
+ return parseInt(match[1], 10);
6269
+ }
6270
+ function getNavbarIndicesInDom() {
6271
+ const indices = /* @__PURE__ */ new Set();
6272
+ for (const item of listNavbarItems()) {
6273
+ const idx = parseNavIndexFromKey(item.getAttribute("data-ohw-href-key"));
6274
+ if (idx !== null) indices.add(idx);
6275
+ }
6276
+ return [...indices].sort((a, b) => a - b);
6277
+ }
6278
+ function getNextNavbarIndex() {
6279
+ const indices = getNavbarIndicesInDom();
6280
+ if (indices.length === 0) return 0;
6281
+ return Math.max(...indices) + 1;
6282
+ }
6283
+ function getNavbarExistingTargets() {
6284
+ return listNavbarItems().map((el) => getLinkHref(el)).filter(Boolean);
6285
+ }
6286
+ function getNavOrderFromDom() {
6287
+ return listNavbarItems().map((el) => el.getAttribute("data-ohw-href-key")).filter((key) => Boolean(key));
6288
+ }
6289
+ function parseNavOrder(content) {
6290
+ const raw = content[NAV_ORDER_KEY];
6291
+ if (!raw) return null;
6292
+ try {
6293
+ const parsed = JSON.parse(raw);
6294
+ if (!Array.isArray(parsed)) return null;
6295
+ return parsed.filter((key) => typeof key === "string" && key.length > 0);
6296
+ } catch {
6297
+ return null;
6298
+ }
6299
+ }
6300
+ function findCounterpartByHrefKey(hrefKey, root) {
6301
+ return root.querySelector(`[data-ohw-href-key="${CSS.escape(hrefKey)}"]`);
6302
+ }
6303
+ function cloneNavLinkShell(template) {
6304
+ const anchor = document.createElement("a");
6305
+ if (template) {
6306
+ anchor.style.cssText = template.style.cssText;
6307
+ if (template.className) anchor.className = template.className;
6308
+ }
6309
+ anchor.style.cursor = "pointer";
6310
+ anchor.style.textDecoration = "none";
6311
+ return anchor;
6312
+ }
6313
+ function buildNavLink(index, href, label, template) {
6314
+ const anchor = cloneNavLinkShell(template);
6315
+ anchor.href = href;
6316
+ anchor.setAttribute("data-ohw-href-key", `nav-${index}-href`);
6317
+ const span = document.createElement("span");
6318
+ span.setAttribute("data-ohw-editable", "text");
6319
+ span.setAttribute("data-ohw-key", `nav-${index}-label`);
6320
+ span.textContent = label;
6321
+ anchor.appendChild(span);
6322
+ return anchor;
6323
+ }
6324
+ function insertNavLinkInContainer(container, anchor, afterHrefKey) {
6325
+ if (!afterHrefKey) {
6326
+ container.appendChild(anchor);
6327
+ return;
6328
+ }
6329
+ const afterEl = findCounterpartByHrefKey(afterHrefKey, container);
6330
+ if (afterEl?.parentElement === container) {
6331
+ afterEl.insertAdjacentElement("afterend", anchor);
6332
+ return;
6333
+ }
6334
+ container.appendChild(anchor);
6335
+ }
6336
+ function insertNavbarItemDom(index, href, label, afterAnchor) {
6337
+ const afterHrefKey = afterAnchor?.getAttribute("data-ohw-href-key") ?? null;
6338
+ const desktopItems = getNavbarDesktopContainer()?.querySelectorAll("[data-ohw-href-key]") ?? [];
6339
+ const drawerItems = getNavbarDrawerContainer()?.querySelectorAll("[data-ohw-href-key]") ?? [];
6340
+ const desktopTemplate = Array.from(desktopItems).find(isNavbarLinkItem) ?? null;
6341
+ const drawerTemplate = Array.from(drawerItems).find(isNavbarLinkItem) ?? null;
6342
+ let primary = null;
6343
+ const desktopContainer = getNavbarDesktopContainer();
6344
+ if (desktopContainer) {
6345
+ const desktopAnchor = buildNavLink(index, href, label, desktopTemplate);
6346
+ insertNavLinkInContainer(desktopContainer, desktopAnchor, afterHrefKey);
6347
+ primary = desktopAnchor;
6348
+ }
6349
+ const drawerContainer = getNavbarDrawerContainer();
6350
+ if (drawerContainer) {
6351
+ const drawerAnchor = buildNavLink(index, href, label, drawerTemplate);
6352
+ insertNavLinkInContainer(drawerContainer, drawerAnchor, afterHrefKey);
6353
+ if (!primary) primary = drawerAnchor;
6354
+ }
6355
+ if (!primary) {
6356
+ const fallback = buildNavLink(index, href, label, listNavbarItems()[0] ?? null);
6357
+ const nav = document.querySelector("nav");
6358
+ nav?.appendChild(fallback);
6359
+ primary = fallback;
6360
+ }
6361
+ return primary;
6362
+ }
6363
+ function applyNavOrderToContainer(container, order) {
6364
+ const seen = /* @__PURE__ */ new Set();
6365
+ for (const hrefKey of order) {
6366
+ if (seen.has(hrefKey)) continue;
6367
+ seen.add(hrefKey);
6368
+ const el = findCounterpartByHrefKey(hrefKey, container);
6369
+ if (el) container.appendChild(el);
6370
+ }
6371
+ for (const el of container.querySelectorAll("[data-ohw-href-key]")) {
6372
+ if (!isNavbarLinkItem(el)) continue;
6373
+ const key = el.getAttribute("data-ohw-href-key");
6374
+ if (!key || seen.has(key)) continue;
6375
+ container.appendChild(el);
6376
+ }
6377
+ }
6378
+ function applyNavOrder(order) {
6379
+ const desktop = getNavbarDesktopContainer();
6380
+ if (desktop) applyNavOrderToContainer(desktop, order);
6381
+ const drawer = getNavbarDrawerContainer();
6382
+ if (drawer) applyNavOrderToContainer(drawer, order);
6383
+ }
6384
+ function collectNavbarIndicesFromContent(content) {
6385
+ const indices = /* @__PURE__ */ new Set();
6386
+ for (const key of Object.keys(content)) {
6387
+ const idx = parseNavIndexFromKey(key);
6388
+ if (idx !== null) indices.add(idx);
6389
+ }
6390
+ const countRaw = content[NAV_COUNT_KEY];
6391
+ if (countRaw) {
6392
+ const count = parseInt(countRaw, 10);
6393
+ if (Number.isFinite(count) && count > 0) {
6394
+ for (let i = 0; i < count; i++) indices.add(i);
6395
+ }
6396
+ }
6397
+ return [...indices].sort((a, b) => a - b);
6398
+ }
6399
+ function navbarIndexExistsInDom(index) {
6400
+ return document.querySelector(`[data-ohw-href-key="nav-${index}-href"]`) !== null;
6401
+ }
6402
+ function reconcileNavbarItemsFromContent(content) {
6403
+ const indices = collectNavbarIndicesFromContent(content);
6404
+ for (const index of indices) {
6405
+ if (navbarIndexExistsInDom(index)) continue;
6406
+ const href = content[`nav-${index}-href`];
6407
+ const label = content[`nav-${index}-label`];
6408
+ if (!href && !label) continue;
6409
+ insertNavbarItemDom(index, href ?? "/", label ?? "Untitled", null);
6410
+ }
6411
+ const order = parseNavOrder(content);
6412
+ if (order?.length) applyNavOrder(order);
6413
+ }
6414
+ function buildNavOrderAfterInsert(afterAnchor, newHrefKey) {
6415
+ const order = getNavOrderFromDom();
6416
+ if (!afterAnchor) {
6417
+ if (!order.includes(newHrefKey)) order.push(newHrefKey);
6418
+ return order;
6419
+ }
6420
+ const afterKey = afterAnchor.getAttribute("data-ohw-href-key");
6421
+ if (!afterKey) {
6422
+ if (!order.includes(newHrefKey)) order.push(newHrefKey);
6423
+ return order;
6424
+ }
6425
+ const withoutNew = order.filter((key) => key !== newHrefKey);
6426
+ const pos = withoutNew.indexOf(afterKey);
6427
+ if (pos >= 0) {
6428
+ withoutNew.splice(pos + 1, 0, newHrefKey);
6429
+ return withoutNew;
6430
+ }
6431
+ withoutNew.push(newHrefKey);
6432
+ return withoutNew;
6433
+ }
6434
+ function insertNavbarItem(href, label, afterAnchor = null) {
6435
+ const index = getNextNavbarIndex();
6436
+ const anchor = insertNavbarItemDom(index, href, label, afterAnchor);
6437
+ if (!anchor) throw new Error("Failed to insert navbar item");
6438
+ const hrefKey = `nav-${index}-href`;
6439
+ const order = buildNavOrderAfterInsert(afterAnchor, hrefKey);
6440
+ applyNavOrder(order);
6441
+ return {
6442
+ anchor,
6443
+ index,
6444
+ hrefKey,
6445
+ labelKey: `nav-${index}-label`,
6446
+ href,
6447
+ label,
6448
+ order
6449
+ };
6450
+ }
6451
+
6452
+ // src/ui/navbar-container-chrome.tsx
6453
+ var import_lucide_react10 = require("lucide-react");
6454
+ var import_jsx_runtime21 = require("react/jsx-runtime");
6455
+ function NavbarContainerChrome({
6456
+ rect,
6457
+ onAdd
5574
6458
  }) {
5575
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5576
- Dialog2,
6459
+ const chromeGap = 6;
6460
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
6461
+ "div",
5577
6462
  {
5578
- open,
5579
- onOpenChange: (next) => {
5580
- if (!next) onClose?.();
6463
+ "data-ohw-navbar-container-chrome": "",
6464
+ "data-ohw-bridge": "",
6465
+ className: "pointer-events-none fixed z-[2147483647]",
6466
+ style: {
6467
+ top: rect.top - chromeGap,
6468
+ left: rect.left - chromeGap,
6469
+ width: rect.width + chromeGap * 2,
6470
+ height: rect.height + chromeGap * 2
5581
6471
  },
5582
- children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5583
- DialogContent,
6472
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
6473
+ "button",
5584
6474
  {
5585
- ref: panelRef,
5586
- container: portalContainer,
5587
- "data-ohw-link-popover-root": "",
5588
- "data-ohw-link-modal-root": "",
5589
- "data-ohw-bridge": "",
5590
- showCloseButton: false,
5591
- className: "gap-0 p-0 w-full md:w-md pointer-events-auto",
5592
- children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(LinkEditorPanel, { ...editorProps, open, onClose })
6475
+ type: "button",
6476
+ "data-ohw-navbar-add-button": "",
6477
+ className: "pointer-events-auto absolute left-1/2 flex p-0.5 rounded-[10px] size-7 -translate-x-1/2 translate-y-1/2 items-center justify-center border border-border bg-background shadow-sm transition-colors hover:bg-muted/80",
6478
+ style: { top: "70%" },
6479
+ "aria-label": "Add item",
6480
+ onMouseDown: (e) => {
6481
+ e.preventDefault();
6482
+ e.stopPropagation();
6483
+ },
6484
+ onClick: (e) => {
6485
+ e.preventDefault();
6486
+ e.stopPropagation();
6487
+ onAdd();
6488
+ },
6489
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react10.Plus, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
5593
6490
  }
5594
6491
  )
5595
6492
  }
5596
6493
  );
5597
6494
  }
5598
6495
 
5599
- // src/ui/link-modal/devFixtures.ts
5600
- var DEV_SITE_PAGES = [
5601
- { path: "/", title: "Home" },
5602
- { path: "/about", title: "About" },
5603
- { path: "/classes", title: "Classes" },
5604
- { path: "/pricing", title: "Pricing" },
5605
- { path: "/studio", title: "Studio" },
5606
- { path: "/instructors", title: "Instructors" },
5607
- { path: "/contact", title: "Contact" }
5608
- ];
5609
- var DEV_SECTIONS_BY_PATH = {
5610
- "/": [
5611
- { id: "hero", label: "Hero" },
5612
- { id: "lagree-intro", label: "Lagree Intro" },
5613
- { id: "classes-strip", label: "Classes" },
5614
- { id: "feel-split", label: "Feel Split" },
5615
- { id: "founder-teaser", label: "Founder" },
5616
- { id: "plan-form", label: "Plan Form" },
5617
- { id: "testimonials", label: "Testimonials" },
5618
- { id: "wordmark", label: "Wordmark" }
5619
- ],
5620
- "/about": [
5621
- { id: "manifesto", label: "Manifesto" },
5622
- { id: "story-letter", label: "Our Story" },
5623
- { id: "lagree-explainer", label: "Lagree Explainer" },
5624
- { id: "waitlist-cta", label: "Waitlist" },
5625
- { id: "personal-training", label: "Personal training" }
5626
- ],
5627
- "/classes": [{ id: "class-library", label: "Class Library" }],
5628
- "/pricing": [
5629
- { id: "page-header", label: "Page Header" },
5630
- { id: "free-class-cta", label: "Free Class" },
5631
- { id: "benefits-marquee", label: "Benefits" },
5632
- { id: "monthly-memberships", label: "Memberships" },
5633
- { id: "specialty-programs", label: "Specialty Programs" },
5634
- { id: "package-matcher", label: "Packages" },
5635
- { id: "wordmark", label: "Wordmark" }
5636
- ],
5637
- "/studio": [
5638
- { id: "studio-intro", label: "Studio Intro" },
5639
- { id: "studio-features", label: "Studio Features" },
5640
- { id: "studio-gallery", label: "Studio Gallery" },
5641
- { id: "studio-visit", label: "Visit Studio" }
5642
- ],
5643
- "/instructors": [{ id: "instructor-grid", label: "Instructors" }],
5644
- "/contact": [
5645
- { id: "hello-hero", label: "Hello" },
5646
- { id: "contact-form", label: "Contact Form" },
5647
- { id: "faq", label: "FAQ" }
5648
- ]
5649
- };
5650
- function shouldUseDevFixtures() {
5651
- if (typeof window === "undefined") return false;
5652
- const raw = readPreservedSearch();
5653
- const q = raw.startsWith("?") ? raw.slice(1) : raw;
5654
- return new URLSearchParams(q).get("ohw-fixtures") === "1";
5655
- }
5656
-
5657
6496
  // src/ui/badge.tsx
5658
- var import_jsx_runtime20 = require("react/jsx-runtime");
6497
+ var import_jsx_runtime22 = require("react/jsx-runtime");
5659
6498
  var badgeVariants = cva(
5660
6499
  "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
5661
6500
  {
@@ -5673,12 +6512,12 @@ var badgeVariants = cva(
5673
6512
  }
5674
6513
  );
5675
6514
  function Badge({ className, variant, ...props }) {
5676
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: cn(badgeVariants({ variant }), className), ...props });
6515
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: cn(badgeVariants({ variant }), className), ...props });
5677
6516
  }
5678
6517
 
5679
6518
  // src/OhhwellsBridge.tsx
5680
- var import_lucide_react3 = require("lucide-react");
5681
- var import_jsx_runtime21 = require("react/jsx-runtime");
6519
+ var import_lucide_react11 = require("lucide-react");
6520
+ var import_jsx_runtime23 = require("react/jsx-runtime");
5682
6521
  var PRIMARY2 = "#0885FE";
5683
6522
  var IMAGE_FADE_MS = 300;
5684
6523
  function runOpacityFade(el, onDone) {
@@ -5850,9 +6689,9 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
5850
6689
  tail.insertAdjacentElement("afterend", container);
5851
6690
  }
5852
6691
  const root = (0, import_client.createRoot)(container);
5853
- (0, import_react_dom.flushSync)(() => {
6692
+ (0, import_react_dom2.flushSync)(() => {
5854
6693
  root.render(
5855
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
6694
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
5856
6695
  SchedulingWidget,
5857
6696
  {
5858
6697
  notifyOnConnect,
@@ -5892,7 +6731,7 @@ function mountSchedulingEntries(entries, notifyOnConnect = false) {
5892
6731
  }
5893
6732
  }
5894
6733
  }
5895
- function getLinkHref(el) {
6734
+ function getLinkHref2(el) {
5896
6735
  const anchor = el instanceof HTMLAnchorElement ? el : el.querySelector("a");
5897
6736
  return anchor?.getAttribute("href") ?? "";
5898
6737
  }
@@ -5929,7 +6768,7 @@ function collectEditableNodes() {
5929
6768
  return { key: el.dataset.ohwKey ?? "", type: "video", text: getVideoEl(el)?.src ?? "" };
5930
6769
  }
5931
6770
  if (el.dataset.ohwEditable === "link") {
5932
- return { key: el.dataset.ohwKey ?? "", type: "link", text: getLinkHref(el) };
6771
+ return { key: el.dataset.ohwKey ?? "", type: "link", text: getLinkHref2(el) };
5933
6772
  }
5934
6773
  return {
5935
6774
  key: el.dataset.ohwKey ?? "",
@@ -5940,7 +6779,7 @@ function collectEditableNodes() {
5940
6779
  document.querySelectorAll("[data-ohw-href-key]").forEach((el) => {
5941
6780
  const key = el.getAttribute("data-ohw-href-key") ?? "";
5942
6781
  if (!key) return;
5943
- nodes.push({ key, type: "link", text: getLinkHref(el) });
6782
+ nodes.push({ key, type: "link", text: getLinkHref2(el) });
5944
6783
  });
5945
6784
  document.querySelectorAll('[data-ohw-editable="video"]').forEach((el) => {
5946
6785
  const key = el.dataset.ohwKey ?? "";
@@ -6010,7 +6849,7 @@ function applyLinkByKey(key, val) {
6010
6849
  }
6011
6850
  function isInsideLinkEditor(target) {
6012
6851
  return Boolean(
6013
- 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"]')
6852
+ 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"]')
6014
6853
  );
6015
6854
  }
6016
6855
  function getHrefKeyFromElement(el) {
@@ -6021,7 +6860,7 @@ function getHrefKeyFromElement(el) {
6021
6860
  if (!key) return null;
6022
6861
  return { anchor, key };
6023
6862
  }
6024
- function isNavbarButton(el) {
6863
+ function isNavbarButton2(el) {
6025
6864
  return Boolean(el.closest('[data-ohw-role="navbar-button"]'));
6026
6865
  }
6027
6866
  function getNavigationItemAnchor(el) {
@@ -6033,19 +6872,8 @@ function getNavigationItemAnchor(el) {
6033
6872
  function isNavigationItem(el) {
6034
6873
  return getNavigationItemAnchor(el) !== null;
6035
6874
  }
6036
- function getNavigationItemAddHintTarget(anchor) {
6037
- const items = listNavigationItems();
6038
- const idx = items.indexOf(anchor);
6039
- if (idx >= 0 && idx < items.length - 1) return items[idx + 1];
6040
- return anchor;
6041
- }
6042
- function listNavigationItems() {
6043
- return Array.from(
6044
- document.querySelectorAll("nav [data-ohw-href-key], footer [data-ohw-href-key]")
6045
- ).filter((el) => isNavigationItem(el));
6046
- }
6047
6875
  function getNavigationItemReorderState(anchor) {
6048
- if (isNavbarButton(anchor)) return { key: null, disabled: false };
6876
+ if (isNavbarButton2(anchor)) return { key: null, disabled: false };
6049
6877
  return getReorderHandleStateFromAnchor(anchor);
6050
6878
  }
6051
6879
  function getReorderHandleState(el) {
@@ -6096,6 +6924,39 @@ function isInferredFooterGroup(el) {
6096
6924
  function isNavigationContainer(el) {
6097
6925
  return el.hasAttribute("data-ohw-nav-container") || isNavigationRoot(el) || isInferredFooterGroup(el);
6098
6926
  }
6927
+ function isPointOverNavigation(x, y) {
6928
+ const navRoot = document.querySelector("[data-ohw-nav-root]") ?? document.querySelector("nav");
6929
+ if (!navRoot) return false;
6930
+ const r2 = navRoot.getBoundingClientRect();
6931
+ return x >= r2.left && x <= r2.right && y >= r2.top && y <= r2.bottom;
6932
+ }
6933
+ function isPointOverNavItem(container, x, y) {
6934
+ return Array.from(container.querySelectorAll("[data-ohw-href-key]")).some((el) => {
6935
+ if (!isNavigationItem(el) || isNavbarButton2(el)) return false;
6936
+ const itemRect = el.getBoundingClientRect();
6937
+ return x >= itemRect.left && x <= itemRect.right && y >= itemRect.top && y <= itemRect.bottom;
6938
+ });
6939
+ }
6940
+ function resolveNavContainerSelectionTarget(target, clientX, clientY) {
6941
+ if (getNavigationItemAnchor(target)) return null;
6942
+ if (target.closest('[data-ohw-role="navbar-button"]')) return null;
6943
+ const plainLink = target.closest("a");
6944
+ if (plainLink && !plainLink.hasAttribute("data-ohw-href-key") && !plainLink.closest("[data-ohw-nav-container]")) {
6945
+ return null;
6946
+ }
6947
+ const fromClosest = target.closest("[data-ohw-nav-container]");
6948
+ if (fromClosest && !isPointOverNavItem(fromClosest, clientX, clientY)) {
6949
+ return fromClosest;
6950
+ }
6951
+ const navRoot = target.closest("[data-ohw-nav-root]");
6952
+ if (!navRoot) return null;
6953
+ const container = navRoot.querySelector("[data-ohw-nav-container]");
6954
+ if (!container || isPointOverNavItem(container, clientX, clientY)) return null;
6955
+ if (target === navRoot || target.hasAttribute("data-ohw-nav-root")) {
6956
+ return container;
6957
+ }
6958
+ return null;
6959
+ }
6099
6960
  function getNavigationSelectionParent(el) {
6100
6961
  if (isNavigationItem(el)) {
6101
6962
  const explicit = el.closest("[data-ohw-nav-container]");
@@ -6297,7 +7158,7 @@ function EditGlowChrome({
6297
7158
  dragDisabled = false
6298
7159
  }) {
6299
7160
  const GAP = 6;
6300
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
7161
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
6301
7162
  "div",
6302
7163
  {
6303
7164
  ref: elRef,
@@ -6312,7 +7173,7 @@ function EditGlowChrome({
6312
7173
  zIndex: 2147483646
6313
7174
  },
6314
7175
  children: [
6315
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7176
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6316
7177
  "div",
6317
7178
  {
6318
7179
  style: {
@@ -6325,7 +7186,7 @@ function EditGlowChrome({
6325
7186
  }
6326
7187
  }
6327
7188
  ),
6328
- reorderHrefKey && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7189
+ reorderHrefKey && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6329
7190
  "div",
6330
7191
  {
6331
7192
  "data-ohw-drag-handle-container": "",
@@ -6337,7 +7198,7 @@ function EditGlowChrome({
6337
7198
  transform: "translate(calc(-100% - 7px), -50%)",
6338
7199
  pointerEvents: dragDisabled ? "none" : "auto"
6339
7200
  },
6340
- children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7201
+ children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6341
7202
  DragHandle,
6342
7203
  {
6343
7204
  "aria-label": `Reorder ${reorderHrefKey}`,
@@ -6441,7 +7302,7 @@ function FloatingToolbar({
6441
7302
  onEditLink
6442
7303
  }) {
6443
7304
  const { top, left, transform } = calcToolbarPos(rect, parentScroll, 330);
6444
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7305
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6445
7306
  "div",
6446
7307
  {
6447
7308
  ref: elRef,
@@ -6462,12 +7323,12 @@ function FloatingToolbar({
6462
7323
  fontFamily: "sans-serif",
6463
7324
  pointerEvents: "auto"
6464
7325
  },
6465
- children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(CustomToolbar, { children: [
6466
- TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_react6.default.Fragment, { children: [
6467
- gi > 0 && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(CustomToolbarDivider, {}),
7326
+ children: /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(CustomToolbar, { children: [
7327
+ TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_react8.default.Fragment, { children: [
7328
+ gi > 0 && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(CustomToolbarDivider, {}),
6468
7329
  btns.map((btn) => {
6469
7330
  const isActive = activeCommands.has(btn.cmd);
6470
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7331
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6471
7332
  CustomToolbarButton,
6472
7333
  {
6473
7334
  title: btn.title,
@@ -6476,7 +7337,7 @@ function FloatingToolbar({
6476
7337
  e.preventDefault();
6477
7338
  onCommand(btn.cmd);
6478
7339
  },
6479
- children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7340
+ children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6480
7341
  "svg",
6481
7342
  {
6482
7343
  width: "16",
@@ -6497,7 +7358,7 @@ function FloatingToolbar({
6497
7358
  );
6498
7359
  })
6499
7360
  ] }, gi)),
6500
- showEditLink ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7361
+ showEditLink ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6501
7362
  CustomToolbarButton,
6502
7363
  {
6503
7364
  type: "button",
@@ -6511,7 +7372,7 @@ function FloatingToolbar({
6511
7372
  e.preventDefault();
6512
7373
  e.stopPropagation();
6513
7374
  },
6514
- children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react3.Link, { className: "size-4 shrink-0", "aria-hidden": true })
7375
+ children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_lucide_react11.Link, { className: "size-4 shrink-0", "aria-hidden": true })
6515
7376
  }
6516
7377
  ) : null
6517
7378
  ] })
@@ -6528,7 +7389,7 @@ function StateToggle({
6528
7389
  states,
6529
7390
  onStateChange
6530
7391
  }) {
6531
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7392
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6532
7393
  ToggleGroup,
6533
7394
  {
6534
7395
  "data-ohw-state-toggle": "",
@@ -6542,7 +7403,7 @@ function StateToggle({
6542
7403
  left: rect.right - 8,
6543
7404
  transform: "translateX(-100%)"
6544
7405
  },
6545
- children: states.map((state) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
7406
+ children: states.map((state) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
6546
7407
  }
6547
7408
  );
6548
7409
  }
@@ -6565,12 +7426,12 @@ function resolveSubdomain(subdomainFromQuery) {
6565
7426
  return "";
6566
7427
  }
6567
7428
  function OhhwellsBridge() {
6568
- const pathname = (0, import_navigation.usePathname)();
6569
- const router = (0, import_navigation.useRouter)();
6570
- const searchParams = (0, import_navigation.useSearchParams)();
7429
+ const pathname = (0, import_navigation2.usePathname)();
7430
+ const router = (0, import_navigation2.useRouter)();
7431
+ const searchParams = (0, import_navigation2.useSearchParams)();
6571
7432
  const isEditMode = isEditSessionActive();
6572
- const [bridgeRoot, setBridgeRoot] = (0, import_react6.useState)(null);
6573
- (0, import_react6.useEffect)(() => {
7433
+ const [bridgeRoot, setBridgeRoot] = (0, import_react8.useState)(null);
7434
+ (0, import_react8.useEffect)(() => {
6574
7435
  const figtreeFontId = "ohw-figtree-font";
6575
7436
  if (!document.getElementById(figtreeFontId)) {
6576
7437
  const preconnect1 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.googleapis.com" });
@@ -6578,7 +7439,10 @@ function OhhwellsBridge() {
6578
7439
  const stylesheet = Object.assign(document.createElement("link"), {
6579
7440
  id: figtreeFontId,
6580
7441
  rel: "stylesheet",
6581
- href: "https://fonts.googleapis.com/css2?family=Figtree:ital,wght@0,300..900;1,300..900&display=swap"
7442
+ // Inter is loaded alongside Figtree for the media overlay's Replace button. The bridge
7443
+ // renders inside the customer's document, so it cannot assume any font is present — an
7444
+ // unloaded family would silently fall back to whatever the template happens to use.
7445
+ href: "https://fonts.googleapis.com/css2?family=Figtree:ital,wght@0,300..900;1,300..900&family=Inter:wght@400;500;600&display=swap"
6582
7446
  });
6583
7447
  document.head.append(preconnect1, preconnect2, stylesheet);
6584
7448
  }
@@ -6595,82 +7459,90 @@ function OhhwellsBridge() {
6595
7459
  const subdomainFromQuery = searchParams.get("subdomain");
6596
7460
  const subdomain = resolveSubdomain(subdomainFromQuery);
6597
7461
  useLinkHrefGuardian(pathname, subdomain, isEditMode);
6598
- const postToParent = (0, import_react6.useCallback)((data) => {
7462
+ const postToParent2 = (0, import_react8.useCallback)((data) => {
6599
7463
  if (typeof window !== "undefined" && window.parent !== window) {
6600
7464
  window.parent.postMessage(data, "*");
6601
7465
  }
6602
7466
  }, []);
6603
- const [fetchState, setFetchState] = (0, import_react6.useState)("idle");
6604
- const autoSaveTimers = (0, import_react6.useRef)(/* @__PURE__ */ new Map());
6605
- const activeElRef = (0, import_react6.useRef)(null);
6606
- const selectedElRef = (0, import_react6.useRef)(null);
6607
- const originalContentRef = (0, import_react6.useRef)(null);
6608
- const activeStateElRef = (0, import_react6.useRef)(null);
6609
- const parentScrollRef = (0, import_react6.useRef)(null);
6610
- const visibleViewportRef = (0, import_react6.useRef)(null);
6611
- const [dialogPortalContainer, setDialogPortalContainer] = (0, import_react6.useState)(null);
6612
- const attachVisibleViewport = (0, import_react6.useCallback)((node) => {
7467
+ const [fetchState, setFetchState] = (0, import_react8.useState)("idle");
7468
+ const autoSaveTimers = (0, import_react8.useRef)(/* @__PURE__ */ new Map());
7469
+ const activeElRef = (0, import_react8.useRef)(null);
7470
+ const selectedElRef = (0, import_react8.useRef)(null);
7471
+ const originalContentRef = (0, import_react8.useRef)(null);
7472
+ const activeStateElRef = (0, import_react8.useRef)(null);
7473
+ const parentScrollRef = (0, import_react8.useRef)(null);
7474
+ const visibleViewportRef = (0, import_react8.useRef)(null);
7475
+ const [dialogPortalContainer, setDialogPortalContainer] = (0, import_react8.useState)(null);
7476
+ const attachVisibleViewport = (0, import_react8.useCallback)((node) => {
6613
7477
  visibleViewportRef.current = node;
6614
7478
  setDialogPortalContainer(node);
6615
7479
  if (node) applyVisibleViewport(node, parentScrollRef.current);
6616
7480
  }, []);
6617
- const toolbarElRef = (0, import_react6.useRef)(null);
6618
- const glowElRef = (0, import_react6.useRef)(null);
6619
- const hoveredImageRef = (0, import_react6.useRef)(null);
6620
- const hoveredImageHasTextOverlapRef = (0, import_react6.useRef)(false);
6621
- const hoveredGapRef = (0, import_react6.useRef)(null);
6622
- const imageUnhoverTimerRef = (0, import_react6.useRef)(null);
6623
- const imageShowTimerRef = (0, import_react6.useRef)(null);
6624
- const editStylesRef = (0, import_react6.useRef)(null);
6625
- const activateRef = (0, import_react6.useRef)(() => {
7481
+ const toolbarElRef = (0, import_react8.useRef)(null);
7482
+ const glowElRef = (0, import_react8.useRef)(null);
7483
+ const hoveredImageRef = (0, import_react8.useRef)(null);
7484
+ const hoveredImageHasTextOverlapRef = (0, import_react8.useRef)(false);
7485
+ const [mediaHover, setMediaHover] = (0, import_react8.useState)(null);
7486
+ const [uploadingRects, setUploadingRects] = (0, import_react8.useState)({});
7487
+ const hoveredGapRef = (0, import_react8.useRef)(null);
7488
+ const imageUnhoverTimerRef = (0, import_react8.useRef)(null);
7489
+ const imageShowTimerRef = (0, import_react8.useRef)(null);
7490
+ const editStylesRef = (0, import_react8.useRef)(null);
7491
+ const activateRef = (0, import_react8.useRef)(() => {
6626
7492
  });
6627
- const deactivateRef = (0, import_react6.useRef)(() => {
7493
+ const deactivateRef = (0, import_react8.useRef)(() => {
6628
7494
  });
6629
- const selectRef = (0, import_react6.useRef)(() => {
7495
+ const selectRef = (0, import_react8.useRef)(() => {
6630
7496
  });
6631
- const selectFrameRef = (0, import_react6.useRef)(() => {
7497
+ const selectFrameRef = (0, import_react8.useRef)(() => {
6632
7498
  });
6633
- const deselectRef = (0, import_react6.useRef)(() => {
7499
+ const deselectRef = (0, import_react8.useRef)(() => {
6634
7500
  });
6635
- const reselectNavigationItemRef = (0, import_react6.useRef)(() => {
7501
+ const reselectNavigationItemRef = (0, import_react8.useRef)(() => {
6636
7502
  });
6637
- const commitNavigationTextEditRef = (0, import_react6.useRef)(() => {
7503
+ const commitNavigationTextEditRef = (0, import_react8.useRef)(() => {
6638
7504
  });
6639
- const refreshActiveCommandsRef = (0, import_react6.useRef)(() => {
7505
+ const refreshActiveCommandsRef = (0, import_react8.useRef)(() => {
6640
7506
  });
6641
- const postToParentRef = (0, import_react6.useRef)(postToParent);
6642
- postToParentRef.current = postToParent;
6643
- const sectionsLoadedRef = (0, import_react6.useRef)(false);
6644
- const pendingScheduleConfigRequests = (0, import_react6.useRef)([]);
6645
- const [toolbarRect, setToolbarRect] = (0, import_react6.useState)(null);
6646
- const [toolbarVariant, setToolbarVariant] = (0, import_react6.useState)("none");
6647
- const toolbarVariantRef = (0, import_react6.useRef)("none");
7507
+ const postToParentRef = (0, import_react8.useRef)(postToParent2);
7508
+ postToParentRef.current = postToParent2;
7509
+ const sectionsLoadedRef = (0, import_react8.useRef)(false);
7510
+ const pendingScheduleConfigRequests = (0, import_react8.useRef)([]);
7511
+ const [toolbarRect, setToolbarRect] = (0, import_react8.useState)(null);
7512
+ const [toolbarVariant, setToolbarVariant] = (0, import_react8.useState)("none");
7513
+ const toolbarVariantRef = (0, import_react8.useRef)("none");
6648
7514
  toolbarVariantRef.current = toolbarVariant;
6649
- const [reorderHrefKey, setReorderHrefKey] = (0, import_react6.useState)(null);
6650
- const [reorderDragDisabled, setReorderDragDisabled] = (0, import_react6.useState)(false);
6651
- const [toggleState, setToggleState] = (0, import_react6.useState)(null);
6652
- const [maxBadge, setMaxBadge] = (0, import_react6.useState)(null);
6653
- const [activeCommands, setActiveCommands] = (0, import_react6.useState)(/* @__PURE__ */ new Set());
6654
- const [sectionGap, setSectionGap] = (0, import_react6.useState)(null);
6655
- const [toolbarShowEditLink, setToolbarShowEditLink] = (0, import_react6.useState)(false);
6656
- const hoveredItemElRef = (0, import_react6.useRef)(null);
6657
- const [hoveredItemRect, setHoveredItemRect] = (0, import_react6.useState)(null);
6658
- const siblingHintElRef = (0, import_react6.useRef)(null);
6659
- const [siblingHintRect, setSiblingHintRect] = (0, import_react6.useState)(null);
6660
- const [isItemDragging, setIsItemDragging] = (0, import_react6.useState)(false);
6661
- const [linkPopover, setLinkPopover] = (0, import_react6.useState)(null);
6662
- const [sitePages, setSitePages] = (0, import_react6.useState)([]);
6663
- const [sectionsByPath, setSectionsByPath] = (0, import_react6.useState)({});
6664
- const sectionsPrefetchGenRef = (0, import_react6.useRef)(0);
6665
- const setLinkPopoverRef = (0, import_react6.useRef)(setLinkPopover);
6666
- const linkPopoverPanelRef = (0, import_react6.useRef)(null);
6667
- const linkPopoverOpenRef = (0, import_react6.useRef)(false);
6668
- const linkPopoverGraceUntilRef = (0, import_react6.useRef)(0);
7515
+ const [reorderHrefKey, setReorderHrefKey] = (0, import_react8.useState)(null);
7516
+ const [reorderDragDisabled, setReorderDragDisabled] = (0, import_react8.useState)(false);
7517
+ const [toggleState, setToggleState] = (0, import_react8.useState)(null);
7518
+ const [maxBadge, setMaxBadge] = (0, import_react8.useState)(null);
7519
+ const [activeCommands, setActiveCommands] = (0, import_react8.useState)(/* @__PURE__ */ new Set());
7520
+ const [sectionGap, setSectionGap] = (0, import_react8.useState)(null);
7521
+ const [toolbarShowEditLink, setToolbarShowEditLink] = (0, import_react8.useState)(false);
7522
+ const hoveredNavContainerRef = (0, import_react8.useRef)(null);
7523
+ const [hoveredNavContainerRect, setHoveredNavContainerRect] = (0, import_react8.useState)(null);
7524
+ const hoveredItemElRef = (0, import_react8.useRef)(null);
7525
+ const [hoveredItemRect, setHoveredItemRect] = (0, import_react8.useState)(null);
7526
+ const siblingHintElRef = (0, import_react8.useRef)(null);
7527
+ const [siblingHintRect, setSiblingHintRect] = (0, import_react8.useState)(null);
7528
+ const [isItemDragging, setIsItemDragging] = (0, import_react8.useState)(false);
7529
+ const [linkPopover, setLinkPopover] = (0, import_react8.useState)(null);
7530
+ const linkPopoverSessionRef = (0, import_react8.useRef)(null);
7531
+ const addNavAfterAnchorRef = (0, import_react8.useRef)(null);
7532
+ const editContentRef = (0, import_react8.useRef)({});
7533
+ const [sitePages, setSitePages] = (0, import_react8.useState)([]);
7534
+ const [sectionsByPath, setSectionsByPath] = (0, import_react8.useState)({});
7535
+ const sectionsPrefetchGenRef = (0, import_react8.useRef)(0);
7536
+ const setLinkPopoverRef = (0, import_react8.useRef)(setLinkPopover);
7537
+ const linkPopoverPanelRef = (0, import_react8.useRef)(null);
7538
+ const linkPopoverOpenRef = (0, import_react8.useRef)(false);
7539
+ const linkPopoverGraceUntilRef = (0, import_react8.useRef)(0);
6669
7540
  setLinkPopoverRef.current = setLinkPopover;
7541
+ linkPopoverSessionRef.current = linkPopover;
6670
7542
  const bumpLinkPopoverGrace = () => {
6671
7543
  linkPopoverGraceUntilRef.current = Date.now() + 350;
6672
7544
  };
6673
- const runSectionsPrefetch = (0, import_react6.useCallback)((pages) => {
7545
+ const runSectionsPrefetch = (0, import_react8.useCallback)((pages) => {
6674
7546
  if (!isEditMode || shouldUseDevFixtures() || pages.length === 0) return;
6675
7547
  const gen = ++sectionsPrefetchGenRef.current;
6676
7548
  const paths = pages.map((p) => p.path);
@@ -6689,9 +7561,9 @@ function OhhwellsBridge() {
6689
7561
  );
6690
7562
  });
6691
7563
  }, [isEditMode, pathname]);
6692
- const runSectionsPrefetchRef = (0, import_react6.useRef)(runSectionsPrefetch);
7564
+ const runSectionsPrefetchRef = (0, import_react8.useRef)(runSectionsPrefetch);
6693
7565
  runSectionsPrefetchRef.current = runSectionsPrefetch;
6694
- (0, import_react6.useEffect)(() => {
7566
+ (0, import_react8.useEffect)(() => {
6695
7567
  if (!linkPopover) return;
6696
7568
  if (hoveredImageRef.current) {
6697
7569
  hoveredImageRef.current = null;
@@ -6699,33 +7571,9 @@ function OhhwellsBridge() {
6699
7571
  }
6700
7572
  hoveredGapRef.current = null;
6701
7573
  setSectionGap(null);
6702
- postToParent({ type: "ow:image-unhover" });
6703
- postToParent({ type: "ow:link-modal-lock", locked: true });
6704
- const html = document.documentElement;
6705
- const body = document.body;
6706
- const prevHtmlOverflow = html.style.overflow;
6707
- const prevBodyOverflow = body.style.overflow;
6708
- html.style.overflow = "hidden";
6709
- body.style.overflow = "hidden";
6710
- const preventBackgroundScroll = (e) => {
6711
- const target = e.target;
6712
- if (target instanceof Element && target.closest('[data-ohw-link-modal-root], [data-slot="dialog-overlay"]')) {
6713
- return;
6714
- }
6715
- e.preventDefault();
6716
- };
6717
- const scrollOpts = { passive: false, capture: true };
6718
- document.addEventListener("wheel", preventBackgroundScroll, scrollOpts);
6719
- document.addEventListener("touchmove", preventBackgroundScroll, scrollOpts);
6720
- return () => {
6721
- postToParent({ type: "ow:link-modal-lock", locked: false });
6722
- html.style.overflow = prevHtmlOverflow;
6723
- body.style.overflow = prevBodyOverflow;
6724
- document.removeEventListener("wheel", preventBackgroundScroll, scrollOpts);
6725
- document.removeEventListener("touchmove", preventBackgroundScroll, scrollOpts);
6726
- };
6727
- }, [linkPopover, postToParent]);
6728
- (0, import_react6.useEffect)(() => {
7574
+ postToParent2({ type: "ow:image-unhover" });
7575
+ }, [linkPopover, postToParent2]);
7576
+ (0, import_react8.useEffect)(() => {
6729
7577
  if (!isEditMode) return;
6730
7578
  const useFixtures = shouldUseDevFixtures();
6731
7579
  if (useFixtures) {
@@ -6746,17 +7594,17 @@ function OhhwellsBridge() {
6746
7594
  runSectionsPrefetchRef.current(mapped);
6747
7595
  };
6748
7596
  window.addEventListener("message", onSitePages);
6749
- if (!useFixtures) postToParent({ type: "ow:request-site-pages" });
7597
+ if (!useFixtures) postToParent2({ type: "ow:request-site-pages" });
6750
7598
  return () => window.removeEventListener("message", onSitePages);
6751
- }, [isEditMode, postToParent]);
6752
- (0, import_react6.useEffect)(() => {
7599
+ }, [isEditMode, postToParent2]);
7600
+ (0, import_react8.useEffect)(() => {
6753
7601
  if (!isEditMode || shouldUseDevFixtures()) return;
6754
7602
  void loadAllSectionsManifest().then((manifest) => {
6755
7603
  if (Object.keys(manifest).length === 0) return;
6756
7604
  setSectionsByPath((prev) => ({ ...manifest, ...prev }));
6757
7605
  });
6758
7606
  }, [isEditMode]);
6759
- (0, import_react6.useEffect)(() => {
7607
+ (0, import_react8.useEffect)(() => {
6760
7608
  const update = () => {
6761
7609
  const el = activeElRef.current ?? selectedElRef.current;
6762
7610
  if (el) setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
@@ -6780,10 +7628,10 @@ function OhhwellsBridge() {
6780
7628
  vvp.removeEventListener("resize", update);
6781
7629
  };
6782
7630
  }, []);
6783
- const refreshStateRules = (0, import_react6.useCallback)(() => {
7631
+ const refreshStateRules = (0, import_react8.useCallback)(() => {
6784
7632
  editStylesRef.current?.forceHover && (editStylesRef.current.forceHover.textContent = collectStateRules());
6785
7633
  }, []);
6786
- const processConfigRequest = (0, import_react6.useCallback)((insertAfterVal) => {
7634
+ const processConfigRequest = (0, import_react8.useCallback)((insertAfterVal) => {
6787
7635
  const tracker = getSectionsTracker();
6788
7636
  let entries = [];
6789
7637
  try {
@@ -6806,7 +7654,7 @@ function OhhwellsBridge() {
6806
7654
  }
6807
7655
  window.postMessage({ type: "ow:schedule-config", insertAfter: insertAfterVal, scheduleId: null }, "*");
6808
7656
  }, [isEditMode]);
6809
- const deactivate = (0, import_react6.useCallback)(() => {
7657
+ const deactivate = (0, import_react8.useCallback)(() => {
6810
7658
  const el = activeElRef.current;
6811
7659
  if (!el) return;
6812
7660
  const key = el.dataset.ohwKey;
@@ -6835,21 +7683,23 @@ function OhhwellsBridge() {
6835
7683
  setMaxBadge(null);
6836
7684
  setActiveCommands(/* @__PURE__ */ new Set());
6837
7685
  setToolbarShowEditLink(false);
6838
- postToParent({ type: "ow:exit-edit" });
6839
- }, [postToParent]);
6840
- const deselect = (0, import_react6.useCallback)(() => {
7686
+ postToParent2({ type: "ow:exit-edit" });
7687
+ }, [postToParent2]);
7688
+ const deselect = (0, import_react8.useCallback)(() => {
6841
7689
  selectedElRef.current = null;
6842
7690
  setReorderHrefKey(null);
6843
7691
  setReorderDragDisabled(false);
6844
7692
  siblingHintElRef.current = null;
6845
7693
  setSiblingHintRect(null);
6846
7694
  setIsItemDragging(false);
7695
+ hoveredNavContainerRef.current = null;
7696
+ setHoveredNavContainerRect(null);
6847
7697
  if (!activeElRef.current) {
6848
7698
  setToolbarRect(null);
6849
7699
  setToolbarVariant("none");
6850
7700
  }
6851
7701
  }, []);
6852
- const reselectNavigationItem = (0, import_react6.useCallback)((navAnchor) => {
7702
+ const reselectNavigationItem = (0, import_react8.useCallback)((navAnchor) => {
6853
7703
  selectedElRef.current = navAnchor;
6854
7704
  const { key, disabled } = getNavigationItemReorderState(navAnchor);
6855
7705
  setReorderHrefKey(key);
@@ -6859,7 +7709,7 @@ function OhhwellsBridge() {
6859
7709
  setToolbarShowEditLink(false);
6860
7710
  setActiveCommands(/* @__PURE__ */ new Set());
6861
7711
  }, []);
6862
- const commitNavigationTextEdit = (0, import_react6.useCallback)((navAnchor) => {
7712
+ const commitNavigationTextEdit = (0, import_react8.useCallback)((navAnchor) => {
6863
7713
  const el = activeElRef.current;
6864
7714
  if (!el) return;
6865
7715
  const key = el.dataset.ohwKey;
@@ -6872,9 +7722,9 @@ function OhhwellsBridge() {
6872
7722
  const html = sanitizeHtml(el.innerHTML);
6873
7723
  const original = originalContentRef.current ?? "";
6874
7724
  if (html !== sanitizeHtml(original)) {
6875
- postToParent({ type: "ow:change", nodes: [{ key, text: html }] });
7725
+ postToParent2({ type: "ow:change", nodes: [{ key, text: html }] });
6876
7726
  const h = document.documentElement.scrollHeight;
6877
- if (h > 50) postToParent({ type: "ow:height", height: h });
7727
+ if (h > 50) postToParent2({ type: "ow:height", height: h });
6878
7728
  }
6879
7729
  }
6880
7730
  el.removeAttribute("contenteditable");
@@ -6882,31 +7732,38 @@ function OhhwellsBridge() {
6882
7732
  setMaxBadge(null);
6883
7733
  setActiveCommands(/* @__PURE__ */ new Set());
6884
7734
  setToolbarShowEditLink(false);
6885
- postToParent({ type: "ow:exit-edit" });
7735
+ postToParent2({ type: "ow:exit-edit" });
6886
7736
  reselectNavigationItem(navAnchor);
6887
- }, [postToParent, reselectNavigationItem]);
6888
- const handleAddNavigationItem = (0, import_react6.useCallback)(() => {
6889
- const selected = selectedElRef.current;
6890
- if (!selected) return;
6891
- const target = getNavigationItemAddHintTarget(selected);
6892
- siblingHintElRef.current = target;
6893
- setSiblingHintRect(target.getBoundingClientRect());
7737
+ }, [postToParent2, reselectNavigationItem]);
7738
+ const handleAddTopLevelNavItem = (0, import_react8.useCallback)(() => {
7739
+ const items = listNavbarItems();
7740
+ addNavAfterAnchorRef.current = items[items.length - 1] ?? null;
7741
+ deselectRef.current();
7742
+ deactivateRef.current();
7743
+ bumpLinkPopoverGrace();
7744
+ setLinkPopover({
7745
+ key: "__add-nav__",
7746
+ mode: "create",
7747
+ intent: "add-nav"
7748
+ });
6894
7749
  }, []);
6895
- const handleItemDragStart = (0, import_react6.useCallback)(() => {
7750
+ const handleItemDragStart = (0, import_react8.useCallback)(() => {
6896
7751
  siblingHintElRef.current = null;
6897
7752
  setSiblingHintRect(null);
6898
7753
  setIsItemDragging(true);
6899
7754
  }, []);
6900
- const handleItemDragEnd = (0, import_react6.useCallback)(() => {
7755
+ const handleItemDragEnd = (0, import_react8.useCallback)(() => {
6901
7756
  setIsItemDragging(false);
6902
7757
  }, []);
6903
7758
  reselectNavigationItemRef.current = reselectNavigationItem;
6904
7759
  commitNavigationTextEditRef.current = commitNavigationTextEdit;
6905
- const select = (0, import_react6.useCallback)((anchor) => {
7760
+ const select = (0, import_react8.useCallback)((anchor) => {
6906
7761
  if (!isNavigationItem(anchor)) return;
6907
7762
  if (activeElRef.current) deactivate();
6908
7763
  selectedElRef.current = anchor;
6909
7764
  clearHrefKeyHover(anchor);
7765
+ hoveredNavContainerRef.current = null;
7766
+ setHoveredNavContainerRect(null);
6910
7767
  setHoveredItemRect(null);
6911
7768
  hoveredItemElRef.current = null;
6912
7769
  siblingHintElRef.current = null;
@@ -6920,11 +7777,13 @@ function OhhwellsBridge() {
6920
7777
  setToolbarShowEditLink(false);
6921
7778
  setActiveCommands(/* @__PURE__ */ new Set());
6922
7779
  }, [deactivate]);
6923
- const selectFrame = (0, import_react6.useCallback)((el) => {
7780
+ const selectFrame = (0, import_react8.useCallback)((el) => {
6924
7781
  if (!isNavigationContainer(el)) return;
6925
7782
  if (activeElRef.current) deactivate();
6926
7783
  selectedElRef.current = el;
6927
7784
  clearHrefKeyHover(el);
7785
+ hoveredNavContainerRef.current = null;
7786
+ setHoveredNavContainerRect(null);
6928
7787
  setHoveredItemRect(null);
6929
7788
  hoveredItemElRef.current = null;
6930
7789
  siblingHintElRef.current = null;
@@ -6937,13 +7796,13 @@ function OhhwellsBridge() {
6937
7796
  setToolbarShowEditLink(false);
6938
7797
  setActiveCommands(/* @__PURE__ */ new Set());
6939
7798
  }, [deactivate]);
6940
- const activate = (0, import_react6.useCallback)((el, options) => {
7799
+ const activate = (0, import_react8.useCallback)((el, options) => {
6941
7800
  if (activeElRef.current === el) return;
6942
7801
  selectedElRef.current = null;
6943
7802
  deactivate();
6944
7803
  if (hoveredImageRef.current) {
6945
7804
  hoveredImageRef.current = null;
6946
- postToParentRef.current({ type: "ow:image-unhover" });
7805
+ setMediaHover(null);
6947
7806
  }
6948
7807
  setToolbarVariant("rich-text");
6949
7808
  siblingHintElRef.current = null;
@@ -6969,15 +7828,15 @@ function OhhwellsBridge() {
6969
7828
  setReorderDragDisabled(reorderDisabled);
6970
7829
  }
6971
7830
  setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
6972
- postToParent({ type: "ow:enter-edit", key: el.dataset.ohwKey });
7831
+ postToParent2({ type: "ow:enter-edit", key: el.dataset.ohwKey });
6973
7832
  requestAnimationFrame(() => refreshActiveCommandsRef.current());
6974
- }, [deactivate, postToParent]);
7833
+ }, [deactivate, postToParent2]);
6975
7834
  activateRef.current = activate;
6976
7835
  deactivateRef.current = deactivate;
6977
7836
  selectRef.current = select;
6978
7837
  selectFrameRef.current = selectFrame;
6979
7838
  deselectRef.current = deselect;
6980
- (0, import_react6.useLayoutEffect)(() => {
7839
+ (0, import_react8.useLayoutEffect)(() => {
6981
7840
  if (!subdomain || isEditMode) {
6982
7841
  setFetchState("done");
6983
7842
  return;
@@ -7045,7 +7904,7 @@ function OhhwellsBridge() {
7045
7904
  cancelled = true;
7046
7905
  };
7047
7906
  }, [subdomain, isEditMode]);
7048
- (0, import_react6.useEffect)(() => {
7907
+ (0, import_react8.useEffect)(() => {
7049
7908
  if (!subdomain || isEditMode) return;
7050
7909
  let debounceTimer = null;
7051
7910
  let observer = null;
@@ -7093,26 +7952,40 @@ function OhhwellsBridge() {
7093
7952
  if (debounceTimer) clearTimeout(debounceTimer);
7094
7953
  };
7095
7954
  }, [subdomain, isEditMode, pathname]);
7096
- (0, import_react6.useLayoutEffect)(() => {
7955
+ (0, import_react8.useLayoutEffect)(() => {
7097
7956
  const el = document.getElementById("ohw-loader");
7098
7957
  if (!el) return;
7099
7958
  const visible = Boolean(subdomain) && fetchState !== "done";
7100
7959
  el.style.display = visible ? "flex" : "none";
7101
7960
  }, [subdomain, fetchState]);
7102
- (0, import_react6.useEffect)(() => {
7103
- postToParent({ type: "ow:navigation", path: pathname });
7104
- }, [pathname, postToParent]);
7105
- (0, import_react6.useEffect)(() => {
7961
+ (0, import_react8.useEffect)(() => {
7962
+ postToParent2({ type: "ow:navigation", path: pathname });
7963
+ }, [pathname, postToParent2]);
7964
+ (0, import_react8.useEffect)(() => {
7106
7965
  if (!isEditMode) return;
7966
+ if (linkPopoverSessionRef.current?.intent === "add-nav") return;
7967
+ if (document.querySelector("[data-ohw-section-picker]")) return;
7107
7968
  setLinkPopover(null);
7108
7969
  deselectRef.current();
7109
7970
  deactivateRef.current();
7110
7971
  }, [pathname, isEditMode]);
7111
- (0, import_react6.useEffect)(() => {
7972
+ (0, import_react8.useEffect)(() => {
7973
+ if (!isEditMode) return;
7974
+ const run = () => reconcileNavbarItemsFromContent(editContentRef.current);
7975
+ run();
7976
+ const nav = document.querySelector("nav");
7977
+ if (!nav) return;
7978
+ const observer = new MutationObserver(() => {
7979
+ requestAnimationFrame(run);
7980
+ });
7981
+ observer.observe(nav, { childList: true, subtree: true });
7982
+ return () => observer.disconnect();
7983
+ }, [isEditMode, pathname]);
7984
+ (0, import_react8.useEffect)(() => {
7112
7985
  if (!isEditMode) return;
7113
7986
  const measure = () => {
7114
7987
  const h = document.body.scrollHeight;
7115
- if (h > 50) postToParent({ type: "ow:height", height: h });
7988
+ if (h > 50) postToParent2({ type: "ow:height", height: h });
7116
7989
  };
7117
7990
  const t1 = setTimeout(measure, 50);
7118
7991
  const t2 = setTimeout(measure, 500);
@@ -7131,8 +8004,8 @@ function OhhwellsBridge() {
7131
8004
  if (resizeTimer) clearTimeout(resizeTimer);
7132
8005
  window.removeEventListener("resize", handleResize);
7133
8006
  };
7134
- }, [pathname, isEditMode, postToParent]);
7135
- (0, import_react6.useEffect)(() => {
8007
+ }, [pathname, isEditMode, postToParent2]);
8008
+ (0, import_react8.useEffect)(() => {
7136
8009
  if (!subdomainFromQuery || isEditMode) return;
7137
8010
  const handleClick = (e) => {
7138
8011
  const anchor = e.target.closest("a");
@@ -7148,7 +8021,7 @@ function OhhwellsBridge() {
7148
8021
  document.addEventListener("click", handleClick, true);
7149
8022
  return () => document.removeEventListener("click", handleClick, true);
7150
8023
  }, [subdomainFromQuery, isEditMode, router]);
7151
- (0, import_react6.useEffect)(() => {
8024
+ (0, import_react8.useEffect)(() => {
7152
8025
  if (!isEditMode) {
7153
8026
  editStylesRef.current?.base.remove();
7154
8027
  editStylesRef.current?.forceHover.remove();
@@ -7222,6 +8095,13 @@ function OhhwellsBridge() {
7222
8095
  if (target.closest("[data-ohw-max-badge]")) return;
7223
8096
  if (isInsideLinkEditor(target)) return;
7224
8097
  if (target.closest('[data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
8098
+ const navFromChrome = resolveNavContainerSelectionTarget(target, e.clientX, e.clientY);
8099
+ if (navFromChrome) {
8100
+ e.preventDefault();
8101
+ e.stopPropagation();
8102
+ selectFrameRef.current(navFromChrome);
8103
+ return;
8104
+ }
7225
8105
  e.preventDefault();
7226
8106
  e.stopPropagation();
7227
8107
  return;
@@ -7236,14 +8116,15 @@ function OhhwellsBridge() {
7236
8116
  bumpLinkPopoverGrace();
7237
8117
  setLinkPopoverRef.current({
7238
8118
  key: editable.dataset.ohwKey ?? "",
7239
- target: getLinkHref(editable)
8119
+ mode: "edit",
8120
+ target: getLinkHref2(editable)
7240
8121
  });
7241
8122
  return;
7242
8123
  }
7243
8124
  if (isMediaEditable(editable)) {
7244
8125
  e.preventDefault();
7245
8126
  e.stopPropagation();
7246
- postToParentRef.current({ type: "ow:image-pick", key: editable.dataset.ohwKey ?? "" });
8127
+ postToParentRef.current({ type: "ow:image-pick", key: editable.dataset.ohwKey ?? "", elementType: editable.dataset.ohwEditable ?? "image" });
7247
8128
  return;
7248
8129
  }
7249
8130
  const hrefCtx = getHrefKeyFromElement(editable);
@@ -7272,6 +8153,13 @@ function OhhwellsBridge() {
7272
8153
  selectRef.current(hrefAnchor);
7273
8154
  return;
7274
8155
  }
8156
+ const navContainerToSelect = resolveNavContainerSelectionTarget(target, e.clientX, e.clientY);
8157
+ if (navContainerToSelect) {
8158
+ e.preventDefault();
8159
+ e.stopPropagation();
8160
+ selectFrameRef.current(navContainerToSelect);
8161
+ return;
8162
+ }
7275
8163
  const selectedContainer = selectedElRef.current;
7276
8164
  if (selectedContainer && isNavigationContainer(selectedContainer)) {
7277
8165
  const navItem = getNavigationItemAnchor(target);
@@ -7330,8 +8218,24 @@ function OhhwellsBridge() {
7330
8218
  };
7331
8219
  const handleMouseOver = (e) => {
7332
8220
  const target = e.target;
8221
+ if (toolbarVariantRef.current !== "select-frame") {
8222
+ const navContainer = target.closest("[data-ohw-nav-container]");
8223
+ if (navContainer && !getNavigationItemAnchor(target)) {
8224
+ hoveredNavContainerRef.current = navContainer;
8225
+ setHoveredNavContainerRect(navContainer.getBoundingClientRect());
8226
+ hoveredItemElRef.current = null;
8227
+ setHoveredItemRect(null);
8228
+ return;
8229
+ }
8230
+ if (!target.closest("[data-ohw-nav-container]")) {
8231
+ hoveredNavContainerRef.current = null;
8232
+ setHoveredNavContainerRect(null);
8233
+ }
8234
+ }
7333
8235
  const navAnchor = getNavigationItemAnchor(target);
7334
8236
  if (navAnchor) {
8237
+ hoveredNavContainerRef.current = null;
8238
+ setHoveredNavContainerRect(null);
7335
8239
  const selected2 = selectedElRef.current;
7336
8240
  if (selected2 === navAnchor || selected2?.contains(navAnchor)) return;
7337
8241
  clearHrefKeyHover(navAnchor);
@@ -7356,6 +8260,14 @@ function OhhwellsBridge() {
7356
8260
  };
7357
8261
  const handleMouseOut = (e) => {
7358
8262
  const target = e.target;
8263
+ if (target.closest("[data-ohw-nav-container]")) {
8264
+ const related2 = e.relatedTarget instanceof Element ? e.relatedTarget : null;
8265
+ if (related2?.closest("[data-ohw-nav-container], [data-ohw-navbar-container-chrome], [data-ohw-navbar-add-button]")) {
8266
+ return;
8267
+ }
8268
+ hoveredNavContainerRef.current = null;
8269
+ setHoveredNavContainerRect(null);
8270
+ }
7359
8271
  const navAnchor = getNavigationItemAnchor(target);
7360
8272
  if (navAnchor) {
7361
8273
  const related2 = e.relatedTarget instanceof Element ? e.relatedTarget : null;
@@ -7417,25 +8329,23 @@ function OhhwellsBridge() {
7417
8329
  }
7418
8330
  return { top, left, width: Math.max(0, right - left), height: Math.max(0, bottom - top) };
7419
8331
  };
7420
- const postImageHover = (imgEl, isDragOver = false, forceTextOverlap) => {
8332
+ const showImageHover = (imgEl, isDragOver = false, forceTextOverlap) => {
7421
8333
  const r2 = getVisibleRect(imgEl);
7422
8334
  const hasTextOverlap = forceTextOverlap ?? false;
7423
8335
  hoveredImageHasTextOverlapRef.current = hasTextOverlap;
7424
8336
  const video = imgEl.dataset.ohwEditable === "video" ? getVideoEl(imgEl) : null;
7425
- postToParentRef.current({
7426
- type: "ow:image-hover",
8337
+ setMediaHover({
7427
8338
  key: imgEl.dataset.ohwKey ?? "",
7428
- // Lets the parent pick the file-picker `accept`, overlay label/icon and drop
7429
- // mime-check without a parallel ow:video-* message channel.
7430
- elementType: imgEl.dataset.ohwEditable ?? "image",
7431
8339
  rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height },
8340
+ elementType: imgEl.dataset.ohwEditable ?? "image",
7432
8341
  hasTextOverlap,
7433
- ...video ? { videoAutoplay: video.autoplay, videoMuted: video.muted } : {},
7434
- ...isDragOver ? { isDragOver: true } : {}
8342
+ isDragOver,
8343
+ ...video ? { videoAutoplay: video.autoplay, videoMuted: video.muted } : {}
7435
8344
  });
7436
8345
  const track = imgEl.closest("[data-ohw-hover-pause]");
7437
8346
  if (track) track.setAttribute("data-ohw-hover-paused", "");
7438
8347
  };
8348
+ const clearImageHover = () => setMediaHover(null);
7439
8349
  const findImageAtPoint = (clientX, clientY, fromParentViewport) => {
7440
8350
  const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
7441
8351
  const images = Array.from(document.querySelectorAll(MEDIA_SELECTOR));
@@ -7479,47 +8389,91 @@ function OhhwellsBridge() {
7479
8389
  }
7480
8390
  return smallest;
7481
8391
  };
8392
+ const dismissImageHover = () => {
8393
+ if (hoveredImageRef.current) {
8394
+ hoveredImageRef.current = null;
8395
+ hoveredImageHasTextOverlapRef.current = false;
8396
+ resumeAnimTracks();
8397
+ postToParentRef.current({ type: "ow:image-unhover" });
8398
+ }
8399
+ clearImageHover();
8400
+ };
8401
+ const probeNavigationHoverAt = (x, y) => {
8402
+ if (toolbarVariantRef.current === "select-frame") {
8403
+ hoveredNavContainerRef.current = null;
8404
+ setHoveredNavContainerRect(null);
8405
+ return;
8406
+ }
8407
+ const navContainer = document.querySelector("[data-ohw-nav-container]");
8408
+ if (!navContainer) {
8409
+ hoveredNavContainerRef.current = null;
8410
+ setHoveredNavContainerRect(null);
8411
+ return;
8412
+ }
8413
+ const containerRect = navContainer.getBoundingClientRect();
8414
+ const overContainer = x >= containerRect.left && x <= containerRect.right && y >= containerRect.top && y <= containerRect.bottom;
8415
+ if (!overContainer) {
8416
+ hoveredNavContainerRef.current = null;
8417
+ setHoveredNavContainerRect(null);
8418
+ return;
8419
+ }
8420
+ const navItemHit = Array.from(
8421
+ navContainer.querySelectorAll("[data-ohw-href-key]")
8422
+ ).find((el) => {
8423
+ if (!isNavigationItem(el) || isNavbarButton2(el)) return false;
8424
+ const itemRect = el.getBoundingClientRect();
8425
+ return x >= itemRect.left && x <= itemRect.right && y >= itemRect.top && y <= itemRect.bottom;
8426
+ });
8427
+ if (navItemHit) {
8428
+ hoveredNavContainerRef.current = null;
8429
+ setHoveredNavContainerRect(null);
8430
+ const selected = selectedElRef.current;
8431
+ if (selected !== navItemHit && !selected?.contains(navItemHit)) {
8432
+ clearHrefKeyHover(navItemHit);
8433
+ hoveredItemElRef.current = navItemHit;
8434
+ setHoveredItemRect(navItemHit.getBoundingClientRect());
8435
+ }
8436
+ return;
8437
+ }
8438
+ hoveredNavContainerRef.current = navContainer;
8439
+ setHoveredNavContainerRect(containerRect);
8440
+ hoveredItemElRef.current = null;
8441
+ setHoveredItemRect(null);
8442
+ };
7482
8443
  const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
7483
8444
  if (linkPopoverOpenRef.current) {
7484
- if (hoveredImageRef.current) {
7485
- hoveredImageRef.current = null;
7486
- hoveredImageHasTextOverlapRef.current = false;
7487
- resumeAnimTracks();
7488
- postToParentRef.current({ type: "ow:image-unhover" });
7489
- }
8445
+ dismissImageHover();
8446
+ return;
8447
+ }
8448
+ const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
8449
+ if (isPointOverNavigation(x, y)) {
8450
+ dismissImageHover();
8451
+ probeNavigationHoverAt(x, y);
7490
8452
  return;
7491
8453
  }
8454
+ hoveredNavContainerRef.current = null;
8455
+ setHoveredNavContainerRect(null);
7492
8456
  const toggleEl = document.querySelector("[data-ohw-state-toggle]");
7493
8457
  if (toggleEl) {
7494
- const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
7495
8458
  const tr = toggleEl.getBoundingClientRect();
7496
8459
  if (x >= tr.left && x <= tr.right && y >= tr.top && y <= tr.bottom) {
7497
- if (hoveredImageRef.current) {
7498
- hoveredImageRef.current = null;
7499
- resumeAnimTracks();
7500
- postToParentRef.current({ type: "ow:image-unhover" });
7501
- }
8460
+ dismissImageHover();
7502
8461
  return;
7503
8462
  }
7504
8463
  }
7505
- if (activeElRef.current) {
7506
- if (hoveredImageRef.current) {
7507
- hoveredImageRef.current = null;
7508
- hoveredImageHasTextOverlapRef.current = false;
7509
- resumeAnimTracks();
7510
- postToParentRef.current({ type: "ow:image-unhover" });
7511
- }
8464
+ const imgEl = findImageAtPoint(clientX, clientY, fromParentViewport);
8465
+ const activeEl = activeElRef.current;
8466
+ if (activeEl && (!imgEl || imgEl.contains(activeEl))) {
8467
+ dismissImageHover();
7512
8468
  return;
7513
8469
  }
7514
- const imgEl = findImageAtPoint(clientX, clientY, fromParentViewport);
7515
8470
  if (imgEl) {
7516
- const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
7517
8471
  const topEl = document.elementFromPoint(x, y);
7518
8472
  if (topEl?.closest('[data-ohw-toolbar], [data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
7519
8473
  if (hoveredImageRef.current) {
7520
8474
  hoveredImageRef.current = null;
7521
8475
  resumeAnimTracks();
7522
- postToParentRef.current({ type: "ow:image-unhover" });
8476
+ clearImageHover();
7523
8477
  }
7524
8478
  return;
7525
8479
  }
@@ -7528,7 +8482,7 @@ function OhhwellsBridge() {
7528
8482
  hoveredImageRef.current = null;
7529
8483
  hoveredImageHasTextOverlapRef.current = false;
7530
8484
  resumeAnimTracks();
7531
- postToParentRef.current({ type: "ow:image-unhover" });
8485
+ clearImageHover();
7532
8486
  }
7533
8487
  return;
7534
8488
  }
@@ -7553,14 +8507,14 @@ function OhhwellsBridge() {
7553
8507
  hoveredImageRef.current = null;
7554
8508
  hoveredImageHasTextOverlapRef.current = false;
7555
8509
  resumeAnimTracks();
7556
- postToParentRef.current({ type: "ow:image-unhover" });
8510
+ clearImageHover();
7557
8511
  }
7558
8512
  return;
7559
8513
  }
7560
8514
  if (isStateCardImage) {
7561
8515
  if (imgEl !== hoveredImageRef.current || !hoveredImageHasTextOverlapRef.current) {
7562
8516
  hoveredImageRef.current = imgEl;
7563
- postImageHover(imgEl, isDragOver, true);
8517
+ showImageHover(imgEl, isDragOver, true);
7564
8518
  }
7565
8519
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
7566
8520
  if (textEditable && !isInsideNavigationItem(textEditable)) {
@@ -7572,7 +8526,7 @@ function OhhwellsBridge() {
7572
8526
  hoveredImageRef.current = null;
7573
8527
  hoveredImageHasTextOverlapRef.current = false;
7574
8528
  resumeAnimTracks();
7575
- postToParentRef.current({ type: "ow:image-unhover" });
8529
+ clearImageHover();
7576
8530
  }
7577
8531
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
7578
8532
  if (textEditable && !isInsideNavigationItem(textEditable)) {
@@ -7584,7 +8538,7 @@ function OhhwellsBridge() {
7584
8538
  if (hoveredImageRef.current) {
7585
8539
  hoveredImageRef.current = null;
7586
8540
  resumeAnimTracks();
7587
- postToParentRef.current({ type: "ow:image-unhover" });
8541
+ clearImageHover();
7588
8542
  }
7589
8543
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
7590
8544
  return;
@@ -7592,9 +8546,9 @@ function OhhwellsBridge() {
7592
8546
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
7593
8547
  if (imgEl !== hoveredImageRef.current || hoveredImageHasTextOverlapRef.current) {
7594
8548
  hoveredImageRef.current = imgEl;
7595
- postImageHover(imgEl, isDragOver);
8549
+ showImageHover(imgEl, isDragOver);
7596
8550
  } else if (isDragOver) {
7597
- postImageHover(imgEl, true);
8551
+ showImageHover(imgEl, true);
7598
8552
  }
7599
8553
  } else {
7600
8554
  const inIframeView = clientX >= 0 && clientY >= 0 && clientX <= window.innerWidth && clientY <= window.innerHeight;
@@ -7611,14 +8565,14 @@ function OhhwellsBridge() {
7611
8565
  hoveredImageRef.current = null;
7612
8566
  hoveredImageHasTextOverlapRef.current = false;
7613
8567
  resumeAnimTracks();
7614
- postToParentRef.current({ type: "ow:image-unhover" });
8568
+ clearImageHover();
7615
8569
  }
7616
- const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
8570
+ const { x: x2, y: y2 } = toProbeCoords(clientX, clientY, fromParentViewport);
7617
8571
  const textEl = Array.from(
7618
8572
  document.querySelectorAll(NON_MEDIA_SELECTOR)
7619
8573
  ).find((el) => {
7620
8574
  const er = el.getBoundingClientRect();
7621
- return x >= er.left && x <= er.right && y >= er.top && y <= er.bottom;
8575
+ return x2 >= er.left && x2 <= er.right && y2 >= er.top && y2 <= er.bottom;
7622
8576
  });
7623
8577
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
7624
8578
  if (textEl && !textEl.hasAttribute("contenteditable")) {
@@ -7724,7 +8678,7 @@ function OhhwellsBridge() {
7724
8678
  if (hoveredImageRef.current !== el) {
7725
8679
  hoveredImageRef.current = el;
7726
8680
  const isStateCard = !!el.closest("[data-ohw-editable-state]");
7727
- postImageHover(el, true, isStateCard ? true : void 0);
8681
+ showImageHover(el, true, isStateCard ? true : void 0);
7728
8682
  }
7729
8683
  };
7730
8684
  const handleDragLeave = (e) => {
@@ -7732,7 +8686,7 @@ function OhhwellsBridge() {
7732
8686
  if (imgEl) return;
7733
8687
  hoveredImageRef.current = null;
7734
8688
  resumeAnimTracks();
7735
- postToParentRef.current({ type: "ow:image-unhover" });
8689
+ clearImageHover();
7736
8690
  };
7737
8691
  const handleDrop = (e) => {
7738
8692
  e.preventDefault();
@@ -7756,31 +8710,7 @@ function OhhwellsBridge() {
7756
8710
  }
7757
8711
  hoveredImageRef.current = null;
7758
8712
  resumeAnimTracks();
7759
- postToParentRef.current({ type: "ow:image-unhover" });
7760
- };
7761
- const handleVideoSettings = (e) => {
7762
- if (e.data?.type !== "ow:video-settings") return;
7763
- const { key, autoplay, muted } = e.data;
7764
- document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
7765
- const video = getVideoEl(el);
7766
- if (!video) return;
7767
- if (typeof autoplay === "boolean") video.autoplay = autoplay;
7768
- if (typeof muted === "boolean") video.muted = muted;
7769
- syncVideoPlayback(video);
7770
- postToParentRef.current({
7771
- type: "ow:change",
7772
- nodes: [
7773
- { key: `${key}${VIDEO_AUTOPLAY_SUFFIX}`, text: String(video.autoplay) },
7774
- { key: `${key}${VIDEO_MUTED_SUFFIX}`, text: String(video.muted) }
7775
- ]
7776
- });
7777
- postToParentRef.current({
7778
- type: "ow:video-settings-applied",
7779
- key,
7780
- autoplay: video.autoplay,
7781
- muted: video.muted
7782
- });
7783
- });
8713
+ clearImageHover();
7784
8714
  };
7785
8715
  const handleImageUrl = (e) => {
7786
8716
  if (e.data?.type !== "ow:image-url") return;
@@ -7798,7 +8728,11 @@ function OhhwellsBridge() {
7798
8728
  }
7799
8729
  });
7800
8730
  hoveredImageRef.current = null;
7801
- postToParentRef.current({ type: "ow:image-loaded", key });
8731
+ setUploadingRects((prev) => {
8732
+ const entry = prev[key];
8733
+ if (!entry || entry.fadingOut) return prev;
8734
+ return { ...prev, [key]: { ...entry, fadingOut: true } };
8735
+ });
7802
8736
  };
7803
8737
  let found = false;
7804
8738
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -7935,6 +8869,8 @@ function OhhwellsBridge() {
7935
8869
  sectionsLoadedRef.current = true;
7936
8870
  pendingScheduleConfigRequests.current.splice(0).forEach(processConfigRequest);
7937
8871
  }
8872
+ editContentRef.current = { ...editContentRef.current, ...content };
8873
+ reconcileNavbarItemsFromContent(editContentRef.current);
7938
8874
  enforceLinkHrefs();
7939
8875
  postToParentRef.current({ type: "ow:hydrate-done" });
7940
8876
  };
@@ -7942,6 +8878,7 @@ function OhhwellsBridge() {
7942
8878
  const handleDeactivate = (e) => {
7943
8879
  if (e.data?.type !== "ow:deactivate") return;
7944
8880
  if (Date.now() < linkPopoverGraceUntilRef.current) return;
8881
+ if (document.querySelector("[data-ohw-section-picker]")) return;
7945
8882
  if (linkPopoverOpenRef.current) {
7946
8883
  setLinkPopoverRef.current(null);
7947
8884
  return;
@@ -7951,6 +8888,7 @@ function OhhwellsBridge() {
7951
8888
  };
7952
8889
  window.addEventListener("message", handleDeactivate);
7953
8890
  const handleKeyDown = (e) => {
8891
+ if (e.key === "Escape" && document.querySelector("[data-ohw-section-picker]")) return;
7954
8892
  if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "a" && activeElRef.current) {
7955
8893
  const navAnchor = getNavigationItemAnchor(activeElRef.current);
7956
8894
  if (navAnchor) {
@@ -7965,8 +8903,13 @@ function OhhwellsBridge() {
7965
8903
  return;
7966
8904
  }
7967
8905
  if (e.key === "Escape" && selectedElRef.current && !activeElRef.current) {
8906
+ if (toolbarVariantRef.current === "select-frame") {
8907
+ deselectRef.current();
8908
+ return;
8909
+ }
7968
8910
  const parent = getNavigationSelectionParent(selectedElRef.current);
7969
8911
  if (parent) {
8912
+ e.preventDefault();
7970
8913
  selectFrameRef.current(parent);
7971
8914
  } else {
7972
8915
  deselectRef.current();
@@ -8028,18 +8971,18 @@ function OhhwellsBridge() {
8028
8971
  if (hoveredItemElRef.current) {
8029
8972
  setHoveredItemRect(hoveredItemElRef.current.getBoundingClientRect());
8030
8973
  }
8974
+ if (hoveredNavContainerRef.current) {
8975
+ setHoveredNavContainerRect(hoveredNavContainerRef.current.getBoundingClientRect());
8976
+ }
8031
8977
  if (siblingHintElRef.current) {
8032
8978
  setSiblingHintRect(siblingHintElRef.current.getBoundingClientRect());
8033
8979
  }
8034
8980
  if (hoveredImageRef.current) {
8035
8981
  const el = hoveredImageRef.current;
8036
8982
  const r2 = el.getBoundingClientRect();
8037
- postToParentRef.current({
8038
- type: "ow:image-hover",
8039
- key: el.dataset.ohwKey ?? "",
8040
- rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height },
8041
- hasTextOverlap: hoveredImageHasTextOverlapRef.current
8042
- });
8983
+ setMediaHover(
8984
+ (prev) => prev ? { ...prev, rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height } } : prev
8985
+ );
8043
8986
  }
8044
8987
  };
8045
8988
  const handleSave = (e) => {
@@ -8128,19 +9071,39 @@ function OhhwellsBridge() {
8128
9071
  refreshActiveCommandsRef.current = handleSelectionChange;
8129
9072
  const handleDocMouseLeave = () => {
8130
9073
  hoveredImageRef.current = null;
9074
+ setMediaHover(null);
8131
9075
  document.querySelectorAll("[data-ohw-state-hovered]").forEach((el) => el.removeAttribute("data-ohw-state-hovered"));
8132
9076
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
8133
9077
  activeStateElRef.current = null;
8134
9078
  setToggleState(null);
8135
9079
  };
8136
- const handleAnimLock = (e) => {
8137
- if (e.data?.type !== "ow:anim-lock") return;
8138
- const { key } = e.data;
9080
+ const handleImageUploading = (e) => {
9081
+ if (e.data?.type !== "ow:image-uploading") return;
9082
+ const { key, uploading } = e.data;
9083
+ setUploadingRects((prev) => {
9084
+ if (!uploading) {
9085
+ if (!(key in prev)) return prev;
9086
+ const next = { ...prev };
9087
+ delete next[key];
9088
+ return next;
9089
+ }
9090
+ const el = document.querySelector(`[data-ohw-key="${key}"]`);
9091
+ if (!el) return prev;
9092
+ const r2 = getVisibleRect(el);
9093
+ return {
9094
+ ...prev,
9095
+ [key]: { rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height } }
9096
+ };
9097
+ });
8139
9098
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
8140
9099
  const track = el.closest("[data-ohw-hover-pause]");
8141
- if (track) {
9100
+ if (!track) return;
9101
+ if (uploading) {
8142
9102
  uploadLockedTracks.add(track);
8143
9103
  track.setAttribute("data-ohw-hover-paused", "");
9104
+ } else {
9105
+ uploadLockedTracks.delete(track);
9106
+ track.removeAttribute("data-ohw-hover-paused");
8144
9107
  }
8145
9108
  });
8146
9109
  };
@@ -8197,7 +9160,7 @@ function OhhwellsBridge() {
8197
9160
  return clientX >= r2.left && clientX <= r2.right && clientY >= r2.top && clientY <= r2.bottom;
8198
9161
  });
8199
9162
  if (stateCardImage) {
8200
- postToParentRef.current({ type: "ow:image-pick", key: stateCardImage.dataset.ohwKey ?? "" });
9163
+ postToParentRef.current({ type: "ow:image-pick", key: stateCardImage.dataset.ohwKey ?? "", elementType: stateCardImage.dataset.ohwEditable ?? "image" });
8201
9164
  return;
8202
9165
  }
8203
9166
  const textEditable = Array.from(
@@ -8220,6 +9183,32 @@ function OhhwellsBridge() {
8220
9183
  activateRef.current(textEditable, { caretX: clientX, caretY: clientY });
8221
9184
  return;
8222
9185
  }
9186
+ const navContainer = document.querySelector("[data-ohw-nav-container]");
9187
+ if (navContainer) {
9188
+ const r2 = navContainer.getBoundingClientRect();
9189
+ const pad = 8;
9190
+ const over = clientX >= r2.left - pad && clientX <= r2.right + pad && clientY >= r2.top - pad && clientY <= r2.bottom + pad;
9191
+ if (over && !isPointOverNavItem(navContainer, clientX, clientY)) {
9192
+ selectFrameRef.current(navContainer);
9193
+ return;
9194
+ }
9195
+ }
9196
+ const navRoot = document.querySelector("[data-ohw-nav-root]");
9197
+ if (navRoot && navContainer) {
9198
+ const rr = navRoot.getBoundingClientRect();
9199
+ if (clientX >= rr.left && clientX <= rr.right && clientY >= rr.top && clientY <= rr.bottom && !isPointOverNavItem(navContainer, clientX, clientY)) {
9200
+ const book = navRoot.querySelector('[data-ohw-role="navbar-button"]');
9201
+ if (book) {
9202
+ const br = book.getBoundingClientRect();
9203
+ if (clientX >= br.left && clientX <= br.right && clientY >= br.top && clientY <= br.bottom) {
9204
+ deactivateRef.current();
9205
+ return;
9206
+ }
9207
+ }
9208
+ selectFrameRef.current(navContainer);
9209
+ return;
9210
+ }
9211
+ }
8223
9212
  deactivateRef.current();
8224
9213
  };
8225
9214
  window.addEventListener("message", handleSave);
@@ -8229,8 +9218,7 @@ function OhhwellsBridge() {
8229
9218
  window.addEventListener("message", handleClearSchedulingWidget);
8230
9219
  window.addEventListener("message", handleRemoveSchedulingSection);
8231
9220
  window.addEventListener("message", handleImageUrl);
8232
- window.addEventListener("message", handleVideoSettings);
8233
- window.addEventListener("message", handleAnimLock);
9221
+ window.addEventListener("message", handleImageUploading);
8234
9222
  window.addEventListener("message", handleCanvasHeight);
8235
9223
  window.addEventListener("message", handleParentScroll);
8236
9224
  window.addEventListener("message", handlePointerSync);
@@ -8277,8 +9265,7 @@ function OhhwellsBridge() {
8277
9265
  window.removeEventListener("message", handleClearSchedulingWidget);
8278
9266
  window.removeEventListener("message", handleRemoveSchedulingSection);
8279
9267
  window.removeEventListener("message", handleImageUrl);
8280
- window.removeEventListener("message", handleVideoSettings);
8281
- window.removeEventListener("message", handleAnimLock);
9268
+ window.removeEventListener("message", handleImageUploading);
8282
9269
  window.removeEventListener("message", handleCanvasHeight);
8283
9270
  window.removeEventListener("message", handleParentScroll);
8284
9271
  window.removeEventListener("resize", handleViewportResize);
@@ -8292,7 +9279,7 @@ function OhhwellsBridge() {
8292
9279
  if (imageShowTimerRef.current) clearTimeout(imageShowTimerRef.current);
8293
9280
  };
8294
9281
  }, [isEditMode, refreshStateRules]);
8295
- (0, import_react6.useEffect)(() => {
9282
+ (0, import_react8.useEffect)(() => {
8296
9283
  const handler = (e) => {
8297
9284
  if (e.data?.type !== "ow:request-schedule-config") return;
8298
9285
  const insertAfterVal = e.data.insertAfter;
@@ -8308,7 +9295,7 @@ function OhhwellsBridge() {
8308
9295
  window.addEventListener("message", handler);
8309
9296
  return () => window.removeEventListener("message", handler);
8310
9297
  }, [processConfigRequest]);
8311
- (0, import_react6.useEffect)(() => {
9298
+ (0, import_react8.useEffect)(() => {
8312
9299
  if (!isEditMode) return;
8313
9300
  document.querySelectorAll("[data-ohw-active-state]").forEach((el) => {
8314
9301
  el.removeAttribute("data-ohw-active-state");
@@ -8329,27 +9316,27 @@ function OhhwellsBridge() {
8329
9316
  const next = { ...prev, [pathKey]: sections };
8330
9317
  return shouldUseDevFixtures() ? { ...DEV_SECTIONS_BY_PATH, ...next } : next;
8331
9318
  });
8332
- postToParent({ type: "ow:ready", version: "1", path: pathname, nodes: collectEditableNodes(), sections });
8333
- postToParent({ type: "ow:request-site-pages" });
9319
+ postToParent2({ type: "ow:ready", version: "1", path: pathname, nodes: collectEditableNodes(), sections });
9320
+ postToParent2({ type: "ow:request-site-pages" });
8334
9321
  }, 150);
8335
9322
  return () => {
8336
9323
  cancelAnimationFrame(raf);
8337
9324
  clearTimeout(timer);
8338
9325
  };
8339
- }, [pathname, isEditMode, refreshStateRules, postToParent]);
8340
- (0, import_react6.useEffect)(() => {
9326
+ }, [pathname, isEditMode, refreshStateRules, postToParent2]);
9327
+ (0, import_react8.useEffect)(() => {
8341
9328
  scrollToHashSectionWhenReady();
8342
9329
  const onHashChange = () => scrollToHashSectionWhenReady();
8343
9330
  window.addEventListener("hashchange", onHashChange);
8344
9331
  return () => window.removeEventListener("hashchange", onHashChange);
8345
9332
  }, [pathname]);
8346
- const handleCommand = (0, import_react6.useCallback)((cmd) => {
9333
+ const handleCommand = (0, import_react8.useCallback)((cmd) => {
8347
9334
  document.execCommand(cmd, false);
8348
9335
  activeElRef.current?.focus();
8349
9336
  if (activeElRef.current) setToolbarRect(getEditMeasureEl(activeElRef.current).getBoundingClientRect());
8350
9337
  refreshActiveCommandsRef.current();
8351
9338
  }, []);
8352
- const handleStateChange = (0, import_react6.useCallback)((state) => {
9339
+ const handleStateChange = (0, import_react8.useCallback)((state) => {
8353
9340
  if (!activeStateElRef.current) return;
8354
9341
  const el = activeStateElRef.current;
8355
9342
  if (state === "Default") {
@@ -8362,18 +9349,22 @@ function OhhwellsBridge() {
8362
9349
  }
8363
9350
  setToggleState((prev) => prev ? { ...prev, activeState: state } : null);
8364
9351
  }, [deactivate]);
8365
- const closeLinkPopover = (0, import_react6.useCallback)(() => setLinkPopover(null), []);
8366
- const openLinkPopoverForActive = (0, import_react6.useCallback)(() => {
9352
+ const closeLinkPopover = (0, import_react8.useCallback)(() => {
9353
+ addNavAfterAnchorRef.current = null;
9354
+ setLinkPopover(null);
9355
+ }, []);
9356
+ const openLinkPopoverForActive = (0, import_react8.useCallback)(() => {
8367
9357
  const hrefCtx = getHrefKeyFromElement(activeElRef.current);
8368
9358
  if (!hrefCtx) return;
8369
9359
  bumpLinkPopoverGrace();
8370
9360
  setLinkPopover({
8371
9361
  key: hrefCtx.key,
8372
- target: getLinkHref(hrefCtx.anchor)
9362
+ mode: "edit",
9363
+ target: getLinkHref2(hrefCtx.anchor)
8373
9364
  });
8374
9365
  deactivate();
8375
9366
  }, [deactivate]);
8376
- const openLinkPopoverForSelected = (0, import_react6.useCallback)(() => {
9367
+ const openLinkPopoverForSelected = (0, import_react8.useCallback)(() => {
8377
9368
  const anchor = selectedElRef.current;
8378
9369
  if (!anchor) return;
8379
9370
  const key = anchor.getAttribute("data-ohw-href-key");
@@ -8381,29 +9372,133 @@ function OhhwellsBridge() {
8381
9372
  bumpLinkPopoverGrace();
8382
9373
  setLinkPopover({
8383
9374
  key,
8384
- target: getLinkHref(anchor)
9375
+ mode: "edit",
9376
+ target: getLinkHref2(anchor)
8385
9377
  });
8386
9378
  deselect();
8387
9379
  }, [deselect]);
8388
- const handleLinkPopoverSubmit = (0, import_react6.useCallback)(
9380
+ const handleLinkPopoverSubmit = (0, import_react8.useCallback)(
8389
9381
  (target) => {
8390
- if (!linkPopover) return;
8391
- const { key } = linkPopover;
9382
+ const session = linkPopoverSessionRef.current;
9383
+ if (!session) return;
9384
+ if (session.intent === "add-nav") {
9385
+ const { pageRoute, sectionId } = parseTarget(target);
9386
+ const page = resolvePage(pageRoute, sitePages);
9387
+ const pageSections = getSectionsForPath(sectionsByPath, pageRoute);
9388
+ const section = sectionId ? pageSections.find((s) => s.id === sectionId) : void 0;
9389
+ const label = section?.label ?? page.title;
9390
+ const afterAnchor = addNavAfterAnchorRef.current;
9391
+ const { anchor, hrefKey, labelKey, index, order } = insertNavbarItem(target, label, afterAnchor);
9392
+ applyLinkByKey(hrefKey, target);
9393
+ document.querySelectorAll(`[data-ohw-key="${labelKey}"]`).forEach((el) => {
9394
+ el.textContent = label;
9395
+ });
9396
+ const navCount = String(index + 1);
9397
+ const orderJson = JSON.stringify(order);
9398
+ editContentRef.current = {
9399
+ ...editContentRef.current,
9400
+ [hrefKey]: target,
9401
+ [labelKey]: label,
9402
+ [NAV_COUNT_KEY]: navCount,
9403
+ [NAV_ORDER_KEY]: orderJson
9404
+ };
9405
+ postToParent2({
9406
+ type: "ow:change",
9407
+ nodes: [
9408
+ { key: hrefKey, text: target },
9409
+ { key: labelKey, text: label },
9410
+ { key: NAV_COUNT_KEY, text: navCount },
9411
+ { key: NAV_ORDER_KEY, text: orderJson }
9412
+ ]
9413
+ });
9414
+ addNavAfterAnchorRef.current = null;
9415
+ setLinkPopover(null);
9416
+ enforceLinkHrefs();
9417
+ requestAnimationFrame(() => {
9418
+ selectRef.current(anchor);
9419
+ });
9420
+ return;
9421
+ }
9422
+ const { key } = session;
8392
9423
  applyLinkByKey(key, target);
8393
- postToParent({ type: "ow:change", nodes: [{ key, text: target }] });
9424
+ postToParent2({ type: "ow:change", nodes: [{ key, text: target }] });
8394
9425
  setLinkPopover(null);
8395
9426
  },
8396
- [linkPopover, postToParent]
9427
+ [postToParent2, sitePages, sectionsByPath]
8397
9428
  );
8398
9429
  const showEditLink = toolbarShowEditLink;
8399
9430
  const currentSections = sectionsByPath[pathname] ?? [];
8400
9431
  linkPopoverOpenRef.current = linkPopover !== null;
8401
- return bridgeRoot ? (0, import_react_dom2.createPortal)(
8402
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
8403
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
8404
- siblingHintRect && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
8405
- hoveredItemRect && !siblingHintRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
8406
- toolbarRect && (toolbarVariant === "link-action" || toolbarVariant === "select-frame") && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
9432
+ const handleMediaReplace = (0, import_react8.useCallback)(
9433
+ (key) => {
9434
+ postToParent2({ type: "ow:image-pick", key, elementType: mediaHover?.elementType ?? "image" });
9435
+ },
9436
+ [postToParent2, mediaHover?.elementType]
9437
+ );
9438
+ const handleVideoSettingsChange = (0, import_react8.useCallback)(
9439
+ (key, settings) => {
9440
+ document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
9441
+ const video = getVideoEl(el);
9442
+ if (!video) return;
9443
+ if (typeof settings.autoplay === "boolean") video.autoplay = settings.autoplay;
9444
+ if (typeof settings.muted === "boolean") video.muted = settings.muted;
9445
+ syncVideoPlayback(video);
9446
+ postToParent2({
9447
+ type: "ow:change",
9448
+ nodes: [
9449
+ { key: `${key}${VIDEO_AUTOPLAY_SUFFIX}`, text: String(video.autoplay) },
9450
+ { key: `${key}${VIDEO_MUTED_SUFFIX}`, text: String(video.muted) }
9451
+ ]
9452
+ });
9453
+ setMediaHover(
9454
+ (prev) => prev && prev.key === key ? { ...prev, videoAutoplay: video.autoplay, videoMuted: video.muted } : prev
9455
+ );
9456
+ });
9457
+ },
9458
+ [postToParent2]
9459
+ );
9460
+ const handleMediaFadeOutComplete = (0, import_react8.useCallback)((key) => {
9461
+ setUploadingRects((prev) => {
9462
+ if (!(key in prev)) return prev;
9463
+ const next = { ...prev };
9464
+ delete next[key];
9465
+ return next;
9466
+ });
9467
+ }, []);
9468
+ return bridgeRoot ? (0, import_react_dom3.createPortal)(
9469
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
9470
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
9471
+ Object.entries(uploadingRects).map(([key, { rect, fadingOut }]) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
9472
+ MediaOverlay,
9473
+ {
9474
+ hover: { key, rect, elementType: "image", isDragOver: false, hasTextOverlap: false },
9475
+ isUploading: true,
9476
+ fadingOut,
9477
+ onFadeOutComplete: handleMediaFadeOutComplete,
9478
+ onReplace: handleMediaReplace
9479
+ },
9480
+ `uploading-${key}`
9481
+ )),
9482
+ mediaHover && !(mediaHover.key in uploadingRects) && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
9483
+ MediaOverlay,
9484
+ {
9485
+ hover: mediaHover,
9486
+ isUploading: false,
9487
+ onReplace: handleMediaReplace,
9488
+ onVideoSettingsChange: handleVideoSettingsChange
9489
+ }
9490
+ ),
9491
+ siblingHintRect && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
9492
+ hoveredNavContainerRect && toolbarVariant !== "select-frame" && !linkPopover && !isItemDragging && !siblingHintRect && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
9493
+ toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
9494
+ NavbarContainerChrome,
9495
+ {
9496
+ rect: toolbarRect,
9497
+ onAdd: handleAddTopLevelNavItem
9498
+ }
9499
+ ),
9500
+ hoveredItemRect && !hoveredNavContainerRect && !siblingHintRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
9501
+ toolbarRect && (toolbarVariant === "link-action" || toolbarVariant === "select-frame") && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8407
9502
  ItemInteractionLayer,
8408
9503
  {
8409
9504
  rect: toolbarRect,
@@ -8414,18 +9509,19 @@ function OhhwellsBridge() {
8414
9509
  dragHandleLabel: reorderHrefKey ? `Reorder ${reorderHrefKey}` : "Reorder item",
8415
9510
  onDragHandleDragStart: handleItemDragStart,
8416
9511
  onDragHandleDragEnd: handleItemDragEnd,
8417
- toolbar: toolbarVariant === "link-action" && !isItemDragging ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
9512
+ toolbar: toolbarVariant === "link-action" && !isItemDragging ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8418
9513
  ItemActionToolbar,
8419
9514
  {
8420
9515
  onEditLink: openLinkPopoverForSelected,
8421
- onAddItem: handleAddNavigationItem,
8422
- addItemDisabled: false
9516
+ addItemDisabled: true,
9517
+ editLinkDisabled: false,
9518
+ moreDisabled: true
8423
9519
  }
8424
9520
  ) : void 0
8425
9521
  }
8426
9522
  ),
8427
- toolbarRect && toolbarVariant === "rich-text" && /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
8428
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
9523
+ toolbarRect && toolbarVariant === "rich-text" && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
9524
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8429
9525
  EditGlowChrome,
8430
9526
  {
8431
9527
  rect: toolbarRect,
@@ -8434,7 +9530,7 @@ function OhhwellsBridge() {
8434
9530
  dragDisabled: reorderDragDisabled
8435
9531
  }
8436
9532
  ),
8437
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
9533
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8438
9534
  FloatingToolbar,
8439
9535
  {
8440
9536
  rect: toolbarRect,
@@ -8447,7 +9543,7 @@ function OhhwellsBridge() {
8447
9543
  }
8448
9544
  )
8449
9545
  ] }),
8450
- maxBadge && /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
9546
+ maxBadge && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
8451
9547
  "div",
8452
9548
  {
8453
9549
  "data-ohw-max-badge": "",
@@ -8473,7 +9569,7 @@ function OhhwellsBridge() {
8473
9569
  ]
8474
9570
  }
8475
9571
  ),
8476
- toggleState && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
9572
+ toggleState && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8477
9573
  StateToggle,
8478
9574
  {
8479
9575
  rect: toggleState.rect,
@@ -8482,15 +9578,15 @@ function OhhwellsBridge() {
8482
9578
  onStateChange: handleStateChange
8483
9579
  }
8484
9580
  ),
8485
- sectionGap && /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
9581
+ sectionGap && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
8486
9582
  "div",
8487
9583
  {
8488
9584
  "data-ohw-section-insert-line": "",
8489
9585
  className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
8490
9586
  style: { top: sectionGap.y, transform: "translateY(-50%)" },
8491
9587
  children: [
8492
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
8493
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
9588
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
9589
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8494
9590
  Badge,
8495
9591
  {
8496
9592
  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",
@@ -8503,35 +9599,36 @@ function OhhwellsBridge() {
8503
9599
  children: "Add Section"
8504
9600
  }
8505
9601
  ),
8506
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
9602
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
8507
9603
  ]
8508
9604
  }
8509
9605
  ),
8510
- linkPopover && dialogPortalContainer ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
9606
+ linkPopover && dialogPortalContainer ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8511
9607
  LinkPopover,
8512
9608
  {
8513
9609
  panelRef: linkPopoverPanelRef,
8514
9610
  portalContainer: dialogPortalContainer,
8515
9611
  open: true,
8516
- mode: "edit",
9612
+ mode: linkPopover.mode ?? "edit",
8517
9613
  pages: sitePages,
8518
9614
  sections: currentSections,
8519
9615
  sectionsByPath,
8520
9616
  initialTarget: linkPopover.target,
9617
+ existingTargets: linkPopover.intent === "add-nav" ? getNavbarExistingTargets() : [],
8521
9618
  onClose: closeLinkPopover,
8522
9619
  onSubmit: handleLinkPopoverSubmit
8523
9620
  },
8524
- `${linkPopover.key}-${pathname}`
9621
+ linkPopover.key
8525
9622
  ) : null,
8526
- sectionGap && /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
9623
+ sectionGap && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
8527
9624
  "div",
8528
9625
  {
8529
9626
  "data-ohw-section-insert-line": "",
8530
9627
  className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
8531
9628
  style: { top: sectionGap.y, transform: "translateY(-50%)" },
8532
9629
  children: [
8533
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
8534
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
9630
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
9631
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8535
9632
  Badge,
8536
9633
  {
8537
9634
  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",
@@ -8544,7 +9641,7 @@ function OhhwellsBridge() {
8544
9641
  children: "Add Section"
8545
9642
  }
8546
9643
  ),
8547
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
9644
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
8548
9645
  ]
8549
9646
  }
8550
9647
  )