@ohhwells/bridge 0.1.41 → 0.1.42-next.100

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 React9, { useCallback as useCallback3, useEffect as useEffect7, useLayoutEffect as useLayoutEffect2, useRef as useRef5, 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: [
@@ -4242,7 +4298,8 @@ var CustomToolbar = React4.forwardRef(({ className, onMouseDown, ...props }, ref
4242
4298
  ref,
4243
4299
  "data-ohw-toolbar": "",
4244
4300
  className: cn(
4245
- "inline-flex items-center gap-1 rounded-lg border border-border bg-background p-0.5 font-sans whitespace-nowrap shadow-[0px_2px_4px_-2px_rgba(0,0,0,0.1),0px_4px_6px_-1px_rgba(0,0,0,0.1)]",
4301
+ // Figma: bg background, radius 8, gap-1, p-0.5, shadow-md no border
4302
+ "inline-flex h-8 items-center gap-1 rounded-[var(--radius,0.5rem)] bg-background p-0.5 font-sans whitespace-nowrap shadow-md",
4246
4303
  className
4247
4304
  ),
4248
4305
  onMouseDown: (e) => {
@@ -4270,8 +4327,8 @@ var CustomToolbarButton = React4.forwardRef(
4270
4327
  ref,
4271
4328
  type,
4272
4329
  className: cn(
4273
- "inline-flex shrink-0 items-center justify-center rounded p-1.5 transition-colors",
4274
- active ? "bg-primary text-primary-foreground" : "bg-transparent text-foreground hover:bg-muted disabled:cursor-not-allowed disabled:text-muted-foreground disabled:opacity-60",
4330
+ "inline-flex size-7 shrink-0 items-center justify-center rounded-[calc(var(--radius,0.5rem)-2px)] text-foreground transition-colors",
4331
+ active ? "bg-primary text-primary-foreground" : "bg-transparent hover:bg-muted disabled:cursor-not-allowed disabled:text-muted-foreground disabled:opacity-60",
4275
4332
  className
4276
4333
  ),
4277
4334
  ...props
@@ -4306,9 +4363,10 @@ var arrowClassBySide = {
4306
4363
  };
4307
4364
  function TooltipContent({
4308
4365
  className,
4309
- sideOffset = 6,
4366
+ sideOffset = 9,
4310
4367
  side = "bottom",
4311
4368
  children,
4369
+ hideArrow,
4312
4370
  ...props
4313
4371
  }) {
4314
4372
  const resolvedSide = side ?? "bottom";
@@ -4319,12 +4377,13 @@ function TooltipContent({
4319
4377
  side,
4320
4378
  sideOffset,
4321
4379
  className: cn(
4322
- "relative z-[2147483647] w-fit max-w-xs rounded-md bg-foreground px-3 py-1.5 text-xs text-background shadow-md",
4323
- 'before:pointer-events-none before:absolute before:size-2 before:rotate-45 before:bg-foreground before:content-[""]',
4324
- arrowClassBySide[resolvedSide],
4325
- "animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
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",
4381
+ "origin-[var(--radix-tooltip-content-transform-origin)] animate-in fade-in-0 zoom-in-95",
4382
+ "data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
4326
4383
  "data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2",
4327
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],
4328
4387
  className
4329
4388
  ),
4330
4389
  ...props,
@@ -4345,7 +4404,7 @@ function ToolbarActionTooltip({
4345
4404
  const button = /* @__PURE__ */ jsx8(CustomToolbarButton, { type: "button", "aria-label": label, disabled, ...buttonProps, children });
4346
4405
  return /* @__PURE__ */ jsxs3(Tooltip, { children: [
4347
4406
  /* @__PURE__ */ jsx8(TooltipTrigger, { asChild: true, children: disabled ? /* @__PURE__ */ jsx8("span", { className: "inline-flex", children: button }) : button }),
4348
- /* @__PURE__ */ jsx8(TooltipContent, { side, children: label })
4407
+ /* @__PURE__ */ jsx8(TooltipContent, { side, sideOffset: 9, children: label })
4349
4408
  ] });
4350
4409
  }
4351
4410
  function ItemActionToolbar({
@@ -4355,6 +4414,8 @@ function ItemActionToolbar({
4355
4414
  editLinkDisabled = false,
4356
4415
  addItemDisabled = true,
4357
4416
  moreDisabled = true,
4417
+ showAddItem = true,
4418
+ showMore = true,
4358
4419
  tooltipSide = "bottom"
4359
4420
  }) {
4360
4421
  return /* @__PURE__ */ jsx8(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ jsxs3(CustomToolbar, { children: [
@@ -4379,8 +4440,7 @@ function ItemActionToolbar({
4379
4440
  children: /* @__PURE__ */ jsx8(Link, { className: "size-4 shrink-0", "aria-hidden": true })
4380
4441
  }
4381
4442
  ),
4382
- /* @__PURE__ */ jsx8(CustomToolbarDivider, {}),
4383
- /* @__PURE__ */ jsx8(
4443
+ showAddItem ? /* @__PURE__ */ jsx8(
4384
4444
  ToolbarActionTooltip,
4385
4445
  {
4386
4446
  label: "Add item",
@@ -4396,9 +4456,8 @@ function ItemActionToolbar({
4396
4456
  },
4397
4457
  children: /* @__PURE__ */ jsx8(Plus, { className: "size-4 shrink-0", "aria-hidden": true })
4398
4458
  }
4399
- ),
4400
- /* @__PURE__ */ jsx8(CustomToolbarDivider, {}),
4401
- /* @__PURE__ */ jsx8(
4459
+ ) : null,
4460
+ showMore ? /* @__PURE__ */ jsx8(
4402
4461
  ToolbarActionTooltip,
4403
4462
  {
4404
4463
  label: "More",
@@ -4414,15 +4473,32 @@ function ItemActionToolbar({
4414
4473
  },
4415
4474
  children: /* @__PURE__ */ jsx8(MoreHorizontal, { className: "size-4 shrink-0", "aria-hidden": true })
4416
4475
  }
4417
- )
4476
+ ) : null
4418
4477
  ] }) });
4419
4478
  }
4420
4479
 
4421
4480
  // src/ui/item-interaction-layer.tsx
4481
+ import * as React5 from "react";
4422
4482
  import { jsx as jsx9, jsxs as jsxs4 } from "react/jsx-runtime";
4423
4483
  var PRIMARY = "var(--ohw-primary, #0885FE)";
4424
- var FOCUS_RING = "0 0 0 4px rgba(8, 133, 254, 0.12)";
4484
+ var FOCUS_RING = `0 0 0 4px color-mix(in srgb, ${PRIMARY} 12%, transparent)`;
4425
4485
  var DRAG_SHADOW = "0px 4px 6px -1px rgba(0, 0, 0, 0.1), 0px 2px 4px -2px rgba(0, 0, 0, 0.1)";
4486
+ var TOOLBAR_EDGE_MARGIN = 4;
4487
+ var SELECTION_CHROME_GAP = 4;
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
+ }
4426
4502
  function getChromeStyle(state) {
4427
4503
  switch (state) {
4428
4504
  case "hover":
@@ -4444,12 +4520,65 @@ function getChromeStyle(state) {
4444
4520
  case "dragging":
4445
4521
  return {
4446
4522
  border: `2px solid ${PRIMARY}`,
4447
- boxShadow: DRAG_SHADOW
4523
+ boxShadow: `${FOCUS_RING}, ${DRAG_SHADOW}`
4448
4524
  };
4449
4525
  default:
4450
4526
  return {};
4451
4527
  }
4452
4528
  }
4529
+ function ClampedToolbarSlot({
4530
+ placement,
4531
+ children
4532
+ }) {
4533
+ const slotRef = React5.useRef(null);
4534
+ React5.useLayoutEffect(() => {
4535
+ const slot = slotRef.current;
4536
+ if (!slot) return;
4537
+ const clamp = () => {
4538
+ const toolbar = slot.firstElementChild;
4539
+ if (!toolbar) return;
4540
+ const w = toolbar.offsetWidth;
4541
+ if (w <= 0) return;
4542
+ const parent = slot.offsetParent;
4543
+ if (!parent) return;
4544
+ const parentRect = parent.getBoundingClientRect();
4545
+ const centerX = parentRect.left + parentRect.width / 2;
4546
+ const half = w / 2;
4547
+ const clampedCenter = Math.max(
4548
+ TOOLBAR_EDGE_MARGIN + half,
4549
+ Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN - half)
4550
+ );
4551
+ const offsetX = clampedCenter - centerX;
4552
+ slot.style.transform = `translateX(calc(-50% + ${offsetX}px))`;
4553
+ };
4554
+ clamp();
4555
+ const ro = new ResizeObserver(clamp);
4556
+ ro.observe(slot);
4557
+ if (slot.firstElementChild) ro.observe(slot.firstElementChild);
4558
+ window.addEventListener("resize", clamp);
4559
+ return () => {
4560
+ ro.disconnect();
4561
+ window.removeEventListener("resize", clamp);
4562
+ };
4563
+ }, [placement, children]);
4564
+ return /* @__PURE__ */ jsx9(
4565
+ "div",
4566
+ {
4567
+ ref: slotRef,
4568
+ className: cn(
4569
+ "pointer-events-auto absolute left-1/2",
4570
+ placement === "top" ? "bottom-full" : "top-full"
4571
+ ),
4572
+ style: {
4573
+ marginBottom: placement === "top" ? TOOLBAR_STROKE_GAP : void 0,
4574
+ marginTop: placement === "bottom" ? TOOLBAR_STROKE_GAP : void 0,
4575
+ transform: "translateX(-50%)"
4576
+ },
4577
+ "data-ohw-item-toolbar-anchor": placement,
4578
+ children
4579
+ }
4580
+ );
4581
+ }
4453
4582
  function ItemInteractionLayer({
4454
4583
  rect,
4455
4584
  state,
@@ -4460,14 +4589,18 @@ function ItemInteractionLayer({
4460
4589
  dragHandleLabel = "Reorder item",
4461
4590
  onDragHandleDragStart,
4462
4591
  onDragHandleDragEnd,
4463
- chromeGap = 6,
4592
+ onItemPointerDown,
4593
+ onItemClick,
4594
+ itemDragSurface = true,
4595
+ chromeGap = SELECTION_CHROME_GAP,
4464
4596
  className
4465
4597
  }) {
4466
4598
  if (state === "default") return null;
4467
4599
  const isActive = state === "active-top" || state === "active-bottom";
4468
4600
  const isDragging = state === "dragging";
4469
4601
  const showToolbar = isActive && toolbar;
4470
- const showDragHandle = isActive && showHandle && !isDragging;
4602
+ const showDragHandle = (isActive || isDragging) && showHandle;
4603
+ const itemDragEnabled = itemDragSurface && isActive && showHandle && !isDragging && !dragDisabled;
4471
4604
  return /* @__PURE__ */ jsxs4(
4472
4605
  "div",
4473
4606
  {
@@ -4481,7 +4614,7 @@ function ItemInteractionLayer({
4481
4614
  left: rect.left - chromeGap,
4482
4615
  width: rect.width + chromeGap * 2,
4483
4616
  height: rect.height + chromeGap * 2,
4484
- zIndex: 2147483646
4617
+ zIndex: getChromeZIndex(state)
4485
4618
  },
4486
4619
  children: [
4487
4620
  /* @__PURE__ */ jsx9(
@@ -4492,11 +4625,29 @@ function ItemInteractionLayer({
4492
4625
  style: getChromeStyle(state)
4493
4626
  }
4494
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
+ ),
4495
4644
  showDragHandle && /* @__PURE__ */ jsx9(
4496
4645
  "div",
4497
4646
  {
4498
4647
  "data-ohw-drag-handle-container": "",
4499
- 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,
4500
4651
  children: /* @__PURE__ */ jsx9(
4501
4652
  DragHandle,
4502
4653
  {
@@ -4508,7 +4659,17 @@ function ItemInteractionLayer({
4508
4659
  e.preventDefault();
4509
4660
  return;
4510
4661
  }
4511
- 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);
4512
4673
  e.dataTransfer.effectAllowed = "move";
4513
4674
  onDragHandleDragStart?.(e);
4514
4675
  },
@@ -4517,33 +4678,19 @@ function ItemInteractionLayer({
4517
4678
  )
4518
4679
  }
4519
4680
  ),
4520
- showToolbar && state === "active-top" && /* @__PURE__ */ jsx9(
4521
- "div",
4522
- {
4523
- className: "pointer-events-auto absolute bottom-full left-1/2 mb-2 -translate-x-1/2",
4524
- "data-ohw-item-toolbar-anchor": "top",
4525
- children: toolbar
4526
- }
4527
- ),
4528
- showToolbar && state === "active-bottom" && /* @__PURE__ */ jsx9(
4529
- "div",
4530
- {
4531
- className: "pointer-events-auto absolute left-1/2 top-full mt-2 -translate-x-1/2",
4532
- "data-ohw-item-toolbar-anchor": "bottom",
4533
- children: toolbar
4534
- }
4535
- )
4681
+ showToolbar && state === "active-top" && /* @__PURE__ */ jsx9(ClampedToolbarSlot, { placement: "top", children: toolbar }),
4682
+ showToolbar && state === "active-bottom" && /* @__PURE__ */ jsx9(ClampedToolbarSlot, { placement: "bottom", children: toolbar })
4536
4683
  ]
4537
4684
  }
4538
4685
  );
4539
4686
  }
4540
4687
 
4541
4688
  // src/ui/MediaOverlay.tsx
4542
- import * as React6 from "react";
4689
+ import * as React7 from "react";
4543
4690
  import { Film, ImageIcon, Pause, Play, Volume2, VolumeX } from "lucide-react";
4544
4691
 
4545
4692
  // src/ui/button.tsx
4546
- import * as React5 from "react";
4693
+ import * as React6 from "react";
4547
4694
  import { Slot } from "radix-ui";
4548
4695
  import { jsx as jsx10 } from "react/jsx-runtime";
4549
4696
  var buttonVariants = cva(
@@ -4566,7 +4713,7 @@ var buttonVariants = cva(
4566
4713
  }
4567
4714
  }
4568
4715
  );
4569
- var Button = React5.forwardRef(
4716
+ var Button = React6.forwardRef(
4570
4717
  ({ className, variant, size, asChild = false, ...props }, ref) => {
4571
4718
  const Comp = asChild ? Slot.Root : "button";
4572
4719
  return /* @__PURE__ */ jsx10(
@@ -4618,7 +4765,7 @@ function MediaOverlay({
4618
4765
  onVideoSettingsChange
4619
4766
  }) {
4620
4767
  const { rect } = hover;
4621
- const skeletonRef = React6.useRef(null);
4768
+ const skeletonRef = React7.useRef(null);
4622
4769
  const isVideo = hover.elementType === "video";
4623
4770
  const autoplay = hover.videoAutoplay ?? true;
4624
4771
  const muted = hover.videoMuted ?? true;
@@ -4630,7 +4777,7 @@ function MediaOverlay({
4630
4777
  height: rect.height,
4631
4778
  zIndex: 2147483646
4632
4779
  };
4633
- React6.useEffect(() => {
4780
+ React7.useEffect(() => {
4634
4781
  if (!isUploading || !fadingOut || !skeletonRef.current) return;
4635
4782
  const anim = skeletonRef.current.animate([{ opacity: 1 }, { opacity: 0 }], {
4636
4783
  duration: MEDIA_UPLOAD_FADE_MS,
@@ -5035,7 +5182,7 @@ function scrollToHashSectionWhenReady(behavior = "smooth") {
5035
5182
  import { useEffect as useEffect6 } from "react";
5036
5183
 
5037
5184
  // src/ui/dialog.tsx
5038
- import * as React7 from "react";
5185
+ import * as React8 from "react";
5039
5186
  import { Dialog as DialogPrimitive } from "radix-ui";
5040
5187
  import { X } from "lucide-react";
5041
5188
  import { jsx as jsx12, jsxs as jsxs6 } from "react/jsx-runtime";
@@ -5063,7 +5210,7 @@ function DialogOverlay({
5063
5210
  }
5064
5211
  );
5065
5212
  }
5066
- var DialogContent = React7.forwardRef(
5213
+ var DialogContent = React8.forwardRef(
5067
5214
  ({ className, children, showCloseButton = true, container, ...props }, ref) => {
5068
5215
  const positionMode = container ? "absolute" : "fixed";
5069
5216
  return /* @__PURE__ */ jsxs6(DialogPortal, { container: container ?? void 0, children: [
@@ -5118,7 +5265,7 @@ function DialogFooter({
5118
5265
  }
5119
5266
  );
5120
5267
  }
5121
- var DialogTitle = React7.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx12(
5268
+ var DialogTitle = React8.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx12(
5122
5269
  DialogPrimitive.Title,
5123
5270
  {
5124
5271
  ref,
@@ -5130,7 +5277,7 @@ var DialogTitle = React7.forwardRef(({ className, ...props }, ref) => /* @__PURE
5130
5277
  }
5131
5278
  ));
5132
5279
  DialogTitle.displayName = DialogPrimitive.Title.displayName;
5133
- var DialogDescription = React7.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx12(
5280
+ var DialogDescription = React8.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx12(
5134
5281
  DialogPrimitive.Description,
5135
5282
  {
5136
5283
  ref,
@@ -5232,12 +5379,12 @@ function SectionTreeItem({
5232
5379
  }
5233
5380
 
5234
5381
  // src/ui/link-modal/UrlOrPageInput.tsx
5235
- import { useEffect as useEffect3, useId as useId2, useRef as useRef3, useState as useState3 } from "react";
5382
+ import { useEffect as useEffect3, useId as useId2, useRef as useRef4, useState as useState3 } from "react";
5236
5383
 
5237
5384
  // src/ui/input.tsx
5238
- import * as React8 from "react";
5385
+ import * as React9 from "react";
5239
5386
  import { jsx as jsx15 } from "react/jsx-runtime";
5240
- var Input = React8.forwardRef(
5387
+ var Input = React9.forwardRef(
5241
5388
  ({ className, type, ...props }, ref) => {
5242
5389
  return /* @__PURE__ */ jsx15(
5243
5390
  "input",
@@ -5301,8 +5448,8 @@ function UrlOrPageInput({
5301
5448
  urlError
5302
5449
  }) {
5303
5450
  const inputId = useId2();
5304
- const inputRef = useRef3(null);
5305
- const rootRef = useRef3(null);
5451
+ const inputRef = useRef4(null);
5452
+ const rootRef = useRef4(null);
5306
5453
  const [isFocused, setIsFocused] = useState3(false);
5307
5454
  useEffect3(() => {
5308
5455
  if (!dropdownOpen) return;
@@ -5512,7 +5659,7 @@ function LinkEditorPanel({ state, onClose }) {
5512
5659
  }
5513
5660
 
5514
5661
  // src/ui/link-modal/SectionPickerOverlay.tsx
5515
- import { useCallback, useEffect as useEffect4, useMemo as useMemo2, useRef as useRef4, useState as useState4 } from "react";
5662
+ import { useCallback as useCallback2, useEffect as useEffect4, useMemo as useMemo2, useRef as useRef5, useState as useState4 } from "react";
5516
5663
  import { createPortal } from "react-dom";
5517
5664
  import { ArrowLeft, Check } from "lucide-react";
5518
5665
  import { usePathname, useRouter } from "next/navigation";
@@ -5597,10 +5744,10 @@ function SectionPickerOverlay({
5597
5744
  const pathname = usePathname();
5598
5745
  const [selectedId, setSelectedId] = useState4(null);
5599
5746
  const [hoveredId, setHoveredId] = useState4(null);
5600
- const pointerRef = useRef4(null);
5601
- const pointerScreenRef = useRef4(null);
5602
- const iframeOffsetTopRef = useRef4(null);
5603
- const sectionIdsRef = useRef4([]);
5747
+ const pointerRef = useRef5(null);
5748
+ const pointerScreenRef = useRef5(null);
5749
+ const iframeOffsetTopRef = useRef5(null);
5750
+ const sectionIdsRef = useRef5([]);
5604
5751
  const [chromeClip, setChromeClip] = useState4(
5605
5752
  () => ({
5606
5753
  top: 0,
@@ -5643,7 +5790,7 @@ function SectionPickerOverlay({
5643
5790
  );
5644
5791
  sectionIdsRef.current = sectionIds;
5645
5792
  const rects = useSectionRects(sectionIdsKey, isOnTargetPage);
5646
- const applyHoverAt = useCallback(
5793
+ const applyHoverAt = useCallback2(
5647
5794
  (x, y, liveRects) => {
5648
5795
  const ids = sectionIdsRef.current;
5649
5796
  const map = liveRects ?? readSectionRects(ids);
@@ -5723,7 +5870,7 @@ function SectionPickerOverlay({
5723
5870
  window.removeEventListener("message", onPointerSync);
5724
5871
  };
5725
5872
  }, [rects, applyHoverAt]);
5726
- const handleSelect = useCallback(
5873
+ const handleSelect = useCallback2(
5727
5874
  (section) => {
5728
5875
  if (selectedId) return;
5729
5876
  setSelectedId(section.id);
@@ -5792,8 +5939,8 @@ function SectionPickerOverlay({
5792
5939
  !isOnTargetPage ? /* @__PURE__ */ jsx19(
5793
5940
  "div",
5794
5941
  {
5795
- className: "pointer-events-none fixed left-1/2 z-[1] -translate-x-1/2 rounded-md bg-background/90 px-3 py-2 text-sm text-muted-foreground shadow-sm",
5796
- style: { top: chromeClip.top + 64 },
5942
+ className: "pointer-events-none fixed left-1/2 z-[1] -translate-x-1/2 rounded-md px-3 py-2 text-sm text-muted-foreground shadow-sm",
5943
+ style: { top: chromeClip.top + 64, backgroundColor: "var(--ohw-background, #ffffff)" },
5797
5944
  children: "Loading page preview\u2026"
5798
5945
  }
5799
5946
  ) : null,
@@ -5854,7 +6001,7 @@ function SectionPickerOverlay({
5854
6001
  }
5855
6002
 
5856
6003
  // src/ui/link-modal/useLinkModalState.ts
5857
- import { useCallback as useCallback2, useEffect as useEffect5, useMemo as useMemo3, useState as useState5 } from "react";
6004
+ import { useCallback as useCallback3, useEffect as useEffect5, useMemo as useMemo3, useState as useState5 } from "react";
5858
6005
  function useLinkModalState({
5859
6006
  open,
5860
6007
  mode,
@@ -5873,7 +6020,7 @@ function useLinkModalState({
5873
6020
  const [step, setStep] = useState5("input");
5874
6021
  const [dropdownOpen, setDropdownOpen] = useState5(false);
5875
6022
  const [urlError, setUrlError] = useState5("");
5876
- const reset = useCallback2(() => {
6023
+ const reset = useCallback3(() => {
5877
6024
  setSearchValue("");
5878
6025
  setSelectedPage(null);
5879
6026
  setSelectedSection(null);
@@ -6218,9 +6365,155 @@ function shouldUseDevFixtures() {
6218
6365
  return new URLSearchParams(q).get("ohw-fixtures") === "1";
6219
6366
  }
6220
6367
 
6368
+ // src/lib/nav-tree.ts
6369
+ var MAX_LEVEL = { nav: 1 };
6370
+ var ROOT = null;
6371
+ function cloneForest(nodes) {
6372
+ return nodes.map((n) => ({ id: n.id, children: cloneForest(n.children) }));
6373
+ }
6374
+ function findNode(nodes, id, parentId = ROOT) {
6375
+ for (let i = 0; i < nodes.length; i++) {
6376
+ const node = nodes[i];
6377
+ if (node.id === id) return { node, parentId, index: i, siblings: nodes };
6378
+ const nested = findNode(node.children, id, node.id);
6379
+ if (nested) return nested;
6380
+ }
6381
+ return null;
6382
+ }
6383
+ function getChildrenOf(nodes, parentId) {
6384
+ if (parentId === ROOT) return nodes;
6385
+ const found = findNode(nodes, parentId);
6386
+ return found ? found.node.children : null;
6387
+ }
6388
+ function isDescendant(nodes, ancestorId, maybeDescendantId) {
6389
+ const found = findNode(nodes, ancestorId);
6390
+ if (!found) return false;
6391
+ return findNode(found.node.children, maybeDescendantId) !== null;
6392
+ }
6393
+ function levelOf(nodes, id) {
6394
+ const loc = findNode(nodes, id);
6395
+ if (!loc) return -1;
6396
+ if (loc.parentId === ROOT) return 0;
6397
+ return 1 + Math.max(0, levelOf(nodes, loc.parentId));
6398
+ }
6399
+ function insertedLevel(nodes, parentId) {
6400
+ if (parentId === ROOT) return 0;
6401
+ const parentLevel = levelOf(nodes, parentId);
6402
+ if (parentLevel < 0) return -1;
6403
+ return parentLevel + 1;
6404
+ }
6405
+ function takeNode(nodes, id) {
6406
+ const forest = cloneForest(nodes);
6407
+ const loc = findNode(forest, id);
6408
+ if (!loc) return null;
6409
+ const [taken] = loc.siblings.splice(loc.index, 1);
6410
+ if (!taken) return null;
6411
+ return { forest, taken };
6412
+ }
6413
+ function insertInto(forest, node, parentId, index) {
6414
+ const siblings = getChildrenOf(forest, parentId);
6415
+ if (!siblings) return false;
6416
+ const clamped = Math.max(0, Math.min(index, siblings.length));
6417
+ siblings.splice(clamped, 0, node);
6418
+ return true;
6419
+ }
6420
+ function normalize(nodes) {
6421
+ const walk = (list) => list.map((n) => {
6422
+ const children = walk(n.children);
6423
+ return { id: n.id, children };
6424
+ });
6425
+ return walk(nodes);
6426
+ }
6427
+ function moveNode(model, draggedId, target) {
6428
+ const { parentId, index: targetIndex } = target;
6429
+ if (parentId === draggedId) return null;
6430
+ if (parentId !== ROOT && isDescendant(model, draggedId, parentId)) return null;
6431
+ const nextLevel = insertedLevel(model, parentId);
6432
+ if (nextLevel < 0 || nextLevel > MAX_LEVEL.nav) return null;
6433
+ const draggedLoc = findNode(model, draggedId);
6434
+ if (!draggedLoc) return null;
6435
+ if (parentId !== ROOT && draggedLoc.node.children.length > 0) return null;
6436
+ const taken = takeNode(model, draggedId);
6437
+ if (!taken) return null;
6438
+ let insertIndex = targetIndex;
6439
+ if (draggedLoc.parentId === parentId && draggedLoc.index < targetIndex) {
6440
+ insertIndex -= 1;
6441
+ }
6442
+ if (!insertInto(taken.forest, taken.taken, parentId, insertIndex)) return null;
6443
+ const next = normalize(taken.forest);
6444
+ if (forestsEqual(model, next)) return null;
6445
+ return next;
6446
+ }
6447
+ function forestsEqual(a, b) {
6448
+ if (a.length !== b.length) return false;
6449
+ for (let i = 0; i < a.length; i++) {
6450
+ const left = a[i];
6451
+ const right = b[i];
6452
+ if (left.id !== right.id) return false;
6453
+ if (!forestsEqual(left.children, right.children)) return false;
6454
+ }
6455
+ return true;
6456
+ }
6457
+ function forestFromFlatOrder(order) {
6458
+ return order.map((id) => ({ id, children: [] }));
6459
+ }
6460
+ function serializeNavOrder(nodes) {
6461
+ const hasNesting = nodes.some((n) => n.children.length > 0);
6462
+ if (!hasNesting) {
6463
+ return JSON.stringify(nodes.map((n) => n.id));
6464
+ }
6465
+ return JSON.stringify(nodes);
6466
+ }
6467
+ function parseNavOrderJson(raw) {
6468
+ if (!raw) return null;
6469
+ try {
6470
+ const parsed = JSON.parse(raw);
6471
+ if (!Array.isArray(parsed)) return null;
6472
+ if (parsed.every((x) => typeof x === "string")) {
6473
+ return forestFromFlatOrder(parsed.filter((x) => typeof x === "string" && x.length > 0));
6474
+ }
6475
+ return parseNavNodeList(parsed);
6476
+ } catch {
6477
+ return null;
6478
+ }
6479
+ }
6480
+ function parseNavNodeList(raw) {
6481
+ const out = [];
6482
+ for (const item of raw) {
6483
+ if (typeof item === "string" && item.length > 0) {
6484
+ out.push({ id: item, children: [] });
6485
+ continue;
6486
+ }
6487
+ if (!item || typeof item !== "object") return null;
6488
+ const rec = item;
6489
+ if (typeof rec.id !== "string" || !rec.id) return null;
6490
+ const children = Array.isArray(rec.children) ? parseNavNodeList(rec.children) : [];
6491
+ if (!children) return null;
6492
+ out.push({ id: rec.id, children });
6493
+ }
6494
+ return out;
6495
+ }
6496
+ function flattenNavOrder(nodes) {
6497
+ const out = [];
6498
+ const walk = (list) => {
6499
+ for (const n of list) {
6500
+ out.push(n.id);
6501
+ walk(n.children);
6502
+ }
6503
+ };
6504
+ walk(nodes);
6505
+ return out;
6506
+ }
6507
+
6221
6508
  // src/lib/nav-items.ts
6222
6509
  var NAV_COUNT_KEY = "__ohw_nav_count";
6223
6510
  var NAV_ORDER_KEY = "__ohw_nav_order";
6511
+ var NAV_HREF_RE = /^nav-(\d+)-href$/;
6512
+ function isNavbarHrefKey(key) {
6513
+ if (!key) return false;
6514
+ if (key === "nav-book-href") return false;
6515
+ return NAV_HREF_RE.test(key);
6516
+ }
6224
6517
  function getLinkHref(el) {
6225
6518
  const key = el.getAttribute("data-ohw-href-key");
6226
6519
  if (key) {
@@ -6257,7 +6550,7 @@ function listNavbarItems() {
6257
6550
  }
6258
6551
  function parseNavIndexFromKey(key) {
6259
6552
  if (!key) return null;
6260
- const match = key.match(/^nav-(\d+)-href$/);
6553
+ const match = key.match(NAV_HREF_RE);
6261
6554
  if (!match) return null;
6262
6555
  return parseInt(match[1], 10);
6263
6556
  }
@@ -6280,17 +6573,6 @@ function getNavbarExistingTargets() {
6280
6573
  function getNavOrderFromDom() {
6281
6574
  return listNavbarItems().map((el) => el.getAttribute("data-ohw-href-key")).filter((key) => Boolean(key));
6282
6575
  }
6283
- function parseNavOrder(content) {
6284
- const raw = content[NAV_ORDER_KEY];
6285
- if (!raw) return null;
6286
- try {
6287
- const parsed = JSON.parse(raw);
6288
- if (!Array.isArray(parsed)) return null;
6289
- return parsed.filter((key) => typeof key === "string" && key.length > 0);
6290
- } catch {
6291
- return null;
6292
- }
6293
- }
6294
6576
  function findCounterpartByHrefKey(hrefKey, root) {
6295
6577
  return root.querySelector(`[data-ohw-href-key="${CSS.escape(hrefKey)}"]`);
6296
6578
  }
@@ -6354,19 +6636,47 @@ function insertNavbarItemDom(index, href, label, afterAnchor) {
6354
6636
  }
6355
6637
  return primary;
6356
6638
  }
6639
+ function navOrderKeysEqual(a, b) {
6640
+ if (a.length !== b.length) return false;
6641
+ for (let i = 0; i < a.length; i++) {
6642
+ if (a[i] !== b[i]) return false;
6643
+ }
6644
+ return true;
6645
+ }
6646
+ function getNavbarLinkKeysInContainer(container) {
6647
+ return Array.from(container.querySelectorAll("[data-ohw-href-key]")).filter(isNavbarLinkItem).map((el) => el.getAttribute("data-ohw-href-key")).filter((key) => Boolean(key));
6648
+ }
6357
6649
  function applyNavOrderToContainer(container, order) {
6650
+ const desired = order.filter((key, i) => order.indexOf(key) === i);
6651
+ const current = getNavbarLinkKeysInContainer(container);
6652
+ const extras = current.filter((key) => !desired.includes(key));
6653
+ const expected = [...desired.filter((key) => current.includes(key)), ...extras];
6654
+ const orderedEls = [];
6655
+ if (navOrderKeysEqual(current, expected)) return;
6656
+ if (current.length === expected.length && current.every((key, i) => key === expected[i])) {
6657
+ return;
6658
+ }
6659
+ const lastCurrentLinkEl = current.length > 0 ? findCounterpartByHrefKey(current[current.length - 1], container) : null;
6660
+ const anchor = lastCurrentLinkEl?.parentElement === container ? lastCurrentLinkEl.nextElementSibling : null;
6358
6661
  const seen = /* @__PURE__ */ new Set();
6359
- for (const hrefKey of order) {
6662
+ for (const hrefKey of desired) {
6360
6663
  if (seen.has(hrefKey)) continue;
6361
6664
  seen.add(hrefKey);
6362
6665
  const el = findCounterpartByHrefKey(hrefKey, container);
6363
- if (el) container.appendChild(el);
6666
+ if (el && isNavbarLinkItem(el)) orderedEls.push(el);
6364
6667
  }
6365
6668
  for (const el of container.querySelectorAll("[data-ohw-href-key]")) {
6366
6669
  if (!isNavbarLinkItem(el)) continue;
6367
6670
  const key = el.getAttribute("data-ohw-href-key");
6368
6671
  if (!key || seen.has(key)) continue;
6369
- container.appendChild(el);
6672
+ orderedEls.push(el);
6673
+ }
6674
+ for (const el of orderedEls) {
6675
+ if (anchor) {
6676
+ if (el.nextSibling !== anchor) container.insertBefore(el, anchor);
6677
+ } else if (container.lastElementChild !== el) {
6678
+ container.appendChild(el);
6679
+ }
6370
6680
  }
6371
6681
  }
6372
6682
  function applyNavOrder(order) {
@@ -6375,6 +6685,75 @@ function applyNavOrder(order) {
6375
6685
  const drawer = getNavbarDrawerContainer();
6376
6686
  if (drawer) applyNavOrderToContainer(drawer, order);
6377
6687
  }
6688
+ function applyChildrenOrder(parentHrefKey, childKeys, root) {
6689
+ const parent = findCounterpartByHrefKey(parentHrefKey, root);
6690
+ if (!parent) return;
6691
+ const group = parent.closest("[data-ohw-nav-group]");
6692
+ const childrenRoot = group?.querySelector(":scope > [data-ohw-nav-children]");
6693
+ if (!childrenRoot) return;
6694
+ const current = Array.from(childrenRoot.querySelectorAll(":scope > [data-ohw-href-key]")).map((el) => el.getAttribute("data-ohw-href-key")).filter((key) => Boolean(key));
6695
+ if (navOrderKeysEqual(current, childKeys)) return;
6696
+ for (const key of childKeys) {
6697
+ const el = findCounterpartByHrefKey(key, root);
6698
+ if (!el) continue;
6699
+ if (childrenRoot.lastElementChild !== el) {
6700
+ childrenRoot.appendChild(el);
6701
+ }
6702
+ }
6703
+ }
6704
+ function applyNavForest(forest) {
6705
+ if (forestsEqual(forest, getNavForestFromDom())) return;
6706
+ const rootKeys = forest.map((n) => n.id);
6707
+ applyNavOrder(rootKeys);
6708
+ const desktop = getNavbarDesktopContainer();
6709
+ const drawer = getNavbarDrawerContainer();
6710
+ for (const node of forest) {
6711
+ if (node.children.length === 0) continue;
6712
+ const childKeys = node.children.map((c) => c.id);
6713
+ if (desktop) applyChildrenOrder(node.id, childKeys, desktop);
6714
+ if (drawer) applyChildrenOrder(node.id, childKeys, drawer);
6715
+ }
6716
+ }
6717
+ function getNavForestFromDom() {
6718
+ const items = listNavbarItems();
6719
+ const nestedChildKeys = /* @__PURE__ */ new Set();
6720
+ const roots = [];
6721
+ for (const item of items) {
6722
+ const key = item.getAttribute("data-ohw-href-key");
6723
+ if (!key || !isNavbarHrefKey(key)) continue;
6724
+ if (item.closest("[data-ohw-nav-children]")) continue;
6725
+ const group = item.closest("[data-ohw-nav-group]");
6726
+ const childrenRoot = group?.querySelector(":scope > [data-ohw-nav-children]");
6727
+ const children = [];
6728
+ if (childrenRoot) {
6729
+ for (const child of childrenRoot.querySelectorAll(":scope > [data-ohw-href-key]")) {
6730
+ if (!isNavbarLinkItem(child)) continue;
6731
+ const childKey = child.getAttribute("data-ohw-href-key");
6732
+ if (!childKey || !isNavbarHrefKey(childKey)) continue;
6733
+ nestedChildKeys.add(childKey);
6734
+ children.push({ id: childKey, children: [] });
6735
+ }
6736
+ }
6737
+ roots.push({ id: key, children });
6738
+ }
6739
+ if (roots.length === 0) {
6740
+ return forestFromFlatOrder(
6741
+ getNavOrderFromDom().filter((k) => isNavbarHrefKey(k) && !nestedChildKeys.has(k))
6742
+ );
6743
+ }
6744
+ return roots;
6745
+ }
6746
+ function planNavItemMove(hrefKey, parentId, insertIndex) {
6747
+ const model = getNavForestFromDom();
6748
+ const next = moveNode(model, hrefKey, { parentId, index: insertIndex });
6749
+ if (!next) return null;
6750
+ if (forestsEqual(model, next)) return null;
6751
+ return {
6752
+ forest: next,
6753
+ orderJson: serializeNavOrder(next),
6754
+ flatOrder: flattenNavOrder(next)
6755
+ };
6756
+ }
6378
6757
  function collectNavbarIndicesFromContent(content) {
6379
6758
  const indices = /* @__PURE__ */ new Set();
6380
6759
  for (const key of Object.keys(content)) {
@@ -6402,8 +6781,10 @@ function reconcileNavbarItemsFromContent(content) {
6402
6781
  if (!href && !label) continue;
6403
6782
  insertNavbarItemDom(index, href ?? "/", label ?? "Untitled", null);
6404
6783
  }
6405
- const order = parseNavOrder(content);
6406
- if (order?.length) applyNavOrder(order);
6784
+ const orderForest = parseNavOrderJson(content[NAV_ORDER_KEY]);
6785
+ if (orderForest?.length) {
6786
+ applyNavForest(orderForest);
6787
+ }
6407
6788
  }
6408
6789
  function buildNavOrderAfterInsert(afterAnchor, newHrefKey) {
6409
6790
  const order = getNavOrderFromDom();
@@ -6443,112 +6824,1019 @@ function insertNavbarItem(href, label, afterAnchor = null) {
6443
6824
  };
6444
6825
  }
6445
6826
 
6446
- // src/ui/navbar-container-chrome.tsx
6447
- import { Plus as Plus2 } from "lucide-react";
6448
- import { jsx as jsx21 } from "react/jsx-runtime";
6449
- function NavbarContainerChrome({
6450
- rect,
6451
- onAdd
6452
- }) {
6453
- const chromeGap = 6;
6454
- return /* @__PURE__ */ jsx21(
6455
- "div",
6456
- {
6457
- "data-ohw-navbar-container-chrome": "",
6458
- "data-ohw-bridge": "",
6459
- className: "pointer-events-none fixed z-[2147483647]",
6460
- style: {
6461
- top: rect.top - chromeGap,
6462
- left: rect.left - chromeGap,
6463
- width: rect.width + chromeGap * 2,
6464
- height: rect.height + chromeGap * 2
6465
- },
6466
- children: /* @__PURE__ */ jsx21(
6467
- "button",
6468
- {
6469
- type: "button",
6470
- "data-ohw-navbar-add-button": "",
6471
- 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",
6472
- style: { top: "70%" },
6473
- "aria-label": "Add item",
6474
- onMouseDown: (e) => {
6475
- e.preventDefault();
6476
- e.stopPropagation();
6477
- },
6478
- onClick: (e) => {
6479
- e.preventDefault();
6480
- e.stopPropagation();
6481
- onAdd();
6482
- },
6483
- children: /* @__PURE__ */ jsx21(Plus2, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
6484
- }
6485
- )
6486
- }
6487
- );
6827
+ // src/lib/footer-items.ts
6828
+ var FOOTER_ORDER_KEY = "__ohw_footer_order";
6829
+ var FOOTER_HREF_RE = /^footer-(\d+)-(\d+)-href$/;
6830
+ function parseFooterHrefKey(key) {
6831
+ if (!key) return null;
6832
+ const match = key.match(FOOTER_HREF_RE);
6833
+ if (!match) return null;
6834
+ return { col: parseInt(match[1], 10), item: parseInt(match[2], 10) };
6488
6835
  }
6489
-
6490
- // src/ui/badge.tsx
6491
- import { jsx as jsx22 } from "react/jsx-runtime";
6492
- var badgeVariants = cva(
6493
- "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",
6494
- {
6495
- variants: {
6496
- variant: {
6497
- default: "border-transparent bg-primary text-primary-foreground",
6498
- secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
6499
- destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
6500
- outline: "text-foreground"
6501
- }
6502
- },
6503
- defaultVariants: {
6504
- variant: "default"
6505
- }
6506
- }
6507
- );
6508
- function Badge({ className, variant, ...props }) {
6509
- return /* @__PURE__ */ jsx22("div", { className: cn(badgeVariants({ variant }), className), ...props });
6836
+ function isFooterHrefKey(key) {
6837
+ return parseFooterHrefKey(key) !== null;
6510
6838
  }
6511
-
6512
- // src/OhhwellsBridge.tsx
6513
- import { Link as Link2 } from "lucide-react";
6514
- import { Fragment as Fragment5, jsx as jsx23, jsxs as jsxs13 } from "react/jsx-runtime";
6515
- var PRIMARY2 = "#0885FE";
6516
- var IMAGE_FADE_MS = 300;
6517
- function runOpacityFade(el, onDone) {
6518
- const anim = el.animate([{ opacity: 0 }, { opacity: 1 }], {
6519
- duration: IMAGE_FADE_MS,
6520
- easing: "ease",
6521
- fill: "forwards"
6522
- });
6523
- let finished = false;
6524
- const finish = () => {
6525
- if (finished) return;
6526
- finished = true;
6527
- anim.cancel();
6528
- el.style.opacity = "";
6529
- onDone();
6530
- };
6531
- anim.finished.then(finish).catch(finish);
6532
- window.setTimeout(finish, IMAGE_FADE_MS + 100);
6839
+ function getFooterRoot() {
6840
+ return document.querySelector('footer[data-ohw-section="footer"], footer');
6533
6841
  }
6534
- function fadeInImageElement(img, onReady) {
6535
- onReady();
6536
- img.style.opacity = "0";
6537
- runOpacityFade(img, () => {
6538
- img.style.opacity = "";
6842
+ function getFooterLinksContainer() {
6843
+ return document.querySelector("[data-ohw-footer-links]") ?? document.querySelector(".rb-footer-links");
6844
+ }
6845
+ function isFooterLinkAnchor(el) {
6846
+ if (!el.matches("[data-ohw-href-key]")) return false;
6847
+ if (!isFooterHrefKey(el.getAttribute("data-ohw-href-key"))) return false;
6848
+ return Boolean(el.querySelector('[data-ohw-editable="text"]'));
6849
+ }
6850
+ function listFooterColumns() {
6851
+ const root = getFooterLinksContainer() ?? getFooterRoot()?.querySelector(".rb-footer-links") ?? null;
6852
+ if (!root) return [];
6853
+ const explicit = Array.from(root.querySelectorAll(":scope > [data-ohw-footer-col]"));
6854
+ if (explicit.length > 0) return explicit;
6855
+ return Array.from(root.children).filter((el) => {
6856
+ if (!(el instanceof HTMLElement)) return false;
6857
+ return Array.from(el.querySelectorAll(":scope > [data-ohw-href-key]")).some(
6858
+ isFooterLinkAnchor
6859
+ );
6539
6860
  });
6540
6861
  }
6541
- function applyEditableImageSrc(img, url) {
6542
- img.removeAttribute("srcset");
6543
- img.removeAttribute("sizes");
6544
- img.src = url;
6862
+ function listFooterLinksInColumn(column) {
6863
+ return Array.from(column.querySelectorAll(":scope > [data-ohw-href-key]")).filter(
6864
+ isFooterLinkAnchor
6865
+ );
6545
6866
  }
6546
- function fadeInBgImage(el, url, onReady) {
6547
- const prevPos = el.style.position;
6548
- if (!prevPos || prevPos === "static") el.style.position = "relative";
6549
- const layer = document.createElement("div");
6550
- layer.setAttribute("data-ohw-bg-fade-layer", "");
6551
- Object.assign(layer.style, {
6867
+ function findFooterColumnForLink(anchor) {
6868
+ const columns = listFooterColumns();
6869
+ return columns.find((col) => col.contains(anchor)) ?? null;
6870
+ }
6871
+ function getFooterOrderFromDom() {
6872
+ return listFooterColumns().map(
6873
+ (col) => listFooterLinksInColumn(col).map((el) => el.getAttribute("data-ohw-href-key")).filter((key) => Boolean(key))
6874
+ );
6875
+ }
6876
+ function findFooterLinkByKey(hrefKey) {
6877
+ return document.querySelector(
6878
+ `footer [data-ohw-href-key="${CSS.escape(hrefKey)}"]`
6879
+ );
6880
+ }
6881
+ function syncFooterColumnIndices(columns) {
6882
+ columns.forEach((col, index) => {
6883
+ const next = String(index);
6884
+ if (col.getAttribute("data-ohw-footer-col") !== next) {
6885
+ col.setAttribute("data-ohw-footer-col", next);
6886
+ }
6887
+ });
6888
+ }
6889
+ function appendChildIfNeeded(parent, child) {
6890
+ if (parent.lastElementChild !== child) {
6891
+ parent.appendChild(child);
6892
+ }
6893
+ }
6894
+ function applyFooterOrder(order) {
6895
+ const container = getFooterLinksContainer();
6896
+ if (!container) return;
6897
+ const columns = listFooterColumns();
6898
+ if (columns.length === 0) return;
6899
+ const currentOrder = getFooterOrderFromDom();
6900
+ if (ordersEqual(order, currentOrder)) {
6901
+ syncFooterColumnIndices(columns);
6902
+ return;
6903
+ }
6904
+ const used = /* @__PURE__ */ new Set();
6905
+ const orderedCols = [];
6906
+ for (const colOrder of order) {
6907
+ let matched = null;
6908
+ for (const hrefKey of colOrder) {
6909
+ const link = findFooterLinkByKey(hrefKey);
6910
+ if (!link) continue;
6911
+ const owner = findFooterColumnForLink(link);
6912
+ if (owner && !used.has(owner)) {
6913
+ matched = owner;
6914
+ break;
6915
+ }
6916
+ }
6917
+ if (!matched) {
6918
+ matched = columns.find((col) => !used.has(col)) ?? null;
6919
+ }
6920
+ if (matched) {
6921
+ used.add(matched);
6922
+ orderedCols.push(matched);
6923
+ }
6924
+ }
6925
+ for (const col of columns) {
6926
+ if (!used.has(col)) orderedCols.push(col);
6927
+ }
6928
+ for (const col of orderedCols) {
6929
+ appendChildIfNeeded(container, col);
6930
+ }
6931
+ const freshColumns = listFooterColumns();
6932
+ syncFooterColumnIndices(freshColumns);
6933
+ for (let c = 0; c < order.length; c++) {
6934
+ const col = freshColumns[c];
6935
+ if (!col) continue;
6936
+ const colOrder = order[c];
6937
+ for (const hrefKey of colOrder) {
6938
+ const el = findFooterLinkByKey(hrefKey);
6939
+ if (!el) continue;
6940
+ if (el.parentElement !== col || col.lastElementChild !== el) {
6941
+ col.appendChild(el);
6942
+ }
6943
+ }
6944
+ }
6945
+ }
6946
+ function ordersEqual(a, b) {
6947
+ if (a.length !== b.length) return false;
6948
+ for (let i = 0; i < a.length; i++) {
6949
+ const left = a[i];
6950
+ const right = b[i];
6951
+ if (left.length !== right.length) return false;
6952
+ for (let j = 0; j < left.length; j++) {
6953
+ if (left[j] !== right[j]) return false;
6954
+ }
6955
+ }
6956
+ return true;
6957
+ }
6958
+ function planFooterLinkMove(hrefKey, targetColIndex, insertIndex) {
6959
+ const order = getFooterOrderFromDom();
6960
+ if (targetColIndex < 0 || targetColIndex >= order.length) return null;
6961
+ let fromCol = -1;
6962
+ let fromIdx = -1;
6963
+ for (let c = 0; c < order.length; c++) {
6964
+ const idx = order[c].indexOf(hrefKey);
6965
+ if (idx >= 0) {
6966
+ fromCol = c;
6967
+ fromIdx = idx;
6968
+ break;
6969
+ }
6970
+ }
6971
+ if (fromCol < 0) return null;
6972
+ const next = order.map((col) => [...col]);
6973
+ next[fromCol].splice(fromIdx, 1);
6974
+ let adjusted = insertIndex;
6975
+ if (fromCol === targetColIndex && fromIdx < insertIndex) adjusted -= 1;
6976
+ adjusted = Math.max(0, Math.min(adjusted, next[targetColIndex].length));
6977
+ next[targetColIndex].splice(adjusted, 0, hrefKey);
6978
+ if (ordersEqual(next, order)) return null;
6979
+ return next;
6980
+ }
6981
+ function planFooterColumnMove(fromIndex, toIndex) {
6982
+ const order = getFooterOrderFromDom();
6983
+ const columns = listFooterColumns();
6984
+ if (fromIndex < 0 || fromIndex >= columns.length || toIndex < 0 || toIndex > columns.length) {
6985
+ return null;
6986
+ }
6987
+ if (toIndex === fromIndex || toIndex === fromIndex + 1) return null;
6988
+ const nextOrder = order.map((col) => [...col]);
6989
+ const [movedOrder] = nextOrder.splice(fromIndex, 1);
6990
+ if (!movedOrder) return null;
6991
+ let adjusted = toIndex;
6992
+ if (fromIndex < toIndex) adjusted -= 1;
6993
+ adjusted = Math.max(0, Math.min(adjusted, nextOrder.length));
6994
+ nextOrder.splice(adjusted, 0, movedOrder);
6995
+ if (ordersEqual(nextOrder, order)) return null;
6996
+ return nextOrder;
6997
+ }
6998
+ function parseFooterOrder(content) {
6999
+ const raw = content[FOOTER_ORDER_KEY];
7000
+ if (!raw) return null;
7001
+ try {
7002
+ const parsed = JSON.parse(raw);
7003
+ if (!Array.isArray(parsed)) return null;
7004
+ return parsed.filter((col) => Array.isArray(col)).map((col) => col.filter((key) => typeof key === "string" && key.length > 0));
7005
+ } catch {
7006
+ return null;
7007
+ }
7008
+ }
7009
+ function reconcileFooterOrderFromContent(content) {
7010
+ const order = parseFooterOrder(content);
7011
+ if (!order?.length) return;
7012
+ const current = getFooterOrderFromDom();
7013
+ if (ordersEqual(order, current)) {
7014
+ syncFooterColumnIndices(listFooterColumns());
7015
+ return;
7016
+ }
7017
+ applyFooterOrder(order);
7018
+ }
7019
+ function isRowLayoutColumn(links) {
7020
+ if (links.length < 2) return false;
7021
+ const firstTop = links[0].getBoundingClientRect().top;
7022
+ return links.every((link) => Math.abs(link.getBoundingClientRect().top - firstTop) < 4);
7023
+ }
7024
+ function buildLinkDropSlots(column, columnIndex) {
7025
+ const links = listFooterLinksInColumn(column);
7026
+ const colRect = column.getBoundingClientRect();
7027
+ const slots = [];
7028
+ const barThickness = 3;
7029
+ if (links.length === 0) {
7030
+ slots.push({
7031
+ insertIndex: 0,
7032
+ columnIndex,
7033
+ left: colRect.left,
7034
+ top: colRect.top + colRect.height / 2 - barThickness / 2,
7035
+ width: colRect.width,
7036
+ height: barThickness,
7037
+ direction: "horizontal"
7038
+ });
7039
+ return slots;
7040
+ }
7041
+ if (isRowLayoutColumn(links)) {
7042
+ for (let i = 0; i <= links.length; i++) {
7043
+ let left;
7044
+ if (i === 0) {
7045
+ left = links[0].getBoundingClientRect().left - barThickness / 2;
7046
+ } else if (i === links.length) {
7047
+ const last = links[links.length - 1].getBoundingClientRect();
7048
+ left = last.right - barThickness / 2;
7049
+ } else {
7050
+ const prev = links[i - 1].getBoundingClientRect();
7051
+ const next = links[i].getBoundingClientRect();
7052
+ left = (prev.right + next.left) / 2 - barThickness / 2;
7053
+ }
7054
+ const heightRef = i === 0 ? links[0].getBoundingClientRect() : i === links.length ? links[links.length - 1].getBoundingClientRect() : links[i].getBoundingClientRect();
7055
+ slots.push({
7056
+ insertIndex: i,
7057
+ columnIndex,
7058
+ left,
7059
+ top: heightRef.top,
7060
+ width: barThickness,
7061
+ height: Math.max(heightRef.height, colRect.height * 0.8),
7062
+ direction: "vertical"
7063
+ });
7064
+ }
7065
+ return slots;
7066
+ }
7067
+ for (let i = 0; i <= links.length; i++) {
7068
+ let top;
7069
+ if (i === 0) {
7070
+ top = links[0].getBoundingClientRect().top - barThickness / 2;
7071
+ } else if (i === links.length) {
7072
+ const last = links[links.length - 1].getBoundingClientRect();
7073
+ top = last.bottom - barThickness / 2;
7074
+ } else {
7075
+ const prev = links[i - 1].getBoundingClientRect();
7076
+ const next = links[i].getBoundingClientRect();
7077
+ top = (prev.bottom + next.top) / 2 - barThickness / 2;
7078
+ }
7079
+ const widthRef = i === 0 ? links[0].getBoundingClientRect() : i === links.length ? links[links.length - 1].getBoundingClientRect() : links[i].getBoundingClientRect();
7080
+ slots.push({
7081
+ insertIndex: i,
7082
+ columnIndex,
7083
+ left: widthRef.left,
7084
+ top,
7085
+ width: Math.max(widthRef.width, colRect.width * 0.8),
7086
+ height: barThickness,
7087
+ direction: "horizontal"
7088
+ });
7089
+ }
7090
+ return slots;
7091
+ }
7092
+ function buildColumnDropSlots() {
7093
+ const columns = listFooterColumns();
7094
+ const slots = [];
7095
+ const barThickness = 3;
7096
+ if (columns.length === 0) return slots;
7097
+ for (let i = 0; i <= columns.length; i++) {
7098
+ let left;
7099
+ let height;
7100
+ let top;
7101
+ if (i === 0) {
7102
+ const first = columns[0].getBoundingClientRect();
7103
+ left = first.left - barThickness / 2;
7104
+ top = first.top;
7105
+ height = first.height;
7106
+ } else if (i === columns.length) {
7107
+ const last = columns[columns.length - 1].getBoundingClientRect();
7108
+ left = last.right - barThickness / 2;
7109
+ top = last.top;
7110
+ height = last.height;
7111
+ } else {
7112
+ const prev = columns[i - 1].getBoundingClientRect();
7113
+ const next = columns[i].getBoundingClientRect();
7114
+ left = (prev.right + next.left) / 2 - barThickness / 2;
7115
+ top = Math.min(prev.top, next.top);
7116
+ height = Math.max(prev.bottom, next.bottom) - top;
7117
+ }
7118
+ slots.push({
7119
+ insertIndex: i,
7120
+ columnIndex: i,
7121
+ left,
7122
+ top,
7123
+ width: barThickness,
7124
+ height: Math.max(height, 24),
7125
+ direction: "vertical"
7126
+ });
7127
+ }
7128
+ return slots;
7129
+ }
7130
+ function hitTestLinkDropSlot(clientX, clientY, draggedHrefKey) {
7131
+ const columns = listFooterColumns();
7132
+ let best = null;
7133
+ for (let c = 0; c < columns.length; c++) {
7134
+ const col = columns[c];
7135
+ const colRect = col.getBoundingClientRect();
7136
+ const inColX = clientX >= colRect.left - 24 && clientX <= colRect.right + 24;
7137
+ if (!inColX) continue;
7138
+ const slots = buildLinkDropSlots(col, c).filter((slot) => {
7139
+ const links = listFooterLinksInColumn(col);
7140
+ const fromIdx = links.findIndex(
7141
+ (el) => el.getAttribute("data-ohw-href-key") === draggedHrefKey
7142
+ );
7143
+ if (fromIdx < 0) return true;
7144
+ if (c === findColumnIndexForKey(draggedHrefKey) && (slot.insertIndex === fromIdx || slot.insertIndex === fromIdx + 1)) {
7145
+ return true;
7146
+ }
7147
+ return true;
7148
+ });
7149
+ for (const slot of slots) {
7150
+ const dist = slot.direction === "vertical" ? Math.abs(clientX - (slot.left + slot.width / 2)) : Math.abs(clientY - (slot.top + slot.height / 2));
7151
+ if (!best || dist < best.dist) best = { slot, dist };
7152
+ }
7153
+ }
7154
+ return best?.slot ?? null;
7155
+ }
7156
+ function findColumnIndexForKey(hrefKey) {
7157
+ const order = getFooterOrderFromDom();
7158
+ for (let c = 0; c < order.length; c++) {
7159
+ if (order[c].includes(hrefKey)) return c;
7160
+ }
7161
+ return -1;
7162
+ }
7163
+ function hitTestColumnDropSlot(clientX, _clientY) {
7164
+ const slots = buildColumnDropSlots();
7165
+ let best = null;
7166
+ for (const slot of slots) {
7167
+ const cx2 = slot.left + slot.width / 2;
7168
+ const dist = Math.abs(clientX - cx2);
7169
+ if (!best || dist < best.dist) best = { slot, dist };
7170
+ }
7171
+ return best?.slot ?? null;
7172
+ }
7173
+
7174
+ // src/lib/item-drag-interaction.ts
7175
+ function disableNativeHrefDrag(el) {
7176
+ if (el.draggable) el.draggable = false;
7177
+ if (el.getAttribute("draggable") !== "false") {
7178
+ el.setAttribute("draggable", "false");
7179
+ }
7180
+ }
7181
+ function clearTextSelection() {
7182
+ const sel = window.getSelection();
7183
+ if (sel && !sel.isCollapsed) sel.removeAllRanges();
7184
+ }
7185
+ function armItemPressDrag() {
7186
+ document.documentElement.setAttribute("data-ohw-footer-press-drag", "");
7187
+ }
7188
+ function lockItemDuringDrag() {
7189
+ document.documentElement.removeAttribute("data-ohw-footer-press-drag");
7190
+ document.documentElement.setAttribute("data-ohw-item-dragging", "");
7191
+ clearTextSelection();
7192
+ }
7193
+ function unlockItemDragInteraction() {
7194
+ document.documentElement.removeAttribute("data-ohw-footer-press-drag");
7195
+ document.documentElement.removeAttribute("data-ohw-item-dragging");
7196
+ clearTextSelection();
7197
+ }
7198
+ var armFooterPressDrag = armItemPressDrag;
7199
+ var lockFooterDuringDrag = lockItemDuringDrag;
7200
+ var unlockFooterDragInteraction = unlockItemDragInteraction;
7201
+
7202
+ // src/lib/nav-dnd.ts
7203
+ function listReorderableNavItems() {
7204
+ return listNavbarItems().filter((el) => {
7205
+ const key = el.getAttribute("data-ohw-href-key");
7206
+ return isNavbarHrefKey(key) && !el.closest("[data-ohw-nav-children]");
7207
+ });
7208
+ }
7209
+ function listNavChildren(parentHrefKey) {
7210
+ const items = listNavbarItems();
7211
+ const parent = items.find((el) => el.getAttribute("data-ohw-href-key") === parentHrefKey);
7212
+ if (!parent) return [];
7213
+ const group = parent.closest("[data-ohw-nav-group]");
7214
+ const childrenRoot = group?.querySelector(":scope > [data-ohw-nav-children]");
7215
+ if (!childrenRoot) return [];
7216
+ return Array.from(childrenRoot.querySelectorAll(":scope > [data-ohw-href-key]")).filter(
7217
+ (el) => isNavbarHrefKey(el.getAttribute("data-ohw-href-key"))
7218
+ );
7219
+ }
7220
+ function activeNavListContainer() {
7221
+ const desktop = getNavbarDesktopContainer();
7222
+ if (desktop && desktop.getClientRects().length > 0) {
7223
+ const style = window.getComputedStyle(desktop);
7224
+ if (style.display !== "none" && style.visibility !== "hidden") return desktop;
7225
+ }
7226
+ return getNavbarDrawerContainer();
7227
+ }
7228
+ function buildRootNavDropSlots() {
7229
+ const items = listReorderableNavItems();
7230
+ const slots = [];
7231
+ const barThickness = 3;
7232
+ if (items.length === 0) {
7233
+ const container = activeNavListContainer();
7234
+ if (!container) return slots;
7235
+ const rect = container.getBoundingClientRect();
7236
+ slots.push({
7237
+ insertIndex: 0,
7238
+ parentId: null,
7239
+ left: rect.left + rect.width / 2 - barThickness / 2,
7240
+ top: rect.top,
7241
+ width: barThickness,
7242
+ height: Math.max(rect.height, 24),
7243
+ direction: "vertical"
7244
+ });
7245
+ return slots;
7246
+ }
7247
+ for (let i = 0; i <= items.length; i++) {
7248
+ let left;
7249
+ let top;
7250
+ let height;
7251
+ if (i === 0) {
7252
+ const first = items[0].getBoundingClientRect();
7253
+ left = first.left - barThickness / 2;
7254
+ top = first.top;
7255
+ height = first.height;
7256
+ } else if (i === items.length) {
7257
+ const last = items[items.length - 1].getBoundingClientRect();
7258
+ left = last.right - barThickness / 2;
7259
+ top = last.top;
7260
+ height = last.height;
7261
+ } else {
7262
+ const prev = items[i - 1].getBoundingClientRect();
7263
+ const next = items[i].getBoundingClientRect();
7264
+ left = (prev.right + next.left) / 2 - barThickness / 2;
7265
+ top = Math.min(prev.top, next.top);
7266
+ height = Math.max(prev.bottom, next.bottom) - top;
7267
+ }
7268
+ slots.push({
7269
+ insertIndex: i,
7270
+ parentId: null,
7271
+ left,
7272
+ top,
7273
+ width: barThickness,
7274
+ height: Math.max(height, 24),
7275
+ direction: "vertical"
7276
+ });
7277
+ }
7278
+ return slots;
7279
+ }
7280
+ function buildChildNavDropSlots(parentHrefKey) {
7281
+ const children = listNavChildren(parentHrefKey);
7282
+ const slots = [];
7283
+ const barThickness = 3;
7284
+ if (children.length === 0) return slots;
7285
+ for (let i = 0; i <= children.length; i++) {
7286
+ let top;
7287
+ let width;
7288
+ let left;
7289
+ if (i === 0) {
7290
+ const first = children[0].getBoundingClientRect();
7291
+ top = first.top - barThickness / 2;
7292
+ left = first.left;
7293
+ width = first.width;
7294
+ } else if (i === children.length) {
7295
+ const last = children[children.length - 1].getBoundingClientRect();
7296
+ top = last.bottom - barThickness / 2;
7297
+ left = last.left;
7298
+ width = last.width;
7299
+ } else {
7300
+ const prev = children[i - 1].getBoundingClientRect();
7301
+ const next = children[i].getBoundingClientRect();
7302
+ top = (prev.bottom + next.top) / 2 - barThickness / 2;
7303
+ left = Math.min(prev.left, next.left);
7304
+ width = Math.max(prev.right, next.right) - left;
7305
+ }
7306
+ slots.push({
7307
+ insertIndex: i,
7308
+ parentId: parentHrefKey,
7309
+ left,
7310
+ top,
7311
+ width: Math.max(width, 40),
7312
+ height: barThickness,
7313
+ direction: "horizontal"
7314
+ });
7315
+ }
7316
+ return slots;
7317
+ }
7318
+ function buildAllNavDropSlots(draggedHrefKey) {
7319
+ const slots = [...buildRootNavDropSlots()];
7320
+ for (const item of listReorderableNavItems()) {
7321
+ const key = item.getAttribute("data-ohw-href-key");
7322
+ if (!key || key === draggedHrefKey) continue;
7323
+ const group = item.closest("[data-ohw-nav-group]");
7324
+ if (!group?.querySelector(":scope > [data-ohw-nav-children]")) continue;
7325
+ slots.push(...buildChildNavDropSlots(key));
7326
+ }
7327
+ const dragged = listNavbarItems().find((el) => el.getAttribute("data-ohw-href-key") === draggedHrefKey);
7328
+ const parentGroup = dragged?.closest("[data-ohw-nav-children]")?.closest("[data-ohw-nav-group]");
7329
+ const parentTrigger = parentGroup?.querySelector(":scope > [data-ohw-href-key]");
7330
+ const parentKey = parentTrigger?.getAttribute("data-ohw-href-key");
7331
+ if (parentKey && isNavbarHrefKey(parentKey)) {
7332
+ const existing = slots.some((s) => s.parentId === parentKey);
7333
+ if (!existing) slots.push(...buildChildNavDropSlots(parentKey));
7334
+ }
7335
+ return slots;
7336
+ }
7337
+ function hitTestNavDropSlot(clientX, clientY, draggedHrefKey) {
7338
+ const slots = buildAllNavDropSlots(draggedHrefKey);
7339
+ let best = null;
7340
+ for (const slot of slots) {
7341
+ const cx2 = slot.left + slot.width / 2;
7342
+ const cy = slot.top + slot.height / 2;
7343
+ 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;
7344
+ if (!best || dist < best.dist) best = { slot, dist };
7345
+ }
7346
+ return best?.slot ?? null;
7347
+ }
7348
+ function siblingRectsForNavDrag(draggedEl, activeParentId) {
7349
+ if (activeParentId) {
7350
+ return listNavChildren(activeParentId).filter((el) => el !== draggedEl).map((el) => el.getBoundingClientRect());
7351
+ }
7352
+ return listReorderableNavItems().filter((el) => el !== draggedEl).map((el) => el.getBoundingClientRect());
7353
+ }
7354
+
7355
+ // src/useNavItemDrag.ts
7356
+ import { useCallback as useCallback4, useEffect as useEffect7, useRef as useRef6, useState as useState6 } from "react";
7357
+ function useNavItemDrag({
7358
+ isEditMode,
7359
+ editContentRef,
7360
+ selectedElRef,
7361
+ activeElRef,
7362
+ footerDragRef,
7363
+ suppressNextClickRef,
7364
+ suppressClickUntilRef,
7365
+ siblingHintElRef,
7366
+ postToParentRef,
7367
+ deselectRef,
7368
+ selectRef,
7369
+ deactivateRef,
7370
+ setLinkPopover,
7371
+ linkPopoverOpenRef,
7372
+ setIsItemDragging,
7373
+ setDraggedItemRect,
7374
+ setSiblingHintRect,
7375
+ setSiblingHintRects,
7376
+ setToolbarRect,
7377
+ getNavigationItemAnchor: getNavigationItemAnchor2,
7378
+ isDragHandleDisabled: isDragHandleDisabled2,
7379
+ isNavbarButton: isNavbarButton3
7380
+ }) {
7381
+ const navDragRef = useRef6(null);
7382
+ const [navDropSlots, setNavDropSlots] = useState6([]);
7383
+ const [activeNavDropIndex, setActiveNavDropIndex] = useState6(null);
7384
+ const navPointerDragRef = useRef6(null);
7385
+ const clearNavDragVisuals = useCallback4(() => {
7386
+ navDragRef.current = null;
7387
+ setNavDropSlots([]);
7388
+ setActiveNavDropIndex(null);
7389
+ setDraggedItemRect(null);
7390
+ setSiblingHintRects([]);
7391
+ setIsItemDragging(false);
7392
+ unlockItemDragInteraction();
7393
+ }, [setDraggedItemRect, setIsItemDragging, setSiblingHintRects]);
7394
+ const refreshNavDragVisuals = useCallback4(
7395
+ (session, activeSlot, clientX, clientY) => {
7396
+ setDraggedItemRect(session.draggedEl.getBoundingClientRect());
7397
+ if (typeof clientX === "number" && typeof clientY === "number") {
7398
+ session.lastClientX = clientX;
7399
+ session.lastClientY = clientY;
7400
+ }
7401
+ session.activeSlot = activeSlot;
7402
+ const parentId = activeSlot?.parentId ?? null;
7403
+ setSiblingHintRects(siblingRectsForNavDrag(session.draggedEl, parentId));
7404
+ const slots = buildAllNavDropSlots(session.hrefKey);
7405
+ setNavDropSlots(slots);
7406
+ const activeIdx = activeSlot ? slots.findIndex(
7407
+ (s) => s.parentId === activeSlot.parentId && s.insertIndex === activeSlot.insertIndex
7408
+ ) : -1;
7409
+ setActiveNavDropIndex(activeIdx >= 0 ? activeIdx : null);
7410
+ },
7411
+ [setDraggedItemRect, setSiblingHintRects]
7412
+ );
7413
+ const refreshNavDragVisualsRef = useRef6(refreshNavDragVisuals);
7414
+ refreshNavDragVisualsRef.current = refreshNavDragVisuals;
7415
+ const commitNavDragRef = useRef6(() => {
7416
+ });
7417
+ const beginNavDragRef = useRef6(() => {
7418
+ });
7419
+ const beginNavDrag = useCallback4(
7420
+ (session) => {
7421
+ const rect = session.draggedEl.getBoundingClientRect();
7422
+ session.lastClientX = session.lastClientX || rect.left + rect.width / 2;
7423
+ session.lastClientY = session.lastClientY || rect.top + rect.height / 2;
7424
+ session.activeSlot = session.activeSlot ?? null;
7425
+ navDragRef.current = session;
7426
+ setIsItemDragging(true);
7427
+ lockItemDuringDrag();
7428
+ siblingHintElRef.current = null;
7429
+ setSiblingHintRect(null);
7430
+ if (session.wasSelected && selectedElRef.current === session.draggedEl) {
7431
+ setToolbarRect(rect);
7432
+ }
7433
+ const initialSlot = hitTestNavDropSlot(session.lastClientX, session.lastClientY, session.hrefKey);
7434
+ refreshNavDragVisuals(session, initialSlot, session.lastClientX, session.lastClientY);
7435
+ },
7436
+ [
7437
+ refreshNavDragVisuals,
7438
+ selectedElRef,
7439
+ setIsItemDragging,
7440
+ setSiblingHintRect,
7441
+ setToolbarRect,
7442
+ siblingHintElRef
7443
+ ]
7444
+ );
7445
+ beginNavDragRef.current = beginNavDrag;
7446
+ const commitNavDrag = useCallback4(
7447
+ (clientX, clientY) => {
7448
+ const session = navDragRef.current;
7449
+ if (!session) {
7450
+ clearNavDragVisuals();
7451
+ return;
7452
+ }
7453
+ const x = typeof clientX === "number" && (clientX !== 0 || clientY !== 0) ? clientX : session.lastClientX;
7454
+ const y = typeof clientY === "number" && (clientX !== 0 || clientY !== 0) ? clientY : session.lastClientY;
7455
+ const slot = session.activeSlot ?? hitTestNavDropSlot(x, y, session.hrefKey);
7456
+ const planned = slot != null ? planNavItemMove(session.hrefKey, slot.parentId, slot.insertIndex) : null;
7457
+ const wasSelected = session.wasSelected;
7458
+ const hrefKey = session.hrefKey;
7459
+ const applySelectionAfterDrop = () => {
7460
+ if (!wasSelected) {
7461
+ deselectRef.current();
7462
+ return;
7463
+ }
7464
+ const desktop = document.querySelector("[data-ohw-nav-container]");
7465
+ const drawer = document.querySelector("[data-ohw-nav-drawer]");
7466
+ 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)}"]`);
7467
+ if (link) {
7468
+ selectRef.current(link);
7469
+ return;
7470
+ }
7471
+ deselectRef.current();
7472
+ };
7473
+ if (planned) {
7474
+ editContentRef.current = {
7475
+ ...editContentRef.current,
7476
+ [NAV_ORDER_KEY]: planned.orderJson
7477
+ };
7478
+ applyNavForest(planned.forest);
7479
+ document.querySelectorAll(
7480
+ "nav [data-ohw-href-key], [data-ohw-nav-container] [data-ohw-href-key], [data-ohw-nav-drawer] [data-ohw-href-key]"
7481
+ ).forEach((el) => {
7482
+ if (isNavbarHrefKey(el.getAttribute("data-ohw-href-key"))) {
7483
+ disableNativeHrefDrag(el);
7484
+ }
7485
+ });
7486
+ postToParentRef.current({
7487
+ type: "ow:change",
7488
+ nodes: [{ key: NAV_ORDER_KEY, text: planned.orderJson }]
7489
+ });
7490
+ applySelectionAfterDrop();
7491
+ clearNavDragVisuals();
7492
+ requestAnimationFrame(() => {
7493
+ if (editContentRef.current[NAV_ORDER_KEY] === planned.orderJson) {
7494
+ applyNavForest(planned.forest);
7495
+ }
7496
+ applySelectionAfterDrop();
7497
+ requestAnimationFrame(applySelectionAfterDrop);
7498
+ });
7499
+ return;
7500
+ }
7501
+ applySelectionAfterDrop();
7502
+ clearNavDragVisuals();
7503
+ },
7504
+ [clearNavDragVisuals, deselectRef, editContentRef, postToParentRef, selectRef]
7505
+ );
7506
+ commitNavDragRef.current = commitNavDrag;
7507
+ const startNavLinkDrag = useCallback4(
7508
+ (anchor, clientX, clientY, wasSelected) => {
7509
+ if (footerDragRef.current) return false;
7510
+ const hrefKey = anchor.getAttribute("data-ohw-href-key");
7511
+ if (!hrefKey || !isNavbarHrefKey(hrefKey)) return false;
7512
+ if (isNavbarButton3(anchor) || isDragHandleDisabled2(anchor)) return false;
7513
+ beginNavDrag({
7514
+ hrefKey,
7515
+ wasSelected,
7516
+ draggedEl: anchor,
7517
+ lastClientX: clientX,
7518
+ lastClientY: clientY,
7519
+ activeSlot: null
7520
+ });
7521
+ return true;
7522
+ },
7523
+ [beginNavDrag, footerDragRef, isDragHandleDisabled2, isNavbarButton3]
7524
+ );
7525
+ const onNavDragOver = useCallback4(
7526
+ (e) => {
7527
+ const session = navDragRef.current;
7528
+ if (!session) return false;
7529
+ e.preventDefault();
7530
+ if (e.dataTransfer) e.dataTransfer.dropEffect = "move";
7531
+ const slot = hitTestNavDropSlot(e.clientX, e.clientY, session.hrefKey);
7532
+ refreshNavDragVisualsRef.current(session, slot, e.clientX, e.clientY);
7533
+ return true;
7534
+ },
7535
+ []
7536
+ );
7537
+ useEffect7(() => {
7538
+ if (!isEditMode) return;
7539
+ const THRESHOLD = 10;
7540
+ const resolveWasSelected = (el) => {
7541
+ if (selectedElRef.current === el) return true;
7542
+ const activeAnchor = activeElRef.current ? getNavigationItemAnchor2(activeElRef.current) : null;
7543
+ return activeAnchor === el;
7544
+ };
7545
+ const onPointerDown = (e) => {
7546
+ if (e.button !== 0) return;
7547
+ if (navDragRef.current || footerDragRef.current) return;
7548
+ const target = e.target;
7549
+ if (!target) return;
7550
+ if (target.closest(
7551
+ '[data-ohw-drag-handle-container], [data-slot="drag-handle"], [data-ohw-toolbar], [data-ohw-item-toolbar-anchor], [data-ohw-link-popover-root]'
7552
+ )) {
7553
+ return;
7554
+ }
7555
+ if (target.closest("[data-ohw-item-drag-surface]")) return;
7556
+ const anchor = getNavigationItemAnchor2(target);
7557
+ const hrefKey = anchor?.getAttribute("data-ohw-href-key") ?? null;
7558
+ if (!anchor || !isNavbarHrefKey(hrefKey)) return;
7559
+ if (isNavbarButton3(anchor) || isDragHandleDisabled2(anchor)) return;
7560
+ navPointerDragRef.current = {
7561
+ el: anchor,
7562
+ startX: e.clientX,
7563
+ startY: e.clientY,
7564
+ pointerId: e.pointerId,
7565
+ wasSelected: resolveWasSelected(anchor),
7566
+ started: false
7567
+ };
7568
+ };
7569
+ const onPointerMove = (e) => {
7570
+ const pending = navPointerDragRef.current;
7571
+ if (!pending) return;
7572
+ if (pending.started) {
7573
+ e.preventDefault();
7574
+ clearTextSelection();
7575
+ const session = navDragRef.current;
7576
+ if (!session) return;
7577
+ const slot = hitTestNavDropSlot(e.clientX, e.clientY, session.hrefKey);
7578
+ refreshNavDragVisualsRef.current(session, slot, e.clientX, e.clientY);
7579
+ return;
7580
+ }
7581
+ const dx = e.clientX - pending.startX;
7582
+ const dy = e.clientY - pending.startY;
7583
+ if (dx * dx + dy * dy < THRESHOLD * THRESHOLD) return;
7584
+ e.preventDefault();
7585
+ pending.started = true;
7586
+ armItemPressDrag();
7587
+ clearTextSelection();
7588
+ try {
7589
+ document.body.setPointerCapture(pending.pointerId);
7590
+ } catch {
7591
+ }
7592
+ if (linkPopoverOpenRef.current) setLinkPopover(null);
7593
+ if (activeElRef.current) deactivateRef.current();
7594
+ const key = pending.el.getAttribute("data-ohw-href-key");
7595
+ if (!key) return;
7596
+ beginNavDragRef.current({
7597
+ hrefKey: key,
7598
+ wasSelected: pending.wasSelected,
7599
+ draggedEl: pending.el,
7600
+ lastClientX: e.clientX,
7601
+ lastClientY: e.clientY,
7602
+ activeSlot: null
7603
+ });
7604
+ };
7605
+ const endPointerDrag = (e) => {
7606
+ const pending = navPointerDragRef.current;
7607
+ navPointerDragRef.current = null;
7608
+ try {
7609
+ if (document.body.hasPointerCapture(e.pointerId)) {
7610
+ document.body.releasePointerCapture(e.pointerId);
7611
+ }
7612
+ } catch {
7613
+ }
7614
+ if (!pending) return;
7615
+ if (!pending.started) {
7616
+ unlockItemDragInteraction();
7617
+ return;
7618
+ }
7619
+ suppressNextClickRef.current = true;
7620
+ suppressClickUntilRef.current = Date.now() + 500;
7621
+ commitNavDragRef.current(e.clientX, e.clientY);
7622
+ };
7623
+ const blockSelectStart = (e) => {
7624
+ if (navDragRef.current || navPointerDragRef.current?.started || document.documentElement.hasAttribute("data-ohw-footer-press-drag")) {
7625
+ e.preventDefault();
7626
+ }
7627
+ };
7628
+ const onDragEnd = () => {
7629
+ if (!navDragRef.current) return;
7630
+ suppressNextClickRef.current = true;
7631
+ suppressClickUntilRef.current = Date.now() + 500;
7632
+ commitNavDragRef.current();
7633
+ };
7634
+ document.addEventListener("pointerdown", onPointerDown, true);
7635
+ document.addEventListener("pointermove", onPointerMove, true);
7636
+ document.addEventListener("pointerup", endPointerDrag, true);
7637
+ document.addEventListener("pointercancel", endPointerDrag, true);
7638
+ document.addEventListener("selectstart", blockSelectStart, true);
7639
+ document.addEventListener("dragend", onDragEnd, true);
7640
+ return () => {
7641
+ document.removeEventListener("pointerdown", onPointerDown, true);
7642
+ document.removeEventListener("pointermove", onPointerMove, true);
7643
+ document.removeEventListener("pointerup", endPointerDrag, true);
7644
+ document.removeEventListener("pointercancel", endPointerDrag, true);
7645
+ document.removeEventListener("selectstart", blockSelectStart, true);
7646
+ document.removeEventListener("dragend", onDragEnd, true);
7647
+ unlockItemDragInteraction();
7648
+ };
7649
+ }, [
7650
+ activeElRef,
7651
+ deactivateRef,
7652
+ footerDragRef,
7653
+ getNavigationItemAnchor2,
7654
+ isDragHandleDisabled2,
7655
+ isEditMode,
7656
+ isNavbarButton3,
7657
+ linkPopoverOpenRef,
7658
+ selectedElRef,
7659
+ setLinkPopover,
7660
+ suppressNextClickRef
7661
+ ]);
7662
+ const armNavPressFromChrome = useCallback4(
7663
+ (selected, clientX, clientY, pointerId) => {
7664
+ const hrefKey = selected.getAttribute("data-ohw-href-key");
7665
+ if (!hrefKey || !isNavbarHrefKey(hrefKey)) return false;
7666
+ if (isNavbarButton3(selected) || isDragHandleDisabled2(selected)) return false;
7667
+ navPointerDragRef.current = {
7668
+ el: selected,
7669
+ startX: clientX,
7670
+ startY: clientY,
7671
+ pointerId,
7672
+ wasSelected: true,
7673
+ started: false
7674
+ };
7675
+ return true;
7676
+ },
7677
+ [isDragHandleDisabled2, isNavbarButton3]
7678
+ );
7679
+ return {
7680
+ navDragRef,
7681
+ navDropSlots,
7682
+ activeNavDropIndex,
7683
+ startNavLinkDrag,
7684
+ commitNavDrag,
7685
+ onNavDragOver,
7686
+ armNavPressFromChrome,
7687
+ clearNavDragVisuals,
7688
+ refreshNavDragVisualsRef
7689
+ };
7690
+ }
7691
+
7692
+ // src/ui/navbar-container-chrome.tsx
7693
+ import { Plus as Plus2 } from "lucide-react";
7694
+ import { jsx as jsx21 } from "react/jsx-runtime";
7695
+ function NavbarContainerChrome({
7696
+ rect,
7697
+ onAdd
7698
+ }) {
7699
+ const chromeGap = 6;
7700
+ return /* @__PURE__ */ jsx21(
7701
+ "div",
7702
+ {
7703
+ "data-ohw-navbar-container-chrome": "",
7704
+ "data-ohw-bridge": "",
7705
+ className: "pointer-events-none fixed z-[2147483647]",
7706
+ style: {
7707
+ top: rect.top - chromeGap,
7708
+ left: rect.left - chromeGap,
7709
+ width: rect.width + chromeGap * 2,
7710
+ height: rect.height + chromeGap * 2
7711
+ },
7712
+ children: /* @__PURE__ */ jsx21(
7713
+ "button",
7714
+ {
7715
+ type: "button",
7716
+ "data-ohw-navbar-add-button": "",
7717
+ 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",
7718
+ style: { top: "70%" },
7719
+ "aria-label": "Add item",
7720
+ onMouseDown: (e) => {
7721
+ e.preventDefault();
7722
+ e.stopPropagation();
7723
+ },
7724
+ onClick: (e) => {
7725
+ e.preventDefault();
7726
+ e.stopPropagation();
7727
+ onAdd();
7728
+ },
7729
+ children: /* @__PURE__ */ jsx21(Plus2, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
7730
+ }
7731
+ )
7732
+ }
7733
+ );
7734
+ }
7735
+
7736
+ // src/ui/drop-indicator.tsx
7737
+ import * as React10 from "react";
7738
+ import { jsx as jsx22 } from "react/jsx-runtime";
7739
+ var dropIndicatorVariants = cva(
7740
+ "ov-gap-line pointer-events-none shrink-0 transition-opacity duration-150",
7741
+ {
7742
+ variants: {
7743
+ direction: {
7744
+ vertical: "h-6 w-[3px]",
7745
+ horizontal: "h-[3px] w-[200px]"
7746
+ },
7747
+ state: {
7748
+ default: "opacity-0",
7749
+ hover: "opacity-100",
7750
+ dragIdle: "opacity-40",
7751
+ dragActive: "opacity-100"
7752
+ }
7753
+ },
7754
+ defaultVariants: {
7755
+ direction: "vertical",
7756
+ state: "default"
7757
+ }
7758
+ }
7759
+ );
7760
+ var DropIndicator = React10.forwardRef(
7761
+ ({ className, direction, state, ...props }, ref) => {
7762
+ return /* @__PURE__ */ jsx22(
7763
+ "div",
7764
+ {
7765
+ ref,
7766
+ "data-slot": "drop-indicator",
7767
+ "data-direction": direction ?? "vertical",
7768
+ "data-state": state ?? "default",
7769
+ "aria-hidden": "true",
7770
+ className: cn(dropIndicatorVariants({ direction, state }), className),
7771
+ ...props
7772
+ }
7773
+ );
7774
+ }
7775
+ );
7776
+ DropIndicator.displayName = "DropIndicator";
7777
+
7778
+ // src/ui/badge.tsx
7779
+ import { jsx as jsx23 } from "react/jsx-runtime";
7780
+ var badgeVariants = cva(
7781
+ "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",
7782
+ {
7783
+ variants: {
7784
+ variant: {
7785
+ default: "border-transparent bg-primary text-primary-foreground",
7786
+ secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
7787
+ destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
7788
+ outline: "text-foreground"
7789
+ }
7790
+ },
7791
+ defaultVariants: {
7792
+ variant: "default"
7793
+ }
7794
+ }
7795
+ );
7796
+ function Badge({ className, variant, ...props }) {
7797
+ return /* @__PURE__ */ jsx23("div", { className: cn(badgeVariants({ variant }), className), ...props });
7798
+ }
7799
+
7800
+ // src/OhhwellsBridge.tsx
7801
+ import { Link as Link2 } from "lucide-react";
7802
+ import { Fragment as Fragment5, jsx as jsx24, jsxs as jsxs13 } from "react/jsx-runtime";
7803
+ var PRIMARY2 = "#0885FE";
7804
+ var IMAGE_FADE_MS = 300;
7805
+ function runOpacityFade(el, onDone) {
7806
+ const anim = el.animate([{ opacity: 0 }, { opacity: 1 }], {
7807
+ duration: IMAGE_FADE_MS,
7808
+ easing: "ease",
7809
+ fill: "forwards"
7810
+ });
7811
+ let finished = false;
7812
+ const finish = () => {
7813
+ if (finished) return;
7814
+ finished = true;
7815
+ anim.cancel();
7816
+ el.style.opacity = "";
7817
+ onDone();
7818
+ };
7819
+ anim.finished.then(finish).catch(finish);
7820
+ window.setTimeout(finish, IMAGE_FADE_MS + 100);
7821
+ }
7822
+ function fadeInImageElement(img, onReady) {
7823
+ onReady();
7824
+ img.style.opacity = "0";
7825
+ runOpacityFade(img, () => {
7826
+ img.style.opacity = "";
7827
+ });
7828
+ }
7829
+ function applyEditableImageSrc(img, url) {
7830
+ img.removeAttribute("srcset");
7831
+ img.removeAttribute("sizes");
7832
+ img.src = url;
7833
+ }
7834
+ function fadeInBgImage(el, url, onReady) {
7835
+ const prevPos = el.style.position;
7836
+ if (!prevPos || prevPos === "static") el.style.position = "relative";
7837
+ const layer = document.createElement("div");
7838
+ layer.setAttribute("data-ohw-bg-fade-layer", "");
7839
+ Object.assign(layer.style, {
6552
7840
  position: "absolute",
6553
7841
  inset: "0",
6554
7842
  zIndex: "0",
@@ -6690,7 +7978,7 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
6690
7978
  const root = createRoot(container);
6691
7979
  flushSync(() => {
6692
7980
  root.render(
6693
- /* @__PURE__ */ jsx23(
7981
+ /* @__PURE__ */ jsx24(
6694
7982
  SchedulingWidget,
6695
7983
  {
6696
7984
  notifyOnConnect,
@@ -6752,7 +8040,20 @@ function isDragHandleDisabled(el) {
6752
8040
  return raw === "true" || raw === "";
6753
8041
  });
6754
8042
  }
6755
- function collectEditableNodes() {
8043
+ function canDragNavigationItem(anchor) {
8044
+ const { key, disabled } = getNavigationItemReorderState(anchor);
8045
+ return Boolean(key) && !disabled;
8046
+ }
8047
+ function syncNavigationDragCursorAttrs() {
8048
+ document.querySelectorAll("nav [data-ohw-href-key], footer [data-ohw-href-key]").forEach((el) => {
8049
+ if (!isNavigationItem(el) || !canDragNavigationItem(el)) {
8050
+ el.removeAttribute("data-ohw-can-drag");
8051
+ return;
8052
+ }
8053
+ el.setAttribute("data-ohw-can-drag", "");
8054
+ });
8055
+ }
8056
+ function collectEditableNodes(extraContent) {
6756
8057
  const nodes = Array.from(document.querySelectorAll("[data-ohw-editable]")).map((el) => {
6757
8058
  if (el.dataset.ohwEditable === "image") {
6758
8059
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -6780,6 +8081,14 @@ function collectEditableNodes() {
6780
8081
  if (!key) return;
6781
8082
  nodes.push({ key, type: "link", text: getLinkHref2(el) });
6782
8083
  });
8084
+ if (extraContent) {
8085
+ for (const key of [NAV_ORDER_KEY, FOOTER_ORDER_KEY, NAV_COUNT_KEY]) {
8086
+ const text = extraContent[key];
8087
+ if (typeof text === "string" && text.length > 0) {
8088
+ nodes.push({ key, type: "meta", text });
8089
+ }
8090
+ }
8091
+ }
6783
8092
  document.querySelectorAll('[data-ohw-editable="video"]').forEach((el) => {
6784
8093
  const key = el.dataset.ohwKey ?? "";
6785
8094
  const video = getVideoEl(el);
@@ -6893,6 +8202,11 @@ function getNavigationItemAnchor(el) {
6893
8202
  function isNavigationItem(el) {
6894
8203
  return getNavigationItemAnchor(el) !== null;
6895
8204
  }
8205
+ function listNavigationItems() {
8206
+ return Array.from(
8207
+ document.querySelectorAll("nav [data-ohw-href-key], footer [data-ohw-href-key]")
8208
+ ).filter((el) => isNavigationItem(el));
8209
+ }
6896
8210
  function getNavigationItemReorderState(anchor) {
6897
8211
  if (isNavbarButton2(anchor)) return { key: null, disabled: false };
6898
8212
  return getReorderHandleStateFromAnchor(anchor);
@@ -6921,18 +8235,20 @@ function getNavigationRoot(el) {
6921
8235
  if (explicit) return explicit;
6922
8236
  return el.closest("nav, footer, aside");
6923
8237
  }
8238
+ function countFooterNavItems(el) {
8239
+ return Array.from(el.querySelectorAll("[data-ohw-href-key]")).filter(isNavigationItem).length;
8240
+ }
6924
8241
  function findFooterItemGroup(item) {
8242
+ const explicit = item.closest("[data-ohw-footer-col], [data-ohw-footer-column]");
8243
+ if (explicit) return explicit;
6925
8244
  const footer = item.closest("footer");
6926
8245
  if (!footer) return null;
6927
8246
  let node = item.parentElement;
6928
8247
  while (node && node !== footer) {
6929
- const hasDirectNavItems = Array.from(
6930
- node.querySelectorAll(":scope > [data-ohw-href-key]")
6931
- ).some(isNavigationItem);
6932
- if (hasDirectNavItems) return node;
8248
+ if (countFooterNavItems(node) >= 2) return node;
6933
8249
  node = node.parentElement;
6934
8250
  }
6935
- return null;
8251
+ return footer;
6936
8252
  }
6937
8253
  function isNavigationRoot(el) {
6938
8254
  return el.hasAttribute("data-ohw-nav-root") || el.matches("nav, footer, aside");
@@ -6940,16 +8256,49 @@ function isNavigationRoot(el) {
6940
8256
  function isInferredFooterGroup(el) {
6941
8257
  const footer = el.closest("footer");
6942
8258
  if (!footer || el === footer) return false;
6943
- return Array.from(el.querySelectorAll(":scope > [data-ohw-href-key]")).some(isNavigationItem);
8259
+ if (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column")) return true;
8260
+ return countFooterNavItems(el) >= 2;
6944
8261
  }
6945
8262
  function isNavigationContainer(el) {
6946
- return el.hasAttribute("data-ohw-nav-container") || isNavigationRoot(el) || isInferredFooterGroup(el);
8263
+ return el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isNavigationRoot(el) || isInferredFooterGroup(el);
8264
+ }
8265
+ function isNavbarLinksContainer(el) {
8266
+ return el.hasAttribute("data-ohw-nav-container");
8267
+ }
8268
+ function getFooterColumn(el) {
8269
+ return el.closest("[data-ohw-footer-col], [data-ohw-footer-column]");
8270
+ }
8271
+ function resolveFooterColumnSelectionTarget(target, clientX, clientY) {
8272
+ if (getNavigationItemAnchor(target)) return null;
8273
+ const column = getFooterColumn(target);
8274
+ if (!column) return null;
8275
+ if (isPointOverNavItem(column, clientX, clientY)) return null;
8276
+ return column;
6947
8277
  }
6948
8278
  function isPointOverNavigation(x, y) {
8279
+ const roots = /* @__PURE__ */ new Set();
6949
8280
  const navRoot = document.querySelector("[data-ohw-nav-root]") ?? document.querySelector("nav");
6950
- if (!navRoot) return false;
6951
- const r2 = navRoot.getBoundingClientRect();
6952
- return x >= r2.left && x <= r2.right && y >= r2.top && y <= r2.bottom;
8281
+ if (navRoot) roots.add(navRoot);
8282
+ const footer = document.querySelector("footer");
8283
+ if (footer) roots.add(footer);
8284
+ for (const root of roots) {
8285
+ const r2 = root.getBoundingClientRect();
8286
+ if (x >= r2.left && x <= r2.right && y >= r2.top && y <= r2.bottom) return true;
8287
+ }
8288
+ return false;
8289
+ }
8290
+ function findHoveredNavOrFooterContainer(x, y) {
8291
+ const candidates = [
8292
+ ...document.querySelectorAll("[data-ohw-nav-container]"),
8293
+ ...listFooterColumns()
8294
+ ];
8295
+ for (const container of candidates) {
8296
+ const r2 = container.getBoundingClientRect();
8297
+ if (x < r2.left || x > r2.right || y < r2.top || y > r2.bottom) continue;
8298
+ if (isPointOverNavItem(container, x, y)) continue;
8299
+ return container;
8300
+ }
8301
+ return null;
6953
8302
  }
6954
8303
  function isPointOverNavItem(container, x, y) {
6955
8304
  return Array.from(container.querySelectorAll("[data-ohw-href-key]")).some((el) => {
@@ -6982,36 +8331,63 @@ function getNavigationSelectionParent(el) {
6982
8331
  if (isNavigationItem(el)) {
6983
8332
  const explicit = el.closest("[data-ohw-nav-container]");
6984
8333
  if (explicit) return explicit;
8334
+ const footerColumn = getFooterColumn(el);
8335
+ if (footerColumn) return footerColumn;
6985
8336
  const footerGroup = findFooterItemGroup(el);
6986
8337
  if (footerGroup) return footerGroup;
6987
8338
  return getNavigationRoot(el);
6988
8339
  }
6989
- if (el.hasAttribute("data-ohw-nav-container") || isInferredFooterGroup(el)) {
8340
+ if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup(el)) {
6990
8341
  return getNavigationRoot(el);
6991
8342
  }
6992
8343
  return null;
6993
8344
  }
8345
+ function collectNavigationItemSiblingHintRects(selected) {
8346
+ if (!isNavigationItem(selected)) return [];
8347
+ const footerColumn = getFooterColumn(selected);
8348
+ if (footerColumn) {
8349
+ return listFooterLinksInColumn(footerColumn).filter((link) => link !== selected).map((link) => link.getBoundingClientRect());
8350
+ }
8351
+ const navContainer = selected.closest("[data-ohw-nav-container], [data-ohw-nav-drawer]");
8352
+ if (navContainer) {
8353
+ return Array.from(navContainer.querySelectorAll("[data-ohw-href-key]")).filter((el) => isNavigationItem(el) && el !== selected).map((el) => el.getBoundingClientRect());
8354
+ }
8355
+ return listNavigationItems().filter((el) => el !== selected).map((el) => el.getBoundingClientRect());
8356
+ }
6994
8357
  function placeCaretAtPoint(el, x, y) {
6995
8358
  const selection = window.getSelection();
6996
8359
  if (!selection) return;
6997
- if (typeof document.caretRangeFromPoint === "function") {
6998
- const range = document.caretRangeFromPoint(x, y);
6999
- if (range && el.contains(range.startContainer)) {
7000
- selection.removeAllRanges();
7001
- selection.addRange(range);
7002
- return;
8360
+ const overlays = Array.from(
8361
+ document.querySelectorAll(
8362
+ "[data-ohw-item-drag-surface], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-ohw-item-toolbar-anchor]"
8363
+ )
8364
+ );
8365
+ const prevPointerEvents = overlays.map((node) => node.style.pointerEvents);
8366
+ for (const node of overlays) node.style.pointerEvents = "none";
8367
+ try {
8368
+ if (typeof document.caretRangeFromPoint === "function") {
8369
+ const range = document.caretRangeFromPoint(x, y);
8370
+ if (range && el.contains(range.startContainer)) {
8371
+ selection.removeAllRanges();
8372
+ selection.addRange(range);
8373
+ return;
8374
+ }
7003
8375
  }
7004
- }
7005
- const caretPositionFromPoint = document.caretPositionFromPoint;
7006
- if (typeof caretPositionFromPoint === "function") {
7007
- const position = caretPositionFromPoint(x, y);
7008
- if (position && el.contains(position.offsetNode)) {
7009
- const range = document.createRange();
7010
- range.setStart(position.offsetNode, position.offset);
7011
- range.collapse(true);
7012
- selection.removeAllRanges();
7013
- selection.addRange(range);
8376
+ const caretPositionFromPoint = document.caretPositionFromPoint;
8377
+ if (typeof caretPositionFromPoint === "function") {
8378
+ const position = caretPositionFromPoint(x, y);
8379
+ if (position && el.contains(position.offsetNode)) {
8380
+ const range = document.createRange();
8381
+ range.setStart(position.offsetNode, position.offset);
8382
+ range.collapse(true);
8383
+ selection.removeAllRanges();
8384
+ selection.addRange(range);
8385
+ }
7014
8386
  }
8387
+ } finally {
8388
+ overlays.forEach((node, i) => {
8389
+ node.style.pointerEvents = prevPointerEvents[i] ?? "";
8390
+ });
7015
8391
  }
7016
8392
  }
7017
8393
  function selectAllTextInEditable(el) {
@@ -7153,6 +8529,9 @@ var ICONS = {
7153
8529
  insertUnorderedList: '<line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/>',
7154
8530
  insertOrderedList: '<line x1="10" y1="6" x2="21" y2="6"/><line x1="10" y1="12" x2="21" y2="12"/><line x1="10" y1="18" x2="21" y2="18"/><path d="M4 6h1v4"/><path d="M4 10h2"/><path d="M6 18H4c0-1 2-2 2-3s-1-1.5-2-1"/>'
7155
8531
  };
8532
+ var SELECTION_CHROME_GAP2 = 4;
8533
+ var TOOLBAR_STROKE_GAP2 = 4;
8534
+ var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
7156
8535
  var TOOLBAR_GROUPS = [
7157
8536
  [
7158
8537
  { cmd: "bold", title: "Bold" },
@@ -7174,9 +8553,10 @@ function EditGlowChrome({
7174
8553
  rect,
7175
8554
  elRef,
7176
8555
  reorderHrefKey,
7177
- dragDisabled = false
8556
+ dragDisabled = false,
8557
+ hideHandle = false
7178
8558
  }) {
7179
- const GAP = 6;
8559
+ const GAP = SELECTION_CHROME_GAP2;
7180
8560
  return /* @__PURE__ */ jsxs13(
7181
8561
  "div",
7182
8562
  {
@@ -7192,7 +8572,7 @@ function EditGlowChrome({
7192
8572
  zIndex: 2147483646
7193
8573
  },
7194
8574
  children: [
7195
- /* @__PURE__ */ jsx23(
8575
+ /* @__PURE__ */ jsx24(
7196
8576
  "div",
7197
8577
  {
7198
8578
  style: {
@@ -7200,12 +8580,12 @@ function EditGlowChrome({
7200
8580
  inset: 0,
7201
8581
  border: "2px solid var(--ohw-primary, #0885FE)",
7202
8582
  borderRadius: 8,
7203
- boxShadow: "0 0 0 4px rgba(8, 133, 254, 0.12)",
8583
+ boxShadow: "0 0 0 4px color-mix(in srgb, var(--ohw-primary, #0885FE) 12%, transparent)",
7204
8584
  pointerEvents: "none"
7205
8585
  }
7206
8586
  }
7207
8587
  ),
7208
- reorderHrefKey && /* @__PURE__ */ jsx23(
8588
+ reorderHrefKey && !hideHandle && /* @__PURE__ */ jsx24(
7209
8589
  "div",
7210
8590
  {
7211
8591
  "data-ohw-drag-handle-container": "",
@@ -7217,7 +8597,7 @@ function EditGlowChrome({
7217
8597
  transform: "translate(calc(-100% - 7px), -50%)",
7218
8598
  pointerEvents: dragDisabled ? "none" : "auto"
7219
8599
  },
7220
- children: /* @__PURE__ */ jsx23(
8600
+ children: /* @__PURE__ */ jsx24(
7221
8601
  DragHandle,
7222
8602
  {
7223
8603
  "aria-label": `Reorder ${reorderHrefKey}`,
@@ -7248,6 +8628,7 @@ function applyVisibleViewport(el, parentScroll) {
7248
8628
  el.style.top = `${top}px`;
7249
8629
  el.style.height = `${height}px`;
7250
8630
  }
8631
+ var TOOLBAR_EDGE_MARGIN2 = 4;
7251
8632
  function clampToolbarToClip(top, transform, rect, clip, approxH, gap) {
7252
8633
  const isAbove = transform.includes("translateY(-100%)");
7253
8634
  let visualTop = isAbove ? top - approxH : top;
@@ -7271,10 +8652,16 @@ function clampToolbarToClip(top, transform, rect, clip, approxH, gap) {
7271
8652
  const pinnedTop = Math.min(maxBottom - approxH, Math.max(minTop, clip.top + gap));
7272
8653
  return { top: pinnedTop + approxH, transform: "translateX(-50%) translateY(-100%)" };
7273
8654
  }
7274
- function calcToolbarPos(rect, parentScroll, approxW = 306) {
7275
- const GAP = 8;
8655
+ function clampToolbarLeft(centerX, toolbarWidth) {
8656
+ const half = toolbarWidth / 2;
8657
+ return Math.max(
8658
+ TOOLBAR_EDGE_MARGIN2 + half,
8659
+ Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN2 - half)
8660
+ );
8661
+ }
8662
+ function calcToolbarPos(rect, parentScroll, toolbarW = 306) {
8663
+ const GAP = TOOLBAR_OFFSET_FROM_ELEMENT;
7276
8664
  const APPROX_H = 32;
7277
- const APPROX_W = approxW;
7278
8665
  const clip = getIframeVisibleClip(parentScroll);
7279
8666
  const clipTop = clip?.top ?? 0;
7280
8667
  const canvasTopInIframe = parentScroll != null ? parentScroll.headerH - parentScroll.iframeOffsetTop : 0;
@@ -7304,7 +8691,7 @@ function calcToolbarPos(rect, parentScroll, approxW = 306) {
7304
8691
  transform = clamped.transform;
7305
8692
  }
7306
8693
  const rawLeft = rect.left + rect.width / 2;
7307
- const left = Math.max(GAP + APPROX_W / 2, Math.min(rawLeft, window.innerWidth - GAP - APPROX_W / 2));
8694
+ const left = clampToolbarLeft(rawLeft, toolbarW);
7308
8695
  return { top, left, transform };
7309
8696
  }
7310
8697
  function resolveItemInteractionState(rect, parentScroll) {
@@ -7320,11 +8707,37 @@ function FloatingToolbar({
7320
8707
  showEditLink,
7321
8708
  onEditLink
7322
8709
  }) {
7323
- const { top, left, transform } = calcToolbarPos(rect, parentScroll, 330);
7324
- return /* @__PURE__ */ jsx23(
8710
+ const localRef = React11.useRef(null);
8711
+ const [measuredW, setMeasuredW] = React11.useState(330);
8712
+ const setRefs = React11.useCallback(
8713
+ (node) => {
8714
+ localRef.current = node;
8715
+ if (typeof elRef === "function") elRef(node);
8716
+ else if (elRef) elRef.current = node;
8717
+ if (node) {
8718
+ const w = node.offsetWidth;
8719
+ if (w > 0) setMeasuredW(w);
8720
+ }
8721
+ },
8722
+ [elRef]
8723
+ );
8724
+ React11.useLayoutEffect(() => {
8725
+ const node = localRef.current;
8726
+ if (!node) return;
8727
+ const update = () => {
8728
+ const w = node.offsetWidth;
8729
+ if (w > 0) setMeasuredW(w);
8730
+ };
8731
+ update();
8732
+ const ro = new ResizeObserver(update);
8733
+ ro.observe(node);
8734
+ return () => ro.disconnect();
8735
+ }, [showEditLink, activeCommands]);
8736
+ const { top, left, transform } = calcToolbarPos(rect, parentScroll, measuredW);
8737
+ return /* @__PURE__ */ jsx24(
7325
8738
  "div",
7326
8739
  {
7327
- ref: elRef,
8740
+ ref: setRefs,
7328
8741
  style: {
7329
8742
  position: "fixed",
7330
8743
  top,
@@ -7334,11 +8747,11 @@ function FloatingToolbar({
7334
8747
  pointerEvents: "auto"
7335
8748
  },
7336
8749
  children: /* @__PURE__ */ jsxs13(CustomToolbar, { children: [
7337
- TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ jsxs13(React9.Fragment, { children: [
7338
- gi > 0 && /* @__PURE__ */ jsx23(CustomToolbarDivider, {}),
8750
+ TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ jsxs13(React11.Fragment, { children: [
8751
+ gi > 0 && /* @__PURE__ */ jsx24(CustomToolbarDivider, {}),
7339
8752
  btns.map((btn) => {
7340
8753
  const isActive = activeCommands.has(btn.cmd);
7341
- return /* @__PURE__ */ jsx23(
8754
+ return /* @__PURE__ */ jsx24(
7342
8755
  CustomToolbarButton,
7343
8756
  {
7344
8757
  title: btn.title,
@@ -7347,7 +8760,7 @@ function FloatingToolbar({
7347
8760
  e.preventDefault();
7348
8761
  onCommand(btn.cmd);
7349
8762
  },
7350
- children: /* @__PURE__ */ jsx23(
8763
+ children: /* @__PURE__ */ jsx24(
7351
8764
  "svg",
7352
8765
  {
7353
8766
  width: "16",
@@ -7368,7 +8781,7 @@ function FloatingToolbar({
7368
8781
  );
7369
8782
  })
7370
8783
  ] }, gi)),
7371
- showEditLink ? /* @__PURE__ */ jsx23(
8784
+ showEditLink ? /* @__PURE__ */ jsx24(
7372
8785
  CustomToolbarButton,
7373
8786
  {
7374
8787
  type: "button",
@@ -7382,7 +8795,7 @@ function FloatingToolbar({
7382
8795
  e.preventDefault();
7383
8796
  e.stopPropagation();
7384
8797
  },
7385
- children: /* @__PURE__ */ jsx23(Link2, { className: "size-4 shrink-0", "aria-hidden": true })
8798
+ children: /* @__PURE__ */ jsx24(Link2, { className: "size-4 shrink-0", "aria-hidden": true })
7386
8799
  }
7387
8800
  ) : null
7388
8801
  ] })
@@ -7399,7 +8812,7 @@ function StateToggle({
7399
8812
  states,
7400
8813
  onStateChange
7401
8814
  }) {
7402
- return /* @__PURE__ */ jsx23(
8815
+ return /* @__PURE__ */ jsx24(
7403
8816
  ToggleGroup,
7404
8817
  {
7405
8818
  "data-ohw-state-toggle": "",
@@ -7413,7 +8826,7 @@ function StateToggle({
7413
8826
  left: rect.right - 8,
7414
8827
  transform: "translateX(-100%)"
7415
8828
  },
7416
- children: states.map((state) => /* @__PURE__ */ jsx23(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
8829
+ children: states.map((state) => /* @__PURE__ */ jsx24(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
7417
8830
  }
7418
8831
  );
7419
8832
  }
@@ -7440,8 +8853,8 @@ function OhhwellsBridge() {
7440
8853
  const router = useRouter2();
7441
8854
  const searchParams = useSearchParams();
7442
8855
  const isEditMode = isEditSessionActive();
7443
- const [bridgeRoot, setBridgeRoot] = useState6(null);
7444
- useEffect7(() => {
8856
+ const [bridgeRoot, setBridgeRoot] = useState7(null);
8857
+ useEffect8(() => {
7445
8858
  const figtreeFontId = "ohw-figtree-font";
7446
8859
  if (!document.getElementById(figtreeFontId)) {
7447
8860
  const preconnect1 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.googleapis.com" });
@@ -7469,91 +8882,137 @@ function OhhwellsBridge() {
7469
8882
  const subdomainFromQuery = searchParams.get("subdomain");
7470
8883
  const subdomain = resolveSubdomain(subdomainFromQuery);
7471
8884
  useLinkHrefGuardian(pathname, subdomain, isEditMode);
7472
- const postToParent2 = useCallback3((data) => {
8885
+ const postToParent2 = useCallback5((data) => {
7473
8886
  if (typeof window !== "undefined" && window.parent !== window) {
7474
8887
  window.parent.postMessage(data, "*");
7475
8888
  }
7476
8889
  }, []);
7477
- const [fetchState, setFetchState] = useState6("idle");
7478
- const autoSaveTimers = useRef5(/* @__PURE__ */ new Map());
7479
- const activeElRef = useRef5(null);
7480
- const selectedElRef = useRef5(null);
7481
- const originalContentRef = useRef5(null);
7482
- const activeStateElRef = useRef5(null);
7483
- const parentScrollRef = useRef5(null);
7484
- const visibleViewportRef = useRef5(null);
7485
- const [dialogPortalContainer, setDialogPortalContainer] = useState6(null);
7486
- const attachVisibleViewport = useCallback3((node) => {
8890
+ const [fetchState, setFetchState] = useState7("idle");
8891
+ const autoSaveTimers = useRef7(/* @__PURE__ */ new Map());
8892
+ const activeElRef = useRef7(null);
8893
+ const pointerHeldRef = useRef7(false);
8894
+ const selectedElRef = useRef7(null);
8895
+ const selectedHrefKeyRef = useRef7(null);
8896
+ const selectedFooterColAttrRef = useRef7(null);
8897
+ const originalContentRef = useRef7(null);
8898
+ const activeStateElRef = useRef7(null);
8899
+ const parentScrollRef = useRef7(null);
8900
+ const visibleViewportRef = useRef7(null);
8901
+ const [dialogPortalContainer, setDialogPortalContainer] = useState7(null);
8902
+ const attachVisibleViewport = useCallback5((node) => {
7487
8903
  visibleViewportRef.current = node;
7488
8904
  setDialogPortalContainer(node);
7489
8905
  if (node) applyVisibleViewport(node, parentScrollRef.current);
7490
8906
  }, []);
7491
- const toolbarElRef = useRef5(null);
7492
- const glowElRef = useRef5(null);
7493
- const hoveredImageRef = useRef5(null);
7494
- const hoveredImageHasTextOverlapRef = useRef5(false);
7495
- const dragOverElRef = useRef5(null);
7496
- const [mediaHover, setMediaHover] = useState6(null);
7497
- const [uploadingRects, setUploadingRects] = useState6({});
7498
- const hoveredGapRef = useRef5(null);
7499
- const imageUnhoverTimerRef = useRef5(null);
7500
- const imageShowTimerRef = useRef5(null);
7501
- const editStylesRef = useRef5(null);
7502
- const activateRef = useRef5(() => {
8907
+ const toolbarElRef = useRef7(null);
8908
+ const glowElRef = useRef7(null);
8909
+ const hoveredImageRef = useRef7(null);
8910
+ const hoveredImageHasTextOverlapRef = useRef7(false);
8911
+ const dragOverElRef = useRef7(null);
8912
+ const [mediaHover, setMediaHover] = useState7(null);
8913
+ const [uploadingRects, setUploadingRects] = useState7({});
8914
+ const hoveredGapRef = useRef7(null);
8915
+ const imageUnhoverTimerRef = useRef7(null);
8916
+ const imageShowTimerRef = useRef7(null);
8917
+ const editStylesRef = useRef7(null);
8918
+ const activateRef = useRef7(() => {
7503
8919
  });
7504
- const deactivateRef = useRef5(() => {
8920
+ const deactivateRef = useRef7(() => {
7505
8921
  });
7506
- const selectRef = useRef5(() => {
8922
+ const selectRef = useRef7(() => {
7507
8923
  });
7508
- const selectFrameRef = useRef5(() => {
8924
+ const selectFrameRef = useRef7(() => {
7509
8925
  });
7510
- const deselectRef = useRef5(() => {
8926
+ const deselectRef = useRef7(() => {
7511
8927
  });
7512
- const reselectNavigationItemRef = useRef5(() => {
8928
+ const reselectNavigationItemRef = useRef7(() => {
7513
8929
  });
7514
- const commitNavigationTextEditRef = useRef5(() => {
8930
+ const commitNavigationTextEditRef = useRef7(() => {
7515
8931
  });
7516
- const refreshActiveCommandsRef = useRef5(() => {
8932
+ const refreshActiveCommandsRef = useRef7(() => {
7517
8933
  });
7518
- const postToParentRef = useRef5(postToParent2);
8934
+ const postToParentRef = useRef7(postToParent2);
7519
8935
  postToParentRef.current = postToParent2;
7520
- const sectionsLoadedRef = useRef5(false);
7521
- const pendingScheduleConfigRequests = useRef5([]);
7522
- const [toolbarRect, setToolbarRect] = useState6(null);
7523
- const [toolbarVariant, setToolbarVariant] = useState6("none");
7524
- const toolbarVariantRef = useRef5("none");
8936
+ const sectionsLoadedRef = useRef7(false);
8937
+ const pendingScheduleConfigRequests = useRef7([]);
8938
+ const [toolbarRect, setToolbarRect] = useState7(null);
8939
+ const [toolbarVariant, setToolbarVariant] = useState7("none");
8940
+ const toolbarVariantRef = useRef7("none");
7525
8941
  toolbarVariantRef.current = toolbarVariant;
7526
- const [reorderHrefKey, setReorderHrefKey] = useState6(null);
7527
- const [reorderDragDisabled, setReorderDragDisabled] = useState6(false);
7528
- const [toggleState, setToggleState] = useState6(null);
7529
- const [maxBadge, setMaxBadge] = useState6(null);
7530
- const [activeCommands, setActiveCommands] = useState6(/* @__PURE__ */ new Set());
7531
- const [sectionGap, setSectionGap] = useState6(null);
7532
- const [toolbarShowEditLink, setToolbarShowEditLink] = useState6(false);
7533
- const hoveredNavContainerRef = useRef5(null);
7534
- const [hoveredNavContainerRect, setHoveredNavContainerRect] = useState6(null);
7535
- const hoveredItemElRef = useRef5(null);
7536
- const [hoveredItemRect, setHoveredItemRect] = useState6(null);
7537
- const siblingHintElRef = useRef5(null);
7538
- const [siblingHintRect, setSiblingHintRect] = useState6(null);
7539
- const [isItemDragging, setIsItemDragging] = useState6(false);
7540
- const [linkPopover, setLinkPopover] = useState6(null);
7541
- const linkPopoverSessionRef = useRef5(null);
7542
- const addNavAfterAnchorRef = useRef5(null);
7543
- const editContentRef = useRef5({});
7544
- const [sitePages, setSitePages] = useState6([]);
7545
- const [sectionsByPath, setSectionsByPath] = useState6({});
7546
- const sectionsPrefetchGenRef = useRef5(0);
7547
- const setLinkPopoverRef = useRef5(setLinkPopover);
7548
- const linkPopoverPanelRef = useRef5(null);
7549
- const linkPopoverOpenRef = useRef5(false);
7550
- const linkPopoverGraceUntilRef = useRef5(0);
8942
+ const [selectedIsCta, setSelectedIsCta] = useState7(false);
8943
+ const [reorderHrefKey, setReorderHrefKey] = useState7(null);
8944
+ const [reorderDragDisabled, setReorderDragDisabled] = useState7(false);
8945
+ const [toggleState, setToggleState] = useState7(null);
8946
+ const [maxBadge, setMaxBadge] = useState7(null);
8947
+ const [activeCommands, setActiveCommands] = useState7(/* @__PURE__ */ new Set());
8948
+ const [sectionGap, setSectionGap] = useState7(null);
8949
+ const [toolbarShowEditLink, setToolbarShowEditLink] = useState7(false);
8950
+ const hoveredNavContainerRef = useRef7(null);
8951
+ const [hoveredNavContainerRect, setHoveredNavContainerRect] = useState7(null);
8952
+ const hoveredItemElRef = useRef7(null);
8953
+ const [hoveredItemRect, setHoveredItemRect] = useState7(null);
8954
+ const siblingHintElRef = useRef7(null);
8955
+ const [siblingHintRect, setSiblingHintRect] = useState7(null);
8956
+ const [siblingHintRects, setSiblingHintRects] = useState7([]);
8957
+ const [isItemDragging, setIsItemDragging] = useState7(false);
8958
+ const [isFooterFrameSelection, setIsFooterFrameSelection] = useState7(false);
8959
+ const footerDragRef = useRef7(null);
8960
+ const [footerDropSlots, setFooterDropSlots] = useState7([]);
8961
+ const [activeFooterDropIndex, setActiveFooterDropIndex] = useState7(null);
8962
+ const [draggedItemRect, setDraggedItemRect] = useState7(null);
8963
+ const footerPointerDragRef = useRef7(null);
8964
+ const suppressNextClickRef = useRef7(false);
8965
+ const suppressClickUntilRef = useRef7(0);
8966
+ const [linkPopover, setLinkPopover] = useState7(null);
8967
+ const linkPopoverSessionRef = useRef7(null);
8968
+ const addNavAfterAnchorRef = useRef7(null);
8969
+ const editContentRef = useRef7({});
8970
+ const [sitePages, setSitePages] = useState7([]);
8971
+ const [sectionsByPath, setSectionsByPath] = useState7({});
8972
+ const sectionsPrefetchGenRef = useRef7(0);
8973
+ const setLinkPopoverRef = useRef7(setLinkPopover);
8974
+ const linkPopoverPanelRef = useRef7(null);
8975
+ const linkPopoverOpenRef = useRef7(false);
8976
+ const linkPopoverGraceUntilRef = useRef7(0);
7551
8977
  setLinkPopoverRef.current = setLinkPopover;
7552
8978
  linkPopoverSessionRef.current = linkPopover;
8979
+ const {
8980
+ navDragRef,
8981
+ navDropSlots,
8982
+ activeNavDropIndex,
8983
+ startNavLinkDrag,
8984
+ commitNavDrag,
8985
+ onNavDragOver,
8986
+ armNavPressFromChrome,
8987
+ refreshNavDragVisualsRef
8988
+ } = useNavItemDrag({
8989
+ isEditMode,
8990
+ editContentRef,
8991
+ selectedElRef,
8992
+ activeElRef,
8993
+ footerDragRef,
8994
+ suppressNextClickRef,
8995
+ suppressClickUntilRef,
8996
+ siblingHintElRef,
8997
+ postToParentRef,
8998
+ deselectRef,
8999
+ selectRef,
9000
+ deactivateRef,
9001
+ setLinkPopover: () => setLinkPopover(null),
9002
+ linkPopoverOpenRef,
9003
+ setIsItemDragging,
9004
+ setDraggedItemRect,
9005
+ setSiblingHintRect,
9006
+ setSiblingHintRects,
9007
+ setToolbarRect,
9008
+ getNavigationItemAnchor,
9009
+ isDragHandleDisabled,
9010
+ isNavbarButton: isNavbarButton2
9011
+ });
7553
9012
  const bumpLinkPopoverGrace = () => {
7554
9013
  linkPopoverGraceUntilRef.current = Date.now() + 350;
7555
9014
  };
7556
- const runSectionsPrefetch = useCallback3((pages) => {
9015
+ const runSectionsPrefetch = useCallback5((pages) => {
7557
9016
  if (!isEditMode || shouldUseDevFixtures() || pages.length === 0) return;
7558
9017
  const gen = ++sectionsPrefetchGenRef.current;
7559
9018
  const paths = pages.map((p) => p.path);
@@ -7572,10 +9031,24 @@ function OhhwellsBridge() {
7572
9031
  );
7573
9032
  });
7574
9033
  }, [isEditMode, pathname]);
7575
- const runSectionsPrefetchRef = useRef5(runSectionsPrefetch);
9034
+ const runSectionsPrefetchRef = useRef7(runSectionsPrefetch);
7576
9035
  runSectionsPrefetchRef.current = runSectionsPrefetch;
7577
- useEffect7(() => {
7578
- if (!linkPopover) return;
9036
+ useEffect8(() => {
9037
+ if (!linkPopover) {
9038
+ document.documentElement.removeAttribute("data-ohw-link-popover-open");
9039
+ return;
9040
+ }
9041
+ document.documentElement.setAttribute("data-ohw-link-popover-open", "");
9042
+ hoveredItemElRef.current = null;
9043
+ setHoveredItemRect(null);
9044
+ hoveredNavContainerRef.current = null;
9045
+ setHoveredNavContainerRect(null);
9046
+ siblingHintElRef.current = null;
9047
+ setSiblingHintRect(null);
9048
+ setSiblingHintRects([]);
9049
+ if (selectedElRef.current || toolbarVariantRef.current !== "none") {
9050
+ deselectRef.current();
9051
+ }
7579
9052
  if (hoveredImageRef.current) {
7580
9053
  hoveredImageRef.current = null;
7581
9054
  hoveredImageHasTextOverlapRef.current = false;
@@ -7609,7 +9082,7 @@ function OhhwellsBridge() {
7609
9082
  document.removeEventListener("touchmove", preventBackgroundScroll, scrollOpts);
7610
9083
  };
7611
9084
  }, [linkPopover, postToParent2]);
7612
- useEffect7(() => {
9085
+ useEffect8(() => {
7613
9086
  if (!isEditMode) return;
7614
9087
  const useFixtures = shouldUseDevFixtures();
7615
9088
  if (useFixtures) {
@@ -7633,14 +9106,14 @@ function OhhwellsBridge() {
7633
9106
  if (!useFixtures) postToParent2({ type: "ow:request-site-pages" });
7634
9107
  return () => window.removeEventListener("message", onSitePages);
7635
9108
  }, [isEditMode, postToParent2]);
7636
- useEffect7(() => {
9109
+ useEffect8(() => {
7637
9110
  if (!isEditMode || shouldUseDevFixtures()) return;
7638
9111
  void loadAllSectionsManifest().then((manifest) => {
7639
9112
  if (Object.keys(manifest).length === 0) return;
7640
9113
  setSectionsByPath((prev) => ({ ...manifest, ...prev }));
7641
9114
  });
7642
9115
  }, [isEditMode]);
7643
- useEffect7(() => {
9116
+ useEffect8(() => {
7644
9117
  const update = () => {
7645
9118
  const el = activeElRef.current ?? selectedElRef.current;
7646
9119
  if (el) setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
@@ -7664,10 +9137,10 @@ function OhhwellsBridge() {
7664
9137
  vvp.removeEventListener("resize", update);
7665
9138
  };
7666
9139
  }, []);
7667
- const refreshStateRules = useCallback3(() => {
9140
+ const refreshStateRules = useCallback5(() => {
7668
9141
  editStylesRef.current?.forceHover && (editStylesRef.current.forceHover.textContent = collectStateRules());
7669
9142
  }, []);
7670
- const processConfigRequest = useCallback3((insertAfterVal) => {
9143
+ const processConfigRequest = useCallback5((insertAfterVal) => {
7671
9144
  const tracker = getSectionsTracker();
7672
9145
  let entries = [];
7673
9146
  try {
@@ -7690,7 +9163,7 @@ function OhhwellsBridge() {
7690
9163
  }
7691
9164
  window.postMessage({ type: "ow:schedule-config", insertAfter: insertAfterVal, scheduleId: null }, "*");
7692
9165
  }, [isEditMode]);
7693
- const deactivate = useCallback3(() => {
9166
+ const deactivate = useCallback5(() => {
7694
9167
  const el = activeElRef.current;
7695
9168
  if (!el) return;
7696
9169
  const key = el.dataset.ohwKey;
@@ -7709,6 +9182,7 @@ function OhhwellsBridge() {
7709
9182
  }
7710
9183
  }
7711
9184
  el.removeAttribute("contenteditable");
9185
+ el.removeAttribute("data-ohw-editing");
7712
9186
  activeElRef.current = null;
7713
9187
  setReorderHrefKey(null);
7714
9188
  setReorderDragDisabled(false);
@@ -7721,12 +9195,23 @@ function OhhwellsBridge() {
7721
9195
  setToolbarShowEditLink(false);
7722
9196
  postToParent2({ type: "ow:exit-edit" });
7723
9197
  }, [postToParent2]);
7724
- const deselect = useCallback3(() => {
9198
+ const clearSelectedAttr = useCallback5(() => {
9199
+ document.querySelectorAll("[data-ohw-selected]").forEach((el) => {
9200
+ el.removeAttribute("data-ohw-selected");
9201
+ });
9202
+ }, []);
9203
+ const deselect = useCallback5(() => {
9204
+ clearSelectedAttr();
7725
9205
  selectedElRef.current = null;
9206
+ selectedHrefKeyRef.current = null;
9207
+ selectedFooterColAttrRef.current = null;
9208
+ setSelectedIsCta(false);
7726
9209
  setReorderHrefKey(null);
7727
9210
  setReorderDragDisabled(false);
9211
+ setIsFooterFrameSelection(false);
7728
9212
  siblingHintElRef.current = null;
7729
9213
  setSiblingHintRect(null);
9214
+ setSiblingHintRects([]);
7730
9215
  setIsItemDragging(false);
7731
9216
  hoveredNavContainerRef.current = null;
7732
9217
  setHoveredNavContainerRect(null);
@@ -7734,18 +9219,70 @@ function OhhwellsBridge() {
7734
9219
  setToolbarRect(null);
7735
9220
  setToolbarVariant("none");
7736
9221
  }
9222
+ }, [clearSelectedAttr]);
9223
+ const markSelected = useCallback5((el) => {
9224
+ clearSelectedAttr();
9225
+ el.setAttribute("data-ohw-selected", "");
9226
+ }, [clearSelectedAttr]);
9227
+ const resolveHrefKeyElement = useCallback5((hrefKey) => {
9228
+ if (isFooterHrefKey(hrefKey)) {
9229
+ return document.querySelector(
9230
+ `footer [data-ohw-href-key="${CSS.escape(hrefKey)}"]`
9231
+ );
9232
+ }
9233
+ if (isNavbarHrefKey(hrefKey)) {
9234
+ const desktop = document.querySelector("[data-ohw-nav-container]");
9235
+ const drawer = document.querySelector("[data-ohw-nav-drawer]");
9236
+ 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)}"]`);
9237
+ }
9238
+ return document.querySelector(
9239
+ `[data-ohw-href-key="${CSS.escape(hrefKey)}"]`
9240
+ );
7737
9241
  }, []);
7738
- const reselectNavigationItem = useCallback3((navAnchor) => {
9242
+ const resyncSelectedNavigationItem = useCallback5(() => {
9243
+ const hrefKey = selectedHrefKeyRef.current;
9244
+ if (hrefKey) {
9245
+ const link = resolveHrefKeyElement(hrefKey);
9246
+ if (!link || !isNavigationItem(link)) return;
9247
+ selectedElRef.current = link;
9248
+ const { key, disabled } = getNavigationItemReorderState(link);
9249
+ setReorderHrefKey(key);
9250
+ setReorderDragDisabled(disabled);
9251
+ setIsFooterFrameSelection(false);
9252
+ setToolbarVariant("link-action");
9253
+ setToolbarRect(link.getBoundingClientRect());
9254
+ setSiblingHintRects(collectNavigationItemSiblingHintRects(link));
9255
+ return;
9256
+ }
9257
+ const colAttr = selectedFooterColAttrRef.current;
9258
+ if (colAttr != null) {
9259
+ const column = document.querySelector(`[data-ohw-footer-col="${CSS.escape(colAttr)}"]`) ?? listFooterColumns()[Number(colAttr)] ?? null;
9260
+ if (!column || !isNavigationContainer(column)) return;
9261
+ selectedElRef.current = column;
9262
+ setIsFooterFrameSelection(true);
9263
+ setToolbarVariant("select-frame");
9264
+ setToolbarRect(column.getBoundingClientRect());
9265
+ setSiblingHintRects(
9266
+ listFooterColumns().filter((c) => c !== column).map((c) => c.getBoundingClientRect())
9267
+ );
9268
+ }
9269
+ }, [resolveHrefKeyElement]);
9270
+ const reselectNavigationItem = useCallback5((navAnchor) => {
7739
9271
  selectedElRef.current = navAnchor;
9272
+ selectedHrefKeyRef.current = navAnchor.getAttribute("data-ohw-href-key");
9273
+ selectedFooterColAttrRef.current = null;
9274
+ markSelected(navAnchor);
9275
+ setSelectedIsCta(isNavbarButton2(navAnchor));
7740
9276
  const { key, disabled } = getNavigationItemReorderState(navAnchor);
7741
9277
  setReorderHrefKey(key);
7742
9278
  setReorderDragDisabled(disabled);
7743
9279
  setToolbarVariant("link-action");
7744
9280
  setToolbarRect(navAnchor.getBoundingClientRect());
9281
+ setSiblingHintRects(collectNavigationItemSiblingHintRects(navAnchor));
7745
9282
  setToolbarShowEditLink(false);
7746
9283
  setActiveCommands(/* @__PURE__ */ new Set());
7747
- }, []);
7748
- const commitNavigationTextEdit = useCallback3((navAnchor) => {
9284
+ }, [markSelected]);
9285
+ const commitNavigationTextEdit = useCallback5((navAnchor) => {
7749
9286
  const el = activeElRef.current;
7750
9287
  if (!el) return;
7751
9288
  const key = el.dataset.ohwKey;
@@ -7764,6 +9301,7 @@ function OhhwellsBridge() {
7764
9301
  }
7765
9302
  }
7766
9303
  el.removeAttribute("contenteditable");
9304
+ el.removeAttribute("data-ohw-editing");
7767
9305
  activeElRef.current = null;
7768
9306
  setMaxBadge(null);
7769
9307
  setActiveCommands(/* @__PURE__ */ new Set());
@@ -7771,7 +9309,7 @@ function OhhwellsBridge() {
7771
9309
  postToParent2({ type: "ow:exit-edit" });
7772
9310
  reselectNavigationItem(navAnchor);
7773
9311
  }, [postToParent2, reselectNavigationItem]);
7774
- const handleAddTopLevelNavItem = useCallback3(() => {
9312
+ const handleAddTopLevelNavItem = useCallback5(() => {
7775
9313
  const items = listNavbarItems();
7776
9314
  addNavAfterAnchorRef.current = items[items.length - 1] ?? null;
7777
9315
  deselectRef.current();
@@ -7783,20 +9321,321 @@ function OhhwellsBridge() {
7783
9321
  intent: "add-nav"
7784
9322
  });
7785
9323
  }, []);
7786
- const handleItemDragStart = useCallback3(() => {
7787
- siblingHintElRef.current = null;
7788
- setSiblingHintRect(null);
7789
- setIsItemDragging(true);
7790
- }, []);
7791
- const handleItemDragEnd = useCallback3(() => {
9324
+ const clearFooterDragVisuals = useCallback5(() => {
9325
+ footerDragRef.current = null;
9326
+ setSiblingHintRects([]);
9327
+ setFooterDropSlots([]);
9328
+ setActiveFooterDropIndex(null);
9329
+ setDraggedItemRect(null);
7792
9330
  setIsItemDragging(false);
9331
+ unlockFooterDragInteraction();
9332
+ }, []);
9333
+ const refreshFooterDragVisuals = useCallback5((session, activeSlot, clientX, clientY) => {
9334
+ const dragged = session.draggedEl;
9335
+ setDraggedItemRect(dragged.getBoundingClientRect());
9336
+ if (typeof clientX === "number" && typeof clientY === "number") {
9337
+ session.lastClientX = clientX;
9338
+ session.lastClientY = clientY;
9339
+ }
9340
+ session.activeSlot = activeSlot;
9341
+ if (session.kind === "link") {
9342
+ const columns2 = listFooterColumns();
9343
+ const focusCols = /* @__PURE__ */ new Set([session.sourceColumnIndex]);
9344
+ if (activeSlot) focusCols.add(activeSlot.columnIndex);
9345
+ const siblingRects = [];
9346
+ columns2.forEach((col, i) => {
9347
+ if (!focusCols.has(i)) return;
9348
+ for (const link of listFooterLinksInColumn(col)) {
9349
+ if (link === dragged) continue;
9350
+ siblingRects.push(link.getBoundingClientRect());
9351
+ }
9352
+ });
9353
+ setSiblingHintRects(siblingRects);
9354
+ const slots2 = [];
9355
+ columns2.forEach((col, i) => {
9356
+ slots2.push(...buildLinkDropSlots(col, i));
9357
+ });
9358
+ setFooterDropSlots(slots2);
9359
+ const activeIdx2 = activeSlot ? slots2.findIndex((s) => s.columnIndex === activeSlot.columnIndex && s.insertIndex === activeSlot.insertIndex) : -1;
9360
+ setActiveFooterDropIndex(activeIdx2 >= 0 ? activeIdx2 : null);
9361
+ return;
9362
+ }
9363
+ const columns = listFooterColumns();
9364
+ setSiblingHintRects(columns.filter((col) => col !== dragged).map((col) => col.getBoundingClientRect()));
9365
+ const slots = buildColumnDropSlots();
9366
+ setFooterDropSlots(slots);
9367
+ const activeIdx = activeSlot ? slots.findIndex((s) => s.insertIndex === activeSlot.insertIndex) : -1;
9368
+ setActiveFooterDropIndex(activeIdx >= 0 ? activeIdx : null);
9369
+ }, []);
9370
+ const refreshFooterDragVisualsRef = useRef7(refreshFooterDragVisuals);
9371
+ refreshFooterDragVisualsRef.current = refreshFooterDragVisuals;
9372
+ const commitFooterDragRef = useRef7(() => {
9373
+ });
9374
+ const beginFooterDragRef = useRef7(() => {
9375
+ });
9376
+ const beginFooterDrag = useCallback5(
9377
+ (session) => {
9378
+ const rect = session.draggedEl.getBoundingClientRect();
9379
+ session.lastClientX = session.lastClientX || rect.left + rect.width / 2;
9380
+ session.lastClientY = session.lastClientY || rect.top + rect.height / 2;
9381
+ session.activeSlot = session.activeSlot ?? null;
9382
+ footerDragRef.current = session;
9383
+ setIsItemDragging(true);
9384
+ lockFooterDuringDrag();
9385
+ siblingHintElRef.current = null;
9386
+ setSiblingHintRect(null);
9387
+ if (session.wasSelected && selectedElRef.current === session.draggedEl) {
9388
+ setToolbarRect(rect);
9389
+ }
9390
+ const initialSlot = session.kind === "link" && session.hrefKey ? hitTestLinkDropSlot(session.lastClientX, session.lastClientY, session.hrefKey) : hitTestColumnDropSlot(session.lastClientX, session.lastClientY);
9391
+ refreshFooterDragVisuals(session, initialSlot, session.lastClientX, session.lastClientY);
9392
+ },
9393
+ [refreshFooterDragVisuals]
9394
+ );
9395
+ beginFooterDragRef.current = beginFooterDrag;
9396
+ const commitFooterDrag = useCallback5(
9397
+ (clientX, clientY) => {
9398
+ const session = footerDragRef.current;
9399
+ if (!session) {
9400
+ clearFooterDragVisuals();
9401
+ return;
9402
+ }
9403
+ const x = typeof clientX === "number" && (clientX !== 0 || clientY !== 0) ? clientX : session.lastClientX;
9404
+ const y = typeof clientY === "number" && (clientX !== 0 || clientY !== 0) ? clientY : session.lastClientY;
9405
+ let nextOrder = null;
9406
+ const slot = session.activeSlot ?? (session.kind === "link" && session.hrefKey ? hitTestLinkDropSlot(x, y, session.hrefKey) : session.kind === "column" ? hitTestColumnDropSlot(x, y) : null);
9407
+ if (session.kind === "link" && session.hrefKey && slot) {
9408
+ nextOrder = planFooterLinkMove(session.hrefKey, slot.columnIndex, slot.insertIndex);
9409
+ } else if (session.kind === "column" && slot) {
9410
+ nextOrder = planFooterColumnMove(session.sourceColumnIndex, slot.insertIndex);
9411
+ }
9412
+ const wasSelected = session.wasSelected;
9413
+ const draggedEl = session.draggedEl;
9414
+ const hrefKey = session.hrefKey;
9415
+ let movedColumnIndex = null;
9416
+ if (session.kind === "column" && slot && nextOrder) {
9417
+ let adjusted = slot.insertIndex;
9418
+ if (session.sourceColumnIndex < slot.insertIndex) adjusted -= 1;
9419
+ movedColumnIndex = Math.max(0, Math.min(adjusted, nextOrder.length - 1));
9420
+ }
9421
+ const applySelectionAfterDrop = () => {
9422
+ if (!wasSelected) {
9423
+ deselectRef.current();
9424
+ return;
9425
+ }
9426
+ if (hrefKey) {
9427
+ selectedHrefKeyRef.current = hrefKey;
9428
+ selectedFooterColAttrRef.current = null;
9429
+ const link = resolveHrefKeyElement(hrefKey);
9430
+ if (link && isNavigationItem(link)) {
9431
+ selectRef.current(link);
9432
+ return;
9433
+ }
9434
+ }
9435
+ if (movedColumnIndex !== null && nextOrder) {
9436
+ const colKeys = nextOrder[movedColumnIndex] ?? [];
9437
+ let column = null;
9438
+ for (const key of colKeys) {
9439
+ const link = document.querySelector(
9440
+ `footer [data-ohw-href-key="${CSS.escape(key)}"]`
9441
+ );
9442
+ if (link) {
9443
+ column = findFooterColumnForLink(link);
9444
+ if (column) break;
9445
+ }
9446
+ }
9447
+ if (!column) column = listFooterColumns()[movedColumnIndex] ?? null;
9448
+ if (column && isNavigationContainer(column)) {
9449
+ selectFrameRef.current(column);
9450
+ return;
9451
+ }
9452
+ }
9453
+ if (document.body.contains(draggedEl)) {
9454
+ if (isNavigationItem(draggedEl)) {
9455
+ selectRef.current(draggedEl);
9456
+ return;
9457
+ }
9458
+ if (isNavigationContainer(draggedEl)) {
9459
+ selectFrameRef.current(draggedEl);
9460
+ return;
9461
+ }
9462
+ }
9463
+ deselectRef.current();
9464
+ };
9465
+ if (nextOrder) {
9466
+ const orderJson = JSON.stringify(nextOrder);
9467
+ editContentRef.current = {
9468
+ ...editContentRef.current,
9469
+ [FOOTER_ORDER_KEY]: orderJson
9470
+ };
9471
+ applyFooterOrder(nextOrder);
9472
+ document.querySelectorAll("footer [data-ohw-href-key]").forEach(disableNativeHrefDrag);
9473
+ postToParentRef.current({
9474
+ type: "ow:change",
9475
+ nodes: [{ key: FOOTER_ORDER_KEY, text: orderJson }]
9476
+ });
9477
+ applySelectionAfterDrop();
9478
+ clearFooterDragVisuals();
9479
+ requestAnimationFrame(() => {
9480
+ if (editContentRef.current[FOOTER_ORDER_KEY] === orderJson) {
9481
+ applyFooterOrder(nextOrder);
9482
+ document.querySelectorAll("footer [data-ohw-href-key]").forEach(disableNativeHrefDrag);
9483
+ }
9484
+ applySelectionAfterDrop();
9485
+ requestAnimationFrame(() => {
9486
+ if (editContentRef.current[FOOTER_ORDER_KEY] === orderJson) {
9487
+ applyFooterOrder(nextOrder);
9488
+ }
9489
+ resyncSelectedNavigationItem();
9490
+ });
9491
+ });
9492
+ return;
9493
+ }
9494
+ applySelectionAfterDrop();
9495
+ clearFooterDragVisuals();
9496
+ },
9497
+ [clearFooterDragVisuals, resolveHrefKeyElement, resyncSelectedNavigationItem]
9498
+ );
9499
+ commitFooterDragRef.current = commitFooterDrag;
9500
+ const startFooterLinkDrag = useCallback5(
9501
+ (anchor, clientX, clientY, wasSelected) => {
9502
+ const hrefKey = anchor.getAttribute("data-ohw-href-key");
9503
+ if (!hrefKey || !isFooterHrefKey(hrefKey)) return false;
9504
+ const column = findFooterColumnForLink(anchor);
9505
+ const columns = listFooterColumns();
9506
+ beginFooterDrag({
9507
+ kind: "link",
9508
+ hrefKey,
9509
+ columnEl: column,
9510
+ sourceColumnIndex: column ? columns.indexOf(column) : 0,
9511
+ wasSelected,
9512
+ draggedEl: anchor,
9513
+ lastClientX: clientX,
9514
+ lastClientY: clientY,
9515
+ activeSlot: null
9516
+ });
9517
+ return true;
9518
+ },
9519
+ [beginFooterDrag]
9520
+ );
9521
+ const startFooterColumnDrag = useCallback5(
9522
+ (columnEl, clientX, clientY, wasSelected) => {
9523
+ const columns = listFooterColumns();
9524
+ const idx = columns.indexOf(columnEl);
9525
+ if (idx < 0) return false;
9526
+ beginFooterDrag({
9527
+ kind: "column",
9528
+ hrefKey: null,
9529
+ columnEl,
9530
+ sourceColumnIndex: idx,
9531
+ wasSelected,
9532
+ draggedEl: columnEl,
9533
+ lastClientX: clientX,
9534
+ lastClientY: clientY,
9535
+ activeSlot: null
9536
+ });
9537
+ return true;
9538
+ },
9539
+ [beginFooterDrag]
9540
+ );
9541
+ const handleItemDragStart = useCallback5(
9542
+ (e) => {
9543
+ const selected = selectedElRef.current;
9544
+ if (!selected) {
9545
+ setIsItemDragging(true);
9546
+ return;
9547
+ }
9548
+ const clientX = e?.clientX ?? selected.getBoundingClientRect().left + 8;
9549
+ const clientY = e?.clientY ?? selected.getBoundingClientRect().top + 8;
9550
+ if (startFooterLinkDrag(selected, clientX, clientY, true)) return;
9551
+ if (selected.hasAttribute("data-ohw-footer-col") || isInferredFooterGroup(selected) && selected.closest("footer")) {
9552
+ if (startFooterColumnDrag(selected, clientX, clientY, true)) return;
9553
+ }
9554
+ if (startNavLinkDrag(selected, clientX, clientY, true)) return;
9555
+ siblingHintElRef.current = null;
9556
+ setSiblingHintRect(null);
9557
+ setIsItemDragging(true);
9558
+ },
9559
+ [startFooterColumnDrag, startFooterLinkDrag, startNavLinkDrag]
9560
+ );
9561
+ const handleItemDragEnd = useCallback5(
9562
+ (e) => {
9563
+ if (footerDragRef.current) {
9564
+ const x = e?.clientX;
9565
+ const y = e?.clientY;
9566
+ if (typeof x === "number" && typeof y === "number" && (x !== 0 || y !== 0)) {
9567
+ commitFooterDrag(x, y);
9568
+ } else {
9569
+ commitFooterDrag();
9570
+ }
9571
+ return;
9572
+ }
9573
+ if (navDragRef.current) {
9574
+ const x = e?.clientX;
9575
+ const y = e?.clientY;
9576
+ if (typeof x === "number" && typeof y === "number" && (x !== 0 || y !== 0)) {
9577
+ commitNavDrag(x, y);
9578
+ } else {
9579
+ commitNavDrag();
9580
+ }
9581
+ return;
9582
+ }
9583
+ setIsItemDragging(false);
9584
+ },
9585
+ [commitFooterDrag, commitNavDrag, navDragRef]
9586
+ );
9587
+ const handleItemChromePointerDown = useCallback5((e) => {
9588
+ if (e.button !== 0) return;
9589
+ const selected = selectedElRef.current;
9590
+ if (!selected) return;
9591
+ const hrefKey = selected.getAttribute("data-ohw-href-key");
9592
+ if (hrefKey && isFooterHrefKey(hrefKey)) {
9593
+ footerPointerDragRef.current = {
9594
+ el: selected,
9595
+ kind: "link",
9596
+ startX: e.clientX,
9597
+ startY: e.clientY,
9598
+ pointerId: e.pointerId,
9599
+ wasSelected: true,
9600
+ started: false
9601
+ };
9602
+ return;
9603
+ }
9604
+ if (selected.hasAttribute("data-ohw-footer-col") || isInferredFooterGroup(selected) && selected.closest("footer")) {
9605
+ footerPointerDragRef.current = {
9606
+ el: selected,
9607
+ kind: "column",
9608
+ startX: e.clientX,
9609
+ startY: e.clientY,
9610
+ pointerId: e.pointerId,
9611
+ wasSelected: true,
9612
+ started: false
9613
+ };
9614
+ return;
9615
+ }
9616
+ if (armNavPressFromChrome(selected, e.clientX, e.clientY, e.pointerId)) return;
9617
+ }, [armNavPressFromChrome]);
9618
+ const handleItemChromeClick = useCallback5((clientX, clientY) => {
9619
+ if (suppressNextClickRef.current || Date.now() < suppressClickUntilRef.current) {
9620
+ suppressNextClickRef.current = false;
9621
+ return;
9622
+ }
9623
+ const selected = selectedElRef.current;
9624
+ if (!selected || !isNavigationItem(selected)) return;
9625
+ const editable = selected.querySelector('[data-ohw-editable="text"]') ?? selected.querySelector("[data-ohw-editable]");
9626
+ if (!editable) return;
9627
+ activateRef.current(editable, { caretX: clientX, caretY: clientY });
7793
9628
  }, []);
7794
9629
  reselectNavigationItemRef.current = reselectNavigationItem;
7795
9630
  commitNavigationTextEditRef.current = commitNavigationTextEdit;
7796
- const select = useCallback3((anchor) => {
9631
+ const select = useCallback5((anchor) => {
7797
9632
  if (!isNavigationItem(anchor)) return;
7798
9633
  if (activeElRef.current) deactivate();
7799
9634
  selectedElRef.current = anchor;
9635
+ selectedHrefKeyRef.current = anchor.getAttribute("data-ohw-href-key");
9636
+ selectedFooterColAttrRef.current = null;
9637
+ markSelected(anchor);
9638
+ setSelectedIsCta(isNavbarButton2(anchor));
7800
9639
  clearHrefKeyHover(anchor);
7801
9640
  hoveredNavContainerRef.current = null;
7802
9641
  setHoveredNavContainerRect(null);
@@ -7804,19 +9643,26 @@ function OhhwellsBridge() {
7804
9643
  hoveredItemElRef.current = null;
7805
9644
  siblingHintElRef.current = null;
7806
9645
  setSiblingHintRect(null);
9646
+ setSiblingHintRects(collectNavigationItemSiblingHintRects(anchor));
7807
9647
  setIsItemDragging(false);
7808
9648
  const { key, disabled } = getNavigationItemReorderState(anchor);
7809
9649
  setReorderHrefKey(key);
7810
9650
  setReorderDragDisabled(disabled);
9651
+ setIsFooterFrameSelection(false);
7811
9652
  setToolbarVariant("link-action");
7812
9653
  setToolbarRect(anchor.getBoundingClientRect());
7813
9654
  setToolbarShowEditLink(false);
7814
9655
  setActiveCommands(/* @__PURE__ */ new Set());
7815
- }, [deactivate]);
7816
- const selectFrame = useCallback3((el) => {
9656
+ }, [deactivate, markSelected]);
9657
+ const selectFrame = useCallback5((el) => {
7817
9658
  if (!isNavigationContainer(el)) return;
7818
9659
  if (activeElRef.current) deactivate();
9660
+ const isFooterColumn = el.hasAttribute("data-ohw-footer-col") || Boolean(el.closest("footer") && isInferredFooterGroup(el));
7819
9661
  selectedElRef.current = el;
9662
+ selectedHrefKeyRef.current = null;
9663
+ selectedFooterColAttrRef.current = isFooterColumn ? el.getAttribute("data-ohw-footer-col") ?? String(listFooterColumns().indexOf(el)) : null;
9664
+ markSelected(el);
9665
+ setSelectedIsCta(false);
7820
9666
  clearHrefKeyHover(el);
7821
9667
  hoveredNavContainerRef.current = null;
7822
9668
  setHoveredNavContainerRect(null);
@@ -7824,17 +9670,25 @@ function OhhwellsBridge() {
7824
9670
  hoveredItemElRef.current = null;
7825
9671
  siblingHintElRef.current = null;
7826
9672
  setSiblingHintRect(null);
9673
+ setSiblingHintRects(
9674
+ isFooterColumn ? listFooterColumns().filter((column) => column !== el).map((column) => column.getBoundingClientRect()) : []
9675
+ );
7827
9676
  setIsItemDragging(false);
7828
9677
  setReorderHrefKey(null);
7829
9678
  setReorderDragDisabled(false);
9679
+ setIsFooterFrameSelection(isFooterColumn);
7830
9680
  setToolbarVariant("select-frame");
7831
9681
  setToolbarRect(el.getBoundingClientRect());
7832
9682
  setToolbarShowEditLink(false);
7833
9683
  setActiveCommands(/* @__PURE__ */ new Set());
7834
- }, [deactivate]);
7835
- const activate = useCallback3((el, options) => {
9684
+ }, [deactivate, markSelected]);
9685
+ const activate = useCallback5((el, options) => {
7836
9686
  if (activeElRef.current === el) return;
9687
+ clearSelectedAttr();
7837
9688
  selectedElRef.current = null;
9689
+ selectedHrefKeyRef.current = null;
9690
+ selectedFooterColAttrRef.current = null;
9691
+ setSelectedIsCta(false);
7838
9692
  deactivate();
7839
9693
  if (hoveredImageRef.current) {
7840
9694
  hoveredImageRef.current = null;
@@ -7844,14 +9698,34 @@ function OhhwellsBridge() {
7844
9698
  siblingHintElRef.current = null;
7845
9699
  setSiblingHintRect(null);
7846
9700
  setIsItemDragging(false);
9701
+ const preActivationSelection = window.getSelection();
9702
+ const preservedRange = preActivationSelection && preActivationSelection.rangeCount > 0 && !preActivationSelection.isCollapsed && el.contains(preActivationSelection.getRangeAt(0).commonAncestorContainer) ? preActivationSelection.getRangeAt(0).cloneRange() : null;
7847
9703
  el.setAttribute("contenteditable", "true");
9704
+ el.setAttribute("data-ohw-editing", "");
7848
9705
  el.removeAttribute("data-ohw-hovered");
7849
9706
  el.closest("[data-ohw-href-key]")?.removeAttribute("data-ohw-hovered");
7850
9707
  activeElRef.current = el;
7851
9708
  originalContentRef.current = el.innerHTML;
7852
- el.focus();
7853
- if (options?.caretX !== void 0 && options?.caretY !== void 0) {
7854
- placeCaretAtPoint(el, options.caretX, options.caretY);
9709
+ el.focus({ preventScroll: true });
9710
+ if (preservedRange) {
9711
+ const selection = window.getSelection();
9712
+ selection?.removeAllRanges();
9713
+ selection?.addRange(preservedRange);
9714
+ } else if (options?.caretX !== void 0 && options?.caretY !== void 0) {
9715
+ const { caretX, caretY } = options;
9716
+ placeCaretAtPoint(el, caretX, caretY);
9717
+ requestAnimationFrame(() => {
9718
+ if (activeElRef.current === el) placeCaretAtPoint(el, caretX, caretY);
9719
+ });
9720
+ } else {
9721
+ const selection = window.getSelection();
9722
+ if (selection && selection.rangeCount === 0) {
9723
+ const range = document.createRange();
9724
+ range.selectNodeContents(el);
9725
+ range.collapse(false);
9726
+ selection.removeAllRanges();
9727
+ selection.addRange(range);
9728
+ }
7855
9729
  }
7856
9730
  setToolbarShowEditLink(Boolean(getHrefKeyFromElement(el)));
7857
9731
  const navAnchor = getNavigationItemAnchor(el);
@@ -7866,13 +9740,13 @@ function OhhwellsBridge() {
7866
9740
  setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
7867
9741
  postToParent2({ type: "ow:enter-edit", key: el.dataset.ohwKey });
7868
9742
  requestAnimationFrame(() => refreshActiveCommandsRef.current());
7869
- }, [deactivate, postToParent2]);
9743
+ }, [clearSelectedAttr, deactivate, postToParent2]);
7870
9744
  activateRef.current = activate;
7871
9745
  deactivateRef.current = deactivate;
7872
9746
  selectRef.current = select;
7873
9747
  selectFrameRef.current = selectFrame;
7874
9748
  deselectRef.current = deselect;
7875
- useLayoutEffect2(() => {
9749
+ useLayoutEffect3(() => {
7876
9750
  if (!subdomain || isEditMode) {
7877
9751
  setFetchState("done");
7878
9752
  return;
@@ -7913,6 +9787,7 @@ function OhhwellsBridge() {
7913
9787
  applyLinkByKey(key, val);
7914
9788
  }
7915
9789
  reconcileNavbarItemsFromContent(content);
9790
+ reconcileFooterOrderFromContent(content);
7916
9791
  enforceLinkHrefs();
7917
9792
  initSectionsFromContent(content, true);
7918
9793
  sectionsLoadedRef.current = true;
@@ -7941,7 +9816,7 @@ function OhhwellsBridge() {
7941
9816
  cancelled = true;
7942
9817
  };
7943
9818
  }, [subdomain, isEditMode]);
7944
- useEffect7(() => {
9819
+ useEffect8(() => {
7945
9820
  if (!subdomain || isEditMode) return;
7946
9821
  let debounceTimer = null;
7947
9822
  let observer = null;
@@ -7973,6 +9848,7 @@ function OhhwellsBridge() {
7973
9848
  applyLinkByKey(key, val);
7974
9849
  }
7975
9850
  reconcileNavbarItemsFromContent(content);
9851
+ reconcileFooterOrderFromContent(content);
7976
9852
  } finally {
7977
9853
  observer?.observe(document.body, { childList: true, subtree: true });
7978
9854
  }
@@ -7990,16 +9866,16 @@ function OhhwellsBridge() {
7990
9866
  if (debounceTimer) clearTimeout(debounceTimer);
7991
9867
  };
7992
9868
  }, [subdomain, isEditMode, pathname]);
7993
- useLayoutEffect2(() => {
9869
+ useLayoutEffect3(() => {
7994
9870
  const el = document.getElementById("ohw-loader");
7995
9871
  if (!el) return;
7996
9872
  const visible = Boolean(subdomain) && fetchState !== "done";
7997
9873
  el.style.display = visible ? "flex" : "none";
7998
9874
  }, [subdomain, fetchState]);
7999
- useEffect7(() => {
9875
+ useEffect8(() => {
8000
9876
  postToParent2({ type: "ow:navigation", path: pathname });
8001
9877
  }, [pathname, postToParent2]);
8002
- useEffect7(() => {
9878
+ useEffect8(() => {
8003
9879
  if (!isEditMode) return;
8004
9880
  if (linkPopoverSessionRef.current?.intent === "add-nav") return;
8005
9881
  if (document.querySelector("[data-ohw-section-picker]")) return;
@@ -8007,23 +9883,72 @@ function OhhwellsBridge() {
8007
9883
  deselectRef.current();
8008
9884
  deactivateRef.current();
8009
9885
  }, [pathname, isEditMode]);
8010
- useEffect7(() => {
9886
+ useEffect8(() => {
8011
9887
  const contentForNav = () => {
8012
9888
  if (isEditMode) return editContentRef.current;
8013
9889
  if (!subdomain) return {};
8014
9890
  return contentCache.get(subdomain) ?? {};
8015
9891
  };
8016
- const run = () => reconcileNavbarItemsFromContent(contentForNav());
9892
+ const observeRoots = () => [document.querySelector("nav"), document.querySelector("footer")].filter(
9893
+ (el) => Boolean(el)
9894
+ );
9895
+ const roots = observeRoots();
9896
+ if (roots.length === 0) {
9897
+ reconcileNavbarItemsFromContent(contentForNav());
9898
+ reconcileFooterOrderFromContent(contentForNav());
9899
+ if (isEditMode) syncNavigationDragCursorAttrs();
9900
+ return;
9901
+ }
9902
+ let rafId = null;
9903
+ let applying = false;
9904
+ let observer = null;
9905
+ const attach = () => {
9906
+ for (const root of observeRoots()) {
9907
+ observer?.observe(root, { childList: true, subtree: true });
9908
+ }
9909
+ };
9910
+ const run = () => {
9911
+ if (footerDragRef.current || navDragRef.current || applying) return;
9912
+ applying = true;
9913
+ observer?.disconnect();
9914
+ try {
9915
+ const content = contentForNav();
9916
+ reconcileNavbarItemsFromContent(content);
9917
+ reconcileFooterOrderFromContent(content);
9918
+ document.querySelectorAll("footer [data-ohw-href-key]").forEach((el) => {
9919
+ if (isFooterHrefKey(el.getAttribute("data-ohw-href-key"))) {
9920
+ disableNativeHrefDrag(el);
9921
+ }
9922
+ });
9923
+ document.querySelectorAll(
9924
+ "nav [data-ohw-href-key], [data-ohw-nav-container] [data-ohw-href-key], [data-ohw-nav-drawer] [data-ohw-href-key]"
9925
+ ).forEach((el) => {
9926
+ if (isNavbarHrefKey(el.getAttribute("data-ohw-href-key"))) {
9927
+ disableNativeHrefDrag(el);
9928
+ }
9929
+ });
9930
+ resyncSelectedNavigationItem();
9931
+ if (isEditMode) syncNavigationDragCursorAttrs();
9932
+ } finally {
9933
+ applying = false;
9934
+ attach();
9935
+ }
9936
+ };
9937
+ const schedule = () => {
9938
+ if (applying || rafId != null) return;
9939
+ rafId = requestAnimationFrame(() => {
9940
+ rafId = null;
9941
+ run();
9942
+ });
9943
+ };
9944
+ observer = new MutationObserver(schedule);
8017
9945
  run();
8018
- const nav = document.querySelector("nav");
8019
- if (!nav) return;
8020
- const observer = new MutationObserver(() => {
8021
- requestAnimationFrame(run);
8022
- });
8023
- observer.observe(nav, { childList: true, subtree: true });
8024
- return () => observer.disconnect();
8025
- }, [isEditMode, pathname, subdomain, fetchState]);
8026
- useEffect7(() => {
9946
+ return () => {
9947
+ if (rafId != null) cancelAnimationFrame(rafId);
9948
+ observer?.disconnect();
9949
+ };
9950
+ }, [isEditMode, pathname, subdomain, fetchState, resyncSelectedNavigationItem]);
9951
+ useEffect8(() => {
8027
9952
  if (!isEditMode) return;
8028
9953
  const measure = () => {
8029
9954
  const h = document.body.scrollHeight;
@@ -8047,7 +9972,7 @@ function OhhwellsBridge() {
8047
9972
  window.removeEventListener("resize", handleResize);
8048
9973
  };
8049
9974
  }, [pathname, isEditMode, postToParent2]);
8050
- useEffect7(() => {
9975
+ useEffect8(() => {
8051
9976
  if (!subdomainFromQuery || isEditMode) return;
8052
9977
  const handleClick = (e) => {
8053
9978
  const anchor = e.target.closest("a");
@@ -8063,19 +9988,24 @@ function OhhwellsBridge() {
8063
9988
  document.addEventListener("click", handleClick, true);
8064
9989
  return () => document.removeEventListener("click", handleClick, true);
8065
9990
  }, [subdomainFromQuery, isEditMode, router]);
8066
- useEffect7(() => {
9991
+ useEffect8(() => {
8067
9992
  if (!isEditMode) {
8068
9993
  editStylesRef.current?.base.remove();
8069
9994
  editStylesRef.current?.forceHover.remove();
8070
9995
  editStylesRef.current?.stateViews.remove();
8071
9996
  editStylesRef.current = null;
9997
+ document.querySelectorAll("[data-ohw-can-drag]").forEach((el) => {
9998
+ el.removeAttribute("data-ohw-can-drag");
9999
+ });
8072
10000
  return;
8073
10001
  }
8074
- if (!editStylesRef.current) {
8075
- const initialVh = window.innerHeight;
8076
- const base = document.createElement("style");
8077
- base.setAttribute("data-ohw-edit-style", "");
8078
- base.textContent = `
10002
+ const existing = editStylesRef.current;
10003
+ let initialVh = window.innerHeight;
10004
+ if (existing?.base.textContent) {
10005
+ const match = existing.base.textContent.match(/\.min-h-screen[^{]*\{[^}]*min-height:\s*(\d+)px/);
10006
+ if (match) initialVh = parseInt(match[1], 10);
10007
+ }
10008
+ const baseCss = `
8079
10009
  html { height: auto !important; }
8080
10010
  body { height: auto !important; min-height: 0 !important; overflow: hidden !important; }
8081
10011
  .min-h-screen, .min-h-svh, .min-h-dvh { min-height: ${initialVh}px !important; }
@@ -8086,7 +10016,25 @@ function OhhwellsBridge() {
8086
10016
  [data-ohw-editable] {
8087
10017
  display: block;
8088
10018
  }
8089
- [data-ohw-editable]:not([contenteditable]):not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"]):not([data-ohw-editable="video"]) { cursor: text !important; }
10019
+ /* Body text (no item-action toolbar) \u2014 first click enters text edit \u2192 I-beam.
10020
+ Exclude select-first nav/footer labels so grab/default on [data-ohw-href-key] can win. */
10021
+ [data-ohw-editable]:not([contenteditable]):not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"]):not([data-ohw-editable="video"]):not([data-ohw-editable="link"]):not(:is([data-ohw-href-key] *)) {
10022
+ cursor: text !important;
10023
+ }
10024
+ /* Select-first items: default arrow; grab only when drag-start is allowed (data-ohw-can-drag). */
10025
+ [data-ohw-href-key]:not([data-ohw-selected]),
10026
+ [data-ohw-href-key]:not([data-ohw-selected]) * {
10027
+ cursor: default !important;
10028
+ }
10029
+ [data-ohw-href-key][data-ohw-can-drag]:not([data-ohw-selected]),
10030
+ [data-ohw-href-key][data-ohw-can-drag]:not([data-ohw-selected]) * {
10031
+ cursor: grab !important;
10032
+ }
10033
+ /* Selected (action-toolbar) \u2014 next click enters text edit at caret. */
10034
+ [data-ohw-href-key][data-ohw-selected],
10035
+ [data-ohw-href-key][data-ohw-selected] * {
10036
+ cursor: text !important;
10037
+ }
8090
10038
  [data-ohw-editable="image"], [data-ohw-editable="image"] *,
8091
10039
  [data-ohw-editable="video"], [data-ohw-editable="video"] *,
8092
10040
  [data-ohw-editable="bg-image"], [data-ohw-editable="bg-image"] * { cursor: pointer !important; }
@@ -8102,9 +10050,32 @@ function OhhwellsBridge() {
8102
10050
  outline: none !important;
8103
10051
  outline-offset: 0 !important;
8104
10052
  }
8105
- [data-ohw-editable][contenteditable] {
10053
+ /* Editor chrome owns selection \u2014 suppress native focus rings on nav/footer items. */
10054
+ nav [data-ohw-href-key]:focus,
10055
+ nav [data-ohw-href-key]:focus-visible,
10056
+ nav [data-ohw-href-key]:focus-within,
10057
+ footer [data-ohw-href-key]:focus,
10058
+ footer [data-ohw-href-key]:focus-visible,
10059
+ footer [data-ohw-href-key]:focus-within,
10060
+ nav [data-ohw-href-key] [data-ohw-editable]:focus,
10061
+ nav [data-ohw-href-key] [data-ohw-editable]:focus-visible,
10062
+ footer [data-ohw-href-key] [data-ohw-editable]:focus,
10063
+ footer [data-ohw-href-key] [data-ohw-editable]:focus-visible {
10064
+ outline: none !important;
10065
+ outline-offset: 0 !important;
10066
+ box-shadow: none !important;
10067
+ }
10068
+ /* Text edit wins over grab/default (must beat [data-ohw-can-drag] *). */
10069
+ [data-ohw-editing],
10070
+ [data-ohw-editing] *,
10071
+ [data-ohw-editable][contenteditable],
10072
+ [data-ohw-editable][contenteditable] *,
10073
+ nav [data-ohw-href-key] [data-ohw-editable][contenteditable],
10074
+ nav [data-ohw-href-key] [data-ohw-editable][contenteditable] *,
10075
+ footer [data-ohw-href-key] [data-ohw-editable][contenteditable],
10076
+ footer [data-ohw-href-key] [data-ohw-editable][contenteditable] * {
8106
10077
  outline: none !important;
8107
- caret-color: ${PRIMARY2};
10078
+ caret-color: ${PRIMARY2} !important;
8108
10079
  cursor: text !important;
8109
10080
  }
8110
10081
  [data-ohw-editable][contenteditable]::selection,
@@ -8113,6 +10084,9 @@ function OhhwellsBridge() {
8113
10084
  [data-ohw-editable-state][data-ohw-active-state] [data-ohw-editable] { pointer-events: auto !important; }
8114
10085
  [data-ohw-editable-state][data-ohw-active-state][data-ohw-editable] { pointer-events: auto !important; }
8115
10086
  `;
10087
+ if (!existing) {
10088
+ const base = document.createElement("style");
10089
+ base.setAttribute("data-ohw-edit-style", "");
8116
10090
  const forceHover = document.createElement("style");
8117
10091
  forceHover.setAttribute("data-ohw-active-state-style", "");
8118
10092
  const stateViews = document.createElement("style");
@@ -8128,14 +10102,43 @@ function OhhwellsBridge() {
8128
10102
  document.head.appendChild(stateViews);
8129
10103
  editStylesRef.current = { base, forceHover, stateViews };
8130
10104
  }
10105
+ editStylesRef.current.base.textContent = baseCss;
10106
+ syncNavigationDragCursorAttrs();
8131
10107
  refreshStateRules();
8132
10108
  const handleClick = (e) => {
10109
+ if (suppressNextClickRef.current || Date.now() < suppressClickUntilRef.current) {
10110
+ suppressNextClickRef.current = false;
10111
+ e.preventDefault();
10112
+ e.stopPropagation();
10113
+ return;
10114
+ }
8133
10115
  const target = e.target;
8134
10116
  if (target.closest("[data-ohw-toolbar]")) return;
8135
10117
  if (target.closest("[data-ohw-state-toggle]")) return;
8136
10118
  if (target.closest("[data-ohw-max-badge]")) return;
8137
10119
  if (isInsideLinkEditor(target)) return;
8138
10120
  if (target.closest('[data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
10121
+ if (target.closest("[data-ohw-item-drag-surface]")) {
10122
+ e.preventDefault();
10123
+ e.stopPropagation();
10124
+ const selected = selectedElRef.current;
10125
+ if (selected && isNavigationItem(selected)) {
10126
+ const editable2 = selected.querySelector('[data-ohw-editable="text"]') ?? selected.querySelector("[data-ohw-editable]");
10127
+ if (editable2) {
10128
+ activateRef.current(editable2, {
10129
+ caretX: e.clientX,
10130
+ caretY: e.clientY
10131
+ });
10132
+ }
10133
+ } else if (selected?.hasAttribute("data-ohw-footer-col")) {
10134
+ const link = listFooterLinksInColumn(selected).find((el) => {
10135
+ const r2 = el.getBoundingClientRect();
10136
+ return e.clientX >= r2.left && e.clientX <= r2.right && e.clientY >= r2.top && e.clientY <= r2.bottom;
10137
+ });
10138
+ if (link) selectRef.current(link);
10139
+ }
10140
+ return;
10141
+ }
8139
10142
  const navFromChrome = resolveNavContainerSelectionTarget(target, e.clientX, e.clientY);
8140
10143
  if (navFromChrome) {
8141
10144
  e.preventDefault();
@@ -8143,10 +10146,45 @@ function OhhwellsBridge() {
8143
10146
  selectFrameRef.current(navFromChrome);
8144
10147
  return;
8145
10148
  }
10149
+ const footerFromChrome = resolveFooterColumnSelectionTarget(target, e.clientX, e.clientY);
10150
+ if (footerFromChrome) {
10151
+ e.preventDefault();
10152
+ e.stopPropagation();
10153
+ selectFrameRef.current(footerFromChrome);
10154
+ return;
10155
+ }
8146
10156
  e.preventDefault();
8147
10157
  e.stopPropagation();
8148
10158
  return;
8149
10159
  }
10160
+ if (activeElRef.current) {
10161
+ const active = activeElRef.current;
10162
+ const inActive = active === target || active.contains(target);
10163
+ const navAnchor = getNavigationItemAnchor(active);
10164
+ const inActiveNav = Boolean(
10165
+ navAnchor && (navAnchor === target || navAnchor.contains(target))
10166
+ );
10167
+ if (inActive || inActiveNav) {
10168
+ e.preventDefault();
10169
+ e.stopPropagation();
10170
+ const selection = window.getSelection();
10171
+ const hasRangeSelection = Boolean(selection && !selection.isCollapsed);
10172
+ console.log(
10173
+ "[OHW DEBUG handleClick] already-active branch",
10174
+ "detail=",
10175
+ e.detail,
10176
+ "hasRangeSelection=",
10177
+ hasRangeSelection,
10178
+ "willPlaceCaret=",
10179
+ e.detail < 2 && !hasRangeSelection
10180
+ );
10181
+ if (e.detail < 2 && !hasRangeSelection) {
10182
+ placeCaretAtPoint(active, e.clientX, e.clientY);
10183
+ refreshActiveCommandsRef.current();
10184
+ }
10185
+ return;
10186
+ }
10187
+ }
8150
10188
  const editable = target.closest("[data-ohw-editable]");
8151
10189
  if (editable) {
8152
10190
  if (editable.dataset.ohwEditable === "link") {
@@ -8183,6 +10221,15 @@ function OhhwellsBridge() {
8183
10221
  }
8184
10222
  e.preventDefault();
8185
10223
  e.stopPropagation();
10224
+ console.log(
10225
+ "[OHW DEBUG handleClick] first-activation branch",
10226
+ "detail=",
10227
+ e.detail,
10228
+ "selectionAtClick=",
10229
+ JSON.stringify(window.getSelection()?.toString() ?? ""),
10230
+ "collapsedAtClick=",
10231
+ window.getSelection()?.isCollapsed
10232
+ );
8186
10233
  activateRef.current(editable);
8187
10234
  return;
8188
10235
  }
@@ -8190,10 +10237,26 @@ function OhhwellsBridge() {
8190
10237
  if (hrefAnchor) {
8191
10238
  e.preventDefault();
8192
10239
  e.stopPropagation();
8193
- if (selectedElRef.current === hrefAnchor) return;
10240
+ if (selectedElRef.current === hrefAnchor) {
10241
+ const textEditable = hrefAnchor.querySelector('[data-ohw-editable="text"]') ?? hrefAnchor.querySelector("[data-ohw-editable]");
10242
+ if (textEditable) {
10243
+ activateRef.current(textEditable, {
10244
+ caretX: e.clientX,
10245
+ caretY: e.clientY
10246
+ });
10247
+ }
10248
+ return;
10249
+ }
8194
10250
  selectRef.current(hrefAnchor);
8195
10251
  return;
8196
10252
  }
10253
+ const footerColClick = target.closest("[data-ohw-footer-col]");
10254
+ if (footerColClick) {
10255
+ e.preventDefault();
10256
+ e.stopPropagation();
10257
+ selectFrameRef.current(footerColClick);
10258
+ return;
10259
+ }
8197
10260
  const navContainerToSelect = resolveNavContainerSelectionTarget(target, e.clientX, e.clientY);
8198
10261
  if (navContainerToSelect) {
8199
10262
  e.preventDefault();
@@ -8201,6 +10264,13 @@ function OhhwellsBridge() {
8201
10264
  selectFrameRef.current(navContainerToSelect);
8202
10265
  return;
8203
10266
  }
10267
+ const footerColumnToSelect = resolveFooterColumnSelectionTarget(target, e.clientX, e.clientY);
10268
+ if (footerColumnToSelect) {
10269
+ e.preventDefault();
10270
+ e.stopPropagation();
10271
+ selectFrameRef.current(footerColumnToSelect);
10272
+ return;
10273
+ }
8204
10274
  const selectedContainer = selectedElRef.current;
8205
10275
  if (selectedContainer && isNavigationContainer(selectedContainer)) {
8206
10276
  const navItem = getNavigationItemAnchor(target);
@@ -8260,6 +10330,13 @@ function OhhwellsBridge() {
8260
10330
  const handleMouseOver = (e) => {
8261
10331
  if (document.documentElement.hasAttribute("data-ohw-section-picking")) return;
8262
10332
  const target = e.target;
10333
+ if (linkPopoverOpenRef.current) {
10334
+ hoveredItemElRef.current = null;
10335
+ setHoveredItemRect(null);
10336
+ hoveredNavContainerRef.current = null;
10337
+ setHoveredNavContainerRect(null);
10338
+ return;
10339
+ }
8263
10340
  if (toolbarVariantRef.current !== "select-frame") {
8264
10341
  const navContainer = target.closest("[data-ohw-nav-container]");
8265
10342
  if (navContainer && !getNavigationItemAnchor(target)) {
@@ -8279,12 +10356,21 @@ function OhhwellsBridge() {
8279
10356
  hoveredNavContainerRef.current = null;
8280
10357
  setHoveredNavContainerRect(null);
8281
10358
  const selected2 = selectedElRef.current;
8282
- if (selected2 === navAnchor || selected2?.contains(navAnchor)) return;
10359
+ if (selected2 === navAnchor) return;
8283
10360
  clearHrefKeyHover(navAnchor);
8284
10361
  hoveredItemElRef.current = navAnchor;
8285
10362
  setHoveredItemRect(navAnchor.getBoundingClientRect());
8286
10363
  return;
8287
10364
  }
10365
+ const footerCol = target.closest("[data-ohw-footer-col]") ?? target.closest("[data-ohw-footer-column]");
10366
+ if (footerCol) {
10367
+ hoveredNavContainerRef.current = null;
10368
+ setHoveredNavContainerRect(null);
10369
+ if (selectedElRef.current === footerCol) return;
10370
+ hoveredItemElRef.current = footerCol;
10371
+ setHoveredItemRect(footerCol.getBoundingClientRect());
10372
+ return;
10373
+ }
8288
10374
  const editable = target.closest("[data-ohw-editable]");
8289
10375
  if (!editable) return;
8290
10376
  const selected = selectedElRef.current;
@@ -8302,14 +10388,25 @@ function OhhwellsBridge() {
8302
10388
  };
8303
10389
  const handleMouseOut = (e) => {
8304
10390
  const target = e.target;
8305
- if (target.closest("[data-ohw-nav-container]")) {
10391
+ if (target.closest("[data-ohw-nav-container], [data-ohw-footer-col], [data-ohw-footer-column]")) {
8306
10392
  const related2 = e.relatedTarget instanceof Element ? e.relatedTarget : null;
8307
- if (related2?.closest("[data-ohw-nav-container], [data-ohw-navbar-container-chrome], [data-ohw-navbar-add-button]")) {
10393
+ if (related2?.closest("[data-ohw-nav-container], [data-ohw-footer-col], [data-ohw-footer-column], [data-ohw-navbar-container-chrome], [data-ohw-navbar-add-button]")) {
8308
10394
  return;
8309
10395
  }
8310
10396
  hoveredNavContainerRef.current = null;
8311
10397
  setHoveredNavContainerRect(null);
8312
10398
  }
10399
+ const footerCol = target.closest("[data-ohw-footer-col]") ?? target.closest("[data-ohw-footer-column]");
10400
+ if (footerCol && hoveredItemElRef.current === footerCol) {
10401
+ const related2 = e.relatedTarget instanceof Element ? e.relatedTarget : null;
10402
+ if (related2?.closest("[data-ohw-drag-handle-container], [data-ohw-item-interaction]")) return;
10403
+ if (related2?.closest("[data-ohw-footer-col], [data-ohw-footer-column]") === footerCol) {
10404
+ return;
10405
+ }
10406
+ hoveredItemElRef.current = null;
10407
+ setHoveredItemRect(null);
10408
+ return;
10409
+ }
8313
10410
  const navAnchor = getNavigationItemAnchor(target);
8314
10411
  if (navAnchor) {
8315
10412
  const related2 = e.relatedTarget instanceof Element ? e.relatedTarget : null;
@@ -8443,43 +10540,75 @@ function OhhwellsBridge() {
8443
10540
  if (toolbarVariantRef.current === "select-frame") {
8444
10541
  hoveredNavContainerRef.current = null;
8445
10542
  setHoveredNavContainerRect(null);
8446
- return;
8447
10543
  }
8448
- const navContainer = document.querySelector("[data-ohw-nav-container]");
8449
- if (!navContainer) {
8450
- hoveredNavContainerRef.current = null;
8451
- setHoveredNavContainerRect(null);
8452
- return;
10544
+ const navContainers = Array.from(
10545
+ document.querySelectorAll("[data-ohw-nav-container]")
10546
+ );
10547
+ const footerColumns = listFooterColumns();
10548
+ for (const container of [...navContainers, ...footerColumns]) {
10549
+ const containerRect = container.getBoundingClientRect();
10550
+ const overContainer = x >= containerRect.left && x <= containerRect.right && y >= containerRect.top && y <= containerRect.bottom;
10551
+ if (!overContainer) continue;
10552
+ const navItemHit = Array.from(
10553
+ container.querySelectorAll("[data-ohw-href-key]")
10554
+ ).find((el) => {
10555
+ if (!isNavigationItem(el) || isNavbarButton2(el)) return false;
10556
+ const itemRect = el.getBoundingClientRect();
10557
+ return x >= itemRect.left && x <= itemRect.right && y >= itemRect.top && y <= itemRect.bottom;
10558
+ });
10559
+ if (navItemHit) {
10560
+ hoveredNavContainerRef.current = null;
10561
+ setHoveredNavContainerRect(null);
10562
+ const selected = selectedElRef.current;
10563
+ if (selected !== navItemHit) {
10564
+ clearHrefKeyHover(navItemHit);
10565
+ hoveredItemElRef.current = navItemHit;
10566
+ setHoveredItemRect(navItemHit.getBoundingClientRect());
10567
+ } else if (hoveredItemElRef.current === navItemHit) {
10568
+ hoveredItemElRef.current = null;
10569
+ setHoveredItemRect(null);
10570
+ }
10571
+ return;
10572
+ }
8453
10573
  }
8454
- const containerRect = navContainer.getBoundingClientRect();
8455
- const overContainer = x >= containerRect.left && x <= containerRect.right && y >= containerRect.top && y <= containerRect.bottom;
8456
- if (!overContainer) {
10574
+ if (toolbarVariantRef.current !== "select-frame") {
10575
+ for (const container of navContainers) {
10576
+ const containerRect = container.getBoundingClientRect();
10577
+ const overContainer = x >= containerRect.left && x <= containerRect.right && y >= containerRect.top && y <= containerRect.bottom;
10578
+ if (!overContainer) continue;
10579
+ if (isPointOverNavItem(container, x, y)) continue;
10580
+ hoveredNavContainerRef.current = container;
10581
+ setHoveredNavContainerRect(container.getBoundingClientRect());
10582
+ hoveredItemElRef.current = null;
10583
+ setHoveredItemRect(null);
10584
+ return;
10585
+ }
8457
10586
  hoveredNavContainerRef.current = null;
8458
10587
  setHoveredNavContainerRect(null);
8459
- return;
8460
- }
8461
- const navItemHit = Array.from(
8462
- navContainer.querySelectorAll("[data-ohw-href-key]")
8463
- ).find((el) => {
8464
- if (!isNavigationItem(el) || isNavbarButton2(el)) return false;
8465
- const itemRect = el.getBoundingClientRect();
8466
- return x >= itemRect.left && x <= itemRect.right && y >= itemRect.top && y <= itemRect.bottom;
8467
- });
8468
- if (navItemHit) {
10588
+ } else {
8469
10589
  hoveredNavContainerRef.current = null;
8470
10590
  setHoveredNavContainerRect(null);
8471
- const selected = selectedElRef.current;
8472
- if (selected !== navItemHit && !selected?.contains(navItemHit)) {
8473
- clearHrefKeyHover(navItemHit);
8474
- hoveredItemElRef.current = navItemHit;
8475
- setHoveredItemRect(navItemHit.getBoundingClientRect());
10591
+ }
10592
+ for (const column of footerColumns) {
10593
+ const containerRect = column.getBoundingClientRect();
10594
+ const overContainer = x >= containerRect.left && x <= containerRect.right && y >= containerRect.top && y <= containerRect.bottom;
10595
+ if (!overContainer) continue;
10596
+ if (isPointOverNavItem(column, x, y)) continue;
10597
+ if (selectedElRef.current === column) {
10598
+ if (hoveredItemElRef.current === column) {
10599
+ hoveredItemElRef.current = null;
10600
+ setHoveredItemRect(null);
10601
+ }
10602
+ return;
8476
10603
  }
10604
+ hoveredItemElRef.current = column;
10605
+ setHoveredItemRect(column.getBoundingClientRect());
8477
10606
  return;
8478
10607
  }
8479
- hoveredNavContainerRef.current = navContainer;
8480
- setHoveredNavContainerRect(containerRect);
8481
- hoveredItemElRef.current = null;
8482
- setHoveredItemRect(null);
10608
+ if (hoveredItemElRef.current?.hasAttribute("data-ohw-footer-col") || hoveredItemElRef.current?.hasAttribute("data-ohw-footer-column")) {
10609
+ hoveredItemElRef.current = null;
10610
+ setHoveredItemRect(null);
10611
+ }
8483
10612
  };
8484
10613
  const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
8485
10614
  if (linkPopoverOpenRef.current) {
@@ -8500,6 +10629,10 @@ function OhhwellsBridge() {
8500
10629
  }
8501
10630
  hoveredNavContainerRef.current = null;
8502
10631
  setHoveredNavContainerRect(null);
10632
+ if (hoveredItemElRef.current?.hasAttribute("data-ohw-footer-col") || hoveredItemElRef.current?.hasAttribute("data-ohw-footer-column")) {
10633
+ hoveredItemElRef.current = null;
10634
+ setHoveredItemRect(null);
10635
+ }
8503
10636
  const toggleEl = document.querySelector("[data-ohw-state-toggle]");
8504
10637
  if (toggleEl) {
8505
10638
  const tr = toggleEl.getBoundingClientRect();
@@ -8747,6 +10880,15 @@ function OhhwellsBridge() {
8747
10880
  probeHoverCardsAt(clientX, clientY);
8748
10881
  };
8749
10882
  const handleDragOver = (e) => {
10883
+ const footerSession = footerDragRef.current;
10884
+ if (footerSession) {
10885
+ e.preventDefault();
10886
+ if (e.dataTransfer) e.dataTransfer.dropEffect = "move";
10887
+ const slot = footerSession.kind === "link" && footerSession.hrefKey ? hitTestLinkDropSlot(e.clientX, e.clientY, footerSession.hrefKey) : hitTestColumnDropSlot(e.clientX, e.clientY);
10888
+ refreshFooterDragVisualsRef.current(footerSession, slot, e.clientX, e.clientY);
10889
+ return;
10890
+ }
10891
+ if (onNavDragOver(e)) return;
8750
10892
  e.preventDefault();
8751
10893
  const el = findImageAtPoint(e.clientX, e.clientY, false);
8752
10894
  if (!el) {
@@ -8909,8 +11051,8 @@ function OhhwellsBridge() {
8909
11051
  if (sibling !== el) sibling.innerHTML = html;
8910
11052
  });
8911
11053
  const timers = autoSaveTimers.current;
8912
- const existing = timers.get(key);
8913
- if (existing) clearTimeout(existing);
11054
+ const existing2 = timers.get(key);
11055
+ if (existing2) clearTimeout(existing2);
8914
11056
  timers.set(key, setTimeout(() => {
8915
11057
  timers.delete(key);
8916
11058
  postToParentRef.current({ type: "ow:change", nodes: [{ key, text: html }] });
@@ -8953,6 +11095,7 @@ function OhhwellsBridge() {
8953
11095
  }
8954
11096
  editContentRef.current = { ...editContentRef.current, ...content };
8955
11097
  reconcileNavbarItemsFromContent(editContentRef.current);
11098
+ syncNavigationDragCursorAttrs();
8956
11099
  enforceLinkHrefs();
8957
11100
  postToParentRef.current({ type: "ow:hydrate-done" });
8958
11101
  };
@@ -8973,6 +11116,48 @@ function OhhwellsBridge() {
8973
11116
  if (e.data?.type !== "ui:escape") return;
8974
11117
  if (linkPopoverOpenRef.current) {
8975
11118
  setLinkPopoverRef.current(null);
11119
+ return;
11120
+ }
11121
+ if (activeElRef.current) {
11122
+ const hrefCtx = getHrefKeyFromElement(activeElRef.current);
11123
+ const navAnchor = hrefCtx ? getNavigationItemAnchor(hrefCtx.anchor) : null;
11124
+ if (navAnchor) {
11125
+ const el = activeElRef.current;
11126
+ if (originalContentRef.current !== null) {
11127
+ el.innerHTML = originalContentRef.current;
11128
+ }
11129
+ el.removeAttribute("contenteditable");
11130
+ el.removeAttribute("data-ohw-editing");
11131
+ activeElRef.current = null;
11132
+ setMaxBadge(null);
11133
+ setActiveCommands(/* @__PURE__ */ new Set());
11134
+ setToolbarShowEditLink(false);
11135
+ postToParentRef.current({ type: "ow:exit-edit" });
11136
+ reselectNavigationItemRef.current(navAnchor);
11137
+ return;
11138
+ }
11139
+ if (originalContentRef.current !== null) {
11140
+ activeElRef.current.innerHTML = originalContentRef.current;
11141
+ const key = activeElRef.current.dataset.ohwKey;
11142
+ if (key) {
11143
+ postToParentRef.current({ type: "ow:change", nodes: [{ key, text: originalContentRef.current }] });
11144
+ }
11145
+ }
11146
+ deselectRef.current();
11147
+ deactivateRef.current();
11148
+ return;
11149
+ }
11150
+ if (selectedElRef.current) {
11151
+ if (toolbarVariantRef.current === "select-frame") {
11152
+ deselectRef.current();
11153
+ return;
11154
+ }
11155
+ const parent = getNavigationSelectionParent(selectedElRef.current);
11156
+ if (parent && isNavigationContainer(parent)) {
11157
+ selectFrameRef.current(parent);
11158
+ } else {
11159
+ deselectRef.current();
11160
+ }
8976
11161
  }
8977
11162
  };
8978
11163
  window.addEventListener("message", handleUiEscape);
@@ -8997,7 +11182,7 @@ function OhhwellsBridge() {
8997
11182
  return;
8998
11183
  }
8999
11184
  const parent = getNavigationSelectionParent(selectedElRef.current);
9000
- if (parent) {
11185
+ if (parent && isNavigationContainer(parent)) {
9001
11186
  e.preventDefault();
9002
11187
  selectFrameRef.current(parent);
9003
11188
  } else {
@@ -9015,6 +11200,7 @@ function OhhwellsBridge() {
9015
11200
  el2.innerHTML = originalContentRef.current;
9016
11201
  }
9017
11202
  el2.removeAttribute("contenteditable");
11203
+ el2.removeAttribute("data-ohw-editing");
9018
11204
  activeElRef.current = null;
9019
11205
  setMaxBadge(null);
9020
11206
  setActiveCommands(/* @__PURE__ */ new Set());
@@ -9066,6 +11252,26 @@ function OhhwellsBridge() {
9066
11252
  if (siblingHintElRef.current) {
9067
11253
  setSiblingHintRect(siblingHintElRef.current.getBoundingClientRect());
9068
11254
  }
11255
+ const selected = selectedElRef.current;
11256
+ if (selected && !footerDragRef.current && !navDragRef.current) {
11257
+ if (selected.hasAttribute("data-ohw-footer-col") || Boolean(selected.closest("footer") && isInferredFooterGroup(selected))) {
11258
+ setSiblingHintRects(
11259
+ listFooterColumns().filter((column) => column !== selected).map((column) => column.getBoundingClientRect())
11260
+ );
11261
+ } else if (isNavigationItem(selected)) {
11262
+ setSiblingHintRects(collectNavigationItemSiblingHintRects(selected));
11263
+ }
11264
+ }
11265
+ if (footerDragRef.current) {
11266
+ const session = footerDragRef.current;
11267
+ const slot = session.kind === "link" && session.hrefKey ? hitTestLinkDropSlot(session.lastClientX, session.lastClientY, session.hrefKey) : hitTestColumnDropSlot(session.lastClientX, session.lastClientY);
11268
+ refreshFooterDragVisualsRef.current(session, slot, session.lastClientX, session.lastClientY);
11269
+ }
11270
+ if (navDragRef.current) {
11271
+ const session = navDragRef.current;
11272
+ const slot = hitTestNavDropSlot(session.lastClientX, session.lastClientY, session.hrefKey);
11273
+ refreshNavDragVisualsRef.current(session, slot, session.lastClientX, session.lastClientY);
11274
+ }
9069
11275
  if (hoveredImageRef.current) {
9070
11276
  const el = hoveredImageRef.current;
9071
11277
  const r2 = el.getBoundingClientRect();
@@ -9076,7 +11282,7 @@ function OhhwellsBridge() {
9076
11282
  };
9077
11283
  const handleSave = (e) => {
9078
11284
  if (e.data?.type !== "ow:save") return;
9079
- const nodes = collectEditableNodes();
11285
+ const nodes = collectEditableNodes(editContentRef.current);
9080
11286
  const tracker = document.querySelector("[data-ohw-sections-tracker]");
9081
11287
  if (tracker?.textContent) nodes.push({ key: "__ohw_sections", type: "sections", text: tracker.textContent });
9082
11288
  postToParentRef.current({ type: "ow:save-result", nodes });
@@ -9134,30 +11340,83 @@ function OhhwellsBridge() {
9134
11340
  const h = document.documentElement.scrollHeight;
9135
11341
  if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
9136
11342
  };
9137
- const handleSelectionChange = () => {
9138
- if (!activeElRef.current) return;
11343
+ let selectionChangeRaf = null;
11344
+ const runSelectionChange = () => {
11345
+ selectionChangeRaf = null;
11346
+ const activeRoot = activeElRef.current;
11347
+ if (!activeRoot) return;
9139
11348
  const next = /* @__PURE__ */ new Set();
9140
- for (const cmd of ["bold", "italic", "underline", "strikeThrough", "insertUnorderedList", "insertOrderedList"]) {
9141
- try {
9142
- if (document.queryCommandState(cmd)) next.add(cmd);
9143
- } catch {
9144
- }
9145
- }
9146
11349
  const sel = window.getSelection();
9147
- const anchor = sel?.anchorNode;
9148
- if (anchor) {
9149
- const el = anchor.nodeType === Node.TEXT_NODE ? anchor.parentElement : anchor;
9150
- const block = el?.closest("div, p, h1, h2, h3, h4, h5, h6, li, td, th") ?? el;
9151
- if (block) {
9152
- const align = getComputedStyle(block).textAlign;
9153
- if (align === "center") next.add("justifyCenter");
9154
- else if (align === "right" || align === "end") next.add("justifyRight");
9155
- else next.add("justifyLeft");
11350
+ let startNode = null;
11351
+ if (sel && sel.rangeCount > 0) {
11352
+ const range = sel.getRangeAt(0);
11353
+ startNode = range.startContainer;
11354
+ if (startNode.nodeType === Node.ELEMENT_NODE) {
11355
+ const el = startNode;
11356
+ startNode = el.childNodes[range.startOffset] ?? el.childNodes[range.startOffset - 1] ?? el;
9156
11357
  }
9157
11358
  }
9158
- setActiveCommands(next);
11359
+ const startEl = startNode ? startNode.nodeType === Node.TEXT_NODE ? startNode.parentElement : startNode : null;
11360
+ if (startEl) {
11361
+ const cs = getComputedStyle(startEl);
11362
+ const weight = parseInt(cs.fontWeight, 10);
11363
+ let hasBold = cs.fontWeight === "bold" || !Number.isNaN(weight) && weight >= 600;
11364
+ let hasItalic = cs.fontStyle === "italic" || cs.fontStyle === "oblique";
11365
+ let hasUnderline = false;
11366
+ let hasStrike = false;
11367
+ let hasUl = false;
11368
+ let hasOl = false;
11369
+ for (let el = startEl; el; el = el.parentElement) {
11370
+ if (el.tagName === "B" || el.tagName === "STRONG") hasBold = true;
11371
+ if (el.tagName === "I" || el.tagName === "EM") hasItalic = true;
11372
+ const decoration = getComputedStyle(el).textDecorationLine;
11373
+ if (decoration.includes("underline") || el.tagName === "U" || el.tagName === "INS") hasUnderline = true;
11374
+ if (decoration.includes("line-through") || el.tagName === "S" || el.tagName === "STRIKE" || el.tagName === "DEL") hasStrike = true;
11375
+ if (el.tagName === "UL") hasUl = true;
11376
+ if (el.tagName === "OL") hasOl = true;
11377
+ if (el === activeRoot) break;
11378
+ }
11379
+ if (hasBold) next.add("bold");
11380
+ if (hasItalic) next.add("italic");
11381
+ if (hasUnderline) next.add("underline");
11382
+ if (hasStrike) next.add("strikeThrough");
11383
+ if (hasUl) next.add("insertUnorderedList");
11384
+ if (hasOl) next.add("insertOrderedList");
11385
+ const block = startEl.closest("div, p, h1, h2, h3, h4, h5, h6, li, td, th") ?? startEl;
11386
+ const align = getComputedStyle(block).textAlign;
11387
+ if (align === "center") next.add("justifyCenter");
11388
+ else if (align === "right" || align === "end") next.add("justifyRight");
11389
+ else next.add("justifyLeft");
11390
+ }
11391
+ setActiveCommands((prev) => {
11392
+ if (prev.size === next.size) {
11393
+ let same = true;
11394
+ for (const cmd of prev) {
11395
+ if (!next.has(cmd)) {
11396
+ same = false;
11397
+ break;
11398
+ }
11399
+ }
11400
+ if (same) return prev;
11401
+ }
11402
+ return next;
11403
+ });
11404
+ };
11405
+ const handleSelectionChange = () => {
11406
+ if (pointerHeldRef.current) return;
11407
+ if (selectionChangeRaf !== null) return;
11408
+ selectionChangeRaf = requestAnimationFrame(runSelectionChange);
11409
+ };
11410
+ const markPointerHeld = (e) => {
11411
+ if (e.button !== 0) return;
11412
+ pointerHeldRef.current = true;
11413
+ };
11414
+ const markPointerReleased = () => {
11415
+ if (!pointerHeldRef.current) return;
11416
+ pointerHeldRef.current = false;
11417
+ handleSelectionChange();
9159
11418
  };
9160
- refreshActiveCommandsRef.current = handleSelectionChange;
11419
+ refreshActiveCommandsRef.current = runSelectionChange;
9161
11420
  const handleDocMouseLeave = () => {
9162
11421
  hoveredImageRef.current = null;
9163
11422
  setMediaHover(null);
@@ -9202,15 +11461,15 @@ function OhhwellsBridge() {
9202
11461
  };
9203
11462
  const applyToolbarPos = (rect) => {
9204
11463
  const ps = parentScrollRef.current;
9205
- const approxW = 330;
11464
+ const measuredW = toolbarElRef.current?.offsetWidth || 330;
9206
11465
  if (toolbarElRef.current) {
9207
- const { top, left, transform } = calcToolbarPos(rect, ps, approxW);
11466
+ const { top, left, transform } = calcToolbarPos(rect, ps, measuredW);
9208
11467
  toolbarElRef.current.style.top = `${top}px`;
9209
11468
  toolbarElRef.current.style.left = `${left}px`;
9210
11469
  toolbarElRef.current.style.transform = transform;
9211
11470
  }
9212
11471
  if (glowElRef.current) {
9213
- const GAP = 6;
11472
+ const GAP = SELECTION_CHROME_GAP2;
9214
11473
  glowElRef.current.style.top = `${rect.top - GAP}px`;
9215
11474
  glowElRef.current.style.left = `${rect.left - GAP}px`;
9216
11475
  glowElRef.current.style.width = `${rect.width + GAP * 2}px`;
@@ -9298,6 +11557,11 @@ function OhhwellsBridge() {
9298
11557
  return;
9299
11558
  }
9300
11559
  }
11560
+ const footerColumn = findHoveredNavOrFooterContainer(clientX, clientY);
11561
+ if (footerColumn?.hasAttribute("data-ohw-footer-col") || footerColumn?.hasAttribute("data-ohw-footer-column")) {
11562
+ selectFrameRef.current(footerColumn);
11563
+ return;
11564
+ }
9301
11565
  deactivateRef.current();
9302
11566
  };
9303
11567
  window.addEventListener("message", handleSave);
@@ -9369,7 +11633,177 @@ function OhhwellsBridge() {
9369
11633
  if (imageShowTimerRef.current) clearTimeout(imageShowTimerRef.current);
9370
11634
  };
9371
11635
  }, [isEditMode, refreshStateRules]);
9372
- useEffect7(() => {
11636
+ useEffect8(() => {
11637
+ if (!isEditMode) return;
11638
+ const THRESHOLD = 10;
11639
+ const resolveWasSelected = (el) => {
11640
+ if (selectedElRef.current === el) return true;
11641
+ const activeAnchor = activeElRef.current ? getNavigationItemAnchor(activeElRef.current) : null;
11642
+ return activeAnchor === el;
11643
+ };
11644
+ const onPointerDown = (e) => {
11645
+ if (e.button !== 0) return;
11646
+ if (suppressNextClickRef.current && Date.now() >= suppressClickUntilRef.current && !footerDragRef.current && !footerPointerDragRef.current?.started) {
11647
+ suppressNextClickRef.current = false;
11648
+ }
11649
+ if (footerDragRef.current) return;
11650
+ const target = e.target;
11651
+ if (!target) return;
11652
+ 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]')) {
11653
+ return;
11654
+ }
11655
+ if (target.closest("[data-ohw-item-drag-surface]")) return;
11656
+ const anchor = getNavigationItemAnchor(target);
11657
+ const hrefKey = anchor?.getAttribute("data-ohw-href-key") ?? null;
11658
+ if (anchor && isFooterHrefKey(hrefKey)) {
11659
+ footerPointerDragRef.current = {
11660
+ el: anchor,
11661
+ kind: "link",
11662
+ startX: e.clientX,
11663
+ startY: e.clientY,
11664
+ pointerId: e.pointerId,
11665
+ wasSelected: resolveWasSelected(anchor),
11666
+ started: false
11667
+ };
11668
+ return;
11669
+ }
11670
+ const col = target.closest("[data-ohw-footer-col]");
11671
+ if (col && !getNavigationItemAnchor(target)) {
11672
+ footerPointerDragRef.current = {
11673
+ el: col,
11674
+ kind: "column",
11675
+ startX: e.clientX,
11676
+ startY: e.clientY,
11677
+ pointerId: e.pointerId,
11678
+ wasSelected: resolveWasSelected(col),
11679
+ started: false
11680
+ };
11681
+ }
11682
+ };
11683
+ const onPointerMove = (e) => {
11684
+ const pending = footerPointerDragRef.current;
11685
+ if (!pending) return;
11686
+ if (pending.started) {
11687
+ e.preventDefault();
11688
+ clearTextSelection();
11689
+ const session = footerDragRef.current;
11690
+ if (!session) return;
11691
+ const slot = session.kind === "link" && session.hrefKey ? hitTestLinkDropSlot(e.clientX, e.clientY, session.hrefKey) : hitTestColumnDropSlot(e.clientX, e.clientY);
11692
+ refreshFooterDragVisualsRef.current(session, slot, e.clientX, e.clientY);
11693
+ return;
11694
+ }
11695
+ const dx = e.clientX - pending.startX;
11696
+ const dy = e.clientY - pending.startY;
11697
+ if (dx * dx + dy * dy < THRESHOLD * THRESHOLD) return;
11698
+ e.preventDefault();
11699
+ pending.started = true;
11700
+ armFooterPressDrag();
11701
+ clearTextSelection();
11702
+ try {
11703
+ document.body.setPointerCapture(pending.pointerId);
11704
+ } catch {
11705
+ }
11706
+ if (linkPopoverOpenRef.current) {
11707
+ setLinkPopoverRef.current(null);
11708
+ }
11709
+ if (activeElRef.current) {
11710
+ deactivateRef.current();
11711
+ }
11712
+ if (pending.kind === "link") {
11713
+ const key = pending.el.getAttribute("data-ohw-href-key");
11714
+ if (!key) return;
11715
+ const column = findFooterColumnForLink(pending.el);
11716
+ const columns2 = listFooterColumns();
11717
+ beginFooterDragRef.current({
11718
+ kind: "link",
11719
+ hrefKey: key,
11720
+ columnEl: column,
11721
+ sourceColumnIndex: column ? columns2.indexOf(column) : 0,
11722
+ wasSelected: pending.wasSelected,
11723
+ draggedEl: pending.el,
11724
+ lastClientX: e.clientX,
11725
+ lastClientY: e.clientY,
11726
+ activeSlot: null
11727
+ });
11728
+ return;
11729
+ }
11730
+ const columns = listFooterColumns();
11731
+ const idx = columns.indexOf(pending.el);
11732
+ if (idx < 0) return;
11733
+ beginFooterDragRef.current({
11734
+ kind: "column",
11735
+ hrefKey: null,
11736
+ columnEl: pending.el,
11737
+ sourceColumnIndex: idx,
11738
+ wasSelected: pending.wasSelected,
11739
+ draggedEl: pending.el,
11740
+ lastClientX: e.clientX,
11741
+ lastClientY: e.clientY,
11742
+ activeSlot: null
11743
+ });
11744
+ };
11745
+ const endPointerDrag = (e) => {
11746
+ const pending = footerPointerDragRef.current;
11747
+ footerPointerDragRef.current = null;
11748
+ try {
11749
+ if (document.body.hasPointerCapture(e.pointerId)) {
11750
+ document.body.releasePointerCapture(e.pointerId);
11751
+ }
11752
+ } catch {
11753
+ }
11754
+ if (!pending) return;
11755
+ if (!pending.started) {
11756
+ unlockFooterDragInteraction();
11757
+ return;
11758
+ }
11759
+ suppressNextClickRef.current = true;
11760
+ suppressClickUntilRef.current = Date.now() + 500;
11761
+ commitFooterDragRef.current(e.clientX, e.clientY);
11762
+ };
11763
+ const blockSelectStart = (e) => {
11764
+ if (footerDragRef.current || footerPointerDragRef.current?.started || document.documentElement.hasAttribute("data-ohw-footer-press-drag")) {
11765
+ e.preventDefault();
11766
+ }
11767
+ };
11768
+ const onDragEnd = (_e) => {
11769
+ if (!footerDragRef.current) return;
11770
+ suppressNextClickRef.current = true;
11771
+ suppressClickUntilRef.current = Date.now() + 500;
11772
+ commitFooterDragRef.current();
11773
+ };
11774
+ document.addEventListener("pointerdown", onPointerDown, true);
11775
+ document.addEventListener("pointermove", onPointerMove, true);
11776
+ document.addEventListener("pointerup", endPointerDrag, true);
11777
+ document.addEventListener("pointercancel", endPointerDrag, true);
11778
+ document.addEventListener("selectstart", blockSelectStart, true);
11779
+ document.addEventListener("dragend", onDragEnd, true);
11780
+ return () => {
11781
+ document.removeEventListener("pointerdown", onPointerDown, true);
11782
+ document.removeEventListener("pointermove", onPointerMove, true);
11783
+ document.removeEventListener("pointerup", endPointerDrag, true);
11784
+ document.removeEventListener("pointercancel", endPointerDrag, true);
11785
+ document.removeEventListener("selectstart", blockSelectStart, true);
11786
+ document.removeEventListener("dragend", onDragEnd, true);
11787
+ unlockFooterDragInteraction();
11788
+ };
11789
+ }, [isEditMode]);
11790
+ useEffect8(() => {
11791
+ if (!isEditMode) return;
11792
+ const debugSelectionChange = () => {
11793
+ const sel = window.getSelection();
11794
+ console.log(
11795
+ "[OHW DEBUG selectionchange]",
11796
+ JSON.stringify(sel?.toString() ?? ""),
11797
+ "collapsed=",
11798
+ sel?.isCollapsed,
11799
+ "anchorNode=",
11800
+ sel?.anchorNode,
11801
+ "activeEl=",
11802
+ activeElRef.current
11803
+ );
11804
+ };
11805
+ }, [isEditMode]);
11806
+ useEffect8(() => {
9373
11807
  const handler = (e) => {
9374
11808
  if (e.data?.type !== "ow:request-schedule-config") return;
9375
11809
  const insertAfterVal = e.data.insertAfter;
@@ -9385,7 +11819,7 @@ function OhhwellsBridge() {
9385
11819
  window.addEventListener("message", handler);
9386
11820
  return () => window.removeEventListener("message", handler);
9387
11821
  }, [processConfigRequest]);
9388
- useEffect7(() => {
11822
+ useEffect8(() => {
9389
11823
  if (!isEditMode) return;
9390
11824
  document.querySelectorAll("[data-ohw-active-state]").forEach((el) => {
9391
11825
  el.removeAttribute("data-ohw-active-state");
@@ -9406,7 +11840,13 @@ function OhhwellsBridge() {
9406
11840
  const next = { ...prev, [pathKey]: sections };
9407
11841
  return shouldUseDevFixtures() ? { ...DEV_SECTIONS_BY_PATH, ...next } : next;
9408
11842
  });
9409
- postToParent2({ type: "ow:ready", version: "1", path: pathname, nodes: collectEditableNodes(), sections });
11843
+ postToParent2({
11844
+ type: "ow:ready",
11845
+ version: "1",
11846
+ path: pathname,
11847
+ nodes: collectEditableNodes(editContentRef.current),
11848
+ sections
11849
+ });
9410
11850
  postToParent2({ type: "ow:request-site-pages" });
9411
11851
  }, 150);
9412
11852
  return () => {
@@ -9414,19 +11854,20 @@ function OhhwellsBridge() {
9414
11854
  clearTimeout(timer);
9415
11855
  };
9416
11856
  }, [pathname, isEditMode, refreshStateRules, postToParent2]);
9417
- useEffect7(() => {
11857
+ useEffect8(() => {
9418
11858
  scrollToHashSectionWhenReady();
9419
11859
  const onHashChange = () => scrollToHashSectionWhenReady();
9420
11860
  window.addEventListener("hashchange", onHashChange);
9421
11861
  return () => window.removeEventListener("hashchange", onHashChange);
9422
11862
  }, [pathname]);
9423
- const handleCommand = useCallback3((cmd) => {
11863
+ const handleCommand = useCallback5((cmd) => {
11864
+ document.execCommand("styleWithCSS", false, true);
9424
11865
  document.execCommand(cmd, false);
9425
11866
  activeElRef.current?.focus();
9426
11867
  if (activeElRef.current) setToolbarRect(getEditMeasureEl(activeElRef.current).getBoundingClientRect());
9427
11868
  refreshActiveCommandsRef.current();
9428
11869
  }, []);
9429
- const handleStateChange = useCallback3((state) => {
11870
+ const handleStateChange = useCallback5((state) => {
9430
11871
  if (!activeStateElRef.current) return;
9431
11872
  const el = activeStateElRef.current;
9432
11873
  if (state === "Default") {
@@ -9439,11 +11880,11 @@ function OhhwellsBridge() {
9439
11880
  }
9440
11881
  setToggleState((prev) => prev ? { ...prev, activeState: state } : null);
9441
11882
  }, [deactivate]);
9442
- const closeLinkPopover = useCallback3(() => {
11883
+ const closeLinkPopover = useCallback5(() => {
9443
11884
  addNavAfterAnchorRef.current = null;
9444
11885
  setLinkPopover(null);
9445
11886
  }, []);
9446
- const openLinkPopoverForActive = useCallback3(() => {
11887
+ const openLinkPopoverForActive = useCallback5(() => {
9447
11888
  const hrefCtx = getHrefKeyFromElement(activeElRef.current);
9448
11889
  if (!hrefCtx) return;
9449
11890
  bumpLinkPopoverGrace();
@@ -9454,7 +11895,7 @@ function OhhwellsBridge() {
9454
11895
  });
9455
11896
  deactivate();
9456
11897
  }, [deactivate]);
9457
- const openLinkPopoverForSelected = useCallback3(() => {
11898
+ const openLinkPopoverForSelected = useCallback5(() => {
9458
11899
  const anchor = selectedElRef.current;
9459
11900
  if (!anchor) return;
9460
11901
  const key = anchor.getAttribute("data-ohw-href-key");
@@ -9467,7 +11908,7 @@ function OhhwellsBridge() {
9467
11908
  });
9468
11909
  deselect();
9469
11910
  }, [deselect]);
9470
- const handleLinkPopoverSubmit = useCallback3(
11911
+ const handleLinkPopoverSubmit = useCallback5(
9471
11912
  (target) => {
9472
11913
  const session = linkPopoverSessionRef.current;
9473
11914
  if (!session) return;
@@ -9526,13 +11967,13 @@ function OhhwellsBridge() {
9526
11967
  const showEditLink = toolbarShowEditLink;
9527
11968
  const currentSections = sectionsByPath[pathname] ?? [];
9528
11969
  linkPopoverOpenRef.current = linkPopover !== null;
9529
- const handleMediaReplace = useCallback3(
11970
+ const handleMediaReplace = useCallback5(
9530
11971
  (key) => {
9531
11972
  postToParent2({ type: "ow:image-pick", key, elementType: mediaHover?.elementType ?? "image" });
9532
11973
  },
9533
11974
  [postToParent2, mediaHover?.elementType]
9534
11975
  );
9535
- const handleMediaFadeOutComplete = useCallback3((key) => {
11976
+ const handleMediaFadeOutComplete = useCallback5((key) => {
9536
11977
  setUploadingRects((prev) => {
9537
11978
  if (!(key in prev)) return prev;
9538
11979
  const next = { ...prev };
@@ -9540,7 +11981,7 @@ function OhhwellsBridge() {
9540
11981
  return next;
9541
11982
  });
9542
11983
  }, []);
9543
- const handleVideoSettingsChange = useCallback3(
11984
+ const handleVideoSettingsChange = useCallback5(
9544
11985
  (key, settings) => {
9545
11986
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
9546
11987
  const video = getVideoEl(el);
@@ -9564,8 +12005,8 @@ function OhhwellsBridge() {
9564
12005
  );
9565
12006
  return bridgeRoot ? createPortal2(
9566
12007
  /* @__PURE__ */ jsxs13(Fragment5, { children: [
9567
- /* @__PURE__ */ jsx23("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
9568
- Object.entries(uploadingRects).map(([key, { rect, fadingOut }]) => /* @__PURE__ */ jsx23(
12008
+ /* @__PURE__ */ jsx24("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
12009
+ Object.entries(uploadingRects).map(([key, { rect, fadingOut }]) => /* @__PURE__ */ jsx24(
9569
12010
  MediaOverlay,
9570
12011
  {
9571
12012
  hover: { key, rect, elementType: "image", isDragOver: false, hasTextOverlap: false },
@@ -9576,7 +12017,7 @@ function OhhwellsBridge() {
9576
12017
  },
9577
12018
  `uploading-${key}`
9578
12019
  )),
9579
- mediaHover && !(mediaHover.key in uploadingRects) && /* @__PURE__ */ jsx23(
12020
+ mediaHover && !(mediaHover.key in uploadingRects) && /* @__PURE__ */ jsx24(
9580
12021
  MediaOverlay,
9581
12022
  {
9582
12023
  hover: mediaHover,
@@ -9585,49 +12026,93 @@ function OhhwellsBridge() {
9585
12026
  onVideoSettingsChange: handleVideoSettingsChange
9586
12027
  }
9587
12028
  ),
9588
- siblingHintRect && !isItemDragging && /* @__PURE__ */ jsx23(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
9589
- hoveredNavContainerRect && toolbarVariant !== "select-frame" && !linkPopover && !isItemDragging && !siblingHintRect && /* @__PURE__ */ jsx23(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
9590
- toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && /* @__PURE__ */ jsx23(
9591
- NavbarContainerChrome,
12029
+ siblingHintRect && !linkPopover && !isItemDragging && siblingHintRects.length === 0 && /* @__PURE__ */ jsx24(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
12030
+ siblingHintRects.length > 0 && !linkPopover && siblingHintRects.map((rect, i) => /* @__PURE__ */ jsx24(ItemInteractionLayer, { rect, state: "sibling-hint" }, `sibling-hint-${i}`)),
12031
+ isItemDragging && draggedItemRect && selectedElRef.current !== footerDragRef.current?.draggedEl && selectedElRef.current !== navDragRef.current?.draggedEl && /* @__PURE__ */ jsx24(ItemInteractionLayer, { rect: draggedItemRect, state: "dragging" }),
12032
+ isItemDragging && footerDropSlots.map((slot, i) => /* @__PURE__ */ jsx24(
12033
+ "div",
9592
12034
  {
9593
- rect: toolbarRect,
9594
- onAdd: handleAddTopLevelNavItem
9595
- }
9596
- ),
9597
- hoveredItemRect && !hoveredNavContainerRect && !siblingHintRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ jsx23(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
9598
- toolbarRect && (toolbarVariant === "link-action" || toolbarVariant === "select-frame") && /* @__PURE__ */ jsx23(
12035
+ className: "pointer-events-none fixed z-2147483646",
12036
+ style: {
12037
+ left: slot.left,
12038
+ top: slot.top,
12039
+ width: slot.width,
12040
+ height: slot.height
12041
+ },
12042
+ children: /* @__PURE__ */ jsx24(
12043
+ DropIndicator,
12044
+ {
12045
+ direction: slot.direction,
12046
+ state: activeFooterDropIndex === i ? "dragActive" : "dragIdle",
12047
+ className: "!h-full !w-full"
12048
+ }
12049
+ )
12050
+ },
12051
+ `footer-drop-${slot.direction}-${slot.columnIndex}-${slot.insertIndex}-${i}`
12052
+ )),
12053
+ isItemDragging && navDropSlots.map((slot, i) => /* @__PURE__ */ jsx24(
12054
+ "div",
12055
+ {
12056
+ className: "pointer-events-none fixed z-2147483646",
12057
+ style: {
12058
+ left: slot.left,
12059
+ top: slot.top,
12060
+ width: slot.width,
12061
+ height: slot.height
12062
+ },
12063
+ children: /* @__PURE__ */ jsx24(
12064
+ DropIndicator,
12065
+ {
12066
+ direction: slot.direction,
12067
+ state: activeNavDropIndex === i ? "dragActive" : "dragIdle",
12068
+ className: "!h-full !w-full"
12069
+ }
12070
+ )
12071
+ },
12072
+ `nav-drop-${slot.direction}-${slot.parentId ?? "root"}-${slot.insertIndex}-${i}`
12073
+ )),
12074
+ hoveredNavContainerRect && toolbarVariant !== "select-frame" && !linkPopover && !isItemDragging && /* @__PURE__ */ jsx24(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
12075
+ hoveredItemRect && !linkPopover && !hoveredNavContainerRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ jsx24(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
12076
+ toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isNavbarLinksContainer(selectedElRef.current) && /* @__PURE__ */ jsx24(NavbarContainerChrome, { rect: toolbarRect, onAdd: handleAddTopLevelNavItem }),
12077
+ toolbarRect && !linkPopover && (toolbarVariant === "link-action" || toolbarVariant === "select-frame") && /* @__PURE__ */ jsx24(
9599
12078
  ItemInteractionLayer,
9600
12079
  {
9601
- rect: toolbarRect,
12080
+ rect: isItemDragging && draggedItemRect && (footerDragRef.current?.wasSelected || navDragRef.current?.wasSelected) ? draggedItemRect : toolbarRect,
9602
12081
  elRef: glowElRef,
9603
12082
  state: isItemDragging ? "dragging" : resolveItemInteractionState(toolbarRect, parentScrollRef.current),
9604
- showHandle: toolbarVariant === "link-action" && Boolean(reorderHrefKey),
12083
+ showHandle: toolbarVariant === "link-action" && Boolean(reorderHrefKey) || toolbarVariant === "select-frame" && isFooterFrameSelection,
9605
12084
  dragDisabled: reorderDragDisabled,
9606
12085
  dragHandleLabel: reorderHrefKey ? `Reorder ${reorderHrefKey}` : "Reorder item",
9607
12086
  onDragHandleDragStart: handleItemDragStart,
9608
12087
  onDragHandleDragEnd: handleItemDragEnd,
9609
- toolbar: toolbarVariant === "link-action" && !isItemDragging ? /* @__PURE__ */ jsx23(
12088
+ onItemPointerDown: handleItemChromePointerDown,
12089
+ onItemClick: handleItemChromeClick,
12090
+ itemDragSurface: !isFooterFrameSelection,
12091
+ toolbar: toolbarVariant === "link-action" && !isItemDragging ? /* @__PURE__ */ jsx24(
9610
12092
  ItemActionToolbar,
9611
12093
  {
9612
12094
  onEditLink: openLinkPopoverForSelected,
9613
12095
  addItemDisabled: true,
9614
12096
  editLinkDisabled: false,
9615
- moreDisabled: true
12097
+ moreDisabled: true,
12098
+ showAddItem: !selectedIsCta,
12099
+ showMore: !selectedIsCta
9616
12100
  }
9617
12101
  ) : void 0
9618
12102
  }
9619
12103
  ),
9620
- toolbarRect && toolbarVariant === "rich-text" && /* @__PURE__ */ jsxs13(Fragment5, { children: [
9621
- /* @__PURE__ */ jsx23(
12104
+ toolbarRect && toolbarVariant === "rich-text" && !linkPopover && /* @__PURE__ */ jsxs13(Fragment5, { children: [
12105
+ /* @__PURE__ */ jsx24(
9622
12106
  EditGlowChrome,
9623
12107
  {
9624
12108
  rect: toolbarRect,
9625
12109
  elRef: glowElRef,
9626
12110
  reorderHrefKey,
9627
- dragDisabled: reorderDragDisabled
12111
+ dragDisabled: reorderDragDisabled,
12112
+ hideHandle: isItemDragging
9628
12113
  }
9629
12114
  ),
9630
- /* @__PURE__ */ jsx23(
12115
+ /* @__PURE__ */ jsx24(
9631
12116
  FloatingToolbar,
9632
12117
  {
9633
12118
  rect: toolbarRect,
@@ -9666,7 +12151,7 @@ function OhhwellsBridge() {
9666
12151
  ]
9667
12152
  }
9668
12153
  ),
9669
- toggleState && !linkPopover && /* @__PURE__ */ jsx23(
12154
+ toggleState && !linkPopover && /* @__PURE__ */ jsx24(
9670
12155
  StateToggle,
9671
12156
  {
9672
12157
  rect: toggleState.rect,
@@ -9682,25 +12167,29 @@ function OhhwellsBridge() {
9682
12167
  className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
9683
12168
  style: { top: sectionGap.y, transform: "translateY(-50%)" },
9684
12169
  children: [
9685
- /* @__PURE__ */ jsx23("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
9686
- /* @__PURE__ */ jsx23(
12170
+ /* @__PURE__ */ jsx24("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
12171
+ /* @__PURE__ */ jsx24(
9687
12172
  Badge,
9688
12173
  {
9689
12174
  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",
9690
12175
  onClick: () => {
9691
12176
  window.parent.postMessage(
9692
- { type: "ow:add-section", insertAfter: sectionGap.insertAfter, insertBefore: sectionGap.insertBefore },
12177
+ {
12178
+ type: "ow:add-section",
12179
+ insertAfter: sectionGap.insertAfter,
12180
+ insertBefore: sectionGap.insertBefore
12181
+ },
9693
12182
  "*"
9694
12183
  );
9695
12184
  },
9696
12185
  children: "Add Section"
9697
12186
  }
9698
12187
  ),
9699
- /* @__PURE__ */ jsx23("div", { className: "flex-1 bg-primary", style: { height: 3 } })
12188
+ /* @__PURE__ */ jsx24("div", { className: "flex-1 bg-primary", style: { height: 3 } })
9700
12189
  ]
9701
12190
  }
9702
12191
  ),
9703
- linkPopover && dialogPortalContainer ? /* @__PURE__ */ jsx23(
12192
+ linkPopover && dialogPortalContainer ? /* @__PURE__ */ jsx24(
9704
12193
  LinkPopover,
9705
12194
  {
9706
12195
  panelRef: linkPopoverPanelRef,
@@ -9726,6 +12215,7 @@ export {
9726
12215
  CustomToolbarButton,
9727
12216
  CustomToolbarDivider,
9728
12217
  DragHandle,
12218
+ DropIndicator,
9729
12219
  ItemActionToolbar,
9730
12220
  ItemInteractionLayer,
9731
12221
  LinkEditorPanel,
@@ -9740,6 +12230,7 @@ export {
9740
12230
  TooltipProvider,
9741
12231
  TooltipTrigger,
9742
12232
  buildTarget,
12233
+ dropIndicatorVariants,
9743
12234
  filterAvailablePages,
9744
12235
  getEditModeInitialState,
9745
12236
  isEditSessionActive,