@ohhwells/bridge 0.1.39 → 0.1.41

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") ?? "";
@@ -4408,6 +4411,7 @@ function ItemActionToolbar({
4408
4411
  onEditLink,
4409
4412
  onAddItem,
4410
4413
  onMore,
4414
+ editLinkDisabled = false,
4411
4415
  addItemDisabled = true,
4412
4416
  moreDisabled = true,
4413
4417
  tooltipSide = "bottom"
@@ -4416,10 +4420,12 @@ function ItemActionToolbar({
4416
4420
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4417
4421
  ToolbarActionTooltip,
4418
4422
  {
4419
- label: "Add link",
4423
+ label: "Edit link",
4420
4424
  side: tooltipSide,
4425
+ disabled: editLinkDisabled,
4421
4426
  buttonProps: {
4422
4427
  onMouseDown: (e) => {
4428
+ if (editLinkDisabled) return;
4423
4429
  e.preventDefault();
4424
4430
  e.stopPropagation();
4425
4431
  onEditLink?.();
@@ -4806,8 +4812,8 @@ function MediaOverlay({
4806
4812
  }
4807
4813
 
4808
4814
  // src/OhhwellsBridge.tsx
4809
- var import_react_dom2 = require("react-dom");
4810
- var import_navigation = require("next/navigation");
4815
+ var import_react_dom3 = require("react-dom");
4816
+ var import_navigation2 = require("next/navigation");
4811
4817
 
4812
4818
  // src/lib/session-search.ts
4813
4819
  var STORAGE_KEY = "ohw-preserved-search";
@@ -4837,6 +4843,7 @@ function parseSectionsFromRoot(root) {
4837
4843
  for (const el of root.querySelectorAll("[data-ohw-section]")) {
4838
4844
  const id = el.getAttribute("data-ohw-section") ?? "";
4839
4845
  if (!id || seen.has(id)) continue;
4846
+ if (el.parentElement?.closest("[data-ohw-section]")) continue;
4840
4847
  seen.add(id);
4841
4848
  const label = el.getAttribute("data-ohw-section-label") ?? titleCaseSectionId(id);
4842
4849
  sections.push({ id, label });
@@ -5083,61 +5090,29 @@ function scrollToHashSectionWhenReady(behavior = "smooth") {
5083
5090
  tick();
5084
5091
  }
5085
5092
 
5093
+ // src/ui/link-modal/LinkPopover.tsx
5094
+ var import_react7 = require("react");
5095
+
5086
5096
  // src/ui/dialog.tsx
5087
5097
  var React7 = __toESM(require("react"), 1);
5088
5098
  var import_radix_ui5 = require("radix-ui");
5089
-
5090
- // src/ui/icons.tsx
5099
+ var import_lucide_react4 = require("lucide-react");
5091
5100
  var import_jsx_runtime12 = require("react/jsx-runtime");
5092
- function IconX({ className, ...props }) {
5093
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
5094
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("path", { d: "M18 6 6 18" }),
5095
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("path", { d: "m6 6 12 12" })
5096
- ] });
5097
- }
5098
- function IconChevronDown({ className, ...props }) {
5099
- return /* @__PURE__ */ (0, import_jsx_runtime12.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_runtime12.jsx)("path", { d: "m6 9 6 6 6-6" }) });
5100
- }
5101
- function IconFile({ className, ...props }) {
5102
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
5103
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }),
5104
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("path", { d: "M14 2v4a2 2 0 0 0 2 2h4" })
5105
- ] });
5106
- }
5107
- function IconInfo({ className, ...props }) {
5108
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
5109
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
5110
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("path", { d: "M12 16v-4" }),
5111
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("path", { d: "M12 8h.01" })
5112
- ] });
5113
- }
5114
- function IconArrowRight({ className, ...props }) {
5115
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
5116
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("path", { d: "M5 12h14" }),
5117
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("path", { d: "m12 5 7 7-7 7" })
5118
- ] });
5119
- }
5120
- function IconSection({ className, ...props }) {
5121
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
5122
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("rect", { width: "18", height: "7", x: "3", y: "3", rx: "1" }),
5123
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("rect", { width: "9", height: "7", x: "3", y: "14", rx: "1" }),
5124
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("rect", { width: "5", height: "7", x: "16", y: "14", rx: "1" })
5125
- ] });
5126
- }
5127
-
5128
- // src/ui/dialog.tsx
5129
- var import_jsx_runtime13 = require("react/jsx-runtime");
5130
- function Dialog2({ ...props }) {
5131
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_radix_ui5.Dialog.Root, { "data-slot": "dialog", ...props });
5101
+ function Dialog2({
5102
+ ...props
5103
+ }) {
5104
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_radix_ui5.Dialog.Root, { "data-slot": "dialog", ...props });
5132
5105
  }
5133
- function DialogPortal({ ...props }) {
5134
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_radix_ui5.Dialog.Portal, { ...props });
5106
+ function DialogPortal({
5107
+ ...props
5108
+ }) {
5109
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_radix_ui5.Dialog.Portal, { ...props });
5135
5110
  }
5136
5111
  function DialogOverlay({
5137
5112
  className,
5138
5113
  ...props
5139
5114
  }) {
5140
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5115
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
5141
5116
  import_radix_ui5.Dialog.Overlay,
5142
5117
  {
5143
5118
  "data-slot": "dialog-overlay",
@@ -5147,57 +5122,74 @@ function DialogOverlay({
5147
5122
  }
5148
5123
  );
5149
5124
  }
5150
- var DialogContent = React7.forwardRef(({ className, children, showCloseButton = true, container, ...props }, ref) => {
5151
- const positionMode = container ? "absolute" : "fixed";
5152
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(DialogPortal, { container: container ?? void 0, children: [
5153
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(DialogOverlay, { className: cn(positionMode, "inset-0") }),
5154
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
5155
- import_radix_ui5.Dialog.Content,
5156
- {
5157
- ref,
5158
- "data-slot": "dialog-content",
5159
- className: cn(
5160
- positionMode,
5161
- "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",
5162
- "rounded-xl border border-border bg-background font-sans shadow-lg outline-none",
5163
- container ? "max-h-[calc(100%-32px)] max-w-[calc(100%-16px)]" : "max-h-[calc(100vh-32px)] max-w-[calc(100vw-16px)]",
5164
- className
5165
- ),
5166
- onMouseDown: (e) => e.stopPropagation(),
5167
- ...props,
5168
- children: [
5169
- children,
5170
- showCloseButton ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5171
- import_radix_ui5.Dialog.Close,
5172
- {
5173
- type: "button",
5174
- className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
5175
- "aria-label": "Close",
5176
- children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(IconX, { "aria-hidden": true })
5177
- }
5178
- ) : null
5179
- ]
5180
- }
5181
- )
5182
- ] });
5183
- });
5125
+ var DialogContent = React7.forwardRef(
5126
+ ({ className, children, showCloseButton = true, container, ...props }, ref) => {
5127
+ const positionMode = container ? "absolute" : "fixed";
5128
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(DialogPortal, { container: container ?? void 0, children: [
5129
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(DialogOverlay, { className: cn(positionMode, "inset-0") }),
5130
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
5131
+ import_radix_ui5.Dialog.Content,
5132
+ {
5133
+ ref,
5134
+ "data-slot": "dialog-content",
5135
+ className: cn(
5136
+ positionMode,
5137
+ "left-1/2 top-1/2 z-[2147483647] flex w-full max-w-[448px] -translate-x-1/2 -translate-y-1/2 flex-col overflow-visible",
5138
+ "rounded-xl border border-border bg-background font-sans shadow-lg outline-none",
5139
+ container ? "max-h-[calc(100%-32px)]" : "max-h-[calc(100vh-32px)]",
5140
+ className
5141
+ ),
5142
+ onMouseDown: (e) => e.stopPropagation(),
5143
+ ...props,
5144
+ children: [
5145
+ children,
5146
+ showCloseButton ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
5147
+ import_radix_ui5.Dialog.Close,
5148
+ {
5149
+ type: "button",
5150
+ className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
5151
+ "aria-label": "Close",
5152
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react4.X, { size: 16, "aria-hidden": true })
5153
+ }
5154
+ ) : null
5155
+ ]
5156
+ }
5157
+ )
5158
+ ] });
5159
+ }
5160
+ );
5184
5161
  DialogContent.displayName = import_radix_ui5.Dialog.Content.displayName;
5185
- function DialogHeader({ className, ...props }) {
5186
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: cn("flex flex-col gap-1.5", className), ...props });
5162
+ function DialogHeader({
5163
+ className,
5164
+ ...props
5165
+ }) {
5166
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: cn("flex flex-col gap-1.5", className), ...props });
5187
5167
  }
5188
- function DialogFooter({ className, ...props }) {
5189
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: cn("flex items-center justify-end gap-2", className), ...props });
5168
+ function DialogFooter({
5169
+ className,
5170
+ ...props
5171
+ }) {
5172
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
5173
+ "div",
5174
+ {
5175
+ className: cn("flex items-center justify-end gap-2", className),
5176
+ ...props
5177
+ }
5178
+ );
5190
5179
  }
5191
- var DialogTitle = React7.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5180
+ var DialogTitle = React7.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
5192
5181
  import_radix_ui5.Dialog.Title,
5193
5182
  {
5194
5183
  ref,
5195
- className: cn("text-lg font-semibold leading-none tracking-tight text-card-foreground", className),
5184
+ className: cn(
5185
+ "text-lg font-semibold leading-none tracking-tight text-card-foreground",
5186
+ className
5187
+ ),
5196
5188
  ...props
5197
5189
  }
5198
5190
  ));
5199
5191
  DialogTitle.displayName = import_radix_ui5.Dialog.Title.displayName;
5200
- var DialogDescription = React7.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5192
+ var DialogDescription = React7.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
5201
5193
  import_radix_ui5.Dialog.Description,
