@ohhwells/bridge 0.1.44 → 0.1.46-next.109

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  // src/OhhwellsBridge.tsx
4
- import React10, { useCallback as useCallback3, useEffect as useEffect7, useLayoutEffect as useLayoutEffect3, useRef as useRef6, useState as useState6 } from "react";
4
+ import React11, { useCallback as useCallback5, useEffect as useEffect8, useLayoutEffect as useLayoutEffect3, useRef as useRef7, useState as useState7 } from "react";
5
5
  import { createRoot } from "react-dom/client";
6
6
  import { flushSync } from "react-dom";
7
7
 
@@ -48,12 +48,13 @@ function useLinkHrefGuardian(...deps) {
48
48
  }
49
49
 
50
50
  // src/ui/SchedulingWidget.tsx
51
- import { useEffect, useId, useMemo, useRef, useState as useState2 } from "react";
51
+ import { useCallback, useEffect, useId, useMemo, useRef, useState as useState2 } from "react";
52
52
 
53
53
  // src/ui/EmailCaptureModal.tsx
54
54
  import { useState } from "react";
55
55
  import { Dialog } from "radix-ui";
56
56
  import { jsx, jsxs } from "react/jsx-runtime";
57
+ var isValidEmail = (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
57
58
  function Spinner() {
58
59
  return /* @__PURE__ */ jsxs(
59
60
  "svg",
@@ -87,12 +88,17 @@ function EmailCaptureModal({ title, subtitle, onSubmit, onClose }) {
87
88
  const [error, setError] = useState(null);
88
89
  const isBook = title === "Confirm your spot";
89
90
  const handleSubmit = async () => {
90
- if (!email.trim()) return;
91
+ const trimmed = email.trim();
92
+ if (!trimmed) return;
93
+ if (!isValidEmail(trimmed)) {
94
+ setError("Please enter a valid email address.");
95
+ return;
96
+ }
91
97
  setLoading(true);
92
98
  setError(null);
93
99
  try {
94
- await onSubmit(email.trim());
95
- setSubmittedEmail(email.trim());
100
+ await onSubmit(trimmed);
101
+ setSubmittedEmail(trimmed);
96
102
  setSuccess(true);
97
103
  } catch (e) {
98
104
  setError(e instanceof Error ? e.message : "Something went wrong. Please try again.");
@@ -167,7 +173,10 @@ function EmailCaptureModal({ title, subtitle, onSubmit, onClose }) {
167
173
  {
168
174
  type: "email",
169
175
  value: email,
170
- onChange: (e) => setEmail(e.target.value),
176
+ onChange: (e) => {
177
+ setEmail(e.target.value);
178
+ if (error) setError(null);
179
+ },
171
180
  onKeyDown: handleKeyDown,
172
181
  placeholder: "hello@gmail.com",
173
182
  autoFocus: true,
@@ -224,12 +233,20 @@ function formatClassTime(cls) {
224
233
  const em = endTotal % 60;
225
234
  return `${h}:${cls.startTime.minutes}-${eh}:${String(em).padStart(2, "0")}`;
226
235
  }
236
+ function formatBookingLabel(cls) {
237
+ const price = cls.price;
238
+ const isFree = price === null || price === void 0 || `${price}` === "" || `${price}` === "0";
239
+ return isFree ? "Book for free" : `Book for ${cls.currency ?? ""} ${price}`.trim();
240
+ }
227
241
  function getBookingsOnDate(cls, date) {
228
242
  if (!cls.bookings?.length) return 0;
229
- const ds = date.toISOString().split("T")[0];
243
+ const target = new Date(date);
244
+ target.setHours(0, 0, 0, 0);
230
245
  return cls.bookings.filter((b) => {
231
246
  try {
232
- return new Date(b.classDate).toISOString().split("T")[0] === ds;
247
+ const bookingDate = new Date(b.classDate);
248
+ bookingDate.setHours(0, 0, 0, 0);
249
+ return bookingDate.getTime() === target.getTime();
233
250
  } catch {
234
251
  return false;
235
252
  }
@@ -494,7 +511,7 @@ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal
494
511
  }
495
512
  )
496
513
  ] }),
497
- /* @__PURE__ */ jsx2("div", { className: "sm:hidden flex flex-col gap-px", children: isFull ? /* @__PURE__ */ jsx2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
514
+ cls.showAvailability !== false && /* @__PURE__ */ jsx2("div", { className: "sm:hidden flex flex-col gap-px", children: isFull ? /* @__PURE__ */ jsx2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
498
515
  /* @__PURE__ */ jsxs2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
499
516
  available,
500
517
  "/",
@@ -506,9 +523,17 @@ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal
506
523
  /* @__PURE__ */ jsxs2("div", { className: "flex flex-1 flex-col gap-2 min-w-0 sm:contents", children: [
507
524
  /* @__PURE__ */ jsxs2("div", { className: "flex-1 flex flex-col gap-2 min-w-0", children: [
508
525
  /* @__PURE__ */ jsx2("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block truncate", children: cls.name }),
509
- cls.hostName && /* @__PURE__ */ jsx2("span", { className: "font-body text-base font-normal text-(--color-dark,#200C02) opacity-80 block truncate", children: cls.hostName })
526
+ cls.hostName && /* @__PURE__ */ jsx2("span", { className: "font-body text-base font-normal text-(--color-dark,#200C02) opacity-80 block truncate", children: cls.hostName }),
527
+ cls.description && /* @__PURE__ */ jsx2(
528
+ "span",
529
+ {
530
+ className: "font-body text-base font-normal text-(--color-dark,#200C02) opacity-80 block",
531
+ style: { wordBreak: "break-word" },
532
+ children: cls.description
533
+ }
534
+ )
510
535
  ] }),
511
- /* @__PURE__ */ jsx2("div", { className: "hidden sm:flex w-14 shrink-0 flex-col gap-px", children: isFull ? /* @__PURE__ */ jsx2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
536
+ cls.showAvailability !== false && /* @__PURE__ */ jsx2("div", { className: "hidden sm:flex w-14 shrink-0 flex-col gap-px", children: isFull ? /* @__PURE__ */ jsx2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
512
537
  /* @__PURE__ */ jsxs2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
513
538
  available,
514
539
  "/",
@@ -533,7 +558,7 @@ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal
533
558
  if (!cls.id) return;
534
559
  onOpenModal?.(isFull ? "waitlist" : "book", cls.id, selectedDate);
535
560
  },
536
- children: isFull ? "Join Waitlist" : "Book Now"
561
+ children: isFull ? "Join Waitlist" : formatBookingLabel(cls)
537
562
  }
538
563
  )
539
564
  ] })
@@ -575,6 +600,17 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
575
600
  const [isHovered, setIsHovered] = useState2(false);
576
601
  const [modalState, setModalState] = useState2(null);
577
602
  const switchScheduleIdRef = useRef(null);
603
+ const liveScheduleIdRef = useRef(null);
604
+ const refetchLiveSchedule = useCallback(async () => {
605
+ const id = liveScheduleIdRef.current;
606
+ if (!id) return;
607
+ try {
608
+ const res = await fetch(`${API_URL}/api/schedule/id/${id}`);
609
+ const data = await res.json();
610
+ if (data?.id) setSchedule(data);
611
+ } catch {
612
+ }
613
+ }, []);
578
614
  const dates = useMemo(() => {
579
615
  const today = /* @__PURE__ */ new Date();
580
616
  today.setHours(0, 0, 0, 0);
@@ -594,6 +630,18 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
594
630
  }
595
631
  }
596
632
  }, [schedule, dates]);
633
+ useEffect(() => {
634
+ if (typeof window === "undefined") return;
635
+ const onVisible = () => {
636
+ if (!document.hidden) void refetchLiveSchedule();
637
+ };
638
+ window.addEventListener("focus", refetchLiveSchedule);
639
+ document.addEventListener("visibilitychange", onVisible);
640
+ return () => {
641
+ window.removeEventListener("focus", refetchLiveSchedule);
642
+ document.removeEventListener("visibilitychange", onVisible);
643
+ };
644
+ }, [refetchLiveSchedule]);
597
645
  useEffect(() => {
598
646
  if (typeof window === "undefined") return;
599
647
  const isInEditor = window.self !== window.top;
@@ -655,7 +703,7 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
655
703
  setLoading(false);
656
704
  return;
657
705
  }
658
- ;
706
+ liveScheduleIdRef.current = effectiveId;
659
707
  (async () => {
660
708
  try {
661
709
  const res = await fetch(`${API_URL}/api/schedule/id/${effectiveId}`);
@@ -712,18 +760,14 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
712
760
  const data = await res.json().catch(() => ({}));
713
761
  throw new Error(data?.message ?? "Something went wrong. Please try again.");
714
762
  }
763
+ await refetchLiveSchedule();
715
764
  };
716
765
  const handleReplaceSchedule = () => {
717
766
  setIsHovered(false);
718
- if (schedule) {
719
- window.parent.postMessage({
720
- type: "ow:schedule-connected",
721
- schedule: { id: schedule.id, name: schedule.name },
722
- insertAfter
723
- }, "*");
724
- } else {
725
- window.parent.postMessage({ type: "ow:scheduling-not-connected", insertAfter }, "*");
726
- }
767
+ window.parent.postMessage(
768
+ { type: "ow:scheduling-not-connected", insertAfter, currentScheduleId: schedule?.id },
769
+ "*"
770
+ );
727
771
  };
728
772
  if (!inEditor && !loading && !schedule) return null;
729
773
  const sectionId = `scheduling-${insertAfter}`;
@@ -766,7 +810,19 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
766
810
  ),
767
811
  /* @__PURE__ */ jsxs2("div", { className: "max-w-[1280px] mx-auto flex flex-col items-center gap-16", children: [
768
812
  /* @__PURE__ */ jsxs2("div", { className: "flex flex-col items-center gap-4", children: [
769
- /* @__PURE__ */ jsx2("h2", { className: "font-display text-4xl sm:text-5xl font-bold text-center m-0 text-(--color-dark,#200C02)", children: schedule?.name ?? "Book an appointment" }),
813
+ /* @__PURE__ */ jsx2(
814
+ "h2",
815
+ {
816
+ className: "font-display text-center m-0 text-(--color-dark,#200C02)",
817
+ style: {
818
+ fontSize: "var(--fs-section-h2, clamp(40px, 4.4vw, 64px))",
819
+ fontWeight: "var(--font-weight-heading, 400)",
820
+ lineHeight: 1.05,
821
+ letterSpacing: "-0.02em"
822
+ },
823
+ children: schedule?.name ?? "Book an appointment"
824
+ }
825
+ ),
770
826
  schedule?.description && /* @__PURE__ */ jsx2("p", { className: "font-body text-body text-center m-0 text-(--color-accent,#A89B83)", children: schedule.description })
771
827
  ] }),
772
828
  /* @__PURE__ */ jsxs2("div", { className: "w-full flex flex-col items-end gap-3", children: [
@@ -4286,7 +4342,7 @@ import { Link, MoreHorizontal, Plus } from "lucide-react";
4286
4342
 
4287
4343
  // src/ui/tooltip.tsx
4288
4344
  import { Tooltip as TooltipPrimitive } from "radix-ui";
4289
- import { jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
4345
+ import { jsx as jsx7 } from "react/jsx-runtime";
4290
4346
  function TooltipProvider({
4291
4347
  delayDuration = 0,
4292
4348
  ...props
@@ -4299,39 +4355,45 @@ function Tooltip({ ...props }) {
4299
4355
  function TooltipTrigger({ ...props }) {
4300
4356
  return /* @__PURE__ */ jsx7(TooltipPrimitive.Trigger, { "data-slot": "tooltip-trigger", ...props });
4301
4357
  }
4358
+ var arrowClassBySide = {
4359
+ top: "before:bottom-0 before:left-1/2 before:-translate-x-1/2 before:translate-y-1/2",
4360
+ bottom: "before:top-0 before:left-1/2 before:-translate-x-1/2 before:-translate-y-1/2",
4361
+ left: "before:right-0 before:top-1/2 before:-translate-y-1/2 before:translate-x-1/2",
4362
+ right: "before:left-0 before:top-1/2 before:-translate-y-1/2 before:-translate-x-1/2"
4363
+ };
4302
4364
  function TooltipContent({
4303
4365
  className,
4304
- sideOffset = 3,
4366
+ sideOffset = 9,
4305
4367
  side = "bottom",
4306
4368
  children,
4307
4369
  hideArrow,
4308
4370
  ...props
4309
4371
  }) {
4310
- return /* @__PURE__ */ jsx7(TooltipPrimitive.Portal, { children: /* @__PURE__ */ jsxs3(
4372
+ const resolvedSide = side ?? "bottom";
4373
+ return /* @__PURE__ */ jsx7(TooltipPrimitive.Portal, { children: /* @__PURE__ */ jsx7(
4311
4374
  TooltipPrimitive.Content,
4312
4375
  {
4313
4376
  "data-slot": "tooltip-content",
4314
4377
  side,
4315
4378
  sideOffset,
4316
4379
  className: cn(
4317
- "z-[2147483647] inline-flex w-fit max-w-xs items-center rounded-md bg-foreground px-3 py-1.5 text-xs text-background",
4380
+ "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",
4318
4381
  "origin-[var(--radix-tooltip-content-transform-origin)] animate-in fade-in-0 zoom-in-95",
4319
4382
  "data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
4320
4383
  "data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2",
4321
4384
  "data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2",
4385
+ !hideArrow && 'before:pointer-events-none before:absolute before:size-2 before:rotate-45 before:bg-foreground before:content-[""]',
4386
+ !hideArrow && arrowClassBySide[resolvedSide],
4322
4387
  className
4323
4388
  ),
4324
4389
  ...props,
4325
- children: [
4326
- children,
4327
- !hideArrow && /* @__PURE__ */ jsx7(TooltipPrimitive.Arrow, { className: "z-[2147483647] size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground" })
4328
- ]
4390
+ children
4329
4391
  }
4330
4392
  ) });
4331
4393
  }
4332
4394
 
4333
4395
  // src/ui/item-action-toolbar.tsx
4334
- import { jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
4396
+ import { jsx as jsx8, jsxs as jsxs3 } from "react/jsx-runtime";
4335
4397
  function ToolbarActionTooltip({
4336
4398
  label,
4337
4399
  side = "bottom",
@@ -4340,9 +4402,9 @@ function ToolbarActionTooltip({
4340
4402
  children
4341
4403
  }) {
4342
4404
  const button = /* @__PURE__ */ jsx8(CustomToolbarButton, { type: "button", "aria-label": label, disabled, ...buttonProps, children });
4343
- return /* @__PURE__ */ jsxs4(Tooltip, { children: [
4405
+ return /* @__PURE__ */ jsxs3(Tooltip, { children: [
4344
4406
  /* @__PURE__ */ jsx8(TooltipTrigger, { asChild: true, children: disabled ? /* @__PURE__ */ jsx8("span", { className: "inline-flex", children: button }) : button }),
4345
- /* @__PURE__ */ jsx8(TooltipContent, { side, sideOffset: 3, children: label })
4407
+ /* @__PURE__ */ jsx8(TooltipContent, { side, sideOffset: 9, children: label })
4346
4408
  ] });
4347
4409
  }
4348
4410
  function ItemActionToolbar({
@@ -4356,7 +4418,7 @@ function ItemActionToolbar({
4356
4418
  showMore = true,
4357
4419
  tooltipSide = "bottom"
4358
4420
  }) {
4359
- return /* @__PURE__ */ jsx8(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ jsxs4(CustomToolbar, { children: [
4421
+ return /* @__PURE__ */ jsx8(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ jsxs3(CustomToolbar, { children: [
4360
4422
  /* @__PURE__ */ jsx8(
4361
4423
  ToolbarActionTooltip,
4362
4424
  {
@@ -4417,13 +4479,26 @@ function ItemActionToolbar({
4417
4479
 
4418
4480
  // src/ui/item-interaction-layer.tsx
4419
4481
  import * as React5 from "react";
4420
- import { jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
4482
+ import { jsx as jsx9, jsxs as jsxs4 } from "react/jsx-runtime";
4421
4483
  var PRIMARY = "var(--ohw-primary, #0885FE)";
4422
4484
  var FOCUS_RING = `0 0 0 4px color-mix(in srgb, ${PRIMARY} 12%, transparent)`;
4423
4485
  var DRAG_SHADOW = "0px 4px 6px -1px rgba(0, 0, 0, 0.1), 0px 2px 4px -2px rgba(0, 0, 0, 0.1)";
4424
4486
  var TOOLBAR_EDGE_MARGIN = 4;
4425
4487
  var SELECTION_CHROME_GAP = 4;
4426
4488
  var TOOLBAR_STROKE_GAP = 4;
4489
+ function getChromeZIndex(state) {
4490
+ switch (state) {
4491
+ case "active-top":
4492
+ case "active-bottom":
4493
+ case "dragging":
4494
+ return 2147483644;
4495
+ case "hover":
4496
+ case "sibling-hint":
4497
+ return 2147483643;
4498
+ default:
4499
+ return 2147483642;
4500
+ }
4501
+ }
4427
4502
  function getChromeStyle(state) {
4428
4503
  switch (state) {
4429
4504
  case "hover":
@@ -4445,7 +4520,7 @@ function getChromeStyle(state) {
4445
4520
  case "dragging":
4446
4521
  return {
4447
4522
  border: `2px solid ${PRIMARY}`,
4448
- boxShadow: DRAG_SHADOW
4523
+ boxShadow: `${FOCUS_RING}, ${DRAG_SHADOW}`
4449
4524
  };
4450
4525
  default:
4451
4526
  return {};
@@ -4514,6 +4589,9 @@ function ItemInteractionLayer({
4514
4589
  dragHandleLabel = "Reorder item",
4515
4590
  onDragHandleDragStart,
4516
4591
  onDragHandleDragEnd,
4592
+ onItemPointerDown,
4593
+ onItemClick,
4594
+ itemDragSurface = true,
4517
4595
  chromeGap = SELECTION_CHROME_GAP,
4518
4596
  className
4519
4597
  }) {
@@ -4521,8 +4599,9 @@ function ItemInteractionLayer({
4521
4599
  const isActive = state === "active-top" || state === "active-bottom";
4522
4600
  const isDragging = state === "dragging";
4523
4601
  const showToolbar = isActive && toolbar;
4524
- const showDragHandle = isActive && showHandle && !isDragging;
4525
- return /* @__PURE__ */ jsxs5(
4602
+ const showDragHandle = (isActive || isDragging) && showHandle;
4603
+ const itemDragEnabled = itemDragSurface && isActive && showHandle && !isDragging && !dragDisabled;
4604
+ return /* @__PURE__ */ jsxs4(
4526
4605
  "div",
4527
4606
  {
4528
4607
  ref: elRef,
@@ -4535,7 +4614,7 @@ function ItemInteractionLayer({
4535
4614
  left: rect.left - chromeGap,
4536
4615
  width: rect.width + chromeGap * 2,
4537
4616
  height: rect.height + chromeGap * 2,
4538
- zIndex: 2147483646
4617
+ zIndex: getChromeZIndex(state)
4539
4618
  },
4540
4619
  children: [
4541
4620
  /* @__PURE__ */ jsx9(
@@ -4546,11 +4625,29 @@ function ItemInteractionLayer({
4546
4625
  style: getChromeStyle(state)
4547
4626
  }
4548
4627
  ),
4628
+ itemDragEnabled && /* @__PURE__ */ jsx9(
4629
+ "div",
4630
+ {
4631
+ "data-ohw-item-drag-surface": "",
4632
+ className: "pointer-events-auto absolute inset-0 cursor-text rounded-lg",
4633
+ onPointerDown: (e) => {
4634
+ if (e.button !== 0) return;
4635
+ onItemPointerDown?.(e);
4636
+ },
4637
+ onClick: (e) => {
4638
+ e.preventDefault();
4639
+ e.stopPropagation();
4640
+ onItemClick?.(e.clientX, e.clientY);
4641
+ }
4642
+ }
4643
+ ),
4549
4644
  showDragHandle && /* @__PURE__ */ jsx9(
4550
4645
  "div",
4551
4646
  {
4552
4647
  "data-ohw-drag-handle-container": "",
4553
- className: "pointer-events-auto absolute left-0 top-1/2 -translate-x-[calc(100%+7px)] -translate-y-1/2",
4648
+ className: "pointer-events-auto absolute left-0 top-1/2 z-10 -translate-x-[calc(100%+7px)] -translate-y-1/2",
4649
+ style: isDragging ? { opacity: 0 } : void 0,
4650
+ "aria-hidden": isDragging || void 0,
4554
4651
  children: /* @__PURE__ */ jsx9(
4555
4652
  DragHandle,
4556
4653
  {
@@ -4562,7 +4659,17 @@ function ItemInteractionLayer({
4562
4659
  e.preventDefault();
4563
4660
  return;
4564
4661
  }
4565
- e.dataTransfer?.setData("text/plain", dragHandleLabel);
4662
+ const blank = document.createElement("canvas");
4663
+ blank.width = 1;
4664
+ blank.height = 1;
4665
+ blank.style.cssText = "position:fixed;left:-9999px;top:-9999px;width:1px;height:1px;pointer-events:none";
4666
+ document.body.appendChild(blank);
4667
+ try {
4668
+ e.dataTransfer.setDragImage(blank, 0, 0);
4669
+ } finally {
4670
+ requestAnimationFrame(() => blank.remove());
4671
+ }
4672
+ e.dataTransfer.setData("text/plain", dragHandleLabel);
4566
4673
  e.dataTransfer.effectAllowed = "move";
4567
4674
  onDragHandleDragStart?.(e);
4568
4675
  },
@@ -4623,7 +4730,7 @@ var Button = React6.forwardRef(
4623
4730
  Button.displayName = "Button";
4624
4731
 
4625
4732
  // src/ui/MediaOverlay.tsx
4626
- import { Fragment as Fragment2, jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
4733
+ import { Fragment as Fragment2, jsx as jsx11, jsxs as jsxs5 } from "react/jsx-runtime";
4627
4734
  var MEDIA_UPLOAD_FADE_MS = 300;
4628
4735
  var VIDEO_SETTINGS_BAR_INSET = 8;
4629
4736
  var OVERLAY_BUTTON_STYLE = {
@@ -4694,7 +4801,7 @@ function MediaOverlay({
4694
4801
  }
4695
4802
  );
4696
4803
  }
4697
- const settingsBar = isVideo && !hover.isDragOver ? /* @__PURE__ */ jsxs6(
4804
+ const settingsBar = isVideo && !hover.isDragOver ? /* @__PURE__ */ jsxs5(
4698
4805
  "div",
4699
4806
  {
4700
4807
  "data-ohw-bridge": "",
@@ -4749,7 +4856,7 @@ function MediaOverlay({
4749
4856
  ]
4750
4857
  }
4751
4858
  ) : null;
4752
- return /* @__PURE__ */ jsxs6(Fragment2, { children: [
4859
+ return /* @__PURE__ */ jsxs5(Fragment2, { children: [
4753
4860
  settingsBar,
4754
4861
  /* @__PURE__ */ jsx11(
4755
4862
  "div",
@@ -4768,14 +4875,18 @@ function MediaOverlay({
4768
4875
  background: "color-mix(in srgb, var(--color-primary) 20%, transparent)"
4769
4876
  },
4770
4877
  onClick: () => onReplace(hover.key),
4771
- children: /* @__PURE__ */ jsxs6(
4878
+ children: /* @__PURE__ */ jsxs5(
4772
4879
  Button,
4773
4880
  {
4774
4881
  "data-ohw-media-overlay": "",
4775
4882
  variant: "outline",
4776
4883
  size: "sm",
4777
4884
  className: "gap-1.5 cursor-pointer hover:bg-background",
4778
- style: OVERLAY_BUTTON_STYLE,
4885
+ style: {
4886
+ ...OVERLAY_BUTTON_STYLE,
4887
+ // Don't let Replace steal clicks when text (e.g. nav) sits on top of this media.
4888
+ pointerEvents: hover.hasTextOverlap ? "none" : "auto"
4889
+ },
4779
4890
  onMouseDown: (e) => e.preventDefault(),
4780
4891
  onClick: (e) => {
4781
4892
  e.stopPropagation();
@@ -5078,7 +5189,7 @@ import { useEffect as useEffect6 } from "react";
5078
5189
  import * as React8 from "react";
5079
5190
  import { Dialog as DialogPrimitive } from "radix-ui";
5080
5191
  import { X } from "lucide-react";
5081
- import { jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
5192
+ import { jsx as jsx12, jsxs as jsxs6 } from "react/jsx-runtime";
5082
5193
  function Dialog2({
5083
5194
  ...props
5084
5195
  }) {
@@ -5106,9 +5217,9 @@ function DialogOverlay({
5106
5217
  var DialogContent = React8.forwardRef(
5107
5218
  ({ className, children, showCloseButton = true, container, ...props }, ref) => {
5108
5219
  const positionMode = container ? "absolute" : "fixed";
5109
- return /* @__PURE__ */ jsxs7(DialogPortal, { container: container ?? void 0, children: [
5220
+ return /* @__PURE__ */ jsxs6(DialogPortal, { container: container ?? void 0, children: [
5110
5221
  /* @__PURE__ */ jsx12(DialogOverlay, { className: cn(positionMode, "inset-0") }),
5111
- /* @__PURE__ */ jsxs7(
5222
+ /* @__PURE__ */ jsxs6(
5112
5223
  DialogPrimitive.Content,
5113
5224
  {
5114
5225
  ref,
@@ -5186,15 +5297,15 @@ import { Info, X as X3 } from "lucide-react";
5186
5297
 
5187
5298
  // src/ui/link-modal/DestinationBreadcrumb.tsx
5188
5299
  import { ArrowRight, File, GalleryVertical } from "lucide-react";
5189
- import { jsx as jsx13, jsxs as jsxs8 } from "react/jsx-runtime";
5300
+ import { jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
5190
5301
  function DestinationBreadcrumb({
5191
5302
  pageTitle,
5192
5303
  sectionLabel
5193
5304
  }) {
5194
- return /* @__PURE__ */ jsxs8("div", { className: "flex w-full flex-col gap-2", children: [
5305
+ return /* @__PURE__ */ jsxs7("div", { className: "flex w-full flex-col gap-2", children: [
5195
5306
  /* @__PURE__ */ jsx13("p", { className: "text-sm font-medium! text-foreground m-0", children: "Destination" }),
5196
- /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-3", children: [
5197
- /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-2", children: [
5307
+ /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-3", children: [
5308
+ /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-2", children: [
5198
5309
  /* @__PURE__ */ jsx13(File, { size: 16, className: "shrink-0 text-foreground", "aria-hidden": true }),
5199
5310
  /* @__PURE__ */ jsx13("span", { className: "text-sm font-medium leading-none text-foreground!", children: pageTitle })
5200
5311
  ] }),
@@ -5206,7 +5317,7 @@ function DestinationBreadcrumb({
5206
5317
  "aria-hidden": true
5207
5318
  }
5208
5319
  ),
5209
- /* @__PURE__ */ jsxs8("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [
5320
+ /* @__PURE__ */ jsxs7("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [
5210
5321
  /* @__PURE__ */ jsx13(
5211
5322
  GalleryVertical,
5212
5323
  {
@@ -5223,14 +5334,14 @@ function DestinationBreadcrumb({
5223
5334
 
5224
5335
  // src/ui/link-modal/SectionTreeItem.tsx
5225
5336
  import { GalleryVertical as GalleryVertical2 } from "lucide-react";
5226
- import { jsx as jsx14, jsxs as jsxs9 } from "react/jsx-runtime";
5337
+ import { jsx as jsx14, jsxs as jsxs8 } from "react/jsx-runtime";
5227
5338
  function SectionTreeItem({
5228
5339
  section,
5229
5340
  onSelect,
5230
5341
  selected
5231
5342
  }) {
5232
5343
  const interactive = Boolean(onSelect);
5233
- return /* @__PURE__ */ jsxs9("div", { className: "flex h-9 w-full items-end pl-3", children: [
5344
+ return /* @__PURE__ */ jsxs8("div", { className: "flex h-9 w-full items-end pl-3", children: [
5234
5345
  /* @__PURE__ */ jsx14(
5235
5346
  "div",
5236
5347
  {
@@ -5238,7 +5349,7 @@ function SectionTreeItem({
5238
5349
  "aria-hidden": true
5239
5350
  }
5240
5351
  ),
5241
- /* @__PURE__ */ jsxs9(
5352
+ /* @__PURE__ */ jsxs8(
5242
5353
  "div",
5243
5354
  {
5244
5355
  role: interactive ? "button" : void 0,
@@ -5312,7 +5423,7 @@ function Label({ className, ...props }) {
5312
5423
 
5313
5424
  // src/ui/link-modal/UrlOrPageInput.tsx
5314
5425
  import { ChevronDown, File as File2, X as X2 } from "lucide-react";
5315
- import { jsx as jsx17, jsxs as jsxs10 } from "react/jsx-runtime";
5426
+ import { jsx as jsx17, jsxs as jsxs9 } from "react/jsx-runtime";
5316
5427
  function FieldChevron({
5317
5428
  onClick
5318
5429
  }) {
@@ -5385,10 +5496,10 @@ function UrlOrPageInput({
5385
5496
  "data-ohw-link-field flex h-[36px] w-full items-center overflow-hidden rounded-md border bg-background pl-3 pr-3 py-2 outline-none transition-[border-color,box-shadow]",
5386
5497
  urlError ? "border-destructive shadow-[0_0_0_1px_var(--ohw-destructive)]" : isFocused ? "border-primary shadow-[0_0_0_1px_var(--ohw-primary)]" : "border-input"
5387
5498
  );
5388
- return /* @__PURE__ */ jsxs10("div", { className: "flex w-full flex-col gap-2 p-0", children: [
5499
+ return /* @__PURE__ */ jsxs9("div", { className: "flex w-full flex-col gap-2 p-0", children: [
5389
5500
  /* @__PURE__ */ jsx17(Label, { htmlFor: inputId, className: cn(urlError && "text-destructive"), children: "Destination" }),
5390
- /* @__PURE__ */ jsxs10("div", { ref: rootRef, className: "relative w-full", children: [
5391
- /* @__PURE__ */ jsxs10("div", { "data-ohw-link-field": true, className: fieldClassName, children: [
5501
+ /* @__PURE__ */ jsxs9("div", { ref: rootRef, className: "relative w-full", children: [
5502
+ /* @__PURE__ */ jsxs9("div", { "data-ohw-link-field": true, className: fieldClassName, children: [
5392
5503
  selectedPage ? /* @__PURE__ */ jsx17("div", { className: "flex shrink-0 items-center pr-2", children: /* @__PURE__ */ jsx17(
5393
5504
  File2,
5394
5505
  {
@@ -5442,7 +5553,7 @@ function UrlOrPageInput({
5442
5553
  "data-ohw-link-page-dropdown": "",
5443
5554
  className: "absolute left-0 right-0 top-[calc(100%+4px)] z-50 max-h-48 overflow-auto rounded-lg border border-border bg-popover py-1 shadow-lg",
5444
5555
  onMouseDown: (e) => e.preventDefault(),
5445
- children: filteredPages.map((page) => /* @__PURE__ */ jsxs10(
5556
+ children: filteredPages.map((page) => /* @__PURE__ */ jsxs9(
5446
5557
  "button",
5447
5558
  {
5448
5559
  type: "button",
@@ -5463,10 +5574,10 @@ function UrlOrPageInput({
5463
5574
  }
5464
5575
 
5465
5576
  // src/ui/link-modal/LinkEditorPanel.tsx
5466
- import { Fragment as Fragment3, jsx as jsx18, jsxs as jsxs11 } from "react/jsx-runtime";
5577
+ import { Fragment as Fragment3, jsx as jsx18, jsxs as jsxs10 } from "react/jsx-runtime";
5467
5578
  function LinkEditorPanel({ state, onClose }) {
5468
5579
  const isCancel = state.secondaryLabel === "Cancel" || state.secondaryLabel === "Back to sections";
5469
- return /* @__PURE__ */ jsxs11(Fragment3, { children: [
5580
+ return /* @__PURE__ */ jsxs10(Fragment3, { children: [
5470
5581
  /* @__PURE__ */ jsx18(DialogClose, { asChild: true, children: /* @__PURE__ */ jsx18(
5471
5582
  "button",
5472
5583
  {
@@ -5478,7 +5589,7 @@ function LinkEditorPanel({ state, onClose }) {
5478
5589
  }
5479
5590
  ) }),
5480
5591
  /* @__PURE__ */ jsx18(DialogHeader, { className: "w-full gap-1.5 p-6 pr-12", children: /* @__PURE__ */ jsx18(DialogTitle, { className: "m-0 w-full break-words", children: state.title }) }),
5481
- /* @__PURE__ */ jsxs11("div", { className: "flex w-full flex-col gap-3 px-6 pb-8 pt-1", children: [
5592
+ /* @__PURE__ */ jsxs10("div", { className: "flex w-full flex-col gap-3 px-6 pb-8 pt-1", children: [
5482
5593
  state.showBreadcrumb && state.selectedPage && state.selectedSection ? /* @__PURE__ */ jsx18(
5483
5594
  DestinationBreadcrumb,
5484
5595
  {
@@ -5498,7 +5609,7 @@ function LinkEditorPanel({ state, onClose }) {
5498
5609
  urlError: state.urlError
5499
5610
  }
5500
5611
  ),
5501
- state.showChooseSection ? /* @__PURE__ */ jsxs11("div", { className: "flex flex-col justify-center gap-2", children: [
5612
+ state.showChooseSection ? /* @__PURE__ */ jsxs10("div", { className: "flex flex-col justify-center gap-2", children: [
5502
5613
  /* @__PURE__ */ jsx18(
5503
5614
  Button,
5504
5615
  {
@@ -5510,14 +5621,14 @@ function LinkEditorPanel({ state, onClose }) {
5510
5621
  children: "Choose a section"
5511
5622
  }
5512
5623
  ),
5513
- /* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-1 text-sm text-muted-foreground", children: [
5624
+ /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-1 text-sm text-muted-foreground", children: [
5514
5625
  /* @__PURE__ */ jsx18(Info, { size: 16, className: "shrink-0", "aria-hidden": true }),
5515
5626
  /* @__PURE__ */ jsx18("span", { children: "Pick a section this link should scroll to." })
5516
5627
  ] })
5517
5628
  ] }) : null,
5518
5629
  state.showSectionRow && state.selectedSection ? /* @__PURE__ */ jsx18(SectionTreeItem, { section: state.selectedSection, selected: true }) : null
5519
5630
  ] }),
5520
- /* @__PURE__ */ jsxs11(DialogFooter, { className: "w-full px-6 pb-6", children: [
5631
+ /* @__PURE__ */ jsxs10(DialogFooter, { className: "w-full px-6 pb-6", children: [
5521
5632
  /* @__PURE__ */ jsx18(
5522
5633
  Button,
5523
5634
  {
@@ -5552,11 +5663,11 @@ function LinkEditorPanel({ state, onClose }) {
5552
5663
  }
5553
5664
 
5554
5665
  // src/ui/link-modal/SectionPickerOverlay.tsx
5555
- import { useCallback, useEffect as useEffect4, useMemo as useMemo2, useRef as useRef5, useState as useState4 } from "react";
5666
+ import { useCallback as useCallback2, useEffect as useEffect4, useMemo as useMemo2, useRef as useRef5, useState as useState4 } from "react";
5556
5667
  import { createPortal } from "react-dom";
5557
5668
  import { ArrowLeft, Check } from "lucide-react";
5558
5669
  import { usePathname, useRouter } from "next/navigation";
5559
- import { jsx as jsx19, jsxs as jsxs12 } from "react/jsx-runtime";
5670
+ import { jsx as jsx19, jsxs as jsxs11 } from "react/jsx-runtime";
5560
5671
  var DIM_OVERLAY = "rgba(0, 0, 0, 0.45)";
5561
5672
  function rectsEqual(a, b) {
5562
5673
  if (a.size !== b.size) return false;
@@ -5683,7 +5794,7 @@ function SectionPickerOverlay({
5683
5794
  );
5684
5795
  sectionIdsRef.current = sectionIds;
5685
5796
  const rects = useSectionRects(sectionIdsKey, isOnTargetPage);
5686
- const applyHoverAt = useCallback(
5797
+ const applyHoverAt = useCallback2(
5687
5798
  (x, y, liveRects) => {
5688
5799
  const ids = sectionIdsRef.current;
5689
5800
  const map = liveRects ?? readSectionRects(ids);
@@ -5763,7 +5874,7 @@ function SectionPickerOverlay({
5763
5874
  window.removeEventListener("message", onPointerSync);
5764
5875
  };
5765
5876
  }, [rects, applyHoverAt]);
5766
- const handleSelect = useCallback(
5877
+ const handleSelect = useCallback2(
5767
5878
  (section) => {
5768
5879
  if (selectedId) return;
5769
5880
  setSelectedId(section.id);
@@ -5785,7 +5896,7 @@ function SectionPickerOverlay({
5785
5896
  const portalRoot = typeof document !== "undefined" ? document.querySelector("[data-ohw-bridge-root]") ?? document.body : null;
5786
5897
  if (!portalRoot) return null;
5787
5898
  return createPortal(
5788
- /* @__PURE__ */ jsxs12(
5899
+ /* @__PURE__ */ jsxs11(
5789
5900
  "div",
5790
5901
  {
5791
5902
  "data-ohw-section-picker": "",
@@ -5800,7 +5911,7 @@ function SectionPickerOverlay({
5800
5911
  {
5801
5912
  className: "pointer-events-auto fixed left-5 z-[2]",
5802
5913
  style: { top: chromeClip.top + 20 },
5803
- children: /* @__PURE__ */ jsxs12(
5914
+ children: /* @__PURE__ */ jsxs11(
5804
5915
  Button,
5805
5916
  {
5806
5917
  type: "button",
@@ -5844,7 +5955,7 @@ function SectionPickerOverlay({
5844
5955
  const isSelected = selectedId === section.id;
5845
5956
  const isHovered = hoveredId === section.id;
5846
5957
  const isLit = isSelected || isHovered;
5847
- return /* @__PURE__ */ jsxs12(
5958
+ return /* @__PURE__ */ jsxs11(
5848
5959
  "button",
5849
5960
  {
5850
5961
  type: "button",
@@ -5894,7 +6005,7 @@ function SectionPickerOverlay({
5894
6005
  }
5895
6006
 
5896
6007
  // src/ui/link-modal/useLinkModalState.ts
5897
- import { useCallback as useCallback2, useEffect as useEffect5, useMemo as useMemo3, useState as useState5 } from "react";
6008
+ import { useCallback as useCallback3, useEffect as useEffect5, useMemo as useMemo3, useState as useState5 } from "react";
5898
6009
  function useLinkModalState({
5899
6010
  open,
5900
6011
  mode,
@@ -5913,7 +6024,7 @@ function useLinkModalState({
5913
6024
  const [step, setStep] = useState5("input");
5914
6025
  const [dropdownOpen, setDropdownOpen] = useState5(false);
5915
6026
  const [urlError, setUrlError] = useState5("");
5916
- const reset = useCallback2(() => {
6027
+ const reset = useCallback3(() => {
5917
6028
  setSearchValue("");
5918
6029
  setSelectedPage(null);
5919
6030
  setSelectedSection(null);
@@ -6058,7 +6169,7 @@ function useLinkModalState({
6058
6169
  }
6059
6170
 
6060
6171
  // src/ui/link-modal/LinkPopover.tsx
6061
- import { Fragment as Fragment4, jsx as jsx20, jsxs as jsxs13 } from "react/jsx-runtime";
6172
+ import { Fragment as Fragment4, jsx as jsx20, jsxs as jsxs12 } from "react/jsx-runtime";
6062
6173
  function postToParent(data) {
6063
6174
  window.parent?.postMessage(data, "*");
6064
6175
  }
@@ -6154,7 +6265,7 @@ function LinkPopover({
6154
6265
  );
6155
6266
  };
6156
6267
  }, [open, sectionPickerActive]);
6157
- return /* @__PURE__ */ jsxs13(Fragment4, { children: [
6268
+ return /* @__PURE__ */ jsxs12(Fragment4, { children: [
6158
6269
  /* @__PURE__ */ jsx20(
6159
6270
  Dialog2,
6160
6271
  {
@@ -6258,9 +6369,155 @@ function shouldUseDevFixtures() {
6258
6369
  return new URLSearchParams(q).get("ohw-fixtures") === "1";
6259
6370
  }
6260
6371
 
6372
+ // src/lib/nav-tree.ts
6373
+ var MAX_LEVEL = { nav: 1 };
6374
+ var ROOT = null;
6375
+ function cloneForest(nodes) {
6376
+ return nodes.map((n) => ({ id: n.id, children: cloneForest(n.children) }));
6377
+ }
6378
+ function findNode(nodes, id, parentId = ROOT) {
6379
+ for (let i = 0; i < nodes.length; i++) {
6380
+ const node = nodes[i];
6381
+ if (node.id === id) return { node, parentId, index: i, siblings: nodes };
6382
+ const nested = findNode(node.children, id, node.id);
6383
+ if (nested) return nested;
6384
+ }
6385
+ return null;
6386
+ }
6387
+ function getChildrenOf(nodes, parentId) {
6388
+ if (parentId === ROOT) return nodes;
6389
+ const found = findNode(nodes, parentId);
6390
+ return found ? found.node.children : null;
6391
+ }
6392
+ function isDescendant(nodes, ancestorId, maybeDescendantId) {
6393
+ const found = findNode(nodes, ancestorId);
6394
+ if (!found) return false;
6395
+ return findNode(found.node.children, maybeDescendantId) !== null;
6396
+ }
6397
+ function levelOf(nodes, id) {
6398
+ const loc = findNode(nodes, id);
6399
+ if (!loc) return -1;
6400
+ if (loc.parentId === ROOT) return 0;
6401
+ return 1 + Math.max(0, levelOf(nodes, loc.parentId));
6402
+ }
6403
+ function insertedLevel(nodes, parentId) {
6404
+ if (parentId === ROOT) return 0;
6405
+ const parentLevel = levelOf(nodes, parentId);
6406
+ if (parentLevel < 0) return -1;
6407
+ return parentLevel + 1;
6408
+ }
6409
+ function takeNode(nodes, id) {
6410
+ const forest = cloneForest(nodes);
6411
+ const loc = findNode(forest, id);
6412
+ if (!loc) return null;
6413
+ const [taken] = loc.siblings.splice(loc.index, 1);
6414
+ if (!taken) return null;
6415
+ return { forest, taken };
6416
+ }
6417
+ function insertInto(forest, node, parentId, index) {
6418
+ const siblings = getChildrenOf(forest, parentId);
6419
+ if (!siblings) return false;
6420
+ const clamped = Math.max(0, Math.min(index, siblings.length));
6421
+ siblings.splice(clamped, 0, node);
6422
+ return true;
6423
+ }
6424
+ function normalize(nodes) {
6425
+ const walk = (list) => list.map((n) => {
6426
+ const children = walk(n.children);
6427
+ return { id: n.id, children };
6428
+ });
6429
+ return walk(nodes);
6430
+ }
6431
+ function moveNode(model, draggedId, target) {
6432
+ const { parentId, index: targetIndex } = target;
6433
+ if (parentId === draggedId) return null;
6434
+ if (parentId !== ROOT && isDescendant(model, draggedId, parentId)) return null;
6435
+ const nextLevel = insertedLevel(model, parentId);
6436
+ if (nextLevel < 0 || nextLevel > MAX_LEVEL.nav) return null;
6437
+ const draggedLoc = findNode(model, draggedId);
6438
+ if (!draggedLoc) return null;
6439
+ if (parentId !== ROOT && draggedLoc.node.children.length > 0) return null;
6440
+ const taken = takeNode(model, draggedId);
6441
+ if (!taken) return null;
6442
+ let insertIndex = targetIndex;
6443
+ if (draggedLoc.parentId === parentId && draggedLoc.index < targetIndex) {
6444
+ insertIndex -= 1;
6445
+ }
6446
+ if (!insertInto(taken.forest, taken.taken, parentId, insertIndex)) return null;
6447
+ const next = normalize(taken.forest);
6448
+ if (forestsEqual(model, next)) return null;
6449
+ return next;
6450
+ }
6451
+ function forestsEqual(a, b) {
6452
+ if (a.length !== b.length) return false;
6453
+ for (let i = 0; i < a.length; i++) {
6454
+ const left = a[i];
6455
+ const right = b[i];
6456
+ if (left.id !== right.id) return false;
6457
+ if (!forestsEqual(left.children, right.children)) return false;
6458
+ }
6459
+ return true;
6460
+ }
6461
+ function forestFromFlatOrder(order) {
6462
+ return order.map((id) => ({ id, children: [] }));
6463
+ }
6464
+ function serializeNavOrder(nodes) {
6465
+ const hasNesting = nodes.some((n) => n.children.length > 0);
6466
+ if (!hasNesting) {
6467
+ return JSON.stringify(nodes.map((n) => n.id));
6468
+ }
6469
+ return JSON.stringify(nodes);
6470
+ }
6471
+ function parseNavOrderJson(raw) {
6472
+ if (!raw) return null;
6473
+ try {
6474
+ const parsed = JSON.parse(raw);
6475
+ if (!Array.isArray(parsed)) return null;
6476
+ if (parsed.every((x) => typeof x === "string")) {
6477
+ return forestFromFlatOrder(parsed.filter((x) => typeof x === "string" && x.length > 0));
6478
+ }
6479
+ return parseNavNodeList(parsed);
6480
+ } catch {
6481
+ return null;
6482
+ }
6483
+ }
6484
+ function parseNavNodeList(raw) {
6485
+ const out = [];
6486
+ for (const item of raw) {
6487
+ if (typeof item === "string" && item.length > 0) {
6488
+ out.push({ id: item, children: [] });
6489
+ continue;
6490
+ }
6491
+ if (!item || typeof item !== "object") return null;
6492
+ const rec = item;
6493
+ if (typeof rec.id !== "string" || !rec.id) return null;
6494
+ const children = Array.isArray(rec.children) ? parseNavNodeList(rec.children) : [];
6495
+ if (!children) return null;
6496
+ out.push({ id: rec.id, children });
6497
+ }
6498
+ return out;
6499
+ }
6500
+ function flattenNavOrder(nodes) {
6501
+ const out = [];
6502
+ const walk = (list) => {
6503
+ for (const n of list) {
6504
+ out.push(n.id);
6505
+ walk(n.children);
6506
+ }
6507
+ };
6508
+ walk(nodes);
6509
+ return out;
6510
+ }
6511
+
6261
6512
  // src/lib/nav-items.ts
6262
6513
  var NAV_COUNT_KEY = "__ohw_nav_count";
6263
6514
  var NAV_ORDER_KEY = "__ohw_nav_order";
6515
+ var NAV_HREF_RE = /^nav-(\d+)-href$/;
6516
+ function isNavbarHrefKey(key) {
6517
+ if (!key) return false;
6518
+ if (key === "nav-book-href") return false;
6519
+ return NAV_HREF_RE.test(key);
6520
+ }
6264
6521
  function getLinkHref(el) {
6265
6522
  const key = el.getAttribute("data-ohw-href-key");
6266
6523
  if (key) {
@@ -6297,7 +6554,7 @@ function listNavbarItems() {
6297
6554
  }
6298
6555
  function parseNavIndexFromKey(key) {
6299
6556
  if (!key) return null;
6300
- const match = key.match(/^nav-(\d+)-href$/);
6557
+ const match = key.match(NAV_HREF_RE);
6301
6558
  if (!match) return null;
6302
6559
  return parseInt(match[1], 10);
6303
6560
  }
@@ -6320,17 +6577,6 @@ function getNavbarExistingTargets() {
6320
6577
  function getNavOrderFromDom() {
6321
6578
  return listNavbarItems().map((el) => el.getAttribute("data-ohw-href-key")).filter((key) => Boolean(key));
6322
6579
  }
6323
- function parseNavOrder(content) {
6324
- const raw = content[NAV_ORDER_KEY];
6325
- if (!raw) return null;
6326
- try {
6327
- const parsed = JSON.parse(raw);
6328
- if (!Array.isArray(parsed)) return null;
6329
- return parsed.filter((key) => typeof key === "string" && key.length > 0);
6330
- } catch {
6331
- return null;
6332
- }
6333
- }
6334
6580
  function findCounterpartByHrefKey(hrefKey, root) {
6335
6581
  return root.querySelector(`[data-ohw-href-key="${CSS.escape(hrefKey)}"]`);
6336
6582
  }
@@ -6394,6 +6640,13 @@ function insertNavbarItemDom(index, href, label, afterAnchor) {
6394
6640
  }
6395
6641
  return primary;
6396
6642
  }
6643
+ function navOrderKeysEqual(a, b) {
6644
+ if (a.length !== b.length) return false;
6645
+ for (let i = 0; i < a.length; i++) {
6646
+ if (a[i] !== b[i]) return false;
6647
+ }
6648
+ return true;
6649
+ }
6397
6650
  function getNavbarLinkKeysInContainer(container) {
6398
6651
  return Array.from(container.querySelectorAll("[data-ohw-href-key]")).filter(isNavbarLinkItem).map((el) => el.getAttribute("data-ohw-href-key")).filter((key) => Boolean(key));
6399
6652
  }
@@ -6402,21 +6655,32 @@ function applyNavOrderToContainer(container, order) {
6402
6655
  const current = getNavbarLinkKeysInContainer(container);
6403
6656
  const extras = current.filter((key) => !desired.includes(key));
6404
6657
  const expected = [...desired.filter((key) => current.includes(key)), ...extras];
6658
+ const orderedEls = [];
6659
+ if (navOrderKeysEqual(current, expected)) return;
6405
6660
  if (current.length === expected.length && current.every((key, i) => key === expected[i])) {
6406
6661
  return;
6407
6662
  }
6663
+ const lastCurrentLinkEl = current.length > 0 ? findCounterpartByHrefKey(current[current.length - 1], container) : null;
6664
+ const anchor = lastCurrentLinkEl?.parentElement === container ? lastCurrentLinkEl.nextElementSibling : null;
6408
6665
  const seen = /* @__PURE__ */ new Set();
6409
6666
  for (const hrefKey of desired) {
6410
6667
  if (seen.has(hrefKey)) continue;
6411
6668
  seen.add(hrefKey);
6412
6669
  const el = findCounterpartByHrefKey(hrefKey, container);
6413
- if (el) container.appendChild(el);
6670
+ if (el && isNavbarLinkItem(el)) orderedEls.push(el);
6414
6671
  }
6415
6672
  for (const el of container.querySelectorAll("[data-ohw-href-key]")) {
6416
6673
  if (!isNavbarLinkItem(el)) continue;
6417
6674
  const key = el.getAttribute("data-ohw-href-key");
6418
6675
  if (!key || seen.has(key)) continue;
6419
- container.appendChild(el);
6676
+ orderedEls.push(el);
6677
+ }
6678
+ for (const el of orderedEls) {
6679
+ if (anchor) {
6680
+ if (el.nextSibling !== anchor) container.insertBefore(el, anchor);
6681
+ } else if (container.lastElementChild !== el) {
6682
+ container.appendChild(el);
6683
+ }
6420
6684
  }
6421
6685
  }
6422
6686
  function applyNavOrder(order) {
@@ -6425,6 +6689,75 @@ function applyNavOrder(order) {
6425
6689
  const drawer = getNavbarDrawerContainer();
6426
6690
  if (drawer) applyNavOrderToContainer(drawer, order);
6427
6691
  }
6692
+ function applyChildrenOrder(parentHrefKey, childKeys, root) {
6693
+ const parent = findCounterpartByHrefKey(parentHrefKey, root);
6694
+ if (!parent) return;
6695
+ const group = parent.closest("[data-ohw-nav-group]");
6696
+ const childrenRoot = group?.querySelector(":scope > [data-ohw-nav-children]");
6697
+ if (!childrenRoot) return;
6698
+ const current = Array.from(childrenRoot.querySelectorAll(":scope > [data-ohw-href-key]")).map((el) => el.getAttribute("data-ohw-href-key")).filter((key) => Boolean(key));
6699
+ if (navOrderKeysEqual(current, childKeys)) return;
6700
+ for (const key of childKeys) {
6701
+ const el = findCounterpartByHrefKey(key, root);
6702
+ if (!el) continue;
6703
+ if (childrenRoot.lastElementChild !== el) {
6704
+ childrenRoot.appendChild(el);
6705
+ }
6706
+ }
6707
+ }
6708
+ function applyNavForest(forest) {
6709
+ if (forestsEqual(forest, getNavForestFromDom())) return;
6710
+ const rootKeys = forest.map((n) => n.id);
6711
+ applyNavOrder(rootKeys);
6712
+ const desktop = getNavbarDesktopContainer();
6713
+ const drawer = getNavbarDrawerContainer();
6714
+ for (const node of forest) {
6715
+ if (node.children.length === 0) continue;
6716
+ const childKeys = node.children.map((c) => c.id);
6717
+ if (desktop) applyChildrenOrder(node.id, childKeys, desktop);
6718
+ if (drawer) applyChildrenOrder(node.id, childKeys, drawer);
6719
+ }
6720
+ }
6721
+ function getNavForestFromDom() {
6722
+ const items = listNavbarItems();
6723
+ const nestedChildKeys = /* @__PURE__ */ new Set();
6724
+ const roots = [];
6725
+ for (const item of items) {
6726
+ const key = item.getAttribute("data-ohw-href-key");
6727
+ if (!key || !isNavbarHrefKey(key)) continue;
6728
+ if (item.closest("[data-ohw-nav-children]")) continue;
6729
+ const group = item.closest("[data-ohw-nav-group]");
6730
+ const childrenRoot = group?.querySelector(":scope > [data-ohw-nav-children]");
6731
+ const children = [];
6732
+ if (childrenRoot) {
6733
+ for (const child of childrenRoot.querySelectorAll(":scope > [data-ohw-href-key]")) {
6734
+ if (!isNavbarLinkItem(child)) continue;
6735
+ const childKey = child.getAttribute("data-ohw-href-key");
6736
+ if (!childKey || !isNavbarHrefKey(childKey)) continue;
6737
+ nestedChildKeys.add(childKey);
6738
+ children.push({ id: childKey, children: [] });
6739
+ }
6740
+ }
6741
+ roots.push({ id: key, children });
6742
+ }
6743
+ if (roots.length === 0) {
6744
+ return forestFromFlatOrder(
6745
+ getNavOrderFromDom().filter((k) => isNavbarHrefKey(k) && !nestedChildKeys.has(k))
6746
+ );
6747
+ }
6748
+ return roots;
6749
+ }
6750
+ function planNavItemMove(hrefKey, parentId, insertIndex) {
6751
+ const model = getNavForestFromDom();
6752
+ const next = moveNode(model, hrefKey, { parentId, index: insertIndex });
6753
+ if (!next) return null;
6754
+ if (forestsEqual(model, next)) return null;
6755
+ return {
6756
+ forest: next,
6757
+ orderJson: serializeNavOrder(next),
6758
+ flatOrder: flattenNavOrder(next)
6759
+ };
6760
+ }
6428
6761
  function collectNavbarIndicesFromContent(content) {
6429
6762
  const indices = /* @__PURE__ */ new Set();
6430
6763
  for (const key of Object.keys(content)) {
@@ -6452,8 +6785,10 @@ function reconcileNavbarItemsFromContent(content) {
6452
6785
  if (!href && !label) continue;
6453
6786
  insertNavbarItemDom(index, href ?? "/", label ?? "Untitled", null);
6454
6787
  }
6455
- const order = parseNavOrder(content);
6456
- if (order?.length) applyNavOrder(order);
6788
+ const orderForest = parseNavOrderJson(content[NAV_ORDER_KEY]);
6789
+ if (orderForest?.length) {
6790
+ applyNavForest(orderForest);
6791
+ }
6457
6792
  }
6458
6793
  function buildNavOrderAfterInsert(afterAnchor, newHrefKey) {
6459
6794
  const order = getNavOrderFromDom();
@@ -6493,149 +6828,1347 @@ function insertNavbarItem(href, label, afterAnchor = null) {
6493
6828
  };
6494
6829
  }
6495
6830
 
6496
- // src/ui/navbar-container-chrome.tsx
6497
- import { Plus as Plus2 } from "lucide-react";
6498
- import { jsx as jsx21 } from "react/jsx-runtime";
6499
- function NavbarContainerChrome({
6500
- rect,
6501
- onAdd
6502
- }) {
6503
- const chromeGap = 6;
6504
- return /* @__PURE__ */ jsx21(
6505
- "div",
6506
- {
6507
- "data-ohw-navbar-container-chrome": "",
6508
- "data-ohw-bridge": "",
6509
- className: "pointer-events-none fixed z-[2147483647]",
6510
- style: {
6511
- top: rect.top - chromeGap,
6512
- left: rect.left - chromeGap,
6513
- width: rect.width + chromeGap * 2,
6514
- height: rect.height + chromeGap * 2
6515
- },
6516
- children: /* @__PURE__ */ jsx21(
6517
- "button",
6518
- {
6519
- type: "button",
6520
- "data-ohw-navbar-add-button": "",
6521
- 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",
6522
- style: { top: "70%" },
6523
- "aria-label": "Add item",
6524
- onMouseDown: (e) => {
6525
- e.preventDefault();
6526
- e.stopPropagation();
6527
- },
6528
- onClick: (e) => {
6529
- e.preventDefault();
6530
- e.stopPropagation();
6531
- onAdd();
6532
- },
6533
- children: /* @__PURE__ */ jsx21(Plus2, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
6534
- }
6535
- )
6536
- }
6537
- );
6831
+ // src/lib/footer-items.ts
6832
+ var FOOTER_ORDER_KEY = "__ohw_footer_order";
6833
+ var FOOTER_HREF_RE = /^footer-(\d+)-(\d+)-href$/;
6834
+ function parseFooterHrefKey(key) {
6835
+ if (!key) return null;
6836
+ const match = key.match(FOOTER_HREF_RE);
6837
+ if (!match) return null;
6838
+ return { col: parseInt(match[1], 10), item: parseInt(match[2], 10) };
6538
6839
  }
6539
-
6540
- // src/ui/badge.tsx
6541
- import { jsx as jsx22 } from "react/jsx-runtime";
6542
- var badgeVariants = cva(
6543
- "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",
6544
- {
6545
- variants: {
6546
- variant: {
6547
- default: "border-transparent bg-primary text-primary-foreground",
6548
- secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
6549
- destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
6550
- outline: "text-foreground"
6551
- }
6552
- },
6553
- defaultVariants: {
6554
- variant: "default"
6555
- }
6556
- }
6557
- );
6558
- function Badge({ className, variant, ...props }) {
6559
- return /* @__PURE__ */ jsx22("div", { className: cn(badgeVariants({ variant }), className), ...props });
6840
+ function isFooterHrefKey(key) {
6841
+ return parseFooterHrefKey(key) !== null;
6560
6842
  }
6561
-
6562
- // src/OhhwellsBridge.tsx
6563
- import { Link as Link2 } from "lucide-react";
6564
- import { Fragment as Fragment5, jsx as jsx23, jsxs as jsxs14 } from "react/jsx-runtime";
6565
- var PRIMARY2 = "#0885FE";
6566
- var IMAGE_FADE_MS = 300;
6567
- function runOpacityFade(el, onDone) {
6568
- const anim = el.animate([{ opacity: 0 }, { opacity: 1 }], {
6569
- duration: IMAGE_FADE_MS,
6570
- easing: "ease",
6571
- fill: "forwards"
6572
- });
6573
- let finished = false;
6574
- const finish = () => {
6575
- if (finished) return;
6576
- finished = true;
6577
- anim.cancel();
6578
- el.style.opacity = "";
6579
- onDone();
6580
- };
6581
- anim.finished.then(finish).catch(finish);
6582
- window.setTimeout(finish, IMAGE_FADE_MS + 100);
6843
+ function getFooterRoot() {
6844
+ return document.querySelector('footer[data-ohw-section="footer"], footer');
6583
6845
  }
6584
- function fadeInImageElement(img, onReady) {
6585
- onReady();
6586
- img.style.opacity = "0";
6587
- runOpacityFade(img, () => {
6588
- img.style.opacity = "";
6846
+ function getFooterLinksContainer() {
6847
+ return document.querySelector("[data-ohw-footer-links]") ?? document.querySelector(".rb-footer-links");
6848
+ }
6849
+ function isFooterLinkAnchor(el) {
6850
+ if (!el.matches("[data-ohw-href-key]")) return false;
6851
+ if (!isFooterHrefKey(el.getAttribute("data-ohw-href-key"))) return false;
6852
+ return Boolean(el.querySelector('[data-ohw-editable="text"]'));
6853
+ }
6854
+ function listFooterColumns() {
6855
+ const root = getFooterLinksContainer() ?? getFooterRoot()?.querySelector(".rb-footer-links") ?? null;
6856
+ if (!root) return [];
6857
+ const explicit = Array.from(root.querySelectorAll(":scope > [data-ohw-footer-col]"));
6858
+ if (explicit.length > 0) return explicit;
6859
+ return Array.from(root.children).filter((el) => {
6860
+ if (!(el instanceof HTMLElement)) return false;
6861
+ return Array.from(el.querySelectorAll(":scope > [data-ohw-href-key]")).some(
6862
+ isFooterLinkAnchor
6863
+ );
6589
6864
  });
6590
6865
  }
6591
- function applyEditableImageSrc(img, url) {
6592
- img.removeAttribute("srcset");
6593
- img.removeAttribute("sizes");
6594
- img.src = url;
6866
+ function listFooterLinksInColumn(column) {
6867
+ return Array.from(column.querySelectorAll(":scope > [data-ohw-href-key]")).filter(
6868
+ isFooterLinkAnchor
6869
+ );
6595
6870
  }
6596
- function fadeInBgImage(el, url, onReady) {
6597
- const prevPos = el.style.position;
6598
- if (!prevPos || prevPos === "static") el.style.position = "relative";
6599
- const layer = document.createElement("div");
6600
- layer.setAttribute("data-ohw-bg-fade-layer", "");
6601
- Object.assign(layer.style, {
6602
- position: "absolute",
6603
- inset: "0",
6604
- zIndex: "0",
6605
- backgroundImage: `url('${url}')`,
6606
- backgroundSize: "cover",
6607
- backgroundPosition: "center",
6608
- backgroundRepeat: "no-repeat",
6609
- opacity: "0",
6610
- pointerEvents: "none"
6611
- });
6612
- el.prepend(layer);
6613
- onReady();
6614
- runOpacityFade(layer, () => {
6615
- el.style.backgroundImage = `url('${url}')`;
6616
- layer.remove();
6617
- if (!prevPos || prevPos === "static") el.style.position = prevPos;
6871
+ function findFooterColumnForLink(anchor) {
6872
+ const columns = listFooterColumns();
6873
+ return columns.find((col) => col.contains(anchor)) ?? null;
6874
+ }
6875
+ function getFooterOrderFromDom() {
6876
+ return listFooterColumns().map(
6877
+ (col) => listFooterLinksInColumn(col).map((el) => el.getAttribute("data-ohw-href-key")).filter((key) => Boolean(key))
6878
+ );
6879
+ }
6880
+ function findFooterLinkByKey(hrefKey) {
6881
+ return document.querySelector(
6882
+ `footer [data-ohw-href-key="${CSS.escape(hrefKey)}"]`
6883
+ );
6884
+ }
6885
+ function syncFooterColumnIndices(columns) {
6886
+ columns.forEach((col, index) => {
6887
+ const next = String(index);
6888
+ if (col.getAttribute("data-ohw-footer-col") !== next) {
6889
+ col.setAttribute("data-ohw-footer-col", next);
6890
+ }
6618
6891
  });
6619
6892
  }
6620
- function getSectionsTracker() {
6621
- let el = document.querySelector("[data-ohw-sections-tracker]");
6622
- if (!el) {
6623
- el = document.createElement("div");
6624
- el.setAttribute("data-ohw-sections-tracker", "");
6625
- el.style.display = "none";
6626
- document.body.appendChild(el);
6893
+ function appendChildIfNeeded(parent, child) {
6894
+ if (parent.lastElementChild !== child) {
6895
+ parent.appendChild(child);
6627
6896
  }
6628
- return el;
6629
6897
  }
6630
- function updateSectionScheduleId(insertAfter, scheduleId) {
6631
- const tracker = getSectionsTracker();
6632
- let sections = [];
6633
- try {
6634
- sections = JSON.parse(tracker.textContent || "[]");
6635
- } catch {
6898
+ function applyFooterOrder(order) {
6899
+ const container = getFooterLinksContainer();
6900
+ if (!container) return;
6901
+ const columns = listFooterColumns();
6902
+ if (columns.length === 0) return;
6903
+ const currentOrder = getFooterOrderFromDom();
6904
+ if (ordersEqual(order, currentOrder)) {
6905
+ syncFooterColumnIndices(columns);
6906
+ return;
6636
6907
  }
6637
- const currentPath = window.location.pathname;
6638
- const updated = sections.map((s) => {
6908
+ const used = /* @__PURE__ */ new Set();
6909
+ const orderedCols = [];
6910
+ for (const colOrder of order) {
6911
+ let matched = null;
6912
+ for (const hrefKey of colOrder) {
6913
+ const link = findFooterLinkByKey(hrefKey);
6914
+ if (!link) continue;
6915
+ const owner = findFooterColumnForLink(link);
6916
+ if (owner && !used.has(owner)) {
6917
+ matched = owner;
6918
+ break;
6919
+ }
6920
+ }
6921
+ if (!matched) {
6922
+ matched = columns.find((col) => !used.has(col)) ?? null;
6923
+ }
6924
+ if (matched) {
6925
+ used.add(matched);
6926
+ orderedCols.push(matched);
6927
+ }
6928
+ }
6929
+ for (const col of columns) {
6930
+ if (!used.has(col)) orderedCols.push(col);
6931
+ }
6932
+ for (const col of orderedCols) {
6933
+ appendChildIfNeeded(container, col);
6934
+ }
6935
+ const freshColumns = listFooterColumns();
6936
+ syncFooterColumnIndices(freshColumns);
6937
+ for (let c = 0; c < order.length; c++) {
6938
+ const col = freshColumns[c];
6939
+ if (!col) continue;
6940
+ const colOrder = order[c];
6941
+ for (const hrefKey of colOrder) {
6942
+ const el = findFooterLinkByKey(hrefKey);
6943
+ if (!el) continue;
6944
+ if (el.parentElement !== col || col.lastElementChild !== el) {
6945
+ col.appendChild(el);
6946
+ }
6947
+ }
6948
+ }
6949
+ }
6950
+ function ordersEqual(a, b) {
6951
+ if (a.length !== b.length) return false;
6952
+ for (let i = 0; i < a.length; i++) {
6953
+ const left = a[i];
6954
+ const right = b[i];
6955
+ if (left.length !== right.length) return false;
6956
+ for (let j = 0; j < left.length; j++) {
6957
+ if (left[j] !== right[j]) return false;
6958
+ }
6959
+ }
6960
+ return true;
6961
+ }
6962
+ function planFooterLinkMove(hrefKey, targetColIndex, insertIndex) {
6963
+ const order = getFooterOrderFromDom();
6964
+ if (targetColIndex < 0 || targetColIndex >= order.length) return null;
6965
+ let fromCol = -1;
6966
+ let fromIdx = -1;
6967
+ for (let c = 0; c < order.length; c++) {
6968
+ const idx = order[c].indexOf(hrefKey);
6969
+ if (idx >= 0) {
6970
+ fromCol = c;
6971
+ fromIdx = idx;
6972
+ break;
6973
+ }
6974
+ }
6975
+ if (fromCol < 0) return null;
6976
+ const next = order.map((col) => [...col]);
6977
+ next[fromCol].splice(fromIdx, 1);
6978
+ let adjusted = insertIndex;
6979
+ if (fromCol === targetColIndex && fromIdx < insertIndex) adjusted -= 1;
6980
+ adjusted = Math.max(0, Math.min(adjusted, next[targetColIndex].length));
6981
+ next[targetColIndex].splice(adjusted, 0, hrefKey);
6982
+ if (ordersEqual(next, order)) return null;
6983
+ return next;
6984
+ }
6985
+ function planFooterColumnMove(fromIndex, toIndex) {
6986
+ const order = getFooterOrderFromDom();
6987
+ const columns = listFooterColumns();
6988
+ if (fromIndex < 0 || fromIndex >= columns.length || toIndex < 0 || toIndex > columns.length) {
6989
+ return null;
6990
+ }
6991
+ if (toIndex === fromIndex || toIndex === fromIndex + 1) return null;
6992
+ const nextOrder = order.map((col) => [...col]);
6993
+ const [movedOrder] = nextOrder.splice(fromIndex, 1);
6994
+ if (!movedOrder) return null;
6995
+ let adjusted = toIndex;
6996
+ if (fromIndex < toIndex) adjusted -= 1;
6997
+ adjusted = Math.max(0, Math.min(adjusted, nextOrder.length));
6998
+ nextOrder.splice(adjusted, 0, movedOrder);
6999
+ if (ordersEqual(nextOrder, order)) return null;
7000
+ return nextOrder;
7001
+ }
7002
+ function parseFooterOrder(content) {
7003
+ const raw = content[FOOTER_ORDER_KEY];
7004
+ if (!raw) return null;
7005
+ try {
7006
+ const parsed = JSON.parse(raw);
7007
+ if (!Array.isArray(parsed)) return null;
7008
+ return parsed.filter((col) => Array.isArray(col)).map((col) => col.filter((key) => typeof key === "string" && key.length > 0));
7009
+ } catch {
7010
+ return null;
7011
+ }
7012
+ }
7013
+ var FOOTER_HEADING_RE = /^footer-(\d+)-heading$/;
7014
+ var FOOTER_LABEL_RE = /^footer-(\d+)-(\d+)-label$/;
7015
+ var DEFAULT_FOOTER_COLUMN_HEADING = "New";
7016
+ var DEFAULT_FOOTER_COLUMN_LINK_LABEL = "New link";
7017
+ var DEFAULT_FOOTER_COLUMN_LINK_HREF = "";
7018
+ function isFooterLinksContainer(el) {
7019
+ return el.hasAttribute("data-ohw-footer-links") || el.classList.contains("rb-footer-links");
7020
+ }
7021
+ function getFooterColumnIndicesInDom() {
7022
+ const indices = /* @__PURE__ */ new Set();
7023
+ for (const col of listFooterColumns()) {
7024
+ const attr = col.getAttribute("data-ohw-footer-col");
7025
+ if (attr != null) {
7026
+ const n = parseInt(attr, 10);
7027
+ if (Number.isFinite(n)) indices.add(n);
7028
+ }
7029
+ for (const link of listFooterLinksInColumn(col)) {
7030
+ const parsed = parseFooterHrefKey(link.getAttribute("data-ohw-href-key"));
7031
+ if (parsed) indices.add(parsed.col);
7032
+ }
7033
+ const heading = col.querySelector('[data-ohw-key^="footer-"][data-ohw-key$="-heading"]');
7034
+ const headingKey = heading?.getAttribute("data-ohw-key");
7035
+ if (headingKey) {
7036
+ const match = headingKey.match(FOOTER_HEADING_RE);
7037
+ if (match) indices.add(parseInt(match[1], 10));
7038
+ }
7039
+ }
7040
+ return [...indices].sort((a, b) => a - b);
7041
+ }
7042
+ function getNextFooterColumnIndex() {
7043
+ const indices = getFooterColumnIndicesInDom();
7044
+ if (indices.length === 0) return 0;
7045
+ return Math.max(...indices) + 1;
7046
+ }
7047
+ function cloneFooterLinkShell(template) {
7048
+ const anchor = document.createElement("a");
7049
+ if (template) {
7050
+ anchor.style.cssText = template.style.cssText;
7051
+ if (template.className) anchor.className = template.className;
7052
+ }
7053
+ anchor.style.cursor = "pointer";
7054
+ anchor.style.textDecoration = "none";
7055
+ return anchor;
7056
+ }
7057
+ function buildFooterHeading(colIndex, text) {
7058
+ const heading = document.createElement("p");
7059
+ heading.setAttribute("data-ohw-editable", "text");
7060
+ heading.setAttribute("data-ohw-key", `footer-${colIndex}-heading`);
7061
+ heading.textContent = text;
7062
+ heading.style.cssText = [
7063
+ "color: var(--espresso, #3d312b)",
7064
+ "font-size: 14px",
7065
+ "font-weight: 800",
7066
+ "opacity: 0.82",
7067
+ "margin: 0 0 6px",
7068
+ "padding: 0",
7069
+ "line-height: 1.2"
7070
+ ].join(";");
7071
+ return heading;
7072
+ }
7073
+ function buildFooterLink(colIndex, itemIndex, href, label, template) {
7074
+ const anchor = cloneFooterLinkShell(template);
7075
+ anchor.setAttribute("href", href);
7076
+ anchor.setAttribute("data-ohw-href-key", `footer-${colIndex}-${itemIndex}-href`);
7077
+ const span = document.createElement("span");
7078
+ span.setAttribute("data-ohw-editable", "text");
7079
+ span.setAttribute("data-ohw-key", `footer-${colIndex}-${itemIndex}-label`);
7080
+ span.textContent = label;
7081
+ anchor.appendChild(span);
7082
+ return anchor;
7083
+ }
7084
+ function footerColumnExistsInDom(colIndex) {
7085
+ return document.querySelector(`[data-ohw-footer-col="${colIndex}"]`) !== null || document.querySelector(`[data-ohw-href-key^="footer-${colIndex}-"]`) !== null || document.querySelector(`[data-ohw-key="footer-${colIndex}-heading"]`) !== null;
7086
+ }
7087
+ function getFooterLinkTemplate() {
7088
+ for (const col of listFooterColumns()) {
7089
+ const link = listFooterLinksInColumn(col)[0];
7090
+ if (link) return link;
7091
+ }
7092
+ return null;
7093
+ }
7094
+ function getFooterColumnTemplate() {
7095
+ return listFooterColumns()[0] ?? null;
7096
+ }
7097
+ function insertFooterColumnDom(colIndex, heading, items) {
7098
+ const container = getFooterLinksContainer();
7099
+ if (!container) return null;
7100
+ const colTemplate = getFooterColumnTemplate();
7101
+ const linkTemplate = getFooterLinkTemplate();
7102
+ const column = document.createElement("div");
7103
+ if (colTemplate?.className) column.className = colTemplate.className;
7104
+ else column.className = "rb-footer-link-col";
7105
+ column.setAttribute("data-ohw-footer-col", String(colIndex));
7106
+ if (colTemplate) {
7107
+ const gap = getComputedStyle(colTemplate).gap;
7108
+ if (gap && gap !== "normal") column.style.gap = gap;
7109
+ column.style.display = getComputedStyle(colTemplate).display || "flex";
7110
+ column.style.flexDirection = "column";
7111
+ }
7112
+ column.appendChild(buildFooterHeading(colIndex, heading));
7113
+ let firstLink = null;
7114
+ items.forEach((item, itemIndex) => {
7115
+ const link = buildFooterLink(colIndex, itemIndex, item.href, item.label, linkTemplate);
7116
+ column.appendChild(link);
7117
+ if (!firstLink) firstLink = link;
7118
+ });
7119
+ container.appendChild(column);
7120
+ return { column, firstLink };
7121
+ }
7122
+ function insertFooterColumn(heading = DEFAULT_FOOTER_COLUMN_HEADING, linkLabel = DEFAULT_FOOTER_COLUMN_LINK_LABEL, linkHref = DEFAULT_FOOTER_COLUMN_LINK_HREF) {
7123
+ const colIndex = getNextFooterColumnIndex();
7124
+ const inserted = insertFooterColumnDom(colIndex, heading, [{ href: linkHref, label: linkLabel }]);
7125
+ if (!inserted?.firstLink) throw new Error("Failed to insert footer column");
7126
+ syncFooterColumnIndices(listFooterColumns());
7127
+ return {
7128
+ column: inserted.column,
7129
+ firstLink: inserted.firstLink,
7130
+ colIndex,
7131
+ headingKey: `footer-${colIndex}-heading`,
7132
+ hrefKey: `footer-${colIndex}-0-href`,
7133
+ labelKey: `footer-${colIndex}-0-label`,
7134
+ heading,
7135
+ label: linkLabel,
7136
+ href: linkHref,
7137
+ order: getFooterOrderFromDom()
7138
+ };
7139
+ }
7140
+ function collectFooterColumnIndicesFromContent(content) {
7141
+ const indices = /* @__PURE__ */ new Set();
7142
+ for (const key of Object.keys(content)) {
7143
+ const href = parseFooterHrefKey(key);
7144
+ if (href) indices.add(href.col);
7145
+ const heading = key.match(FOOTER_HEADING_RE);
7146
+ if (heading) indices.add(parseInt(heading[1], 10));
7147
+ const label = key.match(FOOTER_LABEL_RE);
7148
+ if (label) indices.add(parseInt(label[1], 10));
7149
+ }
7150
+ const order = parseFooterOrder(content);
7151
+ if (order) {
7152
+ for (const col of order) {
7153
+ for (const hrefKey of col) {
7154
+ const parsed = parseFooterHrefKey(hrefKey);
7155
+ if (parsed) indices.add(parsed.col);
7156
+ }
7157
+ }
7158
+ }
7159
+ return [...indices].sort((a, b) => a - b);
7160
+ }
7161
+ function collectFooterItemsForColumn(content, colIndex) {
7162
+ const itemIndices = /* @__PURE__ */ new Set();
7163
+ for (const key of Object.keys(content)) {
7164
+ const href = parseFooterHrefKey(key);
7165
+ if (href?.col === colIndex) itemIndices.add(href.item);
7166
+ const label = key.match(FOOTER_LABEL_RE);
7167
+ if (label && parseInt(label[1], 10) === colIndex) {
7168
+ itemIndices.add(parseInt(label[2], 10));
7169
+ }
7170
+ }
7171
+ const sorted = [...itemIndices].sort((a, b) => a - b);
7172
+ if (sorted.length === 0) {
7173
+ return [
7174
+ {
7175
+ href: content[`footer-${colIndex}-0-href`] ?? DEFAULT_FOOTER_COLUMN_LINK_HREF,
7176
+ label: content[`footer-${colIndex}-0-label`] ?? DEFAULT_FOOTER_COLUMN_LINK_LABEL
7177
+ }
7178
+ ];
7179
+ }
7180
+ return sorted.map((itemIndex) => ({
7181
+ href: content[`footer-${colIndex}-${itemIndex}-href`] ?? DEFAULT_FOOTER_COLUMN_LINK_HREF,
7182
+ label: content[`footer-${colIndex}-${itemIndex}-label`] ?? DEFAULT_FOOTER_COLUMN_LINK_LABEL
7183
+ }));
7184
+ }
7185
+ function reconcileFooterColumnsFromContent(content) {
7186
+ const indices = collectFooterColumnIndicesFromContent(content);
7187
+ for (const colIndex of indices) {
7188
+ if (footerColumnExistsInDom(colIndex)) continue;
7189
+ const heading = content[`footer-${colIndex}-heading`] ?? DEFAULT_FOOTER_COLUMN_HEADING;
7190
+ const items = collectFooterItemsForColumn(content, colIndex);
7191
+ const hasPayload = Boolean(content[`footer-${colIndex}-heading`]) || items.some(
7192
+ (_, i) => content[`footer-${colIndex}-${i}-href`] !== void 0 || content[`footer-${colIndex}-${i}-label`] !== void 0
7193
+ ) || (parseFooterOrder(content)?.some(
7194
+ (col) => col.some((k) => parseFooterHrefKey(k)?.col === colIndex)
7195
+ ) ?? false);
7196
+ if (!hasPayload) continue;
7197
+ insertFooterColumnDom(colIndex, heading, items);
7198
+ }
7199
+ }
7200
+ function reconcileFooterOrderFromContent(content) {
7201
+ reconcileFooterColumnsFromContent(content);
7202
+ const order = parseFooterOrder(content);
7203
+ if (!order?.length) return;
7204
+ const current = getFooterOrderFromDom();
7205
+ if (ordersEqual(order, current)) {
7206
+ syncFooterColumnIndices(listFooterColumns());
7207
+ return;
7208
+ }
7209
+ applyFooterOrder(order);
7210
+ }
7211
+ function resolveFooterLinksContainerSelectionTarget(target, clientX, clientY, isPointOverItem) {
7212
+ const container = getFooterLinksContainer();
7213
+ if (!container) return null;
7214
+ const inContainer = target === container || container.contains(target) && Boolean(target.closest("[data-ohw-footer-links], .rb-footer-links"));
7215
+ if (!inContainer && target !== container) {
7216
+ const r2 = container.getBoundingClientRect();
7217
+ const over = clientX >= r2.left && clientX <= r2.right && clientY >= r2.top && clientY <= r2.bottom;
7218
+ if (!over) return null;
7219
+ }
7220
+ if (isPointOverItem(container, clientX, clientY)) return null;
7221
+ const column = target.closest("[data-ohw-footer-col], [data-ohw-footer-column]") ?? null;
7222
+ if (column && container.contains(column)) {
7223
+ if (target === column || column.contains(target)) return null;
7224
+ }
7225
+ if (target === container || container.contains(target) || inContainer) {
7226
+ if (target === container || isFooterLinksContainer(target)) return container;
7227
+ if (!column) return container;
7228
+ }
7229
+ return null;
7230
+ }
7231
+ function isRowLayoutColumn(links) {
7232
+ if (links.length < 2) return false;
7233
+ const firstTop = links[0].getBoundingClientRect().top;
7234
+ return links.every((link) => Math.abs(link.getBoundingClientRect().top - firstTop) < 4);
7235
+ }
7236
+ function buildLinkDropSlots(column, columnIndex) {
7237
+ const links = listFooterLinksInColumn(column);
7238
+ const colRect = column.getBoundingClientRect();
7239
+ const slots = [];
7240
+ const barThickness = 3;
7241
+ if (links.length === 0) {
7242
+ slots.push({
7243
+ insertIndex: 0,
7244
+ columnIndex,
7245
+ left: colRect.left,
7246
+ top: colRect.top + colRect.height / 2 - barThickness / 2,
7247
+ width: colRect.width,
7248
+ height: barThickness,
7249
+ direction: "horizontal"
7250
+ });
7251
+ return slots;
7252
+ }
7253
+ if (isRowLayoutColumn(links)) {
7254
+ for (let i = 0; i <= links.length; i++) {
7255
+ let left;
7256
+ if (i === 0) {
7257
+ left = links[0].getBoundingClientRect().left - barThickness / 2;
7258
+ } else if (i === links.length) {
7259
+ const last = links[links.length - 1].getBoundingClientRect();
7260
+ left = last.right - barThickness / 2;
7261
+ } else {
7262
+ const prev = links[i - 1].getBoundingClientRect();
7263
+ const next = links[i].getBoundingClientRect();
7264
+ left = (prev.right + next.left) / 2 - barThickness / 2;
7265
+ }
7266
+ const heightRef = i === 0 ? links[0].getBoundingClientRect() : i === links.length ? links[links.length - 1].getBoundingClientRect() : links[i].getBoundingClientRect();
7267
+ slots.push({
7268
+ insertIndex: i,
7269
+ columnIndex,
7270
+ left,
7271
+ top: heightRef.top,
7272
+ width: barThickness,
7273
+ height: Math.max(heightRef.height, colRect.height * 0.8),
7274
+ direction: "vertical"
7275
+ });
7276
+ }
7277
+ return slots;
7278
+ }
7279
+ for (let i = 0; i <= links.length; i++) {
7280
+ let top;
7281
+ if (i === 0) {
7282
+ top = links[0].getBoundingClientRect().top - barThickness / 2;
7283
+ } else if (i === links.length) {
7284
+ const last = links[links.length - 1].getBoundingClientRect();
7285
+ top = last.bottom - barThickness / 2;
7286
+ } else {
7287
+ const prev = links[i - 1].getBoundingClientRect();
7288
+ const next = links[i].getBoundingClientRect();
7289
+ top = (prev.bottom + next.top) / 2 - barThickness / 2;
7290
+ }
7291
+ const widthRef = i === 0 ? links[0].getBoundingClientRect() : i === links.length ? links[links.length - 1].getBoundingClientRect() : links[i].getBoundingClientRect();
7292
+ slots.push({
7293
+ insertIndex: i,
7294
+ columnIndex,
7295
+ left: widthRef.left,
7296
+ top,
7297
+ width: Math.max(widthRef.width, colRect.width * 0.8),
7298
+ height: barThickness,
7299
+ direction: "horizontal"
7300
+ });
7301
+ }
7302
+ return slots;
7303
+ }
7304
+ function buildColumnDropSlots() {
7305
+ const columns = listFooterColumns();
7306
+ const slots = [];
7307
+ const barThickness = 3;
7308
+ if (columns.length === 0) return slots;
7309
+ for (let i = 0; i <= columns.length; i++) {
7310
+ let left;
7311
+ let height;
7312
+ let top;
7313
+ if (i === 0) {
7314
+ const first = columns[0].getBoundingClientRect();
7315
+ left = first.left - barThickness / 2;
7316
+ top = first.top;
7317
+ height = first.height;
7318
+ } else if (i === columns.length) {
7319
+ const last = columns[columns.length - 1].getBoundingClientRect();
7320
+ left = last.right - barThickness / 2;
7321
+ top = last.top;
7322
+ height = last.height;
7323
+ } else {
7324
+ const prev = columns[i - 1].getBoundingClientRect();
7325
+ const next = columns[i].getBoundingClientRect();
7326
+ left = (prev.right + next.left) / 2 - barThickness / 2;
7327
+ top = Math.min(prev.top, next.top);
7328
+ height = Math.max(prev.bottom, next.bottom) - top;
7329
+ }
7330
+ slots.push({
7331
+ insertIndex: i,
7332
+ columnIndex: i,
7333
+ left,
7334
+ top,
7335
+ width: barThickness,
7336
+ height: Math.max(height, 24),
7337
+ direction: "vertical"
7338
+ });
7339
+ }
7340
+ return slots;
7341
+ }
7342
+ function hitTestLinkDropSlot(clientX, clientY, draggedHrefKey) {
7343
+ const columns = listFooterColumns();
7344
+ let best = null;
7345
+ for (let c = 0; c < columns.length; c++) {
7346
+ const col = columns[c];
7347
+ const colRect = col.getBoundingClientRect();
7348
+ const inColX = clientX >= colRect.left - 24 && clientX <= colRect.right + 24;
7349
+ if (!inColX) continue;
7350
+ const slots = buildLinkDropSlots(col, c).filter((slot) => {
7351
+ const links = listFooterLinksInColumn(col);
7352
+ const fromIdx = links.findIndex(
7353
+ (el) => el.getAttribute("data-ohw-href-key") === draggedHrefKey
7354
+ );
7355
+ if (fromIdx < 0) return true;
7356
+ if (c === findColumnIndexForKey(draggedHrefKey) && (slot.insertIndex === fromIdx || slot.insertIndex === fromIdx + 1)) {
7357
+ return true;
7358
+ }
7359
+ return true;
7360
+ });
7361
+ for (const slot of slots) {
7362
+ const dist = slot.direction === "vertical" ? Math.abs(clientX - (slot.left + slot.width / 2)) : Math.abs(clientY - (slot.top + slot.height / 2));
7363
+ if (!best || dist < best.dist) best = { slot, dist };
7364
+ }
7365
+ }
7366
+ return best?.slot ?? null;
7367
+ }
7368
+ function findColumnIndexForKey(hrefKey) {
7369
+ const order = getFooterOrderFromDom();
7370
+ for (let c = 0; c < order.length; c++) {
7371
+ if (order[c].includes(hrefKey)) return c;
7372
+ }
7373
+ return -1;
7374
+ }
7375
+ function hitTestColumnDropSlot(clientX, _clientY) {
7376
+ const slots = buildColumnDropSlots();
7377
+ let best = null;
7378
+ for (const slot of slots) {
7379
+ const cx2 = slot.left + slot.width / 2;
7380
+ const dist = Math.abs(clientX - cx2);
7381
+ if (!best || dist < best.dist) best = { slot, dist };
7382
+ }
7383
+ return best?.slot ?? null;
7384
+ }
7385
+
7386
+ // src/lib/add-footer-column.ts
7387
+ function buildFooterColumnEditContentPatch(result) {
7388
+ return {
7389
+ [result.headingKey]: result.heading,
7390
+ [result.hrefKey]: result.href,
7391
+ [result.labelKey]: result.label,
7392
+ [FOOTER_ORDER_KEY]: JSON.stringify(result.order)
7393
+ };
7394
+ }
7395
+ function addFooterColumnWithPersist({
7396
+ postToParent: postToParent2
7397
+ }) {
7398
+ const result = insertFooterColumn();
7399
+ const patch = buildFooterColumnEditContentPatch(result);
7400
+ setStoredLinkHref(result.hrefKey, result.href);
7401
+ postToParent2({
7402
+ type: "ow:change",
7403
+ nodes: Object.entries(patch).map(([key, text]) => ({ key, text }))
7404
+ });
7405
+ postToParent2({ type: "ow:toast", title: "Item added", toastType: "success" });
7406
+ enforceLinkHrefs();
7407
+ return result;
7408
+ }
7409
+
7410
+ // src/lib/item-drag-interaction.ts
7411
+ function disableNativeHrefDrag(el) {
7412
+ if (el.draggable) el.draggable = false;
7413
+ if (el.getAttribute("draggable") !== "false") {
7414
+ el.setAttribute("draggable", "false");
7415
+ }
7416
+ }
7417
+ function clearTextSelection() {
7418
+ const sel = window.getSelection();
7419
+ if (sel && !sel.isCollapsed) sel.removeAllRanges();
7420
+ }
7421
+ function armItemPressDrag() {
7422
+ document.documentElement.setAttribute("data-ohw-footer-press-drag", "");
7423
+ }
7424
+ function lockItemDuringDrag() {
7425
+ document.documentElement.removeAttribute("data-ohw-footer-press-drag");
7426
+ document.documentElement.setAttribute("data-ohw-item-dragging", "");
7427
+ clearTextSelection();
7428
+ }
7429
+ function unlockItemDragInteraction() {
7430
+ const wasDragging = document.documentElement.hasAttribute("data-ohw-item-dragging");
7431
+ document.documentElement.removeAttribute("data-ohw-footer-press-drag");
7432
+ document.documentElement.removeAttribute("data-ohw-item-dragging");
7433
+ if (wasDragging) clearTextSelection();
7434
+ }
7435
+ var armFooterPressDrag = armItemPressDrag;
7436
+ var lockFooterDuringDrag = lockItemDuringDrag;
7437
+ var unlockFooterDragInteraction = unlockItemDragInteraction;
7438
+
7439
+ // src/lib/nav-dnd.ts
7440
+ function listReorderableNavItems() {
7441
+ return listNavbarItems().filter((el) => {
7442
+ const key = el.getAttribute("data-ohw-href-key");
7443
+ return isNavbarHrefKey(key) && !el.closest("[data-ohw-nav-children]");
7444
+ });
7445
+ }
7446
+ function listNavChildren(parentHrefKey) {
7447
+ const items = listNavbarItems();
7448
+ const parent = items.find((el) => el.getAttribute("data-ohw-href-key") === parentHrefKey);
7449
+ if (!parent) return [];
7450
+ const group = parent.closest("[data-ohw-nav-group]");
7451
+ const childrenRoot = group?.querySelector(":scope > [data-ohw-nav-children]");
7452
+ if (!childrenRoot) return [];
7453
+ return Array.from(childrenRoot.querySelectorAll(":scope > [data-ohw-href-key]")).filter(
7454
+ (el) => isNavbarHrefKey(el.getAttribute("data-ohw-href-key"))
7455
+ );
7456
+ }
7457
+ function activeNavListContainer() {
7458
+ const desktop = getNavbarDesktopContainer();
7459
+ if (desktop && desktop.getClientRects().length > 0) {
7460
+ const style = window.getComputedStyle(desktop);
7461
+ if (style.display !== "none" && style.visibility !== "hidden") return desktop;
7462
+ }
7463
+ return getNavbarDrawerContainer();
7464
+ }
7465
+ function buildRootNavDropSlots() {
7466
+ const items = listReorderableNavItems();
7467
+ const slots = [];
7468
+ const barThickness = 3;
7469
+ if (items.length === 0) {
7470
+ const container = activeNavListContainer();
7471
+ if (!container) return slots;
7472
+ const rect = container.getBoundingClientRect();
7473
+ slots.push({
7474
+ insertIndex: 0,
7475
+ parentId: null,
7476
+ left: rect.left + rect.width / 2 - barThickness / 2,
7477
+ top: rect.top,
7478
+ width: barThickness,
7479
+ height: Math.max(rect.height, 24),
7480
+ direction: "vertical"
7481
+ });
7482
+ return slots;
7483
+ }
7484
+ const edgeGap = (() => {
7485
+ if (items.length < 2) return 32;
7486
+ const a = items[0].getBoundingClientRect();
7487
+ const b = items[1].getBoundingClientRect();
7488
+ return Math.max(0, b.left - a.right);
7489
+ })();
7490
+ for (let i = 0; i <= items.length; i++) {
7491
+ let left;
7492
+ let top;
7493
+ let height;
7494
+ if (i === 0) {
7495
+ const first = items[0].getBoundingClientRect();
7496
+ left = first.left - edgeGap / 2 - barThickness / 2;
7497
+ top = first.top;
7498
+ height = first.height;
7499
+ } else if (i === items.length) {
7500
+ const last = items[items.length - 1].getBoundingClientRect();
7501
+ const lastGap = items.length >= 2 ? Math.max(
7502
+ 0,
7503
+ last.left - items[items.length - 2].getBoundingClientRect().right
7504
+ ) : edgeGap;
7505
+ left = last.right + lastGap / 2 - barThickness / 2;
7506
+ top = last.top;
7507
+ height = last.height;
7508
+ } else {
7509
+ const prev = items[i - 1].getBoundingClientRect();
7510
+ const next = items[i].getBoundingClientRect();
7511
+ left = (prev.right + next.left) / 2 - barThickness / 2;
7512
+ top = Math.min(prev.top, next.top);
7513
+ height = Math.max(prev.bottom, next.bottom) - top;
7514
+ }
7515
+ slots.push({
7516
+ insertIndex: i,
7517
+ parentId: null,
7518
+ left,
7519
+ top,
7520
+ width: barThickness,
7521
+ height: Math.max(height, 24),
7522
+ direction: "vertical"
7523
+ });
7524
+ }
7525
+ return slots;
7526
+ }
7527
+ function buildChildNavDropSlots(parentHrefKey) {
7528
+ const children = listNavChildren(parentHrefKey);
7529
+ const slots = [];
7530
+ const barThickness = 3;
7531
+ if (children.length === 0) return slots;
7532
+ const edgeGap = (() => {
7533
+ if (children.length < 2) return 16;
7534
+ const a = children[0].getBoundingClientRect();
7535
+ const b = children[1].getBoundingClientRect();
7536
+ return Math.max(0, b.top - a.bottom);
7537
+ })();
7538
+ for (let i = 0; i <= children.length; i++) {
7539
+ let top;
7540
+ let width;
7541
+ let left;
7542
+ if (i === 0) {
7543
+ const first = children[0].getBoundingClientRect();
7544
+ top = first.top - edgeGap / 2 - barThickness / 2;
7545
+ left = first.left;
7546
+ width = first.width;
7547
+ } else if (i === children.length) {
7548
+ const last = children[children.length - 1].getBoundingClientRect();
7549
+ const lastGap = children.length >= 2 ? Math.max(
7550
+ 0,
7551
+ last.top - children[children.length - 2].getBoundingClientRect().bottom
7552
+ ) : edgeGap;
7553
+ top = last.bottom + lastGap / 2 - barThickness / 2;
7554
+ left = last.left;
7555
+ width = last.width;
7556
+ } else {
7557
+ const prev = children[i - 1].getBoundingClientRect();
7558
+ const next = children[i].getBoundingClientRect();
7559
+ top = (prev.bottom + next.top) / 2 - barThickness / 2;
7560
+ left = Math.min(prev.left, next.left);
7561
+ width = Math.max(prev.right, next.right) - left;
7562
+ }
7563
+ slots.push({
7564
+ insertIndex: i,
7565
+ parentId: parentHrefKey,
7566
+ left,
7567
+ top,
7568
+ width: Math.max(width, 40),
7569
+ height: barThickness,
7570
+ direction: "horizontal"
7571
+ });
7572
+ }
7573
+ return slots;
7574
+ }
7575
+ function buildAllNavDropSlots(draggedHrefKey) {
7576
+ const slots = [...buildRootNavDropSlots()];
7577
+ for (const item of listReorderableNavItems()) {
7578
+ const key = item.getAttribute("data-ohw-href-key");
7579
+ if (!key || key === draggedHrefKey) continue;
7580
+ const group = item.closest("[data-ohw-nav-group]");
7581
+ if (!group?.querySelector(":scope > [data-ohw-nav-children]")) continue;
7582
+ slots.push(...buildChildNavDropSlots(key));
7583
+ }
7584
+ const dragged = listNavbarItems().find((el) => el.getAttribute("data-ohw-href-key") === draggedHrefKey);
7585
+ const parentGroup = dragged?.closest("[data-ohw-nav-children]")?.closest("[data-ohw-nav-group]");
7586
+ const parentTrigger = parentGroup?.querySelector(":scope > [data-ohw-href-key]");
7587
+ const parentKey = parentTrigger?.getAttribute("data-ohw-href-key");
7588
+ if (parentKey && isNavbarHrefKey(parentKey)) {
7589
+ const existing = slots.some((s) => s.parentId === parentKey);
7590
+ if (!existing) slots.push(...buildChildNavDropSlots(parentKey));
7591
+ }
7592
+ return slots;
7593
+ }
7594
+ function hitTestNavDropSlot(clientX, clientY, draggedHrefKey) {
7595
+ const slots = buildAllNavDropSlots(draggedHrefKey);
7596
+ let best = null;
7597
+ for (const slot of slots) {
7598
+ const cx2 = slot.left + slot.width / 2;
7599
+ const cy = slot.top + slot.height / 2;
7600
+ 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;
7601
+ if (!best || dist < best.dist) best = { slot, dist };
7602
+ }
7603
+ return best?.slot ?? null;
7604
+ }
7605
+
7606
+ // src/useNavItemDrag.ts
7607
+ import { useCallback as useCallback4, useEffect as useEffect7, useRef as useRef6, useState as useState6 } from "react";
7608
+ function useNavItemDrag({
7609
+ isEditMode,
7610
+ editContentRef,
7611
+ selectedElRef,
7612
+ activeElRef,
7613
+ footerDragRef,
7614
+ suppressNextClickRef,
7615
+ suppressClickUntilRef,
7616
+ siblingHintElRef,
7617
+ postToParentRef,
7618
+ deselectRef,
7619
+ selectRef,
7620
+ deactivateRef,
7621
+ setLinkPopover,
7622
+ linkPopoverOpenRef,
7623
+ setIsItemDragging,
7624
+ setDraggedItemRect,
7625
+ setSiblingHintRect,
7626
+ setSiblingHintRects,
7627
+ setToolbarRect,
7628
+ getNavigationItemAnchor: getNavigationItemAnchor2,
7629
+ isDragHandleDisabled: isDragHandleDisabled2,
7630
+ isNavbarButton: isNavbarButton3
7631
+ }) {
7632
+ const navDragRef = useRef6(null);
7633
+ const [navDropSlots, setNavDropSlots] = useState6([]);
7634
+ const [activeNavDropIndex, setActiveNavDropIndex] = useState6(null);
7635
+ const navPointerDragRef = useRef6(null);
7636
+ const clearNavDragVisuals = useCallback4(() => {
7637
+ navDragRef.current = null;
7638
+ setNavDropSlots([]);
7639
+ setActiveNavDropIndex(null);
7640
+ setDraggedItemRect(null);
7641
+ setSiblingHintRects([]);
7642
+ setIsItemDragging(false);
7643
+ unlockItemDragInteraction();
7644
+ }, [setDraggedItemRect, setIsItemDragging, setSiblingHintRects]);
7645
+ const refreshNavDragVisuals = useCallback4(
7646
+ (session, activeSlot, clientX, clientY) => {
7647
+ setDraggedItemRect(session.draggedEl.getBoundingClientRect());
7648
+ if (typeof clientX === "number" && typeof clientY === "number") {
7649
+ session.lastClientX = clientX;
7650
+ session.lastClientY = clientY;
7651
+ }
7652
+ session.activeSlot = activeSlot;
7653
+ setSiblingHintRects([]);
7654
+ const slots = buildAllNavDropSlots(session.hrefKey);
7655
+ setNavDropSlots(slots);
7656
+ const activeIdx = activeSlot ? slots.findIndex(
7657
+ (s) => s.parentId === activeSlot.parentId && s.insertIndex === activeSlot.insertIndex
7658
+ ) : -1;
7659
+ setActiveNavDropIndex(activeIdx >= 0 ? activeIdx : null);
7660
+ },
7661
+ [setDraggedItemRect, setSiblingHintRects]
7662
+ );
7663
+ const refreshNavDragVisualsRef = useRef6(refreshNavDragVisuals);
7664
+ refreshNavDragVisualsRef.current = refreshNavDragVisuals;
7665
+ const commitNavDragRef = useRef6(() => {
7666
+ });
7667
+ const beginNavDragRef = useRef6(() => {
7668
+ });
7669
+ const beginNavDrag = useCallback4(
7670
+ (session) => {
7671
+ const rect = session.draggedEl.getBoundingClientRect();
7672
+ session.lastClientX = session.lastClientX || rect.left + rect.width / 2;
7673
+ session.lastClientY = session.lastClientY || rect.top + rect.height / 2;
7674
+ session.activeSlot = session.activeSlot ?? null;
7675
+ navDragRef.current = session;
7676
+ setIsItemDragging(true);
7677
+ lockItemDuringDrag();
7678
+ siblingHintElRef.current = null;
7679
+ setSiblingHintRect(null);
7680
+ if (session.wasSelected && selectedElRef.current === session.draggedEl) {
7681
+ setToolbarRect(rect);
7682
+ }
7683
+ const initialSlot = hitTestNavDropSlot(session.lastClientX, session.lastClientY, session.hrefKey);
7684
+ refreshNavDragVisuals(session, initialSlot, session.lastClientX, session.lastClientY);
7685
+ },
7686
+ [
7687
+ refreshNavDragVisuals,
7688
+ selectedElRef,
7689
+ setIsItemDragging,
7690
+ setSiblingHintRect,
7691
+ setToolbarRect,
7692
+ siblingHintElRef
7693
+ ]
7694
+ );
7695
+ beginNavDragRef.current = beginNavDrag;
7696
+ const commitNavDrag = useCallback4(
7697
+ (clientX, clientY) => {
7698
+ const session = navDragRef.current;
7699
+ if (!session) {
7700
+ clearNavDragVisuals();
7701
+ return;
7702
+ }
7703
+ const x = typeof clientX === "number" && (clientX !== 0 || clientY !== 0) ? clientX : session.lastClientX;
7704
+ const y = typeof clientY === "number" && (clientX !== 0 || clientY !== 0) ? clientY : session.lastClientY;
7705
+ const slot = session.activeSlot ?? hitTestNavDropSlot(x, y, session.hrefKey);
7706
+ const planned = slot != null ? planNavItemMove(session.hrefKey, slot.parentId, slot.insertIndex) : null;
7707
+ const wasSelected = session.wasSelected;
7708
+ const hrefKey = session.hrefKey;
7709
+ const applySelectionAfterDrop = () => {
7710
+ if (!wasSelected) {
7711
+ deselectRef.current();
7712
+ return;
7713
+ }
7714
+ const desktop = document.querySelector("[data-ohw-nav-container]");
7715
+ const drawer = document.querySelector("[data-ohw-nav-drawer]");
7716
+ 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)}"]`);
7717
+ if (link) {
7718
+ selectRef.current(link);
7719
+ return;
7720
+ }
7721
+ deselectRef.current();
7722
+ };
7723
+ if (planned) {
7724
+ editContentRef.current = {
7725
+ ...editContentRef.current,
7726
+ [NAV_ORDER_KEY]: planned.orderJson
7727
+ };
7728
+ applyNavForest(planned.forest);
7729
+ document.querySelectorAll(
7730
+ "nav [data-ohw-href-key], [data-ohw-nav-container] [data-ohw-href-key], [data-ohw-nav-drawer] [data-ohw-href-key]"
7731
+ ).forEach((el) => {
7732
+ if (isNavbarHrefKey(el.getAttribute("data-ohw-href-key"))) {
7733
+ disableNativeHrefDrag(el);
7734
+ }
7735
+ });
7736
+ postToParentRef.current({
7737
+ type: "ow:change",
7738
+ nodes: [{ key: NAV_ORDER_KEY, text: planned.orderJson }]
7739
+ });
7740
+ applySelectionAfterDrop();
7741
+ clearNavDragVisuals();
7742
+ requestAnimationFrame(() => {
7743
+ if (editContentRef.current[NAV_ORDER_KEY] === planned.orderJson) {
7744
+ applyNavForest(planned.forest);
7745
+ }
7746
+ applySelectionAfterDrop();
7747
+ requestAnimationFrame(applySelectionAfterDrop);
7748
+ });
7749
+ return;
7750
+ }
7751
+ applySelectionAfterDrop();
7752
+ clearNavDragVisuals();
7753
+ },
7754
+ [clearNavDragVisuals, deselectRef, editContentRef, postToParentRef, selectRef]
7755
+ );
7756
+ commitNavDragRef.current = commitNavDrag;
7757
+ const startNavLinkDrag = useCallback4(
7758
+ (anchor, clientX, clientY, wasSelected) => {
7759
+ if (footerDragRef.current) return false;
7760
+ const hrefKey = anchor.getAttribute("data-ohw-href-key");
7761
+ if (!hrefKey || !isNavbarHrefKey(hrefKey)) return false;
7762
+ if (isNavbarButton3(anchor) || isDragHandleDisabled2(anchor)) return false;
7763
+ beginNavDrag({
7764
+ hrefKey,
7765
+ wasSelected,
7766
+ draggedEl: anchor,
7767
+ lastClientX: clientX,
7768
+ lastClientY: clientY,
7769
+ activeSlot: null
7770
+ });
7771
+ return true;
7772
+ },
7773
+ [beginNavDrag, footerDragRef, isDragHandleDisabled2, isNavbarButton3]
7774
+ );
7775
+ const onNavDragOver = useCallback4(
7776
+ (e) => {
7777
+ const session = navDragRef.current;
7778
+ if (!session) return false;
7779
+ e.preventDefault();
7780
+ if (e.dataTransfer) e.dataTransfer.dropEffect = "move";
7781
+ const slot = hitTestNavDropSlot(e.clientX, e.clientY, session.hrefKey);
7782
+ refreshNavDragVisualsRef.current(session, slot, e.clientX, e.clientY);
7783
+ return true;
7784
+ },
7785
+ []
7786
+ );
7787
+ useEffect7(() => {
7788
+ if (!isEditMode) return;
7789
+ const THRESHOLD = 10;
7790
+ const resolveWasSelected = (el) => {
7791
+ if (selectedElRef.current === el) return true;
7792
+ const activeAnchor = activeElRef.current ? getNavigationItemAnchor2(activeElRef.current) : null;
7793
+ return activeAnchor === el;
7794
+ };
7795
+ const onPointerDown = (e) => {
7796
+ if (e.button !== 0) return;
7797
+ if (navDragRef.current || footerDragRef.current) return;
7798
+ const target = e.target;
7799
+ if (!target) return;
7800
+ if (target.closest(
7801
+ '[data-ohw-drag-handle-container], [data-slot="drag-handle"], [data-ohw-toolbar], [data-ohw-item-toolbar-anchor], [data-ohw-link-popover-root]'
7802
+ )) {
7803
+ return;
7804
+ }
7805
+ if (target.closest("[data-ohw-item-drag-surface]")) return;
7806
+ const anchor = getNavigationItemAnchor2(target);
7807
+ const hrefKey = anchor?.getAttribute("data-ohw-href-key") ?? null;
7808
+ if (!anchor || !isNavbarHrefKey(hrefKey)) return;
7809
+ if (isNavbarButton3(anchor) || isDragHandleDisabled2(anchor)) return;
7810
+ navPointerDragRef.current = {
7811
+ el: anchor,
7812
+ startX: e.clientX,
7813
+ startY: e.clientY,
7814
+ pointerId: e.pointerId,
7815
+ wasSelected: resolveWasSelected(anchor),
7816
+ started: false
7817
+ };
7818
+ };
7819
+ const onPointerMove = (e) => {
7820
+ const pending = navPointerDragRef.current;
7821
+ if (!pending) return;
7822
+ if (pending.started) {
7823
+ e.preventDefault();
7824
+ clearTextSelection();
7825
+ const session = navDragRef.current;
7826
+ if (!session) return;
7827
+ const slot = hitTestNavDropSlot(e.clientX, e.clientY, session.hrefKey);
7828
+ refreshNavDragVisualsRef.current(session, slot, e.clientX, e.clientY);
7829
+ return;
7830
+ }
7831
+ const dx = e.clientX - pending.startX;
7832
+ const dy = e.clientY - pending.startY;
7833
+ if (dx * dx + dy * dy < THRESHOLD * THRESHOLD) return;
7834
+ e.preventDefault();
7835
+ pending.started = true;
7836
+ armItemPressDrag();
7837
+ clearTextSelection();
7838
+ try {
7839
+ document.body.setPointerCapture(pending.pointerId);
7840
+ } catch {
7841
+ }
7842
+ if (linkPopoverOpenRef.current) setLinkPopover(null);
7843
+ if (activeElRef.current) deactivateRef.current();
7844
+ const key = pending.el.getAttribute("data-ohw-href-key");
7845
+ if (!key) return;
7846
+ beginNavDragRef.current({
7847
+ hrefKey: key,
7848
+ wasSelected: pending.wasSelected,
7849
+ draggedEl: pending.el,
7850
+ lastClientX: e.clientX,
7851
+ lastClientY: e.clientY,
7852
+ activeSlot: null
7853
+ });
7854
+ };
7855
+ const endPointerDrag = (e) => {
7856
+ const pending = navPointerDragRef.current;
7857
+ navPointerDragRef.current = null;
7858
+ try {
7859
+ if (document.body.hasPointerCapture(e.pointerId)) {
7860
+ document.body.releasePointerCapture(e.pointerId);
7861
+ }
7862
+ } catch {
7863
+ }
7864
+ if (!pending) return;
7865
+ if (!pending.started) {
7866
+ unlockItemDragInteraction();
7867
+ return;
7868
+ }
7869
+ suppressNextClickRef.current = true;
7870
+ suppressClickUntilRef.current = Date.now() + 500;
7871
+ commitNavDragRef.current(e.clientX, e.clientY);
7872
+ };
7873
+ const blockSelectStart = (e) => {
7874
+ if (navDragRef.current || navPointerDragRef.current?.started || document.documentElement.hasAttribute("data-ohw-footer-press-drag")) {
7875
+ e.preventDefault();
7876
+ }
7877
+ };
7878
+ const onDragEnd = () => {
7879
+ if (!navDragRef.current) return;
7880
+ suppressNextClickRef.current = true;
7881
+ suppressClickUntilRef.current = Date.now() + 500;
7882
+ commitNavDragRef.current();
7883
+ };
7884
+ document.addEventListener("pointerdown", onPointerDown, true);
7885
+ document.addEventListener("pointermove", onPointerMove, true);
7886
+ document.addEventListener("pointerup", endPointerDrag, true);
7887
+ document.addEventListener("pointercancel", endPointerDrag, true);
7888
+ document.addEventListener("selectstart", blockSelectStart, true);
7889
+ document.addEventListener("dragend", onDragEnd, true);
7890
+ return () => {
7891
+ document.removeEventListener("pointerdown", onPointerDown, true);
7892
+ document.removeEventListener("pointermove", onPointerMove, true);
7893
+ document.removeEventListener("pointerup", endPointerDrag, true);
7894
+ document.removeEventListener("pointercancel", endPointerDrag, true);
7895
+ document.removeEventListener("selectstart", blockSelectStart, true);
7896
+ document.removeEventListener("dragend", onDragEnd, true);
7897
+ unlockItemDragInteraction();
7898
+ };
7899
+ }, [
7900
+ activeElRef,
7901
+ deactivateRef,
7902
+ footerDragRef,
7903
+ getNavigationItemAnchor2,
7904
+ isDragHandleDisabled2,
7905
+ isEditMode,
7906
+ isNavbarButton3,
7907
+ linkPopoverOpenRef,
7908
+ selectedElRef,
7909
+ setLinkPopover,
7910
+ suppressNextClickRef
7911
+ ]);
7912
+ const armNavPressFromChrome = useCallback4(
7913
+ (selected, clientX, clientY, pointerId) => {
7914
+ const hrefKey = selected.getAttribute("data-ohw-href-key");
7915
+ if (!hrefKey || !isNavbarHrefKey(hrefKey)) return false;
7916
+ if (isNavbarButton3(selected) || isDragHandleDisabled2(selected)) return false;
7917
+ navPointerDragRef.current = {
7918
+ el: selected,
7919
+ startX: clientX,
7920
+ startY: clientY,
7921
+ pointerId,
7922
+ wasSelected: true,
7923
+ started: false
7924
+ };
7925
+ return true;
7926
+ },
7927
+ [isDragHandleDisabled2, isNavbarButton3]
7928
+ );
7929
+ return {
7930
+ navDragRef,
7931
+ navDropSlots,
7932
+ activeNavDropIndex,
7933
+ startNavLinkDrag,
7934
+ commitNavDrag,
7935
+ onNavDragOver,
7936
+ armNavPressFromChrome,
7937
+ clearNavDragVisuals,
7938
+ refreshNavDragVisualsRef
7939
+ };
7940
+ }
7941
+
7942
+ // src/ui/footer-container-chrome.tsx
7943
+ import { Plus as Plus2 } from "lucide-react";
7944
+ import { jsx as jsx21, jsxs as jsxs13 } from "react/jsx-runtime";
7945
+ function FooterContainerChrome({ rect, onAdd }) {
7946
+ const chromeGap = 6;
7947
+ const buttonMargin = 7;
7948
+ return /* @__PURE__ */ jsx21(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ jsx21(
7949
+ "div",
7950
+ {
7951
+ "data-ohw-footer-container-chrome": "",
7952
+ "data-ohw-bridge": "",
7953
+ className: "pointer-events-none fixed z-[2147483647]",
7954
+ style: {
7955
+ top: rect.top - chromeGap,
7956
+ left: rect.left - chromeGap,
7957
+ width: rect.width + chromeGap * 2,
7958
+ height: rect.height + chromeGap * 2
7959
+ },
7960
+ children: /* @__PURE__ */ jsxs13(Tooltip, { children: [
7961
+ /* @__PURE__ */ jsx21(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx21(
7962
+ "button",
7963
+ {
7964
+ type: "button",
7965
+ "data-ohw-footer-add-button": "",
7966
+ className: "pointer-events-auto absolute left-1/2 flex size-7 -translate-x-1/2 -translate-y-full items-center justify-center rounded-[10px] border border-border bg-background p-0.5 shadow-sm transition-colors hover:bg-muted/80",
7967
+ style: { top: chromeGap - buttonMargin },
7968
+ "aria-label": "Add item",
7969
+ onMouseDown: (e) => {
7970
+ e.preventDefault();
7971
+ e.stopPropagation();
7972
+ },
7973
+ onClick: (e) => {
7974
+ e.preventDefault();
7975
+ e.stopPropagation();
7976
+ onAdd();
7977
+ },
7978
+ children: /* @__PURE__ */ jsx21(Plus2, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
7979
+ }
7980
+ ) }),
7981
+ /* @__PURE__ */ jsx21(TooltipContent, { side: "bottom", sideOffset: 9, children: "Add item" })
7982
+ ] })
7983
+ }
7984
+ ) });
7985
+ }
7986
+
7987
+ // src/ui/navbar-container-chrome.tsx
7988
+ import { Plus as Plus3 } from "lucide-react";
7989
+ import { jsx as jsx22 } from "react/jsx-runtime";
7990
+ function NavbarContainerChrome({
7991
+ rect,
7992
+ onAdd
7993
+ }) {
7994
+ const chromeGap = 6;
7995
+ return /* @__PURE__ */ jsx22(
7996
+ "div",
7997
+ {
7998
+ "data-ohw-navbar-container-chrome": "",
7999
+ "data-ohw-bridge": "",
8000
+ className: "pointer-events-none fixed z-[2147483647]",
8001
+ style: {
8002
+ top: rect.top - chromeGap,
8003
+ left: rect.left - chromeGap,
8004
+ width: rect.width + chromeGap * 2,
8005
+ height: rect.height + chromeGap * 2
8006
+ },
8007
+ children: /* @__PURE__ */ jsx22(
8008
+ "button",
8009
+ {
8010
+ type: "button",
8011
+ "data-ohw-navbar-add-button": "",
8012
+ 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",
8013
+ style: { top: "70%" },
8014
+ "aria-label": "Add item",
8015
+ onMouseDown: (e) => {
8016
+ e.preventDefault();
8017
+ e.stopPropagation();
8018
+ },
8019
+ onClick: (e) => {
8020
+ e.preventDefault();
8021
+ e.stopPropagation();
8022
+ onAdd();
8023
+ },
8024
+ children: /* @__PURE__ */ jsx22(Plus3, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
8025
+ }
8026
+ )
8027
+ }
8028
+ );
8029
+ }
8030
+
8031
+ // src/ui/drop-indicator.tsx
8032
+ import * as React10 from "react";
8033
+ import { jsx as jsx23 } from "react/jsx-runtime";
8034
+ var dropIndicatorVariants = cva(
8035
+ "ov-gap-line pointer-events-none shrink-0 transition-opacity duration-150",
8036
+ {
8037
+ variants: {
8038
+ direction: {
8039
+ vertical: "h-6 w-[3px]",
8040
+ horizontal: "h-[3px] w-[200px]"
8041
+ },
8042
+ state: {
8043
+ default: "opacity-0",
8044
+ hover: "opacity-100",
8045
+ dragIdle: "opacity-40",
8046
+ dragActive: "opacity-100"
8047
+ }
8048
+ },
8049
+ defaultVariants: {
8050
+ direction: "vertical",
8051
+ state: "default"
8052
+ }
8053
+ }
8054
+ );
8055
+ var DropIndicator = React10.forwardRef(
8056
+ ({ className, direction, state, ...props }, ref) => {
8057
+ return /* @__PURE__ */ jsx23(
8058
+ "div",
8059
+ {
8060
+ ref,
8061
+ "data-slot": "drop-indicator",
8062
+ "data-direction": direction ?? "vertical",
8063
+ "data-state": state ?? "default",
8064
+ "aria-hidden": "true",
8065
+ className: cn(dropIndicatorVariants({ direction, state }), className),
8066
+ ...props
8067
+ }
8068
+ );
8069
+ }
8070
+ );
8071
+ DropIndicator.displayName = "DropIndicator";
8072
+
8073
+ // src/ui/badge.tsx
8074
+ import { jsx as jsx24 } from "react/jsx-runtime";
8075
+ var badgeVariants = cva(
8076
+ "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",
8077
+ {
8078
+ variants: {
8079
+ variant: {
8080
+ default: "border-transparent bg-primary text-primary-foreground",
8081
+ secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
8082
+ destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
8083
+ outline: "text-foreground"
8084
+ }
8085
+ },
8086
+ defaultVariants: {
8087
+ variant: "default"
8088
+ }
8089
+ }
8090
+ );
8091
+ function Badge({ className, variant, ...props }) {
8092
+ return /* @__PURE__ */ jsx24("div", { className: cn(badgeVariants({ variant }), className), ...props });
8093
+ }
8094
+
8095
+ // src/OhhwellsBridge.tsx
8096
+ import { Link as Link2 } from "lucide-react";
8097
+ import { Fragment as Fragment5, jsx as jsx25, jsxs as jsxs14 } from "react/jsx-runtime";
8098
+ var PRIMARY2 = "#0885FE";
8099
+ var IMAGE_FADE_MS = 300;
8100
+ function runOpacityFade(el, onDone) {
8101
+ const anim = el.animate([{ opacity: 0 }, { opacity: 1 }], {
8102
+ duration: IMAGE_FADE_MS,
8103
+ easing: "ease",
8104
+ fill: "forwards"
8105
+ });
8106
+ let finished = false;
8107
+ const finish = () => {
8108
+ if (finished) return;
8109
+ finished = true;
8110
+ anim.cancel();
8111
+ el.style.opacity = "";
8112
+ onDone();
8113
+ };
8114
+ anim.finished.then(finish).catch(finish);
8115
+ window.setTimeout(finish, IMAGE_FADE_MS + 100);
8116
+ }
8117
+ function fadeInImageElement(img, onReady) {
8118
+ onReady();
8119
+ img.style.opacity = "0";
8120
+ runOpacityFade(img, () => {
8121
+ img.style.opacity = "";
8122
+ });
8123
+ }
8124
+ function applyEditableImageSrc(img, url) {
8125
+ img.removeAttribute("srcset");
8126
+ img.removeAttribute("sizes");
8127
+ img.src = url;
8128
+ }
8129
+ function fadeInBgImage(el, url, onReady) {
8130
+ const prevPos = el.style.position;
8131
+ if (!prevPos || prevPos === "static") el.style.position = "relative";
8132
+ const layer = document.createElement("div");
8133
+ layer.setAttribute("data-ohw-bg-fade-layer", "");
8134
+ Object.assign(layer.style, {
8135
+ position: "absolute",
8136
+ inset: "0",
8137
+ zIndex: "0",
8138
+ backgroundImage: `url('${url}')`,
8139
+ backgroundSize: "cover",
8140
+ backgroundPosition: "center",
8141
+ backgroundRepeat: "no-repeat",
8142
+ opacity: "0",
8143
+ pointerEvents: "none"
8144
+ });
8145
+ el.prepend(layer);
8146
+ onReady();
8147
+ runOpacityFade(layer, () => {
8148
+ el.style.backgroundImage = `url('${url}')`;
8149
+ layer.remove();
8150
+ if (!prevPos || prevPos === "static") el.style.position = prevPos;
8151
+ });
8152
+ }
8153
+ function getSectionsTracker() {
8154
+ let el = document.querySelector("[data-ohw-sections-tracker]");
8155
+ if (!el) {
8156
+ el = document.createElement("div");
8157
+ el.setAttribute("data-ohw-sections-tracker", "");
8158
+ el.style.display = "none";
8159
+ document.body.appendChild(el);
8160
+ }
8161
+ return el;
8162
+ }
8163
+ function updateSectionScheduleId(insertAfter, scheduleId) {
8164
+ const tracker = getSectionsTracker();
8165
+ let sections = [];
8166
+ try {
8167
+ sections = JSON.parse(tracker.textContent || "[]");
8168
+ } catch {
8169
+ }
8170
+ const currentPath = window.location.pathname;
8171
+ const updated = sections.map((s) => {
6639
8172
  if (s.type !== "scheduling" || s.insertAfter !== insertAfter) return s;
6640
8173
  if (s.pagePath && s.pagePath !== currentPath) return s;
6641
8174
  return { ...s, scheduleId };
@@ -6740,7 +8273,7 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
6740
8273
  const root = createRoot(container);
6741
8274
  flushSync(() => {
6742
8275
  root.render(
6743
- /* @__PURE__ */ jsx23(
8276
+ /* @__PURE__ */ jsx25(
6744
8277
  SchedulingWidget,
6745
8278
  {
6746
8279
  notifyOnConnect,
@@ -6815,7 +8348,7 @@ function syncNavigationDragCursorAttrs() {
6815
8348
  el.setAttribute("data-ohw-can-drag", "");
6816
8349
  });
6817
8350
  }
6818
- function collectEditableNodes() {
8351
+ function collectEditableNodes(extraContent) {
6819
8352
  const nodes = Array.from(document.querySelectorAll("[data-ohw-editable]")).map((el) => {
6820
8353
  if (el.dataset.ohwEditable === "image") {
6821
8354
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -6843,6 +8376,14 @@ function collectEditableNodes() {
6843
8376
  if (!key) return;
6844
8377
  nodes.push({ key, type: "link", text: getLinkHref2(el) });
6845
8378
  });
8379
+ if (extraContent) {
8380
+ for (const key of [NAV_ORDER_KEY, FOOTER_ORDER_KEY, NAV_COUNT_KEY]) {
8381
+ const text = extraContent[key];
8382
+ if (typeof text === "string" && text.length > 0) {
8383
+ nodes.push({ key, type: "meta", text });
8384
+ }
8385
+ }
8386
+ }
6846
8387
  document.querySelectorAll('[data-ohw-editable="video"]').forEach((el) => {
6847
8388
  const key = el.dataset.ohwKey ?? "";
6848
8389
  const video = getVideoEl(el);
@@ -6933,7 +8474,7 @@ function applyLinkByKey(key, val) {
6933
8474
  }
6934
8475
  function isInsideLinkEditor(target) {
6935
8476
  return Boolean(
6936
- target.closest("[data-ohw-link-popover-root]") || target.closest("[data-ohw-link-modal-root]") || target.closest("[data-ohw-link-page-dropdown]") || target.closest("[data-ohw-section-picker]") || target.closest("[data-ohw-navbar-container-chrome]") || target.closest("[data-ohw-navbar-add-button]") || target.closest('[data-slot="popover-content"]') || target.closest('[data-slot="dialog-content"]') || target.closest('[data-slot="dialog-overlay"]')
8477
+ target.closest("[data-ohw-link-popover-root]") || target.closest("[data-ohw-link-modal-root]") || target.closest("[data-ohw-link-page-dropdown]") || target.closest("[data-ohw-section-picker]") || target.closest("[data-ohw-navbar-container-chrome]") || target.closest("[data-ohw-navbar-add-button]") || target.closest("[data-ohw-footer-container-chrome]") || target.closest("[data-ohw-footer-add-button]") || target.closest('[data-slot="popover-content"]') || target.closest('[data-slot="dialog-content"]') || target.closest('[data-slot="dialog-overlay"]')
6937
8478
  );
6938
8479
  }
6939
8480
  function getHrefKeyFromElement(el) {
@@ -6956,6 +8497,11 @@ function getNavigationItemAnchor(el) {
6956
8497
  function isNavigationItem(el) {
6957
8498
  return getNavigationItemAnchor(el) !== null;
6958
8499
  }
8500
+ function listNavigationItems() {
8501
+ return Array.from(
8502
+ document.querySelectorAll("nav [data-ohw-href-key], footer [data-ohw-href-key]")
8503
+ ).filter((el) => isNavigationItem(el));
8504
+ }
6959
8505
  function getNavigationItemReorderState(anchor) {
6960
8506
  if (isNavbarButton2(anchor)) return { key: null, disabled: false };
6961
8507
  return getReorderHandleStateFromAnchor(anchor);
@@ -6988,7 +8534,7 @@ function countFooterNavItems(el) {
6988
8534
  return Array.from(el.querySelectorAll("[data-ohw-href-key]")).filter(isNavigationItem).length;
6989
8535
  }
6990
8536
  function findFooterItemGroup(item) {
6991
- const explicit = item.closest("[data-ohw-footer-column]");
8537
+ const explicit = item.closest("[data-ohw-footer-col], [data-ohw-footer-column]");
6992
8538
  if (explicit) return explicit;
6993
8539
  const footer = item.closest("footer");
6994
8540
  if (!footer) return null;
@@ -7005,17 +8551,17 @@ function isNavigationRoot(el) {
7005
8551
  function isInferredFooterGroup(el) {
7006
8552
  const footer = el.closest("footer");
7007
8553
  if (!footer || el === footer) return false;
7008
- if (el.hasAttribute("data-ohw-footer-column")) return true;
8554
+ if (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column")) return true;
7009
8555
  return countFooterNavItems(el) >= 2;
7010
8556
  }
7011
8557
  function isNavigationContainer(el) {
7012
- return el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-footer-column") || isNavigationRoot(el) || isInferredFooterGroup(el);
8558
+ return el.hasAttribute("data-ohw-nav-container") || isFooterLinksContainer(el) || el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isNavigationRoot(el) || isInferredFooterGroup(el);
7013
8559
  }
7014
8560
  function isNavbarLinksContainer(el) {
7015
8561
  return el.hasAttribute("data-ohw-nav-container");
7016
8562
  }
7017
8563
  function getFooterColumn(el) {
7018
- return el.closest("[data-ohw-footer-column]");
8564
+ return el.closest("[data-ohw-footer-col], [data-ohw-footer-column]");
7019
8565
  }
7020
8566
  function resolveFooterColumnSelectionTarget(target, clientX, clientY) {
7021
8567
  if (getNavigationItemAnchor(target)) return null;
@@ -7028,6 +8574,9 @@ function isPointOverNavigation(x, y) {
7028
8574
  const roots = /* @__PURE__ */ new Set();
7029
8575
  const navRoot = document.querySelector("[data-ohw-nav-root]") ?? document.querySelector("nav");
7030
8576
  if (navRoot) roots.add(navRoot);
8577
+ document.querySelectorAll("[data-ohw-nav-drawer], [data-ohw-nav-container]").forEach((el) => {
8578
+ roots.add(el);
8579
+ });
7031
8580
  const footer = document.querySelector("footer");
7032
8581
  if (footer) roots.add(footer);
7033
8582
  for (const root of roots) {
@@ -7039,7 +8588,7 @@ function isPointOverNavigation(x, y) {
7039
8588
  function findHoveredNavOrFooterContainer(x, y) {
7040
8589
  const candidates = [
7041
8590
  ...document.querySelectorAll("[data-ohw-nav-container]"),
7042
- ...document.querySelectorAll("[data-ohw-footer-column]")
8591
+ ...listFooterColumns()
7043
8592
  ];
7044
8593
  for (const container of candidates) {
7045
8594
  const r2 = container.getBoundingClientRect();
@@ -7056,6 +8605,12 @@ function isPointOverNavItem(container, x, y) {
7056
8605
  return x >= itemRect.left && x <= itemRect.right && y >= itemRect.top && y <= itemRect.bottom;
7057
8606
  });
7058
8607
  }
8608
+ function isPointOverFooterColumn(x, y) {
8609
+ return listFooterColumns().some((col) => {
8610
+ const r2 = col.getBoundingClientRect();
8611
+ return x >= r2.left && x <= r2.right && y >= r2.top && y <= r2.bottom;
8612
+ });
8613
+ }
7059
8614
  function resolveNavContainerSelectionTarget(target, clientX, clientY) {
7060
8615
  if (getNavigationItemAnchor(target)) return null;
7061
8616
  if (target.closest('[data-ohw-role="navbar-button"]')) return null;
@@ -7086,32 +8641,60 @@ function getNavigationSelectionParent(el) {
7086
8641
  if (footerGroup) return footerGroup;
7087
8642
  return getNavigationRoot(el);
7088
8643
  }
7089
- if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup(el)) {
8644
+ if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup(el))) {
8645
+ return getFooterLinksContainer();
8646
+ }
8647
+ if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isFooterLinksContainer(el) || isInferredFooterGroup(el)) {
7090
8648
  return getNavigationRoot(el);
7091
8649
  }
7092
8650
  return null;
7093
8651
  }
8652
+ function collectNavigationItemSiblingHintRects(selected) {
8653
+ if (!isNavigationItem(selected)) return [];
8654
+ const footerColumn = getFooterColumn(selected);
8655
+ if (footerColumn) {
8656
+ return listFooterLinksInColumn(footerColumn).filter((link) => link !== selected).map((link) => link.getBoundingClientRect());
8657
+ }
8658
+ const navContainer = selected.closest("[data-ohw-nav-container], [data-ohw-nav-drawer]");
8659
+ if (navContainer) {
8660
+ return Array.from(navContainer.querySelectorAll("[data-ohw-href-key]")).filter((el) => isNavigationItem(el) && el !== selected).map((el) => el.getBoundingClientRect());
8661
+ }
8662
+ return listNavigationItems().filter((el) => el !== selected).map((el) => el.getBoundingClientRect());
8663
+ }
7094
8664
  function placeCaretAtPoint(el, x, y) {
7095
8665
  const selection = window.getSelection();
7096
8666
  if (!selection) return;
7097
- if (typeof document.caretRangeFromPoint === "function") {
7098
- const range = document.caretRangeFromPoint(x, y);
7099
- if (range && el.contains(range.startContainer)) {
7100
- selection.removeAllRanges();
7101
- selection.addRange(range);
7102
- return;
8667
+ const overlays = Array.from(
8668
+ document.querySelectorAll(
8669
+ "[data-ohw-item-drag-surface], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-ohw-item-toolbar-anchor]"
8670
+ )
8671
+ );
8672
+ const prevPointerEvents = overlays.map((node) => node.style.pointerEvents);
8673
+ for (const node of overlays) node.style.pointerEvents = "none";
8674
+ try {
8675
+ if (typeof document.caretRangeFromPoint === "function") {
8676
+ const range = document.caretRangeFromPoint(x, y);
8677
+ if (range && el.contains(range.startContainer)) {
8678
+ selection.removeAllRanges();
8679
+ selection.addRange(range);
8680
+ return;
8681
+ }
7103
8682
  }
7104
- }
7105
- const caretPositionFromPoint = document.caretPositionFromPoint;
7106
- if (typeof caretPositionFromPoint === "function") {
7107
- const position = caretPositionFromPoint(x, y);
7108
- if (position && el.contains(position.offsetNode)) {
7109
- const range = document.createRange();
7110
- range.setStart(position.offsetNode, position.offset);
7111
- range.collapse(true);
7112
- selection.removeAllRanges();
7113
- selection.addRange(range);
8683
+ const caretPositionFromPoint = document.caretPositionFromPoint;
8684
+ if (typeof caretPositionFromPoint === "function") {
8685
+ const position = caretPositionFromPoint(x, y);
8686
+ if (position && el.contains(position.offsetNode)) {
8687
+ const range = document.createRange();
8688
+ range.setStart(position.offsetNode, position.offset);
8689
+ range.collapse(true);
8690
+ selection.removeAllRanges();
8691
+ selection.addRange(range);
8692
+ }
7114
8693
  }
8694
+ } finally {
8695
+ overlays.forEach((node, i) => {
8696
+ node.style.pointerEvents = prevPointerEvents[i] ?? "";
8697
+ });
7115
8698
  }
7116
8699
  }
7117
8700
  function selectAllTextInEditable(el) {
@@ -7232,9 +8815,16 @@ function sanitizeHtml(html) {
7232
8815
  if (!SAFE_TAGS.has(el.tagName)) {
7233
8816
  parent.replaceChild(document.createTextNode(el.textContent ?? ""), el);
7234
8817
  } else {
7235
- const textAlign = el.style?.textAlign || el.getAttribute("align") || "";
8818
+ const htmlEl = el;
8819
+ const textAlign = htmlEl.style?.textAlign || el.getAttribute("align") || "";
8820
+ const fontWeight = htmlEl.style?.fontWeight || "";
8821
+ const fontStyle = htmlEl.style?.fontStyle || "";
8822
+ const textDecorationLine = htmlEl.style?.textDecorationLine || htmlEl.style?.textDecoration || "";
7236
8823
  for (const attr of Array.from(el.attributes)) el.removeAttribute(attr.name);
7237
- if (textAlign) el.style.textAlign = textAlign;
8824
+ if (textAlign) htmlEl.style.textAlign = textAlign;
8825
+ if (fontWeight) htmlEl.style.fontWeight = fontWeight;
8826
+ if (fontStyle) htmlEl.style.fontStyle = fontStyle;
8827
+ if (textDecorationLine) htmlEl.style.textDecorationLine = textDecorationLine;
7238
8828
  walk(el);
7239
8829
  }
7240
8830
  }
@@ -7277,7 +8867,8 @@ function EditGlowChrome({
7277
8867
  rect,
7278
8868
  elRef,
7279
8869
  reorderHrefKey,
7280
- dragDisabled = false
8870
+ dragDisabled = false,
8871
+ hideHandle = false
7281
8872
  }) {
7282
8873
  const GAP = SELECTION_CHROME_GAP2;
7283
8874
  return /* @__PURE__ */ jsxs14(
@@ -7295,7 +8886,7 @@ function EditGlowChrome({
7295
8886
  zIndex: 2147483646
7296
8887
  },
7297
8888
  children: [
7298
- /* @__PURE__ */ jsx23(
8889
+ /* @__PURE__ */ jsx25(
7299
8890
  "div",
7300
8891
  {
7301
8892
  style: {
@@ -7308,7 +8899,7 @@ function EditGlowChrome({
7308
8899
  }
7309
8900
  }
7310
8901
  ),
7311
- reorderHrefKey && /* @__PURE__ */ jsx23(
8902
+ reorderHrefKey && !hideHandle && /* @__PURE__ */ jsx25(
7312
8903
  "div",
7313
8904
  {
7314
8905
  "data-ohw-drag-handle-container": "",
@@ -7320,7 +8911,7 @@ function EditGlowChrome({
7320
8911
  transform: "translate(calc(-100% - 7px), -50%)",
7321
8912
  pointerEvents: dragDisabled ? "none" : "auto"
7322
8913
  },
7323
- children: /* @__PURE__ */ jsx23(
8914
+ children: /* @__PURE__ */ jsx25(
7324
8915
  DragHandle,
7325
8916
  {
7326
8917
  "aria-label": `Reorder ${reorderHrefKey}`,
@@ -7421,6 +9012,79 @@ function resolveItemInteractionState(rect, parentScroll) {
7421
9012
  const { transform } = calcToolbarPos(rect, parentScroll, 120);
7422
9013
  return transform.includes("translateY(-100%)") ? "active-top" : "active-bottom";
7423
9014
  }
9015
+ function resolveSelectionStartElement(sel) {
9016
+ if (!sel || sel.rangeCount === 0) return null;
9017
+ const range = sel.getRangeAt(0);
9018
+ let startNode = range.startContainer;
9019
+ if (startNode.nodeType === Node.ELEMENT_NODE) {
9020
+ const el = startNode;
9021
+ startNode = el.childNodes[range.startOffset] ?? el.childNodes[range.startOffset - 1] ?? el;
9022
+ }
9023
+ return startNode.nodeType === Node.TEXT_NODE ? startNode.parentElement : startNode;
9024
+ }
9025
+ function detectActiveFormats(startEl, activeRoot) {
9026
+ const formats = /* @__PURE__ */ new Set();
9027
+ const cs = getComputedStyle(startEl);
9028
+ const weight = parseInt(cs.fontWeight, 10);
9029
+ let hasBold = cs.fontWeight === "bold" || !Number.isNaN(weight) && weight >= 600;
9030
+ let hasItalic = cs.fontStyle === "italic" || cs.fontStyle === "oblique";
9031
+ let hasUnderline = false;
9032
+ let hasStrike = false;
9033
+ let hasUl = false;
9034
+ let hasOl = false;
9035
+ for (let el = startEl; el; el = el.parentElement) {
9036
+ if (el.tagName === "B" || el.tagName === "STRONG") hasBold = true;
9037
+ if (el.tagName === "I" || el.tagName === "EM") hasItalic = true;
9038
+ const decoration = getComputedStyle(el).textDecorationLine;
9039
+ if (decoration.includes("underline") || el.tagName === "U" || el.tagName === "INS") hasUnderline = true;
9040
+ if (decoration.includes("line-through") || el.tagName === "S" || el.tagName === "STRIKE" || el.tagName === "DEL") hasStrike = true;
9041
+ if (el.tagName === "UL") hasUl = true;
9042
+ if (el.tagName === "OL") hasOl = true;
9043
+ if (el === activeRoot) break;
9044
+ }
9045
+ if (hasBold) formats.add("bold");
9046
+ if (hasItalic) formats.add("italic");
9047
+ if (hasUnderline) formats.add("underline");
9048
+ if (hasStrike) formats.add("strikeThrough");
9049
+ if (hasUl) formats.add("insertUnorderedList");
9050
+ if (hasOl) formats.add("insertOrderedList");
9051
+ const block = startEl.closest("div, p, h1, h2, h3, h4, h5, h6, li, td, th") ?? startEl;
9052
+ const align = getComputedStyle(block).textAlign;
9053
+ if (align === "center") formats.add("justifyCenter");
9054
+ else if (align === "right" || align === "end") formats.add("justifyRight");
9055
+ else formats.add("justifyLeft");
9056
+ return formats;
9057
+ }
9058
+ function setsEqual(a, b) {
9059
+ if (a.size !== b.size) return false;
9060
+ for (const item of a) {
9061
+ if (!b.has(item)) return false;
9062
+ }
9063
+ return true;
9064
+ }
9065
+ function textOffsetInRoot(root, node, offset) {
9066
+ const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
9067
+ let total = 0;
9068
+ let current;
9069
+ while (current = walker.nextNode()) {
9070
+ if (current === node) return total + offset;
9071
+ total += (current.textContent ?? "").length;
9072
+ }
9073
+ return total;
9074
+ }
9075
+ function pointAtTextOffset(root, targetOffset) {
9076
+ const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
9077
+ let total = 0;
9078
+ let current;
9079
+ let last = null;
9080
+ while (current = walker.nextNode()) {
9081
+ const len = (current.textContent ?? "").length;
9082
+ if (total + len >= targetOffset) return { node: current, offset: targetOffset - total };
9083
+ total += len;
9084
+ last = current;
9085
+ }
9086
+ return last ? { node: last, offset: (last.textContent ?? "").length } : null;
9087
+ }
7424
9088
  function FloatingToolbar({
7425
9089
  rect,
7426
9090
  parentScroll,
@@ -7430,9 +9094,9 @@ function FloatingToolbar({
7430
9094
  showEditLink,
7431
9095
  onEditLink
7432
9096
  }) {
7433
- const localRef = React10.useRef(null);
7434
- const [measuredW, setMeasuredW] = React10.useState(330);
7435
- const setRefs = React10.useCallback(
9097
+ const localRef = React11.useRef(null);
9098
+ const [measuredW, setMeasuredW] = React11.useState(330);
9099
+ const setRefs = React11.useCallback(
7436
9100
  (node) => {
7437
9101
  localRef.current = node;
7438
9102
  if (typeof elRef === "function") elRef(node);
@@ -7444,7 +9108,7 @@ function FloatingToolbar({
7444
9108
  },
7445
9109
  [elRef]
7446
9110
  );
7447
- React10.useLayoutEffect(() => {
9111
+ React11.useLayoutEffect(() => {
7448
9112
  const node = localRef.current;
7449
9113
  if (!node) return;
7450
9114
  const update = () => {
@@ -7457,7 +9121,7 @@ function FloatingToolbar({
7457
9121
  return () => ro.disconnect();
7458
9122
  }, [showEditLink, activeCommands]);
7459
9123
  const { top, left, transform } = calcToolbarPos(rect, parentScroll, measuredW);
7460
- return /* @__PURE__ */ jsx23(
9124
+ return /* @__PURE__ */ jsx25(
7461
9125
  "div",
7462
9126
  {
7463
9127
  ref: setRefs,
@@ -7470,11 +9134,11 @@ function FloatingToolbar({
7470
9134
  pointerEvents: "auto"
7471
9135
  },
7472
9136
  children: /* @__PURE__ */ jsxs14(CustomToolbar, { children: [
7473
- TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ jsxs14(React10.Fragment, { children: [
7474
- gi > 0 && /* @__PURE__ */ jsx23(CustomToolbarDivider, {}),
9137
+ TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ jsxs14(React11.Fragment, { children: [
9138
+ gi > 0 && /* @__PURE__ */ jsx25(CustomToolbarDivider, {}),
7475
9139
  btns.map((btn) => {
7476
9140
  const isActive = activeCommands.has(btn.cmd);
7477
- return /* @__PURE__ */ jsx23(
9141
+ return /* @__PURE__ */ jsx25(
7478
9142
  CustomToolbarButton,
7479
9143
  {
7480
9144
  title: btn.title,
@@ -7483,7 +9147,7 @@ function FloatingToolbar({
7483
9147
  e.preventDefault();
7484
9148
  onCommand(btn.cmd);
7485
9149
  },
7486
- children: /* @__PURE__ */ jsx23(
9150
+ children: /* @__PURE__ */ jsx25(
7487
9151
  "svg",
7488
9152
  {
7489
9153
  width: "16",
@@ -7504,7 +9168,7 @@ function FloatingToolbar({
7504
9168
  );
7505
9169
  })
7506
9170
  ] }, gi)),
7507
- showEditLink ? /* @__PURE__ */ jsx23(
9171
+ showEditLink ? /* @__PURE__ */ jsx25(
7508
9172
  CustomToolbarButton,
7509
9173
  {
7510
9174
  type: "button",
@@ -7518,7 +9182,7 @@ function FloatingToolbar({
7518
9182
  e.preventDefault();
7519
9183
  e.stopPropagation();
7520
9184
  },
7521
- children: /* @__PURE__ */ jsx23(Link2, { className: "size-4 shrink-0", "aria-hidden": true })
9185
+ children: /* @__PURE__ */ jsx25(Link2, { className: "size-4 shrink-0", "aria-hidden": true })
7522
9186
  }
7523
9187
  ) : null
7524
9188
  ] })
@@ -7535,7 +9199,7 @@ function StateToggle({
7535
9199
  states,
7536
9200
  onStateChange
7537
9201
  }) {
7538
- return /* @__PURE__ */ jsx23(
9202
+ return /* @__PURE__ */ jsx25(
7539
9203
  ToggleGroup,
7540
9204
  {
7541
9205
  "data-ohw-state-toggle": "",
@@ -7549,7 +9213,7 @@ function StateToggle({
7549
9213
  left: rect.right - 8,
7550
9214
  transform: "translateX(-100%)"
7551
9215
  },
7552
- children: states.map((state) => /* @__PURE__ */ jsx23(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
9216
+ children: states.map((state) => /* @__PURE__ */ jsx25(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
7553
9217
  }
7554
9218
  );
7555
9219
  }
@@ -7576,8 +9240,8 @@ function OhhwellsBridge() {
7576
9240
  const router = useRouter2();
7577
9241
  const searchParams = useSearchParams();
7578
9242
  const isEditMode = isEditSessionActive();
7579
- const [bridgeRoot, setBridgeRoot] = useState6(null);
7580
- useEffect7(() => {
9243
+ const [bridgeRoot, setBridgeRoot] = useState7(null);
9244
+ useEffect8(() => {
7581
9245
  const figtreeFontId = "ohw-figtree-font";
7582
9246
  if (!document.getElementById(figtreeFontId)) {
7583
9247
  const preconnect1 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.googleapis.com" });
@@ -7605,92 +9269,137 @@ function OhhwellsBridge() {
7605
9269
  const subdomainFromQuery = searchParams.get("subdomain");
7606
9270
  const subdomain = resolveSubdomain(subdomainFromQuery);
7607
9271
  useLinkHrefGuardian(pathname, subdomain, isEditMode);
7608
- const postToParent2 = useCallback3((data) => {
9272
+ const postToParent2 = useCallback5((data) => {
7609
9273
  if (typeof window !== "undefined" && window.parent !== window) {
7610
9274
  window.parent.postMessage(data, "*");
7611
9275
  }
7612
9276
  }, []);
7613
- const [fetchState, setFetchState] = useState6("idle");
7614
- const autoSaveTimers = useRef6(/* @__PURE__ */ new Map());
7615
- const activeElRef = useRef6(null);
7616
- const selectedElRef = useRef6(null);
7617
- const originalContentRef = useRef6(null);
7618
- const activeStateElRef = useRef6(null);
7619
- const parentScrollRef = useRef6(null);
7620
- const visibleViewportRef = useRef6(null);
7621
- const [dialogPortalContainer, setDialogPortalContainer] = useState6(null);
7622
- const attachVisibleViewport = useCallback3((node) => {
9277
+ const [fetchState, setFetchState] = useState7("idle");
9278
+ const autoSaveTimers = useRef7(/* @__PURE__ */ new Map());
9279
+ const activeElRef = useRef7(null);
9280
+ const pointerHeldRef = useRef7(false);
9281
+ const selectedElRef = useRef7(null);
9282
+ const selectedHrefKeyRef = useRef7(null);
9283
+ const selectedFooterColAttrRef = useRef7(null);
9284
+ const originalContentRef = useRef7(null);
9285
+ const activeStateElRef = useRef7(null);
9286
+ const parentScrollRef = useRef7(null);
9287
+ const visibleViewportRef = useRef7(null);
9288
+ const [dialogPortalContainer, setDialogPortalContainer] = useState7(null);
9289
+ const attachVisibleViewport = useCallback5((node) => {
7623
9290
  visibleViewportRef.current = node;
7624
9291
  setDialogPortalContainer(node);
7625
9292
  if (node) applyVisibleViewport(node, parentScrollRef.current);
7626
9293
  }, []);
7627
- const toolbarElRef = useRef6(null);
7628
- const glowElRef = useRef6(null);
7629
- const hoveredImageRef = useRef6(null);
7630
- const hoveredImageHasTextOverlapRef = useRef6(false);
7631
- const dragOverElRef = useRef6(null);
7632
- const [mediaHover, setMediaHover] = useState6(null);
7633
- const [uploadingRects, setUploadingRects] = useState6({});
7634
- const hoveredGapRef = useRef6(null);
7635
- const imageUnhoverTimerRef = useRef6(null);
7636
- const imageShowTimerRef = useRef6(null);
7637
- const editStylesRef = useRef6(null);
7638
- const activateRef = useRef6(() => {
9294
+ const toolbarElRef = useRef7(null);
9295
+ const glowElRef = useRef7(null);
9296
+ const hoveredImageRef = useRef7(null);
9297
+ const hoveredImageHasTextOverlapRef = useRef7(false);
9298
+ const dragOverElRef = useRef7(null);
9299
+ const [mediaHover, setMediaHover] = useState7(null);
9300
+ const [uploadingRects, setUploadingRects] = useState7({});
9301
+ const hoveredGapRef = useRef7(null);
9302
+ const imageUnhoverTimerRef = useRef7(null);
9303
+ const imageShowTimerRef = useRef7(null);
9304
+ const editStylesRef = useRef7(null);
9305
+ const activateRef = useRef7(() => {
7639
9306
  });
7640
- const deactivateRef = useRef6(() => {
9307
+ const deactivateRef = useRef7(() => {
7641
9308
  });
7642
- const selectRef = useRef6(() => {
9309
+ const selectRef = useRef7(() => {
7643
9310
  });
7644
- const selectFrameRef = useRef6(() => {
9311
+ const selectFrameRef = useRef7(() => {
7645
9312
  });
7646
- const deselectRef = useRef6(() => {
9313
+ const deselectRef = useRef7(() => {
7647
9314
  });
7648
- const reselectNavigationItemRef = useRef6(() => {
9315
+ const reselectNavigationItemRef = useRef7(() => {
7649
9316
  });
7650
- const commitNavigationTextEditRef = useRef6(() => {
9317
+ const commitNavigationTextEditRef = useRef7(() => {
7651
9318
  });
7652
- const refreshActiveCommandsRef = useRef6(() => {
9319
+ const refreshActiveCommandsRef = useRef7(() => {
7653
9320
  });
7654
- const postToParentRef = useRef6(postToParent2);
9321
+ const postToParentRef = useRef7(postToParent2);
7655
9322
  postToParentRef.current = postToParent2;
7656
- const sectionsLoadedRef = useRef6(false);
7657
- const pendingScheduleConfigRequests = useRef6([]);
7658
- const [toolbarRect, setToolbarRect] = useState6(null);
7659
- const [toolbarVariant, setToolbarVariant] = useState6("none");
7660
- const toolbarVariantRef = useRef6("none");
9323
+ const sectionsLoadedRef = useRef7(false);
9324
+ const pendingScheduleConfigRequests = useRef7([]);
9325
+ const [toolbarRect, setToolbarRect] = useState7(null);
9326
+ const [toolbarVariant, setToolbarVariant] = useState7("none");
9327
+ const toolbarVariantRef = useRef7("none");
7661
9328
  toolbarVariantRef.current = toolbarVariant;
7662
- const [selectedIsCta, setSelectedIsCta] = useState6(false);
7663
- const [reorderHrefKey, setReorderHrefKey] = useState6(null);
7664
- const [reorderDragDisabled, setReorderDragDisabled] = useState6(false);
7665
- const [toggleState, setToggleState] = useState6(null);
7666
- const [maxBadge, setMaxBadge] = useState6(null);
7667
- const [activeCommands, setActiveCommands] = useState6(/* @__PURE__ */ new Set());
7668
- const [sectionGap, setSectionGap] = useState6(null);
7669
- const [toolbarShowEditLink, setToolbarShowEditLink] = useState6(false);
7670
- const hoveredNavContainerRef = useRef6(null);
7671
- const [hoveredNavContainerRect, setHoveredNavContainerRect] = useState6(null);
7672
- const hoveredItemElRef = useRef6(null);
7673
- const [hoveredItemRect, setHoveredItemRect] = useState6(null);
7674
- const siblingHintElRef = useRef6(null);
7675
- const [siblingHintRect, setSiblingHintRect] = useState6(null);
7676
- const [isItemDragging, setIsItemDragging] = useState6(false);
7677
- const [linkPopover, setLinkPopover] = useState6(null);
7678
- const linkPopoverSessionRef = useRef6(null);
7679
- const addNavAfterAnchorRef = useRef6(null);
7680
- const editContentRef = useRef6({});
7681
- const [sitePages, setSitePages] = useState6([]);
7682
- const [sectionsByPath, setSectionsByPath] = useState6({});
7683
- const sectionsPrefetchGenRef = useRef6(0);
7684
- const setLinkPopoverRef = useRef6(setLinkPopover);
7685
- const linkPopoverPanelRef = useRef6(null);
7686
- const linkPopoverOpenRef = useRef6(false);
7687
- const linkPopoverGraceUntilRef = useRef6(0);
9329
+ const [selectedIsCta, setSelectedIsCta] = useState7(false);
9330
+ const [reorderHrefKey, setReorderHrefKey] = useState7(null);
9331
+ const [reorderDragDisabled, setReorderDragDisabled] = useState7(false);
9332
+ const [toggleState, setToggleState] = useState7(null);
9333
+ const [maxBadge, setMaxBadge] = useState7(null);
9334
+ const [activeCommands, setActiveCommands] = useState7(/* @__PURE__ */ new Set());
9335
+ const [sectionGap, setSectionGap] = useState7(null);
9336
+ const [toolbarShowEditLink, setToolbarShowEditLink] = useState7(false);
9337
+ const hoveredNavContainerRef = useRef7(null);
9338
+ const [hoveredNavContainerRect, setHoveredNavContainerRect] = useState7(null);
9339
+ const hoveredItemElRef = useRef7(null);
9340
+ const [hoveredItemRect, setHoveredItemRect] = useState7(null);
9341
+ const siblingHintElRef = useRef7(null);
9342
+ const [siblingHintRect, setSiblingHintRect] = useState7(null);
9343
+ const [siblingHintRects, setSiblingHintRects] = useState7([]);
9344
+ const [isItemDragging, setIsItemDragging] = useState7(false);
9345
+ const [isFooterFrameSelection, setIsFooterFrameSelection] = useState7(false);
9346
+ const footerDragRef = useRef7(null);
9347
+ const [footerDropSlots, setFooterDropSlots] = useState7([]);
9348
+ const [activeFooterDropIndex, setActiveFooterDropIndex] = useState7(null);
9349
+ const [draggedItemRect, setDraggedItemRect] = useState7(null);
9350
+ const footerPointerDragRef = useRef7(null);
9351
+ const suppressNextClickRef = useRef7(false);
9352
+ const suppressClickUntilRef = useRef7(0);
9353
+ const [linkPopover, setLinkPopover] = useState7(null);
9354
+ const linkPopoverSessionRef = useRef7(null);
9355
+ const addNavAfterAnchorRef = useRef7(null);
9356
+ const editContentRef = useRef7({});
9357
+ const [sitePages, setSitePages] = useState7([]);
9358
+ const [sectionsByPath, setSectionsByPath] = useState7({});
9359
+ const sectionsPrefetchGenRef = useRef7(0);
9360
+ const setLinkPopoverRef = useRef7(setLinkPopover);
9361
+ const linkPopoverPanelRef = useRef7(null);
9362
+ const linkPopoverOpenRef = useRef7(false);
9363
+ const linkPopoverGraceUntilRef = useRef7(0);
7688
9364
  setLinkPopoverRef.current = setLinkPopover;
7689
9365
  linkPopoverSessionRef.current = linkPopover;
9366
+ const {
9367
+ navDragRef,
9368
+ navDropSlots,
9369
+ activeNavDropIndex,
9370
+ startNavLinkDrag,
9371
+ commitNavDrag,
9372
+ onNavDragOver,
9373
+ armNavPressFromChrome,
9374
+ refreshNavDragVisualsRef
9375
+ } = useNavItemDrag({
9376
+ isEditMode,
9377
+ editContentRef,
9378
+ selectedElRef,
9379
+ activeElRef,
9380
+ footerDragRef,
9381
+ suppressNextClickRef,
9382
+ suppressClickUntilRef,
9383
+ siblingHintElRef,
9384
+ postToParentRef,
9385
+ deselectRef,
9386
+ selectRef,
9387
+ deactivateRef,
9388
+ setLinkPopover: () => setLinkPopover(null),
9389
+ linkPopoverOpenRef,
9390
+ setIsItemDragging,
9391
+ setDraggedItemRect,
9392
+ setSiblingHintRect,
9393
+ setSiblingHintRects,
9394
+ setToolbarRect,
9395
+ getNavigationItemAnchor,
9396
+ isDragHandleDisabled,
9397
+ isNavbarButton: isNavbarButton2
9398
+ });
7690
9399
  const bumpLinkPopoverGrace = () => {
7691
9400
  linkPopoverGraceUntilRef.current = Date.now() + 350;
7692
9401
  };
7693
- const runSectionsPrefetch = useCallback3((pages) => {
9402
+ const runSectionsPrefetch = useCallback5((pages) => {
7694
9403
  if (!isEditMode || shouldUseDevFixtures() || pages.length === 0) return;
7695
9404
  const gen = ++sectionsPrefetchGenRef.current;
7696
9405
  const paths = pages.map((p) => p.path);
@@ -7709,10 +9418,24 @@ function OhhwellsBridge() {
7709
9418
  );
7710
9419
  });
7711
9420
  }, [isEditMode, pathname]);
7712
- const runSectionsPrefetchRef = useRef6(runSectionsPrefetch);
9421
+ const runSectionsPrefetchRef = useRef7(runSectionsPrefetch);
7713
9422
  runSectionsPrefetchRef.current = runSectionsPrefetch;
7714
- useEffect7(() => {
7715
- if (!linkPopover) return;
9423
+ useEffect8(() => {
9424
+ if (!linkPopover) {
9425
+ document.documentElement.removeAttribute("data-ohw-link-popover-open");
9426
+ return;
9427
+ }
9428
+ document.documentElement.setAttribute("data-ohw-link-popover-open", "");
9429
+ hoveredItemElRef.current = null;
9430
+ setHoveredItemRect(null);
9431
+ hoveredNavContainerRef.current = null;
9432
+ setHoveredNavContainerRect(null);
9433
+ siblingHintElRef.current = null;
9434
+ setSiblingHintRect(null);
9435
+ setSiblingHintRects([]);
9436
+ if (selectedElRef.current || toolbarVariantRef.current !== "none") {
9437
+ deselectRef.current();
9438
+ }
7716
9439
  if (hoveredImageRef.current) {
7717
9440
  hoveredImageRef.current = null;
7718
9441
  hoveredImageHasTextOverlapRef.current = false;
@@ -7739,6 +9462,7 @@ function OhhwellsBridge() {
7739
9462
  document.addEventListener("touchmove", preventBackgroundScroll, scrollOpts);
7740
9463
  postToParent2({ type: "ow:image-unhover" });
7741
9464
  return () => {
9465
+ document.documentElement.removeAttribute("data-ohw-link-popover-open");
7742
9466
  postToParent2({ type: "ow:link-modal-lock", locked: false });
7743
9467
  html.style.overflow = prevHtmlOverflow;
7744
9468
  body.style.overflow = prevBodyOverflow;
@@ -7746,7 +9470,7 @@ function OhhwellsBridge() {
7746
9470
  document.removeEventListener("touchmove", preventBackgroundScroll, scrollOpts);
7747
9471
  };
7748
9472
  }, [linkPopover, postToParent2]);
7749
- useEffect7(() => {
9473
+ useEffect8(() => {
7750
9474
  if (!isEditMode) return;
7751
9475
  const useFixtures = shouldUseDevFixtures();
7752
9476
  if (useFixtures) {
@@ -7770,14 +9494,14 @@ function OhhwellsBridge() {
7770
9494
  if (!useFixtures) postToParent2({ type: "ow:request-site-pages" });
7771
9495
  return () => window.removeEventListener("message", onSitePages);
7772
9496
  }, [isEditMode, postToParent2]);
7773
- useEffect7(() => {
9497
+ useEffect8(() => {
7774
9498
  if (!isEditMode || shouldUseDevFixtures()) return;
7775
9499
  void loadAllSectionsManifest().then((manifest) => {
7776
9500
  if (Object.keys(manifest).length === 0) return;
7777
9501
  setSectionsByPath((prev) => ({ ...manifest, ...prev }));
7778
9502
  });
7779
9503
  }, [isEditMode]);
7780
- useEffect7(() => {
9504
+ useEffect8(() => {
7781
9505
  const update = () => {
7782
9506
  const el = activeElRef.current ?? selectedElRef.current;
7783
9507
  if (el) setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
@@ -7801,10 +9525,10 @@ function OhhwellsBridge() {
7801
9525
  vvp.removeEventListener("resize", update);
7802
9526
  };
7803
9527
  }, []);
7804
- const refreshStateRules = useCallback3(() => {
9528
+ const refreshStateRules = useCallback5(() => {
7805
9529
  editStylesRef.current?.forceHover && (editStylesRef.current.forceHover.textContent = collectStateRules());
7806
9530
  }, []);
7807
- const processConfigRequest = useCallback3((insertAfterVal) => {
9531
+ const processConfigRequest = useCallback5((insertAfterVal) => {
7808
9532
  const tracker = getSectionsTracker();
7809
9533
  let entries = [];
7810
9534
  try {
@@ -7827,7 +9551,7 @@ function OhhwellsBridge() {
7827
9551
  }
7828
9552
  window.postMessage({ type: "ow:schedule-config", insertAfter: insertAfterVal, scheduleId: null }, "*");
7829
9553
  }, [isEditMode]);
7830
- const deactivate = useCallback3(() => {
9554
+ const deactivate = useCallback5(() => {
7831
9555
  const el = activeElRef.current;
7832
9556
  if (!el) return;
7833
9557
  const key = el.dataset.ohwKey;
@@ -7859,19 +9583,23 @@ function OhhwellsBridge() {
7859
9583
  setToolbarShowEditLink(false);
7860
9584
  postToParent2({ type: "ow:exit-edit" });
7861
9585
  }, [postToParent2]);
7862
- const clearSelectedAttr = useCallback3(() => {
9586
+ const clearSelectedAttr = useCallback5(() => {
7863
9587
  document.querySelectorAll("[data-ohw-selected]").forEach((el) => {
7864
9588
  el.removeAttribute("data-ohw-selected");
7865
9589
  });
7866
9590
  }, []);
7867
- const deselect = useCallback3(() => {
9591
+ const deselect = useCallback5(() => {
7868
9592
  clearSelectedAttr();
7869
9593
  selectedElRef.current = null;
9594
+ selectedHrefKeyRef.current = null;
9595
+ selectedFooterColAttrRef.current = null;
7870
9596
  setSelectedIsCta(false);
7871
9597
  setReorderHrefKey(null);
7872
9598
  setReorderDragDisabled(false);
9599
+ setIsFooterFrameSelection(false);
7873
9600
  siblingHintElRef.current = null;
7874
9601
  setSiblingHintRect(null);
9602
+ setSiblingHintRects([]);
7875
9603
  setIsItemDragging(false);
7876
9604
  hoveredNavContainerRef.current = null;
7877
9605
  setHoveredNavContainerRect(null);
@@ -7880,12 +9608,57 @@ function OhhwellsBridge() {
7880
9608
  setToolbarVariant("none");
7881
9609
  }
7882
9610
  }, [clearSelectedAttr]);
7883
- const markSelected = useCallback3((el) => {
9611
+ const markSelected = useCallback5((el) => {
7884
9612
  clearSelectedAttr();
7885
9613
  el.setAttribute("data-ohw-selected", "");
7886
9614
  }, [clearSelectedAttr]);
7887
- const reselectNavigationItem = useCallback3((navAnchor) => {
9615
+ const resolveHrefKeyElement = useCallback5((hrefKey) => {
9616
+ if (isFooterHrefKey(hrefKey)) {
9617
+ return document.querySelector(
9618
+ `footer [data-ohw-href-key="${CSS.escape(hrefKey)}"]`
9619
+ );
9620
+ }
9621
+ if (isNavbarHrefKey(hrefKey)) {
9622
+ const desktop = document.querySelector("[data-ohw-nav-container]");
9623
+ const drawer = document.querySelector("[data-ohw-nav-drawer]");
9624
+ 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)}"]`);
9625
+ }
9626
+ return document.querySelector(
9627
+ `[data-ohw-href-key="${CSS.escape(hrefKey)}"]`
9628
+ );
9629
+ }, []);
9630
+ const resyncSelectedNavigationItem = useCallback5(() => {
9631
+ const hrefKey = selectedHrefKeyRef.current;
9632
+ if (hrefKey) {
9633
+ const link = resolveHrefKeyElement(hrefKey);
9634
+ if (!link || !isNavigationItem(link)) return;
9635
+ selectedElRef.current = link;
9636
+ const { key, disabled } = getNavigationItemReorderState(link);
9637
+ setReorderHrefKey(key);
9638
+ setReorderDragDisabled(disabled);
9639
+ setIsFooterFrameSelection(false);
9640
+ setToolbarVariant("link-action");
9641
+ setToolbarRect(link.getBoundingClientRect());
9642
+ setSiblingHintRects(collectNavigationItemSiblingHintRects(link));
9643
+ return;
9644
+ }
9645
+ const colAttr = selectedFooterColAttrRef.current;
9646
+ if (colAttr != null) {
9647
+ const column = document.querySelector(`[data-ohw-footer-col="${CSS.escape(colAttr)}"]`) ?? listFooterColumns()[Number(colAttr)] ?? null;
9648
+ if (!column || !isNavigationContainer(column)) return;
9649
+ selectedElRef.current = column;
9650
+ setIsFooterFrameSelection(true);
9651
+ setToolbarVariant("select-frame");
9652
+ setToolbarRect(column.getBoundingClientRect());
9653
+ setSiblingHintRects(
9654
+ listFooterColumns().filter((c) => c !== column).map((c) => c.getBoundingClientRect())
9655
+ );
9656
+ }
9657
+ }, [resolveHrefKeyElement]);
9658
+ const reselectNavigationItem = useCallback5((navAnchor) => {
7888
9659
  selectedElRef.current = navAnchor;
9660
+ selectedHrefKeyRef.current = navAnchor.getAttribute("data-ohw-href-key");
9661
+ selectedFooterColAttrRef.current = null;
7889
9662
  markSelected(navAnchor);
7890
9663
  setSelectedIsCta(isNavbarButton2(navAnchor));
7891
9664
  const { key, disabled } = getNavigationItemReorderState(navAnchor);
@@ -7893,10 +9666,11 @@ function OhhwellsBridge() {
7893
9666
  setReorderDragDisabled(disabled);
7894
9667
  setToolbarVariant("link-action");
7895
9668
  setToolbarRect(navAnchor.getBoundingClientRect());
9669
+ setSiblingHintRects(collectNavigationItemSiblingHintRects(navAnchor));
7896
9670
  setToolbarShowEditLink(false);
7897
9671
  setActiveCommands(/* @__PURE__ */ new Set());
7898
9672
  }, [markSelected]);
7899
- const commitNavigationTextEdit = useCallback3((navAnchor) => {
9673
+ const commitNavigationTextEdit = useCallback5((navAnchor) => {
7900
9674
  const el = activeElRef.current;
7901
9675
  if (!el) return;
7902
9676
  const key = el.dataset.ohwKey;
@@ -7923,7 +9697,7 @@ function OhhwellsBridge() {
7923
9697
  postToParent2({ type: "ow:exit-edit" });
7924
9698
  reselectNavigationItem(navAnchor);
7925
9699
  }, [postToParent2, reselectNavigationItem]);
7926
- const handleAddTopLevelNavItem = useCallback3(() => {
9700
+ const handleAddTopLevelNavItem = useCallback5(() => {
7927
9701
  const items = listNavbarItems();
7928
9702
  addNavAfterAnchorRef.current = items[items.length - 1] ?? null;
7929
9703
  deselectRef.current();
@@ -7935,20 +9709,319 @@ function OhhwellsBridge() {
7935
9709
  intent: "add-nav"
7936
9710
  });
7937
9711
  }, []);
7938
- const handleItemDragStart = useCallback3(() => {
7939
- siblingHintElRef.current = null;
7940
- setSiblingHintRect(null);
7941
- setIsItemDragging(true);
7942
- }, []);
7943
- const handleItemDragEnd = useCallback3(() => {
9712
+ const handleAddFooterColumn = useCallback5(() => {
9713
+ deselectRef.current();
9714
+ deactivateRef.current();
9715
+ const result = addFooterColumnWithPersist({ postToParent: postToParent2 });
9716
+ editContentRef.current = {
9717
+ ...editContentRef.current,
9718
+ ...buildFooterColumnEditContentPatch(result)
9719
+ };
9720
+ requestAnimationFrame(() => {
9721
+ selectRef.current(result.firstLink);
9722
+ });
9723
+ }, [postToParent2]);
9724
+ const clearFooterDragVisuals = useCallback5(() => {
9725
+ footerDragRef.current = null;
9726
+ setSiblingHintRects([]);
9727
+ setFooterDropSlots([]);
9728
+ setActiveFooterDropIndex(null);
9729
+ setDraggedItemRect(null);
7944
9730
  setIsItemDragging(false);
9731
+ unlockFooterDragInteraction();
9732
+ }, []);
9733
+ const refreshFooterDragVisuals = useCallback5((session, activeSlot, clientX, clientY) => {
9734
+ const dragged = session.draggedEl;
9735
+ setDraggedItemRect(dragged.getBoundingClientRect());
9736
+ if (typeof clientX === "number" && typeof clientY === "number") {
9737
+ session.lastClientX = clientX;
9738
+ session.lastClientY = clientY;
9739
+ }
9740
+ session.activeSlot = activeSlot;
9741
+ setSiblingHintRects([]);
9742
+ if (session.kind === "link") {
9743
+ const columns = listFooterColumns();
9744
+ const slots2 = [];
9745
+ columns.forEach((col, i) => {
9746
+ slots2.push(...buildLinkDropSlots(col, i));
9747
+ });
9748
+ setFooterDropSlots(slots2);
9749
+ const activeIdx2 = activeSlot ? slots2.findIndex((s) => s.columnIndex === activeSlot.columnIndex && s.insertIndex === activeSlot.insertIndex) : -1;
9750
+ setActiveFooterDropIndex(activeIdx2 >= 0 ? activeIdx2 : null);
9751
+ return;
9752
+ }
9753
+ const slots = buildColumnDropSlots();
9754
+ setFooterDropSlots(slots);
9755
+ const activeIdx = activeSlot ? slots.findIndex((s) => s.insertIndex === activeSlot.insertIndex) : -1;
9756
+ setActiveFooterDropIndex(activeIdx >= 0 ? activeIdx : null);
9757
+ }, []);
9758
+ const refreshFooterDragVisualsRef = useRef7(refreshFooterDragVisuals);
9759
+ refreshFooterDragVisualsRef.current = refreshFooterDragVisuals;
9760
+ const commitFooterDragRef = useRef7(() => {
9761
+ });
9762
+ const beginFooterDragRef = useRef7(() => {
9763
+ });
9764
+ const beginFooterDrag = useCallback5(
9765
+ (session) => {
9766
+ const rect = session.draggedEl.getBoundingClientRect();
9767
+ session.lastClientX = session.lastClientX || rect.left + rect.width / 2;
9768
+ session.lastClientY = session.lastClientY || rect.top + rect.height / 2;
9769
+ session.activeSlot = session.activeSlot ?? null;
9770
+ footerDragRef.current = session;
9771
+ setIsItemDragging(true);
9772
+ lockFooterDuringDrag();
9773
+ siblingHintElRef.current = null;
9774
+ setSiblingHintRect(null);
9775
+ if (session.wasSelected && selectedElRef.current === session.draggedEl) {
9776
+ setToolbarRect(rect);
9777
+ }
9778
+ const initialSlot = session.kind === "link" && session.hrefKey ? hitTestLinkDropSlot(session.lastClientX, session.lastClientY, session.hrefKey) : hitTestColumnDropSlot(session.lastClientX, session.lastClientY);
9779
+ refreshFooterDragVisuals(session, initialSlot, session.lastClientX, session.lastClientY);
9780
+ },
9781
+ [refreshFooterDragVisuals]
9782
+ );
9783
+ beginFooterDragRef.current = beginFooterDrag;
9784
+ const commitFooterDrag = useCallback5(
9785
+ (clientX, clientY) => {
9786
+ const session = footerDragRef.current;
9787
+ if (!session) {
9788
+ clearFooterDragVisuals();
9789
+ return;
9790
+ }
9791
+ const x = typeof clientX === "number" && (clientX !== 0 || clientY !== 0) ? clientX : session.lastClientX;
9792
+ const y = typeof clientY === "number" && (clientX !== 0 || clientY !== 0) ? clientY : session.lastClientY;
9793
+ let nextOrder = null;
9794
+ const slot = session.activeSlot ?? (session.kind === "link" && session.hrefKey ? hitTestLinkDropSlot(x, y, session.hrefKey) : session.kind === "column" ? hitTestColumnDropSlot(x, y) : null);
9795
+ if (session.kind === "link" && session.hrefKey && slot) {
9796
+ nextOrder = planFooterLinkMove(session.hrefKey, slot.columnIndex, slot.insertIndex);
9797
+ } else if (session.kind === "column" && slot) {
9798
+ nextOrder = planFooterColumnMove(session.sourceColumnIndex, slot.insertIndex);
9799
+ }
9800
+ const wasSelected = session.wasSelected;
9801
+ const draggedEl = session.draggedEl;
9802
+ const hrefKey = session.hrefKey;
9803
+ let movedColumnIndex = null;
9804
+ if (session.kind === "column" && slot && nextOrder) {
9805
+ let adjusted = slot.insertIndex;
9806
+ if (session.sourceColumnIndex < slot.insertIndex) adjusted -= 1;
9807
+ movedColumnIndex = Math.max(0, Math.min(adjusted, nextOrder.length - 1));
9808
+ }
9809
+ const applySelectionAfterDrop = () => {
9810
+ if (!wasSelected) {
9811
+ deselectRef.current();
9812
+ return;
9813
+ }
9814
+ if (hrefKey) {
9815
+ selectedHrefKeyRef.current = hrefKey;
9816
+ selectedFooterColAttrRef.current = null;
9817
+ const link = resolveHrefKeyElement(hrefKey);
9818
+ if (link && isNavigationItem(link)) {
9819
+ selectRef.current(link);
9820
+ return;
9821
+ }
9822
+ }
9823
+ if (movedColumnIndex !== null && nextOrder) {
9824
+ const colKeys = nextOrder[movedColumnIndex] ?? [];
9825
+ let column = null;
9826
+ for (const key of colKeys) {
9827
+ const link = document.querySelector(
9828
+ `footer [data-ohw-href-key="${CSS.escape(key)}"]`
9829
+ );
9830
+ if (link) {
9831
+ column = findFooterColumnForLink(link);
9832
+ if (column) break;
9833
+ }
9834
+ }
9835
+ if (!column) column = listFooterColumns()[movedColumnIndex] ?? null;
9836
+ if (column && isNavigationContainer(column)) {
9837
+ selectFrameRef.current(column);
9838
+ return;
9839
+ }
9840
+ }
9841
+ if (document.body.contains(draggedEl)) {
9842
+ if (isNavigationItem(draggedEl)) {
9843
+ selectRef.current(draggedEl);
9844
+ return;
9845
+ }
9846
+ if (isNavigationContainer(draggedEl)) {
9847
+ selectFrameRef.current(draggedEl);
9848
+ return;
9849
+ }
9850
+ }
9851
+ deselectRef.current();
9852
+ };
9853
+ if (nextOrder) {
9854
+ const orderJson = JSON.stringify(nextOrder);
9855
+ editContentRef.current = {
9856
+ ...editContentRef.current,
9857
+ [FOOTER_ORDER_KEY]: orderJson
9858
+ };
9859
+ applyFooterOrder(nextOrder);
9860
+ document.querySelectorAll("footer [data-ohw-href-key]").forEach(disableNativeHrefDrag);
9861
+ postToParentRef.current({
9862
+ type: "ow:change",
9863
+ nodes: [{ key: FOOTER_ORDER_KEY, text: orderJson }]
9864
+ });
9865
+ applySelectionAfterDrop();
9866
+ clearFooterDragVisuals();
9867
+ requestAnimationFrame(() => {
9868
+ if (editContentRef.current[FOOTER_ORDER_KEY] === orderJson) {
9869
+ applyFooterOrder(nextOrder);
9870
+ document.querySelectorAll("footer [data-ohw-href-key]").forEach(disableNativeHrefDrag);
9871
+ }
9872
+ applySelectionAfterDrop();
9873
+ requestAnimationFrame(() => {
9874
+ if (editContentRef.current[FOOTER_ORDER_KEY] === orderJson) {
9875
+ applyFooterOrder(nextOrder);
9876
+ }
9877
+ resyncSelectedNavigationItem();
9878
+ });
9879
+ });
9880
+ return;
9881
+ }
9882
+ applySelectionAfterDrop();
9883
+ clearFooterDragVisuals();
9884
+ },
9885
+ [clearFooterDragVisuals, resolveHrefKeyElement, resyncSelectedNavigationItem]
9886
+ );
9887
+ commitFooterDragRef.current = commitFooterDrag;
9888
+ const startFooterLinkDrag = useCallback5(
9889
+ (anchor, clientX, clientY, wasSelected) => {
9890
+ const hrefKey = anchor.getAttribute("data-ohw-href-key");
9891
+ if (!hrefKey || !isFooterHrefKey(hrefKey)) return false;
9892
+ const column = findFooterColumnForLink(anchor);
9893
+ const columns = listFooterColumns();
9894
+ beginFooterDrag({
9895
+ kind: "link",
9896
+ hrefKey,
9897
+ columnEl: column,
9898
+ sourceColumnIndex: column ? columns.indexOf(column) : 0,
9899
+ wasSelected,
9900
+ draggedEl: anchor,
9901
+ lastClientX: clientX,
9902
+ lastClientY: clientY,
9903
+ activeSlot: null
9904
+ });
9905
+ return true;
9906
+ },
9907
+ [beginFooterDrag]
9908
+ );
9909
+ const startFooterColumnDrag = useCallback5(
9910
+ (columnEl, clientX, clientY, wasSelected) => {
9911
+ const columns = listFooterColumns();
9912
+ const idx = columns.indexOf(columnEl);
9913
+ if (idx < 0) return false;
9914
+ beginFooterDrag({
9915
+ kind: "column",
9916
+ hrefKey: null,
9917
+ columnEl,
9918
+ sourceColumnIndex: idx,
9919
+ wasSelected,
9920
+ draggedEl: columnEl,
9921
+ lastClientX: clientX,
9922
+ lastClientY: clientY,
9923
+ activeSlot: null
9924
+ });
9925
+ return true;
9926
+ },
9927
+ [beginFooterDrag]
9928
+ );
9929
+ const handleItemDragStart = useCallback5(
9930
+ (e) => {
9931
+ const selected = selectedElRef.current;
9932
+ if (!selected) {
9933
+ setIsItemDragging(true);
9934
+ return;
9935
+ }
9936
+ const clientX = e?.clientX ?? selected.getBoundingClientRect().left + 8;
9937
+ const clientY = e?.clientY ?? selected.getBoundingClientRect().top + 8;
9938
+ if (startFooterLinkDrag(selected, clientX, clientY, true)) return;
9939
+ if (selected.hasAttribute("data-ohw-footer-col") || isInferredFooterGroup(selected) && selected.closest("footer")) {
9940
+ if (startFooterColumnDrag(selected, clientX, clientY, true)) return;
9941
+ }
9942
+ if (startNavLinkDrag(selected, clientX, clientY, true)) return;
9943
+ siblingHintElRef.current = null;
9944
+ setSiblingHintRect(null);
9945
+ setIsItemDragging(true);
9946
+ },
9947
+ [startFooterColumnDrag, startFooterLinkDrag, startNavLinkDrag]
9948
+ );
9949
+ const handleItemDragEnd = useCallback5(
9950
+ (e) => {
9951
+ if (footerDragRef.current) {
9952
+ const x = e?.clientX;
9953
+ const y = e?.clientY;
9954
+ if (typeof x === "number" && typeof y === "number" && (x !== 0 || y !== 0)) {
9955
+ commitFooterDrag(x, y);
9956
+ } else {
9957
+ commitFooterDrag();
9958
+ }
9959
+ return;
9960
+ }
9961
+ if (navDragRef.current) {
9962
+ const x = e?.clientX;
9963
+ const y = e?.clientY;
9964
+ if (typeof x === "number" && typeof y === "number" && (x !== 0 || y !== 0)) {
9965
+ commitNavDrag(x, y);
9966
+ } else {
9967
+ commitNavDrag();
9968
+ }
9969
+ return;
9970
+ }
9971
+ setIsItemDragging(false);
9972
+ },
9973
+ [commitFooterDrag, commitNavDrag, navDragRef]
9974
+ );
9975
+ const handleItemChromePointerDown = useCallback5((e) => {
9976
+ if (e.button !== 0) return;
9977
+ const selected = selectedElRef.current;
9978
+ if (!selected) return;
9979
+ const hrefKey = selected.getAttribute("data-ohw-href-key");
9980
+ if (hrefKey && isFooterHrefKey(hrefKey)) {
9981
+ footerPointerDragRef.current = {
9982
+ el: selected,
9983
+ kind: "link",
9984
+ startX: e.clientX,
9985
+ startY: e.clientY,
9986
+ pointerId: e.pointerId,
9987
+ wasSelected: true,
9988
+ started: false
9989
+ };
9990
+ return;
9991
+ }
9992
+ if (selected.hasAttribute("data-ohw-footer-col") || isInferredFooterGroup(selected) && selected.closest("footer")) {
9993
+ footerPointerDragRef.current = {
9994
+ el: selected,
9995
+ kind: "column",
9996
+ startX: e.clientX,
9997
+ startY: e.clientY,
9998
+ pointerId: e.pointerId,
9999
+ wasSelected: true,
10000
+ started: false
10001
+ };
10002
+ return;
10003
+ }
10004
+ if (armNavPressFromChrome(selected, e.clientX, e.clientY, e.pointerId)) return;
10005
+ }, [armNavPressFromChrome]);
10006
+ const handleItemChromeClick = useCallback5((clientX, clientY) => {
10007
+ if (suppressNextClickRef.current || Date.now() < suppressClickUntilRef.current) {
10008
+ suppressNextClickRef.current = false;
10009
+ return;
10010
+ }
10011
+ const selected = selectedElRef.current;
10012
+ if (!selected || !isNavigationItem(selected)) return;
10013
+ const editable = selected.querySelector('[data-ohw-editable="text"]') ?? selected.querySelector("[data-ohw-editable]");
10014
+ if (!editable) return;
10015
+ activateRef.current(editable, { caretX: clientX, caretY: clientY });
7945
10016
  }, []);
7946
10017
  reselectNavigationItemRef.current = reselectNavigationItem;
7947
10018
  commitNavigationTextEditRef.current = commitNavigationTextEdit;
7948
- const select = useCallback3((anchor) => {
10019
+ const select = useCallback5((anchor) => {
7949
10020
  if (!isNavigationItem(anchor)) return;
7950
10021
  if (activeElRef.current) deactivate();
7951
10022
  selectedElRef.current = anchor;
10023
+ selectedHrefKeyRef.current = anchor.getAttribute("data-ohw-href-key");
10024
+ selectedFooterColAttrRef.current = null;
7952
10025
  markSelected(anchor);
7953
10026
  setSelectedIsCta(isNavbarButton2(anchor));
7954
10027
  clearHrefKeyHover(anchor);
@@ -7958,19 +10031,24 @@ function OhhwellsBridge() {
7958
10031
  hoveredItemElRef.current = null;
7959
10032
  siblingHintElRef.current = null;
7960
10033
  setSiblingHintRect(null);
10034
+ setSiblingHintRects(collectNavigationItemSiblingHintRects(anchor));
7961
10035
  setIsItemDragging(false);
7962
10036
  const { key, disabled } = getNavigationItemReorderState(anchor);
7963
10037
  setReorderHrefKey(key);
7964
10038
  setReorderDragDisabled(disabled);
10039
+ setIsFooterFrameSelection(false);
7965
10040
  setToolbarVariant("link-action");
7966
10041
  setToolbarRect(anchor.getBoundingClientRect());
7967
10042
  setToolbarShowEditLink(false);
7968
10043
  setActiveCommands(/* @__PURE__ */ new Set());
7969
10044
  }, [deactivate, markSelected]);
7970
- const selectFrame = useCallback3((el) => {
10045
+ const selectFrame = useCallback5((el) => {
7971
10046
  if (!isNavigationContainer(el)) return;
7972
10047
  if (activeElRef.current) deactivate();
10048
+ const isFooterColumn = !isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || Boolean(el.closest("footer") && isInferredFooterGroup(el)));
7973
10049
  selectedElRef.current = el;
10050
+ selectedHrefKeyRef.current = null;
10051
+ selectedFooterColAttrRef.current = isFooterColumn ? el.getAttribute("data-ohw-footer-col") ?? String(listFooterColumns().indexOf(el)) : null;
7974
10052
  markSelected(el);
7975
10053
  setSelectedIsCta(false);
7976
10054
  clearHrefKeyHover(el);
@@ -7980,18 +10058,24 @@ function OhhwellsBridge() {
7980
10058
  hoveredItemElRef.current = null;
7981
10059
  siblingHintElRef.current = null;
7982
10060
  setSiblingHintRect(null);
10061
+ setSiblingHintRects(
10062
+ isFooterColumn ? listFooterColumns().filter((column) => column !== el).map((column) => column.getBoundingClientRect()) : []
10063
+ );
7983
10064
  setIsItemDragging(false);
7984
10065
  setReorderHrefKey(null);
7985
10066
  setReorderDragDisabled(false);
10067
+ setIsFooterFrameSelection(isFooterColumn);
7986
10068
  setToolbarVariant("select-frame");
7987
10069
  setToolbarRect(el.getBoundingClientRect());
7988
10070
  setToolbarShowEditLink(false);
7989
10071
  setActiveCommands(/* @__PURE__ */ new Set());
7990
10072
  }, [deactivate, markSelected]);
7991
- const activate = useCallback3((el, options) => {
10073
+ const activate = useCallback5((el, options) => {
7992
10074
  if (activeElRef.current === el) return;
7993
10075
  clearSelectedAttr();
7994
10076
  selectedElRef.current = null;
10077
+ selectedHrefKeyRef.current = null;
10078
+ selectedFooterColAttrRef.current = null;
7995
10079
  setSelectedIsCta(false);
7996
10080
  deactivate();
7997
10081
  if (hoveredImageRef.current) {
@@ -8001,7 +10085,10 @@ function OhhwellsBridge() {
8001
10085
  setToolbarVariant("rich-text");
8002
10086
  siblingHintElRef.current = null;
8003
10087
  setSiblingHintRect(null);
10088
+ setSiblingHintRects([]);
8004
10089
  setIsItemDragging(false);
10090
+ const preActivationSelection = window.getSelection();
10091
+ const preservedRange = preActivationSelection && preActivationSelection.rangeCount > 0 && !preActivationSelection.isCollapsed && el.contains(preActivationSelection.getRangeAt(0).commonAncestorContainer) ? preActivationSelection.getRangeAt(0).cloneRange() : null;
8005
10092
  el.setAttribute("contenteditable", "true");
8006
10093
  el.setAttribute("data-ohw-editing", "");
8007
10094
  el.removeAttribute("data-ohw-hovered");
@@ -8009,8 +10096,16 @@ function OhhwellsBridge() {
8009
10096
  activeElRef.current = el;
8010
10097
  originalContentRef.current = el.innerHTML;
8011
10098
  el.focus({ preventScroll: true });
8012
- if (options?.caretX !== void 0 && options?.caretY !== void 0) {
8013
- placeCaretAtPoint(el, options.caretX, options.caretY);
10099
+ if (preservedRange) {
10100
+ const selection = window.getSelection();
10101
+ selection?.removeAllRanges();
10102
+ selection?.addRange(preservedRange);
10103
+ } else if (options?.caretX !== void 0 && options?.caretY !== void 0) {
10104
+ const { caretX, caretY } = options;
10105
+ placeCaretAtPoint(el, caretX, caretY);
10106
+ requestAnimationFrame(() => {
10107
+ if (activeElRef.current === el) placeCaretAtPoint(el, caretX, caretY);
10108
+ });
8014
10109
  } else {
8015
10110
  const selection = window.getSelection();
8016
10111
  if (selection && selection.rangeCount === 0) {
@@ -8081,6 +10176,7 @@ function OhhwellsBridge() {
8081
10176
  applyLinkByKey(key, val);
8082
10177
  }
8083
10178
  reconcileNavbarItemsFromContent(content);
10179
+ reconcileFooterOrderFromContent(content);
8084
10180
  enforceLinkHrefs();
8085
10181
  initSectionsFromContent(content, true);
8086
10182
  sectionsLoadedRef.current = true;
@@ -8109,7 +10205,7 @@ function OhhwellsBridge() {
8109
10205
  cancelled = true;
8110
10206
  };
8111
10207
  }, [subdomain, isEditMode]);
8112
- useEffect7(() => {
10208
+ useEffect8(() => {
8113
10209
  if (!subdomain || isEditMode) return;
8114
10210
  let debounceTimer = null;
8115
10211
  let observer = null;
@@ -8141,6 +10237,7 @@ function OhhwellsBridge() {
8141
10237
  applyLinkByKey(key, val);
8142
10238
  }
8143
10239
  reconcileNavbarItemsFromContent(content);
10240
+ reconcileFooterOrderFromContent(content);
8144
10241
  } finally {
8145
10242
  observer?.observe(document.body, { childList: true, subtree: true });
8146
10243
  }
@@ -8164,10 +10261,10 @@ function OhhwellsBridge() {
8164
10261
  const visible = Boolean(subdomain) && fetchState !== "done";
8165
10262
  el.style.display = visible ? "flex" : "none";
8166
10263
  }, [subdomain, fetchState]);
8167
- useEffect7(() => {
10264
+ useEffect8(() => {
8168
10265
  postToParent2({ type: "ow:navigation", path: pathname });
8169
10266
  }, [pathname, postToParent2]);
8170
- useEffect7(() => {
10267
+ useEffect8(() => {
8171
10268
  if (!isEditMode) return;
8172
10269
  if (linkPopoverSessionRef.current?.intent === "add-nav") return;
8173
10270
  if (document.querySelector("[data-ohw-section-picker]")) return;
@@ -8175,7 +10272,7 @@ function OhhwellsBridge() {
8175
10272
  deselectRef.current();
8176
10273
  deactivateRef.current();
8177
10274
  }, [pathname, isEditMode]);
8178
- useEffect7(() => {
10275
+ useEffect8(() => {
8179
10276
  const contentForNav = () => {
8180
10277
  if (isEditMode) return editContentRef.current;
8181
10278
  if (!subdomain) return {};
@@ -8187,6 +10284,7 @@ function OhhwellsBridge() {
8187
10284
  const roots = observeRoots();
8188
10285
  if (roots.length === 0) {
8189
10286
  reconcileNavbarItemsFromContent(contentForNav());
10287
+ reconcileFooterOrderFromContent(contentForNav());
8190
10288
  if (isEditMode) syncNavigationDragCursorAttrs();
8191
10289
  return;
8192
10290
  }
@@ -8199,11 +10297,26 @@ function OhhwellsBridge() {
8199
10297
  }
8200
10298
  };
8201
10299
  const run = () => {
8202
- if (applying) return;
10300
+ if (footerDragRef.current || navDragRef.current || applying) return;
8203
10301
  applying = true;
8204
10302
  observer?.disconnect();
8205
10303
  try {
8206
- reconcileNavbarItemsFromContent(contentForNav());
10304
+ const content = contentForNav();
10305
+ reconcileNavbarItemsFromContent(content);
10306
+ reconcileFooterOrderFromContent(content);
10307
+ document.querySelectorAll("footer [data-ohw-href-key]").forEach((el) => {
10308
+ if (isFooterHrefKey(el.getAttribute("data-ohw-href-key"))) {
10309
+ disableNativeHrefDrag(el);
10310
+ }
10311
+ });
10312
+ document.querySelectorAll(
10313
+ "nav [data-ohw-href-key], [data-ohw-nav-container] [data-ohw-href-key], [data-ohw-nav-drawer] [data-ohw-href-key]"
10314
+ ).forEach((el) => {
10315
+ if (isNavbarHrefKey(el.getAttribute("data-ohw-href-key"))) {
10316
+ disableNativeHrefDrag(el);
10317
+ }
10318
+ });
10319
+ resyncSelectedNavigationItem();
8207
10320
  if (isEditMode) syncNavigationDragCursorAttrs();
8208
10321
  } finally {
8209
10322
  applying = false;
@@ -8223,8 +10336,8 @@ function OhhwellsBridge() {
8223
10336
  if (rafId != null) cancelAnimationFrame(rafId);
8224
10337
  observer?.disconnect();
8225
10338
  };
8226
- }, [isEditMode, pathname, subdomain, fetchState]);
8227
- useEffect7(() => {
10339
+ }, [isEditMode, pathname, subdomain, fetchState, resyncSelectedNavigationItem]);
10340
+ useEffect8(() => {
8228
10341
  if (!isEditMode) return;
8229
10342
  const measure = () => {
8230
10343
  const h = document.body.scrollHeight;
@@ -8248,7 +10361,7 @@ function OhhwellsBridge() {
8248
10361
  window.removeEventListener("resize", handleResize);
8249
10362
  };
8250
10363
  }, [pathname, isEditMode, postToParent2]);
8251
- useEffect7(() => {
10364
+ useEffect8(() => {
8252
10365
  if (!subdomainFromQuery || isEditMode) return;
8253
10366
  const handleClick = (e) => {
8254
10367
  const anchor = e.target.closest("a");
@@ -8264,7 +10377,7 @@ function OhhwellsBridge() {
8264
10377
  document.addEventListener("click", handleClick, true);
8265
10378
  return () => document.removeEventListener("click", handleClick, true);
8266
10379
  }, [subdomainFromQuery, isEditMode, router]);
8267
- useEffect7(() => {
10380
+ useEffect8(() => {
8268
10381
  if (!isEditMode) {
8269
10382
  editStylesRef.current?.base.remove();
8270
10383
  editStylesRef.current?.forceHover.remove();
@@ -8326,6 +10439,21 @@ function OhhwellsBridge() {
8326
10439
  outline: none !important;
8327
10440
  outline-offset: 0 !important;
8328
10441
  }
10442
+ /* Editor chrome owns selection \u2014 suppress native focus rings on nav/footer items. */
10443
+ nav [data-ohw-href-key]:focus,
10444
+ nav [data-ohw-href-key]:focus-visible,
10445
+ nav [data-ohw-href-key]:focus-within,
10446
+ footer [data-ohw-href-key]:focus,
10447
+ footer [data-ohw-href-key]:focus-visible,
10448
+ footer [data-ohw-href-key]:focus-within,
10449
+ nav [data-ohw-href-key] [data-ohw-editable]:focus,
10450
+ nav [data-ohw-href-key] [data-ohw-editable]:focus-visible,
10451
+ footer [data-ohw-href-key] [data-ohw-editable]:focus,
10452
+ footer [data-ohw-href-key] [data-ohw-editable]:focus-visible {
10453
+ outline: none !important;
10454
+ outline-offset: 0 !important;
10455
+ box-shadow: none !important;
10456
+ }
8329
10457
  /* Text edit wins over grab/default (must beat [data-ohw-can-drag] *). */
8330
10458
  [data-ohw-editing],
8331
10459
  [data-ohw-editing] *,
@@ -8367,12 +10495,39 @@ function OhhwellsBridge() {
8367
10495
  syncNavigationDragCursorAttrs();
8368
10496
  refreshStateRules();
8369
10497
  const handleClick = (e) => {
10498
+ if (suppressNextClickRef.current || Date.now() < suppressClickUntilRef.current) {
10499
+ suppressNextClickRef.current = false;
10500
+ e.preventDefault();
10501
+ e.stopPropagation();
10502
+ return;
10503
+ }
8370
10504
  const target = e.target;
8371
10505
  if (target.closest("[data-ohw-toolbar]")) return;
8372
10506
  if (target.closest("[data-ohw-state-toggle]")) return;
8373
10507
  if (target.closest("[data-ohw-max-badge]")) return;
8374
10508
  if (isInsideLinkEditor(target)) return;
8375
10509
  if (target.closest('[data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
10510
+ if (target.closest("[data-ohw-item-drag-surface]")) {
10511
+ e.preventDefault();
10512
+ e.stopPropagation();
10513
+ const selected = selectedElRef.current;
10514
+ if (selected && isNavigationItem(selected)) {
10515
+ const editable2 = selected.querySelector('[data-ohw-editable="text"]') ?? selected.querySelector("[data-ohw-editable]");
10516
+ if (editable2) {
10517
+ activateRef.current(editable2, {
10518
+ caretX: e.clientX,
10519
+ caretY: e.clientY
10520
+ });
10521
+ }
10522
+ } else if (selected?.hasAttribute("data-ohw-footer-col")) {
10523
+ const link = listFooterLinksInColumn(selected).find((el) => {
10524
+ const r2 = el.getBoundingClientRect();
10525
+ return e.clientX >= r2.left && e.clientX <= r2.right && e.clientY >= r2.top && e.clientY <= r2.bottom;
10526
+ });
10527
+ if (link) selectRef.current(link);
10528
+ }
10529
+ return;
10530
+ }
8376
10531
  const navFromChrome = resolveNavContainerSelectionTarget(target, e.clientX, e.clientY);
8377
10532
  if (navFromChrome) {
8378
10533
  e.preventDefault();
@@ -8401,7 +10556,9 @@ function OhhwellsBridge() {
8401
10556
  if (inActive || inActiveNav) {
8402
10557
  e.preventDefault();
8403
10558
  e.stopPropagation();
8404
- if (e.detail < 2) {
10559
+ const selection = window.getSelection();
10560
+ const hasRangeSelection = Boolean(selection && !selection.isCollapsed);
10561
+ if (e.detail < 2 && !hasRangeSelection) {
8405
10562
  placeCaretAtPoint(active, e.clientX, e.clientY);
8406
10563
  refreshActiveCommandsRef.current();
8407
10564
  }
@@ -8444,17 +10601,45 @@ function OhhwellsBridge() {
8444
10601
  }
8445
10602
  e.preventDefault();
8446
10603
  e.stopPropagation();
8447
- activateRef.current(editable);
10604
+ activateRef.current(editable, { caretX: e.clientX, caretY: e.clientY });
8448
10605
  return;
8449
10606
  }
8450
10607
  const hrefAnchor = getNavigationItemAnchor(target);
8451
10608
  if (hrefAnchor) {
8452
10609
  e.preventDefault();
8453
10610
  e.stopPropagation();
8454
- if (selectedElRef.current === hrefAnchor) return;
10611
+ if (selectedElRef.current === hrefAnchor) {
10612
+ const textEditable = hrefAnchor.querySelector('[data-ohw-editable="text"]') ?? hrefAnchor.querySelector("[data-ohw-editable]");
10613
+ if (textEditable) {
10614
+ activateRef.current(textEditable, {
10615
+ caretX: e.clientX,
10616
+ caretY: e.clientY
10617
+ });
10618
+ }
10619
+ return;
10620
+ }
8455
10621
  selectRef.current(hrefAnchor);
8456
10622
  return;
8457
10623
  }
10624
+ const footerColClick = target.closest("[data-ohw-footer-col]");
10625
+ if (footerColClick) {
10626
+ e.preventDefault();
10627
+ e.stopPropagation();
10628
+ selectFrameRef.current(footerColClick);
10629
+ return;
10630
+ }
10631
+ const footerLinksToSelect = resolveFooterLinksContainerSelectionTarget(
10632
+ target,
10633
+ e.clientX,
10634
+ e.clientY,
10635
+ isPointOverNavItem
10636
+ );
10637
+ if (footerLinksToSelect) {
10638
+ e.preventDefault();
10639
+ e.stopPropagation();
10640
+ selectFrameRef.current(footerLinksToSelect);
10641
+ return;
10642
+ }
8458
10643
  const navContainerToSelect = resolveNavContainerSelectionTarget(target, e.clientX, e.clientY);
8459
10644
  if (navContainerToSelect) {
8460
10645
  e.preventDefault();
@@ -8513,8 +10698,8 @@ function OhhwellsBridge() {
8513
10698
  return;
8514
10699
  }
8515
10700
  const navLabel = getNavigationLabelEditable(target);
8516
- if (!navLabel) return;
8517
- const { editable } = navLabel;
10701
+ const editable = navLabel?.editable ?? target.closest('[data-ohw-editable="text"], [data-ohw-editable="plain"]');
10702
+ if (!editable || isMediaEditable(editable) || editable.dataset.ohwEditable === "link") return;
8518
10703
  e.preventDefault();
8519
10704
  e.stopPropagation();
8520
10705
  if (activeElRef.current !== editable) {
@@ -8528,16 +10713,45 @@ function OhhwellsBridge() {
8528
10713
  const handleMouseOver = (e) => {
8529
10714
  if (document.documentElement.hasAttribute("data-ohw-section-picking")) return;
8530
10715
  const target = e.target;
8531
- if (toolbarVariantRef.current !== "select-frame") {
8532
- const frameContainer = target.closest("[data-ohw-nav-container]") ?? target.closest("[data-ohw-footer-column]");
8533
- if (frameContainer && !getNavigationItemAnchor(target)) {
8534
- hoveredNavContainerRef.current = frameContainer;
8535
- setHoveredNavContainerRect(frameContainer.getBoundingClientRect());
8536
- hoveredItemElRef.current = null;
8537
- setHoveredItemRect(null);
8538
- return;
10716
+ if (linkPopoverOpenRef.current) {
10717
+ hoveredItemElRef.current = null;
10718
+ setHoveredItemRect(null);
10719
+ hoveredNavContainerRef.current = null;
10720
+ setHoveredNavContainerRect(null);
10721
+ return;
10722
+ }
10723
+ {
10724
+ const selected2 = selectedElRef.current;
10725
+ const selectedIsFooterColumn = Boolean(selected2) && !isFooterLinksContainer(selected2) && (selected2.hasAttribute("data-ohw-footer-col") || selected2.hasAttribute("data-ohw-footer-column") || Boolean(selected2.closest("footer") && isInferredFooterGroup(selected2)));
10726
+ const allowNavContainerHover = toolbarVariantRef.current !== "select-frame";
10727
+ const allowFooterLinksHover = toolbarVariantRef.current !== "select-frame" || selectedIsFooterColumn;
10728
+ if (allowNavContainerHover) {
10729
+ const navContainer = target.closest("[data-ohw-nav-container]");
10730
+ if (navContainer && !getNavigationItemAnchor(target)) {
10731
+ hoveredNavContainerRef.current = navContainer;
10732
+ setHoveredNavContainerRect(navContainer.getBoundingClientRect());
10733
+ hoveredItemElRef.current = null;
10734
+ setHoveredItemRect(null);
10735
+ return;
10736
+ }
8539
10737
  }
8540
- if (!target.closest("[data-ohw-nav-container], [data-ohw-footer-column]")) {
10738
+ if (allowFooterLinksHover) {
10739
+ const navContainer = target.closest("[data-ohw-nav-container]");
10740
+ if (!navContainer) {
10741
+ const footerLinks = target.closest("[data-ohw-footer-links], .rb-footer-links") ?? null;
10742
+ if (footerLinks && selected2 !== footerLinks && !getNavigationItemAnchor(target) && !target.closest("[data-ohw-footer-col], [data-ohw-footer-column]")) {
10743
+ hoveredNavContainerRef.current = footerLinks;
10744
+ setHoveredNavContainerRect(footerLinks.getBoundingClientRect());
10745
+ hoveredItemElRef.current = null;
10746
+ setHoveredItemRect(null);
10747
+ return;
10748
+ }
10749
+ if (!footerLinks) {
10750
+ hoveredNavContainerRef.current = null;
10751
+ setHoveredNavContainerRect(null);
10752
+ }
10753
+ }
10754
+ } else if (toolbarVariantRef.current === "select-frame") {
8541
10755
  hoveredNavContainerRef.current = null;
8542
10756
  setHoveredNavContainerRect(null);
8543
10757
  }
@@ -8547,12 +10761,21 @@ function OhhwellsBridge() {
8547
10761
  hoveredNavContainerRef.current = null;
8548
10762
  setHoveredNavContainerRect(null);
8549
10763
  const selected2 = selectedElRef.current;
8550
- if (selected2 === navAnchor || selected2?.contains(navAnchor)) return;
10764
+ if (selected2 === navAnchor) return;
8551
10765
  clearHrefKeyHover(navAnchor);
8552
10766
  hoveredItemElRef.current = navAnchor;
8553
10767
  setHoveredItemRect(navAnchor.getBoundingClientRect());
8554
10768
  return;
8555
10769
  }
10770
+ const footerCol = target.closest("[data-ohw-footer-col]") ?? target.closest("[data-ohw-footer-column]");
10771
+ if (footerCol) {
10772
+ hoveredNavContainerRef.current = null;
10773
+ setHoveredNavContainerRect(null);
10774
+ if (selectedElRef.current === footerCol) return;
10775
+ hoveredItemElRef.current = footerCol;
10776
+ setHoveredItemRect(footerCol.getBoundingClientRect());
10777
+ return;
10778
+ }
8556
10779
  const editable = target.closest("[data-ohw-editable]");
8557
10780
  if (!editable) return;
8558
10781
  const selected = selectedElRef.current;
@@ -8570,14 +10793,25 @@ function OhhwellsBridge() {
8570
10793
  };
8571
10794
  const handleMouseOut = (e) => {
8572
10795
  const target = e.target;
8573
- if (target.closest("[data-ohw-nav-container], [data-ohw-footer-column]")) {
10796
+ if (target.closest("[data-ohw-nav-container], [data-ohw-footer-col], [data-ohw-footer-column], [data-ohw-footer-links], .rb-footer-links")) {
8574
10797
  const related2 = e.relatedTarget instanceof Element ? e.relatedTarget : null;
8575
- if (related2?.closest("[data-ohw-nav-container], [data-ohw-footer-column], [data-ohw-navbar-container-chrome], [data-ohw-navbar-add-button]")) {
10798
+ if (related2?.closest("[data-ohw-nav-container], [data-ohw-footer-col], [data-ohw-footer-column], [data-ohw-footer-links], .rb-footer-links, [data-ohw-navbar-container-chrome], [data-ohw-navbar-add-button], [data-ohw-footer-container-chrome], [data-ohw-footer-add-button]")) {
8576
10799
  return;
8577
10800
  }
8578
10801
  hoveredNavContainerRef.current = null;
8579
10802
  setHoveredNavContainerRect(null);
8580
10803
  }
10804
+ const footerCol = target.closest("[data-ohw-footer-col]") ?? target.closest("[data-ohw-footer-column]");
10805
+ if (footerCol && hoveredItemElRef.current === footerCol) {
10806
+ const related2 = e.relatedTarget instanceof Element ? e.relatedTarget : null;
10807
+ if (related2?.closest("[data-ohw-drag-handle-container], [data-ohw-item-interaction]")) return;
10808
+ if (related2?.closest("[data-ohw-footer-col], [data-ohw-footer-column]") === footerCol) {
10809
+ return;
10810
+ }
10811
+ hoveredItemElRef.current = null;
10812
+ setHoveredItemRect(null);
10813
+ return;
10814
+ }
8581
10815
  const navAnchor = getNavigationItemAnchor(target);
8582
10816
  if (navAnchor) {
8583
10817
  const related2 = e.relatedTarget instanceof Element ? e.relatedTarget : null;
@@ -8656,6 +10890,15 @@ function OhhwellsBridge() {
8656
10890
  if (track) track.setAttribute("data-ohw-hover-paused", "");
8657
10891
  };
8658
10892
  const clearImageHover = () => setMediaHover(null);
10893
+ const dismissImageHover = () => {
10894
+ if (hoveredImageRef.current) {
10895
+ hoveredImageRef.current = null;
10896
+ hoveredImageHasTextOverlapRef.current = false;
10897
+ resumeAnimTracks();
10898
+ postToParentRef.current({ type: "ow:image-unhover" });
10899
+ }
10900
+ clearImageHover();
10901
+ };
8659
10902
  const findImageAtPoint = (clientX, clientY, fromParentViewport) => {
8660
10903
  const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
8661
10904
  const images = Array.from(document.querySelectorAll(MEDIA_SELECTOR));
@@ -8699,26 +10942,21 @@ function OhhwellsBridge() {
8699
10942
  }
8700
10943
  return smallest;
8701
10944
  };
8702
- const dismissImageHover = () => {
8703
- if (hoveredImageRef.current) {
8704
- hoveredImageRef.current = null;
8705
- hoveredImageHasTextOverlapRef.current = false;
8706
- resumeAnimTracks();
8707
- postToParentRef.current({ type: "ow:image-unhover" });
8708
- }
8709
- };
8710
10945
  const probeNavigationHoverAt = (x, y) => {
8711
- if (toolbarVariantRef.current === "select-frame") {
10946
+ const selected = selectedElRef.current;
10947
+ const selectedIsFooterColumn = Boolean(selected) && !isFooterLinksContainer(selected) && (selected.hasAttribute("data-ohw-footer-col") || selected.hasAttribute("data-ohw-footer-column") || Boolean(selected.closest("footer") && isInferredFooterGroup(selected)));
10948
+ const selectedIsFooterLinks = Boolean(selected && isFooterLinksContainer(selected));
10949
+ const allowNavContainerHover = toolbarVariantRef.current !== "select-frame";
10950
+ const allowFooterLinksHover = (toolbarVariantRef.current !== "select-frame" || selectedIsFooterColumn) && !selectedIsFooterLinks;
10951
+ if (toolbarVariantRef.current === "select-frame" && !allowFooterLinksHover) {
8712
10952
  hoveredNavContainerRef.current = null;
8713
10953
  setHoveredNavContainerRect(null);
8714
- return;
8715
10954
  }
8716
- const frameContainers = [
8717
- ...document.querySelectorAll("[data-ohw-nav-container]"),
8718
- ...document.querySelectorAll("[data-ohw-footer-column]")
8719
- ];
8720
- let overFrame = null;
8721
- for (const container of frameContainers) {
10955
+ const navContainers = Array.from(
10956
+ document.querySelectorAll("[data-ohw-nav-container]")
10957
+ );
10958
+ const footerColumns = listFooterColumns();
10959
+ for (const container of [...navContainers, ...footerColumns]) {
8722
10960
  const containerRect = container.getBoundingClientRect();
8723
10961
  const overContainer = x >= containerRect.left && x <= containerRect.right && y >= containerRect.top && y <= containerRect.bottom;
8724
10962
  if (!overContainer) continue;
@@ -8732,26 +10970,66 @@ function OhhwellsBridge() {
8732
10970
  if (navItemHit) {
8733
10971
  hoveredNavContainerRef.current = null;
8734
10972
  setHoveredNavContainerRect(null);
8735
- const selected = selectedElRef.current;
8736
- if (selected !== navItemHit && !selected?.contains(navItemHit)) {
10973
+ if (selected !== navItemHit) {
8737
10974
  clearHrefKeyHover(navItemHit);
8738
10975
  hoveredItemElRef.current = navItemHit;
8739
10976
  setHoveredItemRect(navItemHit.getBoundingClientRect());
10977
+ } else if (hoveredItemElRef.current === navItemHit) {
10978
+ hoveredItemElRef.current = null;
10979
+ setHoveredItemRect(null);
8740
10980
  }
8741
10981
  return;
8742
10982
  }
8743
- overFrame = container;
8744
- break;
8745
10983
  }
8746
- if (!overFrame) {
8747
- hoveredNavContainerRef.current = null;
8748
- setHoveredNavContainerRect(null);
10984
+ if (allowNavContainerHover) {
10985
+ for (const container of navContainers) {
10986
+ const containerRect = container.getBoundingClientRect();
10987
+ const overContainer = x >= containerRect.left && x <= containerRect.right && y >= containerRect.top && y <= containerRect.bottom;
10988
+ if (!overContainer) continue;
10989
+ if (isPointOverNavItem(container, x, y)) continue;
10990
+ hoveredNavContainerRef.current = container;
10991
+ setHoveredNavContainerRect(container.getBoundingClientRect());
10992
+ hoveredItemElRef.current = null;
10993
+ setHoveredItemRect(null);
10994
+ return;
10995
+ }
10996
+ }
10997
+ if (allowFooterLinksHover) {
10998
+ const footerLinks = getFooterLinksContainer();
10999
+ if (footerLinks) {
11000
+ const containerRect = footerLinks.getBoundingClientRect();
11001
+ const overContainer = x >= containerRect.left && x <= containerRect.right && y >= containerRect.top && y <= containerRect.bottom;
11002
+ if (overContainer && !isPointOverNavItem(footerLinks, x, y) && !isPointOverFooterColumn(x, y)) {
11003
+ hoveredNavContainerRef.current = footerLinks;
11004
+ setHoveredNavContainerRect(footerLinks.getBoundingClientRect());
11005
+ hoveredItemElRef.current = null;
11006
+ setHoveredItemRect(null);
11007
+ return;
11008
+ }
11009
+ }
11010
+ }
11011
+ hoveredNavContainerRef.current = null;
11012
+ setHoveredNavContainerRect(null);
11013
+ for (const column of footerColumns) {
11014
+ const containerRect = column.getBoundingClientRect();
11015
+ const overContainer = x >= containerRect.left && x <= containerRect.right && y >= containerRect.top && y <= containerRect.bottom;
11016
+ if (!overContainer) continue;
11017
+ if (isPointOverNavItem(column, x, y)) continue;
11018
+ if (selectedElRef.current === column) {
11019
+ if (hoveredItemElRef.current === column) {
11020
+ hoveredItemElRef.current = null;
11021
+ setHoveredItemRect(null);
11022
+ }
11023
+ return;
11024
+ }
11025
+ hoveredItemElRef.current = column;
11026
+ setHoveredItemRect(column.getBoundingClientRect());
8749
11027
  return;
8750
11028
  }
8751
- hoveredNavContainerRef.current = overFrame;
8752
- setHoveredNavContainerRect(overFrame.getBoundingClientRect());
8753
- hoveredItemElRef.current = null;
8754
- setHoveredItemRect(null);
11029
+ if (hoveredItemElRef.current?.hasAttribute("data-ohw-footer-col") || hoveredItemElRef.current?.hasAttribute("data-ohw-footer-column")) {
11030
+ hoveredItemElRef.current = null;
11031
+ setHoveredItemRect(null);
11032
+ }
8755
11033
  };
8756
11034
  const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
8757
11035
  if (linkPopoverOpenRef.current) {
@@ -8772,6 +11050,10 @@ function OhhwellsBridge() {
8772
11050
  }
8773
11051
  hoveredNavContainerRef.current = null;
8774
11052
  setHoveredNavContainerRect(null);
11053
+ if (hoveredItemElRef.current?.hasAttribute("data-ohw-footer-col") || hoveredItemElRef.current?.hasAttribute("data-ohw-footer-column")) {
11054
+ hoveredItemElRef.current = null;
11055
+ setHoveredItemRect(null);
11056
+ }
8775
11057
  const toggleEl = document.querySelector("[data-ohw-state-toggle]");
8776
11058
  if (toggleEl) {
8777
11059
  const tr = toggleEl.getBoundingClientRect();
@@ -9019,6 +11301,15 @@ function OhhwellsBridge() {
9019
11301
  probeHoverCardsAt(clientX, clientY);
9020
11302
  };
9021
11303
  const handleDragOver = (e) => {
11304
+ const footerSession = footerDragRef.current;
11305
+ if (footerSession) {
11306
+ e.preventDefault();
11307
+ if (e.dataTransfer) e.dataTransfer.dropEffect = "move";
11308
+ const slot = footerSession.kind === "link" && footerSession.hrefKey ? hitTestLinkDropSlot(e.clientX, e.clientY, footerSession.hrefKey) : hitTestColumnDropSlot(e.clientX, e.clientY);
11309
+ refreshFooterDragVisualsRef.current(footerSession, slot, e.clientX, e.clientY);
11310
+ return;
11311
+ }
11312
+ if (onNavDragOver(e)) return;
9022
11313
  e.preventDefault();
9023
11314
  const el = findImageAtPoint(e.clientX, e.clientY, false);
9024
11315
  if (!el) {
@@ -9225,6 +11516,7 @@ function OhhwellsBridge() {
9225
11516
  }
9226
11517
  editContentRef.current = { ...editContentRef.current, ...content };
9227
11518
  reconcileNavbarItemsFromContent(editContentRef.current);
11519
+ reconcileFooterOrderFromContent(editContentRef.current);
9228
11520
  syncNavigationDragCursorAttrs();
9229
11521
  enforceLinkHrefs();
9230
11522
  postToParentRef.current({ type: "ow:hydrate-done" });
@@ -9279,6 +11571,11 @@ function OhhwellsBridge() {
9279
11571
  }
9280
11572
  if (selectedElRef.current) {
9281
11573
  if (toolbarVariantRef.current === "select-frame") {
11574
+ const parent2 = getNavigationSelectionParent(selectedElRef.current);
11575
+ if (parent2 && isNavigationContainer(parent2) && isFooterLinksContainer(parent2) && !isFooterLinksContainer(selectedElRef.current)) {
11576
+ selectFrameRef.current(parent2);
11577
+ return;
11578
+ }
9282
11579
  deselectRef.current();
9283
11580
  return;
9284
11581
  }
@@ -9294,13 +11591,10 @@ function OhhwellsBridge() {
9294
11591
  const handleKeyDown = (e) => {
9295
11592
  if (e.key === "Escape" && document.querySelector("[data-ohw-section-picker]")) return;
9296
11593
  if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "a" && activeElRef.current) {
9297
- const navAnchor = getNavigationItemAnchor(activeElRef.current);
9298
- if (navAnchor) {
9299
- e.preventDefault();
9300
- selectAllTextInEditable(activeElRef.current);
9301
- refreshActiveCommandsRef.current();
9302
- return;
9303
- }
11594
+ e.preventDefault();
11595
+ selectAllTextInEditable(activeElRef.current);
11596
+ refreshActiveCommandsRef.current();
11597
+ return;
9304
11598
  }
9305
11599
  if (e.key === "Escape" && linkPopoverOpenRef.current) {
9306
11600
  setLinkPopoverRef.current(null);
@@ -9308,6 +11602,12 @@ function OhhwellsBridge() {
9308
11602
  }
9309
11603
  if (e.key === "Escape" && selectedElRef.current && !activeElRef.current) {
9310
11604
  if (toolbarVariantRef.current === "select-frame") {
11605
+ const parent2 = getNavigationSelectionParent(selectedElRef.current);
11606
+ if (parent2 && isNavigationContainer(parent2) && isFooterLinksContainer(parent2) && !isFooterLinksContainer(selectedElRef.current)) {
11607
+ e.preventDefault();
11608
+ selectFrameRef.current(parent2);
11609
+ return;
11610
+ }
9311
11611
  deselectRef.current();
9312
11612
  return;
9313
11613
  }
@@ -9382,6 +11682,26 @@ function OhhwellsBridge() {
9382
11682
  if (siblingHintElRef.current) {
9383
11683
  setSiblingHintRect(siblingHintElRef.current.getBoundingClientRect());
9384
11684
  }
11685
+ const selected = selectedElRef.current;
11686
+ if (selected && !footerDragRef.current && !navDragRef.current) {
11687
+ if (selected.hasAttribute("data-ohw-footer-col") || Boolean(selected.closest("footer") && isInferredFooterGroup(selected))) {
11688
+ setSiblingHintRects(
11689
+ listFooterColumns().filter((column) => column !== selected).map((column) => column.getBoundingClientRect())
11690
+ );
11691
+ } else if (isNavigationItem(selected)) {
11692
+ setSiblingHintRects(collectNavigationItemSiblingHintRects(selected));
11693
+ }
11694
+ }
11695
+ if (footerDragRef.current) {
11696
+ const session = footerDragRef.current;
11697
+ const slot = session.kind === "link" && session.hrefKey ? hitTestLinkDropSlot(session.lastClientX, session.lastClientY, session.hrefKey) : hitTestColumnDropSlot(session.lastClientX, session.lastClientY);
11698
+ refreshFooterDragVisualsRef.current(session, slot, session.lastClientX, session.lastClientY);
11699
+ }
11700
+ if (navDragRef.current) {
11701
+ const session = navDragRef.current;
11702
+ const slot = hitTestNavDropSlot(session.lastClientX, session.lastClientY, session.hrefKey);
11703
+ refreshNavDragVisualsRef.current(session, slot, session.lastClientX, session.lastClientY);
11704
+ }
9385
11705
  if (hoveredImageRef.current) {
9386
11706
  const el = hoveredImageRef.current;
9387
11707
  const r2 = el.getBoundingClientRect();
@@ -9392,7 +11712,7 @@ function OhhwellsBridge() {
9392
11712
  };
9393
11713
  const handleSave = (e) => {
9394
11714
  if (e.data?.type !== "ow:save") return;
9395
- const nodes = collectEditableNodes();
11715
+ const nodes = collectEditableNodes(editContentRef.current);
9396
11716
  const tracker = document.querySelector("[data-ohw-sections-tracker]");
9397
11717
  if (tracker?.textContent) nodes.push({ key: "__ohw_sections", type: "sections", text: tracker.textContent });
9398
11718
  postToParentRef.current({ type: "ow:save-result", nodes });
@@ -9450,30 +11770,30 @@ function OhhwellsBridge() {
9450
11770
  const h = document.documentElement.scrollHeight;
9451
11771
  if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
9452
11772
  };
11773
+ let selectionChangeRaf = null;
11774
+ const runSelectionChange = () => {
11775
+ selectionChangeRaf = null;
11776
+ const activeRoot = activeElRef.current;
11777
+ if (!activeRoot) return;
11778
+ const startEl = resolveSelectionStartElement(window.getSelection());
11779
+ const next = startEl ? detectActiveFormats(startEl, activeRoot) : /* @__PURE__ */ new Set();
11780
+ setActiveCommands((prev) => setsEqual(prev, next) ? prev : next);
11781
+ };
9453
11782
  const handleSelectionChange = () => {
9454
- if (!activeElRef.current) return;
9455
- const next = /* @__PURE__ */ new Set();
9456
- for (const cmd of ["bold", "italic", "underline", "strikeThrough", "insertUnorderedList", "insertOrderedList"]) {
9457
- try {
9458
- if (document.queryCommandState(cmd)) next.add(cmd);
9459
- } catch {
9460
- }
9461
- }
9462
- const sel = window.getSelection();
9463
- const anchor = sel?.anchorNode;
9464
- if (anchor) {
9465
- const el = anchor.nodeType === Node.TEXT_NODE ? anchor.parentElement : anchor;
9466
- const block = el?.closest("div, p, h1, h2, h3, h4, h5, h6, li, td, th") ?? el;
9467
- if (block) {
9468
- const align = getComputedStyle(block).textAlign;
9469
- if (align === "center") next.add("justifyCenter");
9470
- else if (align === "right" || align === "end") next.add("justifyRight");
9471
- else next.add("justifyLeft");
9472
- }
9473
- }
9474
- setActiveCommands(next);
11783
+ if (pointerHeldRef.current) return;
11784
+ if (selectionChangeRaf !== null) return;
11785
+ selectionChangeRaf = requestAnimationFrame(runSelectionChange);
11786
+ };
11787
+ const markPointerHeld = (e) => {
11788
+ if (e.button !== 0) return;
11789
+ pointerHeldRef.current = true;
11790
+ };
11791
+ const markPointerReleased = () => {
11792
+ if (!pointerHeldRef.current) return;
11793
+ pointerHeldRef.current = false;
11794
+ handleSelectionChange();
9475
11795
  };
9476
- refreshActiveCommandsRef.current = handleSelectionChange;
11796
+ refreshActiveCommandsRef.current = runSelectionChange;
9477
11797
  const handleDocMouseLeave = () => {
9478
11798
  hoveredImageRef.current = null;
9479
11799
  setMediaHover(null);
@@ -9615,7 +11935,7 @@ function OhhwellsBridge() {
9615
11935
  }
9616
11936
  }
9617
11937
  const footerColumn = findHoveredNavOrFooterContainer(clientX, clientY);
9618
- if (footerColumn?.hasAttribute("data-ohw-footer-column")) {
11938
+ if (footerColumn?.hasAttribute("data-ohw-footer-col") || footerColumn?.hasAttribute("data-ohw-footer-column")) {
9619
11939
  selectFrameRef.current(footerColumn);
9620
11940
  return;
9621
11941
  }
@@ -9690,7 +12010,161 @@ function OhhwellsBridge() {
9690
12010
  if (imageShowTimerRef.current) clearTimeout(imageShowTimerRef.current);
9691
12011
  };
9692
12012
  }, [isEditMode, refreshStateRules]);
9693
- useEffect7(() => {
12013
+ useEffect8(() => {
12014
+ if (!isEditMode) return;
12015
+ const THRESHOLD = 10;
12016
+ const resolveWasSelected = (el) => {
12017
+ if (selectedElRef.current === el) return true;
12018
+ const activeAnchor = activeElRef.current ? getNavigationItemAnchor(activeElRef.current) : null;
12019
+ return activeAnchor === el;
12020
+ };
12021
+ const onPointerDown = (e) => {
12022
+ if (e.button !== 0) return;
12023
+ if (suppressNextClickRef.current && Date.now() >= suppressClickUntilRef.current && !footerDragRef.current && !footerPointerDragRef.current?.started) {
12024
+ suppressNextClickRef.current = false;
12025
+ }
12026
+ if (footerDragRef.current) return;
12027
+ const target = e.target;
12028
+ if (!target) return;
12029
+ 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]')) {
12030
+ return;
12031
+ }
12032
+ if (target.closest("[data-ohw-item-drag-surface]")) return;
12033
+ const anchor = getNavigationItemAnchor(target);
12034
+ const hrefKey = anchor?.getAttribute("data-ohw-href-key") ?? null;
12035
+ if (anchor && isFooterHrefKey(hrefKey)) {
12036
+ footerPointerDragRef.current = {
12037
+ el: anchor,
12038
+ kind: "link",
12039
+ startX: e.clientX,
12040
+ startY: e.clientY,
12041
+ pointerId: e.pointerId,
12042
+ wasSelected: resolveWasSelected(anchor),
12043
+ started: false
12044
+ };
12045
+ return;
12046
+ }
12047
+ const col = target.closest("[data-ohw-footer-col]");
12048
+ if (col && !getNavigationItemAnchor(target)) {
12049
+ footerPointerDragRef.current = {
12050
+ el: col,
12051
+ kind: "column",
12052
+ startX: e.clientX,
12053
+ startY: e.clientY,
12054
+ pointerId: e.pointerId,
12055
+ wasSelected: resolveWasSelected(col),
12056
+ started: false
12057
+ };
12058
+ }
12059
+ };
12060
+ const onPointerMove = (e) => {
12061
+ const pending = footerPointerDragRef.current;
12062
+ if (!pending) return;
12063
+ if (pending.started) {
12064
+ e.preventDefault();
12065
+ clearTextSelection();
12066
+ const session = footerDragRef.current;
12067
+ if (!session) return;
12068
+ const slot = session.kind === "link" && session.hrefKey ? hitTestLinkDropSlot(e.clientX, e.clientY, session.hrefKey) : hitTestColumnDropSlot(e.clientX, e.clientY);
12069
+ refreshFooterDragVisualsRef.current(session, slot, e.clientX, e.clientY);
12070
+ return;
12071
+ }
12072
+ const dx = e.clientX - pending.startX;
12073
+ const dy = e.clientY - pending.startY;
12074
+ if (dx * dx + dy * dy < THRESHOLD * THRESHOLD) return;
12075
+ e.preventDefault();
12076
+ pending.started = true;
12077
+ armFooterPressDrag();
12078
+ clearTextSelection();
12079
+ try {
12080
+ document.body.setPointerCapture(pending.pointerId);
12081
+ } catch {
12082
+ }
12083
+ if (linkPopoverOpenRef.current) {
12084
+ setLinkPopoverRef.current(null);
12085
+ }
12086
+ if (activeElRef.current) {
12087
+ deactivateRef.current();
12088
+ }
12089
+ if (pending.kind === "link") {
12090
+ const key = pending.el.getAttribute("data-ohw-href-key");
12091
+ if (!key) return;
12092
+ const column = findFooterColumnForLink(pending.el);
12093
+ const columns2 = listFooterColumns();
12094
+ beginFooterDragRef.current({
12095
+ kind: "link",
12096
+ hrefKey: key,
12097
+ columnEl: column,
12098
+ sourceColumnIndex: column ? columns2.indexOf(column) : 0,
12099
+ wasSelected: pending.wasSelected,
12100
+ draggedEl: pending.el,
12101
+ lastClientX: e.clientX,
12102
+ lastClientY: e.clientY,
12103
+ activeSlot: null
12104
+ });
12105
+ return;
12106
+ }
12107
+ const columns = listFooterColumns();
12108
+ const idx = columns.indexOf(pending.el);
12109
+ if (idx < 0) return;
12110
+ beginFooterDragRef.current({
12111
+ kind: "column",
12112
+ hrefKey: null,
12113
+ columnEl: pending.el,
12114
+ sourceColumnIndex: idx,
12115
+ wasSelected: pending.wasSelected,
12116
+ draggedEl: pending.el,
12117
+ lastClientX: e.clientX,
12118
+ lastClientY: e.clientY,
12119
+ activeSlot: null
12120
+ });
12121
+ };
12122
+ const endPointerDrag = (e) => {
12123
+ const pending = footerPointerDragRef.current;
12124
+ footerPointerDragRef.current = null;
12125
+ try {
12126
+ if (document.body.hasPointerCapture(e.pointerId)) {
12127
+ document.body.releasePointerCapture(e.pointerId);
12128
+ }
12129
+ } catch {
12130
+ }
12131
+ if (!pending) return;
12132
+ if (!pending.started) {
12133
+ unlockFooterDragInteraction();
12134
+ return;
12135
+ }
12136
+ suppressNextClickRef.current = true;
12137
+ suppressClickUntilRef.current = Date.now() + 500;
12138
+ commitFooterDragRef.current(e.clientX, e.clientY);
12139
+ };
12140
+ const blockSelectStart = (e) => {
12141
+ if (footerDragRef.current || footerPointerDragRef.current?.started || document.documentElement.hasAttribute("data-ohw-footer-press-drag")) {
12142
+ e.preventDefault();
12143
+ }
12144
+ };
12145
+ const onDragEnd = (_e) => {
12146
+ if (!footerDragRef.current) return;
12147
+ suppressNextClickRef.current = true;
12148
+ suppressClickUntilRef.current = Date.now() + 500;
12149
+ commitFooterDragRef.current();
12150
+ };
12151
+ document.addEventListener("pointerdown", onPointerDown, true);
12152
+ document.addEventListener("pointermove", onPointerMove, true);
12153
+ document.addEventListener("pointerup", endPointerDrag, true);
12154
+ document.addEventListener("pointercancel", endPointerDrag, true);
12155
+ document.addEventListener("selectstart", blockSelectStart, true);
12156
+ document.addEventListener("dragend", onDragEnd, true);
12157
+ return () => {
12158
+ document.removeEventListener("pointerdown", onPointerDown, true);
12159
+ document.removeEventListener("pointermove", onPointerMove, true);
12160
+ document.removeEventListener("pointerup", endPointerDrag, true);
12161
+ document.removeEventListener("pointercancel", endPointerDrag, true);
12162
+ document.removeEventListener("selectstart", blockSelectStart, true);
12163
+ document.removeEventListener("dragend", onDragEnd, true);
12164
+ unlockFooterDragInteraction();
12165
+ };
12166
+ }, [isEditMode]);
12167
+ useEffect8(() => {
9694
12168
  const handler = (e) => {
9695
12169
  if (e.data?.type !== "ow:request-schedule-config") return;
9696
12170
  const insertAfterVal = e.data.insertAfter;
@@ -9706,7 +12180,7 @@ function OhhwellsBridge() {
9706
12180
  window.addEventListener("message", handler);
9707
12181
  return () => window.removeEventListener("message", handler);
9708
12182
  }, [processConfigRequest]);
9709
- useEffect7(() => {
12183
+ useEffect8(() => {
9710
12184
  if (!isEditMode) return;
9711
12185
  document.querySelectorAll("[data-ohw-active-state]").forEach((el) => {
9712
12186
  el.removeAttribute("data-ohw-active-state");
@@ -9727,7 +12201,13 @@ function OhhwellsBridge() {
9727
12201
  const next = { ...prev, [pathKey]: sections };
9728
12202
  return shouldUseDevFixtures() ? { ...DEV_SECTIONS_BY_PATH, ...next } : next;
9729
12203
  });
9730
- postToParent2({ type: "ow:ready", version: "1", path: pathname, nodes: collectEditableNodes(), sections });
12204
+ postToParent2({
12205
+ type: "ow:ready",
12206
+ version: "1",
12207
+ path: pathname,
12208
+ nodes: collectEditableNodes(editContentRef.current),
12209
+ sections
12210
+ });
9731
12211
  postToParent2({ type: "ow:request-site-pages" });
9732
12212
  }, 150);
9733
12213
  return () => {
@@ -9735,19 +12215,49 @@ function OhhwellsBridge() {
9735
12215
  clearTimeout(timer);
9736
12216
  };
9737
12217
  }, [pathname, isEditMode, refreshStateRules, postToParent2]);
9738
- useEffect7(() => {
12218
+ useEffect8(() => {
9739
12219
  scrollToHashSectionWhenReady();
9740
12220
  const onHashChange = () => scrollToHashSectionWhenReady();
9741
12221
  window.addEventListener("hashchange", onHashChange);
9742
12222
  return () => window.removeEventListener("hashchange", onHashChange);
9743
12223
  }, [pathname]);
9744
- const handleCommand = useCallback3((cmd) => {
12224
+ const handleCommand = useCallback5((cmd) => {
12225
+ const el = activeElRef.current;
12226
+ const selBefore = window.getSelection();
12227
+ let savedOffsets = null;
12228
+ if (el && selBefore && selBefore.rangeCount > 0 && !selBefore.isCollapsed) {
12229
+ const r2 = selBefore.getRangeAt(0);
12230
+ if (el.contains(r2.commonAncestorContainer)) {
12231
+ savedOffsets = {
12232
+ start: textOffsetInRoot(el, r2.startContainer, r2.startOffset),
12233
+ end: textOffsetInRoot(el, r2.endContainer, r2.endOffset)
12234
+ };
12235
+ }
12236
+ }
12237
+ document.execCommand("styleWithCSS", false, true);
9745
12238
  document.execCommand(cmd, false);
9746
- activeElRef.current?.focus();
9747
- if (activeElRef.current) setToolbarRect(getEditMeasureEl(activeElRef.current).getBoundingClientRect());
12239
+ if (el && document.activeElement !== el) {
12240
+ el.focus();
12241
+ }
12242
+ const selAfter = window.getSelection();
12243
+ if (el && savedOffsets && selAfter?.isCollapsed) {
12244
+ const start = pointAtTextOffset(el, savedOffsets.start);
12245
+ const end = pointAtTextOffset(el, savedOffsets.end);
12246
+ if (start && end) {
12247
+ try {
12248
+ const range = document.createRange();
12249
+ range.setStart(start.node, start.offset);
12250
+ range.setEnd(end.node, end.offset);
12251
+ selAfter.removeAllRanges();
12252
+ selAfter.addRange(range);
12253
+ } catch {
12254
+ }
12255
+ }
12256
+ }
12257
+ if (el) setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
9748
12258
  refreshActiveCommandsRef.current();
9749
12259
  }, []);
9750
- const handleStateChange = useCallback3((state) => {
12260
+ const handleStateChange = useCallback5((state) => {
9751
12261
  if (!activeStateElRef.current) return;
9752
12262
  const el = activeStateElRef.current;
9753
12263
  if (state === "Default") {
@@ -9760,11 +12270,11 @@ function OhhwellsBridge() {
9760
12270
  }
9761
12271
  setToggleState((prev) => prev ? { ...prev, activeState: state } : null);
9762
12272
  }, [deactivate]);
9763
- const closeLinkPopover = useCallback3(() => {
12273
+ const closeLinkPopover = useCallback5(() => {
9764
12274
  addNavAfterAnchorRef.current = null;
9765
12275
  setLinkPopover(null);
9766
12276
  }, []);
9767
- const openLinkPopoverForActive = useCallback3(() => {
12277
+ const openLinkPopoverForActive = useCallback5(() => {
9768
12278
  const hrefCtx = getHrefKeyFromElement(activeElRef.current);
9769
12279
  if (!hrefCtx) return;
9770
12280
  bumpLinkPopoverGrace();
@@ -9775,7 +12285,7 @@ function OhhwellsBridge() {
9775
12285
  });
9776
12286
  deactivate();
9777
12287
  }, [deactivate]);
9778
- const openLinkPopoverForSelected = useCallback3(() => {
12288
+ const openLinkPopoverForSelected = useCallback5(() => {
9779
12289
  const anchor = selectedElRef.current;
9780
12290
  if (!anchor) return;
9781
12291
  const key = anchor.getAttribute("data-ohw-href-key");
@@ -9788,7 +12298,7 @@ function OhhwellsBridge() {
9788
12298
  });
9789
12299
  deselect();
9790
12300
  }, [deselect]);
9791
- const handleLinkPopoverSubmit = useCallback3(
12301
+ const handleLinkPopoverSubmit = useCallback5(
9792
12302
  (target) => {
9793
12303
  const session = linkPopoverSessionRef.current;
9794
12304
  if (!session) return;
@@ -9847,13 +12357,13 @@ function OhhwellsBridge() {
9847
12357
  const showEditLink = toolbarShowEditLink;
9848
12358
  const currentSections = sectionsByPath[pathname] ?? [];
9849
12359
  linkPopoverOpenRef.current = linkPopover !== null;
9850
- const handleMediaReplace = useCallback3(
12360
+ const handleMediaReplace = useCallback5(
9851
12361
  (key) => {
9852
12362
  postToParent2({ type: "ow:image-pick", key, elementType: mediaHover?.elementType ?? "image" });
9853
12363
  },
9854
12364
  [postToParent2, mediaHover?.elementType]
9855
12365
  );
9856
- const handleMediaFadeOutComplete = useCallback3((key) => {
12366
+ const handleMediaFadeOutComplete = useCallback5((key) => {
9857
12367
  setUploadingRects((prev) => {
9858
12368
  if (!(key in prev)) return prev;
9859
12369
  const next = { ...prev };
@@ -9861,7 +12371,7 @@ function OhhwellsBridge() {
9861
12371
  return next;
9862
12372
  });
9863
12373
  }, []);
9864
- const handleVideoSettingsChange = useCallback3(
12374
+ const handleVideoSettingsChange = useCallback5(
9865
12375
  (key, settings) => {
9866
12376
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
9867
12377
  const video = getVideoEl(el);
@@ -9885,8 +12395,8 @@ function OhhwellsBridge() {
9885
12395
  );
9886
12396
  return bridgeRoot ? createPortal2(
9887
12397
  /* @__PURE__ */ jsxs14(Fragment5, { children: [
9888
- /* @__PURE__ */ jsx23("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
9889
- Object.entries(uploadingRects).map(([key, { rect, fadingOut }]) => /* @__PURE__ */ jsx23(
12398
+ /* @__PURE__ */ jsx25("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
12399
+ Object.entries(uploadingRects).map(([key, { rect, fadingOut }]) => /* @__PURE__ */ jsx25(
9890
12400
  MediaOverlay,
9891
12401
  {
9892
12402
  hover: { key, rect, elementType: "image", isDragOver: false, hasTextOverlap: false },
@@ -9897,7 +12407,7 @@ function OhhwellsBridge() {
9897
12407
  },
9898
12408
  `uploading-${key}`
9899
12409
  )),
9900
- mediaHover && !(mediaHover.key in uploadingRects) && /* @__PURE__ */ jsx23(
12410
+ mediaHover && !(mediaHover.key in uploadingRects) && /* @__PURE__ */ jsx25(
9901
12411
  MediaOverlay,
9902
12412
  {
9903
12413
  hover: mediaHover,
@@ -9906,28 +12416,70 @@ function OhhwellsBridge() {
9906
12416
  onVideoSettingsChange: handleVideoSettingsChange
9907
12417
  }
9908
12418
  ),
9909
- siblingHintRect && !isItemDragging && /* @__PURE__ */ jsx23(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
9910
- hoveredNavContainerRect && toolbarVariant !== "select-frame" && !linkPopover && !isItemDragging && !siblingHintRect && /* @__PURE__ */ jsx23(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
9911
- toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && selectedElRef.current && isNavbarLinksContainer(selectedElRef.current) && /* @__PURE__ */ jsx23(
9912
- NavbarContainerChrome,
12419
+ siblingHintRect && !linkPopover && !isItemDragging && siblingHintRects.length === 0 && /* @__PURE__ */ jsx25(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
12420
+ siblingHintRects.length > 0 && !linkPopover && !isItemDragging && siblingHintRects.map((rect, i) => /* @__PURE__ */ jsx25(ItemInteractionLayer, { rect, state: "sibling-hint" }, `sibling-hint-${i}`)),
12421
+ isItemDragging && draggedItemRect && selectedElRef.current !== footerDragRef.current?.draggedEl && selectedElRef.current !== navDragRef.current?.draggedEl && /* @__PURE__ */ jsx25(ItemInteractionLayer, { rect: draggedItemRect, state: "dragging" }),
12422
+ isItemDragging && footerDropSlots.map((slot, i) => /* @__PURE__ */ jsx25(
12423
+ "div",
9913
12424
  {
9914
- rect: toolbarRect,
9915
- onAdd: handleAddTopLevelNavItem
9916
- }
9917
- ),
9918
- hoveredItemRect && !hoveredNavContainerRect && !siblingHintRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ jsx23(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
9919
- toolbarRect && (toolbarVariant === "link-action" || toolbarVariant === "select-frame") && /* @__PURE__ */ jsx23(
12425
+ className: "pointer-events-none fixed z-2147483646",
12426
+ style: {
12427
+ left: slot.left,
12428
+ top: slot.top,
12429
+ width: slot.width,
12430
+ height: slot.height
12431
+ },
12432
+ children: /* @__PURE__ */ jsx25(
12433
+ DropIndicator,
12434
+ {
12435
+ direction: slot.direction,
12436
+ state: activeFooterDropIndex === i ? "dragActive" : "dragIdle",
12437
+ className: "!h-full !w-full"
12438
+ }
12439
+ )
12440
+ },
12441
+ `footer-drop-${slot.direction}-${slot.columnIndex}-${slot.insertIndex}-${i}`
12442
+ )),
12443
+ isItemDragging && navDropSlots.map((slot, i) => /* @__PURE__ */ jsx25(
12444
+ "div",
12445
+ {
12446
+ className: "pointer-events-none fixed z-2147483646",
12447
+ style: {
12448
+ left: slot.left,
12449
+ top: slot.top,
12450
+ width: slot.width,
12451
+ height: slot.height
12452
+ },
12453
+ children: /* @__PURE__ */ jsx25(
12454
+ DropIndicator,
12455
+ {
12456
+ direction: slot.direction,
12457
+ state: activeNavDropIndex === i ? "dragActive" : "dragIdle",
12458
+ className: "!h-full !w-full"
12459
+ }
12460
+ )
12461
+ },
12462
+ `nav-drop-${slot.direction}-${slot.parentId ?? "root"}-${slot.insertIndex}-${i}`
12463
+ )),
12464
+ hoveredNavContainerRect && (toolbarVariant !== "select-frame" || isFooterFrameSelection) && !linkPopover && !isItemDragging && /* @__PURE__ */ jsx25(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
12465
+ hoveredItemRect && !linkPopover && !hoveredNavContainerRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ jsx25(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
12466
+ toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isNavbarLinksContainer(selectedElRef.current) && /* @__PURE__ */ jsx25(NavbarContainerChrome, { rect: toolbarRect, onAdd: handleAddTopLevelNavItem }),
12467
+ toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isFooterLinksContainer(selectedElRef.current) && /* @__PURE__ */ jsx25(FooterContainerChrome, { rect: toolbarRect, onAdd: handleAddFooterColumn }),
12468
+ toolbarRect && !linkPopover && (toolbarVariant === "link-action" || toolbarVariant === "select-frame") && /* @__PURE__ */ jsx25(
9920
12469
  ItemInteractionLayer,
9921
12470
  {
9922
- rect: toolbarRect,
12471
+ rect: isItemDragging && draggedItemRect && (footerDragRef.current?.wasSelected || navDragRef.current?.wasSelected) ? draggedItemRect : toolbarRect,
9923
12472
  elRef: glowElRef,
9924
12473
  state: isItemDragging ? "dragging" : resolveItemInteractionState(toolbarRect, parentScrollRef.current),
9925
- showHandle: toolbarVariant === "link-action" && Boolean(reorderHrefKey),
12474
+ showHandle: toolbarVariant === "link-action" && Boolean(reorderHrefKey) || toolbarVariant === "select-frame" && isFooterFrameSelection,
9926
12475
  dragDisabled: reorderDragDisabled,
9927
12476
  dragHandleLabel: reorderHrefKey ? `Reorder ${reorderHrefKey}` : "Reorder item",
9928
12477
  onDragHandleDragStart: handleItemDragStart,
9929
12478
  onDragHandleDragEnd: handleItemDragEnd,
9930
- toolbar: toolbarVariant === "link-action" && !isItemDragging ? /* @__PURE__ */ jsx23(
12479
+ onItemPointerDown: handleItemChromePointerDown,
12480
+ onItemClick: handleItemChromeClick,
12481
+ itemDragSurface: !isFooterFrameSelection,
12482
+ toolbar: toolbarVariant === "link-action" && !isItemDragging ? /* @__PURE__ */ jsx25(
9931
12483
  ItemActionToolbar,
9932
12484
  {
9933
12485
  onEditLink: openLinkPopoverForSelected,
@@ -9940,17 +12492,18 @@ function OhhwellsBridge() {
9940
12492
  ) : void 0
9941
12493
  }
9942
12494
  ),
9943
- toolbarRect && toolbarVariant === "rich-text" && /* @__PURE__ */ jsxs14(Fragment5, { children: [
9944
- /* @__PURE__ */ jsx23(
12495
+ toolbarRect && toolbarVariant === "rich-text" && !linkPopover && /* @__PURE__ */ jsxs14(Fragment5, { children: [
12496
+ /* @__PURE__ */ jsx25(
9945
12497
  EditGlowChrome,
9946
12498
  {
9947
12499
  rect: toolbarRect,
9948
12500
  elRef: glowElRef,
9949
12501
  reorderHrefKey,
9950
- dragDisabled: reorderDragDisabled
12502
+ dragDisabled: reorderDragDisabled,
12503
+ hideHandle: isItemDragging
9951
12504
  }
9952
12505
  ),
9953
- /* @__PURE__ */ jsx23(
12506
+ /* @__PURE__ */ jsx25(
9954
12507
  FloatingToolbar,
9955
12508
  {
9956
12509
  rect: toolbarRect,
@@ -9989,7 +12542,7 @@ function OhhwellsBridge() {
9989
12542
  ]
9990
12543
  }
9991
12544
  ),
9992
- toggleState && !linkPopover && /* @__PURE__ */ jsx23(
12545
+ toggleState && !linkPopover && /* @__PURE__ */ jsx25(
9993
12546
  StateToggle,
9994
12547
  {
9995
12548
  rect: toggleState.rect,
@@ -10005,25 +12558,29 @@ function OhhwellsBridge() {
10005
12558
  className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
10006
12559
  style: { top: sectionGap.y, transform: "translateY(-50%)" },
10007
12560
  children: [
10008
- /* @__PURE__ */ jsx23("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
10009
- /* @__PURE__ */ jsx23(
12561
+ /* @__PURE__ */ jsx25("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
12562
+ /* @__PURE__ */ jsx25(
10010
12563
  Badge,
10011
12564
  {
10012
12565
  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",
10013
12566
  onClick: () => {
10014
12567
  window.parent.postMessage(
10015
- { type: "ow:add-section", insertAfter: sectionGap.insertAfter, insertBefore: sectionGap.insertBefore },
12568
+ {
12569
+ type: "ow:add-section",
12570
+ insertAfter: sectionGap.insertAfter,
12571
+ insertBefore: sectionGap.insertBefore
12572
+ },
10016
12573
  "*"
10017
12574
  );
10018
12575
  },
10019
12576
  children: "Add Section"
10020
12577
  }
10021
12578
  ),
10022
- /* @__PURE__ */ jsx23("div", { className: "flex-1 bg-primary", style: { height: 3 } })
12579
+ /* @__PURE__ */ jsx25("div", { className: "flex-1 bg-primary", style: { height: 3 } })
10023
12580
  ]
10024
12581
  }
10025
12582
  ),
10026
- linkPopover && dialogPortalContainer ? /* @__PURE__ */ jsx23(
12583
+ linkPopover && dialogPortalContainer ? /* @__PURE__ */ jsx25(
10027
12584
  LinkPopover,
10028
12585
  {
10029
12586
  panelRef: linkPopoverPanelRef,
@@ -10044,48 +12601,6 @@ function OhhwellsBridge() {
10044
12601
  bridgeRoot
10045
12602
  ) : null;
10046
12603
  }
10047
-
10048
- // src/ui/drop-indicator.tsx
10049
- import * as React11 from "react";
10050
- import { jsx as jsx24 } from "react/jsx-runtime";
10051
- var dropIndicatorVariants = cva(
10052
- "ov-gap-line pointer-events-none shrink-0 transition-opacity duration-150",
10053
- {
10054
- variants: {
10055
- direction: {
10056
- vertical: "h-6 w-[3px]",
10057
- horizontal: "h-[3px] w-[200px]"
10058
- },
10059
- state: {
10060
- default: "opacity-0",
10061
- hover: "opacity-100",
10062
- dragIdle: "opacity-40",
10063
- dragActive: "opacity-100"
10064
- }
10065
- },
10066
- defaultVariants: {
10067
- direction: "vertical",
10068
- state: "default"
10069
- }
10070
- }
10071
- );
10072
- var DropIndicator = React11.forwardRef(
10073
- ({ className, direction, state, ...props }, ref) => {
10074
- return /* @__PURE__ */ jsx24(
10075
- "div",
10076
- {
10077
- ref,
10078
- "data-slot": "drop-indicator",
10079
- "data-direction": direction ?? "vertical",
10080
- "data-state": state ?? "default",
10081
- "aria-hidden": "true",
10082
- className: cn(dropIndicatorVariants({ direction, state }), className),
10083
- ...props
10084
- }
10085
- );
10086
- }
10087
- );
10088
- DropIndicator.displayName = "DropIndicator";
10089
12604
  export {
10090
12605
  CustomToolbar,
10091
12606
  CustomToolbarButton,