@ohhwells/bridge 0.1.43 → 0.1.45

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
@@ -62,7 +62,7 @@ __export(index_exports, {
62
62
  module.exports = __toCommonJS(index_exports);
63
63
 
64
64
  // src/OhhwellsBridge.tsx
65
- var import_react8 = __toESM(require("react"), 1);
65
+ var import_react9 = __toESM(require("react"), 1);
66
66
  var import_client = require("react-dom/client");
67
67
  var import_react_dom2 = require("react-dom");
68
68
 
@@ -4303,7 +4303,8 @@ var CustomToolbar = React4.forwardRef(({ className, onMouseDown, ...props }, ref
4303
4303
  ref,
4304
4304
  "data-ohw-toolbar": "",
4305
4305
  className: cn(
4306
- "inline-flex items-center gap-1 rounded-lg border border-border bg-background p-0.5 font-sans whitespace-nowrap shadow-[0px_2px_4px_-2px_rgba(0,0,0,0.1),0px_4px_6px_-1px_rgba(0,0,0,0.1)]",
4306
+ // Figma: bg background, radius 8, gap-1, p-0.5, shadow-md no border
4307
+ "inline-flex h-8 items-center gap-1 rounded-[var(--radius,0.5rem)] bg-background p-0.5 font-sans whitespace-nowrap shadow-md",
4307
4308
  className
4308
4309
  ),
4309
4310
  onMouseDown: (e) => {
@@ -4331,8 +4332,8 @@ var CustomToolbarButton = React4.forwardRef(
4331
4332
  ref,
4332
4333
  type,
4333
4334
  className: cn(
4334
- "inline-flex shrink-0 items-center justify-center rounded p-1.5 transition-colors",
4335
- active ? "bg-primary text-primary-foreground" : "bg-transparent text-foreground hover:bg-muted disabled:cursor-not-allowed disabled:text-muted-foreground disabled:opacity-60",
4335
+ "inline-flex size-7 shrink-0 items-center justify-center rounded-[calc(var(--radius,0.5rem)-2px)] text-foreground transition-colors",
4336
+ active ? "bg-primary text-primary-foreground" : "bg-transparent hover:bg-muted disabled:cursor-not-allowed disabled:text-muted-foreground disabled:opacity-60",
4336
4337
  className
4337
4338
  ),
4338
4339
  ...props
@@ -4367,9 +4368,10 @@ var arrowClassBySide = {
4367
4368
  };
4368
4369
  function TooltipContent({
4369
4370
  className,
4370
- sideOffset = 6,
4371
+ sideOffset = 9,
4371
4372
  side = "bottom",
4372
4373
  children,
4374
+ hideArrow,
4373
4375
  ...props
4374
4376
  }) {
4375
4377
  const resolvedSide = side ?? "bottom";
@@ -4380,12 +4382,13 @@ function TooltipContent({
4380
4382
  side,
4381
4383
  sideOffset,
4382
4384
  className: cn(
4383
- "relative z-[2147483647] w-fit max-w-xs rounded-md bg-foreground px-3 py-1.5 text-xs text-background shadow-md",
4384
- 'before:pointer-events-none before:absolute before:size-2 before:rotate-45 before:bg-foreground before:content-[""]',
4385
- arrowClassBySide[resolvedSide],
4386
- "animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
4385
+ "relative z-[2147483647] inline-flex w-fit max-w-xs items-center rounded-md bg-foreground px-3 py-1.5 text-xs text-background",
4386
+ "origin-[var(--radix-tooltip-content-transform-origin)] animate-in fade-in-0 zoom-in-95",
4387
+ "data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
4387
4388
  "data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2",
4388
4389
  "data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2",
4390
+ !hideArrow && 'before:pointer-events-none before:absolute before:size-2 before:rotate-45 before:bg-foreground before:content-[""]',
4391
+ !hideArrow && arrowClassBySide[resolvedSide],
4389
4392
  className
4390
4393
  ),
4391
4394
  ...props,
@@ -4406,7 +4409,7 @@ function ToolbarActionTooltip({
4406
4409
  const button = /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CustomToolbarButton, { type: "button", "aria-label": label, disabled, ...buttonProps, children });
4407
4410
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Tooltip, { children: [
4408
4411
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(TooltipTrigger, { asChild: true, children: disabled ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "inline-flex", children: button }) : button }),
4409
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(TooltipContent, { side, children: label })
4412
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(TooltipContent, { side, sideOffset: 9, children: label })
4410
4413
  ] });
4411
4414
  }
4412
4415
  function ItemActionToolbar({
@@ -4416,6 +4419,8 @@ function ItemActionToolbar({
4416
4419
  editLinkDisabled = false,
4417
4420
  addItemDisabled = true,
4418
4421
  moreDisabled = true,
4422
+ showAddItem = true,
4423
+ showMore = true,
4419
4424
  tooltipSide = "bottom"
4420
4425
  }) {
4421
4426
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(CustomToolbar, { children: [
@@ -4440,8 +4445,7 @@ function ItemActionToolbar({
4440
4445
  children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react2.Link, { className: "size-4 shrink-0", "aria-hidden": true })
4441
4446
  }
4442
4447
  ),
4443
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CustomToolbarDivider, {}),
4444
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4448
+ showAddItem ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4445
4449
  ToolbarActionTooltip,
4446
4450
  {
4447
4451
  label: "Add item",
@@ -4457,9 +4461,8 @@ function ItemActionToolbar({
4457
4461
  },
4458
4462
  children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react2.Plus, { className: "size-4 shrink-0", "aria-hidden": true })
4459
4463
  }
4460
- ),
4461
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CustomToolbarDivider, {}),
4462
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4464
+ ) : null,
4465
+ showMore ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4463
4466
  ToolbarActionTooltip,
4464
4467
  {
4465
4468
  label: "More",
@@ -4475,15 +4478,32 @@ function ItemActionToolbar({
4475
4478
  },
4476
4479
  children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react2.MoreHorizontal, { className: "size-4 shrink-0", "aria-hidden": true })
4477
4480
  }
4478
- )
4481
+ ) : null
4479
4482
  ] }) });
4480
4483
  }
4481
4484
 
4482
4485
  // src/ui/item-interaction-layer.tsx
4486
+ var React5 = __toESM(require("react"), 1);
4483
4487
  var import_jsx_runtime9 = require("react/jsx-runtime");
4484
4488
  var PRIMARY = "var(--ohw-primary, #0885FE)";
4485
- var FOCUS_RING = "0 0 0 4px rgba(8, 133, 254, 0.12)";
4489
+ var FOCUS_RING = `0 0 0 4px color-mix(in srgb, ${PRIMARY} 12%, transparent)`;
4486
4490
  var DRAG_SHADOW = "0px 4px 6px -1px rgba(0, 0, 0, 0.1), 0px 2px 4px -2px rgba(0, 0, 0, 0.1)";
4491
+ var TOOLBAR_EDGE_MARGIN = 4;
4492
+ var SELECTION_CHROME_GAP = 4;
4493
+ var TOOLBAR_STROKE_GAP = 4;
4494
+ function getChromeZIndex(state) {
4495
+ switch (state) {
4496
+ case "active-top":
4497
+ case "active-bottom":
4498
+ case "dragging":
4499
+ return 2147483644;
4500
+ case "hover":
4501
+ case "sibling-hint":
4502
+ return 2147483643;
4503
+ default:
4504
+ return 2147483642;
4505
+ }
4506
+ }
4487
4507
  function getChromeStyle(state) {
4488
4508
  switch (state) {
4489
4509
  case "hover":
@@ -4505,12 +4525,65 @@ function getChromeStyle(state) {
4505
4525
  case "dragging":
4506
4526
  return {
4507
4527
  border: `2px solid ${PRIMARY}`,
4508
- boxShadow: DRAG_SHADOW
4528
+ boxShadow: `${FOCUS_RING}, ${DRAG_SHADOW}`
4509
4529
  };
4510
4530
  default:
4511
4531
  return {};
4512
4532
  }
4513
4533
  }
4534
+ function ClampedToolbarSlot({
4535
+ placement,
4536
+ children
4537
+ }) {
4538
+ const slotRef = React5.useRef(null);
4539
+ React5.useLayoutEffect(() => {
4540
+ const slot = slotRef.current;
4541
+ if (!slot) return;
4542
+ const clamp = () => {
4543
+ const toolbar = slot.firstElementChild;
4544
+ if (!toolbar) return;
4545
+ const w = toolbar.offsetWidth;
4546
+ if (w <= 0) return;
4547
+ const parent = slot.offsetParent;
4548
+ if (!parent) return;
4549
+ const parentRect = parent.getBoundingClientRect();
4550
+ const centerX = parentRect.left + parentRect.width / 2;
4551
+ const half = w / 2;
4552
+ const clampedCenter = Math.max(
4553
+ TOOLBAR_EDGE_MARGIN + half,
4554
+ Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN - half)
4555
+ );
4556
+ const offsetX = clampedCenter - centerX;
4557
+ slot.style.transform = `translateX(calc(-50% + ${offsetX}px))`;
4558
+ };
4559
+ clamp();
4560
+ const ro = new ResizeObserver(clamp);
4561
+ ro.observe(slot);
4562
+ if (slot.firstElementChild) ro.observe(slot.firstElementChild);
4563
+ window.addEventListener("resize", clamp);
4564
+ return () => {
4565
+ ro.disconnect();
4566
+ window.removeEventListener("resize", clamp);
4567
+ };
4568
+ }, [placement, children]);
4569
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4570
+ "div",
4571
+ {
4572
+ ref: slotRef,
4573
+ className: cn(
4574
+ "pointer-events-auto absolute left-1/2",
4575
+ placement === "top" ? "bottom-full" : "top-full"
4576
+ ),
4577
+ style: {
4578
+ marginBottom: placement === "top" ? TOOLBAR_STROKE_GAP : void 0,
4579
+ marginTop: placement === "bottom" ? TOOLBAR_STROKE_GAP : void 0,
4580
+ transform: "translateX(-50%)"
4581
+ },
4582
+ "data-ohw-item-toolbar-anchor": placement,
4583
+ children
4584
+ }
4585
+ );
4586
+ }
4514
4587
  function ItemInteractionLayer({
4515
4588
  rect,
4516
4589
  state,
@@ -4521,14 +4594,18 @@ function ItemInteractionLayer({
4521
4594
  dragHandleLabel = "Reorder item",
4522
4595
  onDragHandleDragStart,
4523
4596
  onDragHandleDragEnd,
4524
- chromeGap = 6,
4597
+ onItemPointerDown,
4598
+ onItemClick,
4599
+ itemDragSurface = true,
4600
+ chromeGap = SELECTION_CHROME_GAP,
4525
4601
  className
4526
4602
  }) {
4527
4603
  if (state === "default") return null;
4528
4604
  const isActive = state === "active-top" || state === "active-bottom";
4529
4605
  const isDragging = state === "dragging";
4530
4606
  const showToolbar = isActive && toolbar;
4531
- const showDragHandle = isActive && showHandle && !isDragging;
4607
+ const showDragHandle = (isActive || isDragging) && showHandle;
4608
+ const itemDragEnabled = itemDragSurface && isActive && showHandle && !isDragging && !dragDisabled;
4532
4609
  return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
4533
4610
  "div",
4534
4611
  {
@@ -4542,7 +4619,7 @@ function ItemInteractionLayer({
4542
4619
  left: rect.left - chromeGap,
4543
4620
  width: rect.width + chromeGap * 2,
4544
4621
  height: rect.height + chromeGap * 2,
4545
- zIndex: 2147483646
4622
+ zIndex: getChromeZIndex(state)
4546
4623
  },
4547
4624
  children: [
4548
4625
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
@@ -4553,11 +4630,29 @@ function ItemInteractionLayer({
4553
4630
  style: getChromeStyle(state)
4554
4631
  }
4555
4632
  ),
4633
+ itemDragEnabled && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4634
+ "div",
4635
+ {
4636
+ "data-ohw-item-drag-surface": "",
4637
+ className: "pointer-events-auto absolute inset-0 cursor-text rounded-lg",
4638
+ onPointerDown: (e) => {
4639
+ if (e.button !== 0) return;
4640
+ onItemPointerDown?.(e);
4641
+ },
4642
+ onClick: (e) => {
4643
+ e.preventDefault();
4644
+ e.stopPropagation();
4645
+ onItemClick?.(e.clientX, e.clientY);
4646
+ }
4647
+ }
4648
+ ),
4556
4649
  showDragHandle && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4557
4650
  "div",
4558
4651
  {
4559
4652
  "data-ohw-drag-handle-container": "",
4560
- className: "pointer-events-auto absolute left-0 top-1/2 -translate-x-[calc(100%+7px)] -translate-y-1/2",
4653
+ className: "pointer-events-auto absolute left-0 top-1/2 z-10 -translate-x-[calc(100%+7px)] -translate-y-1/2",
4654
+ style: isDragging ? { opacity: 0 } : void 0,
4655
+ "aria-hidden": isDragging || void 0,
4561
4656
  children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4562
4657
  DragHandle,
4563
4658
  {
@@ -4569,7 +4664,17 @@ function ItemInteractionLayer({
4569
4664
  e.preventDefault();
4570
4665
  return;
4571
4666
  }
4572
- e.dataTransfer?.setData("text/plain", dragHandleLabel);
4667
+ const blank = document.createElement("canvas");
4668
+ blank.width = 1;
4669
+ blank.height = 1;
4670
+ blank.style.cssText = "position:fixed;left:-9999px;top:-9999px;width:1px;height:1px;pointer-events:none";
4671
+ document.body.appendChild(blank);
4672
+ try {
4673
+ e.dataTransfer.setDragImage(blank, 0, 0);
4674
+ } finally {
4675
+ requestAnimationFrame(() => blank.remove());
4676
+ }
4677
+ e.dataTransfer.setData("text/plain", dragHandleLabel);
4573
4678
  e.dataTransfer.effectAllowed = "move";
4574
4679
  onDragHandleDragStart?.(e);
4575
4680
  },
@@ -4578,33 +4683,19 @@ function ItemInteractionLayer({
4578
4683
  )
4579
4684
  }
4580
4685
  ),
4581
- showToolbar && state === "active-top" && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4582
- "div",
4583
- {
4584
- className: "pointer-events-auto absolute bottom-full left-1/2 mb-2 -translate-x-1/2",
4585
- "data-ohw-item-toolbar-anchor": "top",
4586
- children: toolbar
4587
- }
4588
- ),
4589
- showToolbar && state === "active-bottom" && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4590
- "div",
4591
- {
4592
- className: "pointer-events-auto absolute left-1/2 top-full mt-2 -translate-x-1/2",
4593
- "data-ohw-item-toolbar-anchor": "bottom",
4594
- children: toolbar
4595
- }
4596
- )
4686
+ showToolbar && state === "active-top" && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ClampedToolbarSlot, { placement: "top", children: toolbar }),
4687
+ showToolbar && state === "active-bottom" && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ClampedToolbarSlot, { placement: "bottom", children: toolbar })
4597
4688
  ]
4598
4689
  }
4599
4690
  );
4600
4691
  }
4601
4692
 
4602
4693
  // src/ui/MediaOverlay.tsx
4603
- var React6 = __toESM(require("react"), 1);
4694
+ var React7 = __toESM(require("react"), 1);
4604
4695
  var import_lucide_react3 = require("lucide-react");
4605
4696
 
4606
4697
  // src/ui/button.tsx
4607
- var React5 = __toESM(require("react"), 1);
4698
+ var React6 = __toESM(require("react"), 1);
4608
4699
  var import_radix_ui4 = require("radix-ui");
4609
4700
  var import_jsx_runtime10 = require("react/jsx-runtime");
4610
4701
  var buttonVariants = cva(
@@ -4627,7 +4718,7 @@ var buttonVariants = cva(
4627
4718
  }
4628
4719
  }
4629
4720
  );