5202
5194
  {
5203
5195
  ref,
@@ -5208,38 +5200,64 @@ var DialogDescription = React7.forwardRef(({ className, ...props }, ref) => /* @
5208
5200
  DialogDescription.displayName = import_radix_ui5.Dialog.Description.displayName;
5209
5201
  var DialogClose = import_radix_ui5.Dialog.Close;
5210
5202
 
5203
+ // src/ui/link-modal/LinkEditorPanel.tsx
5204
+ var import_lucide_react8 = require("lucide-react");
5205
+
5211
5206
  // src/ui/link-modal/DestinationBreadcrumb.tsx
5212
- var import_jsx_runtime14 = require("react/jsx-runtime");
5213
- function DestinationBreadcrumb({ pageTitle, sectionLabel }) {
5214
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex w-full flex-col gap-2", children: [
5215
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { className: "text-sm font-medium text-foreground", children: "Destination" }),
5216
- /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex items-center gap-3", children: [
5217
- /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex items-center gap-2", children: [
5218
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(IconFile, { className: "shrink-0 text-foreground", "aria-hidden": true }),
5219
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "text-sm font-medium leading-none text-foreground", children: pageTitle })
5207
+ var import_lucide_react5 = require("lucide-react");
5208
+ var import_jsx_runtime13 = require("react/jsx-runtime");
5209
+ function DestinationBreadcrumb({
5210
+ pageTitle,
5211
+ sectionLabel
5212
+ }) {
5213
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex w-full flex-col gap-2", children: [
5214
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { className: "text-sm font-medium! text-foreground m-0", children: "Destination" }),
5215
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-center gap-3", children: [
5216
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-center gap-2", children: [
5217
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react5.File, { size: 16, className: "shrink-0 text-foreground", "aria-hidden": true }),
5218
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "text-sm font-medium leading-none text-foreground!", children: pageTitle })
5220
5219
  ] }),
5221
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(IconArrowRight, { className: "shrink-0 text-muted-foreground", "aria-hidden": true }),
5222
- /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [
5223
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(IconSection, { className: "shrink-0 text-foreground", "aria-hidden": true }),
5224
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "truncate text-sm font-medium leading-none text-foreground", children: sectionLabel })
5220
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5221
+ import_lucide_react5.ArrowRight,
5222
+ {
5223
+ size: 16,
5224
+ className: "shrink-0 text-muted-foreground",
5225
+ "aria-hidden": true
5226
+ }
5227
+ ),
5228
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [
5229
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5230
+ import_lucide_react5.GalleryVertical,
5231
+ {
5232
+ size: 16,
5233
+ className: "shrink-0 text-foreground",
5234
+ "aria-hidden": true
5235
+ }
5236
+ ),
5237
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "truncate text-sm font-medium leading-none text-foreground", children: sectionLabel })
5225
5238
  ] })
5226
5239
  ] })
5227
5240
  ] });
5228
5241
  }
5229
5242
 
5230
5243
  // src/ui/link-modal/SectionTreeItem.tsx
5231
- var import_jsx_runtime15 = require("react/jsx-runtime");
5232
- function SectionTreeItem({ section, onSelect, selected }) {
5244
+ var import_lucide_react6 = require("lucide-react");
5245
+ var import_jsx_runtime14 = require("react/jsx-runtime");
5246
+ function SectionTreeItem({
5247
+ section,
5248
+ onSelect,
5249
+ selected
5250
+ }) {
5233
5251
  const interactive = Boolean(onSelect);
5234
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex h-9 w-full items-end pl-3", children: [
5235
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
5252
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex h-9 w-full items-end pl-3", children: [
5253
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
5236
5254
  "div",
5237
5255
  {
5238
5256
  className: "mr-[-1px] h-9 w-2 shrink-0 rounded-bl-sm border-b border-l border-border mb-4",
5239
5257
  "aria-hidden": true
5240
5258
  }
5241
5259
  ),
5242
- /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
5260
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
5243
5261
  "div",
5244
5262
  {
5245
5263
  role: interactive ? "button" : void 0,
@@ -5257,32 +5275,30 @@ function SectionTreeItem({ section, onSelect, selected }) {
5257
5275
  interactive && selected && "border-primary"
5258
5276
  ),
5259
5277
  children: [
5260
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(IconSection, { className: "shrink-0 text-foreground", "aria-hidden": true }),
5261
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "truncate text-sm font-normal leading-5 text-foreground", children: section.label })
5278
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
5279
+ import_lucide_react6.GalleryVertical,
5280
+ {
5281
+ size: 16,
5282
+ className: "shrink-0 text-foreground",
5283
+ "aria-hidden": true
5284
+ }
5285
+ ),
5286
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "truncate text-sm font-normal leading-5 text-foreground", children: section.label })
5262
5287
  ]
5263
5288
  }
5264
5289
  )
5265
5290
  ] });
5266
5291
  }
5267
- function SectionPickerList({ sections, onSelect }) {
5268
- if (sections.length === 0) {
5269
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: "text-sm text-muted-foreground", children: "No sections found on this page." });
5270
- }
5271
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex flex-col gap-1", children: [
5272
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: "text-sm text-muted-foreground", children: "Pick a section this link should scroll to." }),
5273
- sections.map((section) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(SectionTreeItem, { section, onSelect }, section.id))
5274
- ] });
5275
- }
5276
5292
 
5277
5293
  // src/ui/link-modal/UrlOrPageInput.tsx
5278
5294
  var import_react4 = require("react");
5279
5295
 
5280
5296
  // src/ui/input.tsx
5281
5297
  var React8 = __toESM(require("react"), 1);
5282
- var import_jsx_runtime16 = require("react/jsx-runtime");
5298
+ var import_jsx_runtime15 = require("react/jsx-runtime");
5283
5299
  var Input = React8.forwardRef(
5284
5300
  ({ className, type, ...props }, ref) => {
5285
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
5301
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
5286
5302
  "input",
5287
5303
  {
5288
5304
  type,
@@ -5301,9 +5317,9 @@ Input.displayName = "Input";
5301
5317
 
5302
5318
  // src/ui/label.tsx
5303
5319
  var import_radix_ui6 = require("radix-ui");
5304
- var import_jsx_runtime17 = require("react/jsx-runtime");
5320
+ var import_jsx_runtime16 = require("react/jsx-runtime");
5305
5321
  function Label({ className, ...props }) {
5306
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5322
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
5307
5323
  import_radix_ui6.Label.Root,
5308
5324
  {
5309
5325
  "data-slot": "label",
@@ -5314,9 +5330,12 @@ function Label({ className, ...props }) {
5314
5330
  }
5315
5331
 
5316
5332
  // src/ui/link-modal/UrlOrPageInput.tsx
5317
- var import_jsx_runtime18 = require("react/jsx-runtime");
5318
- function FieldChevron({ onClick }) {
5319
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5333
+ var import_lucide_react7 = require("lucide-react");
5334
+ var import_jsx_runtime17 = require("react/jsx-runtime");
5335
+ function FieldChevron({
5336
+ onClick
5337
+ }) {
5338
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5320
5339
  "button",
5321
5340
  {
5322
5341
  type: "button",
@@ -5324,7 +5343,7 @@ function FieldChevron({ onClick }) {
5324
5343
  onClick,
5325
5344
  "aria-label": "Open page list",
5326
5345
  tabIndex: -1,
5327
- children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(IconChevronDown, {})
5346
+ children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react7.ChevronDown, { size: 16 })
5328
5347
  }
5329
5348
  );
5330
5349
  }
@@ -5342,7 +5361,29 @@ function UrlOrPageInput({
5342
5361
  }) {
5343
5362
  const inputId = (0, import_react4.useId)();
5344
5363
  const inputRef = (0, import_react4.useRef)(null);
5364
+ const rootRef = (0, import_react4.useRef)(null);
5345
5365
  const [isFocused, setIsFocused] = (0, import_react4.useState)(false);
5366
+ (0, import_react4.useEffect)(() => {
5367
+ if (!dropdownOpen) return;
5368
+ const isOutside = (e) => {
5369
+ const root = rootRef.current;
5370
+ if (!root) return true;
5371
+ const path = typeof e.composedPath === "function" ? e.composedPath() : [];
5372
+ if (path.includes(root)) return false;
5373
+ const target = e.target;
5374
+ return !(target instanceof Node && root.contains(target));
5375
+ };
5376
+ const closeIfOutside = (e) => {
5377
+ if (!isOutside(e)) return;
5378
+ onDropdownOpenChange(false);
5379
+ };
5380
+ document.addEventListener("pointerdown", closeIfOutside, true);
5381
+ document.addEventListener("mousedown", closeIfOutside, true);
5382
+ return () => {
5383
+ document.removeEventListener("pointerdown", closeIfOutside, true);
5384
+ document.removeEventListener("mousedown", closeIfOutside, true);
5385
+ };
5386
+ }, [dropdownOpen, onDropdownOpenChange]);
5346
5387
  const openDropdown = () => {
5347
5388
  if (readOnly || filteredPages.length === 0) return;
5348
5389
  onDropdownOpenChange(true);
@@ -5360,15 +5401,22 @@ function UrlOrPageInput({
5360
5401
  requestAnimationFrame(() => inputRef.current?.focus());
5361
5402
  };
5362
5403
  const fieldClassName = cn(
5363
- "data-ohw-link-field flex h-10 w-full items-center overflow-hidden rounded-md border bg-background pl-3 pr-3 py-2 outline-none transition-[border-color,box-shadow]",
5404
+ "data-ohw-link-field flex h-[36px] w-full items-center overflow-hidden rounded-md border bg-background pl-3 pr-3 py-2 outline-none transition-[border-color,box-shadow]",
5364
5405
  urlError ? "border-destructive shadow-[0_0_0_1px_var(--ohw-destructive)]" : isFocused ? "border-primary shadow-[0_0_0_1px_var(--ohw-primary)]" : "border-input"
5365
5406
  );
5366
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex w-full flex-col gap-2 p-0", children: [
5367
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Label, { htmlFor: inputId, className: cn(urlError && "text-destructive"), children: "Destination" }),
5368
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "relative w-full", children: [
5369
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { "data-ohw-link-field": true, className: fieldClassName, children: [
5370
- selectedPage ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "flex shrink-0 items-center pr-2", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(IconFile, { className: "text-foreground", "aria-hidden": true }) }) : null,
5371
- readOnly ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "min-w-0 flex-1 truncate text-sm leading-5 text-foreground", children: selectedPage?.title ?? value }) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5407
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex w-full flex-col gap-2 p-0", children: [
5408
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Label, { htmlFor: inputId, className: cn(urlError && "text-destructive"), children: "Destination" }),
5409
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { ref: rootRef, className: "relative w-full", children: [
5410
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { "data-ohw-link-field": true, className: fieldClassName, children: [
5411
+ selectedPage ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "flex shrink-0 items-center pr-2", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5412
+ import_lucide_react7.File,
5413
+ {
5414
+ size: 16,
5415
+ className: "shrink-0 text-foreground",
5416
+ "aria-hidden": true
5417
+ }
5418
+ ) }) : null,
5419
+ 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)(
5372
5420
  Input,
5373
5421
  {
5374
5422
  ref: inputRef,
@@ -5379,7 +5427,14 @@ function UrlOrPageInput({
5379
5427
  setIsFocused(true);
5380
5428
  if (!selectedPage) openDropdown();
5381
5429
  },
5382
- onBlur: () => setIsFocused(false),
5430
+ onBlur: () => {
5431
+ setIsFocused(false);
5432
+ requestAnimationFrame(() => {
5433
+ if (!rootRef.current?.contains(document.activeElement)) {
5434
+ onDropdownOpenChange(false);
5435
+ }
5436
+ });
5437
+ },
5383
5438
  placeholder,
5384
5439
  className: cn(
5385
5440
  "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",
@@ -5387,7 +5442,7 @@ function UrlOrPageInput({
5387
5442
  )
5388
5443
  }
5389
5444
  ),
5390
- selectedPage && !readOnly ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5445
+ selectedPage && !readOnly ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5391
5446
  "button",
5392
5447
  {
5393
5448
  type: "button",
@@ -5395,26 +5450,26 @@ function UrlOrPageInput({
5395
5450
  onMouseDown: clearSelection,
5396
5451
  "aria-label": "Clear selected page",
5397
5452
  tabIndex: -1,
5398
- children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(IconX, { "aria-hidden": true })
5453
+ children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react7.X, { size: 16, "aria-hidden": true })
5399
5454
  }
5400
5455
  ) : null,
5401
- !readOnly ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(FieldChevron, { onClick: toggleDropdown }) : null
5456
+ !readOnly ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(FieldChevron, { onClick: toggleDropdown }) : null
5402
5457
  ] }),
5403
- dropdownOpen && !readOnly && filteredPages.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5458
+ dropdownOpen && !readOnly && filteredPages.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5404
5459
  "div",
5405
5460
  {
5406
5461
  "data-ohw-link-page-dropdown": "",
5407
- 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",
5462
+ 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",
5408
5463
  onMouseDown: (e) => e.preventDefault(),
5409
- children: filteredPages.map((page) => /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
5464
+ children: filteredPages.map((page) => /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
5410
5465
  "button",
5411
5466
  {
5412
5467
  type: "button",
5413
5468
  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",
5414
5469
  onClick: () => onPageSelect(page),
5415
5470
  children: [
5416
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(IconFile, { className: "shrink-0", "aria-hidden": true }),
5417
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "truncate", children: page.title })
5471
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react7.File, { size: 16, className: "shrink-0", "aria-hidden": true }),
5472
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "truncate", children: page.title })
5418
5473
  ]
5419
5474
  },
5420
5475
  page.path
@@ -5422,108 +5477,539 @@ function UrlOrPageInput({
5422
5477
  }
5423
5478
  ) : null
5424
5479
  ] }),
5425
- urlError ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { className: "text-sm font-medium text-destructive", children: urlError }) : null
5480
+ urlError ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("p", { className: "text-sm font-medium text-destructive", children: urlError }) : null
5426
5481
  ] });
5427
5482
  }
5428
5483
 
5429
- // src/ui/link-modal/useLinkModalState.ts
5484
+ // src/ui/link-modal/LinkEditorPanel.tsx
5485
+ var import_jsx_runtime18 = require("react/jsx-runtime");
5486
+ function LinkEditorPanel({ state, onClose }) {
5487
+ const isCancel = state.secondaryLabel === "Cancel" || state.secondaryLabel === "Back to sections";
5488
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [
5489
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(DialogClose, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5490
+ "button",
5491
+ {
5492
+ type: "button",
5493
+ className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50 h-7",
5494
+ "aria-label": "Close",
5495
+ onClick: onClose,
5496
+ children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_lucide_react8.X, { size: 16, "aria-hidden": true })
5497
+ }
5498
+ ) }),
5499
+ /* @__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 }) }),
5500
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex w-full flex-col gap-3 px-6 pb-8 pt-1", children: [
5501
+ state.showBreadcrumb && state.selectedPage && state.selectedSection ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5502
+ DestinationBreadcrumb,
5503
+ {
5504
+ pageTitle: state.selectedPage.title,
5505
+ sectionLabel: state.selectedSection.label
5506
+ }
5507
+ ) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5508
+ UrlOrPageInput,
5509
+ {
5510
+ value: state.searchValue,
5511
+ selectedPage: state.selectedPage,
5512
+ dropdownOpen: state.dropdownOpen,
5513
+ onDropdownOpenChange: state.setDropdownOpen,
5514
+ filteredPages: state.filteredPages,
5515
+ onInputChange: state.handleInputChange,
5516
+ onPageSelect: state.handlePageSelect,
5517
+ urlError: state.urlError
5518
+ }
5519
+ ),
5520
+ state.showChooseSection ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex flex-col justify-center gap-2", children: [
5521
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5522
+ Button,
5523
+ {
5524
+ type: "button",
5525
+ variant: "outline",
5526
+ className: "w-fit cursor-pointer",
5527
+ size: "sm",
5528
+ onClick: state.handleChooseSection,
5529
+ children: "Choose a section"
5530
+ }
5531
+ ),
5532
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex items-center gap-1 text-sm text-muted-foreground", children: [
5533
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_lucide_react8.Info, { size: 16, className: "shrink-0", "aria-hidden": true }),
5534
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { children: "Pick a section this link should scroll to." })
5535
+ ] })
5536
+ ] }) : null,
5537
+ state.showSectionRow && state.selectedSection ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(SectionTreeItem, { section: state.selectedSection, selected: true }) : null
5538
+ ] }),
5539
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(DialogFooter, { className: "w-full px-6 pb-6", children: [
5540
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5541
+ Button,
5542
+ {
5543
+ type: "button",
5544
+ variant: isCancel ? "outline" : "ghost",
5545
+ className: "leading-6 shadow-none cursor-pointer",
5546
+ style: isCancel ? {
5547
+ backgroundColor: "var(--ohw-background)",
5548
+ borderColor: "var(--ohw-border)",
5549
+ color: "var(--ohw-foreground)"
5550
+ } : void 0,
5551
+ onClick: state.handleSecondary,
5552
+ children: state.secondaryLabel
5553
+ }
5554
+ ),
5555
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5556
+ Button,
5557
+ {
5558
+ type: "button",
5559
+ className: "border-0 leading-6 shadow-none hover:opacity-90 disabled:opacity-50 cursor-pointer",
5560
+ style: {
5561
+ backgroundColor: "var(--ohw-primary)",
5562
+ color: "var(--ohw-primary-foreground)"
5563
+ },
5564
+ disabled: !state.isValid,
5565
+ onClick: state.handleSubmit,
5566
+ children: state.submitLabel
5567
+ }
5568
+ )
5569
+ ] })
5570
+ ] });
5571
+ }
5572
+
5573
+ // src/ui/link-modal/SectionPickerOverlay.tsx
5430
5574
  var import_react5 = require("react");
5431
- function useLinkModalState({
5432
- open,
5433
- mode,
5434
- pages,
5435
- sections: _sections,
5436
- sectionsByPath,
5437
- initialTarget,
5438
- existingTargets,
5439
- onClose,
5440
- onSubmit
5441
- }) {
5442
- const availablePages = (0, import_react5.useMemo)(
5443
- () => mode === "create" ? filterAvailablePages(pages, existingTargets) : pages,
5444
- [mode, pages, existingTargets]
5575
+ var import_react_dom = require("react-dom");
5576
+ var import_lucide_react9 = require("lucide-react");
5577
+ var import_navigation = require("next/navigation");
5578
+ var import_jsx_runtime19 = require("react/jsx-runtime");
5579
+ var DIM_OVERLAY = "rgba(0, 0, 0, 0.45)";
5580
+ function rectsEqual(a, b) {
5581
+ if (a.size !== b.size) return false;
5582
+ for (const [id, rect] of a) {
5583
+ const other = b.get(id);
5584
+ if (!other || rect.top !== other.top || rect.left !== other.left || rect.width !== other.width || rect.height !== other.height) {
5585
+ return false;
5586
+ }
5587
+ }
5588
+ return true;
5589
+ }
5590
+ function readSectionRects(sectionIds) {
5591
+ const next = /* @__PURE__ */ new Map();
5592
+ for (const id of sectionIds) {
5593
+ const el = document.querySelector(
5594
+ `[data-ohw-section="${CSS.escape(id)}"]`
5595
+ );
5596
+ if (el) next.set(id, el.getBoundingClientRect());
5597
+ }
5598
+ return next;
5599
+ }
5600
+ function hitTestSectionId(x, y, sectionIds, rects) {
5601
+ for (const id of sectionIds) {
5602
+ const rect = rects.get(id);
5603
+ if (!rect || rect.width <= 0 || rect.height <= 0) continue;
5604
+ if (x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom) {
5605
+ return id;
5606
+ }
5607
+ }
5608
+ return null;
5609
+ }
5610
+ function useSectionRects(sectionIdsKey, enabled) {
5611
+ const [rects, setRects] = (0, import_react5.useState)(/* @__PURE__ */ new Map());
5612
+ const sectionIds = (0, import_react5.useMemo)(
5613
+ () => sectionIdsKey ? sectionIdsKey.split("\0") : [],
5614
+ [sectionIdsKey]
5445
5615
  );
5446
- const [searchValue, setSearchValue] = (0, import_react5.useState)("");
5447
- const [selectedPage, setSelectedPage] = (0, import_react5.useState)(null);
5448
- const [selectedSection, setSelectedSection] = (0, import_react5.useState)(null);
5449
- const [step, setStep] = (0, import_react5.useState)("input");
5450
- const [dropdownOpen, setDropdownOpen] = (0, import_react5.useState)(false);
5451
- const [urlError, setUrlError] = (0, import_react5.useState)("");
5452
- const reset = (0, import_react5.useCallback)(() => {
5453
- setSearchValue("");
5454
- setSelectedPage(null);
5455
- setSelectedSection(null);
5456
- setStep("input");
5457
- setDropdownOpen(false);
5458
- setUrlError("");
5459
- }, []);
5460
5616
  (0, import_react5.useEffect)(() => {
5461
- if (!open) return;
5462
- if (mode === "edit" && initialTarget) {
5463
- const { pageRoute } = parseTarget(initialTarget);
5464
- const targetSections = getSectionsForPath(sectionsByPath, pageRoute);
5465
- const init = getEditModeInitialState(initialTarget, pages, targetSections);
5466
- setSearchValue(init.searchValue);
5467
- setSelectedPage(init.selectedPage);
5468
- setSelectedSection(init.selectedSection);
5469
- setStep(init.step);
5470
- } else {
5471
- reset();
5617
+ if (!enabled || sectionIds.length === 0) {
5618
+ setRects((prev) => prev.size === 0 ? prev : /* @__PURE__ */ new Map());
5619
+ return;
5472
5620
  }
5473
- setDropdownOpen(false);
5474
- setUrlError("");
5475
- }, [open, mode, initialTarget, pages, sectionsByPath, reset]);
5476
- const filteredPages = (0, import_react5.useMemo)(() => {
5477
- if (!searchValue.trim()) return availablePages;
5478
- return availablePages.filter((p) => p.title.toLowerCase().startsWith(searchValue.toLowerCase()));
5479
- }, [availablePages, searchValue]);
5480
- const activeSections = (0, import_react5.useMemo)(() => {
5481
- if (!selectedPage) return [];
5482
- return getSectionsForPath(sectionsByPath, selectedPage.path);
5483
- }, [selectedPage, sectionsByPath]);
5484
- const showBreadcrumb = step === "confirmed" && selectedPage && selectedSection;
5485
- const showSectionPicker = step === "sectionPicker" && selectedPage;
5486
- const showSectionRow = mode === "edit" && selectedPage && selectedSection && step === "input";
5487
- const showChooseSection = step === "input" && selectedPage && !selectedSection && !showSectionRow && activeSections.length > 0;
5488
- const handleInputChange = (value) => {
5489
- setSearchValue(value);
5490
- setSelectedPage(null);
5491
- setSelectedSection(null);
5492
- setStep("input");
5493
- setUrlError("");
5494
- const filtered = value.trim() ? availablePages.filter((p) => p.title.toLowerCase().startsWith(value.toLowerCase())) : availablePages;
5495
- setDropdownOpen(filtered.length > 0);
5496
- if (value.trim()) {
5497
- setUrlError(validateUrlInput(value, availablePages, pages));
5621
+ const update = () => {
5622
+ const next = readSectionRects(sectionIds);
5623
+ setRects((prev) => rectsEqual(prev, next) ? prev : next);
5624
+ };
5625
+ update();
5626
+ const opts = { capture: true, passive: true };
5627
+ window.addEventListener("scroll", update, opts);
5628
+ window.addEventListener("resize", update);
5629
+ const vv = window.visualViewport;
5630
+ vv?.addEventListener("resize", update);
5631
+ vv?.addEventListener("scroll", update);
5632
+ const ro = new ResizeObserver(update);
5633
+ for (const id of sectionIds) {
5634
+ const el = document.querySelector(
5635
+ `[data-ohw-section="${CSS.escape(id)}"]`
5636
+ );
5637
+ if (el) ro.observe(el);
5498
5638
  }
5499
- };
5500
- const handlePageSelect = (page) => {
5501
- setSelectedPage({ path: page.path, title: page.title });
5502
- setSelectedSection(null);
5503
- setSearchValue(page.title);
5504
- setStep("input");
5505
- setDropdownOpen(false);
5506
- setUrlError("");
5507
- };
5508
- const handleChooseSection = () => {
5509
- setStep("sectionPicker");
5510
- };
5511
- const handleSectionSelect = (section) => {
5512
- setSelectedSection({ id: section.id, label: section.label });
5513
- if (mode === "create") {
5514
- setStep("confirmed");
5515
- } else {
5639
+ return () => {
5640
+ window.removeEventListener("scroll", update, opts);
5641
+ window.removeEventListener("resize", update);
5642
+ vv?.removeEventListener("resize", update);
5643
+ vv?.removeEventListener("scroll", update);
5644
+ ro.disconnect();
5645
+ };
5646
+ }, [sectionIds, enabled]);
5647
+ return rects;
5648
+ }
5649
+ function SectionPickerOverlay({
5650
+ pagePath,
5651
+ sections,
5652
+ onBack,
5653
+ onSelect
5654
+ }) {
5655
+ const router = (0, import_navigation.useRouter)();
5656
+ const pathname = (0, import_navigation.usePathname)();
5657
+ const [selectedId, setSelectedId] = (0, import_react5.useState)(null);
5658
+ const [hoveredId, setHoveredId] = (0, import_react5.useState)(null);
5659
+ const pointerRef = (0, import_react5.useRef)(null);
5660
+ const pointerScreenRef = (0, import_react5.useRef)(null);
5661
+ const iframeOffsetTopRef = (0, import_react5.useRef)(null);
5662
+ const sectionIdsRef = (0, import_react5.useRef)([]);
5663
+ const [chromeClip, setChromeClip] = (0, import_react5.useState)(
5664
+ () => ({
5665
+ top: 0,
5666
+ bottom: typeof window !== "undefined" ? window.innerHeight : 0
5667
+ })
5668
+ );
5669
+ const normalizedTarget = normalizePath(pagePath);
5670
+ const isOnTargetPage = normalizePath(pathname) === normalizedTarget;
5671
+ (0, import_react5.useEffect)(() => {
5672
+ if (isOnTargetPage) return;
5673
+ const search = readPreservedSearch();
5674
+ router.push(`${normalizedTarget}${search}`);
5675
+ }, [isOnTargetPage, normalizedTarget, router]);
5676
+ (0, import_react5.useEffect)(() => {
5677
+ document.documentElement.setAttribute("data-ohw-section-picking", "");
5678
+ document.querySelectorAll("[data-ohw-hovered]").forEach((el) => {
5679
+ el.removeAttribute("data-ohw-hovered");
5680
+ });
5681
+ document.querySelectorAll("[data-ohw-state-hovered]").forEach((el) => {
5682
+ el.removeAttribute("data-ohw-state-hovered");
5683
+ });
5684
+ return () => {
5685
+ document.documentElement.removeAttribute("data-ohw-section-picking");
5686
+ };
5687
+ }, []);
5688
+ const liveSections = (0, import_react5.useMemo)(() => {
5689
+ if (!isOnTargetPage) return sections;
5690
+ const live = collectSectionsFromDom();
5691
+ if (live.length === 0) return sections;
5692
+ const labels = new Map(sections.map((s) => [s.id, s.label]));
5693
+ return live.map((s) => ({ id: s.id, label: labels.get(s.id) ?? s.label }));
5694
+ }, [isOnTargetPage, sections, pathname]);
5695
+ const sectionIdsKey = (0, import_react5.useMemo)(
5696
+ () => liveSections.map((s) => s.id).join("\0"),
5697
+ [liveSections]
5698
+ );
5699
+ const sectionIds = (0, import_react5.useMemo)(
5700
+ () => liveSections.map((s) => s.id),
5701
+ [liveSections]
5702
+ );
5703
+ sectionIdsRef.current = sectionIds;
5704
+ const rects = useSectionRects(sectionIdsKey, isOnTargetPage);
5705
+ const applyHoverAt = (0, import_react5.useCallback)(
5706
+ (x, y, liveRects) => {
5707
+ const ids = sectionIdsRef.current;
5708
+ const map = liveRects ?? readSectionRects(ids);
5709
+ const next = hitTestSectionId(x, y, ids, map);
5710
+ setHoveredId((prev) => prev === next ? prev : next);
5711
+ },
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
+ iframeOffsetTopRef.current = iframeOffsetTop;
5730
+ if (!pointerScreenRef.current && pointerRef.current) {
5731
+ pointerScreenRef.current = {
5732
+ x: pointerRef.current.x,
5733
+ y: pointerRef.current.y + iframeOffsetTop
5734
+ };
5735
+ }
5736
+ const screen = pointerScreenRef.current;
5737
+ if (screen) {
5738
+ const next = { x: screen.x, y: screen.y - iframeOffsetTop };
5739
+ pointerRef.current = next;
5740
+ applyHoverAt(next.x, next.y);
5741
+ }
5742
+ };
5743
+ const onResize = () => {
5744
+ setChromeClip((prev) => {
5745
+ const bottom = Math.max(prev.top, window.innerHeight);
5746
+ return prev.bottom === bottom ? prev : { ...prev, bottom };
5747
+ });
5748
+ };
5749
+ window.addEventListener("message", onParentScroll);
5750
+ window.addEventListener("resize", onResize);
5751
+ return () => {
5752
+ window.removeEventListener("message", onParentScroll);
5753
+ window.removeEventListener("resize", onResize);
5754
+ };
5755
+ }, [applyHoverAt]);
5756
+ (0, import_react5.useEffect)(() => {
5757
+ const ptr = pointerRef.current;
5758
+ if (!ptr) return;
5759
+ applyHoverAt(ptr.x, ptr.y, rects);
5760
+ }, [rects, applyHoverAt]);
5761
+ (0, import_react5.useEffect)(() => {
5762
+ const rememberPointer = (clientX, clientY) => {
5763
+ pointerRef.current = { x: clientX, y: clientY };
5764
+ const top = iframeOffsetTopRef.current;
5765
+ if (top != null) {
5766
+ pointerScreenRef.current = { x: clientX, y: clientY + top };
5767
+ }
5768
+ applyHoverAt(clientX, clientY, rects);
5769
+ };
5770
+ const onPointerMove = (e) => {
5771
+ rememberPointer(e.clientX, e.clientY);
5772
+ };
5773
+ const onPointerSync = (e) => {
5774
+ if (e.data?.type !== "ow:pointer-sync") return;
5775
+ const { clientX, clientY } = e.data;
5776
+ rememberPointer(clientX, clientY);
5777
+ };
5778
+ window.addEventListener("pointermove", onPointerMove, { passive: true });
5779
+ window.addEventListener("message", onPointerSync);
5780
+ return () => {
5781
+ window.removeEventListener("pointermove", onPointerMove);
5782
+ window.removeEventListener("message", onPointerSync);
5783
+ };
5784
+ }, [rects, applyHoverAt]);
5785
+ const handleSelect = (0, import_react5.useCallback)(
5786
+ (section) => {
5787
+ if (selectedId) return;
5788
+ setSelectedId(section.id);
5789
+ onSelect(section);
5790
+ },
5791
+ [selectedId, onSelect]
5792
+ );
5793
+ (0, import_react5.useEffect)(() => {
5794
+ const onKeyDown = (e) => {
5795
+ if (e.key === "Escape") {
5796
+ e.preventDefault();
5797
+ e.stopPropagation();
5798
+ onBack();
5799
+ }
5800
+ };
5801
+ window.addEventListener("keydown", onKeyDown, true);
5802
+ return () => window.removeEventListener("keydown", onKeyDown, true);
5803
+ }, [onBack]);
5804
+ const portalRoot = typeof document !== "undefined" ? document.querySelector("[data-ohw-bridge-root]") ?? document.body : null;
5805
+ if (!portalRoot) return null;
5806
+ return (0, import_react_dom.createPortal)(
5807
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
5808
+ "div",
5809
+ {
5810
+ "data-ohw-section-picker": "",
5811
+ "data-ohw-bridge": "",
5812
+ className: `pointer-events-none fixed inset-0 z-[2147483647] transition-opacity duration-200 ${"opacity-100"}`,
5813
+ "aria-modal": true,
5814
+ role: "dialog",
5815
+ "aria-label": "Choose a section",
5816
+ children: [
5817
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5818
+ "div",
5819
+ {
5820
+ className: "pointer-events-auto fixed left-5 z-[2]",
5821
+ style: { top: chromeClip.top + 20 },
5822
+ children: /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
5823
+ Button,
5824
+ {
5825
+ type: "button",
5826
+ variant: "outline",
5827
+ size: "sm",
5828
+ className: "h-8 min-w-0 gap-1 border-border bg-background px-2 py-1.5 shadow-sm hover:bg-muted cursor-pointer",
5829
+ onClick: onBack,
5830
+ children: [
5831
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react9.ArrowLeft, { className: "size-4 shrink-0", "aria-hidden": true }),
5832
+ "Back"
5833
+ ]
5834
+ }
5835
+ )
5836
+ }
5837
+ ),
5838
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5839
+ "div",
5840
+ {
5841
+ 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",
5842
+ style: {
5843
+ top: chromeClip.bottom - 20,
5844
+ transform: "translate(-50%, -100%)",
5845
+ backgroundColor: "var(--ohw-popover-foreground, #0c0a09)"
5846
+ },
5847
+ "data-ohw-section-picker-hint": "",
5848
+ children: "Click on section to select"
5849
+ }
5850
+ ),
5851
+ !isOnTargetPage ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5852
+ "div",
5853
+ {
5854
+ 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",
5855
+ style: { top: chromeClip.top + 64 },
5856
+ children: "Loading page preview\u2026"
5857
+ }
5858
+ ) : null,
5859
+ 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,
5860
+ isOnTargetPage ? liveSections.map((section) => {
5861
+ const rect = rects.get(section.id);
5862
+ if (!rect || rect.width <= 0 || rect.height <= 0) return null;
5863
+ const isSelected = selectedId === section.id;
5864
+ const isHovered = hoveredId === section.id;
5865
+ const isLit = isSelected || isHovered;
5866
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
5867
+ "button",
5868
+ {
5869
+ type: "button",
5870
+ className: "pointer-events-auto fixed z-[1] cursor-pointer border-0 bg-transparent p-0 transition-[background-color] duration-150",
5871
+ style: {
5872
+ top: rect.top,
5873
+ left: rect.left,
5874
+ width: rect.width,
5875
+ height: rect.height,
5876
+ backgroundColor: isLit ? "transparent" : DIM_OVERLAY
5877
+ },
5878
+ "aria-label": `Select section ${section.label}`,
5879
+ onClick: () => handleSelect(section),
5880
+ children: [
5881
+ isLit ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5882
+ "span",
5883
+ {
5884
+ className: "pointer-events-none absolute",
5885
+ style: {
5886
+ inset: 4,
5887
+ borderRadius: "var(--radius, 6px)",
5888
+ border: "1.5px dashed var(--primary, #0885FE)",
5889
+ boxSizing: "border-box"
5890
+ },
5891
+ "aria-hidden": true
5892
+ }
5893
+ ) : null,
5894
+ isSelected ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5895
+ "span",
5896
+ {
5897
+ className: "absolute right-3 top-3 flex size-8 items-center justify-center rounded-full text-white",
5898
+ style: { backgroundColor: "var(--ohw-primary, #0885fe)" },
5899
+ "aria-hidden": true,
5900
+ children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react9.Check, { className: "size-5" })
5901
+ }
5902
+ ) : null
5903
+ ]
5904
+ },
5905
+ section.id
5906
+ );
5907
+ }) : null
5908
+ ]
5909
+ }
5910
+ ),
5911
+ portalRoot
5912
+ );
5913
+ }
5914
+
5915
+ // src/ui/link-modal/useLinkModalState.ts
5916
+ var import_react6 = require("react");
5917
+ function useLinkModalState({
5918
+ open,
5919
+ mode,
5920
+ pages,
5921
+ sections: _sections,
5922
+ sectionsByPath,
5923
+ initialTarget,
5924
+ existingTargets: _existingTargets,
5925
+ onClose,
5926
+ onSubmit
5927
+ }) {
5928
+ const availablePages = (0, import_react6.useMemo)(() => pages, [pages]);
5929
+ const [searchValue, setSearchValue] = (0, import_react6.useState)("");
5930
+ const [selectedPage, setSelectedPage] = (0, import_react6.useState)(null);
5931
+ const [selectedSection, setSelectedSection] = (0, import_react6.useState)(null);
5932
+ const [step, setStep] = (0, import_react6.useState)("input");
5933
+ const [dropdownOpen, setDropdownOpen] = (0, import_react6.useState)(false);
5934
+ const [urlError, setUrlError] = (0, import_react6.useState)("");
5935
+ const reset = (0, import_react6.useCallback)(() => {
5936
+ setSearchValue("");
5937
+ setSelectedPage(null);
5938
+ setSelectedSection(null);
5939
+ setStep("input");
5940
+ setDropdownOpen(false);
5941
+ setUrlError("");
5942
+ }, []);
5943
+ (0, import_react6.useEffect)(() => {
5944
+ if (!open) return;
5945
+ if (mode === "edit" && initialTarget) {
5946
+ const { pageRoute } = parseTarget(initialTarget);
5947
+ const targetSections = getSectionsForPath(sectionsByPath, pageRoute);
5948
+ const init = getEditModeInitialState(initialTarget, pages, targetSections);
5949
+ setSearchValue(init.searchValue);
5950
+ setSelectedPage(init.selectedPage);
5951
+ setSelectedSection(init.selectedSection);
5952
+ setStep(init.step);
5953
+ } else {
5954
+ reset();
5955
+ }
5956
+ setDropdownOpen(false);
5957
+ setUrlError("");
5958
+ }, [open, mode, initialTarget, reset]);
5959
+ const filteredPages = (0, import_react6.useMemo)(() => {
5960
+ if (!searchValue.trim()) return availablePages;
5961
+ return availablePages.filter((p) => p.title.toLowerCase().startsWith(searchValue.toLowerCase()));
5962
+ }, [availablePages, searchValue]);
5963
+ const activeSections = (0, import_react6.useMemo)(() => {
5964
+ if (!selectedPage) return [];
5965
+ return getSectionsForPath(sectionsByPath, selectedPage.path);
5966
+ }, [selectedPage, sectionsByPath]);
5967
+ const showBreadcrumb = step === "confirmed" && selectedPage && selectedSection;
5968
+ const showSectionPicker = step === "sectionPicker" && selectedPage;
5969
+ const showSectionRow = mode === "edit" && selectedPage && selectedSection && step === "input";
5970
+ const showChooseSection = step === "input" && selectedPage && !selectedSection && !showSectionRow && activeSections.length > 0;
5971
+ const handleInputChange = (value) => {
5972
+ setSearchValue(value);
5973
+ setSelectedPage(null);
5974
+ setSelectedSection(null);
5975
+ setStep("input");
5976
+ setUrlError("");
5977
+ const filtered = value.trim() ? availablePages.filter((p) => p.title.toLowerCase().startsWith(value.toLowerCase())) : availablePages;
5978
+ setDropdownOpen(filtered.length > 0);
5979
+ if (value.trim()) {
5980
+ setUrlError(validateUrlInput(value, availablePages, pages));
5981
+ }
5982
+ };
5983
+ const handlePageSelect = (page) => {
5984
+ setSelectedPage({ path: page.path, title: page.title });
5985
+ setSelectedSection(null);
5986
+ setSearchValue(page.title);
5987
+ setStep("input");
5988
+ setDropdownOpen(false);
5989
+ setUrlError("");
5990
+ };
5991
+ const handleChooseSection = () => {
5992
+ setStep("sectionPicker");
5993
+ };
5994
+ const handleSectionSelect = (section) => {
5995
+ setSelectedSection({ id: section.id, label: section.label });
5996
+ if (mode === "create") {
5997
+ setStep("confirmed");
5998
+ } else {
5516
5999
  setStep("input");
5517
6000
  }
5518
6001
  };
5519
6002
  const handleBackToSections = () => {
5520
6003
  setStep("sectionPicker");
5521
6004
  };
6005
+ const handleSectionPickerBack = () => {
6006
+ setStep("input");
6007
+ };
5522
6008
  const handleClose = () => {
5523
6009
  reset();
5524
6010
  onClose();
5525
6011
  };
5526
- const isValid = (0, import_react5.useMemo)(() => {
6012
+ const isValid = (0, import_react6.useMemo)(() => {
5527
6013
  if (urlError) return false;
5528
6014
  if (showBreadcrumb) return true;
5529
6015
  if (selectedPage) return true;
@@ -5556,7 +6042,7 @@ function useLinkModalState({
5556
6042
  onSubmit(normalizeUrl(searchValue));
5557
6043
  handleClose();
5558
6044
  };
5559
- const submitLabel = mode === "edit" ? "Save" : "Create";
6045
+ const submitLabel = showBreadcrumb ? "Save" : mode === "edit" ? "Save" : "Create";
5560
6046
  const title = mode === "edit" ? "Edit navigation item" : "Create navigation item";
5561
6047
  const secondaryLabel = showBreadcrumb ? "Back to sections" : "Cancel";
5562
6048
  const handleSecondary = showBreadcrumb ? handleBackToSections : handleClose;
@@ -5583,23 +6069,29 @@ function useLinkModalState({
5583
6069
  handleChooseSection,
5584
6070
  handleSectionSelect,
5585
6071
  handleBackToSections,
6072
+ handleSectionPickerBack,
5586
6073
  handleClose,
5587
6074
  handleSecondary,
5588
6075
  handleSubmit
5589
6076
  };
5590
6077
  }
5591
6078
 
5592
- // src/ui/link-modal/LinkEditorPanel.tsx
5593
- var import_jsx_runtime19 = require("react/jsx-runtime");
5594
- function LinkEditorPanel({
6079
+ // src/ui/link-modal/LinkPopover.tsx
6080
+ var import_jsx_runtime20 = require("react/jsx-runtime");
6081
+ function postToParent(data) {
6082
+ window.parent?.postMessage(data, "*");
6083
+ }
6084
+ function LinkPopover({
5595
6085
  open = true,
6086
+ panelRef,
6087
+ portalContainer,
6088
+ onClose,
5596
6089
  mode = "create",
5597
6090
  pages,
5598
6091
  sections = [],
5599
6092
  sectionsByPath,
5600
6093
  initialTarget,
5601
6094
  existingTargets = [],
5602
- onClose,
5603
6095
  onSubmit
5604
6096
  }) {
5605
6097
  const state = useLinkModalState({
@@ -5613,175 +6105,449 @@ function LinkEditorPanel({
5613
6105
  onClose,
5614
6106
  onSubmit
5615
6107
  });
5616
- const isCancel = state.secondaryLabel === "Cancel";
5617
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_jsx_runtime19.Fragment, { children: [
5618
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(DialogClose, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5619
- "button",
6108
+ const sectionPickerActive = state.showSectionPicker && Boolean(state.selectedPage);
6109
+ (0, import_react7.useEffect)(() => {
6110
+ if (!open) return;
6111
+ if (sectionPickerActive) {
6112
+ postToParent({ type: "ui:unlock" });
6113
+ postToParent({ type: "ow:section-picker", active: true });
6114
+ document.documentElement.style.overflow = "";
6115
+ document.body.style.overflow = "";
6116
+ return () => {
6117
+ postToParent({ type: "ow:section-picker", active: false });
6118
+ };
6119
+ }
6120
+ postToParent({ type: "ow:section-picker", active: false });
6121
+ postToParent({ type: "ui:lock" });
6122
+ const html = document.documentElement;
6123
+ const body = document.body;
6124
+ const prevHtmlOverflow = html.style.overflow;
6125
+ const prevBodyOverflow = body.style.overflow;
6126
+ const prevHtmlOverscroll = html.style.overscrollBehavior;
6127
+ const prevBodyOverscroll = body.style.overscrollBehavior;
6128
+ html.style.overflow = "hidden";
6129
+ body.style.overflow = "hidden";
6130
+ html.style.overscrollBehavior = "none";
6131
+ body.style.overscrollBehavior = "none";
6132
+ const canScrollElement = (el, deltaY) => {
6133
+ const { overflowY } = getComputedStyle(el);
6134
+ if (overflowY !== "auto" && overflowY !== "scroll") return false;
6135
+ if (el.scrollHeight <= el.clientHeight + 1) return false;
6136
+ if (deltaY < 0) return el.scrollTop > 0;
6137
+ if (deltaY > 0)
6138
+ return el.scrollTop + el.clientHeight < el.scrollHeight - 1;
6139
+ return true;
6140
+ };
6141
+ const allowsInternalScroll = (e) => {
6142
+ const target = e.target;
6143
+ if (!(target instanceof Element)) return false;
6144
+ const scrollRoot = target.closest(
6145
+ "[data-ohw-link-page-dropdown], [data-ohw-allow-scroll]"
6146
+ );
6147
+ if (!scrollRoot) return false;
6148
+ const deltaY = e instanceof WheelEvent ? e.deltaY : 0;
6149
+ return canScrollElement(scrollRoot, deltaY);
6150
+ };
6151
+ const preventBackgroundScroll = (e) => {
6152
+ if (allowsInternalScroll(e)) return;
6153
+ e.preventDefault();
6154
+ };
6155
+ const scrollOpts = { passive: false, capture: true };
6156
+ document.addEventListener("wheel", preventBackgroundScroll, scrollOpts);
6157
+ document.addEventListener("touchmove", preventBackgroundScroll, scrollOpts);
6158
+ return () => {
6159
+ postToParent({ type: "ui:unlock" });
6160
+ html.style.overflow = prevHtmlOverflow;
6161
+ body.style.overflow = prevBodyOverflow;
6162
+ html.style.overscrollBehavior = prevHtmlOverscroll;
6163
+ body.style.overscrollBehavior = prevBodyOverscroll;
6164
+ document.removeEventListener(
6165
+ "wheel",
6166
+ preventBackgroundScroll,
6167
+ scrollOpts
6168
+ );
6169
+ document.removeEventListener(
6170
+ "touchmove",
6171
+ preventBackgroundScroll,
6172
+ scrollOpts
6173
+ );
6174
+ };
6175
+ }, [open, sectionPickerActive]);
6176
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_jsx_runtime20.Fragment, { children: [
6177
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
6178
+ Dialog2,
5620
6179
  {
5621
- type: "button",
5622
- className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
5623
- "aria-label": "Close",
5624
- children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(IconX, { "aria-hidden": true })
6180
+ open: open && !sectionPickerActive,
6181
+ onOpenChange: (next) => {
6182
+ if (!next) onClose?.();
6183
+ },
6184
+ children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
6185
+ DialogContent,
6186
+ {
6187
+ ref: panelRef,
6188
+ container: portalContainer,
6189
+ "data-ohw-link-popover-root": "",
6190
+ "data-ohw-link-modal-root": "",
6191
+ "data-ohw-bridge": "",
6192
+ showCloseButton: false,
6193
+ className: "gap-0 p-0 w-full max-w-[448px] pointer-events-auto z-[2147483646] overflow-visible",
6194
+ children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(LinkEditorPanel, { state, onClose })
6195
+ }
6196
+ )
5625
6197
  }
5626
- ) }),
5627
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(DialogHeader, { className: "w-full gap-1.5 p-6 pr-12", children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(DialogTitle, { className: "m-0 w-full break-words", children: state.title }) }),
5628
- /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex w-full flex-col gap-3 px-6 pb-8 pt-1", children: [
5629
- state.showBreadcrumb && state.selectedPage && state.selectedSection ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5630
- DestinationBreadcrumb,
5631
- {
5632
- pageTitle: state.selectedPage.title,
5633
- sectionLabel: state.selectedSection.label
5634
- }
5635
- ) : /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5636
- UrlOrPageInput,
5637
- {
5638
- value: state.searchValue,
5639
- selectedPage: state.selectedPage,
5640
- dropdownOpen: state.dropdownOpen,
5641
- onDropdownOpenChange: state.setDropdownOpen,
5642
- filteredPages: state.filteredPages,
5643
- onInputChange: state.handleInputChange,
5644
- onPageSelect: state.handlePageSelect,
5645
- urlError: state.urlError
5646
- }
5647
- ),
5648
- state.showChooseSection ? /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex flex-col justify-center gap-2", children: [
5649
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Button, { type: "button", variant: "outline", className: "w-fit cursor-pointer", size: "sm", onClick: state.handleChooseSection, children: "Choose a section" }),
5650
- /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex items-center gap-1 text-sm text-muted-foreground", children: [
5651
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(IconInfo, { className: "shrink-0", "aria-hidden": true }),
5652
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { children: "Pick a section this link should scroll to." })
5653
- ] })
5654
- ] }) : null,
5655
- state.showSectionPicker ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(SectionPickerList, { sections: state.activeSections, onSelect: state.handleSectionSelect }) : null,
5656
- state.showSectionRow && state.selectedSection ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(SectionTreeItem, { section: state.selectedSection, selected: true }) : null
5657
- ] }),
5658
- /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(DialogFooter, { className: "w-full px-6 pb-6", children: [
5659
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5660
- Button,
5661
- {
5662
- type: "button",
5663
- variant: isCancel ? "outline" : "ghost",
5664
- className: "leading-6 shadow-none cursor-pointer",
5665
- style: isCancel ? {
5666
- backgroundColor: "var(--ohw-background)",
5667
- borderColor: "var(--ohw-border)",
5668
- color: "var(--ohw-foreground)"
5669
- } : void 0,
5670
- onClick: state.handleSecondary,
5671
- children: state.secondaryLabel
5672
- }
5673
- ),
5674
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5675
- Button,
5676
- {
5677
- type: "button",
5678
- className: "border-0 leading-6 shadow-none hover:opacity-90 disabled:opacity-50 cursor-pointer",
5679
- style: {
5680
- backgroundColor: "var(--ohw-primary)",
5681
- color: "var(--ohw-primary-foreground)"
5682
- },
5683
- disabled: !state.isValid,
5684
- onClick: state.handleSubmit,
5685
- children: state.submitLabel
5686
- }
5687
- )
5688
- ] })
6198
+ ),
6199
+ sectionPickerActive && state.selectedPage ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
6200
+ SectionPickerOverlay,
6201
+ {
6202
+ pagePath: state.selectedPage.path,
6203
+ sections: state.activeSections,
6204
+ onBack: state.handleSectionPickerBack,
6205
+ onSelect: state.handleSectionSelect
6206
+ }
6207
+ ) : null
5689
6208
  ] });
5690
6209
  }
5691
6210
 
5692
- // src/ui/link-modal/LinkPopover.tsx
5693
- var import_jsx_runtime20 = require("react/jsx-runtime");
5694
- function LinkPopover({
5695
- open = true,
5696
- panelRef,
5697
- portalContainer,
5698
- onClose,
5699
- ...editorProps
6211
+ // src/ui/link-modal/devFixtures.ts
6212
+ var DEV_SITE_PAGES = [
6213
+ { path: "/", title: "Home" },
6214
+ { path: "/about", title: "About" },
6215
+ { path: "/classes", title: "Classes" },
6216
+ { path: "/pricing", title: "Pricing" },
6217
+ { path: "/studio", title: "Studio" },
6218
+ { path: "/instructors", title: "Instructors" },
6219
+ { path: "/contact", title: "Contact" }
6220
+ ];
6221
+ var DEV_SECTIONS_BY_PATH = {
6222
+ "/": [
6223
+ { id: "hero", label: "Hero" },
6224
+ { id: "lagree-intro", label: "Lagree Intro" },
6225
+ { id: "classes-strip", label: "Classes" },
6226
+ { id: "feel-split", label: "Feel Split" },
6227
+ { id: "founder-teaser", label: "Founder" },
6228
+ { id: "plan-form", label: "Plan Form" },
6229
+ { id: "testimonials", label: "Testimonials" },
6230
+ { id: "wordmark", label: "Wordmark" },
6231
+ { id: "footer", label: "Footer" }
6232
+ ],
6233
+ "/about": [
6234
+ { id: "manifesto", label: "Manifesto" },
6235
+ { id: "story-letter", label: "Our Story" },
6236
+ { id: "lagree-explainer", label: "Lagree Explainer" },
6237
+ { id: "waitlist-cta", label: "Waitlist" },
6238
+ { id: "personal-training", label: "Personal training" },
6239
+ { id: "footer", label: "Footer" }
6240
+ ],
6241
+ "/classes": [
6242
+ { id: "class-library", label: "Class Library" },
6243
+ { id: "footer", label: "Footer" }
6244
+ ],
6245
+ "/pricing": [
6246
+ { id: "page-header", label: "Page Header" },
6247
+ { id: "free-class-cta", label: "Free Class" },
6248
+ { id: "benefits-marquee", label: "Benefits" },
6249
+ { id: "monthly-memberships", label: "Memberships" },
6250
+ { id: "specialty-programs", label: "Specialty Programs" },
6251
+ { id: "package-matcher", label: "Packages" },
6252
+ { id: "wordmark", label: "Wordmark" },
6253
+ { id: "footer", label: "Footer" }
6254
+ ],
6255
+ "/studio": [
6256
+ { id: "studio-intro", label: "Studio Intro" },
6257
+ { id: "studio-features", label: "Studio Features" },
6258
+ { id: "studio-gallery", label: "Studio Gallery" },
6259
+ { id: "studio-visit", label: "Visit Studio" },
6260
+ { id: "footer", label: "Footer" }
6261
+ ],
6262
+ "/instructors": [
6263
+ { id: "instructor-grid", label: "Instructors" },
6264
+ { id: "footer", label: "Footer" }
6265
+ ],
6266
+ "/contact": [
6267
+ { id: "hello-hero", label: "Hello" },
6268
+ { id: "contact-form", label: "Contact Form" },
6269
+ { id: "faq", label: "FAQ" },
6270
+ { id: "footer", label: "Footer" }
6271
+ ]
6272
+ };
6273
+ function shouldUseDevFixtures() {
6274
+ if (typeof window === "undefined") return false;
6275
+ const raw = readPreservedSearch();
6276
+ const q = raw.startsWith("?") ? raw.slice(1) : raw;
6277
+ return new URLSearchParams(q).get("ohw-fixtures") === "1";
6278
+ }
6279
+
6280
+ // src/lib/nav-items.ts
6281
+ var NAV_COUNT_KEY = "__ohw_nav_count";
6282
+ var NAV_ORDER_KEY = "__ohw_nav_order";
6283
+ function getLinkHref(el) {
6284
+ const key = el.getAttribute("data-ohw-href-key");
6285
+ if (key) {
6286
+ const stored = getStoredLinkHref(key);
6287
+ if (stored !== void 0) return stored;
6288
+ }
6289
+ return el.getAttribute("href") ?? "";
6290
+ }
6291
+ function isNavbarButton(el) {
6292
+ return el.hasAttribute("data-ohw-role") && el.getAttribute("data-ohw-role") === "navbar-button";
6293
+ }
6294
+ function isNavbarLinkItem(el) {
6295
+ if (!el.matches("[data-ohw-href-key]")) return false;
6296
+ if (isNavbarButton(el)) return false;
6297
+ if (!el.querySelector('[data-ohw-editable="text"]')) return false;
6298
+ return Boolean(el.closest("nav, [data-ohw-nav-container], [data-ohw-nav-drawer]"));
6299
+ }
6300
+ function getNavbarDesktopContainer() {
6301
+ return document.querySelector("[data-ohw-nav-container]");
6302
+ }
6303
+ function getNavbarDrawerContainer() {
6304
+ return document.querySelector("[data-ohw-nav-drawer]");
6305
+ }
6306
+ function listNavbarItems() {
6307
+ const desktop = getNavbarDesktopContainer();
6308
+ if (desktop) {
6309
+ return Array.from(desktop.querySelectorAll("[data-ohw-href-key]")).filter(isNavbarLinkItem);
6310
+ }
6311
+ const drawer = getNavbarDrawerContainer();
6312
+ if (drawer) {
6313
+ return Array.from(drawer.querySelectorAll("[data-ohw-href-key]")).filter(isNavbarLinkItem);
6314
+ }
6315
+ return Array.from(document.querySelectorAll("nav [data-ohw-href-key]")).filter(isNavbarLinkItem);
6316
+ }
6317
+ function parseNavIndexFromKey(key) {
6318
+ if (!key) return null;
6319
+ const match = key.match(/^nav-(\d+)-href$/);
6320
+ if (!match) return null;
6321
+ return parseInt(match[1], 10);
6322
+ }
6323
+ function getNavbarIndicesInDom() {
6324
+ const indices = /* @__PURE__ */ new Set();
6325
+ for (const item of listNavbarItems()) {
6326
+ const idx = parseNavIndexFromKey(item.getAttribute("data-ohw-href-key"));
6327
+ if (idx !== null) indices.add(idx);
6328
+ }
6329
+ return [...indices].sort((a, b) => a - b);
6330
+ }
6331
+ function getNextNavbarIndex() {
6332
+ const indices = getNavbarIndicesInDom();
6333
+ if (indices.length === 0) return 0;
6334
+ return Math.max(...indices) + 1;
6335
+ }
6336
+ function getNavbarExistingTargets() {
6337
+ return listNavbarItems().map((el) => getLinkHref(el)).filter(Boolean);
6338
+ }
6339
+ function getNavOrderFromDom() {
6340
+ return listNavbarItems().map((el) => el.getAttribute("data-ohw-href-key")).filter((key) => Boolean(key));
6341
+ }
6342
+ function parseNavOrder(content) {
6343
+ const raw = content[NAV_ORDER_KEY];
6344
+ if (!raw) return null;
6345
+ try {
6346
+ const parsed = JSON.parse(raw);
6347
+ if (!Array.isArray(parsed)) return null;
6348
+ return parsed.filter((key) => typeof key === "string" && key.length > 0);
6349
+ } catch {
6350
+ return null;
6351
+ }
6352
+ }
6353
+ function findCounterpartByHrefKey(hrefKey, root) {
6354
+ return root.querySelector(`[data-ohw-href-key="${CSS.escape(hrefKey)}"]`);
6355
+ }
6356
+ function cloneNavLinkShell(template) {
6357
+ const anchor = document.createElement("a");
6358
+ if (template) {
6359
+ anchor.style.cssText = template.style.cssText;
6360
+ if (template.className) anchor.className = template.className;
6361
+ }
6362
+ anchor.style.cursor = "pointer";
6363
+ anchor.style.textDecoration = "none";
6364
+ return anchor;
6365
+ }
6366
+ function buildNavLink(index, href, label, template) {
6367
+ const anchor = cloneNavLinkShell(template);
6368
+ anchor.href = href;
6369
+ anchor.setAttribute("data-ohw-href-key", `nav-${index}-href`);
6370
+ const span = document.createElement("span");
6371
+ span.setAttribute("data-ohw-editable", "text");
6372
+ span.setAttribute("data-ohw-key", `nav-${index}-label`);
6373
+ span.textContent = label;
6374
+ anchor.appendChild(span);
6375
+ return anchor;
6376
+ }
6377
+ function insertNavLinkInContainer(container, anchor, afterHrefKey) {
6378
+ if (!afterHrefKey) {
6379
+ container.appendChild(anchor);
6380
+ return;
6381
+ }
6382
+ const afterEl = findCounterpartByHrefKey(afterHrefKey, container);
6383
+ if (afterEl?.parentElement === container) {
6384
+ afterEl.insertAdjacentElement("afterend", anchor);
6385
+ return;
6386
+ }
6387
+ container.appendChild(anchor);
6388
+ }
6389
+ function insertNavbarItemDom(index, href, label, afterAnchor) {
6390
+ const afterHrefKey = afterAnchor?.getAttribute("data-ohw-href-key") ?? null;
6391
+ const desktopItems = getNavbarDesktopContainer()?.querySelectorAll("[data-ohw-href-key]") ?? [];
6392
+ const drawerItems = getNavbarDrawerContainer()?.querySelectorAll("[data-ohw-href-key]") ?? [];
6393
+ const desktopTemplate = Array.from(desktopItems).find(isNavbarLinkItem) ?? null;
6394
+ const drawerTemplate = Array.from(drawerItems).find(isNavbarLinkItem) ?? null;
6395
+ let primary = null;
6396
+ const desktopContainer = getNavbarDesktopContainer();
6397
+ if (desktopContainer) {
6398
+ const desktopAnchor = buildNavLink(index, href, label, desktopTemplate);
6399
+ insertNavLinkInContainer(desktopContainer, desktopAnchor, afterHrefKey);
6400
+ primary = desktopAnchor;
6401
+ }
6402
+ const drawerContainer = getNavbarDrawerContainer();
6403
+ if (drawerContainer) {
6404
+ const drawerAnchor = buildNavLink(index, href, label, drawerTemplate);
6405
+ insertNavLinkInContainer(drawerContainer, drawerAnchor, afterHrefKey);
6406
+ if (!primary) primary = drawerAnchor;
6407
+ }
6408
+ if (!primary) {
6409
+ const fallback = buildNavLink(index, href, label, listNavbarItems()[0] ?? null);
6410
+ const nav = document.querySelector("nav");
6411
+ nav?.appendChild(fallback);
6412
+ primary = fallback;
6413
+ }
6414
+ return primary;
6415
+ }
6416
+ function applyNavOrderToContainer(container, order) {
6417
+ const seen = /* @__PURE__ */ new Set();
6418
+ for (const hrefKey of order) {
6419
+ if (seen.has(hrefKey)) continue;
6420
+ seen.add(hrefKey);
6421
+ const el = findCounterpartByHrefKey(hrefKey, container);
6422
+ if (el) container.appendChild(el);
6423
+ }
6424
+ for (const el of container.querySelectorAll("[data-ohw-href-key]")) {
6425
+ if (!isNavbarLinkItem(el)) continue;
6426
+ const key = el.getAttribute("data-ohw-href-key");
6427
+ if (!key || seen.has(key)) continue;
6428
+ container.appendChild(el);
6429
+ }
6430
+ }
6431
+ function applyNavOrder(order) {
6432
+ const desktop = getNavbarDesktopContainer();
6433
+ if (desktop) applyNavOrderToContainer(desktop, order);
6434
+ const drawer = getNavbarDrawerContainer();
6435
+ if (drawer) applyNavOrderToContainer(drawer, order);
6436
+ }
6437
+ function collectNavbarIndicesFromContent(content) {
6438
+ const indices = /* @__PURE__ */ new Set();
6439
+ for (const key of Object.keys(content)) {
6440
+ const idx = parseNavIndexFromKey(key);
6441
+ if (idx !== null) indices.add(idx);
6442
+ }
6443
+ const countRaw = content[NAV_COUNT_KEY];
6444
+ if (countRaw) {
6445
+ const count = parseInt(countRaw, 10);
6446
+ if (Number.isFinite(count) && count > 0) {
6447
+ for (let i = 0; i < count; i++) indices.add(i);
6448
+ }
6449
+ }
6450
+ return [...indices].sort((a, b) => a - b);
6451
+ }
6452
+ function navbarIndexExistsInDom(index) {
6453
+ return document.querySelector(`[data-ohw-href-key="nav-${index}-href"]`) !== null;
6454
+ }
6455
+ function reconcileNavbarItemsFromContent(content) {
6456
+ const indices = collectNavbarIndicesFromContent(content);
6457
+ for (const index of indices) {
6458
+ if (navbarIndexExistsInDom(index)) continue;
6459
+ const href = content[`nav-${index}-href`];
6460
+ const label = content[`nav-${index}-label`];
6461
+ if (!href && !label) continue;
6462
+ insertNavbarItemDom(index, href ?? "/", label ?? "Untitled", null);
6463
+ }
6464
+ const order = parseNavOrder(content);
6465
+ if (order?.length) applyNavOrder(order);
6466
+ }
6467
+ function buildNavOrderAfterInsert(afterAnchor, newHrefKey) {
6468
+ const order = getNavOrderFromDom();
6469
+ if (!afterAnchor) {
6470
+ if (!order.includes(newHrefKey)) order.push(newHrefKey);
6471
+ return order;
6472
+ }
6473
+ const afterKey = afterAnchor.getAttribute("data-ohw-href-key");
6474
+ if (!afterKey) {
6475
+ if (!order.includes(newHrefKey)) order.push(newHrefKey);
6476
+ return order;
6477
+ }
6478
+ const withoutNew = order.filter((key) => key !== newHrefKey);
6479
+ const pos = withoutNew.indexOf(afterKey);
6480
+ if (pos >= 0) {
6481
+ withoutNew.splice(pos + 1, 0, newHrefKey);
6482
+ return withoutNew;
6483
+ }
6484
+ withoutNew.push(newHrefKey);
6485
+ return withoutNew;
6486
+ }
6487
+ function insertNavbarItem(href, label, afterAnchor = null) {
6488
+ const index = getNextNavbarIndex();
6489
+ const anchor = insertNavbarItemDom(index, href, label, afterAnchor);
6490
+ if (!anchor) throw new Error("Failed to insert navbar item");
6491
+ const hrefKey = `nav-${index}-href`;
6492
+ const order = buildNavOrderAfterInsert(afterAnchor, hrefKey);
6493
+ applyNavOrder(order);
6494
+ return {
6495
+ anchor,
6496
+ index,
6497
+ hrefKey,
6498
+ labelKey: `nav-${index}-label`,
6499
+ href,
6500
+ label,
6501
+ order
6502
+ };
6503
+ }
6504
+
6505
+ // src/ui/navbar-container-chrome.tsx
6506
+ var import_lucide_react10 = require("lucide-react");
6507
+ var import_jsx_runtime21 = require("react/jsx-runtime");
6508
+ function NavbarContainerChrome({
6509
+ rect,
6510
+ onAdd
5700
6511
  }) {
5701
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
5702
- Dialog2,
6512
+ const chromeGap = 6;
6513
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
6514
+ "div",
5703
6515
  {
5704
- open,
5705
- onOpenChange: (next) => {
5706
- if (!next) onClose?.();
6516
+ "data-ohw-navbar-container-chrome": "",
6517
+ "data-ohw-bridge": "",
6518
+ className: "pointer-events-none fixed z-[2147483647]",
6519
+ style: {
6520
+ top: rect.top - chromeGap,
6521
+ left: rect.left - chromeGap,
6522
+ width: rect.width + chromeGap * 2,
6523
+ height: rect.height + chromeGap * 2
5707
6524
  },
5708
- children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
5709
- DialogContent,
6525
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
6526
+ "button",
5710
6527
  {
5711
- ref: panelRef,
5712
- container: portalContainer,
5713
- "data-ohw-link-popover-root": "",
5714
- "data-ohw-link-modal-root": "",
5715
- "data-ohw-bridge": "",
5716
- showCloseButton: false,
5717
- className: "gap-0 p-0 w-full md:w-md pointer-events-auto",
5718
- children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(LinkEditorPanel, { ...editorProps, open, onClose })
6528
+ type: "button",
6529
+ "data-ohw-navbar-add-button": "",
6530
+ 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",
6531
+ style: { top: "70%" },
6532
+ "aria-label": "Add item",
6533
+ onMouseDown: (e) => {
6534
+ e.preventDefault();
6535
+ e.stopPropagation();
6536
+ },
6537
+ onClick: (e) => {
6538
+ e.preventDefault();
6539
+ e.stopPropagation();
6540
+ onAdd();
6541
+ },
6542
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react10.Plus, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
5719
6543
  }
5720
6544
  )
5721
6545
  }
5722
6546
  );
5723
6547
  }
5724
6548
 
5725
- // src/ui/link-modal/devFixtures.ts
5726
- var DEV_SITE_PAGES = [
5727
- { path: "/", title: "Home" },
5728
- { path: "/about", title: "About" },
5729
- { path: "/classes", title: "Classes" },
5730
- { path: "/pricing", title: "Pricing" },
5731
- { path: "/studio", title: "Studio" },
5732
- { path: "/instructors", title: "Instructors" },
5733
- { path: "/contact", title: "Contact" }
5734
- ];
5735
- var DEV_SECTIONS_BY_PATH = {
5736
- "/": [
5737
- { id: "hero", label: "Hero" },
5738
- { id: "lagree-intro", label: "Lagree Intro" },
5739
- { id: "classes-strip", label: "Classes" },
5740
- { id: "feel-split", label: "Feel Split" },
5741
- { id: "founder-teaser", label: "Founder" },
5742
- { id: "plan-form", label: "Plan Form" },
5743
- { id: "testimonials", label: "Testimonials" },
5744
- { id: "wordmark", label: "Wordmark" }
5745
- ],
5746
- "/about": [
5747
- { id: "manifesto", label: "Manifesto" },
5748
- { id: "story-letter", label: "Our Story" },
5749
- { id: "lagree-explainer", label: "Lagree Explainer" },
5750
- { id: "waitlist-cta", label: "Waitlist" },
5751
- { id: "personal-training", label: "Personal training" }
5752
- ],
5753
- "/classes": [{ id: "class-library", label: "Class Library" }],
5754
- "/pricing": [
5755
- { id: "page-header", label: "Page Header" },
5756
- { id: "free-class-cta", label: "Free Class" },
5757
- { id: "benefits-marquee", label: "Benefits" },
5758
- { id: "monthly-memberships", label: "Memberships" },
5759
- { id: "specialty-programs", label: "Specialty Programs" },
5760
- { id: "package-matcher", label: "Packages" },
5761
- { id: "wordmark", label: "Wordmark" }
5762
- ],
5763
- "/studio": [
5764
- { id: "studio-intro", label: "Studio Intro" },
5765
- { id: "studio-features", label: "Studio Features" },
5766
- { id: "studio-gallery", label: "Studio Gallery" },
5767
- { id: "studio-visit", label: "Visit Studio" }
5768
- ],
5769
- "/instructors": [{ id: "instructor-grid", label: "Instructors" }],
5770
- "/contact": [
5771
- { id: "hello-hero", label: "Hello" },
5772
- { id: "contact-form", label: "Contact Form" },
5773
- { id: "faq", label: "FAQ" }
5774
- ]
5775
- };
5776
- function shouldUseDevFixtures() {
5777
- if (typeof window === "undefined") return false;
5778
- const raw = readPreservedSearch();
5779
- const q = raw.startsWith("?") ? raw.slice(1) : raw;
5780
- return new URLSearchParams(q).get("ohw-fixtures") === "1";
5781
- }
5782
-
5783
6549
  // src/ui/badge.tsx
5784
- var import_jsx_runtime21 = require("react/jsx-runtime");
6550
+ var import_jsx_runtime22 = require("react/jsx-runtime");
5785
6551
  var badgeVariants = cva(
5786
6552
  "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",
5787
6553
  {
@@ -5799,12 +6565,12 @@ var badgeVariants = cva(
5799
6565
  }
5800
6566
  );
5801
6567
  function Badge({ className, variant, ...props }) {
5802
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: cn(badgeVariants({ variant }), className), ...props });
6568
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: cn(badgeVariants({ variant }), className), ...props });
5803
6569
  }