4630
- var Button = React5.forwardRef(
4721
+ var Button = React6.forwardRef(
4631
4722
  ({ className, variant, size, asChild = false, ...props }, ref) => {
4632
4723
  const Comp = asChild ? import_radix_ui4.Slot.Root : "button";
4633
4724
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
@@ -4679,7 +4770,7 @@ function MediaOverlay({
4679
4770
  onVideoSettingsChange
4680
4771
  }) {
4681
4772
  const { rect } = hover;
4682
- const skeletonRef = React6.useRef(null);
4773
+ const skeletonRef = React7.useRef(null);
4683
4774
  const isVideo = hover.elementType === "video";
4684
4775
  const autoplay = hover.videoAutoplay ?? true;
4685
4776
  const muted = hover.videoMuted ?? true;
@@ -4691,7 +4782,7 @@ function MediaOverlay({
4691
4782
  height: rect.height,
4692
4783
  zIndex: 2147483646
4693
4784
  };
4694
- React6.useEffect(() => {
4785
+ React7.useEffect(() => {
4695
4786
  if (!isUploading || !fadingOut || !skeletonRef.current) return;
4696
4787
  const anim = skeletonRef.current.animate([{ opacity: 1 }, { opacity: 0 }], {
4697
4788
  duration: MEDIA_UPLOAD_FADE_MS,
@@ -5096,7 +5187,7 @@ function scrollToHashSectionWhenReady(behavior = "smooth") {
5096
5187
  var import_react7 = require("react");
5097
5188
 
5098
5189
  // src/ui/dialog.tsx
5099
- var React7 = __toESM(require("react"), 1);
5190
+ var React8 = __toESM(require("react"), 1);
5100
5191
  var import_radix_ui5 = require("radix-ui");
5101
5192
  var import_lucide_react4 = require("lucide-react");
5102
5193
  var import_jsx_runtime12 = require("react/jsx-runtime");
@@ -5124,7 +5215,7 @@ function DialogOverlay({
5124
5215
  }
5125
5216
  );
5126
5217
  }
5127
- var DialogContent = React7.forwardRef(
5218
+ var DialogContent = React8.forwardRef(
5128
5219
  ({ className, children, showCloseButton = true, container, ...props }, ref) => {
5129
5220
  const positionMode = container ? "absolute" : "fixed";
5130
5221
  return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(DialogPortal, { container: container ?? void 0, children: [
@@ -5179,7 +5270,7 @@ function DialogFooter({
5179
5270
  }
5180
5271
  );
5181
5272
  }
5182
- var DialogTitle = React7.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
5273
+ var DialogTitle = React8.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
5183
5274
  import_radix_ui5.Dialog.Title,
5184
5275
  {
5185
5276
  ref,
@@ -5191,7 +5282,7 @@ var DialogTitle = React7.forwardRef(({ className, ...props }, ref) => /* @__PURE
5191
5282
  }
5192
5283
  ));
5193
5284
  DialogTitle.displayName = import_radix_ui5.Dialog.Title.displayName;
5194
- var DialogDescription = React7.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
5285
+ var DialogDescription = React8.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
5195
5286
  import_radix_ui5.Dialog.Description,
5196
5287
  {
5197
5288
  ref,
@@ -5296,9 +5387,9 @@ function SectionTreeItem({
5296
5387
  var import_react4 = require("react");
5297
5388
 
5298
5389
  // src/ui/input.tsx
5299
- var React8 = __toESM(require("react"), 1);
5390
+ var React9 = __toESM(require("react"), 1);
5300
5391
  var import_jsx_runtime15 = require("react/jsx-runtime");
5301
- var Input = React8.forwardRef(
5392
+ var Input = React9.forwardRef(
5302
5393
  ({ className, type, ...props }, ref) => {
5303
5394
  return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
5304
5395
  "input",
@@ -6279,9 +6370,155 @@ function shouldUseDevFixtures() {
6279
6370
  return new URLSearchParams(q).get("ohw-fixtures") === "1";
6280
6371
  }
6281
6372
 
6373
+ // src/lib/nav-tree.ts
6374
+ var MAX_LEVEL = { nav: 1 };
6375
+ var ROOT = null;
6376
+ function cloneForest(nodes) {
6377
+ return nodes.map((n) => ({ id: n.id, children: cloneForest(n.children) }));
6378
+ }
6379
+ function findNode(nodes, id, parentId = ROOT) {
6380
+ for (let i = 0; i < nodes.length; i++) {
6381
+ const node = nodes[i];
6382
+ if (node.id === id) return { node, parentId, index: i, siblings: nodes };
6383
+ const nested = findNode(node.children, id, node.id);
6384
+ if (nested) return nested;
6385
+ }
6386
+ return null;
6387
+ }
6388
+ function getChildrenOf(nodes, parentId) {
6389
+ if (parentId === ROOT) return nodes;
6390
+ const found = findNode(nodes, parentId);
6391
+ return found ? found.node.children : null;
6392
+ }
6393
+ function isDescendant(nodes, ancestorId, maybeDescendantId) {
6394
+ const found = findNode(nodes, ancestorId);
6395
+ if (!found) return false;
6396
+ return findNode(found.node.children, maybeDescendantId) !== null;
6397
+ }
6398
+ function levelOf(nodes, id) {
6399
+ const loc = findNode(nodes, id);
6400
+ if (!loc) return -1;
6401
+ if (loc.parentId === ROOT) return 0;
6402
+ return 1 + Math.max(0, levelOf(nodes, loc.parentId));
6403
+ }
6404
+ function insertedLevel(nodes, parentId) {
6405
+ if (parentId === ROOT) return 0;
6406
+ const parentLevel = levelOf(nodes, parentId);
6407
+ if (parentLevel < 0) return -1;
6408
+ return parentLevel + 1;
6409
+ }
6410
+ function takeNode(nodes, id) {
6411
+ const forest = cloneForest(nodes);
6412
+ const loc = findNode(forest, id);
6413
+ if (!loc) return null;
6414
+ const [taken] = loc.siblings.splice(loc.index, 1);
6415
+ if (!taken) return null;
6416
+ return { forest, taken };
6417
+ }
6418
+ function insertInto(forest, node, parentId, index) {
6419
+ const siblings = getChildrenOf(forest, parentId);
6420
+ if (!siblings) return false;
6421
+ const clamped = Math.max(0, Math.min(index, siblings.length));
6422
+ siblings.splice(clamped, 0, node);
6423
+ return true;
6424
+ }
6425
+ function normalize(nodes) {
6426
+ const walk = (list) => list.map((n) => {
6427
+ const children = walk(n.children);
6428
+ return { id: n.id, children };
6429
+ });
6430
+ return walk(nodes);
6431
+ }
6432
+ function moveNode(model, draggedId, target) {
6433
+ const { parentId, index: targetIndex } = target;
6434
+ if (parentId === draggedId) return null;
6435
+ if (parentId !== ROOT && isDescendant(model, draggedId, parentId)) return null;
6436
+ const nextLevel = insertedLevel(model, parentId);
6437
+ if (nextLevel < 0 || nextLevel > MAX_LEVEL.nav) return null;
6438
+ const draggedLoc = findNode(model, draggedId);
6439
+ if (!draggedLoc) return null;
6440
+ if (parentId !== ROOT && draggedLoc.node.children.length > 0) return null;
6441
+ const taken = takeNode(model, draggedId);
6442
+ if (!taken) return null;
6443
+ let insertIndex = targetIndex;
6444
+ if (draggedLoc.parentId === parentId && draggedLoc.index < targetIndex) {
6445
+ insertIndex -= 1;
6446
+ }
6447
+ if (!insertInto(taken.forest, taken.taken, parentId, insertIndex)) return null;
6448
+ const next = normalize(taken.forest);
6449
+ if (forestsEqual(model, next)) return null;
6450
+ return next;
6451
+ }
6452
+ function forestsEqual(a, b) {
6453
+ if (a.length !== b.length) return false;
6454
+ for (let i = 0; i < a.length; i++) {
6455
+ const left = a[i];
6456
+ const right = b[i];
6457
+ if (left.id !== right.id) return false;
6458
+ if (!forestsEqual(left.children, right.children)) return false;
6459
+ }
6460
+ return true;
6461
+ }
6462
+ function forestFromFlatOrder(order) {
6463
+ return order.map((id) => ({ id, children: [] }));
6464
+ }
6465
+ function serializeNavOrder(nodes) {
6466
+ const hasNesting = nodes.some((n) => n.children.length > 0);
6467
+ if (!hasNesting) {
6468
+ return JSON.stringify(nodes.map((n) => n.id));
6469
+ }
6470
+ return JSON.stringify(nodes);
6471
+ }
6472
+ function parseNavOrderJson(raw) {
6473
+ if (!raw) return null;
6474
+ try {
6475
+ const parsed = JSON.parse(raw);
6476
+ if (!Array.isArray(parsed)) return null;
6477
+ if (parsed.every((x) => typeof x === "string")) {
6478
+ return forestFromFlatOrder(parsed.filter((x) => typeof x === "string" && x.length > 0));
6479
+ }
6480
+ return parseNavNodeList(parsed);
6481
+ } catch {
6482
+ return null;
6483
+ }
6484
+ }
6485
+ function parseNavNodeList(raw) {
6486
+ const out = [];
6487
+ for (const item of raw) {
6488
+ if (typeof item === "string" && item.length > 0) {
6489
+ out.push({ id: item, children: [] });
6490
+ continue;
6491
+ }
6492
+ if (!item || typeof item !== "object") return null;
6493
+ const rec = item;
6494
+ if (typeof rec.id !== "string" || !rec.id) return null;
6495
+ const children = Array.isArray(rec.children) ? parseNavNodeList(rec.children) : [];
6496
+ if (!children) return null;
6497
+ out.push({ id: rec.id, children });
6498
+ }
6499
+ return out;
6500
+ }
6501
+ function flattenNavOrder(nodes) {
6502
+ const out = [];
6503
+ const walk = (list) => {
6504
+ for (const n of list) {
6505
+ out.push(n.id);
6506
+ walk(n.children);
6507
+ }
6508
+ };
6509
+ walk(nodes);
6510
+ return out;
6511
+ }
6512
+
6282
6513
  // src/lib/nav-items.ts
6283
6514
  var NAV_COUNT_KEY = "__ohw_nav_count";
6284
6515
  var NAV_ORDER_KEY = "__ohw_nav_order";
6516
+ var NAV_HREF_RE = /^nav-(\d+)-href$/;
6517
+ function isNavbarHrefKey(key) {
6518
+ if (!key) return false;
6519
+ if (key === "nav-book-href") return false;
6520
+ return NAV_HREF_RE.test(key);
6521
+ }
6285
6522
  function getLinkHref(el) {
6286
6523
  const key = el.getAttribute("data-ohw-href-key");
6287
6524
  if (key) {
@@ -6318,7 +6555,7 @@ function listNavbarItems() {
6318
6555
  }
6319
6556
  function parseNavIndexFromKey(key) {
6320
6557
  if (!key) return null;
6321
- const match = key.match(/^nav-(\d+)-href$/);
6558
+ const match = key.match(NAV_HREF_RE);
6322
6559
  if (!match) return null;
6323
6560
  return parseInt(match[1], 10);
6324
6561
  }
@@ -6341,17 +6578,6 @@ function getNavbarExistingTargets() {
6341
6578
  function getNavOrderFromDom() {
6342
6579
  return listNavbarItems().map((el) => el.getAttribute("data-ohw-href-key")).filter((key) => Boolean(key));
6343
6580
  }
6344
- function parseNavOrder(content) {
6345
- const raw = content[NAV_ORDER_KEY];
6346
- if (!raw) return null;
6347
- try {
6348
- const parsed = JSON.parse(raw);
6349
- if (!Array.isArray(parsed)) return null;
6350
- return parsed.filter((key) => typeof key === "string" && key.length > 0);
6351
- } catch {
6352
- return null;
6353
- }
6354
- }
6355
6581
  function findCounterpartByHrefKey(hrefKey, root) {
6356
6582
  return root.querySelector(`[data-ohw-href-key="${CSS.escape(hrefKey)}"]`);
6357
6583
  }
@@ -6415,19 +6641,43 @@ function insertNavbarItemDom(index, href, label, afterAnchor) {
6415
6641
  }
6416
6642
  return primary;
6417
6643
  }
6644
+ function navOrderKeysEqual(a, b) {
6645
+ if (a.length !== b.length) return false;
6646
+ for (let i = 0; i < a.length; i++) {
6647
+ if (a[i] !== b[i]) return false;
6648
+ }
6649
+ return true;
6650
+ }
6651
+ function getNavbarLinkKeysInContainer(container) {
6652
+ return Array.from(container.querySelectorAll("[data-ohw-href-key]")).filter(isNavbarLinkItem).map((el) => el.getAttribute("data-ohw-href-key")).filter((key) => Boolean(key));
6653
+ }
6418
6654
  function applyNavOrderToContainer(container, order) {
6655
+ const desired = order.filter((key, i) => order.indexOf(key) === i);
6656
+ const current = getNavbarLinkKeysInContainer(container);
6657
+ const extras = current.filter((key) => !desired.includes(key));
6658
+ const expected = [...desired.filter((key) => current.includes(key)), ...extras];
6659
+ const orderedEls = [];
6660
+ if (navOrderKeysEqual(current, expected)) return;
6661
+ if (current.length === expected.length && current.every((key, i) => key === expected[i])) {
6662
+ return;
6663
+ }
6419
6664
  const seen = /* @__PURE__ */ new Set();
6420
- for (const hrefKey of order) {
6665
+ for (const hrefKey of desired) {
6421
6666
  if (seen.has(hrefKey)) continue;
6422
6667
  seen.add(hrefKey);
6423
6668
  const el = findCounterpartByHrefKey(hrefKey, container);
6424
- if (el) container.appendChild(el);
6669
+ if (el && isNavbarLinkItem(el)) orderedEls.push(el);
6425
6670
  }
6426
6671
  for (const el of container.querySelectorAll("[data-ohw-href-key]")) {
6427
6672
  if (!isNavbarLinkItem(el)) continue;
6428
6673
  const key = el.getAttribute("data-ohw-href-key");
6429
6674
  if (!key || seen.has(key)) continue;
6430
- container.appendChild(el);
6675
+ orderedEls.push(el);
6676
+ }
6677
+ for (const el of orderedEls) {
6678
+ if (container.lastElementChild !== el) {
6679
+ container.appendChild(el);
6680
+ }
6431
6681
  }
6432
6682
  }
6433
6683
  function applyNavOrder(order) {
@@ -6436,6 +6686,75 @@ function applyNavOrder(order) {
6436
6686
  const drawer = getNavbarDrawerContainer();
6437
6687
  if (drawer) applyNavOrderToContainer(drawer, order);
6438
6688
  }
6689
+ function applyChildrenOrder(parentHrefKey, childKeys, root) {
6690
+ const parent = findCounterpartByHrefKey(parentHrefKey, root);
6691
+ if (!parent) return;
6692
+ const group = parent.closest("[data-ohw-nav-group]");
6693
+ const childrenRoot = group?.querySelector(":scope > [data-ohw-nav-children]");
6694
+ if (!childrenRoot) return;
6695
+ const current = Array.from(childrenRoot.querySelectorAll(":scope > [data-ohw-href-key]")).map((el) => el.getAttribute("data-ohw-href-key")).filter((key) => Boolean(key));
6696
+ if (navOrderKeysEqual(current, childKeys)) return;
6697
+ for (const key of childKeys) {
6698
+ const el = findCounterpartByHrefKey(key, root);
6699
+ if (!el) continue;
6700
+ if (childrenRoot.lastElementChild !== el) {
6701
+ childrenRoot.appendChild(el);
6702
+ }
6703
+ }
6704
+ }
6705
+ function applyNavForest(forest) {
6706
+ if (forestsEqual(forest, getNavForestFromDom())) return;
6707
+ const rootKeys = forest.map((n) => n.id);
6708
+ applyNavOrder(rootKeys);
6709
+ const desktop = getNavbarDesktopContainer();
6710
+ const drawer = getNavbarDrawerContainer();
6711
+ for (const node of forest) {
6712
+ if (node.children.length === 0) continue;
6713
+ const childKeys = node.children.map((c) => c.id);
6714
+ if (desktop) applyChildrenOrder(node.id, childKeys, desktop);
6715
+ if (drawer) applyChildrenOrder(node.id, childKeys, drawer);
6716
+ }
6717
+ }
6718
+ function getNavForestFromDom() {
6719
+ const items = listNavbarItems();
6720
+ const nestedChildKeys = /* @__PURE__ */ new Set();
6721
+ const roots = [];
6722
+ for (const item of items) {
6723
+ const key = item.getAttribute("data-ohw-href-key");
6724
+ if (!key || !isNavbarHrefKey(key)) continue;
6725
+ if (item.closest("[data-ohw-nav-children]")) continue;
6726
+ const group = item.closest("[data-ohw-nav-group]");
6727
+ const childrenRoot = group?.querySelector(":scope > [data-ohw-nav-children]");
6728
+ const children = [];
6729
+ if (childrenRoot) {
6730
+ for (const child of childrenRoot.querySelectorAll(":scope > [data-ohw-href-key]")) {
6731
+ if (!isNavbarLinkItem(child)) continue;
6732
+ const childKey = child.getAttribute("data-ohw-href-key");
6733
+ if (!childKey || !isNavbarHrefKey(childKey)) continue;
6734
+ nestedChildKeys.add(childKey);
6735
+ children.push({ id: childKey, children: [] });
6736
+ }
6737
+ }
6738
+ roots.push({ id: key, children });
6739
+ }
6740
+ if (roots.length === 0) {
6741
+ return forestFromFlatOrder(
6742
+ getNavOrderFromDom().filter((k) => isNavbarHrefKey(k) && !nestedChildKeys.has(k))
6743
+ );
6744
+ }
6745
+ return roots;
6746
+ }
6747
+ function planNavItemMove(hrefKey, parentId, insertIndex) {
6748
+ const model = getNavForestFromDom();
6749
+ const next = moveNode(model, hrefKey, { parentId, index: insertIndex });
6750
+ if (!next) return null;
6751
+ if (forestsEqual(model, next)) return null;
6752
+ return {
6753
+ forest: next,
6754
+ orderJson: serializeNavOrder(next),
6755
+ flatOrder: flattenNavOrder(next)
6756
+ };
6757
+ }
6439
6758
  function collectNavbarIndicesFromContent(content) {
6440
6759
  const indices = /* @__PURE__ */ new Set();
6441
6760
  for (const key of Object.keys(content)) {
@@ -6463,8 +6782,10 @@ function reconcileNavbarItemsFromContent(content) {
6463
6782
  if (!href && !label) continue;
6464
6783
  insertNavbarItemDom(index, href ?? "/", label ?? "Untitled", null);
6465
6784
  }
6466
- const order = parseNavOrder(content);
6467
- if (order?.length) applyNavOrder(order);
6785
+ const orderForest = parseNavOrderJson(content[NAV_ORDER_KEY]);
6786
+ if (orderForest?.length) {
6787
+ applyNavForest(orderForest);
6788
+ }
6468
6789
  }
6469
6790
  function buildNavOrderAfterInsert(afterAnchor, newHrefKey) {
6470
6791
  const order = getNavOrderFromDom();
@@ -6504,133 +6825,1023 @@ function insertNavbarItem(href, label, afterAnchor = null) {
6504
6825
  };
6505
6826
  }
6506
6827
 
6507
- // src/ui/navbar-container-chrome.tsx
6508
- var import_lucide_react10 = require("lucide-react");
6509
- var import_jsx_runtime21 = require("react/jsx-runtime");
6510
- function NavbarContainerChrome({
6511
- rect,
6512
- onAdd
6513
- }) {
6514
- const chromeGap = 6;
6515
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
6516
- "div",
6517
- {
6518
- "data-ohw-navbar-container-chrome": "",
6519
- "data-ohw-bridge": "",
6520
- className: "pointer-events-none fixed z-[2147483647]",
6521
- style: {
6522
- top: rect.top - chromeGap,
6523
- left: rect.left - chromeGap,
6524
- width: rect.width + chromeGap * 2,
6525
- height: rect.height + chromeGap * 2
6526
- },
6527
- children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
6528
- "button",
6529
- {
6530
- type: "button",
6531
- "data-ohw-navbar-add-button": "",
6532
- 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",
6533
- style: { top: "70%" },
6534
- "aria-label": "Add item",
6535
- onMouseDown: (e) => {
6536
- e.preventDefault();
6537
- e.stopPropagation();
6538
- },
6539
- onClick: (e) => {
6540
- e.preventDefault();
6541
- e.stopPropagation();
6542
- onAdd();
6543
- },
6544
- children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react10.Plus, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
6545
- }
6546
- )
6547
- }
6548
- );
6828
+ // src/lib/footer-items.ts
6829
+ var FOOTER_ORDER_KEY = "__ohw_footer_order";
6830
+ var FOOTER_HREF_RE = /^footer-(\d+)-(\d+)-href$/;
6831
+ function parseFooterHrefKey(key) {
6832
+ if (!key) return null;
6833
+ const match = key.match(FOOTER_HREF_RE);
6834
+ if (!match) return null;
6835
+ return { col: parseInt(match[1], 10), item: parseInt(match[2], 10) };
6549
6836
  }
6550
-
6551
- // src/ui/badge.tsx
6552
- var import_jsx_runtime22 = require("react/jsx-runtime");
6553
- var badgeVariants = cva(
6554
- "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",
6555
- {
6556
- variants: {
6557
- variant: {
6558
- default: "border-transparent bg-primary text-primary-foreground",
6559
- secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
6560
- destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
6561
- outline: "text-foreground"
6562
- }
6563
- },
6564
- defaultVariants: {
6565
- variant: "default"
6566
- }
6567
- }
6568
- );
6569
- function Badge({ className, variant, ...props }) {
6570
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: cn(badgeVariants({ variant }), className), ...props });
6837
+ function isFooterHrefKey(key) {
6838
+ return parseFooterHrefKey(key) !== null;
6571
6839
  }
6572
-
6573
- // src/OhhwellsBridge.tsx
6574
- var import_lucide_react11 = require("lucide-react");
6575
- var import_jsx_runtime23 = require("react/jsx-runtime");
6576
- var PRIMARY2 = "#0885FE";
6577
- var IMAGE_FADE_MS = 300;
6578
- function runOpacityFade(el, onDone) {
6579
- const anim = el.animate([{ opacity: 0 }, { opacity: 1 }], {
6580
- duration: IMAGE_FADE_MS,
6581
- easing: "ease",
6582
- fill: "forwards"
6583
- });
6584
- let finished = false;
6585
- const finish = () => {
6586
- if (finished) return;
6587
- finished = true;
6588
- anim.cancel();
6589
- el.style.opacity = "";
6590
- onDone();
6591
- };
6592
- anim.finished.then(finish).catch(finish);
6593
- window.setTimeout(finish, IMAGE_FADE_MS + 100);
6840
+ function getFooterRoot() {
6841
+ return document.querySelector('footer[data-ohw-section="footer"], footer');
6594
6842
  }
6595
- function fadeInImageElement(img, onReady) {
6596
- onReady();
6597
- img.style.opacity = "0";
6598
- runOpacityFade(img, () => {
6599
- img.style.opacity = "";
6843
+ function getFooterLinksContainer() {
6844
+ return document.querySelector("[data-ohw-footer-links]") ?? document.querySelector(".rb-footer-links");
6845
+ }
6846
+ function isFooterLinkAnchor(el) {
6847
+ if (!el.matches("[data-ohw-href-key]")) return false;
6848
+ if (!isFooterHrefKey(el.getAttribute("data-ohw-href-key"))) return false;
6849
+ return Boolean(el.querySelector('[data-ohw-editable="text"]'));
6850
+ }
6851
+ function listFooterColumns() {
6852
+ const root = getFooterLinksContainer() ?? getFooterRoot()?.querySelector(".rb-footer-links") ?? null;
6853
+ if (!root) return [];
6854
+ const explicit = Array.from(root.querySelectorAll(":scope > [data-ohw-footer-col]"));
6855
+ if (explicit.length > 0) return explicit;
6856
+ return Array.from(root.children).filter((el) => {
6857
+ if (!(el instanceof HTMLElement)) return false;
6858
+ return Array.from(el.querySelectorAll(":scope > [data-ohw-href-key]")).some(
6859
+ isFooterLinkAnchor
6860
+ );
6600
6861
  });
6601
6862
  }
6602
- function applyEditableImageSrc(img, url) {
6603
- img.removeAttribute("srcset");
6604
- img.removeAttribute("sizes");
6605
- img.src = url;
6863
+ function listFooterLinksInColumn(column) {
6864
+ return Array.from(column.querySelectorAll(":scope > [data-ohw-href-key]")).filter(
6865
+ isFooterLinkAnchor
6866
+ );
6606
6867
  }
6607
- function fadeInBgImage(el, url, onReady) {
6608
- const prevPos = el.style.position;
6609
- if (!prevPos || prevPos === "static") el.style.position = "relative";
6610
- const layer = document.createElement("div");
6611
- layer.setAttribute("data-ohw-bg-fade-layer", "");
6612
- Object.assign(layer.style, {
6613
- position: "absolute",
6614
- inset: "0",
6615
- zIndex: "0",
6616
- backgroundImage: `url('${url}')`,
6617
- backgroundSize: "cover",
6618
- backgroundPosition: "center",
6619
- backgroundRepeat: "no-repeat",
6620
- opacity: "0",
6621
- pointerEvents: "none"
6622
- });
6623
- el.prepend(layer);
6624
- onReady();
6625
- runOpacityFade(layer, () => {
6626
- el.style.backgroundImage = `url('${url}')`;
6627
- layer.remove();
6628
- if (!prevPos || prevPos === "static") el.style.position = prevPos;
6868
+ function findFooterColumnForLink(anchor) {
6869
+ const columns = listFooterColumns();
6870
+ return columns.find((col) => col.contains(anchor)) ?? null;
6871
+ }
6872
+ function getFooterOrderFromDom() {
6873
+ return listFooterColumns().map(
6874
+ (col) => listFooterLinksInColumn(col).map((el) => el.getAttribute("data-ohw-href-key")).filter((key) => Boolean(key))
6875
+ );
6876
+ }
6877
+ function findFooterLinkByKey(hrefKey) {
6878
+ return document.querySelector(
6879
+ `footer [data-ohw-href-key="${CSS.escape(hrefKey)}"]`
6880
+ );
6881
+ }
6882
+ function syncFooterColumnIndices(columns) {
6883
+ columns.forEach((col, index) => {
6884
+ const next = String(index);
6885
+ if (col.getAttribute("data-ohw-footer-col") !== next) {
6886
+ col.setAttribute("data-ohw-footer-col", next);
6887
+ }
6629
6888
  });
6630
6889
  }
6631
- function getSectionsTracker() {
6632
- let el = document.querySelector("[data-ohw-sections-tracker]");
6633
- if (!el) {
6890
+ function appendChildIfNeeded(parent, child) {
6891
+ if (parent.lastElementChild !== child) {
6892
+ parent.appendChild(child);
6893
+ }
6894
+ }
6895
+ function applyFooterOrder(order) {
6896
+ const container = getFooterLinksContainer();
6897
+ if (!container) return;
6898
+ const columns = listFooterColumns();
6899
+ if (columns.length === 0) return;
6900
+ const currentOrder = getFooterOrderFromDom();
6901
+ if (ordersEqual(order, currentOrder)) {
6902
+ syncFooterColumnIndices(columns);
6903
+ return;
6904
+ }
6905
+ const used = /* @__PURE__ */ new Set();
6906
+ const orderedCols = [];
6907
+ for (const colOrder of order) {
6908
+ let matched = null;
6909
+ for (const hrefKey of colOrder) {
6910
+ const link = findFooterLinkByKey(hrefKey);
6911
+ if (!link) continue;
6912
+ const owner = findFooterColumnForLink(link);
6913
+ if (owner && !used.has(owner)) {
6914
+ matched = owner;
6915
+ break;
6916
+ }
6917
+ }
6918
+ if (!matched) {
6919
+ matched = columns.find((col) => !used.has(col)) ?? null;
6920
+ }
6921
+ if (matched) {
6922
+ used.add(matched);
6923
+ orderedCols.push(matched);
6924
+ }
6925
+ }
6926
+ for (const col of columns) {
6927
+ if (!used.has(col)) orderedCols.push(col);
6928
+ }
6929
+ for (const col of orderedCols) {
6930
+ appendChildIfNeeded(container, col);
6931
+ }
6932
+ const freshColumns = listFooterColumns();
6933
+ syncFooterColumnIndices(freshColumns);
6934
+ for (let c = 0; c < order.length; c++) {
6935
+ const col = freshColumns[c];
6936
+ if (!col) continue;
6937
+ const colOrder = order[c];
6938
+ for (const hrefKey of colOrder) {
6939
+ const el = findFooterLinkByKey(hrefKey);
6940
+ if (!el) continue;
6941
+ if (el.parentElement !== col || col.lastElementChild !== el) {
6942
+ col.appendChild(el);
6943
+ }
6944
+ }
6945
+ }
6946
+ }
6947
+ function ordersEqual(a, b) {
6948
+ if (a.length !== b.length) return false;
6949
+ for (let i = 0; i < a.length; i++) {
6950
+ const left = a[i];
6951
+ const right = b[i];
6952
+ if (left.length !== right.length) return false;
6953
+ for (let j = 0; j < left.length; j++) {
6954
+ if (left[j] !== right[j]) return false;
6955
+ }
6956
+ }
6957
+ return true;
6958
+ }
6959
+ function planFooterLinkMove(hrefKey, targetColIndex, insertIndex) {
6960
+ const order = getFooterOrderFromDom();
6961
+ if (targetColIndex < 0 || targetColIndex >= order.length) return null;
6962
+ let fromCol = -1;
6963
+ let fromIdx = -1;
6964
+ for (let c = 0; c < order.length; c++) {
6965
+ const idx = order[c].indexOf(hrefKey);
6966
+ if (idx >= 0) {
6967
+ fromCol = c;
6968
+ fromIdx = idx;
6969
+ break;
6970
+ }
6971
+ }
6972
+ if (fromCol < 0) return null;
6973
+ const next = order.map((col) => [...col]);
6974
+ next[fromCol].splice(fromIdx, 1);
6975
+ let adjusted = insertIndex;
6976
+ if (fromCol === targetColIndex && fromIdx < insertIndex) adjusted -= 1;
6977
+ adjusted = Math.max(0, Math.min(adjusted, next[targetColIndex].length));
6978
+ next[targetColIndex].splice(adjusted, 0, hrefKey);
6979
+ if (ordersEqual(next, order)) return null;
6980
+ return next;
6981
+ }
6982
+ function planFooterColumnMove(fromIndex, toIndex) {
6983
+ const order = getFooterOrderFromDom();
6984
+ const columns = listFooterColumns();
6985
+ if (fromIndex < 0 || fromIndex >= columns.length || toIndex < 0 || toIndex > columns.length) {
6986
+ return null;
6987
+ }
6988
+ if (toIndex === fromIndex || toIndex === fromIndex + 1) return null;
6989
+ const nextOrder = order.map((col) => [...col]);
6990
+ const [movedOrder] = nextOrder.splice(fromIndex, 1);
6991
+ if (!movedOrder) return null;
6992
+ let adjusted = toIndex;
6993
+ if (fromIndex < toIndex) adjusted -= 1;
6994
+ adjusted = Math.max(0, Math.min(adjusted, nextOrder.length));
6995
+ nextOrder.splice(adjusted, 0, movedOrder);
6996
+ if (ordersEqual(nextOrder, order)) return null;
6997
+ return nextOrder;
6998
+ }
6999
+ function parseFooterOrder(content) {
7000
+ const raw = content[FOOTER_ORDER_KEY];
7001
+ if (!raw) return null;
7002
+ try {
7003
+ const parsed = JSON.parse(raw);
7004
+ if (!Array.isArray(parsed)) return null;
7005
+ return parsed.filter((col) => Array.isArray(col)).map((col) => col.filter((key) => typeof key === "string" && key.length > 0));
7006
+ } catch {
7007
+ return null;
7008
+ }
7009
+ }
7010
+ function reconcileFooterOrderFromContent(content) {
7011
+ const order = parseFooterOrder(content);
7012
+ if (!order?.length) return;
7013
+ const current = getFooterOrderFromDom();
7014
+ if (ordersEqual(order, current)) {
7015
+ syncFooterColumnIndices(listFooterColumns());
7016
+ return;
7017
+ }
7018
+ applyFooterOrder(order);
7019
+ }
7020
+ function buildLinkDropSlots(column, columnIndex) {
7021
+ const links = listFooterLinksInColumn(column);
7022
+ const colRect = column.getBoundingClientRect();
7023
+ const slots = [];
7024
+ const barThickness = 3;
7025
+ if (links.length === 0) {
7026
+ slots.push({
7027
+ insertIndex: 0,
7028
+ columnIndex,
7029
+ left: colRect.left,
7030
+ top: colRect.top + colRect.height / 2 - barThickness / 2,
7031
+ width: colRect.width,
7032
+ height: barThickness,
7033
+ direction: "horizontal"
7034
+ });
7035
+ return slots;
7036
+ }
7037
+ for (let i = 0; i <= links.length; i++) {
7038
+ let top;
7039
+ if (i === 0) {
7040
+ top = links[0].getBoundingClientRect().top - barThickness / 2;
7041
+ } else if (i === links.length) {
7042
+ const last = links[links.length - 1].getBoundingClientRect();
7043
+ top = last.bottom - barThickness / 2;
7044
+ } else {
7045
+ const prev = links[i - 1].getBoundingClientRect();
7046
+ const next = links[i].getBoundingClientRect();
7047
+ top = (prev.bottom + next.top) / 2 - barThickness / 2;
7048
+ }
7049
+ const widthRef = i === 0 ? links[0].getBoundingClientRect() : i === links.length ? links[links.length - 1].getBoundingClientRect() : links[i].getBoundingClientRect();
7050
+ slots.push({
7051
+ insertIndex: i,
7052
+ columnIndex,
7053
+ left: widthRef.left,
7054
+ top,
7055
+ width: Math.max(widthRef.width, colRect.width * 0.8),
7056
+ height: barThickness,
7057
+ direction: "horizontal"
7058
+ });
7059
+ }
7060
+ return slots;
7061
+ }
7062
+ function buildColumnDropSlots() {
7063
+ const columns = listFooterColumns();
7064
+ const slots = [];
7065
+ const barThickness = 3;
7066
+ if (columns.length === 0) return slots;
7067
+ for (let i = 0; i <= columns.length; i++) {
7068
+ let left;
7069
+ let height;
7070
+ let top;
7071
+ if (i === 0) {
7072
+ const first = columns[0].getBoundingClientRect();
7073
+ left = first.left - barThickness / 2;
7074
+ top = first.top;
7075
+ height = first.height;
7076
+ } else if (i === columns.length) {
7077
+ const last = columns[columns.length - 1].getBoundingClientRect();
7078
+ left = last.right - barThickness / 2;
7079
+ top = last.top;
7080
+ height = last.height;
7081
+ } else {
7082
+ const prev = columns[i - 1].getBoundingClientRect();
7083
+ const next = columns[i].getBoundingClientRect();
7084
+ left = (prev.right + next.left) / 2 - barThickness / 2;
7085
+ top = Math.min(prev.top, next.top);
7086
+ height = Math.max(prev.bottom, next.bottom) - top;
7087
+ }
7088
+ slots.push({
7089
+ insertIndex: i,
7090
+ columnIndex: i,
7091
+ left,
7092
+ top,
7093
+ width: barThickness,
7094
+ height: Math.max(height, 24),
7095
+ direction: "vertical"
7096
+ });
7097
+ }
7098
+ return slots;
7099
+ }
7100
+ function hitTestLinkDropSlot(clientX, clientY, draggedHrefKey) {
7101
+ const columns = listFooterColumns();
7102
+ let best = null;
7103
+ for (let c = 0; c < columns.length; c++) {
7104
+ const col = columns[c];
7105
+ const colRect = col.getBoundingClientRect();
7106
+ const inColX = clientX >= colRect.left - 24 && clientX <= colRect.right + 24;
7107
+ if (!inColX) continue;
7108
+ const slots = buildLinkDropSlots(col, c).filter((slot) => {
7109
+ const links = listFooterLinksInColumn(col);
7110
+ const fromIdx = links.findIndex(
7111
+ (el) => el.getAttribute("data-ohw-href-key") === draggedHrefKey
7112
+ );
7113
+ if (fromIdx < 0) return true;
7114
+ if (c === findColumnIndexForKey(draggedHrefKey) && (slot.insertIndex === fromIdx || slot.insertIndex === fromIdx + 1)) {
7115
+ return true;
7116
+ }
7117
+ return true;
7118
+ });
7119
+ for (const slot of slots) {
7120
+ const cy = slot.top + slot.height / 2;
7121
+ const dist = Math.abs(clientY - cy) + (inColX ? 0 : 1e3);
7122
+ if (!best || dist < best.dist) best = { slot, dist };
7123
+ }
7124
+ }
7125
+ return best?.slot ?? null;
7126
+ }
7127
+ function findColumnIndexForKey(hrefKey) {
7128
+ const order = getFooterOrderFromDom();
7129
+ for (let c = 0; c < order.length; c++) {
7130
+ if (order[c].includes(hrefKey)) return c;
7131
+ }
7132
+ return -1;
7133
+ }
7134
+ function hitTestColumnDropSlot(clientX, _clientY) {
7135
+ const slots = buildColumnDropSlots();
7136
+ let best = null;
7137
+ for (const slot of slots) {
7138
+ const cx2 = slot.left + slot.width / 2;
7139
+ const dist = Math.abs(clientX - cx2);
7140
+ if (!best || dist < best.dist) best = { slot, dist };
7141
+ }
7142
+ return best?.slot ?? null;
7143
+ }
7144
+
7145
+ // src/lib/item-drag-interaction.ts
7146
+ function disableNativeHrefDrag(el) {
7147
+ if (el.draggable) el.draggable = false;
7148
+ if (el.getAttribute("draggable") !== "false") {
7149
+ el.setAttribute("draggable", "false");
7150
+ }
7151
+ }
7152
+ function clearTextSelection() {
7153
+ const sel = window.getSelection();
7154
+ if (sel && !sel.isCollapsed) sel.removeAllRanges();
7155
+ }
7156
+ function armItemPressDrag() {
7157
+ document.documentElement.setAttribute("data-ohw-footer-press-drag", "");
7158
+ }
7159
+ function lockItemDuringDrag() {
7160
+ document.documentElement.removeAttribute("data-ohw-footer-press-drag");
7161
+ document.documentElement.setAttribute("data-ohw-item-dragging", "");
7162
+ clearTextSelection();
7163
+ }
7164
+ function unlockItemDragInteraction() {
7165
+ document.documentElement.removeAttribute("data-ohw-footer-press-drag");
7166
+ document.documentElement.removeAttribute("data-ohw-item-dragging");
7167
+ clearTextSelection();
7168
+ }
7169
+ var armFooterPressDrag = armItemPressDrag;
7170
+ var lockFooterDuringDrag = lockItemDuringDrag;
7171
+ var unlockFooterDragInteraction = unlockItemDragInteraction;
7172
+
7173
+ // src/lib/nav-dnd.ts
7174
+ function listReorderableNavItems() {
7175
+ return listNavbarItems().filter((el) => {
7176
+ const key = el.getAttribute("data-ohw-href-key");
7177
+ return isNavbarHrefKey(key) && !el.closest("[data-ohw-nav-children]");
7178
+ });
7179
+ }
7180
+ function listNavChildren(parentHrefKey) {
7181
+ const items = listNavbarItems();
7182
+ const parent = items.find((el) => el.getAttribute("data-ohw-href-key") === parentHrefKey);
7183
+ if (!parent) return [];
7184
+ const group = parent.closest("[data-ohw-nav-group]");
7185
+ const childrenRoot = group?.querySelector(":scope > [data-ohw-nav-children]");
7186
+ if (!childrenRoot) return [];
7187
+ return Array.from(childrenRoot.querySelectorAll(":scope > [data-ohw-href-key]")).filter(
7188
+ (el) => isNavbarHrefKey(el.getAttribute("data-ohw-href-key"))
7189
+ );
7190
+ }
7191
+ function activeNavListContainer() {
7192
+ const desktop = getNavbarDesktopContainer();
7193
+ if (desktop && desktop.getClientRects().length > 0) {
7194
+ const style = window.getComputedStyle(desktop);
7195
+ if (style.display !== "none" && style.visibility !== "hidden") return desktop;
7196
+ }
7197
+ return getNavbarDrawerContainer();
7198
+ }
7199
+ function buildRootNavDropSlots() {
7200
+ const items = listReorderableNavItems();
7201
+ const slots = [];
7202
+ const barThickness = 3;
7203
+ if (items.length === 0) {
7204
+ const container = activeNavListContainer();
7205
+ if (!container) return slots;
7206
+ const rect = container.getBoundingClientRect();
7207
+ slots.push({
7208
+ insertIndex: 0,
7209
+ parentId: null,
7210
+ left: rect.left + rect.width / 2 - barThickness / 2,
7211
+ top: rect.top,
7212
+ width: barThickness,
7213
+ height: Math.max(rect.height, 24),
7214
+ direction: "vertical"
7215
+ });
7216
+ return slots;
7217
+ }
7218
+ const edgeGap = (() => {
7219
+ if (items.length < 2) return 32;
7220
+ const a = items[0].getBoundingClientRect();
7221
+ const b = items[1].getBoundingClientRect();
7222
+ return Math.max(0, b.left - a.right);
7223
+ })();
7224
+ for (let i = 0; i <= items.length; i++) {
7225
+ let left;
7226
+ let top;
7227
+ let height;
7228
+ if (i === 0) {
7229
+ const first = items[0].getBoundingClientRect();
7230
+ left = first.left - edgeGap / 2 - barThickness / 2;
7231
+ top = first.top;
7232
+ height = first.height;
7233
+ } else if (i === items.length) {
7234
+ const last = items[items.length - 1].getBoundingClientRect();
7235
+ const lastGap = items.length >= 2 ? Math.max(
7236
+ 0,
7237
+ last.left - items[items.length - 2].getBoundingClientRect().right
7238
+ ) : edgeGap;
7239
+ left = last.right + lastGap / 2 - barThickness / 2;
7240
+ top = last.top;
7241
+ height = last.height;
7242
+ } else {
7243
+ const prev = items[i - 1].getBoundingClientRect();
7244
+ const next = items[i].getBoundingClientRect();
7245
+ left = (prev.right + next.left) / 2 - barThickness / 2;
7246
+ top = Math.min(prev.top, next.top);
7247
+ height = Math.max(prev.bottom, next.bottom) - top;
7248
+ }
7249
+ slots.push({
7250
+ insertIndex: i,
7251
+ parentId: null,
7252
+ left,
7253
+ top,
7254
+ width: barThickness,
7255
+ height: Math.max(height, 24),
7256
+ direction: "vertical"
7257
+ });
7258
+ }
7259
+ return slots;
7260
+ }
7261
+ function buildChildNavDropSlots(parentHrefKey) {
7262
+ const children = listNavChildren(parentHrefKey);
7263
+ const slots = [];
7264
+ const barThickness = 3;
7265
+ if (children.length === 0) return slots;
7266
+ const edgeGap = (() => {
7267
+ if (children.length < 2) return 16;
7268
+ const a = children[0].getBoundingClientRect();
7269
+ const b = children[1].getBoundingClientRect();
7270
+ return Math.max(0, b.top - a.bottom);
7271
+ })();
7272
+ for (let i = 0; i <= children.length; i++) {
7273
+ let top;
7274
+ let width;
7275
+ let left;
7276
+ if (i === 0) {
7277
+ const first = children[0].getBoundingClientRect();
7278
+ top = first.top - edgeGap / 2 - barThickness / 2;
7279
+ left = first.left;
7280
+ width = first.width;
7281
+ } else if (i === children.length) {
7282
+ const last = children[children.length - 1].getBoundingClientRect();
7283
+ const lastGap = children.length >= 2 ? Math.max(
7284
+ 0,
7285
+ last.top - children[children.length - 2].getBoundingClientRect().bottom
7286
+ ) : edgeGap;
7287
+ top = last.bottom + lastGap / 2 - barThickness / 2;
7288
+ left = last.left;
7289
+ width = last.width;
7290
+ } else {
7291
+ const prev = children[i - 1].getBoundingClientRect();
7292
+ const next = children[i].getBoundingClientRect();
7293
+ top = (prev.bottom + next.top) / 2 - barThickness / 2;
7294
+ left = Math.min(prev.left, next.left);
7295
+ width = Math.max(prev.right, next.right) - left;
7296
+ }
7297
+ slots.push({
7298
+ insertIndex: i,
7299
+ parentId: parentHrefKey,
7300
+ left,
7301
+ top,
7302
+ width: Math.max(width, 40),
7303
+ height: barThickness,
7304
+ direction: "horizontal"
7305
+ });
7306
+ }
7307
+ return slots;
7308
+ }
7309
+ function buildAllNavDropSlots(draggedHrefKey) {
7310
+ const slots = [...buildRootNavDropSlots()];
7311
+ for (const item of listReorderableNavItems()) {
7312
+ const key = item.getAttribute("data-ohw-href-key");
7313
+ if (!key || key === draggedHrefKey) continue;
7314
+ const group = item.closest("[data-ohw-nav-group]");
7315
+ if (!group?.querySelector(":scope > [data-ohw-nav-children]")) continue;
7316
+ slots.push(...buildChildNavDropSlots(key));
7317
+ }
7318
+ const dragged = listNavbarItems().find((el) => el.getAttribute("data-ohw-href-key") === draggedHrefKey);
7319
+ const parentGroup = dragged?.closest("[data-ohw-nav-children]")?.closest("[data-ohw-nav-group]");
7320
+ const parentTrigger = parentGroup?.querySelector(":scope > [data-ohw-href-key]");
7321
+ const parentKey = parentTrigger?.getAttribute("data-ohw-href-key");
7322
+ if (parentKey && isNavbarHrefKey(parentKey)) {
7323
+ const existing = slots.some((s) => s.parentId === parentKey);
7324
+ if (!existing) slots.push(...buildChildNavDropSlots(parentKey));
7325
+ }
7326
+ return slots;
7327
+ }
7328
+ function hitTestNavDropSlot(clientX, clientY, draggedHrefKey) {
7329
+ const slots = buildAllNavDropSlots(draggedHrefKey);
7330
+ let best = null;
7331
+ for (const slot of slots) {
7332
+ const cx2 = slot.left + slot.width / 2;
7333
+ const cy = slot.top + slot.height / 2;
7334
+ const dist = slot.direction === "vertical" ? Math.abs(clientX - cx2) + Math.abs(clientY - cy) * 0.25 : Math.abs(clientY - cy) + Math.abs(clientX - cx2) * 0.25;
7335
+ if (!best || dist < best.dist) best = { slot, dist };
7336
+ }
7337
+ return best?.slot ?? null;
7338
+ }
7339
+
7340
+ // src/useNavItemDrag.ts
7341
+ var import_react8 = require("react");
7342
+ function useNavItemDrag({
7343
+ isEditMode,
7344
+ editContentRef,
7345
+ selectedElRef,
7346
+ activeElRef,
7347
+ footerDragRef,
7348
+ suppressNextClickRef,
7349
+ suppressClickUntilRef,
7350
+ siblingHintElRef,
7351
+ postToParentRef,
7352
+ deselectRef,
7353
+ selectRef,
7354
+ deactivateRef,
7355
+ setLinkPopover,
7356
+ linkPopoverOpenRef,
7357
+ setIsItemDragging,
7358
+ setDraggedItemRect,
7359
+ setSiblingHintRect,
7360
+ setSiblingHintRects,
7361
+ setToolbarRect,
7362
+ getNavigationItemAnchor: getNavigationItemAnchor2,
7363
+ isDragHandleDisabled: isDragHandleDisabled2,
7364
+ isNavbarButton: isNavbarButton3
7365
+ }) {
7366
+ const navDragRef = (0, import_react8.useRef)(null);
7367
+ const [navDropSlots, setNavDropSlots] = (0, import_react8.useState)([]);
7368
+ const [activeNavDropIndex, setActiveNavDropIndex] = (0, import_react8.useState)(null);
7369
+ const navPointerDragRef = (0, import_react8.useRef)(null);
7370
+ const clearNavDragVisuals = (0, import_react8.useCallback)(() => {
7371
+ navDragRef.current = null;
7372
+ setNavDropSlots([]);
7373
+ setActiveNavDropIndex(null);
7374
+ setDraggedItemRect(null);
7375
+ setSiblingHintRects([]);
7376
+ setIsItemDragging(false);
7377
+ unlockItemDragInteraction();
7378
+ }, [setDraggedItemRect, setIsItemDragging, setSiblingHintRects]);
7379
+ const refreshNavDragVisuals = (0, import_react8.useCallback)(
7380
+ (session, activeSlot, clientX, clientY) => {
7381
+ setDraggedItemRect(session.draggedEl.getBoundingClientRect());
7382
+ if (typeof clientX === "number" && typeof clientY === "number") {
7383
+ session.lastClientX = clientX;
7384
+ session.lastClientY = clientY;
7385
+ }
7386
+ session.activeSlot = activeSlot;
7387
+ setSiblingHintRects([]);
7388
+ const slots = buildAllNavDropSlots(session.hrefKey);
7389
+ setNavDropSlots(slots);
7390
+ const activeIdx = activeSlot ? slots.findIndex(
7391
+ (s) => s.parentId === activeSlot.parentId && s.insertIndex === activeSlot.insertIndex
7392
+ ) : -1;
7393
+ setActiveNavDropIndex(activeIdx >= 0 ? activeIdx : null);
7394
+ },
7395
+ [setDraggedItemRect, setSiblingHintRects]
7396
+ );
7397
+ const refreshNavDragVisualsRef = (0, import_react8.useRef)(refreshNavDragVisuals);
7398
+ refreshNavDragVisualsRef.current = refreshNavDragVisuals;
7399
+ const commitNavDragRef = (0, import_react8.useRef)(() => {
7400
+ });
7401
+ const beginNavDragRef = (0, import_react8.useRef)(() => {
7402
+ });
7403
+ const beginNavDrag = (0, import_react8.useCallback)(
7404
+ (session) => {
7405
+ const rect = session.draggedEl.getBoundingClientRect();
7406
+ session.lastClientX = session.lastClientX || rect.left + rect.width / 2;
7407
+ session.lastClientY = session.lastClientY || rect.top + rect.height / 2;
7408
+ session.activeSlot = session.activeSlot ?? null;
7409
+ navDragRef.current = session;
7410
+ setIsItemDragging(true);
7411
+ lockItemDuringDrag();
7412
+ siblingHintElRef.current = null;
7413
+ setSiblingHintRect(null);
7414
+ if (session.wasSelected && selectedElRef.current === session.draggedEl) {
7415
+ setToolbarRect(rect);
7416
+ }
7417
+ const initialSlot = hitTestNavDropSlot(session.lastClientX, session.lastClientY, session.hrefKey);
7418
+ refreshNavDragVisuals(session, initialSlot, session.lastClientX, session.lastClientY);
7419
+ },
7420
+ [
7421
+ refreshNavDragVisuals,
7422
+ selectedElRef,
7423
+ setIsItemDragging,
7424
+ setSiblingHintRect,
7425
+ setToolbarRect,
7426
+ siblingHintElRef
7427
+ ]
7428
+ );
7429
+ beginNavDragRef.current = beginNavDrag;
7430
+ const commitNavDrag = (0, import_react8.useCallback)(
7431
+ (clientX, clientY) => {
7432
+ const session = navDragRef.current;
7433
+ if (!session) {
7434
+ clearNavDragVisuals();
7435
+ return;
7436
+ }
7437
+ const x = typeof clientX === "number" && (clientX !== 0 || clientY !== 0) ? clientX : session.lastClientX;
7438
+ const y = typeof clientY === "number" && (clientX !== 0 || clientY !== 0) ? clientY : session.lastClientY;
7439
+ const slot = session.activeSlot ?? hitTestNavDropSlot(x, y, session.hrefKey);
7440
+ const planned = slot != null ? planNavItemMove(session.hrefKey, slot.parentId, slot.insertIndex) : null;
7441
+ const wasSelected = session.wasSelected;
7442
+ const hrefKey = session.hrefKey;
7443
+ const applySelectionAfterDrop = () => {
7444
+ if (!wasSelected) {
7445
+ deselectRef.current();
7446
+ return;
7447
+ }
7448
+ const desktop = document.querySelector("[data-ohw-nav-container]");
7449
+ const drawer = document.querySelector("[data-ohw-nav-drawer]");
7450
+ const link = desktop?.querySelector(`[data-ohw-href-key="${CSS.escape(hrefKey)}"]`) ?? drawer?.querySelector(`[data-ohw-href-key="${CSS.escape(hrefKey)}"]`) ?? document.querySelector(`[data-ohw-href-key="${CSS.escape(hrefKey)}"]`);
7451
+ if (link) {
7452
+ selectRef.current(link);
7453
+ return;
7454
+ }
7455
+ deselectRef.current();
7456
+ };
7457
+ if (planned) {
7458
+ editContentRef.current = {
7459
+ ...editContentRef.current,
7460
+ [NAV_ORDER_KEY]: planned.orderJson
7461
+ };
7462
+ applyNavForest(planned.forest);
7463
+ document.querySelectorAll(
7464
+ "nav [data-ohw-href-key], [data-ohw-nav-container] [data-ohw-href-key], [data-ohw-nav-drawer] [data-ohw-href-key]"
7465
+ ).forEach((el) => {
7466
+ if (isNavbarHrefKey(el.getAttribute("data-ohw-href-key"))) {
7467
+ disableNativeHrefDrag(el);
7468
+ }
7469
+ });
7470
+ postToParentRef.current({
7471
+ type: "ow:change",
7472
+ nodes: [{ key: NAV_ORDER_KEY, text: planned.orderJson }]
7473
+ });
7474
+ applySelectionAfterDrop();
7475
+ clearNavDragVisuals();
7476
+ requestAnimationFrame(() => {
7477
+ if (editContentRef.current[NAV_ORDER_KEY] === planned.orderJson) {
7478
+ applyNavForest(planned.forest);
7479
+ }
7480
+ applySelectionAfterDrop();
7481
+ requestAnimationFrame(applySelectionAfterDrop);
7482
+ });
7483
+ return;
7484
+ }
7485
+ applySelectionAfterDrop();
7486
+ clearNavDragVisuals();
7487
+ },
7488
+ [clearNavDragVisuals, deselectRef, editContentRef, postToParentRef, selectRef]
7489
+ );
7490
+ commitNavDragRef.current = commitNavDrag;
7491
+ const startNavLinkDrag = (0, import_react8.useCallback)(
7492
+ (anchor, clientX, clientY, wasSelected) => {
7493
+ if (footerDragRef.current) return false;
7494
+ const hrefKey = anchor.getAttribute("data-ohw-href-key");
7495
+ if (!hrefKey || !isNavbarHrefKey(hrefKey)) return false;
7496
+ if (isNavbarButton3(anchor) || isDragHandleDisabled2(anchor)) return false;
7497
+ beginNavDrag({
7498
+ hrefKey,
7499
+ wasSelected,
7500
+ draggedEl: anchor,
7501
+ lastClientX: clientX,
7502
+ lastClientY: clientY,
7503
+ activeSlot: null
7504
+ });
7505
+ return true;
7506
+ },
7507
+ [beginNavDrag, footerDragRef, isDragHandleDisabled2, isNavbarButton3]
7508
+ );
7509
+ const onNavDragOver = (0, import_react8.useCallback)(
7510
+ (e) => {
7511
+ const session = navDragRef.current;
7512
+ if (!session) return false;
7513
+ e.preventDefault();
7514
+ if (e.dataTransfer) e.dataTransfer.dropEffect = "move";
7515
+ const slot = hitTestNavDropSlot(e.clientX, e.clientY, session.hrefKey);
7516
+ refreshNavDragVisualsRef.current(session, slot, e.clientX, e.clientY);
7517
+ return true;
7518
+ },
7519
+ []
7520
+ );
7521
+ (0, import_react8.useEffect)(() => {
7522
+ if (!isEditMode) return;
7523
+ const THRESHOLD = 10;
7524
+ const resolveWasSelected = (el) => {
7525
+ if (selectedElRef.current === el) return true;
7526
+ const activeAnchor = activeElRef.current ? getNavigationItemAnchor2(activeElRef.current) : null;
7527
+ return activeAnchor === el;
7528
+ };
7529
+ const onPointerDown = (e) => {
7530
+ if (e.button !== 0) return;
7531
+ if (navDragRef.current || footerDragRef.current) return;
7532
+ const target = e.target;
7533
+ if (!target) return;
7534
+ if (target.closest(
7535
+ '[data-ohw-drag-handle-container], [data-slot="drag-handle"], [data-ohw-toolbar], [data-ohw-item-toolbar-anchor], [data-ohw-link-popover-root]'
7536
+ )) {
7537
+ return;
7538
+ }
7539
+ if (target.closest("[data-ohw-item-drag-surface]")) return;
7540
+ const anchor = getNavigationItemAnchor2(target);
7541
+ const hrefKey = anchor?.getAttribute("data-ohw-href-key") ?? null;
7542
+ if (!anchor || !isNavbarHrefKey(hrefKey)) return;
7543
+ if (isNavbarButton3(anchor) || isDragHandleDisabled2(anchor)) return;
7544
+ armItemPressDrag();
7545
+ navPointerDragRef.current = {
7546
+ el: anchor,
7547
+ startX: e.clientX,
7548
+ startY: e.clientY,
7549
+ pointerId: e.pointerId,
7550
+ wasSelected: resolveWasSelected(anchor),
7551
+ started: false
7552
+ };
7553
+ };
7554
+ const onPointerMove = (e) => {
7555
+ const pending = navPointerDragRef.current;
7556
+ if (!pending) return;
7557
+ if (pending.started) {
7558
+ e.preventDefault();
7559
+ clearTextSelection();
7560
+ const session = navDragRef.current;
7561
+ if (!session) return;
7562
+ const slot = hitTestNavDropSlot(e.clientX, e.clientY, session.hrefKey);
7563
+ refreshNavDragVisualsRef.current(session, slot, e.clientX, e.clientY);
7564
+ return;
7565
+ }
7566
+ const dx = e.clientX - pending.startX;
7567
+ const dy = e.clientY - pending.startY;
7568
+ if (dx * dx + dy * dy < THRESHOLD * THRESHOLD) return;
7569
+ e.preventDefault();
7570
+ pending.started = true;
7571
+ clearTextSelection();
7572
+ try {
7573
+ document.body.setPointerCapture(pending.pointerId);
7574
+ } catch {
7575
+ }
7576
+ if (linkPopoverOpenRef.current) setLinkPopover(null);
7577
+ if (activeElRef.current) deactivateRef.current();
7578
+ const key = pending.el.getAttribute("data-ohw-href-key");
7579
+ if (!key) return;
7580
+ beginNavDragRef.current({
7581
+ hrefKey: key,
7582
+ wasSelected: pending.wasSelected,
7583
+ draggedEl: pending.el,
7584
+ lastClientX: e.clientX,
7585
+ lastClientY: e.clientY,
7586
+ activeSlot: null
7587
+ });
7588
+ };
7589
+ const endPointerDrag = (e) => {
7590
+ const pending = navPointerDragRef.current;
7591
+ navPointerDragRef.current = null;
7592
+ try {
7593
+ if (document.body.hasPointerCapture(e.pointerId)) {
7594
+ document.body.releasePointerCapture(e.pointerId);
7595
+ }
7596
+ } catch {
7597
+ }
7598
+ if (!pending?.started) {
7599
+ unlockItemDragInteraction();
7600
+ return;
7601
+ }
7602
+ suppressNextClickRef.current = true;
7603
+ suppressClickUntilRef.current = Date.now() + 500;
7604
+ commitNavDragRef.current(e.clientX, e.clientY);
7605
+ };
7606
+ const blockSelectStart = (e) => {
7607
+ if (navDragRef.current || navPointerDragRef.current?.started || document.documentElement.hasAttribute("data-ohw-footer-press-drag")) {
7608
+ e.preventDefault();
7609
+ }
7610
+ };
7611
+ const onDragEnd = () => {
7612
+ if (!navDragRef.current) return;
7613
+ suppressNextClickRef.current = true;
7614
+ suppressClickUntilRef.current = Date.now() + 500;
7615
+ commitNavDragRef.current();
7616
+ };
7617
+ document.addEventListener("pointerdown", onPointerDown, true);
7618
+ document.addEventListener("pointermove", onPointerMove, true);
7619
+ document.addEventListener("pointerup", endPointerDrag, true);
7620
+ document.addEventListener("pointercancel", endPointerDrag, true);
7621
+ document.addEventListener("selectstart", blockSelectStart, true);
7622
+ document.addEventListener("dragend", onDragEnd, true);
7623
+ return () => {
7624
+ document.removeEventListener("pointerdown", onPointerDown, true);
7625
+ document.removeEventListener("pointermove", onPointerMove, true);
7626
+ document.removeEventListener("pointerup", endPointerDrag, true);
7627
+ document.removeEventListener("pointercancel", endPointerDrag, true);
7628
+ document.removeEventListener("selectstart", blockSelectStart, true);
7629
+ document.removeEventListener("dragend", onDragEnd, true);
7630
+ unlockItemDragInteraction();
7631
+ };
7632
+ }, [
7633
+ activeElRef,
7634
+ deactivateRef,
7635
+ footerDragRef,
7636
+ getNavigationItemAnchor2,
7637
+ isDragHandleDisabled2,
7638
+ isEditMode,
7639
+ isNavbarButton3,
7640
+ linkPopoverOpenRef,
7641
+ selectedElRef,
7642
+ setLinkPopover,
7643
+ suppressNextClickRef
7644
+ ]);
7645
+ const armNavPressFromChrome = (0, import_react8.useCallback)(
7646
+ (selected, clientX, clientY, pointerId) => {
7647
+ const hrefKey = selected.getAttribute("data-ohw-href-key");
7648
+ if (!hrefKey || !isNavbarHrefKey(hrefKey)) return false;
7649
+ if (isNavbarButton3(selected) || isDragHandleDisabled2(selected)) return false;
7650
+ armItemPressDrag();
7651
+ navPointerDragRef.current = {
7652
+ el: selected,
7653
+ startX: clientX,
7654
+ startY: clientY,
7655
+ pointerId,
7656
+ wasSelected: true,
7657
+ started: false
7658
+ };
7659
+ return true;
7660
+ },
7661
+ [isDragHandleDisabled2, isNavbarButton3]
7662
+ );
7663
+ return {
7664
+ navDragRef,
7665
+ navDropSlots,
7666
+ activeNavDropIndex,
7667
+ startNavLinkDrag,
7668
+ commitNavDrag,
7669
+ onNavDragOver,
7670
+ armNavPressFromChrome,
7671
+ clearNavDragVisuals,
7672
+ refreshNavDragVisualsRef
7673
+ };
7674
+ }
7675
+
7676
+ // src/ui/navbar-container-chrome.tsx
7677
+ var import_lucide_react10 = require("lucide-react");
7678
+ var import_jsx_runtime21 = require("react/jsx-runtime");
7679
+ function NavbarContainerChrome({
7680
+ rect,
7681
+ onAdd
7682
+ }) {
7683
+ const chromeGap = 6;
7684
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7685
+ "div",
7686
+ {
7687
+ "data-ohw-navbar-container-chrome": "",
7688
+ "data-ohw-bridge": "",
7689
+ className: "pointer-events-none fixed z-[2147483647]",
7690
+ style: {
7691
+ top: rect.top - chromeGap,
7692
+ left: rect.left - chromeGap,
7693
+ width: rect.width + chromeGap * 2,
7694
+ height: rect.height + chromeGap * 2
7695
+ },
7696
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7697
+ "button",
7698
+ {
7699
+ type: "button",
7700
+ "data-ohw-navbar-add-button": "",
7701
+ 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",
7702
+ style: { top: "70%" },
7703
+ "aria-label": "Add item",
7704
+ onMouseDown: (e) => {
7705
+ e.preventDefault();
7706
+ e.stopPropagation();
7707
+ },
7708
+ onClick: (e) => {
7709
+ e.preventDefault();
7710
+ e.stopPropagation();
7711
+ onAdd();
7712
+ },
7713
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react10.Plus, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
7714
+ }
7715
+ )
7716
+ }
7717
+ );
7718
+ }
7719
+
7720
+ // src/ui/drop-indicator.tsx
7721
+ var React10 = __toESM(require("react"), 1);
7722
+ var import_jsx_runtime22 = require("react/jsx-runtime");
7723
+ var dropIndicatorVariants = cva(
7724
+ "ov-gap-line pointer-events-none shrink-0 transition-opacity duration-150",
7725
+ {
7726
+ variants: {
7727
+ direction: {
7728
+ vertical: "h-6 w-[3px]",
7729
+ horizontal: "h-[3px] w-[200px]"
7730
+ },
7731
+ state: {
7732
+ default: "opacity-0",
7733
+ hover: "opacity-100",
7734
+ dragIdle: "opacity-40",
7735
+ dragActive: "opacity-100"
7736
+ }
7737
+ },
7738
+ defaultVariants: {
7739
+ direction: "vertical",
7740
+ state: "default"
7741
+ }
7742
+ }
7743
+ );
7744
+ var DropIndicator = React10.forwardRef(
7745
+ ({ className, direction, state, ...props }, ref) => {
7746
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
7747
+ "div",
7748
+ {
7749
+ ref,
7750
+ "data-slot": "drop-indicator",
7751
+ "data-direction": direction ?? "vertical",
7752
+ "data-state": state ?? "default",
7753
+ "aria-hidden": "true",
7754
+ className: cn(dropIndicatorVariants({ direction, state }), className),
7755
+ ...props
7756
+ }
7757
+ );
7758
+ }
7759
+ );
7760
+ DropIndicator.displayName = "DropIndicator";
7761
+
7762
+ // src/ui/badge.tsx
7763
+ var import_jsx_runtime23 = require("react/jsx-runtime");
7764
+ var badgeVariants = cva(
7765
+ "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",
7766
+ {
7767
+ variants: {
7768
+ variant: {
7769
+ default: "border-transparent bg-primary text-primary-foreground",
7770
+ secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
7771
+ destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
7772
+ outline: "text-foreground"
7773
+ }
7774
+ },
7775
+ defaultVariants: {
7776
+ variant: "default"
7777
+ }
7778
+ }
7779
+ );
7780
+ function Badge({ className, variant, ...props }) {
7781
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: cn(badgeVariants({ variant }), className), ...props });
7782
+ }
7783
+
7784
+ // src/OhhwellsBridge.tsx
7785
+ var import_lucide_react11 = require("lucide-react");
7786
+ var import_jsx_runtime24 = require("react/jsx-runtime");
7787
+ var PRIMARY2 = "#0885FE";
7788
+ var IMAGE_FADE_MS = 300;
7789
+ function runOpacityFade(el, onDone) {
7790
+ const anim = el.animate([{ opacity: 0 }, { opacity: 1 }], {
7791
+ duration: IMAGE_FADE_MS,
7792
+ easing: "ease",
7793
+ fill: "forwards"
7794
+ });
7795
+ let finished = false;
7796
+ const finish = () => {
7797
+ if (finished) return;
7798
+ finished = true;
7799
+ anim.cancel();
7800
+ el.style.opacity = "";
7801
+ onDone();
7802
+ };
7803
+ anim.finished.then(finish).catch(finish);
7804
+ window.setTimeout(finish, IMAGE_FADE_MS + 100);
7805
+ }
7806
+ function fadeInImageElement(img, onReady) {
7807
+ onReady();
7808
+ img.style.opacity = "0";
7809
+ runOpacityFade(img, () => {
7810
+ img.style.opacity = "";
7811
+ });
7812
+ }
7813
+ function applyEditableImageSrc(img, url) {
7814
+ img.removeAttribute("srcset");
7815
+ img.removeAttribute("sizes");
7816
+ img.src = url;
7817
+ }
7818
+ function fadeInBgImage(el, url, onReady) {
7819
+ const prevPos = el.style.position;
7820
+ if (!prevPos || prevPos === "static") el.style.position = "relative";
7821
+ const layer = document.createElement("div");
7822
+ layer.setAttribute("data-ohw-bg-fade-layer", "");
7823
+ Object.assign(layer.style, {
7824
+ position: "absolute",
7825
+ inset: "0",
7826
+ zIndex: "0",
7827
+ backgroundImage: `url('${url}')`,
7828
+ backgroundSize: "cover",
7829
+ backgroundPosition: "center",
7830
+ backgroundRepeat: "no-repeat",
7831
+ opacity: "0",
7832
+ pointerEvents: "none"
7833
+ });
7834
+ el.prepend(layer);
7835
+ onReady();
7836
+ runOpacityFade(layer, () => {
7837
+ el.style.backgroundImage = `url('${url}')`;
7838
+ layer.remove();
7839
+ if (!prevPos || prevPos === "static") el.style.position = prevPos;
7840
+ });
7841
+ }
7842
+ function getSectionsTracker() {
7843
+ let el = document.querySelector("[data-ohw-sections-tracker]");
7844
+ if (!el) {
6634
7845
  el = document.createElement("div");
6635
7846
  el.setAttribute("data-ohw-sections-tracker", "");
6636
7847
  el.style.display = "none";
@@ -6751,7 +7962,7 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
6751
7962
  const root = (0, import_client.createRoot)(container);
6752
7963
  (0, import_react_dom2.flushSync)(() => {
6753
7964
  root.render(
6754
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
7965
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
6755
7966
  SchedulingWidget,
6756
7967
  {
6757
7968
  notifyOnConnect,
@@ -6813,7 +8024,20 @@ function isDragHandleDisabled(el) {
6813
8024
  return raw === "true" || raw === "";
6814
8025
  });
6815
8026
  }
6816
- function collectEditableNodes() {
8027
+ function canDragNavigationItem(anchor) {
8028
+ const { key, disabled } = getNavigationItemReorderState(anchor);
8029
+ return Boolean(key) && !disabled;
8030
+ }
8031
+ function syncNavigationDragCursorAttrs() {
8032
+ document.querySelectorAll("nav [data-ohw-href-key], footer [data-ohw-href-key]").forEach((el) => {
8033
+ if (!isNavigationItem(el) || !canDragNavigationItem(el)) {
8034
+ el.removeAttribute("data-ohw-can-drag");
8035
+ return;
8036
+ }
8037
+ el.setAttribute("data-ohw-can-drag", "");
8038
+ });
8039
+ }
8040
+ function collectEditableNodes(extraContent) {
6817
8041
  const nodes = Array.from(document.querySelectorAll("[data-ohw-editable]")).map((el) => {
6818
8042
  if (el.dataset.ohwEditable === "image") {
6819
8043
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -6841,6 +8065,14 @@ function collectEditableNodes() {
6841
8065
  if (!key) return;
6842
8066
  nodes.push({ key, type: "link", text: getLinkHref2(el) });
6843
8067
  });
8068
+ if (extraContent) {
8069
+ for (const key of [NAV_ORDER_KEY, FOOTER_ORDER_KEY, NAV_COUNT_KEY]) {
8070
+ const text = extraContent[key];
8071
+ if (typeof text === "string" && text.length > 0) {
8072
+ nodes.push({ key, type: "meta", text });
8073
+ }
8074
+ }
8075
+ }
6844
8076
  document.querySelectorAll('[data-ohw-editable="video"]').forEach((el) => {
6845
8077
  const key = el.dataset.ohwKey ?? "";
6846
8078
  const video = getVideoEl(el);
@@ -6954,6 +8186,11 @@ function getNavigationItemAnchor(el) {
6954
8186
  function isNavigationItem(el) {
6955
8187
  return getNavigationItemAnchor(el) !== null;
6956
8188
  }
8189
+ function listNavigationItems() {
8190
+ return Array.from(
8191
+ document.querySelectorAll("nav [data-ohw-href-key], footer [data-ohw-href-key]")
8192
+ ).filter((el) => isNavigationItem(el));
8193
+ }
6957
8194
  function getNavigationItemReorderState(anchor) {
6958
8195
  if (isNavbarButton2(anchor)) return { key: null, disabled: false };
6959
8196
  return getReorderHandleStateFromAnchor(anchor);
@@ -6982,18 +8219,20 @@ function getNavigationRoot(el) {
6982
8219
  if (explicit) return explicit;
6983
8220
  return el.closest("nav, footer, aside");
6984
8221
  }
8222
+ function countFooterNavItems(el) {
8223
+ return Array.from(el.querySelectorAll("[data-ohw-href-key]")).filter(isNavigationItem).length;
8224
+ }
6985
8225
  function findFooterItemGroup(item) {
8226
+ const explicit = item.closest("[data-ohw-footer-col], [data-ohw-footer-column]");
8227
+ if (explicit) return explicit;
6986
8228
  const footer = item.closest("footer");
6987
8229
  if (!footer) return null;
6988
8230
  let node = item.parentElement;
6989
8231
  while (node && node !== footer) {
6990
- const hasDirectNavItems = Array.from(
6991
- node.querySelectorAll(":scope > [data-ohw-href-key]")
6992
- ).some(isNavigationItem);
6993
- if (hasDirectNavItems) return node;
8232
+ if (countFooterNavItems(node) >= 2) return node;
6994
8233
  node = node.parentElement;
6995
8234
  }
6996
- return null;
8235
+ return footer;
6997
8236
  }
6998
8237
  function isNavigationRoot(el) {
6999
8238
  return el.hasAttribute("data-ohw-nav-root") || el.matches("nav, footer, aside");
@@ -7001,16 +8240,49 @@ function isNavigationRoot(el) {
7001
8240
  function isInferredFooterGroup(el) {
7002
8241
  const footer = el.closest("footer");
7003
8242
  if (!footer || el === footer) return false;
7004
- return Array.from(el.querySelectorAll(":scope > [data-ohw-href-key]")).some(isNavigationItem);
8243
+ if (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column")) return true;
8244
+ return countFooterNavItems(el) >= 2;
7005
8245
  }
7006
8246
  function isNavigationContainer(el) {
7007
- return el.hasAttribute("data-ohw-nav-container") || isNavigationRoot(el) || isInferredFooterGroup(el);
8247
+ return el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isNavigationRoot(el) || isInferredFooterGroup(el);
8248
+ }
8249
+ function isNavbarLinksContainer(el) {
8250
+ return el.hasAttribute("data-ohw-nav-container");
8251
+ }
8252
+ function getFooterColumn(el) {
8253
+ return el.closest("[data-ohw-footer-col], [data-ohw-footer-column]");
8254
+ }
8255
+ function resolveFooterColumnSelectionTarget(target, clientX, clientY) {
8256
+ if (getNavigationItemAnchor(target)) return null;
8257
+ const column = getFooterColumn(target);
8258
+ if (!column) return null;
8259
+ if (isPointOverNavItem(column, clientX, clientY)) return null;
8260
+ return column;
7008
8261
  }
7009
8262
  function isPointOverNavigation(x, y) {
8263
+ const roots = /* @__PURE__ */ new Set();
7010
8264
  const navRoot = document.querySelector("[data-ohw-nav-root]") ?? document.querySelector("nav");
7011
- if (!navRoot) return false;
7012
- const r2 = navRoot.getBoundingClientRect();
7013
- return x >= r2.left && x <= r2.right && y >= r2.top && y <= r2.bottom;
8265
+ if (navRoot) roots.add(navRoot);
8266
+ const footer = document.querySelector("footer");
8267
+ if (footer) roots.add(footer);
8268
+ for (const root of roots) {
8269
+ const r2 = root.getBoundingClientRect();
8270
+ if (x >= r2.left && x <= r2.right && y >= r2.top && y <= r2.bottom) return true;
8271
+ }
8272
+ return false;
8273
+ }
8274
+ function findHoveredNavOrFooterContainer(x, y) {
8275
+ const candidates = [
8276
+ ...document.querySelectorAll("[data-ohw-nav-container]"),
8277
+ ...listFooterColumns()
8278
+ ];
8279
+ for (const container of candidates) {
8280
+ const r2 = container.getBoundingClientRect();
8281
+ if (x < r2.left || x > r2.right || y < r2.top || y > r2.bottom) continue;
8282
+ if (isPointOverNavItem(container, x, y)) continue;
8283
+ return container;
8284
+ }
8285
+ return null;
7014
8286
  }
7015
8287
  function isPointOverNavItem(container, x, y) {
7016
8288
  return Array.from(container.querySelectorAll("[data-ohw-href-key]")).some((el) => {
@@ -7043,36 +8315,63 @@ function getNavigationSelectionParent(el) {
7043
8315
  if (isNavigationItem(el)) {
7044
8316
  const explicit = el.closest("[data-ohw-nav-container]");
7045
8317
  if (explicit) return explicit;
8318
+ const footerColumn = getFooterColumn(el);
8319
+ if (footerColumn) return footerColumn;
7046
8320
  const footerGroup = findFooterItemGroup(el);
7047
8321
  if (footerGroup) return footerGroup;
7048
8322
  return getNavigationRoot(el);
7049
8323
  }
7050
- if (el.hasAttribute("data-ohw-nav-container") || isInferredFooterGroup(el)) {
8324
+ if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup(el)) {
7051
8325
  return getNavigationRoot(el);
7052
8326
  }
7053
8327
  return null;
7054
8328
  }
8329
+ function collectNavigationItemSiblingHintRects(selected) {
8330
+ if (!isNavigationItem(selected)) return [];
8331
+ const footerColumn = getFooterColumn(selected);
8332
+ if (footerColumn) {
8333
+ return listFooterLinksInColumn(footerColumn).filter((link) => link !== selected).map((link) => link.getBoundingClientRect());
8334
+ }
8335
+ const navContainer = selected.closest("[data-ohw-nav-container], [data-ohw-nav-drawer]");
8336
+ if (navContainer) {
8337
+ return Array.from(navContainer.querySelectorAll("[data-ohw-href-key]")).filter((el) => isNavigationItem(el) && el !== selected).map((el) => el.getBoundingClientRect());
8338
+ }
8339
+ return listNavigationItems().filter((el) => el !== selected).map((el) => el.getBoundingClientRect());
8340
+ }
7055
8341
  function placeCaretAtPoint(el, x, y) {
7056
8342
  const selection = window.getSelection();
7057
8343
  if (!selection) return;
7058
- if (typeof document.caretRangeFromPoint === "function") {
7059
- const range = document.caretRangeFromPoint(x, y);
7060
- if (range && el.contains(range.startContainer)) {
7061
- selection.removeAllRanges();
7062
- selection.addRange(range);
7063
- return;
8344
+ const overlays = Array.from(
8345
+ document.querySelectorAll(
8346
+ "[data-ohw-item-drag-surface], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-ohw-item-toolbar-anchor]"
8347
+ )
8348
+ );
8349
+ const prevPointerEvents = overlays.map((node) => node.style.pointerEvents);
8350
+ for (const node of overlays) node.style.pointerEvents = "none";
8351
+ try {
8352
+ if (typeof document.caretRangeFromPoint === "function") {
8353
+ const range = document.caretRangeFromPoint(x, y);
8354
+ if (range && el.contains(range.startContainer)) {
8355
+ selection.removeAllRanges();
8356
+ selection.addRange(range);
8357
+ return;
8358
+ }
7064
8359
  }
7065
- }
7066
- const caretPositionFromPoint = document.caretPositionFromPoint;
7067
- if (typeof caretPositionFromPoint === "function") {
7068
- const position = caretPositionFromPoint(x, y);
7069
- if (position && el.contains(position.offsetNode)) {
7070
- const range = document.createRange();
7071
- range.setStart(position.offsetNode, position.offset);
7072
- range.collapse(true);
7073
- selection.removeAllRanges();
7074
- selection.addRange(range);
8360
+ const caretPositionFromPoint = document.caretPositionFromPoint;
8361
+ if (typeof caretPositionFromPoint === "function") {
8362
+ const position = caretPositionFromPoint(x, y);
8363
+ if (position && el.contains(position.offsetNode)) {
8364
+ const range = document.createRange();
8365
+ range.setStart(position.offsetNode, position.offset);
8366
+ range.collapse(true);
8367
+ selection.removeAllRanges();
8368
+ selection.addRange(range);
8369
+ }
7075
8370
  }
8371
+ } finally {
8372
+ overlays.forEach((node, i) => {
8373
+ node.style.pointerEvents = prevPointerEvents[i] ?? "";
8374
+ });
7076
8375
  }
7077
8376
  }
7078
8377
  function selectAllTextInEditable(el) {
@@ -7214,6 +8513,9 @@ var ICONS = {
7214
8513
  insertUnorderedList: '<line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/>',
7215
8514
  insertOrderedList: '<line x1="10" y1="6" x2="21" y2="6"/><line x1="10" y1="12" x2="21" y2="12"/><line x1="10" y1="18" x2="21" y2="18"/><path d="M4 6h1v4"/><path d="M4 10h2"/><path d="M6 18H4c0-1 2-2 2-3s-1-1.5-2-1"/>'
7216
8515
  };
8516
+ var SELECTION_CHROME_GAP2 = 4;
8517
+ var TOOLBAR_STROKE_GAP2 = 4;
8518
+ var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
7217
8519
  var TOOLBAR_GROUPS = [
7218
8520
  [
7219
8521
  { cmd: "bold", title: "Bold" },
@@ -7235,10 +8537,11 @@ function EditGlowChrome({
7235
8537
  rect,
7236
8538
  elRef,
7237
8539
  reorderHrefKey,
7238
- dragDisabled = false
8540
+ dragDisabled = false,
8541
+ hideHandle = false
7239
8542
  }) {
7240
- const GAP = 6;
7241
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
8543
+ const GAP = SELECTION_CHROME_GAP2;
8544
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
7242
8545
  "div",
7243
8546
  {
7244
8547
  ref: elRef,
@@ -7253,7 +8556,7 @@ function EditGlowChrome({
7253
8556
  zIndex: 2147483646
7254
8557
  },
7255
8558
  children: [
7256
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8559
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
7257
8560
  "div",
7258
8561
  {
7259
8562
  style: {
@@ -7261,12 +8564,12 @@ function EditGlowChrome({
7261
8564
  inset: 0,
7262
8565
  border: "2px solid var(--ohw-primary, #0885FE)",
7263
8566
  borderRadius: 8,
7264
- boxShadow: "0 0 0 4px rgba(8, 133, 254, 0.12)",
8567
+ boxShadow: "0 0 0 4px color-mix(in srgb, var(--ohw-primary, #0885FE) 12%, transparent)",
7265
8568
  pointerEvents: "none"
7266
8569
  }
7267
8570
  }
7268
8571
  ),
7269
- reorderHrefKey && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8572
+ reorderHrefKey && !hideHandle && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
7270
8573
  "div",
7271
8574
  {
7272
8575
  "data-ohw-drag-handle-container": "",
@@ -7278,7 +8581,7 @@ function EditGlowChrome({
7278
8581
  transform: "translate(calc(-100% - 7px), -50%)",
7279
8582
  pointerEvents: dragDisabled ? "none" : "auto"
7280
8583
  },
7281
- children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8584
+ children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
7282
8585
  DragHandle,
7283
8586
  {
7284
8587
  "aria-label": `Reorder ${reorderHrefKey}`,
@@ -7309,6 +8612,7 @@ function applyVisibleViewport(el, parentScroll) {
7309
8612
  el.style.top = `${top}px`;
7310
8613
  el.style.height = `${height}px`;
7311
8614
  }
8615
+ var TOOLBAR_EDGE_MARGIN2 = 4;
7312
8616
  function clampToolbarToClip(top, transform, rect, clip, approxH, gap) {
7313
8617
  const isAbove = transform.includes("translateY(-100%)");
7314
8618
  let visualTop = isAbove ? top - approxH : top;
@@ -7332,10 +8636,16 @@ function clampToolbarToClip(top, transform, rect, clip, approxH, gap) {
7332
8636
  const pinnedTop = Math.min(maxBottom - approxH, Math.max(minTop, clip.top + gap));
7333
8637
  return { top: pinnedTop + approxH, transform: "translateX(-50%) translateY(-100%)" };
7334
8638
  }
7335
- function calcToolbarPos(rect, parentScroll, approxW = 306) {
7336
- const GAP = 8;
8639
+ function clampToolbarLeft(centerX, toolbarWidth) {
8640
+ const half = toolbarWidth / 2;
8641
+ return Math.max(
8642
+ TOOLBAR_EDGE_MARGIN2 + half,
8643
+ Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN2 - half)
8644
+ );
8645
+ }
8646
+ function calcToolbarPos(rect, parentScroll, toolbarW = 306) {
8647
+ const GAP = TOOLBAR_OFFSET_FROM_ELEMENT;
7337
8648
  const APPROX_H = 32;
7338
- const APPROX_W = approxW;
7339
8649
  const clip = getIframeVisibleClip(parentScroll);
7340
8650
  const clipTop = clip?.top ?? 0;
7341
8651
  const canvasTopInIframe = parentScroll != null ? parentScroll.headerH - parentScroll.iframeOffsetTop : 0;
@@ -7365,7 +8675,7 @@ function calcToolbarPos(rect, parentScroll, approxW = 306) {
7365
8675
  transform = clamped.transform;
7366
8676
  }
7367
8677
  const rawLeft = rect.left + rect.width / 2;
7368
- const left = Math.max(GAP + APPROX_W / 2, Math.min(rawLeft, window.innerWidth - GAP - APPROX_W / 2));
8678
+ const left = clampToolbarLeft(rawLeft, toolbarW);
7369
8679
  return { top, left, transform };
7370
8680
  }
7371
8681
  function resolveItemInteractionState(rect, parentScroll) {
@@ -7381,11 +8691,37 @@ function FloatingToolbar({
7381
8691
  showEditLink,
7382
8692
  onEditLink
7383
8693
  }) {
7384
- const { top, left, transform } = calcToolbarPos(rect, parentScroll, 330);
7385
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8694
+ const localRef = import_react9.default.useRef(null);
8695
+ const [measuredW, setMeasuredW] = import_react9.default.useState(330);
8696
+ const setRefs = import_react9.default.useCallback(
8697
+ (node) => {
8698
+ localRef.current = node;
8699
+ if (typeof elRef === "function") elRef(node);
8700
+ else if (elRef) elRef.current = node;
8701
+ if (node) {
8702
+ const w = node.offsetWidth;
8703
+ if (w > 0) setMeasuredW(w);
8704
+ }
8705
+ },
8706
+ [elRef]
8707
+ );
8708
+ import_react9.default.useLayoutEffect(() => {
8709
+ const node = localRef.current;
8710
+ if (!node) return;
8711
+ const update = () => {
8712
+ const w = node.offsetWidth;
8713
+ if (w > 0) setMeasuredW(w);
8714
+ };
8715
+ update();
8716
+ const ro = new ResizeObserver(update);
8717
+ ro.observe(node);
8718
+ return () => ro.disconnect();
8719
+ }, [showEditLink, activeCommands]);
8720
+ const { top, left, transform } = calcToolbarPos(rect, parentScroll, measuredW);
8721
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
7386
8722
  "div",
7387
8723
  {
7388
- ref: elRef,
8724
+ ref: setRefs,
7389
8725
  style: {
7390
8726
  position: "fixed",
7391
8727
  top,
@@ -7394,12 +8730,12 @@ function FloatingToolbar({
7394
8730
  zIndex: 2147483647,
7395
8731
  pointerEvents: "auto"
7396
8732
  },
7397
- children: /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(CustomToolbar, { children: [
7398
- TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_react8.default.Fragment, { children: [
7399
- gi > 0 && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(CustomToolbarDivider, {}),
8733
+ children: /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(CustomToolbar, { children: [
8734
+ TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_react9.default.Fragment, { children: [
8735
+ gi > 0 && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(CustomToolbarDivider, {}),
7400
8736
  btns.map((btn) => {
7401
8737
  const isActive = activeCommands.has(btn.cmd);
7402
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8738
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
7403
8739
  CustomToolbarButton,
7404
8740
  {
7405
8741
  title: btn.title,
@@ -7408,7 +8744,7 @@ function FloatingToolbar({
7408
8744
  e.preventDefault();
7409
8745
  onCommand(btn.cmd);
7410
8746
  },
7411
- children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8747
+ children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
7412
8748
  "svg",
7413
8749
  {
7414
8750
  width: "16",
@@ -7429,7 +8765,7 @@ function FloatingToolbar({
7429
8765
  );
7430
8766
  })
7431
8767
  ] }, gi)),
7432
- showEditLink ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8768
+ showEditLink ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
7433
8769
  CustomToolbarButton,
7434
8770
  {
7435
8771
  type: "button",
@@ -7443,7 +8779,7 @@ function FloatingToolbar({
7443
8779
  e.preventDefault();
7444
8780
  e.stopPropagation();
7445
8781
  },
7446
- children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_lucide_react11.Link, { className: "size-4 shrink-0", "aria-hidden": true })
8782
+ children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_lucide_react11.Link, { className: "size-4 shrink-0", "aria-hidden": true })
7447
8783
  }
7448
8784
  ) : null
7449
8785
  ] })
@@ -7460,7 +8796,7 @@ function StateToggle({
7460
8796
  states,
7461
8797
  onStateChange
7462
8798
  }) {
7463
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8799
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
7464
8800
  ToggleGroup,
7465
8801
  {
7466
8802
  "data-ohw-state-toggle": "",
@@ -7474,7 +8810,7 @@ function StateToggle({
7474
8810
  left: rect.right - 8,
7475
8811
  transform: "translateX(-100%)"
7476
8812
  },
7477
- children: states.map((state) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
8813
+ children: states.map((state) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
7478
8814
  }
7479
8815
  );
7480
8816
  }
@@ -7501,8 +8837,8 @@ function OhhwellsBridge() {
7501
8837
  const router = (0, import_navigation2.useRouter)();
7502
8838
  const searchParams = (0, import_navigation2.useSearchParams)();
7503
8839
  const isEditMode = isEditSessionActive();
7504
- const [bridgeRoot, setBridgeRoot] = (0, import_react8.useState)(null);
7505
- (0, import_react8.useEffect)(() => {
8840
+ const [bridgeRoot, setBridgeRoot] = (0, import_react9.useState)(null);
8841
+ (0, import_react9.useEffect)(() => {
7506
8842
  const figtreeFontId = "ohw-figtree-font";
7507
8843
  if (!document.getElementById(figtreeFontId)) {
7508
8844
  const preconnect1 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.googleapis.com" });
@@ -7530,91 +8866,136 @@ function OhhwellsBridge() {
7530
8866
  const subdomainFromQuery = searchParams.get("subdomain");
7531
8867
  const subdomain = resolveSubdomain(subdomainFromQuery);
7532
8868
  useLinkHrefGuardian(pathname, subdomain, isEditMode);
7533
- const postToParent2 = (0, import_react8.useCallback)((data) => {
8869
+ const postToParent2 = (0, import_react9.useCallback)((data) => {
7534
8870
  if (typeof window !== "undefined" && window.parent !== window) {
7535
8871
  window.parent.postMessage(data, "*");
7536
8872
  }
7537
8873
  }, []);
7538
- const [fetchState, setFetchState] = (0, import_react8.useState)("idle");
7539
- const autoSaveTimers = (0, import_react8.useRef)(/* @__PURE__ */ new Map());
7540
- const activeElRef = (0, import_react8.useRef)(null);
7541
- const selectedElRef = (0, import_react8.useRef)(null);
7542
- const originalContentRef = (0, import_react8.useRef)(null);
7543
- const activeStateElRef = (0, import_react8.useRef)(null);
7544
- const parentScrollRef = (0, import_react8.useRef)(null);
7545
- const visibleViewportRef = (0, import_react8.useRef)(null);
7546
- const [dialogPortalContainer, setDialogPortalContainer] = (0, import_react8.useState)(null);
7547
- const attachVisibleViewport = (0, import_react8.useCallback)((node) => {
8874
+ const [fetchState, setFetchState] = (0, import_react9.useState)("idle");
8875
+ const autoSaveTimers = (0, import_react9.useRef)(/* @__PURE__ */ new Map());
8876
+ const activeElRef = (0, import_react9.useRef)(null);
8877
+ const selectedElRef = (0, import_react9.useRef)(null);
8878
+ const selectedHrefKeyRef = (0, import_react9.useRef)(null);
8879
+ const selectedFooterColAttrRef = (0, import_react9.useRef)(null);
8880
+ const originalContentRef = (0, import_react9.useRef)(null);
8881
+ const activeStateElRef = (0, import_react9.useRef)(null);
8882
+ const parentScrollRef = (0, import_react9.useRef)(null);
8883
+ const visibleViewportRef = (0, import_react9.useRef)(null);
8884
+ const [dialogPortalContainer, setDialogPortalContainer] = (0, import_react9.useState)(null);
8885
+ const attachVisibleViewport = (0, import_react9.useCallback)((node) => {
7548
8886
  visibleViewportRef.current = node;
7549
8887
  setDialogPortalContainer(node);
7550
8888
  if (node) applyVisibleViewport(node, parentScrollRef.current);
7551
8889
  }, []);
7552
- const toolbarElRef = (0, import_react8.useRef)(null);
7553
- const glowElRef = (0, import_react8.useRef)(null);
7554
- const hoveredImageRef = (0, import_react8.useRef)(null);
7555
- const hoveredImageHasTextOverlapRef = (0, import_react8.useRef)(false);
7556
- const dragOverElRef = (0, import_react8.useRef)(null);
7557
- const [mediaHover, setMediaHover] = (0, import_react8.useState)(null);
7558
- const [uploadingRects, setUploadingRects] = (0, import_react8.useState)({});
7559
- const hoveredGapRef = (0, import_react8.useRef)(null);
7560
- const imageUnhoverTimerRef = (0, import_react8.useRef)(null);
7561
- const imageShowTimerRef = (0, import_react8.useRef)(null);
7562
- const editStylesRef = (0, import_react8.useRef)(null);
7563
- const activateRef = (0, import_react8.useRef)(() => {
8890
+ const toolbarElRef = (0, import_react9.useRef)(null);
8891
+ const glowElRef = (0, import_react9.useRef)(null);
8892
+ const hoveredImageRef = (0, import_react9.useRef)(null);
8893
+ const hoveredImageHasTextOverlapRef = (0, import_react9.useRef)(false);
8894
+ const dragOverElRef = (0, import_react9.useRef)(null);
8895
+ const [mediaHover, setMediaHover] = (0, import_react9.useState)(null);
8896
+ const [uploadingRects, setUploadingRects] = (0, import_react9.useState)({});
8897
+ const hoveredGapRef = (0, import_react9.useRef)(null);
8898
+ const imageUnhoverTimerRef = (0, import_react9.useRef)(null);
8899
+ const imageShowTimerRef = (0, import_react9.useRef)(null);
8900
+ const editStylesRef = (0, import_react9.useRef)(null);
8901
+ const activateRef = (0, import_react9.useRef)(() => {
7564
8902
  });
7565
- const deactivateRef = (0, import_react8.useRef)(() => {
8903
+ const deactivateRef = (0, import_react9.useRef)(() => {
7566
8904
  });
7567
- const selectRef = (0, import_react8.useRef)(() => {
8905
+ const selectRef = (0, import_react9.useRef)(() => {
7568
8906
  });
7569
- const selectFrameRef = (0, import_react8.useRef)(() => {
8907
+ const selectFrameRef = (0, import_react9.useRef)(() => {
7570
8908
  });
7571
- const deselectRef = (0, import_react8.useRef)(() => {
8909
+ const deselectRef = (0, import_react9.useRef)(() => {
7572
8910
  });
7573
- const reselectNavigationItemRef = (0, import_react8.useRef)(() => {
8911
+ const reselectNavigationItemRef = (0, import_react9.useRef)(() => {
7574
8912
  });
7575
- const commitNavigationTextEditRef = (0, import_react8.useRef)(() => {
8913
+ const commitNavigationTextEditRef = (0, import_react9.useRef)(() => {
7576
8914
  });
7577
- const refreshActiveCommandsRef = (0, import_react8.useRef)(() => {
8915
+ const refreshActiveCommandsRef = (0, import_react9.useRef)(() => {
7578
8916
  });
7579
- const postToParentRef = (0, import_react8.useRef)(postToParent2);
8917
+ const postToParentRef = (0, import_react9.useRef)(postToParent2);
7580
8918
  postToParentRef.current = postToParent2;
7581
- const sectionsLoadedRef = (0, import_react8.useRef)(false);
7582
- const pendingScheduleConfigRequests = (0, import_react8.useRef)([]);
7583
- const [toolbarRect, setToolbarRect] = (0, import_react8.useState)(null);
7584
- const [toolbarVariant, setToolbarVariant] = (0, import_react8.useState)("none");
7585
- const toolbarVariantRef = (0, import_react8.useRef)("none");
8919
+ const sectionsLoadedRef = (0, import_react9.useRef)(false);
8920
+ const pendingScheduleConfigRequests = (0, import_react9.useRef)([]);
8921
+ const [toolbarRect, setToolbarRect] = (0, import_react9.useState)(null);
8922
+ const [toolbarVariant, setToolbarVariant] = (0, import_react9.useState)("none");
8923
+ const toolbarVariantRef = (0, import_react9.useRef)("none");
7586
8924
  toolbarVariantRef.current = toolbarVariant;
7587
- const [reorderHrefKey, setReorderHrefKey] = (0, import_react8.useState)(null);
7588
- const [reorderDragDisabled, setReorderDragDisabled] = (0, import_react8.useState)(false);
7589
- const [toggleState, setToggleState] = (0, import_react8.useState)(null);
7590
- const [maxBadge, setMaxBadge] = (0, import_react8.useState)(null);
7591
- const [activeCommands, setActiveCommands] = (0, import_react8.useState)(/* @__PURE__ */ new Set());
7592
- const [sectionGap, setSectionGap] = (0, import_react8.useState)(null);
7593
- const [toolbarShowEditLink, setToolbarShowEditLink] = (0, import_react8.useState)(false);
7594
- const hoveredNavContainerRef = (0, import_react8.useRef)(null);
7595
- const [hoveredNavContainerRect, setHoveredNavContainerRect] = (0, import_react8.useState)(null);
7596
- const hoveredItemElRef = (0, import_react8.useRef)(null);
7597
- const [hoveredItemRect, setHoveredItemRect] = (0, import_react8.useState)(null);
7598
- const siblingHintElRef = (0, import_react8.useRef)(null);
7599
- const [siblingHintRect, setSiblingHintRect] = (0, import_react8.useState)(null);
7600
- const [isItemDragging, setIsItemDragging] = (0, import_react8.useState)(false);
7601
- const [linkPopover, setLinkPopover] = (0, import_react8.useState)(null);
7602
- const linkPopoverSessionRef = (0, import_react8.useRef)(null);
7603
- const addNavAfterAnchorRef = (0, import_react8.useRef)(null);
7604
- const editContentRef = (0, import_react8.useRef)({});
7605
- const [sitePages, setSitePages] = (0, import_react8.useState)([]);
7606
- const [sectionsByPath, setSectionsByPath] = (0, import_react8.useState)({});
7607
- const sectionsPrefetchGenRef = (0, import_react8.useRef)(0);
7608
- const setLinkPopoverRef = (0, import_react8.useRef)(setLinkPopover);
7609
- const linkPopoverPanelRef = (0, import_react8.useRef)(null);
7610
- const linkPopoverOpenRef = (0, import_react8.useRef)(false);
7611
- const linkPopoverGraceUntilRef = (0, import_react8.useRef)(0);
8925
+ const [selectedIsCta, setSelectedIsCta] = (0, import_react9.useState)(false);
8926
+ const [reorderHrefKey, setReorderHrefKey] = (0, import_react9.useState)(null);
8927
+ const [reorderDragDisabled, setReorderDragDisabled] = (0, import_react9.useState)(false);
8928
+ const [toggleState, setToggleState] = (0, import_react9.useState)(null);
8929
+ const [maxBadge, setMaxBadge] = (0, import_react9.useState)(null);
8930
+ const [activeCommands, setActiveCommands] = (0, import_react9.useState)(/* @__PURE__ */ new Set());
8931
+ const [sectionGap, setSectionGap] = (0, import_react9.useState)(null);
8932
+ const [toolbarShowEditLink, setToolbarShowEditLink] = (0, import_react9.useState)(false);
8933
+ const hoveredNavContainerRef = (0, import_react9.useRef)(null);
8934
+ const [hoveredNavContainerRect, setHoveredNavContainerRect] = (0, import_react9.useState)(null);
8935
+ const hoveredItemElRef = (0, import_react9.useRef)(null);
8936
+ const [hoveredItemRect, setHoveredItemRect] = (0, import_react9.useState)(null);
8937
+ const siblingHintElRef = (0, import_react9.useRef)(null);
8938
+ const [siblingHintRect, setSiblingHintRect] = (0, import_react9.useState)(null);
8939
+ const [siblingHintRects, setSiblingHintRects] = (0, import_react9.useState)([]);
8940
+ const [isItemDragging, setIsItemDragging] = (0, import_react9.useState)(false);
8941
+ const [isFooterFrameSelection, setIsFooterFrameSelection] = (0, import_react9.useState)(false);
8942
+ const footerDragRef = (0, import_react9.useRef)(null);
8943
+ const [footerDropSlots, setFooterDropSlots] = (0, import_react9.useState)([]);
8944
+ const [activeFooterDropIndex, setActiveFooterDropIndex] = (0, import_react9.useState)(null);
8945
+ const [draggedItemRect, setDraggedItemRect] = (0, import_react9.useState)(null);
8946
+ const footerPointerDragRef = (0, import_react9.useRef)(null);
8947
+ const suppressNextClickRef = (0, import_react9.useRef)(false);
8948
+ const suppressClickUntilRef = (0, import_react9.useRef)(0);
8949
+ const [linkPopover, setLinkPopover] = (0, import_react9.useState)(null);
8950
+ const linkPopoverSessionRef = (0, import_react9.useRef)(null);
8951
+ const addNavAfterAnchorRef = (0, import_react9.useRef)(null);
8952
+ const editContentRef = (0, import_react9.useRef)({});
8953
+ const [sitePages, setSitePages] = (0, import_react9.useState)([]);
8954
+ const [sectionsByPath, setSectionsByPath] = (0, import_react9.useState)({});
8955
+ const sectionsPrefetchGenRef = (0, import_react9.useRef)(0);
8956
+ const setLinkPopoverRef = (0, import_react9.useRef)(setLinkPopover);
8957
+ const linkPopoverPanelRef = (0, import_react9.useRef)(null);
8958
+ const linkPopoverOpenRef = (0, import_react9.useRef)(false);
8959
+ const linkPopoverGraceUntilRef = (0, import_react9.useRef)(0);
7612
8960
  setLinkPopoverRef.current = setLinkPopover;
7613
8961
  linkPopoverSessionRef.current = linkPopover;
8962
+ const {
8963
+ navDragRef,
8964
+ navDropSlots,
8965
+ activeNavDropIndex,
8966
+ startNavLinkDrag,
8967
+ commitNavDrag,
8968
+ onNavDragOver,
8969
+ armNavPressFromChrome,
8970
+ refreshNavDragVisualsRef
8971
+ } = useNavItemDrag({
8972
+ isEditMode,
8973
+ editContentRef,
8974
+ selectedElRef,
8975
+ activeElRef,
8976
+ footerDragRef,
8977
+ suppressNextClickRef,
8978
+ suppressClickUntilRef,
8979
+ siblingHintElRef,
8980
+ postToParentRef,
8981
+ deselectRef,
8982
+ selectRef,
8983
+ deactivateRef,
8984
+ setLinkPopover: () => setLinkPopover(null),
8985
+ linkPopoverOpenRef,
8986
+ setIsItemDragging,
8987
+ setDraggedItemRect,
8988
+ setSiblingHintRect,
8989
+ setSiblingHintRects,
8990
+ setToolbarRect,
8991
+ getNavigationItemAnchor,
8992
+ isDragHandleDisabled,
8993
+ isNavbarButton: isNavbarButton2
8994
+ });
7614
8995
  const bumpLinkPopoverGrace = () => {
7615
8996
  linkPopoverGraceUntilRef.current = Date.now() + 350;
7616
8997
  };
7617
- const runSectionsPrefetch = (0, import_react8.useCallback)((pages) => {
8998
+ const runSectionsPrefetch = (0, import_react9.useCallback)((pages) => {
7618
8999
  if (!isEditMode || shouldUseDevFixtures() || pages.length === 0) return;
7619
9000
  const gen = ++sectionsPrefetchGenRef.current;
7620
9001
  const paths = pages.map((p) => p.path);
@@ -7633,10 +9014,24 @@ function OhhwellsBridge() {
7633
9014
  );
7634
9015
  });
7635
9016
  }, [isEditMode, pathname]);
7636
- const runSectionsPrefetchRef = (0, import_react8.useRef)(runSectionsPrefetch);
9017
+ const runSectionsPrefetchRef = (0, import_react9.useRef)(runSectionsPrefetch);
7637
9018
  runSectionsPrefetchRef.current = runSectionsPrefetch;
7638
- (0, import_react8.useEffect)(() => {
7639
- if (!linkPopover) return;
9019
+ (0, import_react9.useEffect)(() => {
9020
+ if (!linkPopover) {
9021
+ document.documentElement.removeAttribute("data-ohw-link-popover-open");
9022
+ return;
9023
+ }
9024
+ document.documentElement.setAttribute("data-ohw-link-popover-open", "");
9025
+ hoveredItemElRef.current = null;
9026
+ setHoveredItemRect(null);
9027
+ hoveredNavContainerRef.current = null;
9028
+ setHoveredNavContainerRect(null);
9029
+ siblingHintElRef.current = null;
9030
+ setSiblingHintRect(null);
9031
+ setSiblingHintRects([]);
9032
+ if (selectedElRef.current || toolbarVariantRef.current !== "none") {
9033
+ deselectRef.current();
9034
+ }
7640
9035
  if (hoveredImageRef.current) {
7641
9036
  hoveredImageRef.current = null;
7642
9037
  hoveredImageHasTextOverlapRef.current = false;
@@ -7670,7 +9065,7 @@ function OhhwellsBridge() {
7670
9065
  document.removeEventListener("touchmove", preventBackgroundScroll, scrollOpts);
7671
9066
  };
7672
9067
  }, [linkPopover, postToParent2]);
7673
- (0, import_react8.useEffect)(() => {
9068
+ (0, import_react9.useEffect)(() => {
7674
9069
  if (!isEditMode) return;
7675
9070
  const useFixtures = shouldUseDevFixtures();
7676
9071
  if (useFixtures) {
@@ -7694,14 +9089,14 @@ function OhhwellsBridge() {
7694
9089
  if (!useFixtures) postToParent2({ type: "ow:request-site-pages" });
7695
9090
  return () => window.removeEventListener("message", onSitePages);
7696
9091
  }, [isEditMode, postToParent2]);
7697
- (0, import_react8.useEffect)(() => {
9092
+ (0, import_react9.useEffect)(() => {
7698
9093
  if (!isEditMode || shouldUseDevFixtures()) return;
7699
9094
  void loadAllSectionsManifest().then((manifest) => {
7700
9095
  if (Object.keys(manifest).length === 0) return;
7701
9096
  setSectionsByPath((prev) => ({ ...manifest, ...prev }));
7702
9097
  });
7703
9098
  }, [isEditMode]);
7704
- (0, import_react8.useEffect)(() => {
9099
+ (0, import_react9.useEffect)(() => {
7705
9100
  const update = () => {
7706
9101
  const el = activeElRef.current ?? selectedElRef.current;
7707
9102
  if (el) setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
@@ -7725,10 +9120,10 @@ function OhhwellsBridge() {
7725
9120
  vvp.removeEventListener("resize", update);
7726
9121
  };
7727
9122
  }, []);
7728
- const refreshStateRules = (0, import_react8.useCallback)(() => {
9123
+ const refreshStateRules = (0, import_react9.useCallback)(() => {
7729
9124
  editStylesRef.current?.forceHover && (editStylesRef.current.forceHover.textContent = collectStateRules());
7730
9125
  }, []);
7731
- const processConfigRequest = (0, import_react8.useCallback)((insertAfterVal) => {
9126
+ const processConfigRequest = (0, import_react9.useCallback)((insertAfterVal) => {
7732
9127
  const tracker = getSectionsTracker();
7733
9128
  let entries = [];
7734
9129
  try {
@@ -7751,7 +9146,7 @@ function OhhwellsBridge() {
7751
9146
  }
7752
9147
  window.postMessage({ type: "ow:schedule-config", insertAfter: insertAfterVal, scheduleId: null }, "*");
7753
9148
  }, [isEditMode]);
7754
- const deactivate = (0, import_react8.useCallback)(() => {
9149
+ const deactivate = (0, import_react9.useCallback)(() => {
7755
9150
  const el = activeElRef.current;
7756
9151
  if (!el) return;
7757
9152
  const key = el.dataset.ohwKey;
@@ -7770,6 +9165,7 @@ function OhhwellsBridge() {
7770
9165
  }
7771
9166
  }
7772
9167
  el.removeAttribute("contenteditable");
9168
+ el.removeAttribute("data-ohw-editing");
7773
9169
  activeElRef.current = null;
7774
9170
  setReorderHrefKey(null);
7775
9171
  setReorderDragDisabled(false);
@@ -7782,12 +9178,23 @@ function OhhwellsBridge() {
7782
9178
  setToolbarShowEditLink(false);
7783
9179
  postToParent2({ type: "ow:exit-edit" });
7784
9180
  }, [postToParent2]);
7785
- const deselect = (0, import_react8.useCallback)(() => {
9181
+ const clearSelectedAttr = (0, import_react9.useCallback)(() => {
9182
+ document.querySelectorAll("[data-ohw-selected]").forEach((el) => {
9183
+ el.removeAttribute("data-ohw-selected");
9184
+ });
9185
+ }, []);
9186
+ const deselect = (0, import_react9.useCallback)(() => {
9187
+ clearSelectedAttr();
7786
9188
  selectedElRef.current = null;
9189
+ selectedHrefKeyRef.current = null;
9190
+ selectedFooterColAttrRef.current = null;
9191
+ setSelectedIsCta(false);
7787
9192
  setReorderHrefKey(null);
7788
9193
  setReorderDragDisabled(false);
9194
+ setIsFooterFrameSelection(false);
7789
9195
  siblingHintElRef.current = null;
7790
9196
  setSiblingHintRect(null);
9197
+ setSiblingHintRects([]);
7791
9198
  setIsItemDragging(false);
7792
9199
  hoveredNavContainerRef.current = null;
7793
9200
  setHoveredNavContainerRect(null);
@@ -7795,18 +9202,70 @@ function OhhwellsBridge() {
7795
9202
  setToolbarRect(null);
7796
9203
  setToolbarVariant("none");
7797
9204
  }
9205
+ }, [clearSelectedAttr]);
9206
+ const markSelected = (0, import_react9.useCallback)((el) => {
9207
+ clearSelectedAttr();
9208
+ el.setAttribute("data-ohw-selected", "");
9209
+ }, [clearSelectedAttr]);
9210
+ const resolveHrefKeyElement = (0, import_react9.useCallback)((hrefKey) => {
9211
+ if (isFooterHrefKey(hrefKey)) {
9212
+ return document.querySelector(
9213
+ `footer [data-ohw-href-key="${CSS.escape(hrefKey)}"]`
9214
+ );
9215
+ }
9216
+ if (isNavbarHrefKey(hrefKey)) {
9217
+ const desktop = document.querySelector("[data-ohw-nav-container]");
9218
+ const drawer = document.querySelector("[data-ohw-nav-drawer]");
9219
+ return desktop?.querySelector(`[data-ohw-href-key="${CSS.escape(hrefKey)}"]`) ?? drawer?.querySelector(`[data-ohw-href-key="${CSS.escape(hrefKey)}"]`) ?? document.querySelector(`[data-ohw-href-key="${CSS.escape(hrefKey)}"]`);
9220
+ }
9221
+ return document.querySelector(
9222
+ `[data-ohw-href-key="${CSS.escape(hrefKey)}"]`
9223
+ );
7798
9224
  }, []);
7799
- const reselectNavigationItem = (0, import_react8.useCallback)((navAnchor) => {
9225
+ const resyncSelectedNavigationItem = (0, import_react9.useCallback)(() => {
9226
+ const hrefKey = selectedHrefKeyRef.current;
9227
+ if (hrefKey) {
9228
+ const link = resolveHrefKeyElement(hrefKey);
9229
+ if (!link || !isNavigationItem(link)) return;
9230
+ selectedElRef.current = link;
9231
+ const { key, disabled } = getNavigationItemReorderState(link);
9232
+ setReorderHrefKey(key);
9233
+ setReorderDragDisabled(disabled);
9234
+ setIsFooterFrameSelection(false);
9235
+ setToolbarVariant("link-action");
9236
+ setToolbarRect(link.getBoundingClientRect());
9237
+ setSiblingHintRects(collectNavigationItemSiblingHintRects(link));
9238
+ return;
9239
+ }
9240
+ const colAttr = selectedFooterColAttrRef.current;
9241
+ if (colAttr != null) {
9242
+ const column = document.querySelector(`[data-ohw-footer-col="${CSS.escape(colAttr)}"]`) ?? listFooterColumns()[Number(colAttr)] ?? null;
9243
+ if (!column || !isNavigationContainer(column)) return;
9244
+ selectedElRef.current = column;
9245
+ setIsFooterFrameSelection(true);
9246
+ setToolbarVariant("select-frame");
9247
+ setToolbarRect(column.getBoundingClientRect());
9248
+ setSiblingHintRects(
9249
+ listFooterColumns().filter((c) => c !== column).map((c) => c.getBoundingClientRect())
9250
+ );
9251
+ }
9252
+ }, [resolveHrefKeyElement]);
9253
+ const reselectNavigationItem = (0, import_react9.useCallback)((navAnchor) => {
7800
9254
  selectedElRef.current = navAnchor;
9255
+ selectedHrefKeyRef.current = navAnchor.getAttribute("data-ohw-href-key");
9256
+ selectedFooterColAttrRef.current = null;
9257
+ markSelected(navAnchor);
9258
+ setSelectedIsCta(isNavbarButton2(navAnchor));
7801
9259
  const { key, disabled } = getNavigationItemReorderState(navAnchor);
7802
9260
  setReorderHrefKey(key);
7803
9261
  setReorderDragDisabled(disabled);
7804
9262
  setToolbarVariant("link-action");
7805
9263
  setToolbarRect(navAnchor.getBoundingClientRect());
9264
+ setSiblingHintRects(collectNavigationItemSiblingHintRects(navAnchor));
7806
9265
  setToolbarShowEditLink(false);
7807
9266
  setActiveCommands(/* @__PURE__ */ new Set());
7808
- }, []);
7809
- const commitNavigationTextEdit = (0, import_react8.useCallback)((navAnchor) => {
9267
+ }, [markSelected]);
9268
+ const commitNavigationTextEdit = (0, import_react9.useCallback)((navAnchor) => {
7810
9269
  const el = activeElRef.current;
7811
9270
  if (!el) return;
7812
9271
  const key = el.dataset.ohwKey;
@@ -7825,6 +9284,7 @@ function OhhwellsBridge() {
7825
9284
  }
7826
9285
  }
7827
9286
  el.removeAttribute("contenteditable");
9287
+ el.removeAttribute("data-ohw-editing");
7828
9288
  activeElRef.current = null;
7829
9289
  setMaxBadge(null);
7830
9290
  setActiveCommands(/* @__PURE__ */ new Set());
@@ -7832,7 +9292,7 @@ function OhhwellsBridge() {
7832
9292
  postToParent2({ type: "ow:exit-edit" });
7833
9293
  reselectNavigationItem(navAnchor);
7834
9294
  }, [postToParent2, reselectNavigationItem]);
7835
- const handleAddTopLevelNavItem = (0, import_react8.useCallback)(() => {
9295
+ const handleAddTopLevelNavItem = (0, import_react9.useCallback)(() => {
7836
9296
  const items = listNavbarItems();
7837
9297
  addNavAfterAnchorRef.current = items[items.length - 1] ?? null;
7838
9298
  deselectRef.current();
@@ -7844,20 +9304,310 @@ function OhhwellsBridge() {
7844
9304
  intent: "add-nav"
7845
9305
  });
7846
9306
  }, []);
7847
- const handleItemDragStart = (0, import_react8.useCallback)(() => {
7848
- siblingHintElRef.current = null;
7849
- setSiblingHintRect(null);
7850
- setIsItemDragging(true);
7851
- }, []);
7852
- const handleItemDragEnd = (0, import_react8.useCallback)(() => {
9307
+ const clearFooterDragVisuals = (0, import_react9.useCallback)(() => {
9308
+ footerDragRef.current = null;
9309
+ setSiblingHintRects([]);
9310
+ setFooterDropSlots([]);
9311
+ setActiveFooterDropIndex(null);
9312
+ setDraggedItemRect(null);
7853
9313
  setIsItemDragging(false);
9314
+ unlockFooterDragInteraction();
9315
+ }, []);
9316
+ const refreshFooterDragVisuals = (0, import_react9.useCallback)((session, activeSlot, clientX, clientY) => {
9317
+ const dragged = session.draggedEl;
9318
+ setDraggedItemRect(dragged.getBoundingClientRect());
9319
+ if (typeof clientX === "number" && typeof clientY === "number") {
9320
+ session.lastClientX = clientX;
9321
+ session.lastClientY = clientY;
9322
+ }
9323
+ session.activeSlot = activeSlot;
9324
+ setSiblingHintRects([]);
9325
+ if (session.kind === "link") {
9326
+ const columns = listFooterColumns();
9327
+ const slots2 = [];
9328
+ columns.forEach((col, i) => {
9329
+ slots2.push(...buildLinkDropSlots(col, i));
9330
+ });
9331
+ setFooterDropSlots(slots2);
9332
+ const activeIdx2 = activeSlot ? slots2.findIndex((s) => s.columnIndex === activeSlot.columnIndex && s.insertIndex === activeSlot.insertIndex) : -1;
9333
+ setActiveFooterDropIndex(activeIdx2 >= 0 ? activeIdx2 : null);
9334
+ return;
9335
+ }
9336
+ const slots = buildColumnDropSlots();
9337
+ setFooterDropSlots(slots);
9338
+ const activeIdx = activeSlot ? slots.findIndex((s) => s.insertIndex === activeSlot.insertIndex) : -1;
9339
+ setActiveFooterDropIndex(activeIdx >= 0 ? activeIdx : null);
9340
+ }, []);
9341
+ const refreshFooterDragVisualsRef = (0, import_react9.useRef)(refreshFooterDragVisuals);
9342
+ refreshFooterDragVisualsRef.current = refreshFooterDragVisuals;
9343
+ const commitFooterDragRef = (0, import_react9.useRef)(() => {
9344
+ });
9345
+ const beginFooterDragRef = (0, import_react9.useRef)(() => {
9346
+ });
9347
+ const beginFooterDrag = (0, import_react9.useCallback)(
9348
+ (session) => {
9349
+ const rect = session.draggedEl.getBoundingClientRect();
9350
+ session.lastClientX = session.lastClientX || rect.left + rect.width / 2;
9351
+ session.lastClientY = session.lastClientY || rect.top + rect.height / 2;
9352
+ session.activeSlot = session.activeSlot ?? null;
9353
+ footerDragRef.current = session;
9354
+ setIsItemDragging(true);
9355
+ lockFooterDuringDrag();
9356
+ siblingHintElRef.current = null;
9357
+ setSiblingHintRect(null);
9358
+ if (session.wasSelected && selectedElRef.current === session.draggedEl) {
9359
+ setToolbarRect(rect);
9360
+ }
9361
+ const initialSlot = session.kind === "link" && session.hrefKey ? hitTestLinkDropSlot(session.lastClientX, session.lastClientY, session.hrefKey) : hitTestColumnDropSlot(session.lastClientX, session.lastClientY);
9362
+ refreshFooterDragVisuals(session, initialSlot, session.lastClientX, session.lastClientY);
9363
+ },
9364
+ [refreshFooterDragVisuals]
9365
+ );
9366
+ beginFooterDragRef.current = beginFooterDrag;
9367
+ const commitFooterDrag = (0, import_react9.useCallback)(
9368
+ (clientX, clientY) => {
9369
+ const session = footerDragRef.current;
9370
+ if (!session) {
9371
+ clearFooterDragVisuals();
9372
+ return;
9373
+ }
9374
+ const x = typeof clientX === "number" && (clientX !== 0 || clientY !== 0) ? clientX : session.lastClientX;
9375
+ const y = typeof clientY === "number" && (clientX !== 0 || clientY !== 0) ? clientY : session.lastClientY;
9376
+ let nextOrder = null;
9377
+ const slot = session.activeSlot ?? (session.kind === "link" && session.hrefKey ? hitTestLinkDropSlot(x, y, session.hrefKey) : session.kind === "column" ? hitTestColumnDropSlot(x, y) : null);
9378
+ if (session.kind === "link" && session.hrefKey && slot) {
9379
+ nextOrder = planFooterLinkMove(session.hrefKey, slot.columnIndex, slot.insertIndex);
9380
+ } else if (session.kind === "column" && slot) {
9381
+ nextOrder = planFooterColumnMove(session.sourceColumnIndex, slot.insertIndex);
9382
+ }
9383
+ const wasSelected = session.wasSelected;
9384
+ const draggedEl = session.draggedEl;
9385
+ const hrefKey = session.hrefKey;
9386
+ let movedColumnIndex = null;
9387
+ if (session.kind === "column" && slot && nextOrder) {
9388
+ let adjusted = slot.insertIndex;
9389
+ if (session.sourceColumnIndex < slot.insertIndex) adjusted -= 1;
9390
+ movedColumnIndex = Math.max(0, Math.min(adjusted, nextOrder.length - 1));
9391
+ }
9392
+ const applySelectionAfterDrop = () => {
9393
+ if (!wasSelected) {
9394
+ deselectRef.current();
9395
+ return;
9396
+ }
9397
+ if (hrefKey) {
9398
+ selectedHrefKeyRef.current = hrefKey;
9399
+ selectedFooterColAttrRef.current = null;
9400
+ const link = resolveHrefKeyElement(hrefKey);
9401
+ if (link && isNavigationItem(link)) {
9402
+ selectRef.current(link);
9403
+ return;
9404
+ }
9405
+ }
9406
+ if (movedColumnIndex !== null && nextOrder) {
9407
+ const colKeys = nextOrder[movedColumnIndex] ?? [];
9408
+ let column = null;
9409
+ for (const key of colKeys) {
9410
+ const link = document.querySelector(
9411
+ `footer [data-ohw-href-key="${CSS.escape(key)}"]`
9412
+ );
9413
+ if (link) {
9414
+ column = findFooterColumnForLink(link);
9415
+ if (column) break;
9416
+ }
9417
+ }
9418
+ if (!column) column = listFooterColumns()[movedColumnIndex] ?? null;
9419
+ if (column && isNavigationContainer(column)) {
9420
+ selectFrameRef.current(column);
9421
+ return;
9422
+ }
9423
+ }
9424
+ if (document.body.contains(draggedEl)) {
9425
+ if (isNavigationItem(draggedEl)) {
9426
+ selectRef.current(draggedEl);
9427
+ return;
9428
+ }
9429
+ if (isNavigationContainer(draggedEl)) {
9430
+ selectFrameRef.current(draggedEl);
9431
+ return;
9432
+ }
9433
+ }
9434
+ deselectRef.current();
9435
+ };
9436
+ if (nextOrder) {
9437
+ const orderJson = JSON.stringify(nextOrder);
9438
+ editContentRef.current = {
9439
+ ...editContentRef.current,
9440
+ [FOOTER_ORDER_KEY]: orderJson
9441
+ };
9442
+ applyFooterOrder(nextOrder);
9443
+ document.querySelectorAll("footer [data-ohw-href-key]").forEach(disableNativeHrefDrag);
9444
+ postToParentRef.current({
9445
+ type: "ow:change",
9446
+ nodes: [{ key: FOOTER_ORDER_KEY, text: orderJson }]
9447
+ });
9448
+ applySelectionAfterDrop();
9449
+ clearFooterDragVisuals();
9450
+ requestAnimationFrame(() => {
9451
+ if (editContentRef.current[FOOTER_ORDER_KEY] === orderJson) {
9452
+ applyFooterOrder(nextOrder);
9453
+ document.querySelectorAll("footer [data-ohw-href-key]").forEach(disableNativeHrefDrag);
9454
+ }
9455
+ applySelectionAfterDrop();
9456
+ requestAnimationFrame(() => {
9457
+ if (editContentRef.current[FOOTER_ORDER_KEY] === orderJson) {
9458
+ applyFooterOrder(nextOrder);
9459
+ }
9460
+ resyncSelectedNavigationItem();
9461
+ });
9462
+ });
9463
+ return;
9464
+ }
9465
+ applySelectionAfterDrop();
9466
+ clearFooterDragVisuals();
9467
+ },
9468
+ [clearFooterDragVisuals, resolveHrefKeyElement, resyncSelectedNavigationItem]
9469
+ );
9470
+ commitFooterDragRef.current = commitFooterDrag;
9471
+ const startFooterLinkDrag = (0, import_react9.useCallback)(
9472
+ (anchor, clientX, clientY, wasSelected) => {
9473
+ const hrefKey = anchor.getAttribute("data-ohw-href-key");
9474
+ if (!hrefKey || !isFooterHrefKey(hrefKey)) return false;
9475
+ const column = findFooterColumnForLink(anchor);
9476
+ const columns = listFooterColumns();
9477
+ beginFooterDrag({
9478
+ kind: "link",
9479
+ hrefKey,
9480
+ columnEl: column,
9481
+ sourceColumnIndex: column ? columns.indexOf(column) : 0,
9482
+ wasSelected,
9483
+ draggedEl: anchor,
9484
+ lastClientX: clientX,
9485
+ lastClientY: clientY,
9486
+ activeSlot: null
9487
+ });
9488
+ return true;
9489
+ },
9490
+ [beginFooterDrag]
9491
+ );
9492
+ const startFooterColumnDrag = (0, import_react9.useCallback)(
9493
+ (columnEl, clientX, clientY, wasSelected) => {
9494
+ const columns = listFooterColumns();
9495
+ const idx = columns.indexOf(columnEl);
9496
+ if (idx < 0) return false;
9497
+ beginFooterDrag({
9498
+ kind: "column",
9499
+ hrefKey: null,
9500
+ columnEl,
9501
+ sourceColumnIndex: idx,
9502
+ wasSelected,
9503
+ draggedEl: columnEl,
9504
+ lastClientX: clientX,
9505
+ lastClientY: clientY,
9506
+ activeSlot: null
9507
+ });
9508
+ return true;
9509
+ },
9510
+ [beginFooterDrag]
9511
+ );
9512
+ const handleItemDragStart = (0, import_react9.useCallback)(
9513
+ (e) => {
9514
+ const selected = selectedElRef.current;
9515
+ if (!selected) {
9516
+ setIsItemDragging(true);
9517
+ return;
9518
+ }
9519
+ const clientX = e?.clientX ?? selected.getBoundingClientRect().left + 8;
9520
+ const clientY = e?.clientY ?? selected.getBoundingClientRect().top + 8;
9521
+ if (startFooterLinkDrag(selected, clientX, clientY, true)) return;
9522
+ if (selected.hasAttribute("data-ohw-footer-col") || isInferredFooterGroup(selected) && selected.closest("footer")) {
9523
+ if (startFooterColumnDrag(selected, clientX, clientY, true)) return;
9524
+ }
9525
+ if (startNavLinkDrag(selected, clientX, clientY, true)) return;
9526
+ siblingHintElRef.current = null;
9527
+ setSiblingHintRect(null);
9528
+ setIsItemDragging(true);
9529
+ },
9530
+ [startFooterColumnDrag, startFooterLinkDrag, startNavLinkDrag]
9531
+ );
9532
+ const handleItemDragEnd = (0, import_react9.useCallback)(
9533
+ (e) => {
9534
+ if (footerDragRef.current) {
9535
+ const x = e?.clientX;
9536
+ const y = e?.clientY;
9537
+ if (typeof x === "number" && typeof y === "number" && (x !== 0 || y !== 0)) {
9538
+ commitFooterDrag(x, y);
9539
+ } else {
9540
+ commitFooterDrag();
9541
+ }
9542
+ return;
9543
+ }
9544
+ if (navDragRef.current) {
9545
+ const x = e?.clientX;
9546
+ const y = e?.clientY;
9547
+ if (typeof x === "number" && typeof y === "number" && (x !== 0 || y !== 0)) {
9548
+ commitNavDrag(x, y);
9549
+ } else {
9550
+ commitNavDrag();
9551
+ }
9552
+ return;
9553
+ }
9554
+ setIsItemDragging(false);
9555
+ },
9556
+ [commitFooterDrag, commitNavDrag, navDragRef]
9557
+ );
9558
+ const handleItemChromePointerDown = (0, import_react9.useCallback)((e) => {
9559
+ if (e.button !== 0) return;
9560
+ const selected = selectedElRef.current;
9561
+ if (!selected) return;
9562
+ armFooterPressDrag();
9563
+ const hrefKey = selected.getAttribute("data-ohw-href-key");
9564
+ if (hrefKey && isFooterHrefKey(hrefKey)) {
9565
+ footerPointerDragRef.current = {
9566
+ el: selected,
9567
+ kind: "link",
9568
+ startX: e.clientX,
9569
+ startY: e.clientY,
9570
+ pointerId: e.pointerId,
9571
+ wasSelected: true,
9572
+ started: false
9573
+ };
9574
+ return;
9575
+ }
9576
+ if (selected.hasAttribute("data-ohw-footer-col") || isInferredFooterGroup(selected) && selected.closest("footer")) {
9577
+ footerPointerDragRef.current = {
9578
+ el: selected,
9579
+ kind: "column",
9580
+ startX: e.clientX,
9581
+ startY: e.clientY,
9582
+ pointerId: e.pointerId,
9583
+ wasSelected: true,
9584
+ started: false
9585
+ };
9586
+ return;
9587
+ }
9588
+ if (armNavPressFromChrome(selected, e.clientX, e.clientY, e.pointerId)) return;
9589
+ }, [armNavPressFromChrome]);
9590
+ const handleItemChromeClick = (0, import_react9.useCallback)((clientX, clientY) => {
9591
+ if (suppressNextClickRef.current || Date.now() < suppressClickUntilRef.current) {
9592
+ suppressNextClickRef.current = false;
9593
+ return;
9594
+ }
9595
+ const selected = selectedElRef.current;
9596
+ if (!selected || !isNavigationItem(selected)) return;
9597
+ const editable = selected.querySelector('[data-ohw-editable="text"]') ?? selected.querySelector("[data-ohw-editable]");
9598
+ if (!editable) return;
9599
+ activateRef.current(editable, { caretX: clientX, caretY: clientY });
7854
9600
  }, []);
7855
9601
  reselectNavigationItemRef.current = reselectNavigationItem;
7856
9602
  commitNavigationTextEditRef.current = commitNavigationTextEdit;
7857
- const select = (0, import_react8.useCallback)((anchor) => {
9603
+ const select = (0, import_react9.useCallback)((anchor) => {
7858
9604
  if (!isNavigationItem(anchor)) return;
7859
9605
  if (activeElRef.current) deactivate();
7860
9606
  selectedElRef.current = anchor;
9607
+ selectedHrefKeyRef.current = anchor.getAttribute("data-ohw-href-key");
9608
+ selectedFooterColAttrRef.current = null;
9609
+ markSelected(anchor);
9610
+ setSelectedIsCta(isNavbarButton2(anchor));
7861
9611
  clearHrefKeyHover(anchor);
7862
9612
  hoveredNavContainerRef.current = null;
7863
9613
  setHoveredNavContainerRect(null);
@@ -7865,19 +9615,26 @@ function OhhwellsBridge() {
7865
9615
  hoveredItemElRef.current = null;
7866
9616
  siblingHintElRef.current = null;
7867
9617
  setSiblingHintRect(null);
9618
+ setSiblingHintRects(collectNavigationItemSiblingHintRects(anchor));
7868
9619
  setIsItemDragging(false);
7869
9620
  const { key, disabled } = getNavigationItemReorderState(anchor);
7870
9621
  setReorderHrefKey(key);
7871
9622
  setReorderDragDisabled(disabled);
9623
+ setIsFooterFrameSelection(false);
7872
9624
  setToolbarVariant("link-action");
7873
9625
  setToolbarRect(anchor.getBoundingClientRect());
7874
9626
  setToolbarShowEditLink(false);
7875
9627
  setActiveCommands(/* @__PURE__ */ new Set());
7876
- }, [deactivate]);
7877
- const selectFrame = (0, import_react8.useCallback)((el) => {
9628
+ }, [deactivate, markSelected]);
9629
+ const selectFrame = (0, import_react9.useCallback)((el) => {
7878
9630
  if (!isNavigationContainer(el)) return;
7879
9631
  if (activeElRef.current) deactivate();
9632
+ const isFooterColumn = el.hasAttribute("data-ohw-footer-col") || Boolean(el.closest("footer") && isInferredFooterGroup(el));
7880
9633
  selectedElRef.current = el;
9634
+ selectedHrefKeyRef.current = null;
9635
+ selectedFooterColAttrRef.current = isFooterColumn ? el.getAttribute("data-ohw-footer-col") ?? String(listFooterColumns().indexOf(el)) : null;
9636
+ markSelected(el);
9637
+ setSelectedIsCta(false);
7881
9638
  clearHrefKeyHover(el);
7882
9639
  hoveredNavContainerRef.current = null;
7883
9640
  setHoveredNavContainerRect(null);
@@ -7885,17 +9642,25 @@ function OhhwellsBridge() {
7885
9642
  hoveredItemElRef.current = null;
7886
9643
  siblingHintElRef.current = null;
7887
9644
  setSiblingHintRect(null);
9645
+ setSiblingHintRects(
9646
+ isFooterColumn ? listFooterColumns().filter((column) => column !== el).map((column) => column.getBoundingClientRect()) : []
9647
+ );
7888
9648
  setIsItemDragging(false);
7889
9649
  setReorderHrefKey(null);
7890
9650
  setReorderDragDisabled(false);
9651
+ setIsFooterFrameSelection(isFooterColumn);
7891
9652
  setToolbarVariant("select-frame");
7892
9653
  setToolbarRect(el.getBoundingClientRect());
7893
9654
  setToolbarShowEditLink(false);
7894
9655
  setActiveCommands(/* @__PURE__ */ new Set());
7895
- }, [deactivate]);
7896
- const activate = (0, import_react8.useCallback)((el, options) => {
9656
+ }, [deactivate, markSelected]);
9657
+ const activate = (0, import_react9.useCallback)((el, options) => {
7897
9658
  if (activeElRef.current === el) return;
9659
+ clearSelectedAttr();
7898
9660
  selectedElRef.current = null;
9661
+ selectedHrefKeyRef.current = null;
9662
+ selectedFooterColAttrRef.current = null;
9663
+ setSelectedIsCta(false);
7899
9664
  deactivate();
7900
9665
  if (hoveredImageRef.current) {
7901
9666
  hoveredImageRef.current = null;
@@ -7906,13 +9671,27 @@ function OhhwellsBridge() {
7906
9671
  setSiblingHintRect(null);
7907
9672
  setIsItemDragging(false);
7908
9673
  el.setAttribute("contenteditable", "true");
9674
+ el.setAttribute("data-ohw-editing", "");
7909
9675
  el.removeAttribute("data-ohw-hovered");
7910
9676
  el.closest("[data-ohw-href-key]")?.removeAttribute("data-ohw-hovered");
7911
9677
  activeElRef.current = el;
7912
9678
  originalContentRef.current = el.innerHTML;
7913
- el.focus();
9679
+ el.focus({ preventScroll: true });
7914
9680
  if (options?.caretX !== void 0 && options?.caretY !== void 0) {
7915
- placeCaretAtPoint(el, options.caretX, options.caretY);
9681
+ const { caretX, caretY } = options;
9682
+ placeCaretAtPoint(el, caretX, caretY);
9683
+ requestAnimationFrame(() => {
9684
+ if (activeElRef.current === el) placeCaretAtPoint(el, caretX, caretY);
9685
+ });
9686
+ } else {
9687
+ const selection = window.getSelection();
9688
+ if (selection && selection.rangeCount === 0) {
9689
+ const range = document.createRange();
9690
+ range.selectNodeContents(el);
9691
+ range.collapse(false);
9692
+ selection.removeAllRanges();
9693
+ selection.addRange(range);
9694
+ }
7916
9695
  }
7917
9696
  setToolbarShowEditLink(Boolean(getHrefKeyFromElement(el)));
7918
9697
  const navAnchor = getNavigationItemAnchor(el);
@@ -7927,13 +9706,13 @@ function OhhwellsBridge() {
7927
9706
  setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
7928
9707
  postToParent2({ type: "ow:enter-edit", key: el.dataset.ohwKey });
7929
9708
  requestAnimationFrame(() => refreshActiveCommandsRef.current());
7930
- }, [deactivate, postToParent2]);
9709
+ }, [clearSelectedAttr, deactivate, postToParent2]);
7931
9710
  activateRef.current = activate;
7932
9711
  deactivateRef.current = deactivate;
7933
9712
  selectRef.current = select;
7934
9713
  selectFrameRef.current = selectFrame;
7935
9714
  deselectRef.current = deselect;
7936
- (0, import_react8.useLayoutEffect)(() => {
9715
+ (0, import_react9.useLayoutEffect)(() => {
7937
9716
  if (!subdomain || isEditMode) {
7938
9717
  setFetchState("done");
7939
9718
  return;
@@ -7974,6 +9753,7 @@ function OhhwellsBridge() {
7974
9753
  applyLinkByKey(key, val);
7975
9754
  }
7976
9755
  reconcileNavbarItemsFromContent(content);
9756
+ reconcileFooterOrderFromContent(content);
7977
9757
  enforceLinkHrefs();
7978
9758
  initSectionsFromContent(content, true);
7979
9759
  sectionsLoadedRef.current = true;
@@ -8002,7 +9782,7 @@ function OhhwellsBridge() {
8002
9782
  cancelled = true;
8003
9783
  };
8004
9784
  }, [subdomain, isEditMode]);
8005
- (0, import_react8.useEffect)(() => {
9785
+ (0, import_react9.useEffect)(() => {
8006
9786
  if (!subdomain || isEditMode) return;
8007
9787
  let debounceTimer = null;
8008
9788
  let observer = null;
@@ -8034,6 +9814,7 @@ function OhhwellsBridge() {
8034
9814
  applyLinkByKey(key, val);
8035
9815
  }
8036
9816
  reconcileNavbarItemsFromContent(content);
9817
+ reconcileFooterOrderFromContent(content);
8037
9818
  } finally {
8038
9819
  observer?.observe(document.body, { childList: true, subtree: true });
8039
9820
  }
@@ -8051,16 +9832,16 @@ function OhhwellsBridge() {
8051
9832
  if (debounceTimer) clearTimeout(debounceTimer);
8052
9833
  };
8053
9834
  }, [subdomain, isEditMode, pathname]);
8054
- (0, import_react8.useLayoutEffect)(() => {
9835
+ (0, import_react9.useLayoutEffect)(() => {
8055
9836
  const el = document.getElementById("ohw-loader");
8056
9837
  if (!el) return;
8057
9838
  const visible = Boolean(subdomain) && fetchState !== "done";
8058
9839
  el.style.display = visible ? "flex" : "none";
8059
9840
  }, [subdomain, fetchState]);
8060
- (0, import_react8.useEffect)(() => {
9841
+ (0, import_react9.useEffect)(() => {
8061
9842
  postToParent2({ type: "ow:navigation", path: pathname });
8062
9843
  }, [pathname, postToParent2]);
8063
- (0, import_react8.useEffect)(() => {
9844
+ (0, import_react9.useEffect)(() => {
8064
9845
  if (!isEditMode) return;
8065
9846
  if (linkPopoverSessionRef.current?.intent === "add-nav") return;
8066
9847
  if (document.querySelector("[data-ohw-section-picker]")) return;
@@ -8068,23 +9849,72 @@ function OhhwellsBridge() {
8068
9849
  deselectRef.current();
8069
9850
  deactivateRef.current();
8070
9851
  }, [pathname, isEditMode]);
8071
- (0, import_react8.useEffect)(() => {
9852
+ (0, import_react9.useEffect)(() => {
8072
9853
  const contentForNav = () => {
8073
9854
  if (isEditMode) return editContentRef.current;
8074
9855
  if (!subdomain) return {};
8075
9856
  return contentCache.get(subdomain) ?? {};
8076
9857
  };
8077
- const run = () => reconcileNavbarItemsFromContent(contentForNav());
9858
+ const observeRoots = () => [document.querySelector("nav"), document.querySelector("footer")].filter(
9859
+ (el) => Boolean(el)
9860
+ );
9861
+ const roots = observeRoots();
9862
+ if (roots.length === 0) {
9863
+ reconcileNavbarItemsFromContent(contentForNav());
9864
+ reconcileFooterOrderFromContent(contentForNav());
9865
+ if (isEditMode) syncNavigationDragCursorAttrs();
9866
+ return;
9867
+ }
9868
+ let rafId = null;
9869
+ let applying = false;
9870
+ let observer = null;
9871
+ const attach = () => {
9872
+ for (const root of observeRoots()) {
9873
+ observer?.observe(root, { childList: true, subtree: true });
9874
+ }
9875
+ };
9876
+ const run = () => {
9877
+ if (footerDragRef.current || navDragRef.current || applying) return;
9878
+ applying = true;
9879
+ observer?.disconnect();
9880
+ try {
9881
+ const content = contentForNav();
9882
+ reconcileNavbarItemsFromContent(content);
9883
+ reconcileFooterOrderFromContent(content);
9884
+ document.querySelectorAll("footer [data-ohw-href-key]").forEach((el) => {
9885
+ if (isFooterHrefKey(el.getAttribute("data-ohw-href-key"))) {
9886
+ disableNativeHrefDrag(el);
9887
+ }
9888
+ });
9889
+ document.querySelectorAll(
9890
+ "nav [data-ohw-href-key], [data-ohw-nav-container] [data-ohw-href-key], [data-ohw-nav-drawer] [data-ohw-href-key]"
9891
+ ).forEach((el) => {
9892
+ if (isNavbarHrefKey(el.getAttribute("data-ohw-href-key"))) {
9893
+ disableNativeHrefDrag(el);
9894
+ }
9895
+ });
9896
+ resyncSelectedNavigationItem();
9897
+ if (isEditMode) syncNavigationDragCursorAttrs();
9898
+ } finally {
9899
+ applying = false;
9900
+ attach();
9901
+ }
9902
+ };
9903
+ const schedule = () => {
9904
+ if (applying || rafId != null) return;
9905
+ rafId = requestAnimationFrame(() => {
9906
+ rafId = null;
9907
+ run();
9908
+ });
9909
+ };
9910
+ observer = new MutationObserver(schedule);
8078
9911
  run();
8079
- const nav = document.querySelector("nav");
8080
- if (!nav) return;
8081
- const observer = new MutationObserver(() => {
8082
- requestAnimationFrame(run);
8083
- });
8084
- observer.observe(nav, { childList: true, subtree: true });
8085
- return () => observer.disconnect();
8086
- }, [isEditMode, pathname, subdomain, fetchState]);
8087
- (0, import_react8.useEffect)(() => {
9912
+ return () => {
9913
+ if (rafId != null) cancelAnimationFrame(rafId);
9914
+ observer?.disconnect();
9915
+ };
9916
+ }, [isEditMode, pathname, subdomain, fetchState, resyncSelectedNavigationItem]);
9917
+ (0, import_react9.useEffect)(() => {
8088
9918
  if (!isEditMode) return;
8089
9919
  const measure = () => {
8090
9920
  const h = document.body.scrollHeight;
@@ -8108,7 +9938,7 @@ function OhhwellsBridge() {
8108
9938
  window.removeEventListener("resize", handleResize);
8109
9939
  };
8110
9940
  }, [pathname, isEditMode, postToParent2]);
8111
- (0, import_react8.useEffect)(() => {
9941
+ (0, import_react9.useEffect)(() => {
8112
9942
  if (!subdomainFromQuery || isEditMode) return;
8113
9943
  const handleClick = (e) => {
8114
9944
  const anchor = e.target.closest("a");
@@ -8124,19 +9954,24 @@ function OhhwellsBridge() {
8124
9954
  document.addEventListener("click", handleClick, true);
8125
9955
  return () => document.removeEventListener("click", handleClick, true);
8126
9956
  }, [subdomainFromQuery, isEditMode, router]);
8127
- (0, import_react8.useEffect)(() => {
9957
+ (0, import_react9.useEffect)(() => {
8128
9958
  if (!isEditMode) {
8129
9959
  editStylesRef.current?.base.remove();
8130
9960
  editStylesRef.current?.forceHover.remove();
8131
9961
  editStylesRef.current?.stateViews.remove();
8132
9962
  editStylesRef.current = null;
9963
+ document.querySelectorAll("[data-ohw-can-drag]").forEach((el) => {
9964
+ el.removeAttribute("data-ohw-can-drag");
9965
+ });
8133
9966
  return;
8134
9967
  }
8135
- if (!editStylesRef.current) {
8136
- const initialVh = window.innerHeight;
8137
- const base = document.createElement("style");
8138
- base.setAttribute("data-ohw-edit-style", "");
8139
- base.textContent = `
9968
+ const existing = editStylesRef.current;
9969
+ let initialVh = window.innerHeight;
9970
+ if (existing?.base.textContent) {
9971
+ const match = existing.base.textContent.match(/\.min-h-screen[^{]*\{[^}]*min-height:\s*(\d+)px/);
9972
+ if (match) initialVh = parseInt(match[1], 10);
9973
+ }
9974
+ const baseCss = `
8140
9975
  html { height: auto !important; }
8141
9976
  body { height: auto !important; min-height: 0 !important; overflow: hidden !important; }
8142
9977
  .min-h-screen, .min-h-svh, .min-h-dvh { min-height: ${initialVh}px !important; }
@@ -8147,7 +9982,25 @@ function OhhwellsBridge() {
8147
9982
  [data-ohw-editable] {
8148
9983
  display: block;
8149
9984
  }
8150
- [data-ohw-editable]:not([contenteditable]):not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"]):not([data-ohw-editable="video"]) { cursor: text !important; }
9985
+ /* Body text (no item-action toolbar) \u2014 first click enters text edit \u2192 I-beam.
9986
+ Exclude select-first nav/footer labels so grab/default on [data-ohw-href-key] can win. */
9987
+ [data-ohw-editable]:not([contenteditable]):not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"]):not([data-ohw-editable="video"]):not([data-ohw-editable="link"]):not(:is([data-ohw-href-key] *)) {
9988
+ cursor: text !important;
9989
+ }
9990
+ /* Select-first items: default arrow; grab only when drag-start is allowed (data-ohw-can-drag). */
9991
+ [data-ohw-href-key]:not([data-ohw-selected]),
9992
+ [data-ohw-href-key]:not([data-ohw-selected]) * {
9993
+ cursor: default !important;
9994
+ }
9995
+ [data-ohw-href-key][data-ohw-can-drag]:not([data-ohw-selected]),
9996
+ [data-ohw-href-key][data-ohw-can-drag]:not([data-ohw-selected]) * {
9997
+ cursor: grab !important;
9998
+ }
9999
+ /* Selected (action-toolbar) \u2014 next click enters text edit at caret. */
10000
+ [data-ohw-href-key][data-ohw-selected],
10001
+ [data-ohw-href-key][data-ohw-selected] * {
10002
+ cursor: text !important;
10003
+ }
8151
10004
  [data-ohw-editable="image"], [data-ohw-editable="image"] *,
8152
10005
  [data-ohw-editable="video"], [data-ohw-editable="video"] *,
8153
10006
  [data-ohw-editable="bg-image"], [data-ohw-editable="bg-image"] * { cursor: pointer !important; }
@@ -8163,9 +10016,32 @@ function OhhwellsBridge() {
8163
10016
  outline: none !important;
8164
10017
  outline-offset: 0 !important;
8165
10018
  }
8166
- [data-ohw-editable][contenteditable] {
10019
+ /* Editor chrome owns selection \u2014 suppress native focus rings on nav/footer items. */
10020
+ nav [data-ohw-href-key]:focus,
10021
+ nav [data-ohw-href-key]:focus-visible,
10022
+ nav [data-ohw-href-key]:focus-within,
10023
+ footer [data-ohw-href-key]:focus,
10024
+ footer [data-ohw-href-key]:focus-visible,
10025
+ footer [data-ohw-href-key]:focus-within,
10026
+ nav [data-ohw-href-key] [data-ohw-editable]:focus,
10027
+ nav [data-ohw-href-key] [data-ohw-editable]:focus-visible,
10028
+ footer [data-ohw-href-key] [data-ohw-editable]:focus,
10029
+ footer [data-ohw-href-key] [data-ohw-editable]:focus-visible {
10030
+ outline: none !important;
10031
+ outline-offset: 0 !important;
10032
+ box-shadow: none !important;
10033
+ }
10034
+ /* Text edit wins over grab/default (must beat [data-ohw-can-drag] *). */
10035
+ [data-ohw-editing],
10036
+ [data-ohw-editing] *,
10037
+ [data-ohw-editable][contenteditable],
10038
+ [data-ohw-editable][contenteditable] *,
10039
+ nav [data-ohw-href-key] [data-ohw-editable][contenteditable],
10040
+ nav [data-ohw-href-key] [data-ohw-editable][contenteditable] *,
10041
+ footer [data-ohw-href-key] [data-ohw-editable][contenteditable],
10042
+ footer [data-ohw-href-key] [data-ohw-editable][contenteditable] * {
8167
10043
  outline: none !important;
8168
- caret-color: ${PRIMARY2};
10044
+ caret-color: ${PRIMARY2} !important;
8169
10045
  cursor: text !important;
8170
10046
  }
8171
10047
  [data-ohw-editable][contenteditable]::selection,
@@ -8174,6 +10050,9 @@ function OhhwellsBridge() {
8174
10050
  [data-ohw-editable-state][data-ohw-active-state] [data-ohw-editable] { pointer-events: auto !important; }
8175
10051
  [data-ohw-editable-state][data-ohw-active-state][data-ohw-editable] { pointer-events: auto !important; }
8176
10052
  `;
10053
+ if (!existing) {
10054
+ const base = document.createElement("style");
10055
+ base.setAttribute("data-ohw-edit-style", "");
8177
10056
  const forceHover = document.createElement("style");
8178
10057
  forceHover.setAttribute("data-ohw-active-state-style", "");
8179
10058
  const stateViews = document.createElement("style");
@@ -8189,14 +10068,43 @@ function OhhwellsBridge() {
8189
10068
  document.head.appendChild(stateViews);
8190
10069
  editStylesRef.current = { base, forceHover, stateViews };
8191
10070
  }
10071
+ editStylesRef.current.base.textContent = baseCss;
10072
+ syncNavigationDragCursorAttrs();
8192
10073
  refreshStateRules();
8193
10074
  const handleClick = (e) => {
10075
+ if (suppressNextClickRef.current || Date.now() < suppressClickUntilRef.current) {
10076
+ suppressNextClickRef.current = false;
10077
+ e.preventDefault();
10078
+ e.stopPropagation();
10079
+ return;
10080
+ }
8194
10081
  const target = e.target;
8195
10082
  if (target.closest("[data-ohw-toolbar]")) return;
8196
10083
  if (target.closest("[data-ohw-state-toggle]")) return;
8197
10084
  if (target.closest("[data-ohw-max-badge]")) return;
8198
10085
  if (isInsideLinkEditor(target)) return;
8199
10086
  if (target.closest('[data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
10087
+ if (target.closest("[data-ohw-item-drag-surface]")) {
10088
+ e.preventDefault();
10089
+ e.stopPropagation();
10090
+ const selected = selectedElRef.current;
10091
+ if (selected && isNavigationItem(selected)) {
10092
+ const editable2 = selected.querySelector('[data-ohw-editable="text"]') ?? selected.querySelector("[data-ohw-editable]");
10093
+ if (editable2) {
10094
+ activateRef.current(editable2, {
10095
+ caretX: e.clientX,
10096
+ caretY: e.clientY
10097
+ });
10098
+ }
10099
+ } else if (selected?.hasAttribute("data-ohw-footer-col")) {
10100
+ const link = listFooterLinksInColumn(selected).find((el) => {
10101
+ const r2 = el.getBoundingClientRect();
10102
+ return e.clientX >= r2.left && e.clientX <= r2.right && e.clientY >= r2.top && e.clientY <= r2.bottom;
10103
+ });
10104
+ if (link) selectRef.current(link);
10105
+ }
10106
+ return;
10107
+ }
8200
10108
  const navFromChrome = resolveNavContainerSelectionTarget(target, e.clientX, e.clientY);
8201
10109
  if (navFromChrome) {
8202
10110
  e.preventDefault();
@@ -8204,10 +10112,34 @@ function OhhwellsBridge() {
8204
10112
  selectFrameRef.current(navFromChrome);
8205
10113
  return;
8206
10114
  }
10115
+ const footerFromChrome = resolveFooterColumnSelectionTarget(target, e.clientX, e.clientY);
10116
+ if (footerFromChrome) {
10117
+ e.preventDefault();
10118
+ e.stopPropagation();
10119
+ selectFrameRef.current(footerFromChrome);
10120
+ return;
10121
+ }
8207
10122
  e.preventDefault();
8208
10123
  e.stopPropagation();
8209
10124
  return;
8210
10125
  }
10126
+ if (activeElRef.current) {
10127
+ const active = activeElRef.current;
10128
+ const inActive = active === target || active.contains(target);
10129
+ const navAnchor = getNavigationItemAnchor(active);
10130
+ const inActiveNav = Boolean(
10131
+ navAnchor && (navAnchor === target || navAnchor.contains(target))
10132
+ );
10133
+ if (inActive || inActiveNav) {
10134
+ e.preventDefault();
10135
+ e.stopPropagation();
10136
+ if (e.detail < 2) {
10137
+ placeCaretAtPoint(active, e.clientX, e.clientY);
10138
+ refreshActiveCommandsRef.current();
10139
+ }
10140
+ return;
10141
+ }
10142
+ }
8211
10143
  const editable = target.closest("[data-ohw-editable]");
8212
10144
  if (editable) {
8213
10145
  if (editable.dataset.ohwEditable === "link") {
@@ -8251,10 +10183,26 @@ function OhhwellsBridge() {
8251
10183
  if (hrefAnchor) {
8252
10184
  e.preventDefault();
8253
10185
  e.stopPropagation();
8254
- if (selectedElRef.current === hrefAnchor) return;
10186
+ if (selectedElRef.current === hrefAnchor) {
10187
+ const textEditable = hrefAnchor.querySelector('[data-ohw-editable="text"]') ?? hrefAnchor.querySelector("[data-ohw-editable]");
10188
+ if (textEditable) {
10189
+ activateRef.current(textEditable, {
10190
+ caretX: e.clientX,
10191
+ caretY: e.clientY
10192
+ });
10193
+ }
10194
+ return;
10195
+ }
8255
10196
  selectRef.current(hrefAnchor);
8256
10197
  return;
8257
10198
  }
10199
+ const footerColClick = target.closest("[data-ohw-footer-col]");
10200
+ if (footerColClick) {
10201
+ e.preventDefault();
10202
+ e.stopPropagation();
10203
+ selectFrameRef.current(footerColClick);
10204
+ return;
10205
+ }
8258
10206
  const navContainerToSelect = resolveNavContainerSelectionTarget(target, e.clientX, e.clientY);
8259
10207
  if (navContainerToSelect) {
8260
10208
  e.preventDefault();
@@ -8262,6 +10210,13 @@ function OhhwellsBridge() {
8262
10210
  selectFrameRef.current(navContainerToSelect);
8263
10211
  return;
8264
10212
  }
10213
+ const footerColumnToSelect = resolveFooterColumnSelectionTarget(target, e.clientX, e.clientY);
10214
+ if (footerColumnToSelect) {
10215
+ e.preventDefault();
10216
+ e.stopPropagation();
10217
+ selectFrameRef.current(footerColumnToSelect);
10218
+ return;
10219
+ }
8265
10220
  const selectedContainer = selectedElRef.current;
8266
10221
  if (selectedContainer && isNavigationContainer(selectedContainer)) {
8267
10222
  const navItem = getNavigationItemAnchor(target);
@@ -8321,6 +10276,13 @@ function OhhwellsBridge() {
8321
10276
  const handleMouseOver = (e) => {
8322
10277
  if (document.documentElement.hasAttribute("data-ohw-section-picking")) return;
8323
10278
  const target = e.target;
10279
+ if (linkPopoverOpenRef.current) {
10280
+ hoveredItemElRef.current = null;
10281
+ setHoveredItemRect(null);
10282
+ hoveredNavContainerRef.current = null;
10283
+ setHoveredNavContainerRect(null);
10284
+ return;
10285
+ }
8324
10286
  if (toolbarVariantRef.current !== "select-frame") {
8325
10287
  const navContainer = target.closest("[data-ohw-nav-container]");
8326
10288
  if (navContainer && !getNavigationItemAnchor(target)) {
@@ -8340,12 +10302,21 @@ function OhhwellsBridge() {
8340
10302
  hoveredNavContainerRef.current = null;
8341
10303
  setHoveredNavContainerRect(null);
8342
10304
  const selected2 = selectedElRef.current;
8343
- if (selected2 === navAnchor || selected2?.contains(navAnchor)) return;
10305
+ if (selected2 === navAnchor) return;
8344
10306
  clearHrefKeyHover(navAnchor);
8345
10307
  hoveredItemElRef.current = navAnchor;
8346
10308
  setHoveredItemRect(navAnchor.getBoundingClientRect());
8347
10309
  return;
8348
10310
  }
10311
+ const footerCol = target.closest("[data-ohw-footer-col]") ?? target.closest("[data-ohw-footer-column]");
10312
+ if (footerCol) {
10313
+ hoveredNavContainerRef.current = null;
10314
+ setHoveredNavContainerRect(null);
10315
+ if (selectedElRef.current === footerCol) return;
10316
+ hoveredItemElRef.current = footerCol;
10317
+ setHoveredItemRect(footerCol.getBoundingClientRect());
10318
+ return;
10319
+ }
8349
10320
  const editable = target.closest("[data-ohw-editable]");
8350
10321
  if (!editable) return;
8351
10322
  const selected = selectedElRef.current;
@@ -8363,14 +10334,25 @@ function OhhwellsBridge() {
8363
10334
  };
8364
10335
  const handleMouseOut = (e) => {
8365
10336
  const target = e.target;
8366
- if (target.closest("[data-ohw-nav-container]")) {
10337
+ if (target.closest("[data-ohw-nav-container], [data-ohw-footer-col], [data-ohw-footer-column]")) {
8367
10338
  const related2 = e.relatedTarget instanceof Element ? e.relatedTarget : null;
8368
- if (related2?.closest("[data-ohw-nav-container], [data-ohw-navbar-container-chrome], [data-ohw-navbar-add-button]")) {
10339
+ if (related2?.closest("[data-ohw-nav-container], [data-ohw-footer-col], [data-ohw-footer-column], [data-ohw-navbar-container-chrome], [data-ohw-navbar-add-button]")) {
8369
10340
  return;
8370
10341
  }
8371
10342
  hoveredNavContainerRef.current = null;
8372
10343
  setHoveredNavContainerRect(null);
8373
10344
  }
10345
+ const footerCol = target.closest("[data-ohw-footer-col]") ?? target.closest("[data-ohw-footer-column]");
10346
+ if (footerCol && hoveredItemElRef.current === footerCol) {
10347
+ const related2 = e.relatedTarget instanceof Element ? e.relatedTarget : null;
10348
+ if (related2?.closest("[data-ohw-drag-handle-container], [data-ohw-item-interaction]")) return;
10349
+ if (related2?.closest("[data-ohw-footer-col], [data-ohw-footer-column]") === footerCol) {
10350
+ return;
10351
+ }
10352
+ hoveredItemElRef.current = null;
10353
+ setHoveredItemRect(null);
10354
+ return;
10355
+ }
8374
10356
  const navAnchor = getNavigationItemAnchor(target);
8375
10357
  if (navAnchor) {
8376
10358
  const related2 = e.relatedTarget instanceof Element ? e.relatedTarget : null;
@@ -8504,43 +10486,75 @@ function OhhwellsBridge() {
8504
10486
  if (toolbarVariantRef.current === "select-frame") {
8505
10487
  hoveredNavContainerRef.current = null;
8506
10488
  setHoveredNavContainerRect(null);
8507
- return;
8508
10489
  }
8509
- const navContainer = document.querySelector("[data-ohw-nav-container]");
8510
- if (!navContainer) {
8511
- hoveredNavContainerRef.current = null;
8512
- setHoveredNavContainerRect(null);
8513
- return;
10490
+ const navContainers = Array.from(
10491
+ document.querySelectorAll("[data-ohw-nav-container]")
10492
+ );
10493
+ const footerColumns = listFooterColumns();
10494
+ for (const container of [...navContainers, ...footerColumns]) {
10495
+ const containerRect = container.getBoundingClientRect();
10496
+ const overContainer = x >= containerRect.left && x <= containerRect.right && y >= containerRect.top && y <= containerRect.bottom;
10497
+ if (!overContainer) continue;
10498
+ const navItemHit = Array.from(
10499
+ container.querySelectorAll("[data-ohw-href-key]")
10500
+ ).find((el) => {
10501
+ if (!isNavigationItem(el) || isNavbarButton2(el)) return false;
10502
+ const itemRect = el.getBoundingClientRect();
10503
+ return x >= itemRect.left && x <= itemRect.right && y >= itemRect.top && y <= itemRect.bottom;
10504
+ });
10505
+ if (navItemHit) {
10506
+ hoveredNavContainerRef.current = null;
10507
+ setHoveredNavContainerRect(null);
10508
+ const selected = selectedElRef.current;
10509
+ if (selected !== navItemHit) {
10510
+ clearHrefKeyHover(navItemHit);
10511
+ hoveredItemElRef.current = navItemHit;
10512
+ setHoveredItemRect(navItemHit.getBoundingClientRect());
10513
+ } else if (hoveredItemElRef.current === navItemHit) {
10514
+ hoveredItemElRef.current = null;
10515
+ setHoveredItemRect(null);
10516
+ }
10517
+ return;
10518
+ }
8514
10519
  }
8515
- const containerRect = navContainer.getBoundingClientRect();
8516
- const overContainer = x >= containerRect.left && x <= containerRect.right && y >= containerRect.top && y <= containerRect.bottom;
8517
- if (!overContainer) {
10520
+ if (toolbarVariantRef.current !== "select-frame") {
10521
+ for (const container of navContainers) {
10522
+ const containerRect = container.getBoundingClientRect();
10523
+ const overContainer = x >= containerRect.left && x <= containerRect.right && y >= containerRect.top && y <= containerRect.bottom;
10524
+ if (!overContainer) continue;
10525
+ if (isPointOverNavItem(container, x, y)) continue;
10526
+ hoveredNavContainerRef.current = container;
10527
+ setHoveredNavContainerRect(container.getBoundingClientRect());
10528
+ hoveredItemElRef.current = null;
10529
+ setHoveredItemRect(null);
10530
+ return;
10531
+ }
8518
10532
  hoveredNavContainerRef.current = null;
8519
10533
  setHoveredNavContainerRect(null);
8520
- return;
8521
- }
8522
- const navItemHit = Array.from(
8523
- navContainer.querySelectorAll("[data-ohw-href-key]")
8524
- ).find((el) => {
8525
- if (!isNavigationItem(el) || isNavbarButton2(el)) return false;
8526
- const itemRect = el.getBoundingClientRect();
8527
- return x >= itemRect.left && x <= itemRect.right && y >= itemRect.top && y <= itemRect.bottom;
8528
- });
8529
- if (navItemHit) {
10534
+ } else {
8530
10535
  hoveredNavContainerRef.current = null;
8531
10536
  setHoveredNavContainerRect(null);
8532
- const selected = selectedElRef.current;
8533
- if (selected !== navItemHit && !selected?.contains(navItemHit)) {
8534
- clearHrefKeyHover(navItemHit);
8535
- hoveredItemElRef.current = navItemHit;
8536
- setHoveredItemRect(navItemHit.getBoundingClientRect());
10537
+ }
10538
+ for (const column of footerColumns) {
10539
+ const containerRect = column.getBoundingClientRect();
10540
+ const overContainer = x >= containerRect.left && x <= containerRect.right && y >= containerRect.top && y <= containerRect.bottom;
10541
+ if (!overContainer) continue;
10542
+ if (isPointOverNavItem(column, x, y)) continue;
10543
+ if (selectedElRef.current === column) {
10544
+ if (hoveredItemElRef.current === column) {
10545
+ hoveredItemElRef.current = null;
10546
+ setHoveredItemRect(null);
10547
+ }
10548
+ return;
8537
10549
  }
10550
+ hoveredItemElRef.current = column;
10551
+ setHoveredItemRect(column.getBoundingClientRect());
8538
10552
  return;
8539
10553
  }
8540
- hoveredNavContainerRef.current = navContainer;
8541
- setHoveredNavContainerRect(containerRect);
8542
- hoveredItemElRef.current = null;
8543
- setHoveredItemRect(null);
10554
+ if (hoveredItemElRef.current?.hasAttribute("data-ohw-footer-col") || hoveredItemElRef.current?.hasAttribute("data-ohw-footer-column")) {
10555
+ hoveredItemElRef.current = null;
10556
+ setHoveredItemRect(null);
10557
+ }
8544
10558
  };
8545
10559
  const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
8546
10560
  if (linkPopoverOpenRef.current) {
@@ -8561,6 +10575,10 @@ function OhhwellsBridge() {
8561
10575
  }
8562
10576
  hoveredNavContainerRef.current = null;
8563
10577
  setHoveredNavContainerRect(null);
10578
+ if (hoveredItemElRef.current?.hasAttribute("data-ohw-footer-col") || hoveredItemElRef.current?.hasAttribute("data-ohw-footer-column")) {
10579
+ hoveredItemElRef.current = null;
10580
+ setHoveredItemRect(null);
10581
+ }
8564
10582
  const toggleEl = document.querySelector("[data-ohw-state-toggle]");
8565
10583
  if (toggleEl) {
8566
10584
  const tr = toggleEl.getBoundingClientRect();
@@ -8808,6 +10826,15 @@ function OhhwellsBridge() {
8808
10826
  probeHoverCardsAt(clientX, clientY);
8809
10827
  };
8810
10828
  const handleDragOver = (e) => {
10829
+ const footerSession = footerDragRef.current;
10830
+ if (footerSession) {
10831
+ e.preventDefault();
10832
+ if (e.dataTransfer) e.dataTransfer.dropEffect = "move";
10833
+ const slot = footerSession.kind === "link" && footerSession.hrefKey ? hitTestLinkDropSlot(e.clientX, e.clientY, footerSession.hrefKey) : hitTestColumnDropSlot(e.clientX, e.clientY);
10834
+ refreshFooterDragVisualsRef.current(footerSession, slot, e.clientX, e.clientY);
10835
+ return;
10836
+ }
10837
+ if (onNavDragOver(e)) return;
8811
10838
  e.preventDefault();
8812
10839
  const el = findImageAtPoint(e.clientX, e.clientY, false);
8813
10840
  if (!el) {
@@ -8970,8 +10997,8 @@ function OhhwellsBridge() {
8970
10997
  if (sibling !== el) sibling.innerHTML = html;
8971
10998
  });
8972
10999
  const timers = autoSaveTimers.current;
8973
- const existing = timers.get(key);
8974
- if (existing) clearTimeout(existing);
11000
+ const existing2 = timers.get(key);
11001
+ if (existing2) clearTimeout(existing2);
8975
11002
  timers.set(key, setTimeout(() => {
8976
11003
  timers.delete(key);
8977
11004
  postToParentRef.current({ type: "ow:change", nodes: [{ key, text: html }] });
@@ -9014,6 +11041,7 @@ function OhhwellsBridge() {
9014
11041
  }
9015
11042
  editContentRef.current = { ...editContentRef.current, ...content };
9016
11043
  reconcileNavbarItemsFromContent(editContentRef.current);
11044
+ syncNavigationDragCursorAttrs();
9017
11045
  enforceLinkHrefs();
9018
11046
  postToParentRef.current({ type: "ow:hydrate-done" });
9019
11047
  };
@@ -9034,6 +11062,48 @@ function OhhwellsBridge() {
9034
11062
  if (e.data?.type !== "ui:escape") return;
9035
11063
  if (linkPopoverOpenRef.current) {
9036
11064
  setLinkPopoverRef.current(null);
11065
+ return;
11066
+ }
11067
+ if (activeElRef.current) {
11068
+ const hrefCtx = getHrefKeyFromElement(activeElRef.current);
11069
+ const navAnchor = hrefCtx ? getNavigationItemAnchor(hrefCtx.anchor) : null;
11070
+ if (navAnchor) {
11071
+ const el = activeElRef.current;
11072
+ if (originalContentRef.current !== null) {
11073
+ el.innerHTML = originalContentRef.current;
11074
+ }
11075
+ el.removeAttribute("contenteditable");
11076
+ el.removeAttribute("data-ohw-editing");
11077
+ activeElRef.current = null;
11078
+ setMaxBadge(null);
11079
+ setActiveCommands(/* @__PURE__ */ new Set());
11080
+ setToolbarShowEditLink(false);
11081
+ postToParentRef.current({ type: "ow:exit-edit" });
11082
+ reselectNavigationItemRef.current(navAnchor);
11083
+ return;
11084
+ }
11085
+ if (originalContentRef.current !== null) {
11086
+ activeElRef.current.innerHTML = originalContentRef.current;
11087
+ const key = activeElRef.current.dataset.ohwKey;
11088
+ if (key) {
11089
+ postToParentRef.current({ type: "ow:change", nodes: [{ key, text: originalContentRef.current }] });
11090
+ }
11091
+ }
11092
+ deselectRef.current();
11093
+ deactivateRef.current();
11094
+ return;
11095
+ }
11096
+ if (selectedElRef.current) {
11097
+ if (toolbarVariantRef.current === "select-frame") {
11098
+ deselectRef.current();
11099
+ return;
11100
+ }
11101
+ const parent = getNavigationSelectionParent(selectedElRef.current);
11102
+ if (parent && isNavigationContainer(parent)) {
11103
+ selectFrameRef.current(parent);
11104
+ } else {
11105
+ deselectRef.current();
11106
+ }
9037
11107
  }
9038
11108
  };
9039
11109
  window.addEventListener("message", handleUiEscape);
@@ -9058,7 +11128,7 @@ function OhhwellsBridge() {
9058
11128
  return;
9059
11129
  }
9060
11130
  const parent = getNavigationSelectionParent(selectedElRef.current);
9061
- if (parent) {
11131
+ if (parent && isNavigationContainer(parent)) {
9062
11132
  e.preventDefault();
9063
11133
  selectFrameRef.current(parent);
9064
11134
  } else {
@@ -9076,6 +11146,7 @@ function OhhwellsBridge() {
9076
11146
  el2.innerHTML = originalContentRef.current;
9077
11147
  }
9078
11148
  el2.removeAttribute("contenteditable");
11149
+ el2.removeAttribute("data-ohw-editing");
9079
11150
  activeElRef.current = null;
9080
11151
  setMaxBadge(null);
9081
11152
  setActiveCommands(/* @__PURE__ */ new Set());
@@ -9127,6 +11198,26 @@ function OhhwellsBridge() {
9127
11198
  if (siblingHintElRef.current) {
9128
11199
  setSiblingHintRect(siblingHintElRef.current.getBoundingClientRect());
9129
11200
  }
11201
+ const selected = selectedElRef.current;
11202
+ if (selected && !footerDragRef.current && !navDragRef.current) {
11203
+ if (selected.hasAttribute("data-ohw-footer-col") || Boolean(selected.closest("footer") && isInferredFooterGroup(selected))) {
11204
+ setSiblingHintRects(
11205
+ listFooterColumns().filter((column) => column !== selected).map((column) => column.getBoundingClientRect())
11206
+ );
11207
+ } else if (isNavigationItem(selected)) {
11208
+ setSiblingHintRects(collectNavigationItemSiblingHintRects(selected));
11209
+ }
11210
+ }
11211
+ if (footerDragRef.current) {
11212
+ const session = footerDragRef.current;
11213
+ const slot = session.kind === "link" && session.hrefKey ? hitTestLinkDropSlot(session.lastClientX, session.lastClientY, session.hrefKey) : hitTestColumnDropSlot(session.lastClientX, session.lastClientY);
11214
+ refreshFooterDragVisualsRef.current(session, slot, session.lastClientX, session.lastClientY);
11215
+ }
11216
+ if (navDragRef.current) {
11217
+ const session = navDragRef.current;
11218
+ const slot = hitTestNavDropSlot(session.lastClientX, session.lastClientY, session.hrefKey);
11219
+ refreshNavDragVisualsRef.current(session, slot, session.lastClientX, session.lastClientY);
11220
+ }
9130
11221
  if (hoveredImageRef.current) {
9131
11222
  const el = hoveredImageRef.current;
9132
11223
  const r2 = el.getBoundingClientRect();
@@ -9137,7 +11228,7 @@ function OhhwellsBridge() {
9137
11228
  };
9138
11229
  const handleSave = (e) => {
9139
11230
  if (e.data?.type !== "ow:save") return;
9140
- const nodes = collectEditableNodes();
11231
+ const nodes = collectEditableNodes(editContentRef.current);
9141
11232
  const tracker = document.querySelector("[data-ohw-sections-tracker]");
9142
11233
  if (tracker?.textContent) nodes.push({ key: "__ohw_sections", type: "sections", text: tracker.textContent });
9143
11234
  postToParentRef.current({ type: "ow:save-result", nodes });
@@ -9263,15 +11354,15 @@ function OhhwellsBridge() {
9263
11354
  };
9264
11355
  const applyToolbarPos = (rect) => {
9265
11356
  const ps = parentScrollRef.current;
9266
- const approxW = 330;
11357
+ const measuredW = toolbarElRef.current?.offsetWidth || 330;
9267
11358
  if (toolbarElRef.current) {
9268
- const { top, left, transform } = calcToolbarPos(rect, ps, approxW);
11359
+ const { top, left, transform } = calcToolbarPos(rect, ps, measuredW);
9269
11360
  toolbarElRef.current.style.top = `${top}px`;
9270
11361
  toolbarElRef.current.style.left = `${left}px`;
9271
11362
  toolbarElRef.current.style.transform = transform;
9272
11363
  }
9273
11364
  if (glowElRef.current) {
9274
- const GAP = 6;
11365
+ const GAP = SELECTION_CHROME_GAP2;
9275
11366
  glowElRef.current.style.top = `${rect.top - GAP}px`;
9276
11367
  glowElRef.current.style.left = `${rect.left - GAP}px`;
9277
11368
  glowElRef.current.style.width = `${rect.width + GAP * 2}px`;
@@ -9359,6 +11450,11 @@ function OhhwellsBridge() {
9359
11450
  return;
9360
11451
  }
9361
11452
  }
11453
+ const footerColumn = findHoveredNavOrFooterContainer(clientX, clientY);
11454
+ if (footerColumn?.hasAttribute("data-ohw-footer-col") || footerColumn?.hasAttribute("data-ohw-footer-column")) {
11455
+ selectFrameRef.current(footerColumn);
11456
+ return;
11457
+ }
9362
11458
  deactivateRef.current();
9363
11459
  };
9364
11460
  window.addEventListener("message", handleSave);
@@ -9430,7 +11526,161 @@ function OhhwellsBridge() {
9430
11526
  if (imageShowTimerRef.current) clearTimeout(imageShowTimerRef.current);
9431
11527
  };
9432
11528
  }, [isEditMode, refreshStateRules]);
9433
- (0, import_react8.useEffect)(() => {
11529
+ (0, import_react9.useEffect)(() => {
11530
+ if (!isEditMode) return;
11531
+ const THRESHOLD = 10;
11532
+ const resolveWasSelected = (el) => {
11533
+ if (selectedElRef.current === el) return true;
11534
+ const activeAnchor = activeElRef.current ? getNavigationItemAnchor(activeElRef.current) : null;
11535
+ return activeAnchor === el;
11536
+ };
11537
+ const onPointerDown = (e) => {
11538
+ if (e.button !== 0) return;
11539
+ if (suppressNextClickRef.current && Date.now() >= suppressClickUntilRef.current && !footerDragRef.current && !footerPointerDragRef.current?.started) {
11540
+ suppressNextClickRef.current = false;
11541
+ }
11542
+ if (footerDragRef.current) return;
11543
+ const target = e.target;
11544
+ if (!target) return;
11545
+ if (target.closest('[data-ohw-drag-handle-container], [data-slot="drag-handle"], [data-ohw-toolbar], [data-ohw-item-toolbar-anchor], [data-ohw-link-popover-root]')) {
11546
+ return;
11547
+ }
11548
+ if (target.closest("[data-ohw-item-drag-surface]")) return;
11549
+ const anchor = getNavigationItemAnchor(target);
11550
+ const hrefKey = anchor?.getAttribute("data-ohw-href-key") ?? null;
11551
+ if (anchor && isFooterHrefKey(hrefKey)) {
11552
+ armFooterPressDrag();
11553
+ footerPointerDragRef.current = {
11554
+ el: anchor,
11555
+ kind: "link",
11556
+ startX: e.clientX,
11557
+ startY: e.clientY,
11558
+ pointerId: e.pointerId,
11559
+ wasSelected: resolveWasSelected(anchor),
11560
+ started: false
11561
+ };
11562
+ return;
11563
+ }
11564
+ const col = target.closest("[data-ohw-footer-col]");
11565
+ if (col && !getNavigationItemAnchor(target)) {
11566
+ armFooterPressDrag();
11567
+ footerPointerDragRef.current = {
11568
+ el: col,
11569
+ kind: "column",
11570
+ startX: e.clientX,
11571
+ startY: e.clientY,
11572
+ pointerId: e.pointerId,
11573
+ wasSelected: resolveWasSelected(col),
11574
+ started: false
11575
+ };
11576
+ }
11577
+ };
11578
+ const onPointerMove = (e) => {
11579
+ const pending = footerPointerDragRef.current;
11580
+ if (!pending) return;
11581
+ if (pending.started) {
11582
+ e.preventDefault();
11583
+ clearTextSelection();
11584
+ const session = footerDragRef.current;
11585
+ if (!session) return;
11586
+ const slot = session.kind === "link" && session.hrefKey ? hitTestLinkDropSlot(e.clientX, e.clientY, session.hrefKey) : hitTestColumnDropSlot(e.clientX, e.clientY);
11587
+ refreshFooterDragVisualsRef.current(session, slot, e.clientX, e.clientY);
11588
+ return;
11589
+ }
11590
+ const dx = e.clientX - pending.startX;
11591
+ const dy = e.clientY - pending.startY;
11592
+ if (dx * dx + dy * dy < THRESHOLD * THRESHOLD) return;
11593
+ e.preventDefault();
11594
+ pending.started = true;
11595
+ clearTextSelection();
11596
+ try {
11597
+ document.body.setPointerCapture(pending.pointerId);
11598
+ } catch {
11599
+ }
11600
+ if (linkPopoverOpenRef.current) {
11601
+ setLinkPopoverRef.current(null);
11602
+ }
11603
+ if (activeElRef.current) {
11604
+ deactivateRef.current();
11605
+ }
11606
+ if (pending.kind === "link") {
11607
+ const key = pending.el.getAttribute("data-ohw-href-key");
11608
+ if (!key) return;
11609
+ const column = findFooterColumnForLink(pending.el);
11610
+ const columns2 = listFooterColumns();
11611
+ beginFooterDragRef.current({
11612
+ kind: "link",
11613
+ hrefKey: key,
11614
+ columnEl: column,
11615
+ sourceColumnIndex: column ? columns2.indexOf(column) : 0,
11616
+ wasSelected: pending.wasSelected,
11617
+ draggedEl: pending.el,
11618
+ lastClientX: e.clientX,
11619
+ lastClientY: e.clientY,
11620
+ activeSlot: null
11621
+ });
11622
+ return;
11623
+ }
11624
+ const columns = listFooterColumns();
11625
+ const idx = columns.indexOf(pending.el);
11626
+ if (idx < 0) return;
11627
+ beginFooterDragRef.current({
11628
+ kind: "column",
11629
+ hrefKey: null,
11630
+ columnEl: pending.el,
11631
+ sourceColumnIndex: idx,
11632
+ wasSelected: pending.wasSelected,
11633
+ draggedEl: pending.el,
11634
+ lastClientX: e.clientX,
11635
+ lastClientY: e.clientY,
11636
+ activeSlot: null
11637
+ });
11638
+ };
11639
+ const endPointerDrag = (e) => {
11640
+ const pending = footerPointerDragRef.current;
11641
+ footerPointerDragRef.current = null;
11642
+ try {
11643
+ if (document.body.hasPointerCapture(e.pointerId)) {
11644
+ document.body.releasePointerCapture(e.pointerId);
11645
+ }
11646
+ } catch {
11647
+ }
11648
+ if (!pending?.started) {
11649
+ unlockFooterDragInteraction();
11650
+ return;
11651
+ }
11652
+ suppressNextClickRef.current = true;
11653
+ suppressClickUntilRef.current = Date.now() + 500;
11654
+ commitFooterDragRef.current(e.clientX, e.clientY);
11655
+ };
11656
+ const blockSelectStart = (e) => {
11657
+ if (footerDragRef.current || footerPointerDragRef.current?.started || document.documentElement.hasAttribute("data-ohw-footer-press-drag")) {
11658
+ e.preventDefault();
11659
+ }
11660
+ };
11661
+ const onDragEnd = (_e) => {
11662
+ if (!footerDragRef.current) return;
11663
+ suppressNextClickRef.current = true;
11664
+ suppressClickUntilRef.current = Date.now() + 500;
11665
+ commitFooterDragRef.current();
11666
+ };
11667
+ document.addEventListener("pointerdown", onPointerDown, true);
11668
+ document.addEventListener("pointermove", onPointerMove, true);
11669
+ document.addEventListener("pointerup", endPointerDrag, true);
11670
+ document.addEventListener("pointercancel", endPointerDrag, true);
11671
+ document.addEventListener("selectstart", blockSelectStart, true);
11672
+ document.addEventListener("dragend", onDragEnd, true);
11673
+ return () => {
11674
+ document.removeEventListener("pointerdown", onPointerDown, true);
11675
+ document.removeEventListener("pointermove", onPointerMove, true);
11676
+ document.removeEventListener("pointerup", endPointerDrag, true);
11677
+ document.removeEventListener("pointercancel", endPointerDrag, true);
11678
+ document.removeEventListener("selectstart", blockSelectStart, true);
11679
+ document.removeEventListener("dragend", onDragEnd, true);
11680
+ unlockFooterDragInteraction();
11681
+ };
11682
+ }, [isEditMode]);
11683
+ (0, import_react9.useEffect)(() => {
9434
11684
  const handler = (e) => {
9435
11685
  if (e.data?.type !== "ow:request-schedule-config") return;
9436
11686
  const insertAfterVal = e.data.insertAfter;
@@ -9446,7 +11696,7 @@ function OhhwellsBridge() {
9446
11696
  window.addEventListener("message", handler);
9447
11697
  return () => window.removeEventListener("message", handler);
9448
11698
  }, [processConfigRequest]);
9449
- (0, import_react8.useEffect)(() => {
11699
+ (0, import_react9.useEffect)(() => {
9450
11700
  if (!isEditMode) return;
9451
11701
  document.querySelectorAll("[data-ohw-active-state]").forEach((el) => {
9452
11702
  el.removeAttribute("data-ohw-active-state");
@@ -9467,7 +11717,13 @@ function OhhwellsBridge() {
9467
11717
  const next = { ...prev, [pathKey]: sections };
9468
11718
  return shouldUseDevFixtures() ? { ...DEV_SECTIONS_BY_PATH, ...next } : next;
9469
11719
  });
9470
- postToParent2({ type: "ow:ready", version: "1", path: pathname, nodes: collectEditableNodes(), sections });
11720
+ postToParent2({
11721
+ type: "ow:ready",
11722
+ version: "1",
11723
+ path: pathname,
11724
+ nodes: collectEditableNodes(editContentRef.current),
11725
+ sections
11726
+ });
9471
11727
  postToParent2({ type: "ow:request-site-pages" });
9472
11728
  }, 150);
9473
11729
  return () => {
@@ -9475,19 +11731,19 @@ function OhhwellsBridge() {
9475
11731
  clearTimeout(timer);
9476
11732
  };
9477
11733
  }, [pathname, isEditMode, refreshStateRules, postToParent2]);
9478
- (0, import_react8.useEffect)(() => {
11734
+ (0, import_react9.useEffect)(() => {
9479
11735
  scrollToHashSectionWhenReady();
9480
11736
  const onHashChange = () => scrollToHashSectionWhenReady();
9481
11737
  window.addEventListener("hashchange", onHashChange);
9482
11738
  return () => window.removeEventListener("hashchange", onHashChange);
9483
11739
  }, [pathname]);
9484
- const handleCommand = (0, import_react8.useCallback)((cmd) => {
11740
+ const handleCommand = (0, import_react9.useCallback)((cmd) => {
9485
11741
  document.execCommand(cmd, false);
9486
11742
  activeElRef.current?.focus();
9487
11743
  if (activeElRef.current) setToolbarRect(getEditMeasureEl(activeElRef.current).getBoundingClientRect());
9488
11744
  refreshActiveCommandsRef.current();
9489
11745
  }, []);
9490
- const handleStateChange = (0, import_react8.useCallback)((state) => {
11746
+ const handleStateChange = (0, import_react9.useCallback)((state) => {
9491
11747
  if (!activeStateElRef.current) return;
9492
11748
  const el = activeStateElRef.current;
9493
11749
  if (state === "Default") {
@@ -9500,11 +11756,11 @@ function OhhwellsBridge() {
9500
11756
  }
9501
11757
  setToggleState((prev) => prev ? { ...prev, activeState: state } : null);
9502
11758
  }, [deactivate]);
9503
- const closeLinkPopover = (0, import_react8.useCallback)(() => {
11759
+ const closeLinkPopover = (0, import_react9.useCallback)(() => {
9504
11760
  addNavAfterAnchorRef.current = null;
9505
11761
  setLinkPopover(null);
9506
11762
  }, []);
9507
- const openLinkPopoverForActive = (0, import_react8.useCallback)(() => {
11763
+ const openLinkPopoverForActive = (0, import_react9.useCallback)(() => {
9508
11764
  const hrefCtx = getHrefKeyFromElement(activeElRef.current);
9509
11765
  if (!hrefCtx) return;
9510
11766
  bumpLinkPopoverGrace();
@@ -9515,7 +11771,7 @@ function OhhwellsBridge() {
9515
11771
  });
9516
11772
  deactivate();
9517
11773
  }, [deactivate]);
9518
- const openLinkPopoverForSelected = (0, import_react8.useCallback)(() => {
11774
+ const openLinkPopoverForSelected = (0, import_react9.useCallback)(() => {
9519
11775
  const anchor = selectedElRef.current;
9520
11776
  if (!anchor) return;
9521
11777
  const key = anchor.getAttribute("data-ohw-href-key");
@@ -9528,7 +11784,7 @@ function OhhwellsBridge() {
9528
11784
  });
9529
11785
  deselect();
9530
11786
  }, [deselect]);
9531
- const handleLinkPopoverSubmit = (0, import_react8.useCallback)(
11787
+ const handleLinkPopoverSubmit = (0, import_react9.useCallback)(
9532
11788
  (target) => {
9533
11789
  const session = linkPopoverSessionRef.current;
9534
11790
  if (!session) return;
@@ -9587,13 +11843,13 @@ function OhhwellsBridge() {
9587
11843
  const showEditLink = toolbarShowEditLink;
9588
11844
  const currentSections = sectionsByPath[pathname] ?? [];
9589
11845
  linkPopoverOpenRef.current = linkPopover !== null;
9590
- const handleMediaReplace = (0, import_react8.useCallback)(
11846
+ const handleMediaReplace = (0, import_react9.useCallback)(
9591
11847
  (key) => {
9592
11848
  postToParent2({ type: "ow:image-pick", key, elementType: mediaHover?.elementType ?? "image" });
9593
11849
  },
9594
11850
  [postToParent2, mediaHover?.elementType]
9595
11851
  );
9596
- const handleMediaFadeOutComplete = (0, import_react8.useCallback)((key) => {
11852
+ const handleMediaFadeOutComplete = (0, import_react9.useCallback)((key) => {
9597
11853
  setUploadingRects((prev) => {
9598
11854
  if (!(key in prev)) return prev;
9599
11855
  const next = { ...prev };
@@ -9601,7 +11857,7 @@ function OhhwellsBridge() {
9601
11857
  return next;
9602
11858
  });
9603
11859
  }, []);
9604
- const handleVideoSettingsChange = (0, import_react8.useCallback)(
11860
+ const handleVideoSettingsChange = (0, import_react9.useCallback)(
9605
11861
  (key, settings) => {
9606
11862
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
9607
11863
  const video = getVideoEl(el);
@@ -9624,9 +11880,9 @@ function OhhwellsBridge() {
9624
11880
  [postToParent2]
9625
11881
  );
9626
11882
  return bridgeRoot ? (0, import_react_dom3.createPortal)(
9627
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
9628
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
9629
- Object.entries(uploadingRects).map(([key, { rect, fadingOut }]) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
11883
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
11884
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
11885
+ Object.entries(uploadingRects).map(([key, { rect, fadingOut }]) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9630
11886
  MediaOverlay,
9631
11887
  {
9632
11888
  hover: { key, rect, elementType: "image", isDragOver: false, hasTextOverlap: false },
@@ -9637,7 +11893,7 @@ function OhhwellsBridge() {
9637
11893
  },
9638
11894
  `uploading-${key}`
9639
11895
  )),
9640
- mediaHover && !(mediaHover.key in uploadingRects) && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
11896
+ mediaHover && !(mediaHover.key in uploadingRects) && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9641
11897
  MediaOverlay,
9642
11898
  {
9643
11899
  hover: mediaHover,
@@ -9646,49 +11902,93 @@ function OhhwellsBridge() {
9646
11902
  onVideoSettingsChange: handleVideoSettingsChange
9647
11903
  }
9648
11904
  ),
9649
- siblingHintRect && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
9650
- hoveredNavContainerRect && toolbarVariant !== "select-frame" && !linkPopover && !isItemDragging && !siblingHintRect && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
9651
- toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
9652
- NavbarContainerChrome,
11905
+ siblingHintRect && !linkPopover && !isItemDragging && siblingHintRects.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
11906
+ siblingHintRects.length > 0 && !linkPopover && !isItemDragging && siblingHintRects.map((rect, i) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ItemInteractionLayer, { rect, state: "sibling-hint" }, `sibling-hint-${i}`)),
11907
+ isItemDragging && draggedItemRect && selectedElRef.current !== footerDragRef.current?.draggedEl && selectedElRef.current !== navDragRef.current?.draggedEl && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ItemInteractionLayer, { rect: draggedItemRect, state: "dragging" }),
11908
+ isItemDragging && footerDropSlots.map((slot, i) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
11909
+ "div",
9653
11910
  {
9654
- rect: toolbarRect,
9655
- onAdd: handleAddTopLevelNavItem
9656
- }
9657
- ),
9658
- hoveredItemRect && !hoveredNavContainerRect && !siblingHintRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
9659
- toolbarRect && (toolbarVariant === "link-action" || toolbarVariant === "select-frame") && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
11911
+ className: "pointer-events-none fixed z-2147483646",
11912
+ style: {
11913
+ left: slot.left,
11914
+ top: slot.top,
11915
+ width: slot.width,
11916
+ height: slot.height
11917
+ },
11918
+ children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
11919
+ DropIndicator,
11920
+ {
11921
+ direction: slot.direction,
11922
+ state: activeFooterDropIndex === i ? "dragActive" : "dragIdle",
11923
+ className: "!h-full !w-full"
11924
+ }
11925
+ )
11926
+ },
11927
+ `footer-drop-${slot.direction}-${slot.columnIndex}-${slot.insertIndex}-${i}`
11928
+ )),
11929
+ isItemDragging && navDropSlots.map((slot, i) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
11930
+ "div",
11931
+ {
11932
+ className: "pointer-events-none fixed z-2147483646",
11933
+ style: {
11934
+ left: slot.left,
11935
+ top: slot.top,
11936
+ width: slot.width,
11937
+ height: slot.height
11938
+ },
11939
+ children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
11940
+ DropIndicator,
11941
+ {
11942
+ direction: slot.direction,
11943
+ state: activeNavDropIndex === i ? "dragActive" : "dragIdle",
11944
+ className: "!h-full !w-full"
11945
+ }
11946
+ )
11947
+ },
11948
+ `nav-drop-${slot.direction}-${slot.parentId ?? "root"}-${slot.insertIndex}-${i}`
11949
+ )),
11950
+ hoveredNavContainerRect && toolbarVariant !== "select-frame" && !linkPopover && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
11951
+ hoveredItemRect && !linkPopover && !hoveredNavContainerRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
11952
+ toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isNavbarLinksContainer(selectedElRef.current) && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(NavbarContainerChrome, { rect: toolbarRect, onAdd: handleAddTopLevelNavItem }),
11953
+ toolbarRect && !linkPopover && (toolbarVariant === "link-action" || toolbarVariant === "select-frame") && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9660
11954
  ItemInteractionLayer,
9661
11955
  {
9662
- rect: toolbarRect,
11956
+ rect: isItemDragging && draggedItemRect && (footerDragRef.current?.wasSelected || navDragRef.current?.wasSelected) ? draggedItemRect : toolbarRect,
9663
11957
  elRef: glowElRef,
9664
11958
  state: isItemDragging ? "dragging" : resolveItemInteractionState(toolbarRect, parentScrollRef.current),
9665
- showHandle: toolbarVariant === "link-action" && Boolean(reorderHrefKey),
11959
+ showHandle: toolbarVariant === "link-action" && Boolean(reorderHrefKey) || toolbarVariant === "select-frame" && isFooterFrameSelection,
9666
11960
  dragDisabled: reorderDragDisabled,
9667
11961
  dragHandleLabel: reorderHrefKey ? `Reorder ${reorderHrefKey}` : "Reorder item",
9668
11962
  onDragHandleDragStart: handleItemDragStart,
9669
11963
  onDragHandleDragEnd: handleItemDragEnd,
9670
- toolbar: toolbarVariant === "link-action" && !isItemDragging ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
11964
+ onItemPointerDown: handleItemChromePointerDown,
11965
+ onItemClick: handleItemChromeClick,
11966
+ itemDragSurface: !isFooterFrameSelection,
11967
+ toolbar: toolbarVariant === "link-action" && !isItemDragging ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9671
11968
  ItemActionToolbar,
9672
11969
  {
9673
11970
  onEditLink: openLinkPopoverForSelected,
9674
11971
  addItemDisabled: true,
9675
11972
  editLinkDisabled: false,
9676
- moreDisabled: true
11973
+ moreDisabled: true,
11974
+ showAddItem: !selectedIsCta,
11975
+ showMore: !selectedIsCta
9677
11976
  }
9678
11977
  ) : void 0
9679
11978
  }
9680
11979
  ),
9681
- toolbarRect && toolbarVariant === "rich-text" && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
9682
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
11980
+ toolbarRect && toolbarVariant === "rich-text" && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
11981
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9683
11982
  EditGlowChrome,
9684
11983
  {
9685
11984
  rect: toolbarRect,
9686
11985
  elRef: glowElRef,
9687
11986
  reorderHrefKey,
9688
- dragDisabled: reorderDragDisabled
11987
+ dragDisabled: reorderDragDisabled,
11988
+ hideHandle: isItemDragging
9689
11989
  }
9690
11990
  ),
9691
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
11991
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9692
11992
  FloatingToolbar,
9693
11993
  {
9694
11994
  rect: toolbarRect,
@@ -9701,7 +12001,7 @@ function OhhwellsBridge() {
9701
12001
  }
9702
12002
  )
9703
12003
  ] }),
9704
- maxBadge && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
12004
+ maxBadge && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
9705
12005
  "div",
9706
12006
  {
9707
12007
  "data-ohw-max-badge": "",
@@ -9727,7 +12027,7 @@ function OhhwellsBridge() {
9727
12027
  ]
9728
12028
  }
9729
12029
  ),
9730
- toggleState && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
12030
+ toggleState && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9731
12031
  StateToggle,
9732
12032
  {
9733
12033
  rect: toggleState.rect,
@@ -9736,32 +12036,36 @@ function OhhwellsBridge() {
9736
12036
  onStateChange: handleStateChange
9737
12037
  }
9738
12038
  ),
9739
- sectionGap && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
12039
+ sectionGap && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
9740
12040
  "div",
9741
12041
  {
9742
12042
  "data-ohw-section-insert-line": "",
9743
12043
  className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
9744
12044
  style: { top: sectionGap.y, transform: "translateY(-50%)" },
9745
12045
  children: [
9746
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
9747
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
12046
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
12047
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9748
12048
  Badge,
9749
12049
  {
9750
12050
  className: "px-8 py-1 bg-primary hover:bg-primary text-primary-foreground text-xs font-medium shrink-0 rounded-full cursor-pointer pointer-events-auto",
9751
12051
  onClick: () => {
9752
12052
  window.parent.postMessage(
9753
- { type: "ow:add-section", insertAfter: sectionGap.insertAfter, insertBefore: sectionGap.insertBefore },
12053
+ {
12054
+ type: "ow:add-section",
12055
+ insertAfter: sectionGap.insertAfter,
12056
+ insertBefore: sectionGap.insertBefore
12057
+ },
9754
12058
  "*"
9755
12059
  );
9756
12060
  },
9757
12061
  children: "Add Section"
9758
12062
  }
9759
12063
  ),
9760
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
12064
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
9761
12065
  ]
9762
12066
  }
9763
12067
  ),
9764
- linkPopover && dialogPortalContainer ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
12068
+ linkPopover && dialogPortalContainer ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9765
12069
  LinkPopover,
9766
12070
  {
9767
12071
  panelRef: linkPopoverPanelRef,
@@ -9782,48 +12086,6 @@ function OhhwellsBridge() {
9782
12086
  bridgeRoot
9783
12087
  ) : null;
9784
12088
  }
9785
-
9786
- // src/ui/drop-indicator.tsx
9787
- var React10 = __toESM(require("react"), 1);
9788
- var import_jsx_runtime24 = require("react/jsx-runtime");
9789
- var dropIndicatorVariants = cva(
9790
- "ov-gap-line pointer-events-none shrink-0 transition-opacity duration-150",
9791
- {
9792
- variants: {
9793
- direction: {
9794
- vertical: "h-6 w-[3px]",
9795
- horizontal: "h-[3px] w-[200px]"
9796
- },
9797
- state: {
9798
- default: "opacity-0",
9799
- hover: "opacity-100",
9800
- dragIdle: "opacity-40",
9801
- dragActive: "opacity-100"
9802
- }
9803
- },
9804
- defaultVariants: {
9805
- direction: "vertical",
9806
- state: "default"
9807
- }
9808
- }
9809
- );
9810
- var DropIndicator = React10.forwardRef(
9811
- ({ className, direction, state, ...props }, ref) => {
9812
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9813
- "div",
9814
- {
9815
- ref,
9816
- "data-slot": "drop-indicator",
9817
- "data-direction": direction ?? "vertical",
9818
- "data-state": state ?? "default",
9819
- "aria-hidden": "true",
9820
- className: cn(dropIndicatorVariants({ direction, state }), className),
9821
- ...props
9822
- }
9823
- );
9824
- }
9825
- );
9826
- DropIndicator.displayName = "DropIndicator";
9827
12089
  // Annotate the CommonJS export names for ESM import in node:
9828
12090
  0 && (module.exports = {
9829
12091
  CustomToolbar,