5804
6570
 
5805
6571
  // src/OhhwellsBridge.tsx
5806
- var import_lucide_react4 = require("lucide-react");
5807
- var import_jsx_runtime22 = require("react/jsx-runtime");
6572
+ var import_lucide_react11 = require("lucide-react");
6573
+ var import_jsx_runtime23 = require("react/jsx-runtime");
5808
6574
  var PRIMARY2 = "#0885FE";
5809
6575
  var IMAGE_FADE_MS = 300;
5810
6576
  function runOpacityFade(el, onDone) {
@@ -5981,9 +6747,9 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
5981
6747
  tail.insertAdjacentElement("afterend", container);
5982
6748
  }
5983
6749
  const root = (0, import_client.createRoot)(container);
5984
- (0, import_react_dom.flushSync)(() => {
6750
+ (0, import_react_dom2.flushSync)(() => {
5985
6751
  root.render(
5986
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
6752
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
5987
6753
  SchedulingWidget,
5988
6754
  {
5989
6755
  notifyOnConnect,
@@ -6023,7 +6789,7 @@ function mountSchedulingEntries(entries, notifyOnConnect = false) {
6023
6789
  }
6024
6790
  }
6025
6791
  }
6026
- function getLinkHref(el) {
6792
+ function getLinkHref2(el) {
6027
6793
  const anchor = el instanceof HTMLAnchorElement ? el : el.querySelector("a");
6028
6794
  return anchor?.getAttribute("href") ?? "";
6029
6795
  }
@@ -6060,7 +6826,7 @@ function collectEditableNodes() {
6060
6826
  return { key: el.dataset.ohwKey ?? "", type: "video", text: getVideoEl(el)?.src ?? "" };
6061
6827
  }
6062
6828
  if (el.dataset.ohwEditable === "link") {
6063
- return { key: el.dataset.ohwKey ?? "", type: "link", text: getLinkHref(el) };
6829
+ return { key: el.dataset.ohwKey ?? "", type: "link", text: getLinkHref2(el) };
6064
6830
  }
6065
6831
  return {
6066
6832
  key: el.dataset.ohwKey ?? "",
@@ -6071,7 +6837,7 @@ function collectEditableNodes() {
6071
6837
  document.querySelectorAll("[data-ohw-href-key]").forEach((el) => {
6072
6838
  const key = el.getAttribute("data-ohw-href-key") ?? "";
6073
6839
  if (!key) return;
6074
- nodes.push({ key, type: "link", text: getLinkHref(el) });
6840
+ nodes.push({ key, type: "link", text: getLinkHref2(el) });
6075
6841
  });
6076
6842
  document.querySelectorAll('[data-ohw-editable="video"]').forEach((el) => {
6077
6843
  const key = el.dataset.ohwKey ?? "";
@@ -6163,7 +6929,7 @@ function applyLinkByKey(key, val) {
6163
6929
  }
6164
6930
  function isInsideLinkEditor(target) {
6165
6931
  return Boolean(
6166
- 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"]')
6932
+ 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"]')
6167
6933
  );
6168
6934
  }
6169
6935
  function getHrefKeyFromElement(el) {
@@ -6174,7 +6940,7 @@ function getHrefKeyFromElement(el) {
6174
6940
  if (!key) return null;
6175
6941
  return { anchor, key };
6176
6942
  }
6177
- function isNavbarButton(el) {
6943
+ function isNavbarButton2(el) {
6178
6944
  return Boolean(el.closest('[data-ohw-role="navbar-button"]'));
6179
6945
  }
6180
6946
  function getNavigationItemAnchor(el) {
@@ -6186,19 +6952,8 @@ function getNavigationItemAnchor(el) {
6186
6952
  function isNavigationItem(el) {
6187
6953
  return getNavigationItemAnchor(el) !== null;
6188
6954
  }
6189
- function getNavigationItemAddHintTarget(anchor) {
6190
- const items = listNavigationItems();
6191
- const idx = items.indexOf(anchor);
6192
- if (idx >= 0 && idx < items.length - 1) return items[idx + 1];
6193
- return anchor;
6194
- }
6195
- function listNavigationItems() {
6196
- return Array.from(
6197
- document.querySelectorAll("nav [data-ohw-href-key], footer [data-ohw-href-key]")
6198
- ).filter((el) => isNavigationItem(el));
6199
- }
6200
6955
  function getNavigationItemReorderState(anchor) {
6201
- if (isNavbarButton(anchor)) return { key: null, disabled: false };
6956
+ if (isNavbarButton2(anchor)) return { key: null, disabled: false };
6202
6957
  return getReorderHandleStateFromAnchor(anchor);
6203
6958
  }
6204
6959
  function getReorderHandleState(el) {
@@ -6220,6 +6975,120 @@ function clearHrefKeyHover(anchor) {
6220
6975
  function isInsideNavigationItem(el) {
6221
6976
  return getNavigationItemAnchor(el) !== null;
6222
6977
  }
6978
+ function getNavigationRoot(el) {
6979
+ const explicit = el.closest("[data-ohw-nav-root]");
6980
+ if (explicit) return explicit;
6981
+ return el.closest("nav, footer, aside");
6982
+ }
6983
+ function findFooterItemGroup(item) {
6984
+ const footer = item.closest("footer");
6985
+ if (!footer) return null;
6986
+ let node = item.parentElement;
6987
+ while (node && node !== footer) {
6988
+ const hasDirectNavItems = Array.from(
6989
+ node.querySelectorAll(":scope > [data-ohw-href-key]")
6990
+ ).some(isNavigationItem);
6991
+ if (hasDirectNavItems) return node;
6992
+ node = node.parentElement;
6993
+ }
6994
+ return null;
6995
+ }
6996
+ function isNavigationRoot(el) {
6997
+ return el.hasAttribute("data-ohw-nav-root") || el.matches("nav, footer, aside");
6998
+ }
6999
+ function isInferredFooterGroup(el) {
7000
+ const footer = el.closest("footer");
7001
+ if (!footer || el === footer) return false;
7002
+ return Array.from(el.querySelectorAll(":scope > [data-ohw-href-key]")).some(isNavigationItem);
7003
+ }
7004
+ function isNavigationContainer(el) {
7005
+ return el.hasAttribute("data-ohw-nav-container") || isNavigationRoot(el) || isInferredFooterGroup(el);
7006
+ }
7007
+ function isPointOverNavigation(x, y) {
7008
+ const navRoot = document.querySelector("[data-ohw-nav-root]") ?? document.querySelector("nav");
7009
+ if (!navRoot) return false;
7010
+ const r2 = navRoot.getBoundingClientRect();
7011
+ return x >= r2.left && x <= r2.right && y >= r2.top && y <= r2.bottom;
7012
+ }
7013
+ function isPointOverNavItem(container, x, y) {
7014
+ return Array.from(container.querySelectorAll("[data-ohw-href-key]")).some((el) => {
7015
+ if (!isNavigationItem(el) || isNavbarButton2(el)) return false;
7016
+ const itemRect = el.getBoundingClientRect();
7017
+ return x >= itemRect.left && x <= itemRect.right && y >= itemRect.top && y <= itemRect.bottom;
7018
+ });
7019
+ }
7020
+ function resolveNavContainerSelectionTarget(target, clientX, clientY) {
7021
+ if (getNavigationItemAnchor(target)) return null;
7022
+ if (target.closest('[data-ohw-role="navbar-button"]')) return null;
7023
+ const plainLink = target.closest("a");
7024
+ if (plainLink && !plainLink.hasAttribute("data-ohw-href-key") && !plainLink.closest("[data-ohw-nav-container]")) {
7025
+ return null;
7026
+ }
7027
+ const fromClosest = target.closest("[data-ohw-nav-container]");
7028
+ if (fromClosest && !isPointOverNavItem(fromClosest, clientX, clientY)) {
7029
+ return fromClosest;
7030
+ }
7031
+ const navRoot = target.closest("[data-ohw-nav-root]");
7032
+ if (!navRoot) return null;
7033
+ const container = navRoot.querySelector("[data-ohw-nav-container]");
7034
+ if (!container || isPointOverNavItem(container, clientX, clientY)) return null;
7035
+ if (target === navRoot || target.hasAttribute("data-ohw-nav-root")) {
7036
+ return container;
7037
+ }
7038
+ return null;
7039
+ }
7040
+ function getNavigationSelectionParent(el) {
7041
+ if (isNavigationItem(el)) {
7042
+ const explicit = el.closest("[data-ohw-nav-container]");
7043
+ if (explicit) return explicit;
7044
+ const footerGroup = findFooterItemGroup(el);
7045
+ if (footerGroup) return footerGroup;
7046
+ return getNavigationRoot(el);
7047
+ }
7048
+ if (el.hasAttribute("data-ohw-nav-container") || isInferredFooterGroup(el)) {
7049
+ return getNavigationRoot(el);
7050
+ }
7051
+ return null;
7052
+ }
7053
+ function placeCaretAtPoint(el, x, y) {
7054
+ const selection = window.getSelection();
7055
+ if (!selection) return;
7056
+ if (typeof document.caretRangeFromPoint === "function") {
7057
+ const range = document.caretRangeFromPoint(x, y);
7058
+ if (range && el.contains(range.startContainer)) {
7059
+ selection.removeAllRanges();
7060
+ selection.addRange(range);
7061
+ return;
7062
+ }
7063
+ }
7064
+ const caretPositionFromPoint = document.caretPositionFromPoint;
7065
+ if (typeof caretPositionFromPoint === "function") {
7066
+ const position = caretPositionFromPoint(x, y);
7067
+ if (position && el.contains(position.offsetNode)) {
7068
+ const range = document.createRange();
7069
+ range.setStart(position.offsetNode, position.offset);
7070
+ range.collapse(true);
7071
+ selection.removeAllRanges();
7072
+ selection.addRange(range);
7073
+ }
7074
+ }
7075
+ }
7076
+ function selectAllTextInEditable(el) {
7077
+ const selection = window.getSelection();
7078
+ if (!selection) return;
7079
+ const range = document.createRange();
7080
+ range.selectNodeContents(el);
7081
+ selection.removeAllRanges();
7082
+ selection.addRange(range);
7083
+ }
7084
+ function getNavigationLabelEditable(target) {
7085
+ const editable = target.closest('[data-ohw-editable="text"]');
7086
+ if (!editable) return null;
7087
+ const hrefCtx = getHrefKeyFromElement(editable);
7088
+ const navAnchor = hrefCtx ? getNavigationItemAnchor(hrefCtx.anchor) : null;
7089
+ if (!navAnchor) return null;
7090
+ return { editable, navAnchor };
7091
+ }
6223
7092
  function collectSections() {
6224
7093
  return collectSectionsFromDom();
6225
7094
  }
@@ -6367,7 +7236,7 @@ function EditGlowChrome({
6367
7236
  dragDisabled = false
6368
7237
  }) {
6369
7238
  const GAP = 6;
6370
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
7239
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
6371
7240
  "div",
6372
7241
  {
6373
7242
  ref: elRef,
@@ -6382,7 +7251,7 @@ function EditGlowChrome({
6382
7251
  zIndex: 2147483646
6383
7252
  },
6384
7253
  children: [
6385
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
7254
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6386
7255
  "div",
6387
7256
  {
6388
7257
  style: {
@@ -6395,7 +7264,7 @@ function EditGlowChrome({
6395
7264
  }
6396
7265
  }
6397
7266
  ),
6398
- reorderHrefKey && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
7267
+ reorderHrefKey && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6399
7268
  "div",
6400
7269
  {
6401
7270
  "data-ohw-drag-handle-container": "",
@@ -6407,7 +7276,7 @@ function EditGlowChrome({
6407
7276
  transform: "translate(calc(-100% - 7px), -50%)",
6408
7277
  pointerEvents: dragDisabled ? "none" : "auto"
6409
7278
  },
6410
- children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
7279
+ children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6411
7280
  DragHandle,
6412
7281
  {
6413
7282
  "aria-label": `Reorder ${reorderHrefKey}`,
@@ -6511,7 +7380,7 @@ function FloatingToolbar({
6511
7380
  onEditLink
6512
7381
  }) {
6513
7382
  const { top, left, transform } = calcToolbarPos(rect, parentScroll, 330);
6514
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
7383
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6515
7384
  "div",
6516
7385
  {
6517
7386
  ref: elRef,
@@ -6523,12 +7392,12 @@ function FloatingToolbar({
6523
7392
  zIndex: 2147483647,
6524
7393
  pointerEvents: "auto"
6525
7394
  },
6526
- children: /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(CustomToolbar, { children: [
6527
- TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_react6.default.Fragment, { children: [
6528
- gi > 0 && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(CustomToolbarDivider, {}),
7395
+ children: /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(CustomToolbar, { children: [
7396
+ TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_react8.default.Fragment, { children: [
7397
+ gi > 0 && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(CustomToolbarDivider, {}),
6529
7398
  btns.map((btn) => {
6530
7399
  const isActive = activeCommands.has(btn.cmd);
6531
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
7400
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6532
7401
  CustomToolbarButton,
6533
7402
  {
6534
7403
  title: btn.title,
@@ -6537,7 +7406,7 @@ function FloatingToolbar({
6537
7406
  e.preventDefault();
6538
7407
  onCommand(btn.cmd);
6539
7408
  },
6540
- children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
7409
+ children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6541
7410
  "svg",
6542
7411
  {
6543
7412
  width: "16",
@@ -6558,7 +7427,7 @@ function FloatingToolbar({
6558
7427
  );
6559
7428
  })
6560
7429
  ] }, gi)),
6561
- showEditLink ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
7430
+ showEditLink ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6562
7431
  CustomToolbarButton,
6563
7432
  {
6564
7433
  type: "button",
@@ -6572,7 +7441,7 @@ function FloatingToolbar({
6572
7441
  e.preventDefault();
6573
7442
  e.stopPropagation();
6574
7443
  },
6575
- children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react4.Link, { className: "size-4 shrink-0", "aria-hidden": true })
7444
+ children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_lucide_react11.Link, { className: "size-4 shrink-0", "aria-hidden": true })
6576
7445
  }
6577
7446
  ) : null
6578
7447
  ] })
@@ -6589,7 +7458,7 @@ function StateToggle({
6589
7458
  states,
6590
7459
  onStateChange
6591
7460
  }) {
6592
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
7461
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6593
7462
  ToggleGroup,
6594
7463
  {
6595
7464
  "data-ohw-state-toggle": "",
@@ -6603,7 +7472,7 @@ function StateToggle({
6603
7472
  left: rect.right - 8,
6604
7473
  transform: "translateX(-100%)"
6605
7474
  },
6606
- children: states.map((state) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
7475
+ children: states.map((state) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
6607
7476
  }
6608
7477
  );
6609
7478
  }
@@ -6626,12 +7495,12 @@ function resolveSubdomain(subdomainFromQuery) {
6626
7495
  return "";
6627
7496
  }
6628
7497
  function OhhwellsBridge() {
6629
- const pathname = (0, import_navigation.usePathname)();
6630
- const router = (0, import_navigation.useRouter)();
6631
- const searchParams = (0, import_navigation.useSearchParams)();
7498
+ const pathname = (0, import_navigation2.usePathname)();
7499
+ const router = (0, import_navigation2.useRouter)();
7500
+ const searchParams = (0, import_navigation2.useSearchParams)();
6632
7501
  const isEditMode = isEditSessionActive();
6633
- const [bridgeRoot, setBridgeRoot] = (0, import_react6.useState)(null);
6634
- (0, import_react6.useEffect)(() => {
7502
+ const [bridgeRoot, setBridgeRoot] = (0, import_react8.useState)(null);
7503
+ (0, import_react8.useEffect)(() => {
6635
7504
  const figtreeFontId = "ohw-figtree-font";
6636
7505
  if (!document.getElementById(figtreeFontId)) {
6637
7506
  const preconnect1 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.googleapis.com" });
@@ -6659,83 +7528,91 @@ function OhhwellsBridge() {
6659
7528
  const subdomainFromQuery = searchParams.get("subdomain");
6660
7529
  const subdomain = resolveSubdomain(subdomainFromQuery);
6661
7530
  useLinkHrefGuardian(pathname, subdomain, isEditMode);
6662
- const postToParent = (0, import_react6.useCallback)((data) => {
7531
+ const postToParent2 = (0, import_react8.useCallback)((data) => {
6663
7532
  if (typeof window !== "undefined" && window.parent !== window) {
6664
7533
  window.parent.postMessage(data, "*");
6665
7534
  }
6666
7535
  }, []);
6667
- const [fetchState, setFetchState] = (0, import_react6.useState)("idle");
6668
- const autoSaveTimers = (0, import_react6.useRef)(/* @__PURE__ */ new Map());
6669
- const activeElRef = (0, import_react6.useRef)(null);
6670
- const selectedElRef = (0, import_react6.useRef)(null);
6671
- const originalContentRef = (0, import_react6.useRef)(null);
6672
- const activeStateElRef = (0, import_react6.useRef)(null);
6673
- const parentScrollRef = (0, import_react6.useRef)(null);
6674
- const visibleViewportRef = (0, import_react6.useRef)(null);
6675
- const [dialogPortalContainer, setDialogPortalContainer] = (0, import_react6.useState)(null);
6676
- const attachVisibleViewport = (0, import_react6.useCallback)((node) => {
7536
+ const [fetchState, setFetchState] = (0, import_react8.useState)("idle");
7537
+ const autoSaveTimers = (0, import_react8.useRef)(/* @__PURE__ */ new Map());
7538
+ const activeElRef = (0, import_react8.useRef)(null);
7539
+ const selectedElRef = (0, import_react8.useRef)(null);
7540
+ const originalContentRef = (0, import_react8.useRef)(null);
7541
+ const activeStateElRef = (0, import_react8.useRef)(null);
7542
+ const parentScrollRef = (0, import_react8.useRef)(null);
7543
+ const visibleViewportRef = (0, import_react8.useRef)(null);
7544
+ const [dialogPortalContainer, setDialogPortalContainer] = (0, import_react8.useState)(null);
7545
+ const attachVisibleViewport = (0, import_react8.useCallback)((node) => {
6677
7546
  visibleViewportRef.current = node;
6678
7547
  setDialogPortalContainer(node);
6679
7548
  if (node) applyVisibleViewport(node, parentScrollRef.current);
6680
7549
  }, []);
6681
- const toolbarElRef = (0, import_react6.useRef)(null);
6682
- const glowElRef = (0, import_react6.useRef)(null);
6683
- const hoveredImageRef = (0, import_react6.useRef)(null);
6684
- const hoveredImageHasTextOverlapRef = (0, import_react6.useRef)(false);
6685
- const dragOverElRef = (0, import_react6.useRef)(null);
6686
- const [mediaHover, setMediaHover] = (0, import_react6.useState)(null);
6687
- const [uploadingRects, setUploadingRects] = (0, import_react6.useState)({});
6688
- const hoveredGapRef = (0, import_react6.useRef)(null);
6689
- const imageUnhoverTimerRef = (0, import_react6.useRef)(null);
6690
- const imageShowTimerRef = (0, import_react6.useRef)(null);
6691
- const editStylesRef = (0, import_react6.useRef)(null);
6692
- const activateRef = (0, import_react6.useRef)(() => {
7550
+ const toolbarElRef = (0, import_react8.useRef)(null);
7551
+ const glowElRef = (0, import_react8.useRef)(null);
7552
+ const hoveredImageRef = (0, import_react8.useRef)(null);
7553
+ const hoveredImageHasTextOverlapRef = (0, import_react8.useRef)(false);
7554
+ const dragOverElRef = (0, import_react8.useRef)(null);
7555
+ const [mediaHover, setMediaHover] = (0, import_react8.useState)(null);
7556
+ const [uploadingRects, setUploadingRects] = (0, import_react8.useState)({});
7557
+ const hoveredGapRef = (0, import_react8.useRef)(null);
7558
+ const imageUnhoverTimerRef = (0, import_react8.useRef)(null);
7559
+ const imageShowTimerRef = (0, import_react8.useRef)(null);
7560
+ const editStylesRef = (0, import_react8.useRef)(null);
7561
+ const activateRef = (0, import_react8.useRef)(() => {
7562
+ });
7563
+ const deactivateRef = (0, import_react8.useRef)(() => {
6693
7564
  });
6694
- const deactivateRef = (0, import_react6.useRef)(() => {
7565
+ const selectRef = (0, import_react8.useRef)(() => {
6695
7566
  });
6696
- const selectRef = (0, import_react6.useRef)(() => {
7567
+ const selectFrameRef = (0, import_react8.useRef)(() => {
6697
7568
  });
6698
- const deselectRef = (0, import_react6.useRef)(() => {
7569
+ const deselectRef = (0, import_react8.useRef)(() => {
6699
7570
  });
6700
- const reselectNavigationItemRef = (0, import_react6.useRef)(() => {
7571
+ const reselectNavigationItemRef = (0, import_react8.useRef)(() => {
6701
7572
  });
6702
- const commitNavigationTextEditRef = (0, import_react6.useRef)(() => {
7573
+ const commitNavigationTextEditRef = (0, import_react8.useRef)(() => {
6703
7574
  });
6704
- const refreshActiveCommandsRef = (0, import_react6.useRef)(() => {
7575
+ const refreshActiveCommandsRef = (0, import_react8.useRef)(() => {
6705
7576
  });
6706
- const postToParentRef = (0, import_react6.useRef)(postToParent);
6707
- postToParentRef.current = postToParent;
6708
- const sectionsLoadedRef = (0, import_react6.useRef)(false);
6709
- const pendingScheduleConfigRequests = (0, import_react6.useRef)([]);
6710
- const [toolbarRect, setToolbarRect] = (0, import_react6.useState)(null);
6711
- const [toolbarVariant, setToolbarVariant] = (0, import_react6.useState)("none");
6712
- const toolbarVariantRef = (0, import_react6.useRef)("none");
7577
+ const postToParentRef = (0, import_react8.useRef)(postToParent2);
7578
+ postToParentRef.current = postToParent2;
7579
+ const sectionsLoadedRef = (0, import_react8.useRef)(false);
7580
+ const pendingScheduleConfigRequests = (0, import_react8.useRef)([]);
7581
+ const [toolbarRect, setToolbarRect] = (0, import_react8.useState)(null);
7582
+ const [toolbarVariant, setToolbarVariant] = (0, import_react8.useState)("none");
7583
+ const toolbarVariantRef = (0, import_react8.useRef)("none");
6713
7584
  toolbarVariantRef.current = toolbarVariant;
6714
- const [reorderHrefKey, setReorderHrefKey] = (0, import_react6.useState)(null);
6715
- const [reorderDragDisabled, setReorderDragDisabled] = (0, import_react6.useState)(false);
6716
- const [toggleState, setToggleState] = (0, import_react6.useState)(null);
6717
- const [maxBadge, setMaxBadge] = (0, import_react6.useState)(null);
6718
- const [activeCommands, setActiveCommands] = (0, import_react6.useState)(/* @__PURE__ */ new Set());
6719
- const [sectionGap, setSectionGap] = (0, import_react6.useState)(null);
6720
- const [toolbarShowEditLink, setToolbarShowEditLink] = (0, import_react6.useState)(false);
6721
- const hoveredItemElRef = (0, import_react6.useRef)(null);
6722
- const [hoveredItemRect, setHoveredItemRect] = (0, import_react6.useState)(null);
6723
- const siblingHintElRef = (0, import_react6.useRef)(null);
6724
- const [siblingHintRect, setSiblingHintRect] = (0, import_react6.useState)(null);
6725
- const [isItemDragging, setIsItemDragging] = (0, import_react6.useState)(false);
6726
- const [linkPopover, setLinkPopover] = (0, import_react6.useState)(null);
6727
- const [sitePages, setSitePages] = (0, import_react6.useState)([]);
6728
- const [sectionsByPath, setSectionsByPath] = (0, import_react6.useState)({});
6729
- const sectionsPrefetchGenRef = (0, import_react6.useRef)(0);
6730
- const setLinkPopoverRef = (0, import_react6.useRef)(setLinkPopover);
6731
- const linkPopoverPanelRef = (0, import_react6.useRef)(null);
6732
- const linkPopoverOpenRef = (0, import_react6.useRef)(false);
6733
- const linkPopoverGraceUntilRef = (0, import_react6.useRef)(0);
7585
+ const [reorderHrefKey, setReorderHrefKey] = (0, import_react8.useState)(null);
7586
+ const [reorderDragDisabled, setReorderDragDisabled] = (0, import_react8.useState)(false);
7587
+ const [toggleState, setToggleState] = (0, import_react8.useState)(null);
7588
+ const [maxBadge, setMaxBadge] = (0, import_react8.useState)(null);
7589
+ const [activeCommands, setActiveCommands] = (0, import_react8.useState)(/* @__PURE__ */ new Set());
7590
+ const [sectionGap, setSectionGap] = (0, import_react8.useState)(null);
7591
+ const [toolbarShowEditLink, setToolbarShowEditLink] = (0, import_react8.useState)(false);
7592
+ const hoveredNavContainerRef = (0, import_react8.useRef)(null);
7593
+ const [hoveredNavContainerRect, setHoveredNavContainerRect] = (0, import_react8.useState)(null);
7594
+ const hoveredItemElRef = (0, import_react8.useRef)(null);
7595
+ const [hoveredItemRect, setHoveredItemRect] = (0, import_react8.useState)(null);
7596
+ const siblingHintElRef = (0, import_react8.useRef)(null);
7597
+ const [siblingHintRect, setSiblingHintRect] = (0, import_react8.useState)(null);
7598
+ const [isItemDragging, setIsItemDragging] = (0, import_react8.useState)(false);
7599
+ const [linkPopover, setLinkPopover] = (0, import_react8.useState)(null);
7600
+ const linkPopoverSessionRef = (0, import_react8.useRef)(null);
7601
+ const addNavAfterAnchorRef = (0, import_react8.useRef)(null);
7602
+ const editContentRef = (0, import_react8.useRef)({});
7603
+ const [sitePages, setSitePages] = (0, import_react8.useState)([]);
7604
+ const [sectionsByPath, setSectionsByPath] = (0, import_react8.useState)({});
7605
+ const sectionsPrefetchGenRef = (0, import_react8.useRef)(0);
7606
+ const setLinkPopoverRef = (0, import_react8.useRef)(setLinkPopover);
7607
+ const linkPopoverPanelRef = (0, import_react8.useRef)(null);
7608
+ const linkPopoverOpenRef = (0, import_react8.useRef)(false);
7609
+ const linkPopoverGraceUntilRef = (0, import_react8.useRef)(0);
6734
7610
  setLinkPopoverRef.current = setLinkPopover;
7611
+ linkPopoverSessionRef.current = linkPopover;
6735
7612
  const bumpLinkPopoverGrace = () => {
6736
7613
  linkPopoverGraceUntilRef.current = Date.now() + 350;
6737
7614
  };
6738
- const runSectionsPrefetch = (0, import_react6.useCallback)((pages) => {
7615
+ const runSectionsPrefetch = (0, import_react8.useCallback)((pages) => {
6739
7616
  if (!isEditMode || shouldUseDevFixtures() || pages.length === 0) return;
6740
7617
  const gen = ++sectionsPrefetchGenRef.current;
6741
7618
  const paths = pages.map((p) => p.path);
@@ -6754,9 +7631,9 @@ function OhhwellsBridge() {
6754
7631
  );
6755
7632
  });
6756
7633
  }, [isEditMode, pathname]);
6757
- const runSectionsPrefetchRef = (0, import_react6.useRef)(runSectionsPrefetch);
7634
+ const runSectionsPrefetchRef = (0, import_react8.useRef)(runSectionsPrefetch);
6758
7635
  runSectionsPrefetchRef.current = runSectionsPrefetch;
6759
- (0, import_react6.useEffect)(() => {
7636
+ (0, import_react8.useEffect)(() => {
6760
7637
  if (!linkPopover) return;
6761
7638
  if (hoveredImageRef.current) {
6762
7639
  hoveredImageRef.current = null;
@@ -6765,7 +7642,7 @@ function OhhwellsBridge() {
6765
7642
  hoveredGapRef.current = null;
6766
7643
  setSectionGap(null);
6767
7644
  setMediaHover(null);
6768
- postToParent({ type: "ow:link-modal-lock", locked: true });
7645
+ postToParent2({ type: "ow:link-modal-lock", locked: true });
6769
7646
  const html = document.documentElement;
6770
7647
  const body = document.body;
6771
7648
  const prevHtmlOverflow = html.style.overflow;
@@ -6782,15 +7659,16 @@ function OhhwellsBridge() {
6782
7659
  const scrollOpts = { passive: false, capture: true };
6783
7660
  document.addEventListener("wheel", preventBackgroundScroll, scrollOpts);
6784
7661
  document.addEventListener("touchmove", preventBackgroundScroll, scrollOpts);
7662
+ postToParent2({ type: "ow:image-unhover" });
6785
7663
  return () => {
6786
- postToParent({ type: "ow:link-modal-lock", locked: false });
7664
+ postToParent2({ type: "ow:link-modal-lock", locked: false });
6787
7665
  html.style.overflow = prevHtmlOverflow;
6788
7666
  body.style.overflow = prevBodyOverflow;
6789
7667
  document.removeEventListener("wheel", preventBackgroundScroll, scrollOpts);
6790
7668
  document.removeEventListener("touchmove", preventBackgroundScroll, scrollOpts);
6791
7669
  };
6792
- }, [linkPopover, postToParent]);
6793
- (0, import_react6.useEffect)(() => {
7670
+ }, [linkPopover, postToParent2]);
7671
+ (0, import_react8.useEffect)(() => {
6794
7672
  if (!isEditMode) return;
6795
7673
  const useFixtures = shouldUseDevFixtures();
6796
7674
  if (useFixtures) {
@@ -6811,17 +7689,17 @@ function OhhwellsBridge() {
6811
7689
  runSectionsPrefetchRef.current(mapped);
6812
7690
  };
6813
7691
  window.addEventListener("message", onSitePages);
6814
- if (!useFixtures) postToParent({ type: "ow:request-site-pages" });
7692
+ if (!useFixtures) postToParent2({ type: "ow:request-site-pages" });
6815
7693
  return () => window.removeEventListener("message", onSitePages);
6816
- }, [isEditMode, postToParent]);
6817
- (0, import_react6.useEffect)(() => {
7694
+ }, [isEditMode, postToParent2]);
7695
+ (0, import_react8.useEffect)(() => {
6818
7696
  if (!isEditMode || shouldUseDevFixtures()) return;
6819
7697
  void loadAllSectionsManifest().then((manifest) => {
6820
7698
  if (Object.keys(manifest).length === 0) return;
6821
7699
  setSectionsByPath((prev) => ({ ...manifest, ...prev }));
6822
7700
  });
6823
7701
  }, [isEditMode]);
6824
- (0, import_react6.useEffect)(() => {
7702
+ (0, import_react8.useEffect)(() => {
6825
7703
  const update = () => {
6826
7704
  const el = activeElRef.current ?? selectedElRef.current;
6827
7705
  if (el) setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
@@ -6845,10 +7723,10 @@ function OhhwellsBridge() {
6845
7723
  vvp.removeEventListener("resize", update);
6846
7724
  };
6847
7725
  }, []);
6848
- const refreshStateRules = (0, import_react6.useCallback)(() => {
7726
+ const refreshStateRules = (0, import_react8.useCallback)(() => {
6849
7727
  editStylesRef.current?.forceHover && (editStylesRef.current.forceHover.textContent = collectStateRules());
6850
7728
  }, []);
6851
- const processConfigRequest = (0, import_react6.useCallback)((insertAfterVal) => {
7729
+ const processConfigRequest = (0, import_react8.useCallback)((insertAfterVal) => {
6852
7730
  const tracker = getSectionsTracker();
6853
7731
  let entries = [];
6854
7732
  try {
@@ -6871,7 +7749,7 @@ function OhhwellsBridge() {
6871
7749
  }
6872
7750
  window.postMessage({ type: "ow:schedule-config", insertAfter: insertAfterVal, scheduleId: null }, "*");
6873
7751
  }, [isEditMode]);
6874
- const deactivate = (0, import_react6.useCallback)(() => {
7752
+ const deactivate = (0, import_react8.useCallback)(() => {
6875
7753
  const el = activeElRef.current;
6876
7754
  if (!el) return;
6877
7755
  const key = el.dataset.ohwKey;
@@ -6900,21 +7778,23 @@ function OhhwellsBridge() {
6900
7778
  setMaxBadge(null);
6901
7779
  setActiveCommands(/* @__PURE__ */ new Set());
6902
7780
  setToolbarShowEditLink(false);
6903
- postToParent({ type: "ow:exit-edit" });
6904
- }, [postToParent]);
6905
- const deselect = (0, import_react6.useCallback)(() => {
7781
+ postToParent2({ type: "ow:exit-edit" });
7782
+ }, [postToParent2]);
7783
+ const deselect = (0, import_react8.useCallback)(() => {
6906
7784
  selectedElRef.current = null;
6907
7785
  setReorderHrefKey(null);
6908
7786
  setReorderDragDisabled(false);
6909
7787
  siblingHintElRef.current = null;
6910
7788
  setSiblingHintRect(null);
6911
7789
  setIsItemDragging(false);
7790
+ hoveredNavContainerRef.current = null;
7791
+ setHoveredNavContainerRect(null);
6912
7792
  if (!activeElRef.current) {
6913
7793
  setToolbarRect(null);
6914
7794
  setToolbarVariant("none");
6915
7795
  }
6916
7796
  }, []);
6917
- const reselectNavigationItem = (0, import_react6.useCallback)((navAnchor) => {
7797
+ const reselectNavigationItem = (0, import_react8.useCallback)((navAnchor) => {
6918
7798
  selectedElRef.current = navAnchor;
6919
7799
  const { key, disabled } = getNavigationItemReorderState(navAnchor);
6920
7800
  setReorderHrefKey(key);
@@ -6924,7 +7804,7 @@ function OhhwellsBridge() {
6924
7804
  setToolbarShowEditLink(false);
6925
7805
  setActiveCommands(/* @__PURE__ */ new Set());
6926
7806
  }, []);
6927
- const commitNavigationTextEdit = (0, import_react6.useCallback)((navAnchor) => {
7807
+ const commitNavigationTextEdit = (0, import_react8.useCallback)((navAnchor) => {
6928
7808
  const el = activeElRef.current;
6929
7809
  if (!el) return;
6930
7810
  const key = el.dataset.ohwKey;
@@ -6937,9 +7817,9 @@ function OhhwellsBridge() {
6937
7817
  const html = sanitizeHtml(el.innerHTML);
6938
7818
  const original = originalContentRef.current ?? "";
6939
7819
  if (html !== sanitizeHtml(original)) {
6940
- postToParent({ type: "ow:change", nodes: [{ key, text: html }] });
7820
+ postToParent2({ type: "ow:change", nodes: [{ key, text: html }] });
6941
7821
  const h = document.documentElement.scrollHeight;
6942
- if (h > 50) postToParent({ type: "ow:height", height: h });
7822
+ if (h > 50) postToParent2({ type: "ow:height", height: h });
6943
7823
  }
6944
7824
  }
6945
7825
  el.removeAttribute("contenteditable");
@@ -6947,31 +7827,38 @@ function OhhwellsBridge() {
6947
7827
  setMaxBadge(null);
6948
7828
  setActiveCommands(/* @__PURE__ */ new Set());
6949
7829
  setToolbarShowEditLink(false);
6950
- postToParent({ type: "ow:exit-edit" });
7830
+ postToParent2({ type: "ow:exit-edit" });
6951
7831
  reselectNavigationItem(navAnchor);
6952
- }, [postToParent, reselectNavigationItem]);
6953
- const handleAddNavigationItem = (0, import_react6.useCallback)(() => {
6954
- const selected = selectedElRef.current;
6955
- if (!selected) return;
6956
- const target = getNavigationItemAddHintTarget(selected);
6957
- siblingHintElRef.current = target;
6958
- setSiblingHintRect(target.getBoundingClientRect());
7832
+ }, [postToParent2, reselectNavigationItem]);
7833
+ const handleAddTopLevelNavItem = (0, import_react8.useCallback)(() => {
7834
+ const items = listNavbarItems();
7835
+ addNavAfterAnchorRef.current = items[items.length - 1] ?? null;
7836
+ deselectRef.current();
7837
+ deactivateRef.current();
7838
+ bumpLinkPopoverGrace();
7839
+ setLinkPopover({
7840
+ key: "__add-nav__",
7841
+ mode: "create",
7842
+ intent: "add-nav"
7843
+ });
6959
7844
  }, []);
6960
- const handleItemDragStart = (0, import_react6.useCallback)(() => {
7845
+ const handleItemDragStart = (0, import_react8.useCallback)(() => {
6961
7846
  siblingHintElRef.current = null;
6962
7847
  setSiblingHintRect(null);
6963
7848
  setIsItemDragging(true);
6964
7849
  }, []);
6965
- const handleItemDragEnd = (0, import_react6.useCallback)(() => {
7850
+ const handleItemDragEnd = (0, import_react8.useCallback)(() => {
6966
7851
  setIsItemDragging(false);
6967
7852
  }, []);
6968
7853
  reselectNavigationItemRef.current = reselectNavigationItem;
6969
7854
  commitNavigationTextEditRef.current = commitNavigationTextEdit;
6970
- const select = (0, import_react6.useCallback)((anchor) => {
7855
+ const select = (0, import_react8.useCallback)((anchor) => {
6971
7856
  if (!isNavigationItem(anchor)) return;
6972
7857
  if (activeElRef.current) deactivate();
6973
7858
  selectedElRef.current = anchor;
6974
7859
  clearHrefKeyHover(anchor);
7860
+ hoveredNavContainerRef.current = null;
7861
+ setHoveredNavContainerRect(null);
6975
7862
  setHoveredItemRect(null);
6976
7863
  hoveredItemElRef.current = null;
6977
7864
  siblingHintElRef.current = null;
@@ -6985,7 +7872,26 @@ function OhhwellsBridge() {
6985
7872
  setToolbarShowEditLink(false);
6986
7873
  setActiveCommands(/* @__PURE__ */ new Set());
6987
7874
  }, [deactivate]);
6988
- const activate = (0, import_react6.useCallback)((el) => {
7875
+ const selectFrame = (0, import_react8.useCallback)((el) => {
7876
+ if (!isNavigationContainer(el)) return;
7877
+ if (activeElRef.current) deactivate();
7878
+ selectedElRef.current = el;
7879
+ clearHrefKeyHover(el);
7880
+ hoveredNavContainerRef.current = null;
7881
+ setHoveredNavContainerRect(null);
7882
+ setHoveredItemRect(null);
7883
+ hoveredItemElRef.current = null;
7884
+ siblingHintElRef.current = null;
7885
+ setSiblingHintRect(null);
7886
+ setIsItemDragging(false);
7887
+ setReorderHrefKey(null);
7888
+ setReorderDragDisabled(false);
7889
+ setToolbarVariant("select-frame");
7890
+ setToolbarRect(el.getBoundingClientRect());
7891
+ setToolbarShowEditLink(false);
7892
+ setActiveCommands(/* @__PURE__ */ new Set());
7893
+ }, [deactivate]);
7894
+ const activate = (0, import_react8.useCallback)((el, options) => {
6989
7895
  if (activeElRef.current === el) return;
6990
7896
  selectedElRef.current = null;
6991
7897
  deactivate();
@@ -7003,6 +7909,9 @@ function OhhwellsBridge() {
7003
7909
  activeElRef.current = el;
7004
7910
  originalContentRef.current = el.innerHTML;
7005
7911
  el.focus();
7912
+ if (options?.caretX !== void 0 && options?.caretY !== void 0) {
7913
+ placeCaretAtPoint(el, options.caretX, options.caretY);
7914
+ }
7006
7915
  setToolbarShowEditLink(Boolean(getHrefKeyFromElement(el)));
7007
7916
  const navAnchor = getNavigationItemAnchor(el);
7008
7917
  if (navAnchor) {
@@ -7014,14 +7923,15 @@ function OhhwellsBridge() {
7014
7923
  setReorderDragDisabled(reorderDisabled);
7015
7924
  }
7016
7925
  setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
7017
- postToParent({ type: "ow:enter-edit", key: el.dataset.ohwKey });
7926
+ postToParent2({ type: "ow:enter-edit", key: el.dataset.ohwKey });
7018
7927
  requestAnimationFrame(() => refreshActiveCommandsRef.current());
7019
- }, [deactivate, postToParent]);
7928
+ }, [deactivate, postToParent2]);
7020
7929
  activateRef.current = activate;
7021
7930
  deactivateRef.current = deactivate;
7022
7931
  selectRef.current = select;
7932
+ selectFrameRef.current = selectFrame;
7023
7933
  deselectRef.current = deselect;
7024
- (0, import_react6.useLayoutEffect)(() => {
7934
+ (0, import_react8.useLayoutEffect)(() => {
7025
7935
  if (!subdomain || isEditMode) {
7026
7936
  setFetchState("done");
7027
7937
  return;
@@ -7061,6 +7971,7 @@ function OhhwellsBridge() {
7061
7971
  });
7062
7972
  applyLinkByKey(key, val);
7063
7973
  }
7974
+ reconcileNavbarItemsFromContent(content);
7064
7975
  enforceLinkHrefs();
7065
7976
  initSectionsFromContent(content, true);
7066
7977
  sectionsLoadedRef.current = true;
@@ -7089,7 +8000,7 @@ function OhhwellsBridge() {
7089
8000
  cancelled = true;
7090
8001
  };
7091
8002
  }, [subdomain, isEditMode]);
7092
- (0, import_react6.useEffect)(() => {
8003
+ (0, import_react8.useEffect)(() => {
7093
8004
  if (!subdomain || isEditMode) return;
7094
8005
  let debounceTimer = null;
7095
8006
  let observer = null;
@@ -7120,6 +8031,7 @@ function OhhwellsBridge() {
7120
8031
  });
7121
8032
  applyLinkByKey(key, val);
7122
8033
  }
8034
+ reconcileNavbarItemsFromContent(content);
7123
8035
  } finally {
7124
8036
  observer?.observe(document.body, { childList: true, subtree: true });
7125
8037
  }
@@ -7137,26 +8049,44 @@ function OhhwellsBridge() {
7137
8049
  if (debounceTimer) clearTimeout(debounceTimer);
7138
8050
  };
7139
8051
  }, [subdomain, isEditMode, pathname]);
7140
- (0, import_react6.useLayoutEffect)(() => {
8052
+ (0, import_react8.useLayoutEffect)(() => {
7141
8053
  const el = document.getElementById("ohw-loader");
7142
8054
  if (!el) return;
7143
8055
  const visible = Boolean(subdomain) && fetchState !== "done";
7144
8056
  el.style.display = visible ? "flex" : "none";
7145
8057
  }, [subdomain, fetchState]);
7146
- (0, import_react6.useEffect)(() => {
7147
- postToParent({ type: "ow:navigation", path: pathname });
7148
- }, [pathname, postToParent]);
7149
- (0, import_react6.useEffect)(() => {
8058
+ (0, import_react8.useEffect)(() => {
8059
+ postToParent2({ type: "ow:navigation", path: pathname });
8060
+ }, [pathname, postToParent2]);
8061
+ (0, import_react8.useEffect)(() => {
7150
8062
  if (!isEditMode) return;
8063
+ if (linkPopoverSessionRef.current?.intent === "add-nav") return;
8064
+ if (document.querySelector("[data-ohw-section-picker]")) return;
7151
8065
  setLinkPopover(null);
7152
8066
  deselectRef.current();
7153
8067
  deactivateRef.current();
7154
8068
  }, [pathname, isEditMode]);
7155
- (0, import_react6.useEffect)(() => {
8069
+ (0, import_react8.useEffect)(() => {
8070
+ const contentForNav = () => {
8071
+ if (isEditMode) return editContentRef.current;
8072
+ if (!subdomain) return {};
8073
+ return contentCache.get(subdomain) ?? {};
8074
+ };
8075
+ const run = () => reconcileNavbarItemsFromContent(contentForNav());
8076
+ run();
8077
+ const nav = document.querySelector("nav");
8078
+ if (!nav) return;
8079
+ const observer = new MutationObserver(() => {
8080
+ requestAnimationFrame(run);
8081
+ });
8082
+ observer.observe(nav, { childList: true, subtree: true });
8083
+ return () => observer.disconnect();
8084
+ }, [isEditMode, pathname, subdomain, fetchState]);
8085
+ (0, import_react8.useEffect)(() => {
7156
8086
  if (!isEditMode) return;
7157
8087
  const measure = () => {
7158
8088
  const h = document.body.scrollHeight;
7159
- if (h > 50) postToParent({ type: "ow:height", height: h });
8089
+ if (h > 50) postToParent2({ type: "ow:height", height: h });
7160
8090
  };
7161
8091
  const t1 = setTimeout(measure, 50);
7162
8092
  const t2 = setTimeout(measure, 500);
@@ -7175,8 +8105,8 @@ function OhhwellsBridge() {
7175
8105
  if (resizeTimer) clearTimeout(resizeTimer);
7176
8106
  window.removeEventListener("resize", handleResize);
7177
8107
  };
7178
- }, [pathname, isEditMode, postToParent]);
7179
- (0, import_react6.useEffect)(() => {
8108
+ }, [pathname, isEditMode, postToParent2]);
8109
+ (0, import_react8.useEffect)(() => {
7180
8110
  if (!subdomainFromQuery || isEditMode) return;
7181
8111
  const handleClick = (e) => {
7182
8112
  const anchor = e.target.closest("a");
@@ -7192,7 +8122,7 @@ function OhhwellsBridge() {
7192
8122
  document.addEventListener("click", handleClick, true);
7193
8123
  return () => document.removeEventListener("click", handleClick, true);
7194
8124
  }, [subdomainFromQuery, isEditMode, router]);
7195
- (0, import_react6.useEffect)(() => {
8125
+ (0, import_react8.useEffect)(() => {
7196
8126
  if (!isEditMode) {
7197
8127
  editStylesRef.current?.base.remove();
7198
8128
  editStylesRef.current?.forceHover.remove();
@@ -7265,6 +8195,13 @@ function OhhwellsBridge() {
7265
8195
  if (target.closest("[data-ohw-max-badge]")) return;
7266
8196
  if (isInsideLinkEditor(target)) return;
7267
8197
  if (target.closest('[data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
8198
+ const navFromChrome = resolveNavContainerSelectionTarget(target, e.clientX, e.clientY);
8199
+ if (navFromChrome) {
8200
+ e.preventDefault();
8201
+ e.stopPropagation();
8202
+ selectFrameRef.current(navFromChrome);
8203
+ return;
8204
+ }
7268
8205
  e.preventDefault();
7269
8206
  e.stopPropagation();
7270
8207
  return;
@@ -7279,7 +8216,8 @@ function OhhwellsBridge() {
7279
8216
  bumpLinkPopoverGrace();
7280
8217
  setLinkPopoverRef.current({
7281
8218
  key: editable.dataset.ohwKey ?? "",
7282
- target: getLinkHref(editable)
8219
+ mode: "edit",
8220
+ target: getLinkHref2(editable)
7283
8221
  });
7284
8222
  return;
7285
8223
  }
@@ -7295,7 +8233,8 @@ function OhhwellsBridge() {
7295
8233
  e.preventDefault();
7296
8234
  e.stopPropagation();
7297
8235
  if (selectedElRef.current === navAnchor) {
7298
- activateRef.current(editable);
8236
+ if (e.detail >= 2) return;
8237
+ activateRef.current(editable, { caretX: e.clientX, caretY: e.clientY });
7299
8238
  return;
7300
8239
  }
7301
8240
  selectRef.current(navAnchor);
@@ -7314,6 +8253,22 @@ function OhhwellsBridge() {
7314
8253
  selectRef.current(hrefAnchor);
7315
8254
  return;
7316
8255
  }
8256
+ const navContainerToSelect = resolveNavContainerSelectionTarget(target, e.clientX, e.clientY);
8257
+ if (navContainerToSelect) {
8258
+ e.preventDefault();
8259
+ e.stopPropagation();
8260
+ selectFrameRef.current(navContainerToSelect);
8261
+ return;
8262
+ }
8263
+ const selectedContainer = selectedElRef.current;
8264
+ if (selectedContainer && isNavigationContainer(selectedContainer)) {
8265
+ const navItem = getNavigationItemAnchor(target);
8266
+ if (!navItem && (selectedContainer === target || selectedContainer.contains(target))) {
8267
+ e.preventDefault();
8268
+ e.stopPropagation();
8269
+ return;
8270
+ }
8271
+ }
7317
8272
  if (activeElRef.current) {
7318
8273
  const sel = window.getSelection();
7319
8274
  if (sel && !sel.isCollapsed) {
@@ -7339,10 +8294,49 @@ function OhhwellsBridge() {
7339
8294
  deselectRef.current();
7340
8295
  deactivateRef.current();
7341
8296
  };
8297
+ const handleDblClick = (e) => {
8298
+ const target = e.target;
8299
+ if (target.closest("[data-ohw-toolbar]")) return;
8300
+ if (target.closest("[data-ohw-state-toggle]")) return;
8301
+ if (target.closest("[data-ohw-max-badge]")) return;
8302
+ if (isInsideLinkEditor(target)) return;
8303
+ if (target.closest('[data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
8304
+ return;
8305
+ }
8306
+ const navLabel = getNavigationLabelEditable(target);
8307
+ if (!navLabel) return;
8308
+ const { editable } = navLabel;
8309
+ e.preventDefault();
8310
+ e.stopPropagation();
8311
+ if (activeElRef.current !== editable) {
8312
+ activateRef.current(editable);
8313
+ }
8314
+ requestAnimationFrame(() => {
8315
+ selectAllTextInEditable(editable);
8316
+ refreshActiveCommandsRef.current();
8317
+ });
8318
+ };
7342
8319
  const handleMouseOver = (e) => {
8320
+ if (document.documentElement.hasAttribute("data-ohw-section-picking")) return;
7343
8321
  const target = e.target;
8322
+ if (toolbarVariantRef.current !== "select-frame") {
8323
+ const navContainer = target.closest("[data-ohw-nav-container]");
8324
+ if (navContainer && !getNavigationItemAnchor(target)) {
8325
+ hoveredNavContainerRef.current = navContainer;
8326
+ setHoveredNavContainerRect(navContainer.getBoundingClientRect());
8327
+ hoveredItemElRef.current = null;
8328
+ setHoveredItemRect(null);
8329
+ return;
8330
+ }
8331
+ if (!target.closest("[data-ohw-nav-container]")) {
8332
+ hoveredNavContainerRef.current = null;
8333
+ setHoveredNavContainerRect(null);
8334
+ }
8335
+ }
7344
8336
  const navAnchor = getNavigationItemAnchor(target);
7345
8337
  if (navAnchor) {
8338
+ hoveredNavContainerRef.current = null;
8339
+ setHoveredNavContainerRect(null);
7346
8340
  const selected2 = selectedElRef.current;
7347
8341
  if (selected2 === navAnchor || selected2?.contains(navAnchor)) return;
7348
8342
  clearHrefKeyHover(navAnchor);
@@ -7367,6 +8361,14 @@ function OhhwellsBridge() {
7367
8361
  };
7368
8362
  const handleMouseOut = (e) => {
7369
8363
  const target = e.target;
8364
+ if (target.closest("[data-ohw-nav-container]")) {
8365
+ const related2 = e.relatedTarget instanceof Element ? e.relatedTarget : null;
8366
+ if (related2?.closest("[data-ohw-nav-container], [data-ohw-navbar-container-chrome], [data-ohw-navbar-add-button]")) {
8367
+ return;
8368
+ }
8369
+ hoveredNavContainerRef.current = null;
8370
+ setHoveredNavContainerRect(null);
8371
+ }
7370
8372
  const navAnchor = getNavigationItemAnchor(target);
7371
8373
  if (navAnchor) {
7372
8374
  const related2 = e.relatedTarget instanceof Element ? e.relatedTarget : null;
@@ -7488,6 +8490,56 @@ function OhhwellsBridge() {
7488
8490
  }
7489
8491
  return smallest;
7490
8492
  };
8493
+ const dismissImageHover = () => {
8494
+ if (hoveredImageRef.current) {
8495
+ hoveredImageRef.current = null;
8496
+ hoveredImageHasTextOverlapRef.current = false;
8497
+ resumeAnimTracks();
8498
+ postToParentRef.current({ type: "ow:image-unhover" });
8499
+ }
8500
+ };
8501
+ const probeNavigationHoverAt = (x, y) => {
8502
+ if (toolbarVariantRef.current === "select-frame") {
8503
+ hoveredNavContainerRef.current = null;
8504
+ setHoveredNavContainerRect(null);
8505
+ return;
8506
+ }
8507
+ const navContainer = document.querySelector("[data-ohw-nav-container]");
8508
+ if (!navContainer) {
8509
+ hoveredNavContainerRef.current = null;
8510
+ setHoveredNavContainerRect(null);
8511
+ return;
8512
+ }
8513
+ const containerRect = navContainer.getBoundingClientRect();
8514
+ const overContainer = x >= containerRect.left && x <= containerRect.right && y >= containerRect.top && y <= containerRect.bottom;
8515
+ if (!overContainer) {
8516
+ hoveredNavContainerRef.current = null;
8517
+ setHoveredNavContainerRect(null);
8518
+ return;
8519
+ }
8520
+ const navItemHit = Array.from(
8521
+ navContainer.querySelectorAll("[data-ohw-href-key]")
8522
+ ).find((el) => {
8523
+ if (!isNavigationItem(el) || isNavbarButton2(el)) return false;
8524
+ const itemRect = el.getBoundingClientRect();
8525
+ return x >= itemRect.left && x <= itemRect.right && y >= itemRect.top && y <= itemRect.bottom;
8526
+ });
8527
+ if (navItemHit) {
8528
+ hoveredNavContainerRef.current = null;
8529
+ setHoveredNavContainerRect(null);
8530
+ const selected = selectedElRef.current;
8531
+ if (selected !== navItemHit && !selected?.contains(navItemHit)) {
8532
+ clearHrefKeyHover(navItemHit);
8533
+ hoveredItemElRef.current = navItemHit;
8534
+ setHoveredItemRect(navItemHit.getBoundingClientRect());
8535
+ }
8536
+ return;
8537
+ }
8538
+ hoveredNavContainerRef.current = navContainer;
8539
+ setHoveredNavContainerRect(containerRect);
8540
+ hoveredItemElRef.current = null;
8541
+ setHoveredItemRect(null);
8542
+ };
7491
8543
  const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
7492
8544
  if (linkPopoverOpenRef.current) {
7493
8545
  if (hoveredImageRef.current) {
@@ -7496,18 +8548,22 @@ function OhhwellsBridge() {
7496
8548
  resumeAnimTracks();
7497
8549
  clearImageHover();
7498
8550
  }
8551
+ dismissImageHover();
8552
+ return;
8553
+ }
8554
+ const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
8555
+ if (isPointOverNavigation(x, y)) {
8556
+ dismissImageHover();
8557
+ probeNavigationHoverAt(x, y);
7499
8558
  return;
7500
8559
  }
8560
+ hoveredNavContainerRef.current = null;
8561
+ setHoveredNavContainerRect(null);
7501
8562
  const toggleEl = document.querySelector("[data-ohw-state-toggle]");
7502
8563
  if (toggleEl) {
7503
- const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
7504
8564
  const tr = toggleEl.getBoundingClientRect();
7505
8565
  if (x >= tr.left && x <= tr.right && y >= tr.top && y <= tr.bottom) {
7506
- if (hoveredImageRef.current) {
7507
- hoveredImageRef.current = null;
7508
- resumeAnimTracks();
7509
- clearImageHover();
7510
- }
8566
+ dismissImageHover();
7511
8567
  return;
7512
8568
  }
7513
8569
  }
@@ -7520,15 +8576,18 @@ function OhhwellsBridge() {
7520
8576
  resumeAnimTracks();
7521
8577
  clearImageHover();
7522
8578
  }
7523
- return;
8579
+ if (activeElRef.current) {
8580
+ dismissImageHover();
8581
+ return;
8582
+ }
7524
8583
  }
7525
8584
  if (imgEl) {
7526
- const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
8585
+ const { x: x2, y: y2 } = toProbeCoords(clientX, clientY, fromParentViewport);
7527
8586
  const overMediaChrome = Array.from(
7528
8587
  document.querySelectorAll("[data-ohw-media-chrome]")
7529
8588
  ).some((el) => {
7530
8589
  const r2 = el.getBoundingClientRect();
7531
- return x >= r2.left && x <= r2.right && y >= r2.top && y <= r2.bottom;
8590
+ return x2 >= r2.left && x2 <= r2.right && y2 >= r2.top && y2 <= r2.bottom;
7532
8591
  });
7533
8592
  if (overMediaChrome) {
7534
8593
  if (hoveredImageRef.current) {
@@ -7539,7 +8598,7 @@ function OhhwellsBridge() {
7539
8598
  }
7540
8599
  return;
7541
8600
  }
7542
- const topEl = document.elementFromPoint(x, y);
8601
+ const topEl = document.elementFromPoint(x2, y2);
7543
8602
  if (topEl?.closest('[data-ohw-toolbar], [data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
7544
8603
  if (hoveredImageRef.current) {
7545
8604
  hoveredImageRef.current = null;
@@ -7562,7 +8621,7 @@ function OhhwellsBridge() {
7562
8621
  document.querySelectorAll(NON_MEDIA_SELECTOR)
7563
8622
  ).find((el) => {
7564
8623
  const er = el.getBoundingClientRect();
7565
- return x >= er.left && x <= er.right && y >= er.top && y <= er.bottom;
8624
+ return x2 >= er.left && x2 <= er.right && y2 >= er.top && y2 <= er.bottom;
7566
8625
  });
7567
8626
  if (imageUnhoverTimerRef.current) {
7568
8627
  clearTimeout(imageUnhoverTimerRef.current);
@@ -7638,12 +8697,12 @@ function OhhwellsBridge() {
7638
8697
  resumeAnimTracks();
7639
8698
  clearImageHover();
7640
8699
  }
7641
- const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
8700
+ const { x: x2, y: y2 } = toProbeCoords(clientX, clientY, fromParentViewport);
7642
8701
  const textEl = Array.from(
7643
8702
  document.querySelectorAll(NON_MEDIA_SELECTOR)
7644
8703
  ).find((el) => {
7645
8704
  const er = el.getBoundingClientRect();
7646
- return x >= er.left && x <= er.right && y >= er.top && y <= er.bottom;
8705
+ return x2 >= er.left && x2 <= er.right && y2 >= er.top && y2 <= er.bottom;
7647
8706
  });
7648
8707
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
7649
8708
  if (textEl && !textEl.hasAttribute("contenteditable")) {
@@ -7667,6 +8726,14 @@ function OhhwellsBridge() {
7667
8726
  }
7668
8727
  };
7669
8728
  const probeHoverCardsAt = (clientX, clientY, fromParentViewport = false) => {
8729
+ if (linkPopoverOpenRef.current || document.documentElement.hasAttribute("data-ohw-section-picking")) {
8730
+ if (activeStateElRef.current) {
8731
+ activeStateElRef.current.removeAttribute("data-ohw-state-hovered");
8732
+ activeStateElRef.current = null;
8733
+ setToggleState(null);
8734
+ }
8735
+ return;
8736
+ }
7670
8737
  const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
7671
8738
  const toggleEl = document.querySelector("[data-ohw-state-toggle]");
7672
8739
  if (toggleEl) {
@@ -7943,6 +9010,8 @@ function OhhwellsBridge() {
7943
9010
  sectionsLoadedRef.current = true;
7944
9011
  pendingScheduleConfigRequests.current.splice(0).forEach(processConfigRequest);
7945
9012
  }
9013
+ editContentRef.current = { ...editContentRef.current, ...content };
9014
+ reconcileNavbarItemsFromContent(editContentRef.current);
7946
9015
  enforceLinkHrefs();
7947
9016
  postToParentRef.current({ type: "ow:hydrate-done" });
7948
9017
  };
@@ -7950,6 +9019,7 @@ function OhhwellsBridge() {
7950
9019
  const handleDeactivate = (e) => {
7951
9020
  if (e.data?.type !== "ow:deactivate") return;
7952
9021
  if (Date.now() < linkPopoverGraceUntilRef.current) return;
9022
+ if (document.querySelector("[data-ohw-section-picker]")) return;
7953
9023
  if (linkPopoverOpenRef.current) {
7954
9024
  setLinkPopoverRef.current(null);
7955
9025
  return;
@@ -7958,13 +9028,40 @@ function OhhwellsBridge() {
7958
9028
  deactivateRef.current();
7959
9029
  };
7960
9030
  window.addEventListener("message", handleDeactivate);
9031
+ const handleUiEscape = (e) => {
9032
+ if (e.data?.type !== "ui:escape") return;
9033
+ if (linkPopoverOpenRef.current) {
9034
+ setLinkPopoverRef.current(null);
9035
+ }
9036
+ };
9037
+ window.addEventListener("message", handleUiEscape);
7961
9038
  const handleKeyDown = (e) => {
9039
+ if (e.key === "Escape" && document.querySelector("[data-ohw-section-picker]")) return;
9040
+ if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "a" && activeElRef.current) {
9041
+ const navAnchor = getNavigationItemAnchor(activeElRef.current);
9042
+ if (navAnchor) {
9043
+ e.preventDefault();
9044
+ selectAllTextInEditable(activeElRef.current);
9045
+ refreshActiveCommandsRef.current();
9046
+ return;
9047
+ }
9048
+ }
7962
9049
  if (e.key === "Escape" && linkPopoverOpenRef.current) {
7963
9050
  setLinkPopoverRef.current(null);
7964
9051
  return;
7965
9052
  }
7966
9053
  if (e.key === "Escape" && selectedElRef.current && !activeElRef.current) {
7967
- deselectRef.current();
9054
+ if (toolbarVariantRef.current === "select-frame") {
9055
+ deselectRef.current();
9056
+ return;
9057
+ }
9058
+ const parent = getNavigationSelectionParent(selectedElRef.current);
9059
+ if (parent) {
9060
+ e.preventDefault();
9061
+ selectFrameRef.current(parent);
9062
+ } else {
9063
+ deselectRef.current();
9064
+ }
7968
9065
  return;
7969
9066
  }
7970
9067
  if (e.key === "Escape" && activeElRef.current) {
@@ -8022,6 +9119,9 @@ function OhhwellsBridge() {
8022
9119
  if (hoveredItemElRef.current) {
8023
9120
  setHoveredItemRect(hoveredItemElRef.current.getBoundingClientRect());
8024
9121
  }
9122
+ if (hoveredNavContainerRef.current) {
9123
+ setHoveredNavContainerRect(hoveredNavContainerRef.current.getBoundingClientRect());
9124
+ }
8025
9125
  if (siblingHintElRef.current) {
8026
9126
  setSiblingHintRect(siblingHintElRef.current.getBoundingClientRect());
8027
9127
  }
@@ -8218,9 +9318,45 @@ function OhhwellsBridge() {
8218
9318
  return clientX >= r2.left && clientX <= r2.right && clientY >= r2.top && clientY <= r2.bottom;
8219
9319
  });
8220
9320
  if (textEditable) {
8221
- activateRef.current(textEditable);
9321
+ const hrefCtx = getHrefKeyFromElement(textEditable);
9322
+ const navAnchor = hrefCtx ? getNavigationItemAnchor(hrefCtx.anchor) : null;
9323
+ if (navAnchor) {
9324
+ if (selectedElRef.current === navAnchor) {
9325
+ activateRef.current(textEditable, { caretX: clientX, caretY: clientY });
9326
+ } else {
9327
+ selectRef.current(navAnchor);
9328
+ }
9329
+ return;
9330
+ }
9331
+ activateRef.current(textEditable, { caretX: clientX, caretY: clientY });
8222
9332
  return;
8223
9333
  }
9334
+ const navContainer = document.querySelector("[data-ohw-nav-container]");
9335
+ if (navContainer) {
9336
+ const r2 = navContainer.getBoundingClientRect();
9337
+ const pad = 8;
9338
+ const over = clientX >= r2.left - pad && clientX <= r2.right + pad && clientY >= r2.top - pad && clientY <= r2.bottom + pad;
9339
+ if (over && !isPointOverNavItem(navContainer, clientX, clientY)) {
9340
+ selectFrameRef.current(navContainer);
9341
+ return;
9342
+ }
9343
+ }
9344
+ const navRoot = document.querySelector("[data-ohw-nav-root]");
9345
+ if (navRoot && navContainer) {
9346
+ const rr = navRoot.getBoundingClientRect();
9347
+ if (clientX >= rr.left && clientX <= rr.right && clientY >= rr.top && clientY <= rr.bottom && !isPointOverNavItem(navContainer, clientX, clientY)) {
9348
+ const book = navRoot.querySelector('[data-ohw-role="navbar-button"]');
9349
+ if (book) {
9350
+ const br = book.getBoundingClientRect();
9351
+ if (clientX >= br.left && clientX <= br.right && clientY >= br.top && clientY <= br.bottom) {
9352
+ deactivateRef.current();
9353
+ return;
9354
+ }
9355
+ }
9356
+ selectFrameRef.current(navContainer);
9357
+ return;
9358
+ }
9359
+ }
8224
9360
  deactivateRef.current();
8225
9361
  };
8226
9362
  window.addEventListener("message", handleSave);
@@ -8242,6 +9378,7 @@ function OhhwellsBridge() {
8242
9378
  };
8243
9379
  window.addEventListener("resize", handleViewportResize, { passive: true });
8244
9380
  document.addEventListener("click", handleClick, true);
9381
+ document.addEventListener("dblclick", handleDblClick, true);
8245
9382
  document.addEventListener("paste", handlePaste, true);
8246
9383
  document.addEventListener("input", handleInput, true);
8247
9384
  document.addEventListener("mouseover", handleMouseOver, true);
@@ -8256,6 +9393,7 @@ function OhhwellsBridge() {
8256
9393
  window.addEventListener("scroll", handleScroll, true);
8257
9394
  return () => {
8258
9395
  document.removeEventListener("click", handleClick, true);
9396
+ document.removeEventListener("dblclick", handleDblClick, true);
8259
9397
  document.removeEventListener("paste", handlePaste, true);
8260
9398
  document.removeEventListener("input", handleInput, true);
8261
9399
  document.removeEventListener("mouseover", handleMouseOver, true);
@@ -8283,13 +9421,14 @@ function OhhwellsBridge() {
8283
9421
  window.removeEventListener("message", handleClickAt);
8284
9422
  window.removeEventListener("message", handleHydrate);
8285
9423
  window.removeEventListener("message", handleDeactivate);
9424
+ window.removeEventListener("message", handleUiEscape);
8286
9425
  autoSaveTimers.current.forEach(clearTimeout);
8287
9426
  autoSaveTimers.current.clear();
8288
9427
  if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
8289
9428
  if (imageShowTimerRef.current) clearTimeout(imageShowTimerRef.current);
8290
9429
  };
8291
9430
  }, [isEditMode, refreshStateRules]);
8292
- (0, import_react6.useEffect)(() => {
9431
+ (0, import_react8.useEffect)(() => {
8293
9432
  const handler = (e) => {
8294
9433
  if (e.data?.type !== "ow:request-schedule-config") return;
8295
9434
  const insertAfterVal = e.data.insertAfter;
@@ -8305,7 +9444,7 @@ function OhhwellsBridge() {
8305
9444
  window.addEventListener("message", handler);
8306
9445
  return () => window.removeEventListener("message", handler);
8307
9446
  }, [processConfigRequest]);
8308
- (0, import_react6.useEffect)(() => {
9447
+ (0, import_react8.useEffect)(() => {
8309
9448
  if (!isEditMode) return;
8310
9449
  document.querySelectorAll("[data-ohw-active-state]").forEach((el) => {
8311
9450
  el.removeAttribute("data-ohw-active-state");
@@ -8326,27 +9465,27 @@ function OhhwellsBridge() {
8326
9465
  const next = { ...prev, [pathKey]: sections };
8327
9466
  return shouldUseDevFixtures() ? { ...DEV_SECTIONS_BY_PATH, ...next } : next;
8328
9467
  });
8329
- postToParent({ type: "ow:ready", version: "1", path: pathname, nodes: collectEditableNodes(), sections });
8330
- postToParent({ type: "ow:request-site-pages" });
9468
+ postToParent2({ type: "ow:ready", version: "1", path: pathname, nodes: collectEditableNodes(), sections });
9469
+ postToParent2({ type: "ow:request-site-pages" });
8331
9470
  }, 150);
8332
9471
  return () => {
8333
9472
  cancelAnimationFrame(raf);
8334
9473
  clearTimeout(timer);
8335
9474
  };
8336
- }, [pathname, isEditMode, refreshStateRules, postToParent]);
8337
- (0, import_react6.useEffect)(() => {
9475
+ }, [pathname, isEditMode, refreshStateRules, postToParent2]);
9476
+ (0, import_react8.useEffect)(() => {
8338
9477
  scrollToHashSectionWhenReady();
8339
9478
  const onHashChange = () => scrollToHashSectionWhenReady();
8340
9479
  window.addEventListener("hashchange", onHashChange);
8341
9480
  return () => window.removeEventListener("hashchange", onHashChange);
8342
9481
  }, [pathname]);
8343
- const handleCommand = (0, import_react6.useCallback)((cmd) => {
9482
+ const handleCommand = (0, import_react8.useCallback)((cmd) => {
8344
9483
  document.execCommand(cmd, false);
8345
9484
  activeElRef.current?.focus();
8346
9485
  if (activeElRef.current) setToolbarRect(getEditMeasureEl(activeElRef.current).getBoundingClientRect());
8347
9486
  refreshActiveCommandsRef.current();
8348
9487
  }, []);
8349
- const handleStateChange = (0, import_react6.useCallback)((state) => {
9488
+ const handleStateChange = (0, import_react8.useCallback)((state) => {
8350
9489
  if (!activeStateElRef.current) return;
8351
9490
  const el = activeStateElRef.current;
8352
9491
  if (state === "Default") {
@@ -8359,18 +9498,22 @@ function OhhwellsBridge() {
8359
9498
  }
8360
9499
  setToggleState((prev) => prev ? { ...prev, activeState: state } : null);
8361
9500
  }, [deactivate]);
8362
- const closeLinkPopover = (0, import_react6.useCallback)(() => setLinkPopover(null), []);
8363
- const openLinkPopoverForActive = (0, import_react6.useCallback)(() => {
9501
+ const closeLinkPopover = (0, import_react8.useCallback)(() => {
9502
+ addNavAfterAnchorRef.current = null;
9503
+ setLinkPopover(null);
9504
+ }, []);
9505
+ const openLinkPopoverForActive = (0, import_react8.useCallback)(() => {
8364
9506
  const hrefCtx = getHrefKeyFromElement(activeElRef.current);
8365
9507
  if (!hrefCtx) return;
8366
9508
  bumpLinkPopoverGrace();
8367
9509
  setLinkPopover({
8368
9510
  key: hrefCtx.key,
8369
- target: getLinkHref(hrefCtx.anchor)
9511
+ mode: "edit",
9512
+ target: getLinkHref2(hrefCtx.anchor)
8370
9513
  });
8371
9514
  deactivate();
8372
9515
  }, [deactivate]);
8373
- const openLinkPopoverForSelected = (0, import_react6.useCallback)(() => {
9516
+ const openLinkPopoverForSelected = (0, import_react8.useCallback)(() => {
8374
9517
  const anchor = selectedElRef.current;
8375
9518
  if (!anchor) return;
8376
9519
  const key = anchor.getAttribute("data-ohw-href-key");
@@ -8378,30 +9521,77 @@ function OhhwellsBridge() {
8378
9521
  bumpLinkPopoverGrace();
8379
9522
  setLinkPopover({
8380
9523
  key,
8381
- target: getLinkHref(anchor)
9524
+ mode: "edit",
9525
+ target: getLinkHref2(anchor)
8382
9526
  });
8383
9527
  deselect();
8384
9528
  }, [deselect]);
8385
- const handleLinkPopoverSubmit = (0, import_react6.useCallback)(
9529
+ const handleLinkPopoverSubmit = (0, import_react8.useCallback)(
8386
9530
  (target) => {
8387
- if (!linkPopover) return;
8388
- const { key } = linkPopover;
9531
+ const session = linkPopoverSessionRef.current;
9532
+ if (!session) return;
9533
+ if (session.intent === "add-nav") {
9534
+ const trimmed = target.trim();
9535
+ let href = trimmed;
9536
+ let label = "Untitled";
9537
+ if (trimmed) {
9538
+ const { pageRoute, sectionId } = parseTarget(trimmed);
9539
+ const page = resolvePage(pageRoute, sitePages);
9540
+ const pageSections = getSectionsForPath(sectionsByPath, pageRoute);
9541
+ const section = sectionId ? pageSections.find((s) => s.id === sectionId) : void 0;
9542
+ label = section?.label ?? page.title;
9543
+ href = trimmed;
9544
+ }
9545
+ const afterAnchor = addNavAfterAnchorRef.current;
9546
+ const { anchor, hrefKey, labelKey, index, order } = insertNavbarItem(href, label, afterAnchor);
9547
+ applyLinkByKey(hrefKey, href);
9548
+ document.querySelectorAll(`[data-ohw-key="${labelKey}"]`).forEach((el) => {
9549
+ el.textContent = label;
9550
+ });
9551
+ const navCount = String(index + 1);
9552
+ const orderJson = JSON.stringify(order);
9553
+ editContentRef.current = {
9554
+ ...editContentRef.current,
9555
+ [hrefKey]: href,
9556
+ [labelKey]: label,
9557
+ [NAV_COUNT_KEY]: navCount,
9558
+ [NAV_ORDER_KEY]: orderJson
9559
+ };
9560
+ postToParent2({
9561
+ type: "ow:change",
9562
+ nodes: [
9563
+ { key: hrefKey, text: href },
9564
+ { key: labelKey, text: label },
9565
+ { key: NAV_COUNT_KEY, text: navCount },
9566
+ { key: NAV_ORDER_KEY, text: orderJson }
9567
+ ]
9568
+ });
9569
+ postToParent2({ type: "ow:toast", title: "Link added", toastType: "success" });
9570
+ addNavAfterAnchorRef.current = null;
9571
+ setLinkPopover(null);
9572
+ enforceLinkHrefs();
9573
+ requestAnimationFrame(() => {
9574
+ selectRef.current(anchor);
9575
+ });
9576
+ return;
9577
+ }
9578
+ const { key } = session;
8389
9579
  applyLinkByKey(key, target);
8390
- postToParent({ type: "ow:change", nodes: [{ key, text: target }] });
9580
+ postToParent2({ type: "ow:change", nodes: [{ key, text: target }] });
8391
9581
  setLinkPopover(null);
8392
9582
  },
8393
- [linkPopover, postToParent]
9583
+ [postToParent2, sitePages, sectionsByPath]
8394
9584
  );
8395
9585
  const showEditLink = toolbarShowEditLink;
8396
9586
  const currentSections = sectionsByPath[pathname] ?? [];
8397
9587
  linkPopoverOpenRef.current = linkPopover !== null;
8398
- const handleMediaReplace = (0, import_react6.useCallback)(
9588
+ const handleMediaReplace = (0, import_react8.useCallback)(
8399
9589
  (key) => {
8400
- postToParent({ type: "ow:image-pick", key, elementType: mediaHover?.elementType ?? "image" });
9590
+ postToParent2({ type: "ow:image-pick", key, elementType: mediaHover?.elementType ?? "image" });
8401
9591
  },
8402
- [postToParent, mediaHover?.elementType]
9592
+ [postToParent2, mediaHover?.elementType]
8403
9593
  );
8404
- const handleMediaFadeOutComplete = (0, import_react6.useCallback)((key) => {
9594
+ const handleMediaFadeOutComplete = (0, import_react8.useCallback)((key) => {
8405
9595
  setUploadingRects((prev) => {
8406
9596
  if (!(key in prev)) return prev;
8407
9597
  const next = { ...prev };
@@ -8409,7 +9599,7 @@ function OhhwellsBridge() {
8409
9599
  return next;
8410
9600
  });
8411
9601
  }, []);
8412
- const handleVideoSettingsChange = (0, import_react6.useCallback)(
9602
+ const handleVideoSettingsChange = (0, import_react8.useCallback)(
8413
9603
  (key, settings) => {
8414
9604
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
8415
9605
  const video = getVideoEl(el);
@@ -8417,7 +9607,7 @@ function OhhwellsBridge() {
8417
9607
  if (typeof settings.autoplay === "boolean") video.autoplay = settings.autoplay;
8418
9608
  if (typeof settings.muted === "boolean") video.muted = settings.muted;
8419
9609
  syncVideoPlayback(video);
8420
- postToParent({
9610
+ postToParent2({
8421
9611
  type: "ow:change",
8422
9612
  nodes: [
8423
9613
  { key: `${key}${VIDEO_AUTOPLAY_SUFFIX}`, text: String(video.autoplay) },
@@ -8429,12 +9619,12 @@ function OhhwellsBridge() {
8429
9619
  );
8430
9620
  });
8431
9621
  },
8432
- [postToParent]
9622
+ [postToParent2]
8433
9623
  );
8434
- return bridgeRoot ? (0, import_react_dom2.createPortal)(
8435
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
8436
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
8437
- Object.entries(uploadingRects).map(([key, { rect, fadingOut }]) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
9624
+ return bridgeRoot ? (0, import_react_dom3.createPortal)(
9625
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
9626
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
9627
+ Object.entries(uploadingRects).map(([key, { rect, fadingOut }]) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8438
9628
  MediaOverlay,
8439
9629
  {
8440
9630
  hover: { key, rect, elementType: "image", isDragOver: false, hasTextOverlap: false },
@@ -8445,7 +9635,7 @@ function OhhwellsBridge() {
8445
9635
  },
8446
9636
  `uploading-${key}`
8447
9637
  )),
8448
- mediaHover && !(mediaHover.key in uploadingRects) && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
9638
+ mediaHover && !(mediaHover.key in uploadingRects) && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8449
9639
  MediaOverlay,
8450
9640
  {
8451
9641
  hover: mediaHover,
@@ -8454,31 +9644,40 @@ function OhhwellsBridge() {
8454
9644
  onVideoSettingsChange: handleVideoSettingsChange
8455
9645
  }
8456
9646
  ),
8457
- siblingHintRect && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
8458
- hoveredItemRect && !siblingHintRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
8459
- toolbarRect && toolbarVariant === "link-action" && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
9647
+ siblingHintRect && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
9648
+ hoveredNavContainerRect && toolbarVariant !== "select-frame" && !linkPopover && !isItemDragging && !siblingHintRect && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
9649
+ toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
9650
+ NavbarContainerChrome,
9651
+ {
9652
+ rect: toolbarRect,
9653
+ onAdd: handleAddTopLevelNavItem
9654
+ }
9655
+ ),
9656
+ hoveredItemRect && !hoveredNavContainerRect && !siblingHintRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
9657
+ toolbarRect && (toolbarVariant === "link-action" || toolbarVariant === "select-frame") && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8460
9658
  ItemInteractionLayer,
8461
9659
  {
8462
9660
  rect: toolbarRect,
8463
9661
  elRef: glowElRef,
8464
9662
  state: isItemDragging ? "dragging" : resolveItemInteractionState(toolbarRect, parentScrollRef.current),
8465
- showHandle: Boolean(reorderHrefKey),
9663
+ showHandle: toolbarVariant === "link-action" && Boolean(reorderHrefKey),
8466
9664
  dragDisabled: reorderDragDisabled,
8467
9665
  dragHandleLabel: reorderHrefKey ? `Reorder ${reorderHrefKey}` : "Reorder item",
8468
9666
  onDragHandleDragStart: handleItemDragStart,
8469
9667
  onDragHandleDragEnd: handleItemDragEnd,
8470
- toolbar: isItemDragging ? void 0 : /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
9668
+ toolbar: toolbarVariant === "link-action" && !isItemDragging ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8471
9669
  ItemActionToolbar,
8472
9670
  {
8473
9671
  onEditLink: openLinkPopoverForSelected,
8474
- onAddItem: handleAddNavigationItem,
8475
- addItemDisabled: false
9672
+ addItemDisabled: true,
9673
+ editLinkDisabled: false,
9674
+ moreDisabled: true
8476
9675
  }
8477
- )
9676
+ ) : void 0
8478
9677
  }
8479
9678
  ),
8480
- toolbarRect && toolbarVariant === "rich-text" && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
8481
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
9679
+ toolbarRect && toolbarVariant === "rich-text" && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
9680
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8482
9681
  EditGlowChrome,
8483
9682
  {
8484
9683
  rect: toolbarRect,
@@ -8487,7 +9686,7 @@ function OhhwellsBridge() {
8487
9686
  dragDisabled: reorderDragDisabled
8488
9687
  }
8489
9688
  ),
8490
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
9689
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8491
9690
  FloatingToolbar,
8492
9691
  {
8493
9692
  rect: toolbarRect,
@@ -8500,7 +9699,7 @@ function OhhwellsBridge() {
8500
9699
  }
8501
9700
  )
8502
9701
  ] }),
8503
- maxBadge && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
9702
+ maxBadge && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
8504
9703
  "div",
8505
9704
  {
8506
9705
  "data-ohw-max-badge": "",
@@ -8526,7 +9725,7 @@ function OhhwellsBridge() {
8526
9725
  ]
8527
9726
  }
8528
9727
  ),
8529
- toggleState && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
9728
+ toggleState && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8530
9729
  StateToggle,
8531
9730
  {
8532
9731
  rect: toggleState.rect,
@@ -8535,15 +9734,15 @@ function OhhwellsBridge() {
8535
9734
  onStateChange: handleStateChange
8536
9735
  }
8537
9736
  ),
8538
- sectionGap && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
9737
+ sectionGap && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
8539
9738
  "div",
8540
9739
  {
8541
9740
  "data-ohw-section-insert-line": "",
8542
9741
  className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
8543
9742
  style: { top: sectionGap.y, transform: "translateY(-50%)" },
8544
9743
  children: [
8545
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
8546
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
9744
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
9745
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8547
9746
  Badge,
8548
9747
  {
8549
9748
  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",
@@ -8556,25 +9755,26 @@ function OhhwellsBridge() {
8556
9755
  children: "Add Section"
8557
9756
  }
8558
9757
  ),
8559
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
9758
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
8560
9759
  ]
8561
9760
  }
8562
9761
  ),
8563
- linkPopover && dialogPortalContainer ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
9762
+ linkPopover && dialogPortalContainer ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8564
9763
  LinkPopover,
8565
9764
  {
8566
9765
  panelRef: linkPopoverPanelRef,
8567
9766
  portalContainer: dialogPortalContainer,
8568
9767
  open: true,
8569
- mode: "edit",
9768
+ mode: linkPopover.mode ?? "edit",
8570
9769
  pages: sitePages,
8571
9770
  sections: currentSections,
8572
9771
  sectionsByPath,
8573
9772
  initialTarget: linkPopover.target,
9773
+ existingTargets: linkPopover.intent === "add-nav" ? getNavbarExistingTargets() : [],
8574
9774
  onClose: closeLinkPopover,
8575
9775
  onSubmit: handleLinkPopoverSubmit
8576
9776
  },
8577
- `${linkPopover.key}-${pathname}`
9777
+ linkPopover.key
8578
9778
  ) : null
8579
9779
  ] }),
8580
9780
  bridgeRoot