@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.cjs CHANGED
@@ -35,6 +35,7 @@ __export(index_exports, {
35
35
  CustomToolbarButton: () => CustomToolbarButton,
36
36
  CustomToolbarDivider: () => CustomToolbarDivider,
37
37
  DragHandle: () => DragHandle,
38
+ DropIndicator: () => DropIndicator,
38
39
  ItemActionToolbar: () => ItemActionToolbar,
39
40
  ItemInteractionLayer: () => ItemInteractionLayer,
40
41
  LinkEditorPanel: () => LinkEditorPanel,
@@ -49,6 +50,7 @@ __export(index_exports, {
49
50
  TooltipProvider: () => TooltipProvider,
50
51
  TooltipTrigger: () => TooltipTrigger,
51
52
  buildTarget: () => buildTarget,
53
+ dropIndicatorVariants: () => dropIndicatorVariants,
52
54
  filterAvailablePages: () => filterAvailablePages,
53
55
  getEditModeInitialState: () => getEditModeInitialState,
54
56
  isEditSessionActive: () => isEditSessionActive,
@@ -60,7 +62,7 @@ __export(index_exports, {
60
62
  module.exports = __toCommonJS(index_exports);
61
63
 
62
64
  // src/OhhwellsBridge.tsx
63
- var import_react8 = __toESM(require("react"), 1);
65
+ var import_react9 = __toESM(require("react"), 1);
64
66
  var import_client = require("react-dom/client");
65
67
  var import_react_dom2 = require("react-dom");
66
68
 
@@ -113,6 +115,7 @@ var import_react3 = require("react");
113
115
  var import_react2 = require("react");
114
116
  var import_radix_ui = require("radix-ui");
115
117
  var import_jsx_runtime = require("react/jsx-runtime");
118
+ var isValidEmail = (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
116
119
  function Spinner() {
117
120
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
118
121
  "svg",
@@ -146,12 +149,17 @@ function EmailCaptureModal({ title, subtitle, onSubmit, onClose }) {
146
149
  const [error, setError] = (0, import_react2.useState)(null);
147
150
  const isBook = title === "Confirm your spot";
148
151
  const handleSubmit = async () => {
149
- if (!email.trim()) return;
152
+ const trimmed = email.trim();
153
+ if (!trimmed) return;
154
+ if (!isValidEmail(trimmed)) {
155
+ setError("Please enter a valid email address.");
156
+ return;
157
+ }
150
158
  setLoading(true);
151
159
  setError(null);
152
160
  try {
153
- await onSubmit(email.trim());
154
- setSubmittedEmail(email.trim());
161
+ await onSubmit(trimmed);
162
+ setSubmittedEmail(trimmed);
155
163
  setSuccess(true);
156
164
  } catch (e) {
157
165
  setError(e instanceof Error ? e.message : "Something went wrong. Please try again.");
@@ -226,7 +234,10 @@ function EmailCaptureModal({ title, subtitle, onSubmit, onClose }) {
226
234
  {
227
235
  type: "email",
228
236
  value: email,
229
- onChange: (e) => setEmail(e.target.value),
237
+ onChange: (e) => {
238
+ setEmail(e.target.value);
239
+ if (error) setError(null);
240
+ },
230
241
  onKeyDown: handleKeyDown,
231
242
  placeholder: "hello@gmail.com",
232
243
  autoFocus: true,
@@ -283,12 +294,20 @@ function formatClassTime(cls) {
283
294
  const em = endTotal % 60;
284
295
  return `${h}:${cls.startTime.minutes}-${eh}:${String(em).padStart(2, "0")}`;
285
296
  }
297
+ function formatBookingLabel(cls) {
298
+ const price = cls.price;
299
+ const isFree = price === null || price === void 0 || `${price}` === "" || `${price}` === "0";
300
+ return isFree ? "Book for free" : `Book for ${cls.currency ?? ""} ${price}`.trim();
301
+ }
286
302
  function getBookingsOnDate(cls, date) {
287
303
  if (!cls.bookings?.length) return 0;
288
- const ds = date.toISOString().split("T")[0];
304
+ const target = new Date(date);
305
+ target.setHours(0, 0, 0, 0);
289
306
  return cls.bookings.filter((b) => {
290
307
  try {
291
- return new Date(b.classDate).toISOString().split("T")[0] === ds;
308
+ const bookingDate = new Date(b.classDate);
309
+ bookingDate.setHours(0, 0, 0, 0);
310
+ return bookingDate.getTime() === target.getTime();
292
311
  } catch {
293
312
  return false;
294
313
  }
@@ -553,7 +572,7 @@ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal
553
572
  }
554
573
  )
555
574
  ] }),
556
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "sm:hidden flex flex-col gap-px", children: isFull ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
575
+ cls.showAvailability !== false && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "sm:hidden flex flex-col gap-px", children: isFull ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
557
576
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
558
577
  available,
559
578
  "/",
@@ -565,9 +584,17 @@ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal
565
584
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-1 flex-col gap-2 min-w-0 sm:contents", children: [
566
585
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex-1 flex flex-col gap-2 min-w-0", children: [
567
586
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block truncate", children: cls.name }),
568
- cls.hostName && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-normal text-(--color-dark,#200C02) opacity-80 block truncate", children: cls.hostName })
587
+ cls.hostName && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-normal text-(--color-dark,#200C02) opacity-80 block truncate", children: cls.hostName }),
588
+ cls.description && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
589
+ "span",
590
+ {
591
+ className: "font-body text-base font-normal text-(--color-dark,#200C02) opacity-80 block",
592
+ style: { wordBreak: "break-word" },
593
+ children: cls.description
594
+ }
595
+ )
569
596
  ] }),
570
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hidden sm:flex w-14 shrink-0 flex-col gap-px", children: isFull ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
597
+ cls.showAvailability !== false && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hidden sm:flex w-14 shrink-0 flex-col gap-px", children: isFull ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
571
598
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
572
599
  available,
573
600
  "/",
@@ -592,7 +619,7 @@ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal
592
619
  if (!cls.id) return;
593
620
  onOpenModal?.(isFull ? "waitlist" : "book", cls.id, selectedDate);
594
621
  },
595
- children: isFull ? "Join Waitlist" : "Book Now"
622
+ children: isFull ? "Join Waitlist" : formatBookingLabel(cls)
596
623
  }
597
624
  )
598
625
  ] })
@@ -634,6 +661,17 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
634
661
  const [isHovered, setIsHovered] = (0, import_react3.useState)(false);
635
662
  const [modalState, setModalState] = (0, import_react3.useState)(null);
636
663
  const switchScheduleIdRef = (0, import_react3.useRef)(null);
664
+ const liveScheduleIdRef = (0, import_react3.useRef)(null);
665
+ const refetchLiveSchedule = (0, import_react3.useCallback)(async () => {
666
+ const id = liveScheduleIdRef.current;
667
+ if (!id) return;
668
+ try {
669
+ const res = await fetch(`${API_URL}/api/schedule/id/${id}`);
670
+ const data = await res.json();
671
+ if (data?.id) setSchedule(data);
672
+ } catch {
673
+ }
674
+ }, []);
637
675
  const dates = (0, import_react3.useMemo)(() => {
638
676
  const today = /* @__PURE__ */ new Date();
639
677
  today.setHours(0, 0, 0, 0);
@@ -653,6 +691,18 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
653
691
  }
654
692
  }
655
693
  }, [schedule, dates]);
694
+ (0, import_react3.useEffect)(() => {
695
+ if (typeof window === "undefined") return;
696
+ const onVisible = () => {
697
+ if (!document.hidden) void refetchLiveSchedule();
698
+ };
699
+ window.addEventListener("focus", refetchLiveSchedule);
700
+ document.addEventListener("visibilitychange", onVisible);
701
+ return () => {
702
+ window.removeEventListener("focus", refetchLiveSchedule);
703
+ document.removeEventListener("visibilitychange", onVisible);
704
+ };
705
+ }, [refetchLiveSchedule]);
656
706
  (0, import_react3.useEffect)(() => {
657
707
  if (typeof window === "undefined") return;
658
708
  const isInEditor = window.self !== window.top;
@@ -714,7 +764,7 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
714
764
  setLoading(false);
715
765
  return;
716
766
  }
717
- ;
767
+ liveScheduleIdRef.current = effectiveId;
718
768
  (async () => {
719
769
  try {
720
770
  const res = await fetch(`${API_URL}/api/schedule/id/${effectiveId}`);
@@ -771,18 +821,14 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
771
821
  const data = await res.json().catch(() => ({}));
772
822
  throw new Error(data?.message ?? "Something went wrong. Please try again.");
773
823
  }
824
+ await refetchLiveSchedule();
774
825
  };
775
826
  const handleReplaceSchedule = () => {
776
827
  setIsHovered(false);
777
- if (schedule) {
778
- window.parent.postMessage({
779
- type: "ow:schedule-connected",
780
- schedule: { id: schedule.id, name: schedule.name },
781
- insertAfter
782
- }, "*");
783
- } else {
784
- window.parent.postMessage({ type: "ow:scheduling-not-connected", insertAfter }, "*");
785
- }
828
+ window.parent.postMessage(
829
+ { type: "ow:scheduling-not-connected", insertAfter, currentScheduleId: schedule?.id },
830
+ "*"
831
+ );
786
832
  };
787
833
  if (!inEditor && !loading && !schedule) return null;
788
834
  const sectionId = `scheduling-${insertAfter}`;
@@ -825,7 +871,19 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
825
871
  ),
826
872
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "max-w-[1280px] mx-auto flex flex-col items-center gap-16", children: [
827
873
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col items-center gap-4", children: [
828
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("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" }),
874
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
875
+ "h2",
876
+ {
877
+ className: "font-display text-center m-0 text-(--color-dark,#200C02)",
878
+ style: {
879
+ fontSize: "var(--fs-section-h2, clamp(40px, 4.4vw, 64px))",
880
+ fontWeight: "var(--font-weight-heading, 400)",
881
+ lineHeight: 1.05,
882
+ letterSpacing: "-0.02em"
883
+ },
884
+ children: schedule?.name ?? "Book an appointment"
885
+ }
886
+ ),
829
887
  schedule?.description && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "font-body text-body text-center m-0 text-(--color-accent,#A89B83)", children: schedule.description })
830
888
  ] }),
831
889
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "w-full flex flex-col items-end gap-3", children: [
@@ -4301,7 +4359,8 @@ var CustomToolbar = React4.forwardRef(({ className, onMouseDown, ...props }, ref
4301
4359
  ref,
4302
4360
  "data-ohw-toolbar": "",
4303
4361
  className: cn(
4304
- "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)]",
4362
+ // Figma: bg background, radius 8, gap-1, p-0.5, shadow-md no border
4363
+ "inline-flex h-8 items-center gap-1 rounded-[var(--radius,0.5rem)] bg-background p-0.5 font-sans whitespace-nowrap shadow-md",
4305
4364
  className
4306
4365
  ),
4307
4366
  onMouseDown: (e) => {
@@ -4329,8 +4388,8 @@ var CustomToolbarButton = React4.forwardRef(
4329
4388
  ref,
4330
4389
  type,
4331
4390
  className: cn(
4332
- "inline-flex shrink-0 items-center justify-center rounded p-1.5 transition-colors",
4333
- active ? "bg-primary text-primary-foreground" : "bg-transparent text-foreground hover:bg-muted disabled:cursor-not-allowed disabled:text-muted-foreground disabled:opacity-60",
4391
+ "inline-flex size-7 shrink-0 items-center justify-center rounded-[calc(var(--radius,0.5rem)-2px)] text-foreground transition-colors",
4392
+ active ? "bg-primary text-primary-foreground" : "bg-transparent hover:bg-muted disabled:cursor-not-allowed disabled:text-muted-foreground disabled:opacity-60",
4334
4393
  className
4335
4394
  ),
4336
4395
  ...props
@@ -4365,9 +4424,10 @@ var arrowClassBySide = {
4365
4424
  };
4366
4425
  function TooltipContent({
4367
4426
  className,
4368
- sideOffset = 6,
4427
+ sideOffset = 9,
4369
4428
  side = "bottom",
4370
4429
  children,
4430
+ hideArrow,
4371
4431
  ...props
4372
4432
  }) {
4373
4433
  const resolvedSide = side ?? "bottom";
@@ -4378,12 +4438,13 @@ function TooltipContent({
4378
4438
  side,
4379
4439
  sideOffset,
4380
4440
  className: cn(
4381
- "relative z-[2147483647] w-fit max-w-xs rounded-md bg-foreground px-3 py-1.5 text-xs text-background shadow-md",
4382
- 'before:pointer-events-none before:absolute before:size-2 before:rotate-45 before:bg-foreground before:content-[""]',
4383
- arrowClassBySide[resolvedSide],
4384
- "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",
4441
+ "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",
4442
+ "origin-[var(--radix-tooltip-content-transform-origin)] animate-in fade-in-0 zoom-in-95",
4443
+ "data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
4385
4444
  "data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2",
4386
4445
  "data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2",
4446
+ !hideArrow && 'before:pointer-events-none before:absolute before:size-2 before:rotate-45 before:bg-foreground before:content-[""]',
4447
+ !hideArrow && arrowClassBySide[resolvedSide],
4387
4448
  className
4388
4449
  ),
4389
4450
  ...props,
@@ -4404,7 +4465,7 @@ function ToolbarActionTooltip({
4404
4465
  const button = /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CustomToolbarButton, { type: "button", "aria-label": label, disabled, ...buttonProps, children });
4405
4466
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Tooltip, { children: [
4406
4467
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(TooltipTrigger, { asChild: true, children: disabled ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "inline-flex", children: button }) : button }),
4407
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(TooltipContent, { side, children: label })
4468
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(TooltipContent, { side, sideOffset: 9, children: label })
4408
4469
  ] });
4409
4470
  }
4410
4471
  function ItemActionToolbar({
@@ -4414,6 +4475,8 @@ function ItemActionToolbar({
4414
4475
  editLinkDisabled = false,
4415
4476
  addItemDisabled = true,
4416
4477
  moreDisabled = true,
4478
+ showAddItem = true,
4479
+ showMore = true,
4417
4480
  tooltipSide = "bottom"
4418
4481
  }) {
4419
4482
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(CustomToolbar, { children: [
@@ -4438,8 +4501,7 @@ function ItemActionToolbar({
4438
4501
  children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react2.Link, { className: "size-4 shrink-0", "aria-hidden": true })
4439
4502
  }
4440
4503
  ),
4441
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CustomToolbarDivider, {}),
4442
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4504
+ showAddItem ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4443
4505
  ToolbarActionTooltip,
4444
4506
  {
4445
4507
  label: "Add item",
@@ -4455,9 +4517,8 @@ function ItemActionToolbar({
4455
4517
  },
4456
4518
  children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react2.Plus, { className: "size-4 shrink-0", "aria-hidden": true })
4457
4519
  }
4458
- ),
4459
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CustomToolbarDivider, {}),
4460
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4520
+ ) : null,
4521
+ showMore ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4461
4522
  ToolbarActionTooltip,
4462
4523
  {
4463
4524
  label: "More",
@@ -4473,15 +4534,32 @@ function ItemActionToolbar({
4473
4534
  },
4474
4535
  children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react2.MoreHorizontal, { className: "size-4 shrink-0", "aria-hidden": true })
4475
4536
  }
4476
- )
4537
+ ) : null
4477
4538
  ] }) });
4478
4539
  }
4479
4540
 
4480
4541
  // src/ui/item-interaction-layer.tsx
4542
+ var React5 = __toESM(require("react"), 1);
4481
4543
  var import_jsx_runtime9 = require("react/jsx-runtime");
4482
4544
  var PRIMARY = "var(--ohw-primary, #0885FE)";
4483
- var FOCUS_RING = "0 0 0 4px rgba(8, 133, 254, 0.12)";
4545
+ var FOCUS_RING = `0 0 0 4px color-mix(in srgb, ${PRIMARY} 12%, transparent)`;
4484
4546
  var DRAG_SHADOW = "0px 4px 6px -1px rgba(0, 0, 0, 0.1), 0px 2px 4px -2px rgba(0, 0, 0, 0.1)";
4547
+ var TOOLBAR_EDGE_MARGIN = 4;
4548
+ var SELECTION_CHROME_GAP = 4;
4549
+ var TOOLBAR_STROKE_GAP = 4;
4550
+ function getChromeZIndex(state) {
4551
+ switch (state) {
4552
+ case "active-top":
4553
+ case "active-bottom":
4554
+ case "dragging":
4555
+ return 2147483644;
4556
+ case "hover":
4557
+ case "sibling-hint":
4558
+ return 2147483643;
4559
+ default:
4560
+ return 2147483642;
4561
+ }
4562
+ }
4485
4563
  function getChromeStyle(state) {
4486
4564
  switch (state) {
4487
4565
  case "hover":
@@ -4503,12 +4581,65 @@ function getChromeStyle(state) {
4503
4581
  case "dragging":
4504
4582
  return {
4505
4583
  border: `2px solid ${PRIMARY}`,
4506
- boxShadow: DRAG_SHADOW
4584
+ boxShadow: `${FOCUS_RING}, ${DRAG_SHADOW}`
4507
4585
  };
4508
4586
  default:
4509
4587
  return {};
4510
4588
  }
4511
4589
  }
4590
+ function ClampedToolbarSlot({
4591
+ placement,
4592
+ children
4593
+ }) {
4594
+ const slotRef = React5.useRef(null);
4595
+ React5.useLayoutEffect(() => {
4596
+ const slot = slotRef.current;
4597
+ if (!slot) return;
4598
+ const clamp = () => {
4599
+ const toolbar = slot.firstElementChild;
4600
+ if (!toolbar) return;
4601
+ const w = toolbar.offsetWidth;
4602
+ if (w <= 0) return;
4603
+ const parent = slot.offsetParent;
4604
+ if (!parent) return;
4605
+ const parentRect = parent.getBoundingClientRect();
4606
+ const centerX = parentRect.left + parentRect.width / 2;
4607
+ const half = w / 2;
4608
+ const clampedCenter = Math.max(
4609
+ TOOLBAR_EDGE_MARGIN + half,
4610
+ Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN - half)
4611
+ );
4612
+ const offsetX = clampedCenter - centerX;
4613
+ slot.style.transform = `translateX(calc(-50% + ${offsetX}px))`;
4614
+ };
4615
+ clamp();
4616
+ const ro = new ResizeObserver(clamp);
4617
+ ro.observe(slot);
4618
+ if (slot.firstElementChild) ro.observe(slot.firstElementChild);
4619
+ window.addEventListener("resize", clamp);
4620
+ return () => {
4621
+ ro.disconnect();
4622
+ window.removeEventListener("resize", clamp);
4623
+ };
4624
+ }, [placement, children]);
4625
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4626
+ "div",
4627
+ {
4628
+ ref: slotRef,
4629
+ className: cn(
4630
+ "pointer-events-auto absolute left-1/2",
4631
+ placement === "top" ? "bottom-full" : "top-full"
4632
+ ),
4633
+ style: {
4634
+ marginBottom: placement === "top" ? TOOLBAR_STROKE_GAP : void 0,
4635
+ marginTop: placement === "bottom" ? TOOLBAR_STROKE_GAP : void 0,
4636
+ transform: "translateX(-50%)"
4637
+ },
4638
+ "data-ohw-item-toolbar-anchor": placement,
4639
+ children
4640
+ }
4641
+ );
4642
+ }
4512
4643
  function ItemInteractionLayer({
4513
4644
  rect,
4514
4645
  state,
@@ -4519,14 +4650,18 @@ function ItemInteractionLayer({
4519
4650
  dragHandleLabel = "Reorder item",
4520
4651
  onDragHandleDragStart,
4521
4652
  onDragHandleDragEnd,
4522
- chromeGap = 6,
4653
+ onItemPointerDown,
4654
+ onItemClick,
4655
+ itemDragSurface = true,
4656
+ chromeGap = SELECTION_CHROME_GAP,
4523
4657
  className
4524
4658
  }) {
4525
4659
  if (state === "default") return null;
4526
4660
  const isActive = state === "active-top" || state === "active-bottom";
4527
4661
  const isDragging = state === "dragging";
4528
4662
  const showToolbar = isActive && toolbar;
4529
- const showDragHandle = isActive && showHandle && !isDragging;
4663
+ const showDragHandle = (isActive || isDragging) && showHandle;
4664
+ const itemDragEnabled = itemDragSurface && isActive && showHandle && !isDragging && !dragDisabled;
4530
4665
  return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
4531
4666
  "div",
4532
4667
  {
@@ -4540,7 +4675,7 @@ function ItemInteractionLayer({
4540
4675
  left: rect.left - chromeGap,
4541
4676
  width: rect.width + chromeGap * 2,
4542
4677
  height: rect.height + chromeGap * 2,
4543
- zIndex: 2147483646
4678
+ zIndex: getChromeZIndex(state)
4544
4679
  },
4545
4680
  children: [
4546
4681
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
@@ -4551,11 +4686,29 @@ function ItemInteractionLayer({
4551
4686
  style: getChromeStyle(state)
4552
4687
  }
4553
4688
  ),
4689
+ itemDragEnabled && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4690
+ "div",
4691
+ {
4692
+ "data-ohw-item-drag-surface": "",
4693
+ className: "pointer-events-auto absolute inset-0 cursor-text rounded-lg",
4694
+ onPointerDown: (e) => {
4695
+ if (e.button !== 0) return;
4696
+ onItemPointerDown?.(e);
4697
+ },
4698
+ onClick: (e) => {
4699
+ e.preventDefault();
4700
+ e.stopPropagation();
4701
+ onItemClick?.(e.clientX, e.clientY);
4702
+ }
4703
+ }
4704
+ ),
4554
4705
  showDragHandle && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4555
4706
  "div",
4556
4707
  {
4557
4708
  "data-ohw-drag-handle-container": "",
4558
- className: "pointer-events-auto absolute left-0 top-1/2 -translate-x-[calc(100%+7px)] -translate-y-1/2",
4709
+ className: "pointer-events-auto absolute left-0 top-1/2 z-10 -translate-x-[calc(100%+7px)] -translate-y-1/2",
4710
+ style: isDragging ? { opacity: 0 } : void 0,
4711
+ "aria-hidden": isDragging || void 0,
4559
4712
  children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4560
4713
  DragHandle,
4561
4714
  {
@@ -4567,7 +4720,17 @@ function ItemInteractionLayer({
4567
4720
  e.preventDefault();
4568
4721
  return;
4569
4722
  }
4570
- e.dataTransfer?.setData("text/plain", dragHandleLabel);
4723
+ const blank = document.createElement("canvas");
4724
+ blank.width = 1;
4725
+ blank.height = 1;
4726
+ blank.style.cssText = "position:fixed;left:-9999px;top:-9999px;width:1px;height:1px;pointer-events:none";
4727
+ document.body.appendChild(blank);
4728
+ try {
4729
+ e.dataTransfer.setDragImage(blank, 0, 0);
4730
+ } finally {
4731
+ requestAnimationFrame(() => blank.remove());
4732
+ }
4733
+ e.dataTransfer.setData("text/plain", dragHandleLabel);
4571
4734
  e.dataTransfer.effectAllowed = "move";
4572
4735
  onDragHandleDragStart?.(e);
4573
4736
  },
@@ -4576,33 +4739,19 @@ function ItemInteractionLayer({
4576
4739
  )
4577
4740
  }
4578
4741
  ),
4579
- showToolbar && state === "active-top" && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4580
- "div",
4581
- {
4582
- className: "pointer-events-auto absolute bottom-full left-1/2 mb-2 -translate-x-1/2",
4583
- "data-ohw-item-toolbar-anchor": "top",
4584
- children: toolbar
4585
- }
4586
- ),
4587
- showToolbar && state === "active-bottom" && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4588
- "div",
4589
- {
4590
- className: "pointer-events-auto absolute left-1/2 top-full mt-2 -translate-x-1/2",
4591
- "data-ohw-item-toolbar-anchor": "bottom",
4592
- children: toolbar
4593
- }
4594
- )
4742
+ showToolbar && state === "active-top" && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ClampedToolbarSlot, { placement: "top", children: toolbar }),
4743
+ showToolbar && state === "active-bottom" && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ClampedToolbarSlot, { placement: "bottom", children: toolbar })
4595
4744
  ]
4596
4745
  }
4597
4746
  );
4598
4747
  }
4599
4748
 
4600
4749
  // src/ui/MediaOverlay.tsx
4601
- var React6 = __toESM(require("react"), 1);
4750
+ var React7 = __toESM(require("react"), 1);
4602
4751
  var import_lucide_react3 = require("lucide-react");
4603
4752
 
4604
4753
  // src/ui/button.tsx
4605
- var React5 = __toESM(require("react"), 1);
4754
+ var React6 = __toESM(require("react"), 1);
4606
4755
  var import_radix_ui4 = require("radix-ui");
4607
4756
  var import_jsx_runtime10 = require("react/jsx-runtime");
4608
4757
  var buttonVariants = cva(
@@ -4625,7 +4774,7 @@ var buttonVariants = cva(
4625
4774
  }
4626
4775
  }
4627
4776
  );
4628
- var Button = React5.forwardRef(
4777
+ var Button = React6.forwardRef(
4629
4778
  ({ className, variant, size, asChild = false, ...props }, ref) => {
4630
4779
  const Comp = asChild ? import_radix_ui4.Slot.Root : "button";
4631
4780
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
@@ -4677,7 +4826,7 @@ function MediaOverlay({
4677
4826
  onVideoSettingsChange
4678
4827
  }) {
4679
4828
  const { rect } = hover;
4680
- const skeletonRef = React6.useRef(null);
4829
+ const skeletonRef = React7.useRef(null);
4681
4830
  const isVideo = hover.elementType === "video";
4682
4831
  const autoplay = hover.videoAutoplay ?? true;
4683
4832
  const muted = hover.videoMuted ?? true;
@@ -4689,7 +4838,7 @@ function MediaOverlay({
4689
4838
  height: rect.height,
4690
4839
  zIndex: 2147483646
4691
4840
  };
4692
- React6.useEffect(() => {
4841
+ React7.useEffect(() => {
4693
4842
  if (!isUploading || !fadingOut || !skeletonRef.current) return;
4694
4843
  const anim = skeletonRef.current.animate([{ opacity: 1 }, { opacity: 0 }], {
4695
4844
  duration: MEDIA_UPLOAD_FADE_MS,
@@ -5094,7 +5243,7 @@ function scrollToHashSectionWhenReady(behavior = "smooth") {
5094
5243
  var import_react7 = require("react");
5095
5244
 
5096
5245
  // src/ui/dialog.tsx
5097
- var React7 = __toESM(require("react"), 1);
5246
+ var React8 = __toESM(require("react"), 1);
5098
5247
  var import_radix_ui5 = require("radix-ui");
5099
5248
  var import_lucide_react4 = require("lucide-react");
5100
5249
  var import_jsx_runtime12 = require("react/jsx-runtime");
@@ -5122,7 +5271,7 @@ function DialogOverlay({
5122
5271
  }
5123
5272
  );
5124
5273
  }
5125
- var DialogContent = React7.forwardRef(
5274
+ var DialogContent = React8.forwardRef(
5126
5275
  ({ className, children, showCloseButton = true, container, ...props }, ref) => {
5127
5276
  const positionMode = container ? "absolute" : "fixed";
5128
5277
  return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(DialogPortal, { container: container ?? void 0, children: [
@@ -5177,7 +5326,7 @@ function DialogFooter({
5177
5326
  }
5178
5327
  );
5179
5328
  }
5180
- var DialogTitle = React7.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
5329
+ var DialogTitle = React8.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
5181
5330
  import_radix_ui5.Dialog.Title,
5182
5331
  {
5183
5332
  ref,
@@ -5189,7 +5338,7 @@ var DialogTitle = React7.forwardRef(({ className, ...props }, ref) => /* @__PURE
5189
5338
  }
5190
5339
  ));
5191
5340
  DialogTitle.displayName = import_radix_ui5.Dialog.Title.displayName;
5192
- var DialogDescription = React7.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
5341
+ var DialogDescription = React8.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
5193
5342
  import_radix_ui5.Dialog.Description,
5194
5343
  {
5195
5344
  ref,
@@ -5294,9 +5443,9 @@ function SectionTreeItem({
5294
5443
  var import_react4 = require("react");
5295
5444
 
5296
5445
  // src/ui/input.tsx
5297
- var React8 = __toESM(require("react"), 1);
5446
+ var React9 = __toESM(require("react"), 1);
5298
5447
  var import_jsx_runtime15 = require("react/jsx-runtime");
5299
- var Input = React8.forwardRef(
5448
+ var Input = React9.forwardRef(
5300
5449
  ({ className, type, ...props }, ref) => {
5301
5450
  return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
5302
5451
  "input",
@@ -5851,8 +6000,8 @@ function SectionPickerOverlay({
5851
6000
  !isOnTargetPage ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5852
6001
  "div",
5853
6002
  {
5854
- className: "pointer-events-none fixed left-1/2 z-[1] -translate-x-1/2 rounded-md bg-background/90 px-3 py-2 text-sm text-muted-foreground shadow-sm",
5855
- style: { top: chromeClip.top + 64 },
6003
+ 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",
6004
+ style: { top: chromeClip.top + 64, backgroundColor: "var(--ohw-background, #ffffff)" },
5856
6005
  children: "Loading page preview\u2026"
5857
6006
  }
5858
6007
  ) : null,
@@ -6277,9 +6426,155 @@ function shouldUseDevFixtures() {
6277
6426
  return new URLSearchParams(q).get("ohw-fixtures") === "1";
6278
6427
  }
6279
6428
 
6429
+ // src/lib/nav-tree.ts
6430
+ var MAX_LEVEL = { nav: 1 };
6431
+ var ROOT = null;
6432
+ function cloneForest(nodes) {
6433
+ return nodes.map((n) => ({ id: n.id, children: cloneForest(n.children) }));
6434
+ }
6435
+ function findNode(nodes, id, parentId = ROOT) {
6436
+ for (let i = 0; i < nodes.length; i++) {
6437
+ const node = nodes[i];
6438
+ if (node.id === id) return { node, parentId, index: i, siblings: nodes };
6439
+ const nested = findNode(node.children, id, node.id);
6440
+ if (nested) return nested;
6441
+ }
6442
+ return null;
6443
+ }
6444
+ function getChildrenOf(nodes, parentId) {
6445
+ if (parentId === ROOT) return nodes;
6446
+ const found = findNode(nodes, parentId);
6447
+ return found ? found.node.children : null;
6448
+ }
6449
+ function isDescendant(nodes, ancestorId, maybeDescendantId) {
6450
+ const found = findNode(nodes, ancestorId);
6451
+ if (!found) return false;
6452
+ return findNode(found.node.children, maybeDescendantId) !== null;
6453
+ }
6454
+ function levelOf(nodes, id) {
6455
+ const loc = findNode(nodes, id);
6456
+ if (!loc) return -1;
6457
+ if (loc.parentId === ROOT) return 0;
6458
+ return 1 + Math.max(0, levelOf(nodes, loc.parentId));
6459
+ }
6460
+ function insertedLevel(nodes, parentId) {
6461
+ if (parentId === ROOT) return 0;
6462
+ const parentLevel = levelOf(nodes, parentId);
6463
+ if (parentLevel < 0) return -1;
6464
+ return parentLevel + 1;
6465
+ }
6466
+ function takeNode(nodes, id) {
6467
+ const forest = cloneForest(nodes);
6468
+ const loc = findNode(forest, id);
6469
+ if (!loc) return null;
6470
+ const [taken] = loc.siblings.splice(loc.index, 1);
6471
+ if (!taken) return null;
6472
+ return { forest, taken };
6473
+ }
6474
+ function insertInto(forest, node, parentId, index) {
6475
+ const siblings = getChildrenOf(forest, parentId);
6476
+ if (!siblings) return false;
6477
+ const clamped = Math.max(0, Math.min(index, siblings.length));
6478
+ siblings.splice(clamped, 0, node);
6479
+ return true;
6480
+ }
6481
+ function normalize(nodes) {
6482
+ const walk = (list) => list.map((n) => {
6483
+ const children = walk(n.children);
6484
+ return { id: n.id, children };
6485
+ });
6486
+ return walk(nodes);
6487
+ }
6488
+ function moveNode(model, draggedId, target) {
6489
+ const { parentId, index: targetIndex } = target;
6490
+ if (parentId === draggedId) return null;
6491
+ if (parentId !== ROOT && isDescendant(model, draggedId, parentId)) return null;
6492
+ const nextLevel = insertedLevel(model, parentId);
6493
+ if (nextLevel < 0 || nextLevel > MAX_LEVEL.nav) return null;
6494
+ const draggedLoc = findNode(model, draggedId);
6495
+ if (!draggedLoc) return null;
6496
+ if (parentId !== ROOT && draggedLoc.node.children.length > 0) return null;
6497
+ const taken = takeNode(model, draggedId);
6498
+ if (!taken) return null;
6499
+ let insertIndex = targetIndex;
6500
+ if (draggedLoc.parentId === parentId && draggedLoc.index < targetIndex) {
6501
+ insertIndex -= 1;
6502
+ }
6503
+ if (!insertInto(taken.forest, taken.taken, parentId, insertIndex)) return null;
6504
+ const next = normalize(taken.forest);
6505
+ if (forestsEqual(model, next)) return null;
6506
+ return next;
6507
+ }
6508
+ function forestsEqual(a, b) {
6509
+ if (a.length !== b.length) return false;
6510
+ for (let i = 0; i < a.length; i++) {
6511
+ const left = a[i];
6512
+ const right = b[i];
6513
+ if (left.id !== right.id) return false;
6514
+ if (!forestsEqual(left.children, right.children)) return false;
6515
+ }
6516
+ return true;
6517
+ }
6518
+ function forestFromFlatOrder(order) {
6519
+ return order.map((id) => ({ id, children: [] }));
6520
+ }
6521
+ function serializeNavOrder(nodes) {
6522
+ const hasNesting = nodes.some((n) => n.children.length > 0);
6523
+ if (!hasNesting) {
6524
+ return JSON.stringify(nodes.map((n) => n.id));
6525
+ }
6526
+ return JSON.stringify(nodes);
6527
+ }
6528
+ function parseNavOrderJson(raw) {
6529
+ if (!raw) return null;
6530
+ try {
6531
+ const parsed = JSON.parse(raw);
6532
+ if (!Array.isArray(parsed)) return null;
6533
+ if (parsed.every((x) => typeof x === "string")) {
6534
+ return forestFromFlatOrder(parsed.filter((x) => typeof x === "string" && x.length > 0));
6535
+ }
6536
+ return parseNavNodeList(parsed);
6537
+ } catch {
6538
+ return null;
6539
+ }
6540
+ }
6541
+ function parseNavNodeList(raw) {
6542
+ const out = [];
6543
+ for (const item of raw) {
6544
+ if (typeof item === "string" && item.length > 0) {
6545
+ out.push({ id: item, children: [] });
6546
+ continue;
6547
+ }
6548
+ if (!item || typeof item !== "object") return null;
6549
+ const rec = item;
6550
+ if (typeof rec.id !== "string" || !rec.id) return null;
6551
+ const children = Array.isArray(rec.children) ? parseNavNodeList(rec.children) : [];
6552
+ if (!children) return null;
6553
+ out.push({ id: rec.id, children });
6554
+ }
6555
+ return out;
6556
+ }
6557
+ function flattenNavOrder(nodes) {
6558
+ const out = [];
6559
+ const walk = (list) => {
6560
+ for (const n of list) {
6561
+ out.push(n.id);
6562
+ walk(n.children);
6563
+ }
6564
+ };
6565
+ walk(nodes);
6566
+ return out;
6567
+ }
6568
+
6280
6569
  // src/lib/nav-items.ts
6281
6570
  var NAV_COUNT_KEY = "__ohw_nav_count";
6282
6571
  var NAV_ORDER_KEY = "__ohw_nav_order";
6572
+ var NAV_HREF_RE = /^nav-(\d+)-href$/;
6573
+ function isNavbarHrefKey(key) {
6574
+ if (!key) return false;
6575
+ if (key === "nav-book-href") return false;
6576
+ return NAV_HREF_RE.test(key);
6577
+ }
6283
6578
  function getLinkHref(el) {
6284
6579
  const key = el.getAttribute("data-ohw-href-key");
6285
6580
  if (key) {
@@ -6316,7 +6611,7 @@ function listNavbarItems() {
6316
6611
  }
6317
6612
  function parseNavIndexFromKey(key) {
6318
6613
  if (!key) return null;
6319
- const match = key.match(/^nav-(\d+)-href$/);
6614
+ const match = key.match(NAV_HREF_RE);
6320
6615
  if (!match) return null;
6321
6616
  return parseInt(match[1], 10);
6322
6617
  }
@@ -6339,17 +6634,6 @@ function getNavbarExistingTargets() {
6339
6634
  function getNavOrderFromDom() {
6340
6635
  return listNavbarItems().map((el) => el.getAttribute("data-ohw-href-key")).filter((key) => Boolean(key));
6341
6636
  }
6342
- function parseNavOrder(content) {
6343
- const raw = content[NAV_ORDER_KEY];
6344
- if (!raw) return null;
6345
- try {
6346
- const parsed = JSON.parse(raw);
6347
- if (!Array.isArray(parsed)) return null;
6348
- return parsed.filter((key) => typeof key === "string" && key.length > 0);
6349
- } catch {
6350
- return null;
6351
- }
6352
- }
6353
6637
  function findCounterpartByHrefKey(hrefKey, root) {
6354
6638
  return root.querySelector(`[data-ohw-href-key="${CSS.escape(hrefKey)}"]`);
6355
6639
  }
@@ -6413,19 +6697,47 @@ function insertNavbarItemDom(index, href, label, afterAnchor) {
6413
6697
  }
6414
6698
  return primary;
6415
6699
  }
6700
+ function navOrderKeysEqual(a, b) {
6701
+ if (a.length !== b.length) return false;
6702
+ for (let i = 0; i < a.length; i++) {
6703
+ if (a[i] !== b[i]) return false;
6704
+ }
6705
+ return true;
6706
+ }
6707
+ function getNavbarLinkKeysInContainer(container) {
6708
+ return Array.from(container.querySelectorAll("[data-ohw-href-key]")).filter(isNavbarLinkItem).map((el) => el.getAttribute("data-ohw-href-key")).filter((key) => Boolean(key));
6709
+ }
6416
6710
  function applyNavOrderToContainer(container, order) {
6711
+ const desired = order.filter((key, i) => order.indexOf(key) === i);
6712
+ const current = getNavbarLinkKeysInContainer(container);
6713
+ const extras = current.filter((key) => !desired.includes(key));
6714
+ const expected = [...desired.filter((key) => current.includes(key)), ...extras];
6715
+ const orderedEls = [];
6716
+ if (navOrderKeysEqual(current, expected)) return;
6717
+ if (current.length === expected.length && current.every((key, i) => key === expected[i])) {
6718
+ return;
6719
+ }
6720
+ const lastCurrentLinkEl = current.length > 0 ? findCounterpartByHrefKey(current[current.length - 1], container) : null;
6721
+ const anchor = lastCurrentLinkEl?.parentElement === container ? lastCurrentLinkEl.nextElementSibling : null;
6417
6722
  const seen = /* @__PURE__ */ new Set();
6418
- for (const hrefKey of order) {
6723
+ for (const hrefKey of desired) {
6419
6724
  if (seen.has(hrefKey)) continue;
6420
6725
  seen.add(hrefKey);
6421
6726
  const el = findCounterpartByHrefKey(hrefKey, container);
6422
- if (el) container.appendChild(el);
6727
+ if (el && isNavbarLinkItem(el)) orderedEls.push(el);
6423
6728
  }
6424
6729
  for (const el of container.querySelectorAll("[data-ohw-href-key]")) {
6425
6730
  if (!isNavbarLinkItem(el)) continue;
6426
6731
  const key = el.getAttribute("data-ohw-href-key");
6427
6732
  if (!key || seen.has(key)) continue;
6428
- container.appendChild(el);
6733
+ orderedEls.push(el);
6734
+ }
6735
+ for (const el of orderedEls) {
6736
+ if (anchor) {
6737
+ if (el.nextSibling !== anchor) container.insertBefore(el, anchor);
6738
+ } else if (container.lastElementChild !== el) {
6739
+ container.appendChild(el);
6740
+ }
6429
6741
  }
6430
6742
  }
6431
6743
  function applyNavOrder(order) {
@@ -6434,6 +6746,75 @@ function applyNavOrder(order) {
6434
6746
  const drawer = getNavbarDrawerContainer();
6435
6747
  if (drawer) applyNavOrderToContainer(drawer, order);
6436
6748
  }
6749
+ function applyChildrenOrder(parentHrefKey, childKeys, root) {
6750
+ const parent = findCounterpartByHrefKey(parentHrefKey, root);
6751
+ if (!parent) return;
6752
+ const group = parent.closest("[data-ohw-nav-group]");
6753
+ const childrenRoot = group?.querySelector(":scope > [data-ohw-nav-children]");
6754
+ if (!childrenRoot) return;
6755
+ const current = Array.from(childrenRoot.querySelectorAll(":scope > [data-ohw-href-key]")).map((el) => el.getAttribute("data-ohw-href-key")).filter((key) => Boolean(key));
6756
+ if (navOrderKeysEqual(current, childKeys)) return;
6757
+ for (const key of childKeys) {
6758
+ const el = findCounterpartByHrefKey(key, root);
6759
+ if (!el) continue;
6760
+ if (childrenRoot.lastElementChild !== el) {
6761
+ childrenRoot.appendChild(el);
6762
+ }
6763
+ }
6764
+ }
6765
+ function applyNavForest(forest) {
6766
+ if (forestsEqual(forest, getNavForestFromDom())) return;
6767
+ const rootKeys = forest.map((n) => n.id);
6768
+ applyNavOrder(rootKeys);
6769
+ const desktop = getNavbarDesktopContainer();
6770
+ const drawer = getNavbarDrawerContainer();
6771
+ for (const node of forest) {
6772
+ if (node.children.length === 0) continue;
6773
+ const childKeys = node.children.map((c) => c.id);
6774
+ if (desktop) applyChildrenOrder(node.id, childKeys, desktop);
6775
+ if (drawer) applyChildrenOrder(node.id, childKeys, drawer);
6776
+ }
6777
+ }
6778
+ function getNavForestFromDom() {
6779
+ const items = listNavbarItems();
6780
+ const nestedChildKeys = /* @__PURE__ */ new Set();
6781
+ const roots = [];
6782
+ for (const item of items) {
6783
+ const key = item.getAttribute("data-ohw-href-key");
6784
+ if (!key || !isNavbarHrefKey(key)) continue;
6785
+ if (item.closest("[data-ohw-nav-children]")) continue;
6786
+ const group = item.closest("[data-ohw-nav-group]");
6787
+ const childrenRoot = group?.querySelector(":scope > [data-ohw-nav-children]");
6788
+ const children = [];
6789
+ if (childrenRoot) {
6790
+ for (const child of childrenRoot.querySelectorAll(":scope > [data-ohw-href-key]")) {
6791
+ if (!isNavbarLinkItem(child)) continue;
6792
+ const childKey = child.getAttribute("data-ohw-href-key");
6793
+ if (!childKey || !isNavbarHrefKey(childKey)) continue;
6794
+ nestedChildKeys.add(childKey);
6795
+ children.push({ id: childKey, children: [] });
6796
+ }
6797
+ }
6798
+ roots.push({ id: key, children });
6799
+ }
6800
+ if (roots.length === 0) {
6801
+ return forestFromFlatOrder(
6802
+ getNavOrderFromDom().filter((k) => isNavbarHrefKey(k) && !nestedChildKeys.has(k))
6803
+ );
6804
+ }
6805
+ return roots;
6806
+ }
6807
+ function planNavItemMove(hrefKey, parentId, insertIndex) {
6808
+ const model = getNavForestFromDom();
6809
+ const next = moveNode(model, hrefKey, { parentId, index: insertIndex });
6810
+ if (!next) return null;
6811
+ if (forestsEqual(model, next)) return null;
6812
+ return {
6813
+ forest: next,
6814
+ orderJson: serializeNavOrder(next),
6815
+ flatOrder: flattenNavOrder(next)
6816
+ };
6817
+ }
6437
6818
  function collectNavbarIndicesFromContent(content) {
6438
6819
  const indices = /* @__PURE__ */ new Set();
6439
6820
  for (const key of Object.keys(content)) {
@@ -6461,8 +6842,10 @@ function reconcileNavbarItemsFromContent(content) {
6461
6842
  if (!href && !label) continue;
6462
6843
  insertNavbarItemDom(index, href ?? "/", label ?? "Untitled", null);
6463
6844
  }
6464
- const order = parseNavOrder(content);
6465
- if (order?.length) applyNavOrder(order);
6845
+ const orderForest = parseNavOrderJson(content[NAV_ORDER_KEY]);
6846
+ if (orderForest?.length) {
6847
+ applyNavForest(orderForest);
6848
+ }
6466
6849
  }
6467
6850
  function buildNavOrderAfterInsert(afterAnchor, newHrefKey) {
6468
6851
  const order = getNavOrderFromDom();
@@ -6502,111 +6885,1018 @@ function insertNavbarItem(href, label, afterAnchor = null) {
6502
6885
  };
6503
6886
  }
6504
6887
 
6505
- // src/ui/navbar-container-chrome.tsx
6506
- var import_lucide_react10 = require("lucide-react");
6507
- var import_jsx_runtime21 = require("react/jsx-runtime");
6508
- function NavbarContainerChrome({
6509
- rect,
6510
- onAdd
6511
- }) {
6512
- const chromeGap = 6;
6513
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
6514
- "div",
6515
- {
6516
- "data-ohw-navbar-container-chrome": "",
6517
- "data-ohw-bridge": "",
6518
- className: "pointer-events-none fixed z-[2147483647]",
6519
- style: {
6520
- top: rect.top - chromeGap,
6521
- left: rect.left - chromeGap,
6522
- width: rect.width + chromeGap * 2,
6523
- height: rect.height + chromeGap * 2
6524
- },
6525
- children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
6526
- "button",
6527
- {
6528
- type: "button",
6529
- "data-ohw-navbar-add-button": "",
6530
- className: "pointer-events-auto absolute left-1/2 flex p-0.5 rounded-[10px] size-7 -translate-x-1/2 translate-y-1/2 items-center justify-center border border-border bg-background shadow-sm transition-colors hover:bg-muted/80",
6531
- style: { top: "70%" },
6532
- "aria-label": "Add item",
6533
- onMouseDown: (e) => {
6534
- e.preventDefault();
6535
- e.stopPropagation();
6536
- },
6537
- onClick: (e) => {
6538
- e.preventDefault();
6539
- e.stopPropagation();
6540
- onAdd();
6541
- },
6542
- children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react10.Plus, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
6543
- }
6544
- )
6545
- }
6546
- );
6888
+ // src/lib/footer-items.ts
6889
+ var FOOTER_ORDER_KEY = "__ohw_footer_order";
6890
+ var FOOTER_HREF_RE = /^footer-(\d+)-(\d+)-href$/;
6891
+ function parseFooterHrefKey(key) {
6892
+ if (!key) return null;
6893
+ const match = key.match(FOOTER_HREF_RE);
6894
+ if (!match) return null;
6895
+ return { col: parseInt(match[1], 10), item: parseInt(match[2], 10) };
6547
6896
  }
6548
-
6549
- // src/ui/badge.tsx
6550
- var import_jsx_runtime22 = require("react/jsx-runtime");
6551
- var badgeVariants = cva(
6552
- "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
6553
- {
6554
- variants: {
6555
- variant: {
6556
- default: "border-transparent bg-primary text-primary-foreground",
6557
- secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
6558
- destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
6559
- outline: "text-foreground"
6560
- }
6561
- },
6562
- defaultVariants: {
6563
- variant: "default"
6564
- }
6565
- }
6566
- );
6567
- function Badge({ className, variant, ...props }) {
6568
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: cn(badgeVariants({ variant }), className), ...props });
6897
+ function isFooterHrefKey(key) {
6898
+ return parseFooterHrefKey(key) !== null;
6569
6899
  }
6570
-
6571
- // src/OhhwellsBridge.tsx
6572
- var import_lucide_react11 = require("lucide-react");
6573
- var import_jsx_runtime23 = require("react/jsx-runtime");
6574
- var PRIMARY2 = "#0885FE";
6575
- var IMAGE_FADE_MS = 300;
6576
- function runOpacityFade(el, onDone) {
6577
- const anim = el.animate([{ opacity: 0 }, { opacity: 1 }], {
6578
- duration: IMAGE_FADE_MS,
6579
- easing: "ease",
6580
- fill: "forwards"
6581
- });
6582
- let finished = false;
6583
- const finish = () => {
6584
- if (finished) return;
6585
- finished = true;
6586
- anim.cancel();
6587
- el.style.opacity = "";
6588
- onDone();
6589
- };
6590
- anim.finished.then(finish).catch(finish);
6591
- window.setTimeout(finish, IMAGE_FADE_MS + 100);
6900
+ function getFooterRoot() {
6901
+ return document.querySelector('footer[data-ohw-section="footer"], footer');
6592
6902
  }
6593
- function fadeInImageElement(img, onReady) {
6594
- onReady();
6595
- img.style.opacity = "0";
6596
- runOpacityFade(img, () => {
6597
- img.style.opacity = "";
6903
+ function getFooterLinksContainer() {
6904
+ return document.querySelector("[data-ohw-footer-links]") ?? document.querySelector(".rb-footer-links");
6905
+ }
6906
+ function isFooterLinkAnchor(el) {
6907
+ if (!el.matches("[data-ohw-href-key]")) return false;
6908
+ if (!isFooterHrefKey(el.getAttribute("data-ohw-href-key"))) return false;
6909
+ return Boolean(el.querySelector('[data-ohw-editable="text"]'));
6910
+ }
6911
+ function listFooterColumns() {
6912
+ const root = getFooterLinksContainer() ?? getFooterRoot()?.querySelector(".rb-footer-links") ?? null;
6913
+ if (!root) return [];
6914
+ const explicit = Array.from(root.querySelectorAll(":scope > [data-ohw-footer-col]"));
6915
+ if (explicit.length > 0) return explicit;
6916
+ return Array.from(root.children).filter((el) => {
6917
+ if (!(el instanceof HTMLElement)) return false;
6918
+ return Array.from(el.querySelectorAll(":scope > [data-ohw-href-key]")).some(
6919
+ isFooterLinkAnchor
6920
+ );
6598
6921
  });
6599
6922
  }
6600
- function applyEditableImageSrc(img, url) {
6601
- img.removeAttribute("srcset");
6602
- img.removeAttribute("sizes");
6603
- img.src = url;
6923
+ function listFooterLinksInColumn(column) {
6924
+ return Array.from(column.querySelectorAll(":scope > [data-ohw-href-key]")).filter(
6925
+ isFooterLinkAnchor
6926
+ );
6604
6927
  }
6605
- function fadeInBgImage(el, url, onReady) {
6606
- const prevPos = el.style.position;
6607
- if (!prevPos || prevPos === "static") el.style.position = "relative";
6608
- const layer = document.createElement("div");
6609
- layer.setAttribute("data-ohw-bg-fade-layer", "");
6928
+ function findFooterColumnForLink(anchor) {
6929
+ const columns = listFooterColumns();
6930
+ return columns.find((col) => col.contains(anchor)) ?? null;
6931
+ }
6932
+ function getFooterOrderFromDom() {
6933
+ return listFooterColumns().map(
6934
+ (col) => listFooterLinksInColumn(col).map((el) => el.getAttribute("data-ohw-href-key")).filter((key) => Boolean(key))
6935
+ );
6936
+ }
6937
+ function findFooterLinkByKey(hrefKey) {
6938
+ return document.querySelector(
6939
+ `footer [data-ohw-href-key="${CSS.escape(hrefKey)}"]`
6940
+ );
6941
+ }
6942
+ function syncFooterColumnIndices(columns) {
6943
+ columns.forEach((col, index) => {
6944
+ const next = String(index);
6945
+ if (col.getAttribute("data-ohw-footer-col") !== next) {
6946
+ col.setAttribute("data-ohw-footer-col", next);
6947
+ }
6948
+ });
6949
+ }
6950
+ function appendChildIfNeeded(parent, child) {
6951
+ if (parent.lastElementChild !== child) {
6952
+ parent.appendChild(child);
6953
+ }
6954
+ }
6955
+ function applyFooterOrder(order) {
6956
+ const container = getFooterLinksContainer();
6957
+ if (!container) return;
6958
+ const columns = listFooterColumns();
6959
+ if (columns.length === 0) return;
6960
+ const currentOrder = getFooterOrderFromDom();
6961
+ if (ordersEqual(order, currentOrder)) {
6962
+ syncFooterColumnIndices(columns);
6963
+ return;
6964
+ }
6965
+ const used = /* @__PURE__ */ new Set();
6966
+ const orderedCols = [];
6967
+ for (const colOrder of order) {
6968
+ let matched = null;
6969
+ for (const hrefKey of colOrder) {
6970
+ const link = findFooterLinkByKey(hrefKey);
6971
+ if (!link) continue;
6972
+ const owner = findFooterColumnForLink(link);
6973
+ if (owner && !used.has(owner)) {
6974
+ matched = owner;
6975
+ break;
6976
+ }
6977
+ }
6978
+ if (!matched) {
6979
+ matched = columns.find((col) => !used.has(col)) ?? null;
6980
+ }
6981
+ if (matched) {
6982
+ used.add(matched);
6983
+ orderedCols.push(matched);
6984
+ }
6985
+ }
6986
+ for (const col of columns) {
6987
+ if (!used.has(col)) orderedCols.push(col);
6988
+ }
6989
+ for (const col of orderedCols) {
6990
+ appendChildIfNeeded(container, col);
6991
+ }
6992
+ const freshColumns = listFooterColumns();
6993
+ syncFooterColumnIndices(freshColumns);
6994
+ for (let c = 0; c < order.length; c++) {
6995
+ const col = freshColumns[c];
6996
+ if (!col) continue;
6997
+ const colOrder = order[c];
6998
+ for (const hrefKey of colOrder) {
6999
+ const el = findFooterLinkByKey(hrefKey);
7000
+ if (!el) continue;
7001
+ if (el.parentElement !== col || col.lastElementChild !== el) {
7002
+ col.appendChild(el);
7003
+ }
7004
+ }
7005
+ }
7006
+ }
7007
+ function ordersEqual(a, b) {
7008
+ if (a.length !== b.length) return false;
7009
+ for (let i = 0; i < a.length; i++) {
7010
+ const left = a[i];
7011
+ const right = b[i];
7012
+ if (left.length !== right.length) return false;
7013
+ for (let j = 0; j < left.length; j++) {
7014
+ if (left[j] !== right[j]) return false;
7015
+ }
7016
+ }
7017
+ return true;
7018
+ }
7019
+ function planFooterLinkMove(hrefKey, targetColIndex, insertIndex) {
7020
+ const order = getFooterOrderFromDom();
7021
+ if (targetColIndex < 0 || targetColIndex >= order.length) return null;
7022
+ let fromCol = -1;
7023
+ let fromIdx = -1;
7024
+ for (let c = 0; c < order.length; c++) {
7025
+ const idx = order[c].indexOf(hrefKey);
7026
+ if (idx >= 0) {
7027
+ fromCol = c;
7028
+ fromIdx = idx;
7029
+ break;
7030
+ }
7031
+ }
7032
+ if (fromCol < 0) return null;
7033
+ const next = order.map((col) => [...col]);
7034
+ next[fromCol].splice(fromIdx, 1);
7035
+ let adjusted = insertIndex;
7036
+ if (fromCol === targetColIndex && fromIdx < insertIndex) adjusted -= 1;
7037
+ adjusted = Math.max(0, Math.min(adjusted, next[targetColIndex].length));
7038
+ next[targetColIndex].splice(adjusted, 0, hrefKey);
7039
+ if (ordersEqual(next, order)) return null;
7040
+ return next;
7041
+ }
7042
+ function planFooterColumnMove(fromIndex, toIndex) {
7043
+ const order = getFooterOrderFromDom();
7044
+ const columns = listFooterColumns();
7045
+ if (fromIndex < 0 || fromIndex >= columns.length || toIndex < 0 || toIndex > columns.length) {
7046
+ return null;
7047
+ }
7048
+ if (toIndex === fromIndex || toIndex === fromIndex + 1) return null;
7049
+ const nextOrder = order.map((col) => [...col]);
7050
+ const [movedOrder] = nextOrder.splice(fromIndex, 1);
7051
+ if (!movedOrder) return null;
7052
+ let adjusted = toIndex;
7053
+ if (fromIndex < toIndex) adjusted -= 1;
7054
+ adjusted = Math.max(0, Math.min(adjusted, nextOrder.length));
7055
+ nextOrder.splice(adjusted, 0, movedOrder);
7056
+ if (ordersEqual(nextOrder, order)) return null;
7057
+ return nextOrder;
7058
+ }
7059
+ function parseFooterOrder(content) {
7060
+ const raw = content[FOOTER_ORDER_KEY];
7061
+ if (!raw) return null;
7062
+ try {
7063
+ const parsed = JSON.parse(raw);
7064
+ if (!Array.isArray(parsed)) return null;
7065
+ return parsed.filter((col) => Array.isArray(col)).map((col) => col.filter((key) => typeof key === "string" && key.length > 0));
7066
+ } catch {
7067
+ return null;
7068
+ }
7069
+ }
7070
+ function reconcileFooterOrderFromContent(content) {
7071
+ const order = parseFooterOrder(content);
7072
+ if (!order?.length) return;
7073
+ const current = getFooterOrderFromDom();
7074
+ if (ordersEqual(order, current)) {
7075
+ syncFooterColumnIndices(listFooterColumns());
7076
+ return;
7077
+ }
7078
+ applyFooterOrder(order);
7079
+ }
7080
+ function isRowLayoutColumn(links) {
7081
+ if (links.length < 2) return false;
7082
+ const firstTop = links[0].getBoundingClientRect().top;
7083
+ return links.every((link) => Math.abs(link.getBoundingClientRect().top - firstTop) < 4);
7084
+ }
7085
+ function buildLinkDropSlots(column, columnIndex) {
7086
+ const links = listFooterLinksInColumn(column);
7087
+ const colRect = column.getBoundingClientRect();
7088
+ const slots = [];
7089
+ const barThickness = 3;
7090
+ if (links.length === 0) {
7091
+ slots.push({
7092
+ insertIndex: 0,
7093
+ columnIndex,
7094
+ left: colRect.left,
7095
+ top: colRect.top + colRect.height / 2 - barThickness / 2,
7096
+ width: colRect.width,
7097
+ height: barThickness,
7098
+ direction: "horizontal"
7099
+ });
7100
+ return slots;
7101
+ }
7102
+ if (isRowLayoutColumn(links)) {
7103
+ for (let i = 0; i <= links.length; i++) {
7104
+ let left;
7105
+ if (i === 0) {
7106
+ left = links[0].getBoundingClientRect().left - barThickness / 2;
7107
+ } else if (i === links.length) {
7108
+ const last = links[links.length - 1].getBoundingClientRect();
7109
+ left = last.right - barThickness / 2;
7110
+ } else {
7111
+ const prev = links[i - 1].getBoundingClientRect();
7112
+ const next = links[i].getBoundingClientRect();
7113
+ left = (prev.right + next.left) / 2 - barThickness / 2;
7114
+ }
7115
+ const heightRef = i === 0 ? links[0].getBoundingClientRect() : i === links.length ? links[links.length - 1].getBoundingClientRect() : links[i].getBoundingClientRect();
7116
+ slots.push({
7117
+ insertIndex: i,
7118
+ columnIndex,
7119
+ left,
7120
+ top: heightRef.top,
7121
+ width: barThickness,
7122
+ height: Math.max(heightRef.height, colRect.height * 0.8),
7123
+ direction: "vertical"
7124
+ });
7125
+ }
7126
+ return slots;
7127
+ }
7128
+ for (let i = 0; i <= links.length; i++) {
7129
+ let top;
7130
+ if (i === 0) {
7131
+ top = links[0].getBoundingClientRect().top - barThickness / 2;
7132
+ } else if (i === links.length) {
7133
+ const last = links[links.length - 1].getBoundingClientRect();
7134
+ top = last.bottom - barThickness / 2;
7135
+ } else {
7136
+ const prev = links[i - 1].getBoundingClientRect();
7137
+ const next = links[i].getBoundingClientRect();
7138
+ top = (prev.bottom + next.top) / 2 - barThickness / 2;
7139
+ }
7140
+ const widthRef = i === 0 ? links[0].getBoundingClientRect() : i === links.length ? links[links.length - 1].getBoundingClientRect() : links[i].getBoundingClientRect();
7141
+ slots.push({
7142
+ insertIndex: i,
7143
+ columnIndex,
7144
+ left: widthRef.left,
7145
+ top,
7146
+ width: Math.max(widthRef.width, colRect.width * 0.8),
7147
+ height: barThickness,
7148
+ direction: "horizontal"
7149
+ });
7150
+ }
7151
+ return slots;
7152
+ }
7153
+ function buildColumnDropSlots() {
7154
+ const columns = listFooterColumns();
7155
+ const slots = [];
7156
+ const barThickness = 3;
7157
+ if (columns.length === 0) return slots;
7158
+ for (let i = 0; i <= columns.length; i++) {
7159
+ let left;
7160
+ let height;
7161
+ let top;
7162
+ if (i === 0) {
7163
+ const first = columns[0].getBoundingClientRect();
7164
+ left = first.left - barThickness / 2;
7165
+ top = first.top;
7166
+ height = first.height;
7167
+ } else if (i === columns.length) {
7168
+ const last = columns[columns.length - 1].getBoundingClientRect();
7169
+ left = last.right - barThickness / 2;
7170
+ top = last.top;
7171
+ height = last.height;
7172
+ } else {
7173
+ const prev = columns[i - 1].getBoundingClientRect();
7174
+ const next = columns[i].getBoundingClientRect();
7175
+ left = (prev.right + next.left) / 2 - barThickness / 2;
7176
+ top = Math.min(prev.top, next.top);
7177
+ height = Math.max(prev.bottom, next.bottom) - top;
7178
+ }
7179
+ slots.push({
7180
+ insertIndex: i,
7181
+ columnIndex: i,
7182
+ left,
7183
+ top,
7184
+ width: barThickness,
7185
+ height: Math.max(height, 24),
7186
+ direction: "vertical"
7187
+ });
7188
+ }
7189
+ return slots;
7190
+ }
7191
+ function hitTestLinkDropSlot(clientX, clientY, draggedHrefKey) {
7192
+ const columns = listFooterColumns();
7193
+ let best = null;
7194
+ for (let c = 0; c < columns.length; c++) {
7195
+ const col = columns[c];
7196
+ const colRect = col.getBoundingClientRect();
7197
+ const inColX = clientX >= colRect.left - 24 && clientX <= colRect.right + 24;
7198
+ if (!inColX) continue;
7199
+ const slots = buildLinkDropSlots(col, c).filter((slot) => {
7200
+ const links = listFooterLinksInColumn(col);
7201
+ const fromIdx = links.findIndex(
7202
+ (el) => el.getAttribute("data-ohw-href-key") === draggedHrefKey
7203
+ );
7204
+ if (fromIdx < 0) return true;
7205
+ if (c === findColumnIndexForKey(draggedHrefKey) && (slot.insertIndex === fromIdx || slot.insertIndex === fromIdx + 1)) {
7206
+ return true;
7207
+ }
7208
+ return true;
7209
+ });
7210
+ for (const slot of slots) {
7211
+ const dist = slot.direction === "vertical" ? Math.abs(clientX - (slot.left + slot.width / 2)) : Math.abs(clientY - (slot.top + slot.height / 2));
7212
+ if (!best || dist < best.dist) best = { slot, dist };
7213
+ }
7214
+ }
7215
+ return best?.slot ?? null;
7216
+ }
7217
+ function findColumnIndexForKey(hrefKey) {
7218
+ const order = getFooterOrderFromDom();
7219
+ for (let c = 0; c < order.length; c++) {
7220
+ if (order[c].includes(hrefKey)) return c;
7221
+ }
7222
+ return -1;
7223
+ }
7224
+ function hitTestColumnDropSlot(clientX, _clientY) {
7225
+ const slots = buildColumnDropSlots();
7226
+ let best = null;
7227
+ for (const slot of slots) {
7228
+ const cx2 = slot.left + slot.width / 2;
7229
+ const dist = Math.abs(clientX - cx2);
7230
+ if (!best || dist < best.dist) best = { slot, dist };
7231
+ }
7232
+ return best?.slot ?? null;
7233
+ }
7234
+
7235
+ // src/lib/item-drag-interaction.ts
7236
+ function disableNativeHrefDrag(el) {
7237
+ if (el.draggable) el.draggable = false;
7238
+ if (el.getAttribute("draggable") !== "false") {
7239
+ el.setAttribute("draggable", "false");
7240
+ }
7241
+ }
7242
+ function clearTextSelection() {
7243
+ const sel = window.getSelection();
7244
+ if (sel && !sel.isCollapsed) sel.removeAllRanges();
7245
+ }
7246
+ function armItemPressDrag() {
7247
+ document.documentElement.setAttribute("data-ohw-footer-press-drag", "");
7248
+ }
7249
+ function lockItemDuringDrag() {
7250
+ document.documentElement.removeAttribute("data-ohw-footer-press-drag");
7251
+ document.documentElement.setAttribute("data-ohw-item-dragging", "");
7252
+ clearTextSelection();
7253
+ }
7254
+ function unlockItemDragInteraction() {
7255
+ document.documentElement.removeAttribute("data-ohw-footer-press-drag");
7256
+ document.documentElement.removeAttribute("data-ohw-item-dragging");
7257
+ clearTextSelection();
7258
+ }
7259
+ var armFooterPressDrag = armItemPressDrag;
7260
+ var lockFooterDuringDrag = lockItemDuringDrag;
7261
+ var unlockFooterDragInteraction = unlockItemDragInteraction;
7262
+
7263
+ // src/lib/nav-dnd.ts
7264
+ function listReorderableNavItems() {
7265
+ return listNavbarItems().filter((el) => {
7266
+ const key = el.getAttribute("data-ohw-href-key");
7267
+ return isNavbarHrefKey(key) && !el.closest("[data-ohw-nav-children]");
7268
+ });
7269
+ }
7270
+ function listNavChildren(parentHrefKey) {
7271
+ const items = listNavbarItems();
7272
+ const parent = items.find((el) => el.getAttribute("data-ohw-href-key") === parentHrefKey);
7273
+ if (!parent) return [];
7274
+ const group = parent.closest("[data-ohw-nav-group]");
7275
+ const childrenRoot = group?.querySelector(":scope > [data-ohw-nav-children]");
7276
+ if (!childrenRoot) return [];
7277
+ return Array.from(childrenRoot.querySelectorAll(":scope > [data-ohw-href-key]")).filter(
7278
+ (el) => isNavbarHrefKey(el.getAttribute("data-ohw-href-key"))
7279
+ );
7280
+ }
7281
+ function activeNavListContainer() {
7282
+ const desktop = getNavbarDesktopContainer();
7283
+ if (desktop && desktop.getClientRects().length > 0) {
7284
+ const style = window.getComputedStyle(desktop);
7285
+ if (style.display !== "none" && style.visibility !== "hidden") return desktop;
7286
+ }
7287
+ return getNavbarDrawerContainer();
7288
+ }
7289
+ function buildRootNavDropSlots() {
7290
+ const items = listReorderableNavItems();
7291
+ const slots = [];
7292
+ const barThickness = 3;
7293
+ if (items.length === 0) {
7294
+ const container = activeNavListContainer();
7295
+ if (!container) return slots;
7296
+ const rect = container.getBoundingClientRect();
7297
+ slots.push({
7298
+ insertIndex: 0,
7299
+ parentId: null,
7300
+ left: rect.left + rect.width / 2 - barThickness / 2,
7301
+ top: rect.top,
7302
+ width: barThickness,
7303
+ height: Math.max(rect.height, 24),
7304
+ direction: "vertical"
7305
+ });
7306
+ return slots;
7307
+ }
7308
+ for (let i = 0; i <= items.length; i++) {
7309
+ let left;
7310
+ let top;
7311
+ let height;
7312
+ if (i === 0) {
7313
+ const first = items[0].getBoundingClientRect();
7314
+ left = first.left - barThickness / 2;
7315
+ top = first.top;
7316
+ height = first.height;
7317
+ } else if (i === items.length) {
7318
+ const last = items[items.length - 1].getBoundingClientRect();
7319
+ left = last.right - barThickness / 2;
7320
+ top = last.top;
7321
+ height = last.height;
7322
+ } else {
7323
+ const prev = items[i - 1].getBoundingClientRect();
7324
+ const next = items[i].getBoundingClientRect();
7325
+ left = (prev.right + next.left) / 2 - barThickness / 2;
7326
+ top = Math.min(prev.top, next.top);
7327
+ height = Math.max(prev.bottom, next.bottom) - top;
7328
+ }
7329
+ slots.push({
7330
+ insertIndex: i,
7331
+ parentId: null,
7332
+ left,
7333
+ top,
7334
+ width: barThickness,
7335
+ height: Math.max(height, 24),
7336
+ direction: "vertical"
7337
+ });
7338
+ }
7339
+ return slots;
7340
+ }
7341
+ function buildChildNavDropSlots(parentHrefKey) {
7342
+ const children = listNavChildren(parentHrefKey);
7343
+ const slots = [];
7344
+ const barThickness = 3;
7345
+ if (children.length === 0) return slots;
7346
+ for (let i = 0; i <= children.length; i++) {
7347
+ let top;
7348
+ let width;
7349
+ let left;
7350
+ if (i === 0) {
7351
+ const first = children[0].getBoundingClientRect();
7352
+ top = first.top - barThickness / 2;
7353
+ left = first.left;
7354
+ width = first.width;
7355
+ } else if (i === children.length) {
7356
+ const last = children[children.length - 1].getBoundingClientRect();
7357
+ top = last.bottom - barThickness / 2;
7358
+ left = last.left;
7359
+ width = last.width;
7360
+ } else {
7361
+ const prev = children[i - 1].getBoundingClientRect();
7362
+ const next = children[i].getBoundingClientRect();
7363
+ top = (prev.bottom + next.top) / 2 - barThickness / 2;
7364
+ left = Math.min(prev.left, next.left);
7365
+ width = Math.max(prev.right, next.right) - left;
7366
+ }
7367
+ slots.push({
7368
+ insertIndex: i,
7369
+ parentId: parentHrefKey,
7370
+ left,
7371
+ top,
7372
+ width: Math.max(width, 40),
7373
+ height: barThickness,
7374
+ direction: "horizontal"
7375
+ });
7376
+ }
7377
+ return slots;
7378
+ }
7379
+ function buildAllNavDropSlots(draggedHrefKey) {
7380
+ const slots = [...buildRootNavDropSlots()];
7381
+ for (const item of listReorderableNavItems()) {
7382
+ const key = item.getAttribute("data-ohw-href-key");
7383
+ if (!key || key === draggedHrefKey) continue;
7384
+ const group = item.closest("[data-ohw-nav-group]");
7385
+ if (!group?.querySelector(":scope > [data-ohw-nav-children]")) continue;
7386
+ slots.push(...buildChildNavDropSlots(key));
7387
+ }
7388
+ const dragged = listNavbarItems().find((el) => el.getAttribute("data-ohw-href-key") === draggedHrefKey);
7389
+ const parentGroup = dragged?.closest("[data-ohw-nav-children]")?.closest("[data-ohw-nav-group]");
7390
+ const parentTrigger = parentGroup?.querySelector(":scope > [data-ohw-href-key]");
7391
+ const parentKey = parentTrigger?.getAttribute("data-ohw-href-key");
7392
+ if (parentKey && isNavbarHrefKey(parentKey)) {
7393
+ const existing = slots.some((s) => s.parentId === parentKey);
7394
+ if (!existing) slots.push(...buildChildNavDropSlots(parentKey));
7395
+ }
7396
+ return slots;
7397
+ }
7398
+ function hitTestNavDropSlot(clientX, clientY, draggedHrefKey) {
7399
+ const slots = buildAllNavDropSlots(draggedHrefKey);
7400
+ let best = null;
7401
+ for (const slot of slots) {
7402
+ const cx2 = slot.left + slot.width / 2;
7403
+ const cy = slot.top + slot.height / 2;
7404
+ 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;
7405
+ if (!best || dist < best.dist) best = { slot, dist };
7406
+ }
7407
+ return best?.slot ?? null;
7408
+ }
7409
+ function siblingRectsForNavDrag(draggedEl, activeParentId) {
7410
+ if (activeParentId) {
7411
+ return listNavChildren(activeParentId).filter((el) => el !== draggedEl).map((el) => el.getBoundingClientRect());
7412
+ }
7413
+ return listReorderableNavItems().filter((el) => el !== draggedEl).map((el) => el.getBoundingClientRect());
7414
+ }
7415
+
7416
+ // src/useNavItemDrag.ts
7417
+ var import_react8 = require("react");
7418
+ function useNavItemDrag({
7419
+ isEditMode,
7420
+ editContentRef,
7421
+ selectedElRef,
7422
+ activeElRef,
7423
+ footerDragRef,
7424
+ suppressNextClickRef,
7425
+ suppressClickUntilRef,
7426
+ siblingHintElRef,
7427
+ postToParentRef,
7428
+ deselectRef,
7429
+ selectRef,
7430
+ deactivateRef,
7431
+ setLinkPopover,
7432
+ linkPopoverOpenRef,
7433
+ setIsItemDragging,
7434
+ setDraggedItemRect,
7435
+ setSiblingHintRect,
7436
+ setSiblingHintRects,
7437
+ setToolbarRect,
7438
+ getNavigationItemAnchor: getNavigationItemAnchor2,
7439
+ isDragHandleDisabled: isDragHandleDisabled2,
7440
+ isNavbarButton: isNavbarButton3
7441
+ }) {
7442
+ const navDragRef = (0, import_react8.useRef)(null);
7443
+ const [navDropSlots, setNavDropSlots] = (0, import_react8.useState)([]);
7444
+ const [activeNavDropIndex, setActiveNavDropIndex] = (0, import_react8.useState)(null);
7445
+ const navPointerDragRef = (0, import_react8.useRef)(null);
7446
+ const clearNavDragVisuals = (0, import_react8.useCallback)(() => {
7447
+ navDragRef.current = null;
7448
+ setNavDropSlots([]);
7449
+ setActiveNavDropIndex(null);
7450
+ setDraggedItemRect(null);
7451
+ setSiblingHintRects([]);
7452
+ setIsItemDragging(false);
7453
+ unlockItemDragInteraction();
7454
+ }, [setDraggedItemRect, setIsItemDragging, setSiblingHintRects]);
7455
+ const refreshNavDragVisuals = (0, import_react8.useCallback)(
7456
+ (session, activeSlot, clientX, clientY) => {
7457
+ setDraggedItemRect(session.draggedEl.getBoundingClientRect());
7458
+ if (typeof clientX === "number" && typeof clientY === "number") {
7459
+ session.lastClientX = clientX;
7460
+ session.lastClientY = clientY;
7461
+ }
7462
+ session.activeSlot = activeSlot;
7463
+ const parentId = activeSlot?.parentId ?? null;
7464
+ setSiblingHintRects(siblingRectsForNavDrag(session.draggedEl, parentId));
7465
+ const slots = buildAllNavDropSlots(session.hrefKey);
7466
+ setNavDropSlots(slots);
7467
+ const activeIdx = activeSlot ? slots.findIndex(
7468
+ (s) => s.parentId === activeSlot.parentId && s.insertIndex === activeSlot.insertIndex
7469
+ ) : -1;
7470
+ setActiveNavDropIndex(activeIdx >= 0 ? activeIdx : null);
7471
+ },
7472
+ [setDraggedItemRect, setSiblingHintRects]
7473
+ );
7474
+ const refreshNavDragVisualsRef = (0, import_react8.useRef)(refreshNavDragVisuals);
7475
+ refreshNavDragVisualsRef.current = refreshNavDragVisuals;
7476
+ const commitNavDragRef = (0, import_react8.useRef)(() => {
7477
+ });
7478
+ const beginNavDragRef = (0, import_react8.useRef)(() => {
7479
+ });
7480
+ const beginNavDrag = (0, import_react8.useCallback)(
7481
+ (session) => {
7482
+ const rect = session.draggedEl.getBoundingClientRect();
7483
+ session.lastClientX = session.lastClientX || rect.left + rect.width / 2;
7484
+ session.lastClientY = session.lastClientY || rect.top + rect.height / 2;
7485
+ session.activeSlot = session.activeSlot ?? null;
7486
+ navDragRef.current = session;
7487
+ setIsItemDragging(true);
7488
+ lockItemDuringDrag();
7489
+ siblingHintElRef.current = null;
7490
+ setSiblingHintRect(null);
7491
+ if (session.wasSelected && selectedElRef.current === session.draggedEl) {
7492
+ setToolbarRect(rect);
7493
+ }
7494
+ const initialSlot = hitTestNavDropSlot(session.lastClientX, session.lastClientY, session.hrefKey);
7495
+ refreshNavDragVisuals(session, initialSlot, session.lastClientX, session.lastClientY);
7496
+ },
7497
+ [
7498
+ refreshNavDragVisuals,
7499
+ selectedElRef,
7500
+ setIsItemDragging,
7501
+ setSiblingHintRect,
7502
+ setToolbarRect,
7503
+ siblingHintElRef
7504
+ ]
7505
+ );
7506
+ beginNavDragRef.current = beginNavDrag;
7507
+ const commitNavDrag = (0, import_react8.useCallback)(
7508
+ (clientX, clientY) => {
7509
+ const session = navDragRef.current;
7510
+ if (!session) {
7511
+ clearNavDragVisuals();
7512
+ return;
7513
+ }
7514
+ const x = typeof clientX === "number" && (clientX !== 0 || clientY !== 0) ? clientX : session.lastClientX;
7515
+ const y = typeof clientY === "number" && (clientX !== 0 || clientY !== 0) ? clientY : session.lastClientY;
7516
+ const slot = session.activeSlot ?? hitTestNavDropSlot(x, y, session.hrefKey);
7517
+ const planned = slot != null ? planNavItemMove(session.hrefKey, slot.parentId, slot.insertIndex) : null;
7518
+ const wasSelected = session.wasSelected;
7519
+ const hrefKey = session.hrefKey;
7520
+ const applySelectionAfterDrop = () => {
7521
+ if (!wasSelected) {
7522
+ deselectRef.current();
7523
+ return;
7524
+ }
7525
+ const desktop = document.querySelector("[data-ohw-nav-container]");
7526
+ const drawer = document.querySelector("[data-ohw-nav-drawer]");
7527
+ 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)}"]`);
7528
+ if (link) {
7529
+ selectRef.current(link);
7530
+ return;
7531
+ }
7532
+ deselectRef.current();
7533
+ };
7534
+ if (planned) {
7535
+ editContentRef.current = {
7536
+ ...editContentRef.current,
7537
+ [NAV_ORDER_KEY]: planned.orderJson
7538
+ };
7539
+ applyNavForest(planned.forest);
7540
+ document.querySelectorAll(
7541
+ "nav [data-ohw-href-key], [data-ohw-nav-container] [data-ohw-href-key], [data-ohw-nav-drawer] [data-ohw-href-key]"
7542
+ ).forEach((el) => {
7543
+ if (isNavbarHrefKey(el.getAttribute("data-ohw-href-key"))) {
7544
+ disableNativeHrefDrag(el);
7545
+ }
7546
+ });
7547
+ postToParentRef.current({
7548
+ type: "ow:change",
7549
+ nodes: [{ key: NAV_ORDER_KEY, text: planned.orderJson }]
7550
+ });
7551
+ applySelectionAfterDrop();
7552
+ clearNavDragVisuals();
7553
+ requestAnimationFrame(() => {
7554
+ if (editContentRef.current[NAV_ORDER_KEY] === planned.orderJson) {
7555
+ applyNavForest(planned.forest);
7556
+ }
7557
+ applySelectionAfterDrop();
7558
+ requestAnimationFrame(applySelectionAfterDrop);
7559
+ });
7560
+ return;
7561
+ }
7562
+ applySelectionAfterDrop();
7563
+ clearNavDragVisuals();
7564
+ },
7565
+ [clearNavDragVisuals, deselectRef, editContentRef, postToParentRef, selectRef]
7566
+ );
7567
+ commitNavDragRef.current = commitNavDrag;
7568
+ const startNavLinkDrag = (0, import_react8.useCallback)(
7569
+ (anchor, clientX, clientY, wasSelected) => {
7570
+ if (footerDragRef.current) return false;
7571
+ const hrefKey = anchor.getAttribute("data-ohw-href-key");
7572
+ if (!hrefKey || !isNavbarHrefKey(hrefKey)) return false;
7573
+ if (isNavbarButton3(anchor) || isDragHandleDisabled2(anchor)) return false;
7574
+ beginNavDrag({
7575
+ hrefKey,
7576
+ wasSelected,
7577
+ draggedEl: anchor,
7578
+ lastClientX: clientX,
7579
+ lastClientY: clientY,
7580
+ activeSlot: null
7581
+ });
7582
+ return true;
7583
+ },
7584
+ [beginNavDrag, footerDragRef, isDragHandleDisabled2, isNavbarButton3]
7585
+ );
7586
+ const onNavDragOver = (0, import_react8.useCallback)(
7587
+ (e) => {
7588
+ const session = navDragRef.current;
7589
+ if (!session) return false;
7590
+ e.preventDefault();
7591
+ if (e.dataTransfer) e.dataTransfer.dropEffect = "move";
7592
+ const slot = hitTestNavDropSlot(e.clientX, e.clientY, session.hrefKey);
7593
+ refreshNavDragVisualsRef.current(session, slot, e.clientX, e.clientY);
7594
+ return true;
7595
+ },
7596
+ []
7597
+ );
7598
+ (0, import_react8.useEffect)(() => {
7599
+ if (!isEditMode) return;
7600
+ const THRESHOLD = 10;
7601
+ const resolveWasSelected = (el) => {
7602
+ if (selectedElRef.current === el) return true;
7603
+ const activeAnchor = activeElRef.current ? getNavigationItemAnchor2(activeElRef.current) : null;
7604
+ return activeAnchor === el;
7605
+ };
7606
+ const onPointerDown = (e) => {
7607
+ if (e.button !== 0) return;
7608
+ if (navDragRef.current || footerDragRef.current) return;
7609
+ const target = e.target;
7610
+ if (!target) return;
7611
+ if (target.closest(
7612
+ '[data-ohw-drag-handle-container], [data-slot="drag-handle"], [data-ohw-toolbar], [data-ohw-item-toolbar-anchor], [data-ohw-link-popover-root]'
7613
+ )) {
7614
+ return;
7615
+ }
7616
+ if (target.closest("[data-ohw-item-drag-surface]")) return;
7617
+ const anchor = getNavigationItemAnchor2(target);
7618
+ const hrefKey = anchor?.getAttribute("data-ohw-href-key") ?? null;
7619
+ if (!anchor || !isNavbarHrefKey(hrefKey)) return;
7620
+ if (isNavbarButton3(anchor) || isDragHandleDisabled2(anchor)) return;
7621
+ navPointerDragRef.current = {
7622
+ el: anchor,
7623
+ startX: e.clientX,
7624
+ startY: e.clientY,
7625
+ pointerId: e.pointerId,
7626
+ wasSelected: resolveWasSelected(anchor),
7627
+ started: false
7628
+ };
7629
+ };
7630
+ const onPointerMove = (e) => {
7631
+ const pending = navPointerDragRef.current;
7632
+ if (!pending) return;
7633
+ if (pending.started) {
7634
+ e.preventDefault();
7635
+ clearTextSelection();
7636
+ const session = navDragRef.current;
7637
+ if (!session) return;
7638
+ const slot = hitTestNavDropSlot(e.clientX, e.clientY, session.hrefKey);
7639
+ refreshNavDragVisualsRef.current(session, slot, e.clientX, e.clientY);
7640
+ return;
7641
+ }
7642
+ const dx = e.clientX - pending.startX;
7643
+ const dy = e.clientY - pending.startY;
7644
+ if (dx * dx + dy * dy < THRESHOLD * THRESHOLD) return;
7645
+ e.preventDefault();
7646
+ pending.started = true;
7647
+ armItemPressDrag();
7648
+ clearTextSelection();
7649
+ try {
7650
+ document.body.setPointerCapture(pending.pointerId);
7651
+ } catch {
7652
+ }
7653
+ if (linkPopoverOpenRef.current) setLinkPopover(null);
7654
+ if (activeElRef.current) deactivateRef.current();
7655
+ const key = pending.el.getAttribute("data-ohw-href-key");
7656
+ if (!key) return;
7657
+ beginNavDragRef.current({
7658
+ hrefKey: key,
7659
+ wasSelected: pending.wasSelected,
7660
+ draggedEl: pending.el,
7661
+ lastClientX: e.clientX,
7662
+ lastClientY: e.clientY,
7663
+ activeSlot: null
7664
+ });
7665
+ };
7666
+ const endPointerDrag = (e) => {
7667
+ const pending = navPointerDragRef.current;
7668
+ navPointerDragRef.current = null;
7669
+ try {
7670
+ if (document.body.hasPointerCapture(e.pointerId)) {
7671
+ document.body.releasePointerCapture(e.pointerId);
7672
+ }
7673
+ } catch {
7674
+ }
7675
+ if (!pending) return;
7676
+ if (!pending.started) {
7677
+ unlockItemDragInteraction();
7678
+ return;
7679
+ }
7680
+ suppressNextClickRef.current = true;
7681
+ suppressClickUntilRef.current = Date.now() + 500;
7682
+ commitNavDragRef.current(e.clientX, e.clientY);
7683
+ };
7684
+ const blockSelectStart = (e) => {
7685
+ if (navDragRef.current || navPointerDragRef.current?.started || document.documentElement.hasAttribute("data-ohw-footer-press-drag")) {
7686
+ e.preventDefault();
7687
+ }
7688
+ };
7689
+ const onDragEnd = () => {
7690
+ if (!navDragRef.current) return;
7691
+ suppressNextClickRef.current = true;
7692
+ suppressClickUntilRef.current = Date.now() + 500;
7693
+ commitNavDragRef.current();
7694
+ };
7695
+ document.addEventListener("pointerdown", onPointerDown, true);
7696
+ document.addEventListener("pointermove", onPointerMove, true);
7697
+ document.addEventListener("pointerup", endPointerDrag, true);
7698
+ document.addEventListener("pointercancel", endPointerDrag, true);
7699
+ document.addEventListener("selectstart", blockSelectStart, true);
7700
+ document.addEventListener("dragend", onDragEnd, true);
7701
+ return () => {
7702
+ document.removeEventListener("pointerdown", onPointerDown, true);
7703
+ document.removeEventListener("pointermove", onPointerMove, true);
7704
+ document.removeEventListener("pointerup", endPointerDrag, true);
7705
+ document.removeEventListener("pointercancel", endPointerDrag, true);
7706
+ document.removeEventListener("selectstart", blockSelectStart, true);
7707
+ document.removeEventListener("dragend", onDragEnd, true);
7708
+ unlockItemDragInteraction();
7709
+ };
7710
+ }, [
7711
+ activeElRef,
7712
+ deactivateRef,
7713
+ footerDragRef,
7714
+ getNavigationItemAnchor2,
7715
+ isDragHandleDisabled2,
7716
+ isEditMode,
7717
+ isNavbarButton3,
7718
+ linkPopoverOpenRef,
7719
+ selectedElRef,
7720
+ setLinkPopover,
7721
+ suppressNextClickRef
7722
+ ]);
7723
+ const armNavPressFromChrome = (0, import_react8.useCallback)(
7724
+ (selected, clientX, clientY, pointerId) => {
7725
+ const hrefKey = selected.getAttribute("data-ohw-href-key");
7726
+ if (!hrefKey || !isNavbarHrefKey(hrefKey)) return false;
7727
+ if (isNavbarButton3(selected) || isDragHandleDisabled2(selected)) return false;
7728
+ navPointerDragRef.current = {
7729
+ el: selected,
7730
+ startX: clientX,
7731
+ startY: clientY,
7732
+ pointerId,
7733
+ wasSelected: true,
7734
+ started: false
7735
+ };
7736
+ return true;
7737
+ },
7738
+ [isDragHandleDisabled2, isNavbarButton3]
7739
+ );
7740
+ return {
7741
+ navDragRef,
7742
+ navDropSlots,
7743
+ activeNavDropIndex,
7744
+ startNavLinkDrag,
7745
+ commitNavDrag,
7746
+ onNavDragOver,
7747
+ armNavPressFromChrome,
7748
+ clearNavDragVisuals,
7749
+ refreshNavDragVisualsRef
7750
+ };
7751
+ }
7752
+
7753
+ // src/ui/navbar-container-chrome.tsx
7754
+ var import_lucide_react10 = require("lucide-react");
7755
+ var import_jsx_runtime21 = require("react/jsx-runtime");
7756
+ function NavbarContainerChrome({
7757
+ rect,
7758
+ onAdd
7759
+ }) {
7760
+ const chromeGap = 6;
7761
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7762
+ "div",
7763
+ {
7764
+ "data-ohw-navbar-container-chrome": "",
7765
+ "data-ohw-bridge": "",
7766
+ className: "pointer-events-none fixed z-[2147483647]",
7767
+ style: {
7768
+ top: rect.top - chromeGap,
7769
+ left: rect.left - chromeGap,
7770
+ width: rect.width + chromeGap * 2,
7771
+ height: rect.height + chromeGap * 2
7772
+ },
7773
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
7774
+ "button",
7775
+ {
7776
+ type: "button",
7777
+ "data-ohw-navbar-add-button": "",
7778
+ 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",
7779
+ style: { top: "70%" },
7780
+ "aria-label": "Add item",
7781
+ onMouseDown: (e) => {
7782
+ e.preventDefault();
7783
+ e.stopPropagation();
7784
+ },
7785
+ onClick: (e) => {
7786
+ e.preventDefault();
7787
+ e.stopPropagation();
7788
+ onAdd();
7789
+ },
7790
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react10.Plus, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
7791
+ }
7792
+ )
7793
+ }
7794
+ );
7795
+ }
7796
+
7797
+ // src/ui/drop-indicator.tsx
7798
+ var React10 = __toESM(require("react"), 1);
7799
+ var import_jsx_runtime22 = require("react/jsx-runtime");
7800
+ var dropIndicatorVariants = cva(
7801
+ "ov-gap-line pointer-events-none shrink-0 transition-opacity duration-150",
7802
+ {
7803
+ variants: {
7804
+ direction: {
7805
+ vertical: "h-6 w-[3px]",
7806
+ horizontal: "h-[3px] w-[200px]"
7807
+ },
7808
+ state: {
7809
+ default: "opacity-0",
7810
+ hover: "opacity-100",
7811
+ dragIdle: "opacity-40",
7812
+ dragActive: "opacity-100"
7813
+ }
7814
+ },
7815
+ defaultVariants: {
7816
+ direction: "vertical",
7817
+ state: "default"
7818
+ }
7819
+ }
7820
+ );
7821
+ var DropIndicator = React10.forwardRef(
7822
+ ({ className, direction, state, ...props }, ref) => {
7823
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
7824
+ "div",
7825
+ {
7826
+ ref,
7827
+ "data-slot": "drop-indicator",
7828
+ "data-direction": direction ?? "vertical",
7829
+ "data-state": state ?? "default",
7830
+ "aria-hidden": "true",
7831
+ className: cn(dropIndicatorVariants({ direction, state }), className),
7832
+ ...props
7833
+ }
7834
+ );
7835
+ }
7836
+ );
7837
+ DropIndicator.displayName = "DropIndicator";
7838
+
7839
+ // src/ui/badge.tsx
7840
+ var import_jsx_runtime23 = require("react/jsx-runtime");
7841
+ var badgeVariants = cva(
7842
+ "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",
7843
+ {
7844
+ variants: {
7845
+ variant: {
7846
+ default: "border-transparent bg-primary text-primary-foreground",
7847
+ secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
7848
+ destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
7849
+ outline: "text-foreground"
7850
+ }
7851
+ },
7852
+ defaultVariants: {
7853
+ variant: "default"
7854
+ }
7855
+ }
7856
+ );
7857
+ function Badge({ className, variant, ...props }) {
7858
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: cn(badgeVariants({ variant }), className), ...props });
7859
+ }
7860
+
7861
+ // src/OhhwellsBridge.tsx
7862
+ var import_lucide_react11 = require("lucide-react");
7863
+ var import_jsx_runtime24 = require("react/jsx-runtime");
7864
+ var PRIMARY2 = "#0885FE";
7865
+ var IMAGE_FADE_MS = 300;
7866
+ function runOpacityFade(el, onDone) {
7867
+ const anim = el.animate([{ opacity: 0 }, { opacity: 1 }], {
7868
+ duration: IMAGE_FADE_MS,
7869
+ easing: "ease",
7870
+ fill: "forwards"
7871
+ });
7872
+ let finished = false;
7873
+ const finish = () => {
7874
+ if (finished) return;
7875
+ finished = true;
7876
+ anim.cancel();
7877
+ el.style.opacity = "";
7878
+ onDone();
7879
+ };
7880
+ anim.finished.then(finish).catch(finish);
7881
+ window.setTimeout(finish, IMAGE_FADE_MS + 100);
7882
+ }
7883
+ function fadeInImageElement(img, onReady) {
7884
+ onReady();
7885
+ img.style.opacity = "0";
7886
+ runOpacityFade(img, () => {
7887
+ img.style.opacity = "";
7888
+ });
7889
+ }
7890
+ function applyEditableImageSrc(img, url) {
7891
+ img.removeAttribute("srcset");
7892
+ img.removeAttribute("sizes");
7893
+ img.src = url;
7894
+ }
7895
+ function fadeInBgImage(el, url, onReady) {
7896
+ const prevPos = el.style.position;
7897
+ if (!prevPos || prevPos === "static") el.style.position = "relative";
7898
+ const layer = document.createElement("div");
7899
+ layer.setAttribute("data-ohw-bg-fade-layer", "");
6610
7900
  Object.assign(layer.style, {
6611
7901
  position: "absolute",
6612
7902
  inset: "0",
@@ -6749,7 +8039,7 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
6749
8039
  const root = (0, import_client.createRoot)(container);
6750
8040
  (0, import_react_dom2.flushSync)(() => {
6751
8041
  root.render(
6752
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8042
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
6753
8043
  SchedulingWidget,
6754
8044
  {
6755
8045
  notifyOnConnect,
@@ -6811,7 +8101,20 @@ function isDragHandleDisabled(el) {
6811
8101
  return raw === "true" || raw === "";
6812
8102
  });
6813
8103
  }
6814
- function collectEditableNodes() {
8104
+ function canDragNavigationItem(anchor) {
8105
+ const { key, disabled } = getNavigationItemReorderState(anchor);
8106
+ return Boolean(key) && !disabled;
8107
+ }
8108
+ function syncNavigationDragCursorAttrs() {
8109
+ document.querySelectorAll("nav [data-ohw-href-key], footer [data-ohw-href-key]").forEach((el) => {
8110
+ if (!isNavigationItem(el) || !canDragNavigationItem(el)) {
8111
+ el.removeAttribute("data-ohw-can-drag");
8112
+ return;
8113
+ }
8114
+ el.setAttribute("data-ohw-can-drag", "");
8115
+ });
8116
+ }
8117
+ function collectEditableNodes(extraContent) {
6815
8118
  const nodes = Array.from(document.querySelectorAll("[data-ohw-editable]")).map((el) => {
6816
8119
  if (el.dataset.ohwEditable === "image") {
6817
8120
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -6839,6 +8142,14 @@ function collectEditableNodes() {
6839
8142
  if (!key) return;
6840
8143
  nodes.push({ key, type: "link", text: getLinkHref2(el) });
6841
8144
  });
8145
+ if (extraContent) {
8146
+ for (const key of [NAV_ORDER_KEY, FOOTER_ORDER_KEY, NAV_COUNT_KEY]) {
8147
+ const text = extraContent[key];
8148
+ if (typeof text === "string" && text.length > 0) {
8149
+ nodes.push({ key, type: "meta", text });
8150
+ }
8151
+ }
8152
+ }
6842
8153
  document.querySelectorAll('[data-ohw-editable="video"]').forEach((el) => {
6843
8154
  const key = el.dataset.ohwKey ?? "";
6844
8155
  const video = getVideoEl(el);
@@ -6952,6 +8263,11 @@ function getNavigationItemAnchor(el) {
6952
8263
  function isNavigationItem(el) {
6953
8264
  return getNavigationItemAnchor(el) !== null;
6954
8265
  }
8266
+ function listNavigationItems() {
8267
+ return Array.from(
8268
+ document.querySelectorAll("nav [data-ohw-href-key], footer [data-ohw-href-key]")
8269
+ ).filter((el) => isNavigationItem(el));
8270
+ }
6955
8271
  function getNavigationItemReorderState(anchor) {
6956
8272
  if (isNavbarButton2(anchor)) return { key: null, disabled: false };
6957
8273
  return getReorderHandleStateFromAnchor(anchor);
@@ -6980,18 +8296,20 @@ function getNavigationRoot(el) {
6980
8296
  if (explicit) return explicit;
6981
8297
  return el.closest("nav, footer, aside");
6982
8298
  }
8299
+ function countFooterNavItems(el) {
8300
+ return Array.from(el.querySelectorAll("[data-ohw-href-key]")).filter(isNavigationItem).length;
8301
+ }
6983
8302
  function findFooterItemGroup(item) {
8303
+ const explicit = item.closest("[data-ohw-footer-col], [data-ohw-footer-column]");
8304
+ if (explicit) return explicit;
6984
8305
  const footer = item.closest("footer");
6985
8306
  if (!footer) return null;
6986
8307
  let node = item.parentElement;
6987
8308
  while (node && node !== footer) {
6988
- const hasDirectNavItems = Array.from(
6989
- node.querySelectorAll(":scope > [data-ohw-href-key]")
6990
- ).some(isNavigationItem);
6991
- if (hasDirectNavItems) return node;
8309
+ if (countFooterNavItems(node) >= 2) return node;
6992
8310
  node = node.parentElement;
6993
8311
  }
6994
- return null;
8312
+ return footer;
6995
8313
  }
6996
8314
  function isNavigationRoot(el) {
6997
8315
  return el.hasAttribute("data-ohw-nav-root") || el.matches("nav, footer, aside");
@@ -6999,16 +8317,49 @@ function isNavigationRoot(el) {
6999
8317
  function isInferredFooterGroup(el) {
7000
8318
  const footer = el.closest("footer");
7001
8319
  if (!footer || el === footer) return false;
7002
- return Array.from(el.querySelectorAll(":scope > [data-ohw-href-key]")).some(isNavigationItem);
8320
+ if (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column")) return true;
8321
+ return countFooterNavItems(el) >= 2;
7003
8322
  }
7004
8323
  function isNavigationContainer(el) {
7005
- return el.hasAttribute("data-ohw-nav-container") || isNavigationRoot(el) || isInferredFooterGroup(el);
8324
+ return el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isNavigationRoot(el) || isInferredFooterGroup(el);
8325
+ }
8326
+ function isNavbarLinksContainer(el) {
8327
+ return el.hasAttribute("data-ohw-nav-container");
8328
+ }
8329
+ function getFooterColumn(el) {
8330
+ return el.closest("[data-ohw-footer-col], [data-ohw-footer-column]");
8331
+ }
8332
+ function resolveFooterColumnSelectionTarget(target, clientX, clientY) {
8333
+ if (getNavigationItemAnchor(target)) return null;
8334
+ const column = getFooterColumn(target);
8335
+ if (!column) return null;
8336
+ if (isPointOverNavItem(column, clientX, clientY)) return null;
8337
+ return column;
7006
8338
  }
7007
8339
  function isPointOverNavigation(x, y) {
8340
+ const roots = /* @__PURE__ */ new Set();
7008
8341
  const navRoot = document.querySelector("[data-ohw-nav-root]") ?? document.querySelector("nav");
7009
- if (!navRoot) return false;
7010
- const r2 = navRoot.getBoundingClientRect();
7011
- return x >= r2.left && x <= r2.right && y >= r2.top && y <= r2.bottom;
8342
+ if (navRoot) roots.add(navRoot);
8343
+ const footer = document.querySelector("footer");
8344
+ if (footer) roots.add(footer);
8345
+ for (const root of roots) {
8346
+ const r2 = root.getBoundingClientRect();
8347
+ if (x >= r2.left && x <= r2.right && y >= r2.top && y <= r2.bottom) return true;
8348
+ }
8349
+ return false;
8350
+ }
8351
+ function findHoveredNavOrFooterContainer(x, y) {
8352
+ const candidates = [
8353
+ ...document.querySelectorAll("[data-ohw-nav-container]"),
8354
+ ...listFooterColumns()
8355
+ ];
8356
+ for (const container of candidates) {
8357
+ const r2 = container.getBoundingClientRect();
8358
+ if (x < r2.left || x > r2.right || y < r2.top || y > r2.bottom) continue;
8359
+ if (isPointOverNavItem(container, x, y)) continue;
8360
+ return container;
8361
+ }
8362
+ return null;
7012
8363
  }
7013
8364
  function isPointOverNavItem(container, x, y) {
7014
8365
  return Array.from(container.querySelectorAll("[data-ohw-href-key]")).some((el) => {
@@ -7041,36 +8392,63 @@ function getNavigationSelectionParent(el) {
7041
8392
  if (isNavigationItem(el)) {
7042
8393
  const explicit = el.closest("[data-ohw-nav-container]");
7043
8394
  if (explicit) return explicit;
8395
+ const footerColumn = getFooterColumn(el);
8396
+ if (footerColumn) return footerColumn;
7044
8397
  const footerGroup = findFooterItemGroup(el);
7045
8398
  if (footerGroup) return footerGroup;
7046
8399
  return getNavigationRoot(el);
7047
8400
  }
7048
- if (el.hasAttribute("data-ohw-nav-container") || isInferredFooterGroup(el)) {
8401
+ if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup(el)) {
7049
8402
  return getNavigationRoot(el);
7050
8403
  }
7051
8404
  return null;
7052
8405
  }
8406
+ function collectNavigationItemSiblingHintRects(selected) {
8407
+ if (!isNavigationItem(selected)) return [];
8408
+ const footerColumn = getFooterColumn(selected);
8409
+ if (footerColumn) {
8410
+ return listFooterLinksInColumn(footerColumn).filter((link) => link !== selected).map((link) => link.getBoundingClientRect());
8411
+ }
8412
+ const navContainer = selected.closest("[data-ohw-nav-container], [data-ohw-nav-drawer]");
8413
+ if (navContainer) {
8414
+ return Array.from(navContainer.querySelectorAll("[data-ohw-href-key]")).filter((el) => isNavigationItem(el) && el !== selected).map((el) => el.getBoundingClientRect());
8415
+ }
8416
+ return listNavigationItems().filter((el) => el !== selected).map((el) => el.getBoundingClientRect());
8417
+ }
7053
8418
  function placeCaretAtPoint(el, x, y) {
7054
8419
  const selection = window.getSelection();
7055
8420
  if (!selection) return;
7056
- if (typeof document.caretRangeFromPoint === "function") {
7057
- const range = document.caretRangeFromPoint(x, y);
7058
- if (range && el.contains(range.startContainer)) {
7059
- selection.removeAllRanges();
7060
- selection.addRange(range);
7061
- return;
8421
+ const overlays = Array.from(
8422
+ document.querySelectorAll(
8423
+ "[data-ohw-item-drag-surface], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-ohw-item-toolbar-anchor]"
8424
+ )
8425
+ );
8426
+ const prevPointerEvents = overlays.map((node) => node.style.pointerEvents);
8427
+ for (const node of overlays) node.style.pointerEvents = "none";
8428
+ try {
8429
+ if (typeof document.caretRangeFromPoint === "function") {
8430
+ const range = document.caretRangeFromPoint(x, y);
8431
+ if (range && el.contains(range.startContainer)) {
8432
+ selection.removeAllRanges();
8433
+ selection.addRange(range);
8434
+ return;
8435
+ }
7062
8436
  }
7063
- }
7064
- const caretPositionFromPoint = document.caretPositionFromPoint;
7065
- if (typeof caretPositionFromPoint === "function") {
7066
- const position = caretPositionFromPoint(x, y);
7067
- if (position && el.contains(position.offsetNode)) {
7068
- const range = document.createRange();
7069
- range.setStart(position.offsetNode, position.offset);
7070
- range.collapse(true);
7071
- selection.removeAllRanges();
7072
- selection.addRange(range);
8437
+ const caretPositionFromPoint = document.caretPositionFromPoint;
8438
+ if (typeof caretPositionFromPoint === "function") {
8439
+ const position = caretPositionFromPoint(x, y);
8440
+ if (position && el.contains(position.offsetNode)) {
8441
+ const range = document.createRange();
8442
+ range.setStart(position.offsetNode, position.offset);
8443
+ range.collapse(true);
8444
+ selection.removeAllRanges();
8445
+ selection.addRange(range);
8446
+ }
7073
8447
  }
8448
+ } finally {
8449
+ overlays.forEach((node, i) => {
8450
+ node.style.pointerEvents = prevPointerEvents[i] ?? "";
8451
+ });
7074
8452
  }
7075
8453
  }
7076
8454
  function selectAllTextInEditable(el) {
@@ -7212,6 +8590,9 @@ var ICONS = {
7212
8590
  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"/>',
7213
8591
  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"/>'
7214
8592
  };
8593
+ var SELECTION_CHROME_GAP2 = 4;
8594
+ var TOOLBAR_STROKE_GAP2 = 4;
8595
+ var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
7215
8596
  var TOOLBAR_GROUPS = [
7216
8597
  [
7217
8598
  { cmd: "bold", title: "Bold" },
@@ -7233,10 +8614,11 @@ function EditGlowChrome({
7233
8614
  rect,
7234
8615
  elRef,
7235
8616
  reorderHrefKey,
7236
- dragDisabled = false
8617
+ dragDisabled = false,
8618
+ hideHandle = false
7237
8619
  }) {
7238
- const GAP = 6;
7239
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
8620
+ const GAP = SELECTION_CHROME_GAP2;
8621
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
7240
8622
  "div",
7241
8623
  {
7242
8624
  ref: elRef,
@@ -7251,7 +8633,7 @@ function EditGlowChrome({
7251
8633
  zIndex: 2147483646
7252
8634
  },
7253
8635
  children: [
7254
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8636
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
7255
8637
  "div",
7256
8638
  {
7257
8639
  style: {
@@ -7259,12 +8641,12 @@ function EditGlowChrome({
7259
8641
  inset: 0,
7260
8642
  border: "2px solid var(--ohw-primary, #0885FE)",
7261
8643
  borderRadius: 8,
7262
- boxShadow: "0 0 0 4px rgba(8, 133, 254, 0.12)",
8644
+ boxShadow: "0 0 0 4px color-mix(in srgb, var(--ohw-primary, #0885FE) 12%, transparent)",
7263
8645
  pointerEvents: "none"
7264
8646
  }
7265
8647
  }
7266
8648
  ),
7267
- reorderHrefKey && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8649
+ reorderHrefKey && !hideHandle && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
7268
8650
  "div",
7269
8651
  {
7270
8652
  "data-ohw-drag-handle-container": "",
@@ -7276,7 +8658,7 @@ function EditGlowChrome({
7276
8658
  transform: "translate(calc(-100% - 7px), -50%)",
7277
8659
  pointerEvents: dragDisabled ? "none" : "auto"
7278
8660
  },
7279
- children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8661
+ children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
7280
8662
  DragHandle,
7281
8663
  {
7282
8664
  "aria-label": `Reorder ${reorderHrefKey}`,
@@ -7307,6 +8689,7 @@ function applyVisibleViewport(el, parentScroll) {
7307
8689
  el.style.top = `${top}px`;
7308
8690
  el.style.height = `${height}px`;
7309
8691
  }
8692
+ var TOOLBAR_EDGE_MARGIN2 = 4;
7310
8693
  function clampToolbarToClip(top, transform, rect, clip, approxH, gap) {
7311
8694
  const isAbove = transform.includes("translateY(-100%)");
7312
8695
  let visualTop = isAbove ? top - approxH : top;
@@ -7330,10 +8713,16 @@ function clampToolbarToClip(top, transform, rect, clip, approxH, gap) {
7330
8713
  const pinnedTop = Math.min(maxBottom - approxH, Math.max(minTop, clip.top + gap));
7331
8714
  return { top: pinnedTop + approxH, transform: "translateX(-50%) translateY(-100%)" };
7332
8715
  }
7333
- function calcToolbarPos(rect, parentScroll, approxW = 306) {
7334
- const GAP = 8;
8716
+ function clampToolbarLeft(centerX, toolbarWidth) {
8717
+ const half = toolbarWidth / 2;
8718
+ return Math.max(
8719
+ TOOLBAR_EDGE_MARGIN2 + half,
8720
+ Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN2 - half)
8721
+ );
8722
+ }
8723
+ function calcToolbarPos(rect, parentScroll, toolbarW = 306) {
8724
+ const GAP = TOOLBAR_OFFSET_FROM_ELEMENT;
7335
8725
  const APPROX_H = 32;
7336
- const APPROX_W = approxW;
7337
8726
  const clip = getIframeVisibleClip(parentScroll);
7338
8727
  const clipTop = clip?.top ?? 0;
7339
8728
  const canvasTopInIframe = parentScroll != null ? parentScroll.headerH - parentScroll.iframeOffsetTop : 0;
@@ -7363,7 +8752,7 @@ function calcToolbarPos(rect, parentScroll, approxW = 306) {
7363
8752
  transform = clamped.transform;
7364
8753
  }
7365
8754
  const rawLeft = rect.left + rect.width / 2;
7366
- const left = Math.max(GAP + APPROX_W / 2, Math.min(rawLeft, window.innerWidth - GAP - APPROX_W / 2));
8755
+ const left = clampToolbarLeft(rawLeft, toolbarW);
7367
8756
  return { top, left, transform };
7368
8757
  }
7369
8758
  function resolveItemInteractionState(rect, parentScroll) {
@@ -7379,11 +8768,37 @@ function FloatingToolbar({
7379
8768
  showEditLink,
7380
8769
  onEditLink
7381
8770
  }) {
7382
- const { top, left, transform } = calcToolbarPos(rect, parentScroll, 330);
7383
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8771
+ const localRef = import_react9.default.useRef(null);
8772
+ const [measuredW, setMeasuredW] = import_react9.default.useState(330);
8773
+ const setRefs = import_react9.default.useCallback(
8774
+ (node) => {
8775
+ localRef.current = node;
8776
+ if (typeof elRef === "function") elRef(node);
8777
+ else if (elRef) elRef.current = node;
8778
+ if (node) {
8779
+ const w = node.offsetWidth;
8780
+ if (w > 0) setMeasuredW(w);
8781
+ }
8782
+ },
8783
+ [elRef]
8784
+ );
8785
+ import_react9.default.useLayoutEffect(() => {
8786
+ const node = localRef.current;
8787
+ if (!node) return;
8788
+ const update = () => {
8789
+ const w = node.offsetWidth;
8790
+ if (w > 0) setMeasuredW(w);
8791
+ };
8792
+ update();
8793
+ const ro = new ResizeObserver(update);
8794
+ ro.observe(node);
8795
+ return () => ro.disconnect();
8796
+ }, [showEditLink, activeCommands]);
8797
+ const { top, left, transform } = calcToolbarPos(rect, parentScroll, measuredW);
8798
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
7384
8799
  "div",
7385
8800
  {
7386
- ref: elRef,
8801
+ ref: setRefs,
7387
8802
  style: {
7388
8803
  position: "fixed",
7389
8804
  top,
@@ -7392,12 +8807,12 @@ function FloatingToolbar({
7392
8807
  zIndex: 2147483647,
7393
8808
  pointerEvents: "auto"
7394
8809
  },
7395
- children: /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(CustomToolbar, { children: [
7396
- TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_react8.default.Fragment, { children: [
7397
- gi > 0 && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(CustomToolbarDivider, {}),
8810
+ children: /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(CustomToolbar, { children: [
8811
+ TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_react9.default.Fragment, { children: [
8812
+ gi > 0 && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(CustomToolbarDivider, {}),
7398
8813
  btns.map((btn) => {
7399
8814
  const isActive = activeCommands.has(btn.cmd);
7400
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8815
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
7401
8816
  CustomToolbarButton,
7402
8817
  {
7403
8818
  title: btn.title,
@@ -7406,7 +8821,7 @@ function FloatingToolbar({
7406
8821
  e.preventDefault();
7407
8822
  onCommand(btn.cmd);
7408
8823
  },
7409
- children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8824
+ children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
7410
8825
  "svg",
7411
8826
  {
7412
8827
  width: "16",
@@ -7427,7 +8842,7 @@ function FloatingToolbar({
7427
8842
  );
7428
8843
  })
7429
8844
  ] }, gi)),
7430
- showEditLink ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8845
+ showEditLink ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
7431
8846
  CustomToolbarButton,
7432
8847
  {
7433
8848
  type: "button",
@@ -7441,7 +8856,7 @@ function FloatingToolbar({
7441
8856
  e.preventDefault();
7442
8857
  e.stopPropagation();
7443
8858
  },
7444
- children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_lucide_react11.Link, { className: "size-4 shrink-0", "aria-hidden": true })
8859
+ children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_lucide_react11.Link, { className: "size-4 shrink-0", "aria-hidden": true })
7445
8860
  }
7446
8861
  ) : null
7447
8862
  ] })
@@ -7458,7 +8873,7 @@ function StateToggle({
7458
8873
  states,
7459
8874
  onStateChange
7460
8875
  }) {
7461
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
8876
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
7462
8877
  ToggleGroup,
7463
8878
  {
7464
8879
  "data-ohw-state-toggle": "",
@@ -7472,7 +8887,7 @@ function StateToggle({
7472
8887
  left: rect.right - 8,
7473
8888
  transform: "translateX(-100%)"
7474
8889
  },
7475
- children: states.map((state) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
8890
+ children: states.map((state) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
7476
8891
  }
7477
8892
  );
7478
8893
  }
@@ -7499,8 +8914,8 @@ function OhhwellsBridge() {
7499
8914
  const router = (0, import_navigation2.useRouter)();
7500
8915
  const searchParams = (0, import_navigation2.useSearchParams)();
7501
8916
  const isEditMode = isEditSessionActive();
7502
- const [bridgeRoot, setBridgeRoot] = (0, import_react8.useState)(null);
7503
- (0, import_react8.useEffect)(() => {
8917
+ const [bridgeRoot, setBridgeRoot] = (0, import_react9.useState)(null);
8918
+ (0, import_react9.useEffect)(() => {
7504
8919
  const figtreeFontId = "ohw-figtree-font";
7505
8920
  if (!document.getElementById(figtreeFontId)) {
7506
8921
  const preconnect1 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.googleapis.com" });
@@ -7528,91 +8943,137 @@ function OhhwellsBridge() {
7528
8943
  const subdomainFromQuery = searchParams.get("subdomain");
7529
8944
  const subdomain = resolveSubdomain(subdomainFromQuery);
7530
8945
  useLinkHrefGuardian(pathname, subdomain, isEditMode);
7531
- const postToParent2 = (0, import_react8.useCallback)((data) => {
8946
+ const postToParent2 = (0, import_react9.useCallback)((data) => {
7532
8947
  if (typeof window !== "undefined" && window.parent !== window) {
7533
8948
  window.parent.postMessage(data, "*");
7534
8949
  }
7535
8950
  }, []);
7536
- const [fetchState, setFetchState] = (0, import_react8.useState)("idle");
7537
- const autoSaveTimers = (0, import_react8.useRef)(/* @__PURE__ */ new Map());
7538
- const activeElRef = (0, import_react8.useRef)(null);
7539
- const selectedElRef = (0, import_react8.useRef)(null);
7540
- const originalContentRef = (0, import_react8.useRef)(null);
7541
- const activeStateElRef = (0, import_react8.useRef)(null);
7542
- const parentScrollRef = (0, import_react8.useRef)(null);
7543
- const visibleViewportRef = (0, import_react8.useRef)(null);
7544
- const [dialogPortalContainer, setDialogPortalContainer] = (0, import_react8.useState)(null);
7545
- const attachVisibleViewport = (0, import_react8.useCallback)((node) => {
8951
+ const [fetchState, setFetchState] = (0, import_react9.useState)("idle");
8952
+ const autoSaveTimers = (0, import_react9.useRef)(/* @__PURE__ */ new Map());
8953
+ const activeElRef = (0, import_react9.useRef)(null);
8954
+ const pointerHeldRef = (0, import_react9.useRef)(false);
8955
+ const selectedElRef = (0, import_react9.useRef)(null);
8956
+ const selectedHrefKeyRef = (0, import_react9.useRef)(null);
8957
+ const selectedFooterColAttrRef = (0, import_react9.useRef)(null);
8958
+ const originalContentRef = (0, import_react9.useRef)(null);
8959
+ const activeStateElRef = (0, import_react9.useRef)(null);
8960
+ const parentScrollRef = (0, import_react9.useRef)(null);
8961
+ const visibleViewportRef = (0, import_react9.useRef)(null);
8962
+ const [dialogPortalContainer, setDialogPortalContainer] = (0, import_react9.useState)(null);
8963
+ const attachVisibleViewport = (0, import_react9.useCallback)((node) => {
7546
8964
  visibleViewportRef.current = node;
7547
8965
  setDialogPortalContainer(node);
7548
8966
  if (node) applyVisibleViewport(node, parentScrollRef.current);
7549
8967
  }, []);
7550
- const toolbarElRef = (0, import_react8.useRef)(null);
7551
- const glowElRef = (0, import_react8.useRef)(null);
7552
- const hoveredImageRef = (0, import_react8.useRef)(null);
7553
- const hoveredImageHasTextOverlapRef = (0, import_react8.useRef)(false);
7554
- const dragOverElRef = (0, import_react8.useRef)(null);
7555
- const [mediaHover, setMediaHover] = (0, import_react8.useState)(null);
7556
- const [uploadingRects, setUploadingRects] = (0, import_react8.useState)({});
7557
- const hoveredGapRef = (0, import_react8.useRef)(null);
7558
- const imageUnhoverTimerRef = (0, import_react8.useRef)(null);
7559
- const imageShowTimerRef = (0, import_react8.useRef)(null);
7560
- const editStylesRef = (0, import_react8.useRef)(null);
7561
- const activateRef = (0, import_react8.useRef)(() => {
8968
+ const toolbarElRef = (0, import_react9.useRef)(null);
8969
+ const glowElRef = (0, import_react9.useRef)(null);
8970
+ const hoveredImageRef = (0, import_react9.useRef)(null);
8971
+ const hoveredImageHasTextOverlapRef = (0, import_react9.useRef)(false);
8972
+ const dragOverElRef = (0, import_react9.useRef)(null);
8973
+ const [mediaHover, setMediaHover] = (0, import_react9.useState)(null);
8974
+ const [uploadingRects, setUploadingRects] = (0, import_react9.useState)({});
8975
+ const hoveredGapRef = (0, import_react9.useRef)(null);
8976
+ const imageUnhoverTimerRef = (0, import_react9.useRef)(null);
8977
+ const imageShowTimerRef = (0, import_react9.useRef)(null);
8978
+ const editStylesRef = (0, import_react9.useRef)(null);
8979
+ const activateRef = (0, import_react9.useRef)(() => {
7562
8980
  });
7563
- const deactivateRef = (0, import_react8.useRef)(() => {
8981
+ const deactivateRef = (0, import_react9.useRef)(() => {
7564
8982
  });
7565
- const selectRef = (0, import_react8.useRef)(() => {
8983
+ const selectRef = (0, import_react9.useRef)(() => {
7566
8984
  });
7567
- const selectFrameRef = (0, import_react8.useRef)(() => {
8985
+ const selectFrameRef = (0, import_react9.useRef)(() => {
7568
8986
  });
7569
- const deselectRef = (0, import_react8.useRef)(() => {
8987
+ const deselectRef = (0, import_react9.useRef)(() => {
7570
8988
  });
7571
- const reselectNavigationItemRef = (0, import_react8.useRef)(() => {
8989
+ const reselectNavigationItemRef = (0, import_react9.useRef)(() => {
7572
8990
  });
7573
- const commitNavigationTextEditRef = (0, import_react8.useRef)(() => {
8991
+ const commitNavigationTextEditRef = (0, import_react9.useRef)(() => {
7574
8992
  });
7575
- const refreshActiveCommandsRef = (0, import_react8.useRef)(() => {
8993
+ const refreshActiveCommandsRef = (0, import_react9.useRef)(() => {
7576
8994
  });
7577
- const postToParentRef = (0, import_react8.useRef)(postToParent2);
8995
+ const postToParentRef = (0, import_react9.useRef)(postToParent2);
7578
8996
  postToParentRef.current = postToParent2;
7579
- const sectionsLoadedRef = (0, import_react8.useRef)(false);
7580
- const pendingScheduleConfigRequests = (0, import_react8.useRef)([]);
7581
- const [toolbarRect, setToolbarRect] = (0, import_react8.useState)(null);
7582
- const [toolbarVariant, setToolbarVariant] = (0, import_react8.useState)("none");
7583
- const toolbarVariantRef = (0, import_react8.useRef)("none");
8997
+ const sectionsLoadedRef = (0, import_react9.useRef)(false);
8998
+ const pendingScheduleConfigRequests = (0, import_react9.useRef)([]);
8999
+ const [toolbarRect, setToolbarRect] = (0, import_react9.useState)(null);
9000
+ const [toolbarVariant, setToolbarVariant] = (0, import_react9.useState)("none");
9001
+ const toolbarVariantRef = (0, import_react9.useRef)("none");
7584
9002
  toolbarVariantRef.current = toolbarVariant;
7585
- const [reorderHrefKey, setReorderHrefKey] = (0, import_react8.useState)(null);
7586
- const [reorderDragDisabled, setReorderDragDisabled] = (0, import_react8.useState)(false);
7587
- const [toggleState, setToggleState] = (0, import_react8.useState)(null);
7588
- const [maxBadge, setMaxBadge] = (0, import_react8.useState)(null);
7589
- const [activeCommands, setActiveCommands] = (0, import_react8.useState)(/* @__PURE__ */ new Set());
7590
- const [sectionGap, setSectionGap] = (0, import_react8.useState)(null);
7591
- const [toolbarShowEditLink, setToolbarShowEditLink] = (0, import_react8.useState)(false);
7592
- const hoveredNavContainerRef = (0, import_react8.useRef)(null);
7593
- const [hoveredNavContainerRect, setHoveredNavContainerRect] = (0, import_react8.useState)(null);
7594
- const hoveredItemElRef = (0, import_react8.useRef)(null);
7595
- const [hoveredItemRect, setHoveredItemRect] = (0, import_react8.useState)(null);
7596
- const siblingHintElRef = (0, import_react8.useRef)(null);
7597
- const [siblingHintRect, setSiblingHintRect] = (0, import_react8.useState)(null);
7598
- const [isItemDragging, setIsItemDragging] = (0, import_react8.useState)(false);
7599
- const [linkPopover, setLinkPopover] = (0, import_react8.useState)(null);
7600
- const linkPopoverSessionRef = (0, import_react8.useRef)(null);
7601
- const addNavAfterAnchorRef = (0, import_react8.useRef)(null);
7602
- const editContentRef = (0, import_react8.useRef)({});
7603
- const [sitePages, setSitePages] = (0, import_react8.useState)([]);
7604
- const [sectionsByPath, setSectionsByPath] = (0, import_react8.useState)({});
7605
- const sectionsPrefetchGenRef = (0, import_react8.useRef)(0);
7606
- const setLinkPopoverRef = (0, import_react8.useRef)(setLinkPopover);
7607
- const linkPopoverPanelRef = (0, import_react8.useRef)(null);
7608
- const linkPopoverOpenRef = (0, import_react8.useRef)(false);
7609
- const linkPopoverGraceUntilRef = (0, import_react8.useRef)(0);
9003
+ const [selectedIsCta, setSelectedIsCta] = (0, import_react9.useState)(false);
9004
+ const [reorderHrefKey, setReorderHrefKey] = (0, import_react9.useState)(null);
9005
+ const [reorderDragDisabled, setReorderDragDisabled] = (0, import_react9.useState)(false);
9006
+ const [toggleState, setToggleState] = (0, import_react9.useState)(null);
9007
+ const [maxBadge, setMaxBadge] = (0, import_react9.useState)(null);
9008
+ const [activeCommands, setActiveCommands] = (0, import_react9.useState)(/* @__PURE__ */ new Set());
9009
+ const [sectionGap, setSectionGap] = (0, import_react9.useState)(null);
9010
+ const [toolbarShowEditLink, setToolbarShowEditLink] = (0, import_react9.useState)(false);
9011
+ const hoveredNavContainerRef = (0, import_react9.useRef)(null);
9012
+ const [hoveredNavContainerRect, setHoveredNavContainerRect] = (0, import_react9.useState)(null);
9013
+ const hoveredItemElRef = (0, import_react9.useRef)(null);
9014
+ const [hoveredItemRect, setHoveredItemRect] = (0, import_react9.useState)(null);
9015
+ const siblingHintElRef = (0, import_react9.useRef)(null);
9016
+ const [siblingHintRect, setSiblingHintRect] = (0, import_react9.useState)(null);
9017
+ const [siblingHintRects, setSiblingHintRects] = (0, import_react9.useState)([]);
9018
+ const [isItemDragging, setIsItemDragging] = (0, import_react9.useState)(false);
9019
+ const [isFooterFrameSelection, setIsFooterFrameSelection] = (0, import_react9.useState)(false);
9020
+ const footerDragRef = (0, import_react9.useRef)(null);
9021
+ const [footerDropSlots, setFooterDropSlots] = (0, import_react9.useState)([]);
9022
+ const [activeFooterDropIndex, setActiveFooterDropIndex] = (0, import_react9.useState)(null);
9023
+ const [draggedItemRect, setDraggedItemRect] = (0, import_react9.useState)(null);
9024
+ const footerPointerDragRef = (0, import_react9.useRef)(null);
9025
+ const suppressNextClickRef = (0, import_react9.useRef)(false);
9026
+ const suppressClickUntilRef = (0, import_react9.useRef)(0);
9027
+ const [linkPopover, setLinkPopover] = (0, import_react9.useState)(null);
9028
+ const linkPopoverSessionRef = (0, import_react9.useRef)(null);
9029
+ const addNavAfterAnchorRef = (0, import_react9.useRef)(null);
9030
+ const editContentRef = (0, import_react9.useRef)({});
9031
+ const [sitePages, setSitePages] = (0, import_react9.useState)([]);
9032
+ const [sectionsByPath, setSectionsByPath] = (0, import_react9.useState)({});
9033
+ const sectionsPrefetchGenRef = (0, import_react9.useRef)(0);
9034
+ const setLinkPopoverRef = (0, import_react9.useRef)(setLinkPopover);
9035
+ const linkPopoverPanelRef = (0, import_react9.useRef)(null);
9036
+ const linkPopoverOpenRef = (0, import_react9.useRef)(false);
9037
+ const linkPopoverGraceUntilRef = (0, import_react9.useRef)(0);
7610
9038
  setLinkPopoverRef.current = setLinkPopover;
7611
9039
  linkPopoverSessionRef.current = linkPopover;
9040
+ const {
9041
+ navDragRef,
9042
+ navDropSlots,
9043
+ activeNavDropIndex,
9044
+ startNavLinkDrag,
9045
+ commitNavDrag,
9046
+ onNavDragOver,
9047
+ armNavPressFromChrome,
9048
+ refreshNavDragVisualsRef
9049
+ } = useNavItemDrag({
9050
+ isEditMode,
9051
+ editContentRef,
9052
+ selectedElRef,
9053
+ activeElRef,
9054
+ footerDragRef,
9055
+ suppressNextClickRef,
9056
+ suppressClickUntilRef,
9057
+ siblingHintElRef,
9058
+ postToParentRef,
9059
+ deselectRef,
9060
+ selectRef,
9061
+ deactivateRef,
9062
+ setLinkPopover: () => setLinkPopover(null),
9063
+ linkPopoverOpenRef,
9064
+ setIsItemDragging,
9065
+ setDraggedItemRect,
9066
+ setSiblingHintRect,
9067
+ setSiblingHintRects,
9068
+ setToolbarRect,
9069
+ getNavigationItemAnchor,
9070
+ isDragHandleDisabled,
9071
+ isNavbarButton: isNavbarButton2
9072
+ });
7612
9073
  const bumpLinkPopoverGrace = () => {
7613
9074
  linkPopoverGraceUntilRef.current = Date.now() + 350;
7614
9075
  };
7615
- const runSectionsPrefetch = (0, import_react8.useCallback)((pages) => {
9076
+ const runSectionsPrefetch = (0, import_react9.useCallback)((pages) => {
7616
9077
  if (!isEditMode || shouldUseDevFixtures() || pages.length === 0) return;
7617
9078
  const gen = ++sectionsPrefetchGenRef.current;
7618
9079
  const paths = pages.map((p) => p.path);
@@ -7631,10 +9092,24 @@ function OhhwellsBridge() {
7631
9092
  );
7632
9093
  });
7633
9094
  }, [isEditMode, pathname]);
7634
- const runSectionsPrefetchRef = (0, import_react8.useRef)(runSectionsPrefetch);
9095
+ const runSectionsPrefetchRef = (0, import_react9.useRef)(runSectionsPrefetch);
7635
9096
  runSectionsPrefetchRef.current = runSectionsPrefetch;
7636
- (0, import_react8.useEffect)(() => {
7637
- if (!linkPopover) return;
9097
+ (0, import_react9.useEffect)(() => {
9098
+ if (!linkPopover) {
9099
+ document.documentElement.removeAttribute("data-ohw-link-popover-open");
9100
+ return;
9101
+ }
9102
+ document.documentElement.setAttribute("data-ohw-link-popover-open", "");
9103
+ hoveredItemElRef.current = null;
9104
+ setHoveredItemRect(null);
9105
+ hoveredNavContainerRef.current = null;
9106
+ setHoveredNavContainerRect(null);
9107
+ siblingHintElRef.current = null;
9108
+ setSiblingHintRect(null);
9109
+ setSiblingHintRects([]);
9110
+ if (selectedElRef.current || toolbarVariantRef.current !== "none") {
9111
+ deselectRef.current();
9112
+ }
7638
9113
  if (hoveredImageRef.current) {
7639
9114
  hoveredImageRef.current = null;
7640
9115
  hoveredImageHasTextOverlapRef.current = false;
@@ -7668,7 +9143,7 @@ function OhhwellsBridge() {
7668
9143
  document.removeEventListener("touchmove", preventBackgroundScroll, scrollOpts);
7669
9144
  };
7670
9145
  }, [linkPopover, postToParent2]);
7671
- (0, import_react8.useEffect)(() => {
9146
+ (0, import_react9.useEffect)(() => {
7672
9147
  if (!isEditMode) return;
7673
9148
  const useFixtures = shouldUseDevFixtures();
7674
9149
  if (useFixtures) {
@@ -7692,14 +9167,14 @@ function OhhwellsBridge() {
7692
9167
  if (!useFixtures) postToParent2({ type: "ow:request-site-pages" });
7693
9168
  return () => window.removeEventListener("message", onSitePages);
7694
9169
  }, [isEditMode, postToParent2]);
7695
- (0, import_react8.useEffect)(() => {
9170
+ (0, import_react9.useEffect)(() => {
7696
9171
  if (!isEditMode || shouldUseDevFixtures()) return;
7697
9172
  void loadAllSectionsManifest().then((manifest) => {
7698
9173
  if (Object.keys(manifest).length === 0) return;
7699
9174
  setSectionsByPath((prev) => ({ ...manifest, ...prev }));
7700
9175
  });
7701
9176
  }, [isEditMode]);
7702
- (0, import_react8.useEffect)(() => {
9177
+ (0, import_react9.useEffect)(() => {
7703
9178
  const update = () => {
7704
9179
  const el = activeElRef.current ?? selectedElRef.current;
7705
9180
  if (el) setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
@@ -7723,10 +9198,10 @@ function OhhwellsBridge() {
7723
9198
  vvp.removeEventListener("resize", update);
7724
9199
  };
7725
9200
  }, []);
7726
- const refreshStateRules = (0, import_react8.useCallback)(() => {
9201
+ const refreshStateRules = (0, import_react9.useCallback)(() => {
7727
9202
  editStylesRef.current?.forceHover && (editStylesRef.current.forceHover.textContent = collectStateRules());
7728
9203
  }, []);
7729
- const processConfigRequest = (0, import_react8.useCallback)((insertAfterVal) => {
9204
+ const processConfigRequest = (0, import_react9.useCallback)((insertAfterVal) => {
7730
9205
  const tracker = getSectionsTracker();
7731
9206
  let entries = [];
7732
9207
  try {
@@ -7749,7 +9224,7 @@ function OhhwellsBridge() {
7749
9224
  }
7750
9225
  window.postMessage({ type: "ow:schedule-config", insertAfter: insertAfterVal, scheduleId: null }, "*");
7751
9226
  }, [isEditMode]);
7752
- const deactivate = (0, import_react8.useCallback)(() => {
9227
+ const deactivate = (0, import_react9.useCallback)(() => {
7753
9228
  const el = activeElRef.current;
7754
9229
  if (!el) return;
7755
9230
  const key = el.dataset.ohwKey;
@@ -7768,6 +9243,7 @@ function OhhwellsBridge() {
7768
9243
  }
7769
9244
  }
7770
9245
  el.removeAttribute("contenteditable");
9246
+ el.removeAttribute("data-ohw-editing");
7771
9247
  activeElRef.current = null;
7772
9248
  setReorderHrefKey(null);
7773
9249
  setReorderDragDisabled(false);
@@ -7780,12 +9256,23 @@ function OhhwellsBridge() {
7780
9256
  setToolbarShowEditLink(false);
7781
9257
  postToParent2({ type: "ow:exit-edit" });
7782
9258
  }, [postToParent2]);
7783
- const deselect = (0, import_react8.useCallback)(() => {
9259
+ const clearSelectedAttr = (0, import_react9.useCallback)(() => {
9260
+ document.querySelectorAll("[data-ohw-selected]").forEach((el) => {
9261
+ el.removeAttribute("data-ohw-selected");
9262
+ });
9263
+ }, []);
9264
+ const deselect = (0, import_react9.useCallback)(() => {
9265
+ clearSelectedAttr();
7784
9266
  selectedElRef.current = null;
9267
+ selectedHrefKeyRef.current = null;
9268
+ selectedFooterColAttrRef.current = null;
9269
+ setSelectedIsCta(false);
7785
9270
  setReorderHrefKey(null);
7786
9271
  setReorderDragDisabled(false);
9272
+ setIsFooterFrameSelection(false);
7787
9273
  siblingHintElRef.current = null;
7788
9274
  setSiblingHintRect(null);
9275
+ setSiblingHintRects([]);
7789
9276
  setIsItemDragging(false);
7790
9277
  hoveredNavContainerRef.current = null;
7791
9278
  setHoveredNavContainerRect(null);
@@ -7793,18 +9280,70 @@ function OhhwellsBridge() {
7793
9280
  setToolbarRect(null);
7794
9281
  setToolbarVariant("none");
7795
9282
  }
9283
+ }, [clearSelectedAttr]);
9284
+ const markSelected = (0, import_react9.useCallback)((el) => {
9285
+ clearSelectedAttr();
9286
+ el.setAttribute("data-ohw-selected", "");
9287
+ }, [clearSelectedAttr]);
9288
+ const resolveHrefKeyElement = (0, import_react9.useCallback)((hrefKey) => {
9289
+ if (isFooterHrefKey(hrefKey)) {
9290
+ return document.querySelector(
9291
+ `footer [data-ohw-href-key="${CSS.escape(hrefKey)}"]`
9292
+ );
9293
+ }
9294
+ if (isNavbarHrefKey(hrefKey)) {
9295
+ const desktop = document.querySelector("[data-ohw-nav-container]");
9296
+ const drawer = document.querySelector("[data-ohw-nav-drawer]");
9297
+ 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)}"]`);
9298
+ }
9299
+ return document.querySelector(
9300
+ `[data-ohw-href-key="${CSS.escape(hrefKey)}"]`
9301
+ );
7796
9302
  }, []);
7797
- const reselectNavigationItem = (0, import_react8.useCallback)((navAnchor) => {
9303
+ const resyncSelectedNavigationItem = (0, import_react9.useCallback)(() => {
9304
+ const hrefKey = selectedHrefKeyRef.current;
9305
+ if (hrefKey) {
9306
+ const link = resolveHrefKeyElement(hrefKey);
9307
+ if (!link || !isNavigationItem(link)) return;
9308
+ selectedElRef.current = link;
9309
+ const { key, disabled } = getNavigationItemReorderState(link);
9310
+ setReorderHrefKey(key);
9311
+ setReorderDragDisabled(disabled);
9312
+ setIsFooterFrameSelection(false);
9313
+ setToolbarVariant("link-action");
9314
+ setToolbarRect(link.getBoundingClientRect());
9315
+ setSiblingHintRects(collectNavigationItemSiblingHintRects(link));
9316
+ return;
9317
+ }
9318
+ const colAttr = selectedFooterColAttrRef.current;
9319
+ if (colAttr != null) {
9320
+ const column = document.querySelector(`[data-ohw-footer-col="${CSS.escape(colAttr)}"]`) ?? listFooterColumns()[Number(colAttr)] ?? null;
9321
+ if (!column || !isNavigationContainer(column)) return;
9322
+ selectedElRef.current = column;
9323
+ setIsFooterFrameSelection(true);
9324
+ setToolbarVariant("select-frame");
9325
+ setToolbarRect(column.getBoundingClientRect());
9326
+ setSiblingHintRects(
9327
+ listFooterColumns().filter((c) => c !== column).map((c) => c.getBoundingClientRect())
9328
+ );
9329
+ }
9330
+ }, [resolveHrefKeyElement]);
9331
+ const reselectNavigationItem = (0, import_react9.useCallback)((navAnchor) => {
7798
9332
  selectedElRef.current = navAnchor;
9333
+ selectedHrefKeyRef.current = navAnchor.getAttribute("data-ohw-href-key");
9334
+ selectedFooterColAttrRef.current = null;
9335
+ markSelected(navAnchor);
9336
+ setSelectedIsCta(isNavbarButton2(navAnchor));
7799
9337
  const { key, disabled } = getNavigationItemReorderState(navAnchor);
7800
9338
  setReorderHrefKey(key);
7801
9339
  setReorderDragDisabled(disabled);
7802
9340
  setToolbarVariant("link-action");
7803
9341
  setToolbarRect(navAnchor.getBoundingClientRect());
9342
+ setSiblingHintRects(collectNavigationItemSiblingHintRects(navAnchor));
7804
9343
  setToolbarShowEditLink(false);
7805
9344
  setActiveCommands(/* @__PURE__ */ new Set());
7806
- }, []);
7807
- const commitNavigationTextEdit = (0, import_react8.useCallback)((navAnchor) => {
9345
+ }, [markSelected]);
9346
+ const commitNavigationTextEdit = (0, import_react9.useCallback)((navAnchor) => {
7808
9347
  const el = activeElRef.current;
7809
9348
  if (!el) return;
7810
9349
  const key = el.dataset.ohwKey;
@@ -7823,6 +9362,7 @@ function OhhwellsBridge() {
7823
9362
  }
7824
9363
  }
7825
9364
  el.removeAttribute("contenteditable");
9365
+ el.removeAttribute("data-ohw-editing");
7826
9366
  activeElRef.current = null;
7827
9367
  setMaxBadge(null);
7828
9368
  setActiveCommands(/* @__PURE__ */ new Set());
@@ -7830,7 +9370,7 @@ function OhhwellsBridge() {
7830
9370
  postToParent2({ type: "ow:exit-edit" });
7831
9371
  reselectNavigationItem(navAnchor);
7832
9372
  }, [postToParent2, reselectNavigationItem]);
7833
- const handleAddTopLevelNavItem = (0, import_react8.useCallback)(() => {
9373
+ const handleAddTopLevelNavItem = (0, import_react9.useCallback)(() => {
7834
9374
  const items = listNavbarItems();
7835
9375
  addNavAfterAnchorRef.current = items[items.length - 1] ?? null;
7836
9376
  deselectRef.current();
@@ -7842,20 +9382,321 @@ function OhhwellsBridge() {
7842
9382
  intent: "add-nav"
7843
9383
  });
7844
9384
  }, []);
7845
- const handleItemDragStart = (0, import_react8.useCallback)(() => {
7846
- siblingHintElRef.current = null;
7847
- setSiblingHintRect(null);
7848
- setIsItemDragging(true);
7849
- }, []);
7850
- const handleItemDragEnd = (0, import_react8.useCallback)(() => {
9385
+ const clearFooterDragVisuals = (0, import_react9.useCallback)(() => {
9386
+ footerDragRef.current = null;
9387
+ setSiblingHintRects([]);
9388
+ setFooterDropSlots([]);
9389
+ setActiveFooterDropIndex(null);
9390
+ setDraggedItemRect(null);
7851
9391
  setIsItemDragging(false);
9392
+ unlockFooterDragInteraction();
9393
+ }, []);
9394
+ const refreshFooterDragVisuals = (0, import_react9.useCallback)((session, activeSlot, clientX, clientY) => {
9395
+ const dragged = session.draggedEl;
9396
+ setDraggedItemRect(dragged.getBoundingClientRect());
9397
+ if (typeof clientX === "number" && typeof clientY === "number") {
9398
+ session.lastClientX = clientX;
9399
+ session.lastClientY = clientY;
9400
+ }
9401
+ session.activeSlot = activeSlot;
9402
+ if (session.kind === "link") {
9403
+ const columns2 = listFooterColumns();
9404
+ const focusCols = /* @__PURE__ */ new Set([session.sourceColumnIndex]);
9405
+ if (activeSlot) focusCols.add(activeSlot.columnIndex);
9406
+ const siblingRects = [];
9407
+ columns2.forEach((col, i) => {
9408
+ if (!focusCols.has(i)) return;
9409
+ for (const link of listFooterLinksInColumn(col)) {
9410
+ if (link === dragged) continue;
9411
+ siblingRects.push(link.getBoundingClientRect());
9412
+ }
9413
+ });
9414
+ setSiblingHintRects(siblingRects);
9415
+ const slots2 = [];
9416
+ columns2.forEach((col, i) => {
9417
+ slots2.push(...buildLinkDropSlots(col, i));
9418
+ });
9419
+ setFooterDropSlots(slots2);
9420
+ const activeIdx2 = activeSlot ? slots2.findIndex((s) => s.columnIndex === activeSlot.columnIndex && s.insertIndex === activeSlot.insertIndex) : -1;
9421
+ setActiveFooterDropIndex(activeIdx2 >= 0 ? activeIdx2 : null);
9422
+ return;
9423
+ }
9424
+ const columns = listFooterColumns();
9425
+ setSiblingHintRects(columns.filter((col) => col !== dragged).map((col) => col.getBoundingClientRect()));
9426
+ const slots = buildColumnDropSlots();
9427
+ setFooterDropSlots(slots);
9428
+ const activeIdx = activeSlot ? slots.findIndex((s) => s.insertIndex === activeSlot.insertIndex) : -1;
9429
+ setActiveFooterDropIndex(activeIdx >= 0 ? activeIdx : null);
9430
+ }, []);
9431
+ const refreshFooterDragVisualsRef = (0, import_react9.useRef)(refreshFooterDragVisuals);
9432
+ refreshFooterDragVisualsRef.current = refreshFooterDragVisuals;
9433
+ const commitFooterDragRef = (0, import_react9.useRef)(() => {
9434
+ });
9435
+ const beginFooterDragRef = (0, import_react9.useRef)(() => {
9436
+ });
9437
+ const beginFooterDrag = (0, import_react9.useCallback)(
9438
+ (session) => {
9439
+ const rect = session.draggedEl.getBoundingClientRect();
9440
+ session.lastClientX = session.lastClientX || rect.left + rect.width / 2;
9441
+ session.lastClientY = session.lastClientY || rect.top + rect.height / 2;
9442
+ session.activeSlot = session.activeSlot ?? null;
9443
+ footerDragRef.current = session;
9444
+ setIsItemDragging(true);
9445
+ lockFooterDuringDrag();
9446
+ siblingHintElRef.current = null;
9447
+ setSiblingHintRect(null);
9448
+ if (session.wasSelected && selectedElRef.current === session.draggedEl) {
9449
+ setToolbarRect(rect);
9450
+ }
9451
+ const initialSlot = session.kind === "link" && session.hrefKey ? hitTestLinkDropSlot(session.lastClientX, session.lastClientY, session.hrefKey) : hitTestColumnDropSlot(session.lastClientX, session.lastClientY);
9452
+ refreshFooterDragVisuals(session, initialSlot, session.lastClientX, session.lastClientY);
9453
+ },
9454
+ [refreshFooterDragVisuals]
9455
+ );
9456
+ beginFooterDragRef.current = beginFooterDrag;
9457
+ const commitFooterDrag = (0, import_react9.useCallback)(
9458
+ (clientX, clientY) => {
9459
+ const session = footerDragRef.current;
9460
+ if (!session) {
9461
+ clearFooterDragVisuals();
9462
+ return;
9463
+ }
9464
+ const x = typeof clientX === "number" && (clientX !== 0 || clientY !== 0) ? clientX : session.lastClientX;
9465
+ const y = typeof clientY === "number" && (clientX !== 0 || clientY !== 0) ? clientY : session.lastClientY;
9466
+ let nextOrder = null;
9467
+ const slot = session.activeSlot ?? (session.kind === "link" && session.hrefKey ? hitTestLinkDropSlot(x, y, session.hrefKey) : session.kind === "column" ? hitTestColumnDropSlot(x, y) : null);
9468
+ if (session.kind === "link" && session.hrefKey && slot) {
9469
+ nextOrder = planFooterLinkMove(session.hrefKey, slot.columnIndex, slot.insertIndex);
9470
+ } else if (session.kind === "column" && slot) {
9471
+ nextOrder = planFooterColumnMove(session.sourceColumnIndex, slot.insertIndex);
9472
+ }
9473
+ const wasSelected = session.wasSelected;
9474
+ const draggedEl = session.draggedEl;
9475
+ const hrefKey = session.hrefKey;
9476
+ let movedColumnIndex = null;
9477
+ if (session.kind === "column" && slot && nextOrder) {
9478
+ let adjusted = slot.insertIndex;
9479
+ if (session.sourceColumnIndex < slot.insertIndex) adjusted -= 1;
9480
+ movedColumnIndex = Math.max(0, Math.min(adjusted, nextOrder.length - 1));
9481
+ }
9482
+ const applySelectionAfterDrop = () => {
9483
+ if (!wasSelected) {
9484
+ deselectRef.current();
9485
+ return;
9486
+ }
9487
+ if (hrefKey) {
9488
+ selectedHrefKeyRef.current = hrefKey;
9489
+ selectedFooterColAttrRef.current = null;
9490
+ const link = resolveHrefKeyElement(hrefKey);
9491
+ if (link && isNavigationItem(link)) {
9492
+ selectRef.current(link);
9493
+ return;
9494
+ }
9495
+ }
9496
+ if (movedColumnIndex !== null && nextOrder) {
9497
+ const colKeys = nextOrder[movedColumnIndex] ?? [];
9498
+ let column = null;
9499
+ for (const key of colKeys) {
9500
+ const link = document.querySelector(
9501
+ `footer [data-ohw-href-key="${CSS.escape(key)}"]`
9502
+ );
9503
+ if (link) {
9504
+ column = findFooterColumnForLink(link);
9505
+ if (column) break;
9506
+ }
9507
+ }
9508
+ if (!column) column = listFooterColumns()[movedColumnIndex] ?? null;
9509
+ if (column && isNavigationContainer(column)) {
9510
+ selectFrameRef.current(column);
9511
+ return;
9512
+ }
9513
+ }
9514
+ if (document.body.contains(draggedEl)) {
9515
+ if (isNavigationItem(draggedEl)) {
9516
+ selectRef.current(draggedEl);
9517
+ return;
9518
+ }
9519
+ if (isNavigationContainer(draggedEl)) {
9520
+ selectFrameRef.current(draggedEl);
9521
+ return;
9522
+ }
9523
+ }
9524
+ deselectRef.current();
9525
+ };
9526
+ if (nextOrder) {
9527
+ const orderJson = JSON.stringify(nextOrder);
9528
+ editContentRef.current = {
9529
+ ...editContentRef.current,
9530
+ [FOOTER_ORDER_KEY]: orderJson
9531
+ };
9532
+ applyFooterOrder(nextOrder);
9533
+ document.querySelectorAll("footer [data-ohw-href-key]").forEach(disableNativeHrefDrag);
9534
+ postToParentRef.current({
9535
+ type: "ow:change",
9536
+ nodes: [{ key: FOOTER_ORDER_KEY, text: orderJson }]
9537
+ });
9538
+ applySelectionAfterDrop();
9539
+ clearFooterDragVisuals();
9540
+ requestAnimationFrame(() => {
9541
+ if (editContentRef.current[FOOTER_ORDER_KEY] === orderJson) {
9542
+ applyFooterOrder(nextOrder);
9543
+ document.querySelectorAll("footer [data-ohw-href-key]").forEach(disableNativeHrefDrag);
9544
+ }
9545
+ applySelectionAfterDrop();
9546
+ requestAnimationFrame(() => {
9547
+ if (editContentRef.current[FOOTER_ORDER_KEY] === orderJson) {
9548
+ applyFooterOrder(nextOrder);
9549
+ }
9550
+ resyncSelectedNavigationItem();
9551
+ });
9552
+ });
9553
+ return;
9554
+ }
9555
+ applySelectionAfterDrop();
9556
+ clearFooterDragVisuals();
9557
+ },
9558
+ [clearFooterDragVisuals, resolveHrefKeyElement, resyncSelectedNavigationItem]
9559
+ );
9560
+ commitFooterDragRef.current = commitFooterDrag;
9561
+ const startFooterLinkDrag = (0, import_react9.useCallback)(
9562
+ (anchor, clientX, clientY, wasSelected) => {
9563
+ const hrefKey = anchor.getAttribute("data-ohw-href-key");
9564
+ if (!hrefKey || !isFooterHrefKey(hrefKey)) return false;
9565
+ const column = findFooterColumnForLink(anchor);
9566
+ const columns = listFooterColumns();
9567
+ beginFooterDrag({
9568
+ kind: "link",
9569
+ hrefKey,
9570
+ columnEl: column,
9571
+ sourceColumnIndex: column ? columns.indexOf(column) : 0,
9572
+ wasSelected,
9573
+ draggedEl: anchor,
9574
+ lastClientX: clientX,
9575
+ lastClientY: clientY,
9576
+ activeSlot: null
9577
+ });
9578
+ return true;
9579
+ },
9580
+ [beginFooterDrag]
9581
+ );
9582
+ const startFooterColumnDrag = (0, import_react9.useCallback)(
9583
+ (columnEl, clientX, clientY, wasSelected) => {
9584
+ const columns = listFooterColumns();
9585
+ const idx = columns.indexOf(columnEl);
9586
+ if (idx < 0) return false;
9587
+ beginFooterDrag({
9588
+ kind: "column",
9589
+ hrefKey: null,
9590
+ columnEl,
9591
+ sourceColumnIndex: idx,
9592
+ wasSelected,
9593
+ draggedEl: columnEl,
9594
+ lastClientX: clientX,
9595
+ lastClientY: clientY,
9596
+ activeSlot: null
9597
+ });
9598
+ return true;
9599
+ },
9600
+ [beginFooterDrag]
9601
+ );
9602
+ const handleItemDragStart = (0, import_react9.useCallback)(
9603
+ (e) => {
9604
+ const selected = selectedElRef.current;
9605
+ if (!selected) {
9606
+ setIsItemDragging(true);
9607
+ return;
9608
+ }
9609
+ const clientX = e?.clientX ?? selected.getBoundingClientRect().left + 8;
9610
+ const clientY = e?.clientY ?? selected.getBoundingClientRect().top + 8;
9611
+ if (startFooterLinkDrag(selected, clientX, clientY, true)) return;
9612
+ if (selected.hasAttribute("data-ohw-footer-col") || isInferredFooterGroup(selected) && selected.closest("footer")) {
9613
+ if (startFooterColumnDrag(selected, clientX, clientY, true)) return;
9614
+ }
9615
+ if (startNavLinkDrag(selected, clientX, clientY, true)) return;
9616
+ siblingHintElRef.current = null;
9617
+ setSiblingHintRect(null);
9618
+ setIsItemDragging(true);
9619
+ },
9620
+ [startFooterColumnDrag, startFooterLinkDrag, startNavLinkDrag]
9621
+ );
9622
+ const handleItemDragEnd = (0, import_react9.useCallback)(
9623
+ (e) => {
9624
+ if (footerDragRef.current) {
9625
+ const x = e?.clientX;
9626
+ const y = e?.clientY;
9627
+ if (typeof x === "number" && typeof y === "number" && (x !== 0 || y !== 0)) {
9628
+ commitFooterDrag(x, y);
9629
+ } else {
9630
+ commitFooterDrag();
9631
+ }
9632
+ return;
9633
+ }
9634
+ if (navDragRef.current) {
9635
+ const x = e?.clientX;
9636
+ const y = e?.clientY;
9637
+ if (typeof x === "number" && typeof y === "number" && (x !== 0 || y !== 0)) {
9638
+ commitNavDrag(x, y);
9639
+ } else {
9640
+ commitNavDrag();
9641
+ }
9642
+ return;
9643
+ }
9644
+ setIsItemDragging(false);
9645
+ },
9646
+ [commitFooterDrag, commitNavDrag, navDragRef]
9647
+ );
9648
+ const handleItemChromePointerDown = (0, import_react9.useCallback)((e) => {
9649
+ if (e.button !== 0) return;
9650
+ const selected = selectedElRef.current;
9651
+ if (!selected) return;
9652
+ const hrefKey = selected.getAttribute("data-ohw-href-key");
9653
+ if (hrefKey && isFooterHrefKey(hrefKey)) {
9654
+ footerPointerDragRef.current = {
9655
+ el: selected,
9656
+ kind: "link",
9657
+ startX: e.clientX,
9658
+ startY: e.clientY,
9659
+ pointerId: e.pointerId,
9660
+ wasSelected: true,
9661
+ started: false
9662
+ };
9663
+ return;
9664
+ }
9665
+ if (selected.hasAttribute("data-ohw-footer-col") || isInferredFooterGroup(selected) && selected.closest("footer")) {
9666
+ footerPointerDragRef.current = {
9667
+ el: selected,
9668
+ kind: "column",
9669
+ startX: e.clientX,
9670
+ startY: e.clientY,
9671
+ pointerId: e.pointerId,
9672
+ wasSelected: true,
9673
+ started: false
9674
+ };
9675
+ return;
9676
+ }
9677
+ if (armNavPressFromChrome(selected, e.clientX, e.clientY, e.pointerId)) return;
9678
+ }, [armNavPressFromChrome]);
9679
+ const handleItemChromeClick = (0, import_react9.useCallback)((clientX, clientY) => {
9680
+ if (suppressNextClickRef.current || Date.now() < suppressClickUntilRef.current) {
9681
+ suppressNextClickRef.current = false;
9682
+ return;
9683
+ }
9684
+ const selected = selectedElRef.current;
9685
+ if (!selected || !isNavigationItem(selected)) return;
9686
+ const editable = selected.querySelector('[data-ohw-editable="text"]') ?? selected.querySelector("[data-ohw-editable]");
9687
+ if (!editable) return;
9688
+ activateRef.current(editable, { caretX: clientX, caretY: clientY });
7852
9689
  }, []);
7853
9690
  reselectNavigationItemRef.current = reselectNavigationItem;
7854
9691
  commitNavigationTextEditRef.current = commitNavigationTextEdit;
7855
- const select = (0, import_react8.useCallback)((anchor) => {
9692
+ const select = (0, import_react9.useCallback)((anchor) => {
7856
9693
  if (!isNavigationItem(anchor)) return;
7857
9694
  if (activeElRef.current) deactivate();
7858
9695
  selectedElRef.current = anchor;
9696
+ selectedHrefKeyRef.current = anchor.getAttribute("data-ohw-href-key");
9697
+ selectedFooterColAttrRef.current = null;
9698
+ markSelected(anchor);
9699
+ setSelectedIsCta(isNavbarButton2(anchor));
7859
9700
  clearHrefKeyHover(anchor);
7860
9701
  hoveredNavContainerRef.current = null;
7861
9702
  setHoveredNavContainerRect(null);
@@ -7863,19 +9704,26 @@ function OhhwellsBridge() {
7863
9704
  hoveredItemElRef.current = null;
7864
9705
  siblingHintElRef.current = null;
7865
9706
  setSiblingHintRect(null);
9707
+ setSiblingHintRects(collectNavigationItemSiblingHintRects(anchor));
7866
9708
  setIsItemDragging(false);
7867
9709
  const { key, disabled } = getNavigationItemReorderState(anchor);
7868
9710
  setReorderHrefKey(key);
7869
9711
  setReorderDragDisabled(disabled);
9712
+ setIsFooterFrameSelection(false);
7870
9713
  setToolbarVariant("link-action");
7871
9714
  setToolbarRect(anchor.getBoundingClientRect());
7872
9715
  setToolbarShowEditLink(false);
7873
9716
  setActiveCommands(/* @__PURE__ */ new Set());
7874
- }, [deactivate]);
7875
- const selectFrame = (0, import_react8.useCallback)((el) => {
9717
+ }, [deactivate, markSelected]);
9718
+ const selectFrame = (0, import_react9.useCallback)((el) => {
7876
9719
  if (!isNavigationContainer(el)) return;
7877
9720
  if (activeElRef.current) deactivate();
9721
+ const isFooterColumn = el.hasAttribute("data-ohw-footer-col") || Boolean(el.closest("footer") && isInferredFooterGroup(el));
7878
9722
  selectedElRef.current = el;
9723
+ selectedHrefKeyRef.current = null;
9724
+ selectedFooterColAttrRef.current = isFooterColumn ? el.getAttribute("data-ohw-footer-col") ?? String(listFooterColumns().indexOf(el)) : null;
9725
+ markSelected(el);
9726
+ setSelectedIsCta(false);
7879
9727
  clearHrefKeyHover(el);
7880
9728
  hoveredNavContainerRef.current = null;
7881
9729
  setHoveredNavContainerRect(null);
@@ -7883,17 +9731,25 @@ function OhhwellsBridge() {
7883
9731
  hoveredItemElRef.current = null;
7884
9732
  siblingHintElRef.current = null;
7885
9733
  setSiblingHintRect(null);
9734
+ setSiblingHintRects(
9735
+ isFooterColumn ? listFooterColumns().filter((column) => column !== el).map((column) => column.getBoundingClientRect()) : []
9736
+ );
7886
9737
  setIsItemDragging(false);
7887
9738
  setReorderHrefKey(null);
7888
9739
  setReorderDragDisabled(false);
9740
+ setIsFooterFrameSelection(isFooterColumn);
7889
9741
  setToolbarVariant("select-frame");
7890
9742
  setToolbarRect(el.getBoundingClientRect());
7891
9743
  setToolbarShowEditLink(false);
7892
9744
  setActiveCommands(/* @__PURE__ */ new Set());
7893
- }, [deactivate]);
7894
- const activate = (0, import_react8.useCallback)((el, options) => {
9745
+ }, [deactivate, markSelected]);
9746
+ const activate = (0, import_react9.useCallback)((el, options) => {
7895
9747
  if (activeElRef.current === el) return;
9748
+ clearSelectedAttr();
7896
9749
  selectedElRef.current = null;
9750
+ selectedHrefKeyRef.current = null;
9751
+ selectedFooterColAttrRef.current = null;
9752
+ setSelectedIsCta(false);
7897
9753
  deactivate();
7898
9754
  if (hoveredImageRef.current) {
7899
9755
  hoveredImageRef.current = null;
@@ -7903,14 +9759,34 @@ function OhhwellsBridge() {
7903
9759
  siblingHintElRef.current = null;
7904
9760
  setSiblingHintRect(null);
7905
9761
  setIsItemDragging(false);
9762
+ const preActivationSelection = window.getSelection();
9763
+ const preservedRange = preActivationSelection && preActivationSelection.rangeCount > 0 && !preActivationSelection.isCollapsed && el.contains(preActivationSelection.getRangeAt(0).commonAncestorContainer) ? preActivationSelection.getRangeAt(0).cloneRange() : null;
7906
9764
  el.setAttribute("contenteditable", "true");
9765
+ el.setAttribute("data-ohw-editing", "");
7907
9766
  el.removeAttribute("data-ohw-hovered");
7908
9767
  el.closest("[data-ohw-href-key]")?.removeAttribute("data-ohw-hovered");
7909
9768
  activeElRef.current = el;
7910
9769
  originalContentRef.current = el.innerHTML;
7911
- el.focus();
7912
- if (options?.caretX !== void 0 && options?.caretY !== void 0) {
7913
- placeCaretAtPoint(el, options.caretX, options.caretY);
9770
+ el.focus({ preventScroll: true });
9771
+ if (preservedRange) {
9772
+ const selection = window.getSelection();
9773
+ selection?.removeAllRanges();
9774
+ selection?.addRange(preservedRange);
9775
+ } else if (options?.caretX !== void 0 && options?.caretY !== void 0) {
9776
+ const { caretX, caretY } = options;
9777
+ placeCaretAtPoint(el, caretX, caretY);
9778
+ requestAnimationFrame(() => {
9779
+ if (activeElRef.current === el) placeCaretAtPoint(el, caretX, caretY);
9780
+ });
9781
+ } else {
9782
+ const selection = window.getSelection();
9783
+ if (selection && selection.rangeCount === 0) {
9784
+ const range = document.createRange();
9785
+ range.selectNodeContents(el);
9786
+ range.collapse(false);
9787
+ selection.removeAllRanges();
9788
+ selection.addRange(range);
9789
+ }
7914
9790
  }
7915
9791
  setToolbarShowEditLink(Boolean(getHrefKeyFromElement(el)));
7916
9792
  const navAnchor = getNavigationItemAnchor(el);
@@ -7925,13 +9801,13 @@ function OhhwellsBridge() {
7925
9801
  setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
7926
9802
  postToParent2({ type: "ow:enter-edit", key: el.dataset.ohwKey });
7927
9803
  requestAnimationFrame(() => refreshActiveCommandsRef.current());
7928
- }, [deactivate, postToParent2]);
9804
+ }, [clearSelectedAttr, deactivate, postToParent2]);
7929
9805
  activateRef.current = activate;
7930
9806
  deactivateRef.current = deactivate;
7931
9807
  selectRef.current = select;
7932
9808
  selectFrameRef.current = selectFrame;
7933
9809
  deselectRef.current = deselect;
7934
- (0, import_react8.useLayoutEffect)(() => {
9810
+ (0, import_react9.useLayoutEffect)(() => {
7935
9811
  if (!subdomain || isEditMode) {
7936
9812
  setFetchState("done");
7937
9813
  return;
@@ -7972,6 +9848,7 @@ function OhhwellsBridge() {
7972
9848
  applyLinkByKey(key, val);
7973
9849
  }
7974
9850
  reconcileNavbarItemsFromContent(content);
9851
+ reconcileFooterOrderFromContent(content);
7975
9852
  enforceLinkHrefs();
7976
9853
  initSectionsFromContent(content, true);
7977
9854
  sectionsLoadedRef.current = true;
@@ -8000,7 +9877,7 @@ function OhhwellsBridge() {
8000
9877
  cancelled = true;
8001
9878
  };
8002
9879
  }, [subdomain, isEditMode]);
8003
- (0, import_react8.useEffect)(() => {
9880
+ (0, import_react9.useEffect)(() => {
8004
9881
  if (!subdomain || isEditMode) return;
8005
9882
  let debounceTimer = null;
8006
9883
  let observer = null;
@@ -8032,6 +9909,7 @@ function OhhwellsBridge() {
8032
9909
  applyLinkByKey(key, val);
8033
9910
  }
8034
9911
  reconcileNavbarItemsFromContent(content);
9912
+ reconcileFooterOrderFromContent(content);
8035
9913
  } finally {
8036
9914
  observer?.observe(document.body, { childList: true, subtree: true });
8037
9915
  }
@@ -8049,16 +9927,16 @@ function OhhwellsBridge() {
8049
9927
  if (debounceTimer) clearTimeout(debounceTimer);
8050
9928
  };
8051
9929
  }, [subdomain, isEditMode, pathname]);
8052
- (0, import_react8.useLayoutEffect)(() => {
9930
+ (0, import_react9.useLayoutEffect)(() => {
8053
9931
  const el = document.getElementById("ohw-loader");
8054
9932
  if (!el) return;
8055
9933
  const visible = Boolean(subdomain) && fetchState !== "done";
8056
9934
  el.style.display = visible ? "flex" : "none";
8057
9935
  }, [subdomain, fetchState]);
8058
- (0, import_react8.useEffect)(() => {
9936
+ (0, import_react9.useEffect)(() => {
8059
9937
  postToParent2({ type: "ow:navigation", path: pathname });
8060
9938
  }, [pathname, postToParent2]);
8061
- (0, import_react8.useEffect)(() => {
9939
+ (0, import_react9.useEffect)(() => {
8062
9940
  if (!isEditMode) return;
8063
9941
  if (linkPopoverSessionRef.current?.intent === "add-nav") return;
8064
9942
  if (document.querySelector("[data-ohw-section-picker]")) return;
@@ -8066,23 +9944,72 @@ function OhhwellsBridge() {
8066
9944
  deselectRef.current();
8067
9945
  deactivateRef.current();
8068
9946
  }, [pathname, isEditMode]);
8069
- (0, import_react8.useEffect)(() => {
9947
+ (0, import_react9.useEffect)(() => {
8070
9948
  const contentForNav = () => {
8071
9949
  if (isEditMode) return editContentRef.current;
8072
9950
  if (!subdomain) return {};
8073
9951
  return contentCache.get(subdomain) ?? {};
8074
9952
  };
8075
- const run = () => reconcileNavbarItemsFromContent(contentForNav());
9953
+ const observeRoots = () => [document.querySelector("nav"), document.querySelector("footer")].filter(
9954
+ (el) => Boolean(el)
9955
+ );
9956
+ const roots = observeRoots();
9957
+ if (roots.length === 0) {
9958
+ reconcileNavbarItemsFromContent(contentForNav());
9959
+ reconcileFooterOrderFromContent(contentForNav());
9960
+ if (isEditMode) syncNavigationDragCursorAttrs();
9961
+ return;
9962
+ }
9963
+ let rafId = null;
9964
+ let applying = false;
9965
+ let observer = null;
9966
+ const attach = () => {
9967
+ for (const root of observeRoots()) {
9968
+ observer?.observe(root, { childList: true, subtree: true });
9969
+ }
9970
+ };
9971
+ const run = () => {
9972
+ if (footerDragRef.current || navDragRef.current || applying) return;
9973
+ applying = true;
9974
+ observer?.disconnect();
9975
+ try {
9976
+ const content = contentForNav();
9977
+ reconcileNavbarItemsFromContent(content);
9978
+ reconcileFooterOrderFromContent(content);
9979
+ document.querySelectorAll("footer [data-ohw-href-key]").forEach((el) => {
9980
+ if (isFooterHrefKey(el.getAttribute("data-ohw-href-key"))) {
9981
+ disableNativeHrefDrag(el);
9982
+ }
9983
+ });
9984
+ document.querySelectorAll(
9985
+ "nav [data-ohw-href-key], [data-ohw-nav-container] [data-ohw-href-key], [data-ohw-nav-drawer] [data-ohw-href-key]"
9986
+ ).forEach((el) => {
9987
+ if (isNavbarHrefKey(el.getAttribute("data-ohw-href-key"))) {
9988
+ disableNativeHrefDrag(el);
9989
+ }
9990
+ });
9991
+ resyncSelectedNavigationItem();
9992
+ if (isEditMode) syncNavigationDragCursorAttrs();
9993
+ } finally {
9994
+ applying = false;
9995
+ attach();
9996
+ }
9997
+ };
9998
+ const schedule = () => {
9999
+ if (applying || rafId != null) return;
10000
+ rafId = requestAnimationFrame(() => {
10001
+ rafId = null;
10002
+ run();
10003
+ });
10004
+ };
10005
+ observer = new MutationObserver(schedule);
8076
10006
  run();
8077
- const nav = document.querySelector("nav");
8078
- if (!nav) return;
8079
- const observer = new MutationObserver(() => {
8080
- requestAnimationFrame(run);
8081
- });
8082
- observer.observe(nav, { childList: true, subtree: true });
8083
- return () => observer.disconnect();
8084
- }, [isEditMode, pathname, subdomain, fetchState]);
8085
- (0, import_react8.useEffect)(() => {
10007
+ return () => {
10008
+ if (rafId != null) cancelAnimationFrame(rafId);
10009
+ observer?.disconnect();
10010
+ };
10011
+ }, [isEditMode, pathname, subdomain, fetchState, resyncSelectedNavigationItem]);
10012
+ (0, import_react9.useEffect)(() => {
8086
10013
  if (!isEditMode) return;
8087
10014
  const measure = () => {
8088
10015
  const h = document.body.scrollHeight;
@@ -8106,7 +10033,7 @@ function OhhwellsBridge() {
8106
10033
  window.removeEventListener("resize", handleResize);
8107
10034
  };
8108
10035
  }, [pathname, isEditMode, postToParent2]);
8109
- (0, import_react8.useEffect)(() => {
10036
+ (0, import_react9.useEffect)(() => {
8110
10037
  if (!subdomainFromQuery || isEditMode) return;
8111
10038
  const handleClick = (e) => {
8112
10039
  const anchor = e.target.closest("a");
@@ -8122,19 +10049,24 @@ function OhhwellsBridge() {
8122
10049
  document.addEventListener("click", handleClick, true);
8123
10050
  return () => document.removeEventListener("click", handleClick, true);
8124
10051
  }, [subdomainFromQuery, isEditMode, router]);
8125
- (0, import_react8.useEffect)(() => {
10052
+ (0, import_react9.useEffect)(() => {
8126
10053
  if (!isEditMode) {
8127
10054
  editStylesRef.current?.base.remove();
8128
10055
  editStylesRef.current?.forceHover.remove();
8129
10056
  editStylesRef.current?.stateViews.remove();
8130
10057
  editStylesRef.current = null;
10058
+ document.querySelectorAll("[data-ohw-can-drag]").forEach((el) => {
10059
+ el.removeAttribute("data-ohw-can-drag");
10060
+ });
8131
10061
  return;
8132
10062
  }
8133
- if (!editStylesRef.current) {
8134
- const initialVh = window.innerHeight;
8135
- const base = document.createElement("style");
8136
- base.setAttribute("data-ohw-edit-style", "");
8137
- base.textContent = `
10063
+ const existing = editStylesRef.current;
10064
+ let initialVh = window.innerHeight;
10065
+ if (existing?.base.textContent) {
10066
+ const match = existing.base.textContent.match(/\.min-h-screen[^{]*\{[^}]*min-height:\s*(\d+)px/);
10067
+ if (match) initialVh = parseInt(match[1], 10);
10068
+ }
10069
+ const baseCss = `
8138
10070
  html { height: auto !important; }
8139
10071
  body { height: auto !important; min-height: 0 !important; overflow: hidden !important; }
8140
10072
  .min-h-screen, .min-h-svh, .min-h-dvh { min-height: ${initialVh}px !important; }
@@ -8145,7 +10077,25 @@ function OhhwellsBridge() {
8145
10077
  [data-ohw-editable] {
8146
10078
  display: block;
8147
10079
  }
8148
- [data-ohw-editable]:not([contenteditable]):not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"]):not([data-ohw-editable="video"]) { cursor: text !important; }
10080
+ /* Body text (no item-action toolbar) \u2014 first click enters text edit \u2192 I-beam.
10081
+ Exclude select-first nav/footer labels so grab/default on [data-ohw-href-key] can win. */
10082
+ [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] *)) {
10083
+ cursor: text !important;
10084
+ }
10085
+ /* Select-first items: default arrow; grab only when drag-start is allowed (data-ohw-can-drag). */
10086
+ [data-ohw-href-key]:not([data-ohw-selected]),
10087
+ [data-ohw-href-key]:not([data-ohw-selected]) * {
10088
+ cursor: default !important;
10089
+ }
10090
+ [data-ohw-href-key][data-ohw-can-drag]:not([data-ohw-selected]),
10091
+ [data-ohw-href-key][data-ohw-can-drag]:not([data-ohw-selected]) * {
10092
+ cursor: grab !important;
10093
+ }
10094
+ /* Selected (action-toolbar) \u2014 next click enters text edit at caret. */
10095
+ [data-ohw-href-key][data-ohw-selected],
10096
+ [data-ohw-href-key][data-ohw-selected] * {
10097
+ cursor: text !important;
10098
+ }
8149
10099
  [data-ohw-editable="image"], [data-ohw-editable="image"] *,
8150
10100
  [data-ohw-editable="video"], [data-ohw-editable="video"] *,
8151
10101
  [data-ohw-editable="bg-image"], [data-ohw-editable="bg-image"] * { cursor: pointer !important; }
@@ -8161,9 +10111,32 @@ function OhhwellsBridge() {
8161
10111
  outline: none !important;
8162
10112
  outline-offset: 0 !important;
8163
10113
  }
8164
- [data-ohw-editable][contenteditable] {
10114
+ /* Editor chrome owns selection \u2014 suppress native focus rings on nav/footer items. */
10115
+ nav [data-ohw-href-key]:focus,
10116
+ nav [data-ohw-href-key]:focus-visible,
10117
+ nav [data-ohw-href-key]:focus-within,
10118
+ footer [data-ohw-href-key]:focus,
10119
+ footer [data-ohw-href-key]:focus-visible,
10120
+ footer [data-ohw-href-key]:focus-within,
10121
+ nav [data-ohw-href-key] [data-ohw-editable]:focus,
10122
+ nav [data-ohw-href-key] [data-ohw-editable]:focus-visible,
10123
+ footer [data-ohw-href-key] [data-ohw-editable]:focus,
10124
+ footer [data-ohw-href-key] [data-ohw-editable]:focus-visible {
10125
+ outline: none !important;
10126
+ outline-offset: 0 !important;
10127
+ box-shadow: none !important;
10128
+ }
10129
+ /* Text edit wins over grab/default (must beat [data-ohw-can-drag] *). */
10130
+ [data-ohw-editing],
10131
+ [data-ohw-editing] *,
10132
+ [data-ohw-editable][contenteditable],
10133
+ [data-ohw-editable][contenteditable] *,
10134
+ nav [data-ohw-href-key] [data-ohw-editable][contenteditable],
10135
+ nav [data-ohw-href-key] [data-ohw-editable][contenteditable] *,
10136
+ footer [data-ohw-href-key] [data-ohw-editable][contenteditable],
10137
+ footer [data-ohw-href-key] [data-ohw-editable][contenteditable] * {
8165
10138
  outline: none !important;
8166
- caret-color: ${PRIMARY2};
10139
+ caret-color: ${PRIMARY2} !important;
8167
10140
  cursor: text !important;
8168
10141
  }
8169
10142
  [data-ohw-editable][contenteditable]::selection,
@@ -8172,6 +10145,9 @@ function OhhwellsBridge() {
8172
10145
  [data-ohw-editable-state][data-ohw-active-state] [data-ohw-editable] { pointer-events: auto !important; }
8173
10146
  [data-ohw-editable-state][data-ohw-active-state][data-ohw-editable] { pointer-events: auto !important; }
8174
10147
  `;
10148
+ if (!existing) {
10149
+ const base = document.createElement("style");
10150
+ base.setAttribute("data-ohw-edit-style", "");
8175
10151
  const forceHover = document.createElement("style");
8176
10152
  forceHover.setAttribute("data-ohw-active-state-style", "");
8177
10153
  const stateViews = document.createElement("style");
@@ -8187,14 +10163,43 @@ function OhhwellsBridge() {
8187
10163
  document.head.appendChild(stateViews);
8188
10164
  editStylesRef.current = { base, forceHover, stateViews };
8189
10165
  }
10166
+ editStylesRef.current.base.textContent = baseCss;
10167
+ syncNavigationDragCursorAttrs();
8190
10168
  refreshStateRules();
8191
10169
  const handleClick = (e) => {
10170
+ if (suppressNextClickRef.current || Date.now() < suppressClickUntilRef.current) {
10171
+ suppressNextClickRef.current = false;
10172
+ e.preventDefault();
10173
+ e.stopPropagation();
10174
+ return;
10175
+ }
8192
10176
  const target = e.target;
8193
10177
  if (target.closest("[data-ohw-toolbar]")) return;
8194
10178
  if (target.closest("[data-ohw-state-toggle]")) return;
8195
10179
  if (target.closest("[data-ohw-max-badge]")) return;
8196
10180
  if (isInsideLinkEditor(target)) return;
8197
10181
  if (target.closest('[data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
10182
+ if (target.closest("[data-ohw-item-drag-surface]")) {
10183
+ e.preventDefault();
10184
+ e.stopPropagation();
10185
+ const selected = selectedElRef.current;
10186
+ if (selected && isNavigationItem(selected)) {
10187
+ const editable2 = selected.querySelector('[data-ohw-editable="text"]') ?? selected.querySelector("[data-ohw-editable]");
10188
+ if (editable2) {
10189
+ activateRef.current(editable2, {
10190
+ caretX: e.clientX,
10191
+ caretY: e.clientY
10192
+ });
10193
+ }
10194
+ } else if (selected?.hasAttribute("data-ohw-footer-col")) {
10195
+ const link = listFooterLinksInColumn(selected).find((el) => {
10196
+ const r2 = el.getBoundingClientRect();
10197
+ return e.clientX >= r2.left && e.clientX <= r2.right && e.clientY >= r2.top && e.clientY <= r2.bottom;
10198
+ });
10199
+ if (link) selectRef.current(link);
10200
+ }
10201
+ return;
10202
+ }
8198
10203
  const navFromChrome = resolveNavContainerSelectionTarget(target, e.clientX, e.clientY);
8199
10204
  if (navFromChrome) {
8200
10205
  e.preventDefault();
@@ -8202,10 +10207,45 @@ function OhhwellsBridge() {
8202
10207
  selectFrameRef.current(navFromChrome);
8203
10208
  return;
8204
10209
  }
10210
+ const footerFromChrome = resolveFooterColumnSelectionTarget(target, e.clientX, e.clientY);
10211
+ if (footerFromChrome) {
10212
+ e.preventDefault();
10213
+ e.stopPropagation();
10214
+ selectFrameRef.current(footerFromChrome);
10215
+ return;
10216
+ }
8205
10217
  e.preventDefault();
8206
10218
  e.stopPropagation();
8207
10219
  return;
8208
10220
  }
10221
+ if (activeElRef.current) {
10222
+ const active = activeElRef.current;
10223
+ const inActive = active === target || active.contains(target);
10224
+ const navAnchor = getNavigationItemAnchor(active);
10225
+ const inActiveNav = Boolean(
10226
+ navAnchor && (navAnchor === target || navAnchor.contains(target))
10227
+ );
10228
+ if (inActive || inActiveNav) {
10229
+ e.preventDefault();
10230
+ e.stopPropagation();
10231
+ const selection = window.getSelection();
10232
+ const hasRangeSelection = Boolean(selection && !selection.isCollapsed);
10233
+ console.log(
10234
+ "[OHW DEBUG handleClick] already-active branch",
10235
+ "detail=",
10236
+ e.detail,
10237
+ "hasRangeSelection=",
10238
+ hasRangeSelection,
10239
+ "willPlaceCaret=",
10240
+ e.detail < 2 && !hasRangeSelection
10241
+ );
10242
+ if (e.detail < 2 && !hasRangeSelection) {
10243
+ placeCaretAtPoint(active, e.clientX, e.clientY);
10244
+ refreshActiveCommandsRef.current();
10245
+ }
10246
+ return;
10247
+ }
10248
+ }
8209
10249
  const editable = target.closest("[data-ohw-editable]");
8210
10250
  if (editable) {
8211
10251
  if (editable.dataset.ohwEditable === "link") {
@@ -8242,6 +10282,15 @@ function OhhwellsBridge() {
8242
10282
  }
8243
10283
  e.preventDefault();
8244
10284
  e.stopPropagation();
10285
+ console.log(
10286
+ "[OHW DEBUG handleClick] first-activation branch",
10287
+ "detail=",
10288
+ e.detail,
10289
+ "selectionAtClick=",
10290
+ JSON.stringify(window.getSelection()?.toString() ?? ""),
10291
+ "collapsedAtClick=",
10292
+ window.getSelection()?.isCollapsed
10293
+ );
8245
10294
  activateRef.current(editable);
8246
10295
  return;
8247
10296
  }
@@ -8249,10 +10298,26 @@ function OhhwellsBridge() {
8249
10298
  if (hrefAnchor) {
8250
10299
  e.preventDefault();
8251
10300
  e.stopPropagation();
8252
- if (selectedElRef.current === hrefAnchor) return;
10301
+ if (selectedElRef.current === hrefAnchor) {
10302
+ const textEditable = hrefAnchor.querySelector('[data-ohw-editable="text"]') ?? hrefAnchor.querySelector("[data-ohw-editable]");
10303
+ if (textEditable) {
10304
+ activateRef.current(textEditable, {
10305
+ caretX: e.clientX,
10306
+ caretY: e.clientY
10307
+ });
10308
+ }
10309
+ return;
10310
+ }
8253
10311
  selectRef.current(hrefAnchor);
8254
10312
  return;
8255
10313
  }
10314
+ const footerColClick = target.closest("[data-ohw-footer-col]");
10315
+ if (footerColClick) {
10316
+ e.preventDefault();
10317
+ e.stopPropagation();
10318
+ selectFrameRef.current(footerColClick);
10319
+ return;
10320
+ }
8256
10321
  const navContainerToSelect = resolveNavContainerSelectionTarget(target, e.clientX, e.clientY);
8257
10322
  if (navContainerToSelect) {
8258
10323
  e.preventDefault();
@@ -8260,6 +10325,13 @@ function OhhwellsBridge() {
8260
10325
  selectFrameRef.current(navContainerToSelect);
8261
10326
  return;
8262
10327
  }
10328
+ const footerColumnToSelect = resolveFooterColumnSelectionTarget(target, e.clientX, e.clientY);
10329
+ if (footerColumnToSelect) {
10330
+ e.preventDefault();
10331
+ e.stopPropagation();
10332
+ selectFrameRef.current(footerColumnToSelect);
10333
+ return;
10334
+ }
8263
10335
  const selectedContainer = selectedElRef.current;
8264
10336
  if (selectedContainer && isNavigationContainer(selectedContainer)) {
8265
10337
  const navItem = getNavigationItemAnchor(target);
@@ -8319,6 +10391,13 @@ function OhhwellsBridge() {
8319
10391
  const handleMouseOver = (e) => {
8320
10392
  if (document.documentElement.hasAttribute("data-ohw-section-picking")) return;
8321
10393
  const target = e.target;
10394
+ if (linkPopoverOpenRef.current) {
10395
+ hoveredItemElRef.current = null;
10396
+ setHoveredItemRect(null);
10397
+ hoveredNavContainerRef.current = null;
10398
+ setHoveredNavContainerRect(null);
10399
+ return;
10400
+ }
8322
10401
  if (toolbarVariantRef.current !== "select-frame") {
8323
10402
  const navContainer = target.closest("[data-ohw-nav-container]");
8324
10403
  if (navContainer && !getNavigationItemAnchor(target)) {
@@ -8338,12 +10417,21 @@ function OhhwellsBridge() {
8338
10417
  hoveredNavContainerRef.current = null;
8339
10418
  setHoveredNavContainerRect(null);
8340
10419
  const selected2 = selectedElRef.current;
8341
- if (selected2 === navAnchor || selected2?.contains(navAnchor)) return;
10420
+ if (selected2 === navAnchor) return;
8342
10421
  clearHrefKeyHover(navAnchor);
8343
10422
  hoveredItemElRef.current = navAnchor;
8344
10423
  setHoveredItemRect(navAnchor.getBoundingClientRect());
8345
10424
  return;
8346
10425
  }
10426
+ const footerCol = target.closest("[data-ohw-footer-col]") ?? target.closest("[data-ohw-footer-column]");
10427
+ if (footerCol) {
10428
+ hoveredNavContainerRef.current = null;
10429
+ setHoveredNavContainerRect(null);
10430
+ if (selectedElRef.current === footerCol) return;
10431
+ hoveredItemElRef.current = footerCol;
10432
+ setHoveredItemRect(footerCol.getBoundingClientRect());
10433
+ return;
10434
+ }
8347
10435
  const editable = target.closest("[data-ohw-editable]");
8348
10436
  if (!editable) return;
8349
10437
  const selected = selectedElRef.current;
@@ -8361,14 +10449,25 @@ function OhhwellsBridge() {
8361
10449
  };
8362
10450
  const handleMouseOut = (e) => {
8363
10451
  const target = e.target;
8364
- if (target.closest("[data-ohw-nav-container]")) {
10452
+ if (target.closest("[data-ohw-nav-container], [data-ohw-footer-col], [data-ohw-footer-column]")) {
8365
10453
  const related2 = e.relatedTarget instanceof Element ? e.relatedTarget : null;
8366
- if (related2?.closest("[data-ohw-nav-container], [data-ohw-navbar-container-chrome], [data-ohw-navbar-add-button]")) {
10454
+ 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]")) {
8367
10455
  return;
8368
10456
  }
8369
10457
  hoveredNavContainerRef.current = null;
8370
10458
  setHoveredNavContainerRect(null);
8371
10459
  }
10460
+ const footerCol = target.closest("[data-ohw-footer-col]") ?? target.closest("[data-ohw-footer-column]");
10461
+ if (footerCol && hoveredItemElRef.current === footerCol) {
10462
+ const related2 = e.relatedTarget instanceof Element ? e.relatedTarget : null;
10463
+ if (related2?.closest("[data-ohw-drag-handle-container], [data-ohw-item-interaction]")) return;
10464
+ if (related2?.closest("[data-ohw-footer-col], [data-ohw-footer-column]") === footerCol) {
10465
+ return;
10466
+ }
10467
+ hoveredItemElRef.current = null;
10468
+ setHoveredItemRect(null);
10469
+ return;
10470
+ }
8372
10471
  const navAnchor = getNavigationItemAnchor(target);
8373
10472
  if (navAnchor) {
8374
10473
  const related2 = e.relatedTarget instanceof Element ? e.relatedTarget : null;
@@ -8502,43 +10601,75 @@ function OhhwellsBridge() {
8502
10601
  if (toolbarVariantRef.current === "select-frame") {
8503
10602
  hoveredNavContainerRef.current = null;
8504
10603
  setHoveredNavContainerRect(null);
8505
- return;
8506
10604
  }
8507
- const navContainer = document.querySelector("[data-ohw-nav-container]");
8508
- if (!navContainer) {
8509
- hoveredNavContainerRef.current = null;
8510
- setHoveredNavContainerRect(null);
8511
- return;
10605
+ const navContainers = Array.from(
10606
+ document.querySelectorAll("[data-ohw-nav-container]")
10607
+ );
10608
+ const footerColumns = listFooterColumns();
10609
+ for (const container of [...navContainers, ...footerColumns]) {
10610
+ const containerRect = container.getBoundingClientRect();
10611
+ const overContainer = x >= containerRect.left && x <= containerRect.right && y >= containerRect.top && y <= containerRect.bottom;
10612
+ if (!overContainer) continue;
10613
+ const navItemHit = Array.from(
10614
+ container.querySelectorAll("[data-ohw-href-key]")
10615
+ ).find((el) => {
10616
+ if (!isNavigationItem(el) || isNavbarButton2(el)) return false;
10617
+ const itemRect = el.getBoundingClientRect();
10618
+ return x >= itemRect.left && x <= itemRect.right && y >= itemRect.top && y <= itemRect.bottom;
10619
+ });
10620
+ if (navItemHit) {
10621
+ hoveredNavContainerRef.current = null;
10622
+ setHoveredNavContainerRect(null);
10623
+ const selected = selectedElRef.current;
10624
+ if (selected !== navItemHit) {
10625
+ clearHrefKeyHover(navItemHit);
10626
+ hoveredItemElRef.current = navItemHit;
10627
+ setHoveredItemRect(navItemHit.getBoundingClientRect());
10628
+ } else if (hoveredItemElRef.current === navItemHit) {
10629
+ hoveredItemElRef.current = null;
10630
+ setHoveredItemRect(null);
10631
+ }
10632
+ return;
10633
+ }
8512
10634
  }
8513
- const containerRect = navContainer.getBoundingClientRect();
8514
- const overContainer = x >= containerRect.left && x <= containerRect.right && y >= containerRect.top && y <= containerRect.bottom;
8515
- if (!overContainer) {
10635
+ if (toolbarVariantRef.current !== "select-frame") {
10636
+ for (const container of navContainers) {
10637
+ const containerRect = container.getBoundingClientRect();
10638
+ const overContainer = x >= containerRect.left && x <= containerRect.right && y >= containerRect.top && y <= containerRect.bottom;
10639
+ if (!overContainer) continue;
10640
+ if (isPointOverNavItem(container, x, y)) continue;
10641
+ hoveredNavContainerRef.current = container;
10642
+ setHoveredNavContainerRect(container.getBoundingClientRect());
10643
+ hoveredItemElRef.current = null;
10644
+ setHoveredItemRect(null);
10645
+ return;
10646
+ }
8516
10647
  hoveredNavContainerRef.current = null;
8517
10648
  setHoveredNavContainerRect(null);
8518
- return;
8519
- }
8520
- const navItemHit = Array.from(
8521
- navContainer.querySelectorAll("[data-ohw-href-key]")
8522
- ).find((el) => {
8523
- if (!isNavigationItem(el) || isNavbarButton2(el)) return false;
8524
- const itemRect = el.getBoundingClientRect();
8525
- return x >= itemRect.left && x <= itemRect.right && y >= itemRect.top && y <= itemRect.bottom;
8526
- });
8527
- if (navItemHit) {
10649
+ } else {
8528
10650
  hoveredNavContainerRef.current = null;
8529
10651
  setHoveredNavContainerRect(null);
8530
- const selected = selectedElRef.current;
8531
- if (selected !== navItemHit && !selected?.contains(navItemHit)) {
8532
- clearHrefKeyHover(navItemHit);
8533
- hoveredItemElRef.current = navItemHit;
8534
- setHoveredItemRect(navItemHit.getBoundingClientRect());
10652
+ }
10653
+ for (const column of footerColumns) {
10654
+ const containerRect = column.getBoundingClientRect();
10655
+ const overContainer = x >= containerRect.left && x <= containerRect.right && y >= containerRect.top && y <= containerRect.bottom;
10656
+ if (!overContainer) continue;
10657
+ if (isPointOverNavItem(column, x, y)) continue;
10658
+ if (selectedElRef.current === column) {
10659
+ if (hoveredItemElRef.current === column) {
10660
+ hoveredItemElRef.current = null;
10661
+ setHoveredItemRect(null);
10662
+ }
10663
+ return;
8535
10664
  }
10665
+ hoveredItemElRef.current = column;
10666
+ setHoveredItemRect(column.getBoundingClientRect());
8536
10667
  return;
8537
10668
  }
8538
- hoveredNavContainerRef.current = navContainer;
8539
- setHoveredNavContainerRect(containerRect);
8540
- hoveredItemElRef.current = null;
8541
- setHoveredItemRect(null);
10669
+ if (hoveredItemElRef.current?.hasAttribute("data-ohw-footer-col") || hoveredItemElRef.current?.hasAttribute("data-ohw-footer-column")) {
10670
+ hoveredItemElRef.current = null;
10671
+ setHoveredItemRect(null);
10672
+ }
8542
10673
  };
8543
10674
  const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
8544
10675
  if (linkPopoverOpenRef.current) {
@@ -8559,6 +10690,10 @@ function OhhwellsBridge() {
8559
10690
  }
8560
10691
  hoveredNavContainerRef.current = null;
8561
10692
  setHoveredNavContainerRect(null);
10693
+ if (hoveredItemElRef.current?.hasAttribute("data-ohw-footer-col") || hoveredItemElRef.current?.hasAttribute("data-ohw-footer-column")) {
10694
+ hoveredItemElRef.current = null;
10695
+ setHoveredItemRect(null);
10696
+ }
8562
10697
  const toggleEl = document.querySelector("[data-ohw-state-toggle]");
8563
10698
  if (toggleEl) {
8564
10699
  const tr = toggleEl.getBoundingClientRect();
@@ -8806,6 +10941,15 @@ function OhhwellsBridge() {
8806
10941
  probeHoverCardsAt(clientX, clientY);
8807
10942
  };
8808
10943
  const handleDragOver = (e) => {
10944
+ const footerSession = footerDragRef.current;
10945
+ if (footerSession) {
10946
+ e.preventDefault();
10947
+ if (e.dataTransfer) e.dataTransfer.dropEffect = "move";
10948
+ const slot = footerSession.kind === "link" && footerSession.hrefKey ? hitTestLinkDropSlot(e.clientX, e.clientY, footerSession.hrefKey) : hitTestColumnDropSlot(e.clientX, e.clientY);
10949
+ refreshFooterDragVisualsRef.current(footerSession, slot, e.clientX, e.clientY);
10950
+ return;
10951
+ }
10952
+ if (onNavDragOver(e)) return;
8809
10953
  e.preventDefault();
8810
10954
  const el = findImageAtPoint(e.clientX, e.clientY, false);
8811
10955
  if (!el) {
@@ -8968,8 +11112,8 @@ function OhhwellsBridge() {
8968
11112
  if (sibling !== el) sibling.innerHTML = html;
8969
11113
  });
8970
11114
  const timers = autoSaveTimers.current;
8971
- const existing = timers.get(key);
8972
- if (existing) clearTimeout(existing);
11115
+ const existing2 = timers.get(key);
11116
+ if (existing2) clearTimeout(existing2);
8973
11117
  timers.set(key, setTimeout(() => {
8974
11118
  timers.delete(key);
8975
11119
  postToParentRef.current({ type: "ow:change", nodes: [{ key, text: html }] });
@@ -9012,6 +11156,7 @@ function OhhwellsBridge() {
9012
11156
  }
9013
11157
  editContentRef.current = { ...editContentRef.current, ...content };
9014
11158
  reconcileNavbarItemsFromContent(editContentRef.current);
11159
+ syncNavigationDragCursorAttrs();
9015
11160
  enforceLinkHrefs();
9016
11161
  postToParentRef.current({ type: "ow:hydrate-done" });
9017
11162
  };
@@ -9032,6 +11177,48 @@ function OhhwellsBridge() {
9032
11177
  if (e.data?.type !== "ui:escape") return;
9033
11178
  if (linkPopoverOpenRef.current) {
9034
11179
  setLinkPopoverRef.current(null);
11180
+ return;
11181
+ }
11182
+ if (activeElRef.current) {
11183
+ const hrefCtx = getHrefKeyFromElement(activeElRef.current);
11184
+ const navAnchor = hrefCtx ? getNavigationItemAnchor(hrefCtx.anchor) : null;
11185
+ if (navAnchor) {
11186
+ const el = activeElRef.current;
11187
+ if (originalContentRef.current !== null) {
11188
+ el.innerHTML = originalContentRef.current;
11189
+ }
11190
+ el.removeAttribute("contenteditable");
11191
+ el.removeAttribute("data-ohw-editing");
11192
+ activeElRef.current = null;
11193
+ setMaxBadge(null);
11194
+ setActiveCommands(/* @__PURE__ */ new Set());
11195
+ setToolbarShowEditLink(false);
11196
+ postToParentRef.current({ type: "ow:exit-edit" });
11197
+ reselectNavigationItemRef.current(navAnchor);
11198
+ return;
11199
+ }
11200
+ if (originalContentRef.current !== null) {
11201
+ activeElRef.current.innerHTML = originalContentRef.current;
11202
+ const key = activeElRef.current.dataset.ohwKey;
11203
+ if (key) {
11204
+ postToParentRef.current({ type: "ow:change", nodes: [{ key, text: originalContentRef.current }] });
11205
+ }
11206
+ }
11207
+ deselectRef.current();
11208
+ deactivateRef.current();
11209
+ return;
11210
+ }
11211
+ if (selectedElRef.current) {
11212
+ if (toolbarVariantRef.current === "select-frame") {
11213
+ deselectRef.current();
11214
+ return;
11215
+ }
11216
+ const parent = getNavigationSelectionParent(selectedElRef.current);
11217
+ if (parent && isNavigationContainer(parent)) {
11218
+ selectFrameRef.current(parent);
11219
+ } else {
11220
+ deselectRef.current();
11221
+ }
9035
11222
  }
9036
11223
  };
9037
11224
  window.addEventListener("message", handleUiEscape);
@@ -9056,7 +11243,7 @@ function OhhwellsBridge() {
9056
11243
  return;
9057
11244
  }
9058
11245
  const parent = getNavigationSelectionParent(selectedElRef.current);
9059
- if (parent) {
11246
+ if (parent && isNavigationContainer(parent)) {
9060
11247
  e.preventDefault();
9061
11248
  selectFrameRef.current(parent);
9062
11249
  } else {
@@ -9074,6 +11261,7 @@ function OhhwellsBridge() {
9074
11261
  el2.innerHTML = originalContentRef.current;
9075
11262
  }
9076
11263
  el2.removeAttribute("contenteditable");
11264
+ el2.removeAttribute("data-ohw-editing");
9077
11265
  activeElRef.current = null;
9078
11266
  setMaxBadge(null);
9079
11267
  setActiveCommands(/* @__PURE__ */ new Set());
@@ -9125,6 +11313,26 @@ function OhhwellsBridge() {
9125
11313
  if (siblingHintElRef.current) {
9126
11314
  setSiblingHintRect(siblingHintElRef.current.getBoundingClientRect());
9127
11315
  }
11316
+ const selected = selectedElRef.current;
11317
+ if (selected && !footerDragRef.current && !navDragRef.current) {
11318
+ if (selected.hasAttribute("data-ohw-footer-col") || Boolean(selected.closest("footer") && isInferredFooterGroup(selected))) {
11319
+ setSiblingHintRects(
11320
+ listFooterColumns().filter((column) => column !== selected).map((column) => column.getBoundingClientRect())
11321
+ );
11322
+ } else if (isNavigationItem(selected)) {
11323
+ setSiblingHintRects(collectNavigationItemSiblingHintRects(selected));
11324
+ }
11325
+ }
11326
+ if (footerDragRef.current) {
11327
+ const session = footerDragRef.current;
11328
+ const slot = session.kind === "link" && session.hrefKey ? hitTestLinkDropSlot(session.lastClientX, session.lastClientY, session.hrefKey) : hitTestColumnDropSlot(session.lastClientX, session.lastClientY);
11329
+ refreshFooterDragVisualsRef.current(session, slot, session.lastClientX, session.lastClientY);
11330
+ }
11331
+ if (navDragRef.current) {
11332
+ const session = navDragRef.current;
11333
+ const slot = hitTestNavDropSlot(session.lastClientX, session.lastClientY, session.hrefKey);
11334
+ refreshNavDragVisualsRef.current(session, slot, session.lastClientX, session.lastClientY);
11335
+ }
9128
11336
  if (hoveredImageRef.current) {
9129
11337
  const el = hoveredImageRef.current;
9130
11338
  const r2 = el.getBoundingClientRect();
@@ -9135,7 +11343,7 @@ function OhhwellsBridge() {
9135
11343
  };
9136
11344
  const handleSave = (e) => {
9137
11345
  if (e.data?.type !== "ow:save") return;
9138
- const nodes = collectEditableNodes();
11346
+ const nodes = collectEditableNodes(editContentRef.current);
9139
11347
  const tracker = document.querySelector("[data-ohw-sections-tracker]");
9140
11348
  if (tracker?.textContent) nodes.push({ key: "__ohw_sections", type: "sections", text: tracker.textContent });
9141
11349
  postToParentRef.current({ type: "ow:save-result", nodes });
@@ -9193,30 +11401,83 @@ function OhhwellsBridge() {
9193
11401
  const h = document.documentElement.scrollHeight;
9194
11402
  if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
9195
11403
  };
9196
- const handleSelectionChange = () => {
9197
- if (!activeElRef.current) return;
11404
+ let selectionChangeRaf = null;
11405
+ const runSelectionChange = () => {
11406
+ selectionChangeRaf = null;
11407
+ const activeRoot = activeElRef.current;
11408
+ if (!activeRoot) return;
9198
11409
  const next = /* @__PURE__ */ new Set();
9199
- for (const cmd of ["bold", "italic", "underline", "strikeThrough", "insertUnorderedList", "insertOrderedList"]) {
9200
- try {
9201
- if (document.queryCommandState(cmd)) next.add(cmd);
9202
- } catch {
9203
- }
9204
- }
9205
11410
  const sel = window.getSelection();
9206
- const anchor = sel?.anchorNode;
9207
- if (anchor) {
9208
- const el = anchor.nodeType === Node.TEXT_NODE ? anchor.parentElement : anchor;
9209
- const block = el?.closest("div, p, h1, h2, h3, h4, h5, h6, li, td, th") ?? el;
9210
- if (block) {
9211
- const align = getComputedStyle(block).textAlign;
9212
- if (align === "center") next.add("justifyCenter");
9213
- else if (align === "right" || align === "end") next.add("justifyRight");
9214
- else next.add("justifyLeft");
11411
+ let startNode = null;
11412
+ if (sel && sel.rangeCount > 0) {
11413
+ const range = sel.getRangeAt(0);
11414
+ startNode = range.startContainer;
11415
+ if (startNode.nodeType === Node.ELEMENT_NODE) {
11416
+ const el = startNode;
11417
+ startNode = el.childNodes[range.startOffset] ?? el.childNodes[range.startOffset - 1] ?? el;
9215
11418
  }
9216
11419
  }
9217
- setActiveCommands(next);
11420
+ const startEl = startNode ? startNode.nodeType === Node.TEXT_NODE ? startNode.parentElement : startNode : null;
11421
+ if (startEl) {
11422
+ const cs = getComputedStyle(startEl);
11423
+ const weight = parseInt(cs.fontWeight, 10);
11424
+ let hasBold = cs.fontWeight === "bold" || !Number.isNaN(weight) && weight >= 600;
11425
+ let hasItalic = cs.fontStyle === "italic" || cs.fontStyle === "oblique";
11426
+ let hasUnderline = false;
11427
+ let hasStrike = false;
11428
+ let hasUl = false;
11429
+ let hasOl = false;
11430
+ for (let el = startEl; el; el = el.parentElement) {
11431
+ if (el.tagName === "B" || el.tagName === "STRONG") hasBold = true;
11432
+ if (el.tagName === "I" || el.tagName === "EM") hasItalic = true;
11433
+ const decoration = getComputedStyle(el).textDecorationLine;
11434
+ if (decoration.includes("underline") || el.tagName === "U" || el.tagName === "INS") hasUnderline = true;
11435
+ if (decoration.includes("line-through") || el.tagName === "S" || el.tagName === "STRIKE" || el.tagName === "DEL") hasStrike = true;
11436
+ if (el.tagName === "UL") hasUl = true;
11437
+ if (el.tagName === "OL") hasOl = true;
11438
+ if (el === activeRoot) break;
11439
+ }
11440
+ if (hasBold) next.add("bold");
11441
+ if (hasItalic) next.add("italic");
11442
+ if (hasUnderline) next.add("underline");
11443
+ if (hasStrike) next.add("strikeThrough");
11444
+ if (hasUl) next.add("insertUnorderedList");
11445
+ if (hasOl) next.add("insertOrderedList");
11446
+ const block = startEl.closest("div, p, h1, h2, h3, h4, h5, h6, li, td, th") ?? startEl;
11447
+ const align = getComputedStyle(block).textAlign;
11448
+ if (align === "center") next.add("justifyCenter");
11449
+ else if (align === "right" || align === "end") next.add("justifyRight");
11450
+ else next.add("justifyLeft");
11451
+ }
11452
+ setActiveCommands((prev) => {
11453
+ if (prev.size === next.size) {
11454
+ let same = true;
11455
+ for (const cmd of prev) {
11456
+ if (!next.has(cmd)) {
11457
+ same = false;
11458
+ break;
11459
+ }
11460
+ }
11461
+ if (same) return prev;
11462
+ }
11463
+ return next;
11464
+ });
11465
+ };
11466
+ const handleSelectionChange = () => {
11467
+ if (pointerHeldRef.current) return;
11468
+ if (selectionChangeRaf !== null) return;
11469
+ selectionChangeRaf = requestAnimationFrame(runSelectionChange);
11470
+ };
11471
+ const markPointerHeld = (e) => {
11472
+ if (e.button !== 0) return;
11473
+ pointerHeldRef.current = true;
11474
+ };
11475
+ const markPointerReleased = () => {
11476
+ if (!pointerHeldRef.current) return;
11477
+ pointerHeldRef.current = false;
11478
+ handleSelectionChange();
9218
11479
  };
9219
- refreshActiveCommandsRef.current = handleSelectionChange;
11480
+ refreshActiveCommandsRef.current = runSelectionChange;
9220
11481
  const handleDocMouseLeave = () => {
9221
11482
  hoveredImageRef.current = null;
9222
11483
  setMediaHover(null);
@@ -9261,15 +11522,15 @@ function OhhwellsBridge() {
9261
11522
  };
9262
11523
  const applyToolbarPos = (rect) => {
9263
11524
  const ps = parentScrollRef.current;
9264
- const approxW = 330;
11525
+ const measuredW = toolbarElRef.current?.offsetWidth || 330;
9265
11526
  if (toolbarElRef.current) {
9266
- const { top, left, transform } = calcToolbarPos(rect, ps, approxW);
11527
+ const { top, left, transform } = calcToolbarPos(rect, ps, measuredW);
9267
11528
  toolbarElRef.current.style.top = `${top}px`;
9268
11529
  toolbarElRef.current.style.left = `${left}px`;
9269
11530
  toolbarElRef.current.style.transform = transform;
9270
11531
  }
9271
11532
  if (glowElRef.current) {
9272
- const GAP = 6;
11533
+ const GAP = SELECTION_CHROME_GAP2;
9273
11534
  glowElRef.current.style.top = `${rect.top - GAP}px`;
9274
11535
  glowElRef.current.style.left = `${rect.left - GAP}px`;
9275
11536
  glowElRef.current.style.width = `${rect.width + GAP * 2}px`;
@@ -9357,6 +11618,11 @@ function OhhwellsBridge() {
9357
11618
  return;
9358
11619
  }
9359
11620
  }
11621
+ const footerColumn = findHoveredNavOrFooterContainer(clientX, clientY);
11622
+ if (footerColumn?.hasAttribute("data-ohw-footer-col") || footerColumn?.hasAttribute("data-ohw-footer-column")) {
11623
+ selectFrameRef.current(footerColumn);
11624
+ return;
11625
+ }
9360
11626
  deactivateRef.current();
9361
11627
  };
9362
11628
  window.addEventListener("message", handleSave);
@@ -9428,7 +11694,177 @@ function OhhwellsBridge() {
9428
11694
  if (imageShowTimerRef.current) clearTimeout(imageShowTimerRef.current);
9429
11695
  };
9430
11696
  }, [isEditMode, refreshStateRules]);
9431
- (0, import_react8.useEffect)(() => {
11697
+ (0, import_react9.useEffect)(() => {
11698
+ if (!isEditMode) return;
11699
+ const THRESHOLD = 10;
11700
+ const resolveWasSelected = (el) => {
11701
+ if (selectedElRef.current === el) return true;
11702
+ const activeAnchor = activeElRef.current ? getNavigationItemAnchor(activeElRef.current) : null;
11703
+ return activeAnchor === el;
11704
+ };
11705
+ const onPointerDown = (e) => {
11706
+ if (e.button !== 0) return;
11707
+ if (suppressNextClickRef.current && Date.now() >= suppressClickUntilRef.current && !footerDragRef.current && !footerPointerDragRef.current?.started) {
11708
+ suppressNextClickRef.current = false;
11709
+ }
11710
+ if (footerDragRef.current) return;
11711
+ const target = e.target;
11712
+ if (!target) return;
11713
+ 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]')) {
11714
+ return;
11715
+ }
11716
+ if (target.closest("[data-ohw-item-drag-surface]")) return;
11717
+ const anchor = getNavigationItemAnchor(target);
11718
+ const hrefKey = anchor?.getAttribute("data-ohw-href-key") ?? null;
11719
+ if (anchor && isFooterHrefKey(hrefKey)) {
11720
+ footerPointerDragRef.current = {
11721
+ el: anchor,
11722
+ kind: "link",
11723
+ startX: e.clientX,
11724
+ startY: e.clientY,
11725
+ pointerId: e.pointerId,
11726
+ wasSelected: resolveWasSelected(anchor),
11727
+ started: false
11728
+ };
11729
+ return;
11730
+ }
11731
+ const col = target.closest("[data-ohw-footer-col]");
11732
+ if (col && !getNavigationItemAnchor(target)) {
11733
+ footerPointerDragRef.current = {
11734
+ el: col,
11735
+ kind: "column",
11736
+ startX: e.clientX,
11737
+ startY: e.clientY,
11738
+ pointerId: e.pointerId,
11739
+ wasSelected: resolveWasSelected(col),
11740
+ started: false
11741
+ };
11742
+ }
11743
+ };
11744
+ const onPointerMove = (e) => {
11745
+ const pending = footerPointerDragRef.current;
11746
+ if (!pending) return;
11747
+ if (pending.started) {
11748
+ e.preventDefault();
11749
+ clearTextSelection();
11750
+ const session = footerDragRef.current;
11751
+ if (!session) return;
11752
+ const slot = session.kind === "link" && session.hrefKey ? hitTestLinkDropSlot(e.clientX, e.clientY, session.hrefKey) : hitTestColumnDropSlot(e.clientX, e.clientY);
11753
+ refreshFooterDragVisualsRef.current(session, slot, e.clientX, e.clientY);
11754
+ return;
11755
+ }
11756
+ const dx = e.clientX - pending.startX;
11757
+ const dy = e.clientY - pending.startY;
11758
+ if (dx * dx + dy * dy < THRESHOLD * THRESHOLD) return;
11759
+ e.preventDefault();
11760
+ pending.started = true;
11761
+ armFooterPressDrag();
11762
+ clearTextSelection();
11763
+ try {
11764
+ document.body.setPointerCapture(pending.pointerId);
11765
+ } catch {
11766
+ }
11767
+ if (linkPopoverOpenRef.current) {
11768
+ setLinkPopoverRef.current(null);
11769
+ }
11770
+ if (activeElRef.current) {
11771
+ deactivateRef.current();
11772
+ }
11773
+ if (pending.kind === "link") {
11774
+ const key = pending.el.getAttribute("data-ohw-href-key");
11775
+ if (!key) return;
11776
+ const column = findFooterColumnForLink(pending.el);
11777
+ const columns2 = listFooterColumns();
11778
+ beginFooterDragRef.current({
11779
+ kind: "link",
11780
+ hrefKey: key,
11781
+ columnEl: column,
11782
+ sourceColumnIndex: column ? columns2.indexOf(column) : 0,
11783
+ wasSelected: pending.wasSelected,
11784
+ draggedEl: pending.el,
11785
+ lastClientX: e.clientX,
11786
+ lastClientY: e.clientY,
11787
+ activeSlot: null
11788
+ });
11789
+ return;
11790
+ }
11791
+ const columns = listFooterColumns();
11792
+ const idx = columns.indexOf(pending.el);
11793
+ if (idx < 0) return;
11794
+ beginFooterDragRef.current({
11795
+ kind: "column",
11796
+ hrefKey: null,
11797
+ columnEl: pending.el,
11798
+ sourceColumnIndex: idx,
11799
+ wasSelected: pending.wasSelected,
11800
+ draggedEl: pending.el,
11801
+ lastClientX: e.clientX,
11802
+ lastClientY: e.clientY,
11803
+ activeSlot: null
11804
+ });
11805
+ };
11806
+ const endPointerDrag = (e) => {
11807
+ const pending = footerPointerDragRef.current;
11808
+ footerPointerDragRef.current = null;
11809
+ try {
11810
+ if (document.body.hasPointerCapture(e.pointerId)) {
11811
+ document.body.releasePointerCapture(e.pointerId);
11812
+ }
11813
+ } catch {
11814
+ }
11815
+ if (!pending) return;
11816
+ if (!pending.started) {
11817
+ unlockFooterDragInteraction();
11818
+ return;
11819
+ }
11820
+ suppressNextClickRef.current = true;
11821
+ suppressClickUntilRef.current = Date.now() + 500;
11822
+ commitFooterDragRef.current(e.clientX, e.clientY);
11823
+ };
11824
+ const blockSelectStart = (e) => {
11825
+ if (footerDragRef.current || footerPointerDragRef.current?.started || document.documentElement.hasAttribute("data-ohw-footer-press-drag")) {
11826
+ e.preventDefault();
11827
+ }
11828
+ };
11829
+ const onDragEnd = (_e) => {
11830
+ if (!footerDragRef.current) return;
11831
+ suppressNextClickRef.current = true;
11832
+ suppressClickUntilRef.current = Date.now() + 500;
11833
+ commitFooterDragRef.current();
11834
+ };
11835
+ document.addEventListener("pointerdown", onPointerDown, true);
11836
+ document.addEventListener("pointermove", onPointerMove, true);
11837
+ document.addEventListener("pointerup", endPointerDrag, true);
11838
+ document.addEventListener("pointercancel", endPointerDrag, true);
11839
+ document.addEventListener("selectstart", blockSelectStart, true);
11840
+ document.addEventListener("dragend", onDragEnd, true);
11841
+ return () => {
11842
+ document.removeEventListener("pointerdown", onPointerDown, true);
11843
+ document.removeEventListener("pointermove", onPointerMove, true);
11844
+ document.removeEventListener("pointerup", endPointerDrag, true);
11845
+ document.removeEventListener("pointercancel", endPointerDrag, true);
11846
+ document.removeEventListener("selectstart", blockSelectStart, true);
11847
+ document.removeEventListener("dragend", onDragEnd, true);
11848
+ unlockFooterDragInteraction();
11849
+ };
11850
+ }, [isEditMode]);
11851
+ (0, import_react9.useEffect)(() => {
11852
+ if (!isEditMode) return;
11853
+ const debugSelectionChange = () => {
11854
+ const sel = window.getSelection();
11855
+ console.log(
11856
+ "[OHW DEBUG selectionchange]",
11857
+ JSON.stringify(sel?.toString() ?? ""),
11858
+ "collapsed=",
11859
+ sel?.isCollapsed,
11860
+ "anchorNode=",
11861
+ sel?.anchorNode,
11862
+ "activeEl=",
11863
+ activeElRef.current
11864
+ );
11865
+ };
11866
+ }, [isEditMode]);
11867
+ (0, import_react9.useEffect)(() => {
9432
11868
  const handler = (e) => {
9433
11869
  if (e.data?.type !== "ow:request-schedule-config") return;
9434
11870
  const insertAfterVal = e.data.insertAfter;
@@ -9444,7 +11880,7 @@ function OhhwellsBridge() {
9444
11880
  window.addEventListener("message", handler);
9445
11881
  return () => window.removeEventListener("message", handler);
9446
11882
  }, [processConfigRequest]);
9447
- (0, import_react8.useEffect)(() => {
11883
+ (0, import_react9.useEffect)(() => {
9448
11884
  if (!isEditMode) return;
9449
11885
  document.querySelectorAll("[data-ohw-active-state]").forEach((el) => {
9450
11886
  el.removeAttribute("data-ohw-active-state");
@@ -9465,7 +11901,13 @@ function OhhwellsBridge() {
9465
11901
  const next = { ...prev, [pathKey]: sections };
9466
11902
  return shouldUseDevFixtures() ? { ...DEV_SECTIONS_BY_PATH, ...next } : next;
9467
11903
  });
9468
- postToParent2({ type: "ow:ready", version: "1", path: pathname, nodes: collectEditableNodes(), sections });
11904
+ postToParent2({
11905
+ type: "ow:ready",
11906
+ version: "1",
11907
+ path: pathname,
11908
+ nodes: collectEditableNodes(editContentRef.current),
11909
+ sections
11910
+ });
9469
11911
  postToParent2({ type: "ow:request-site-pages" });
9470
11912
  }, 150);
9471
11913
  return () => {
@@ -9473,19 +11915,20 @@ function OhhwellsBridge() {
9473
11915
  clearTimeout(timer);
9474
11916
  };
9475
11917
  }, [pathname, isEditMode, refreshStateRules, postToParent2]);
9476
- (0, import_react8.useEffect)(() => {
11918
+ (0, import_react9.useEffect)(() => {
9477
11919
  scrollToHashSectionWhenReady();
9478
11920
  const onHashChange = () => scrollToHashSectionWhenReady();
9479
11921
  window.addEventListener("hashchange", onHashChange);
9480
11922
  return () => window.removeEventListener("hashchange", onHashChange);
9481
11923
  }, [pathname]);
9482
- const handleCommand = (0, import_react8.useCallback)((cmd) => {
11924
+ const handleCommand = (0, import_react9.useCallback)((cmd) => {
11925
+ document.execCommand("styleWithCSS", false, true);
9483
11926
  document.execCommand(cmd, false);
9484
11927
  activeElRef.current?.focus();
9485
11928
  if (activeElRef.current) setToolbarRect(getEditMeasureEl(activeElRef.current).getBoundingClientRect());
9486
11929
  refreshActiveCommandsRef.current();
9487
11930
  }, []);
9488
- const handleStateChange = (0, import_react8.useCallback)((state) => {
11931
+ const handleStateChange = (0, import_react9.useCallback)((state) => {
9489
11932
  if (!activeStateElRef.current) return;
9490
11933
  const el = activeStateElRef.current;
9491
11934
  if (state === "Default") {
@@ -9498,11 +11941,11 @@ function OhhwellsBridge() {
9498
11941
  }
9499
11942
  setToggleState((prev) => prev ? { ...prev, activeState: state } : null);
9500
11943
  }, [deactivate]);
9501
- const closeLinkPopover = (0, import_react8.useCallback)(() => {
11944
+ const closeLinkPopover = (0, import_react9.useCallback)(() => {
9502
11945
  addNavAfterAnchorRef.current = null;
9503
11946
  setLinkPopover(null);
9504
11947
  }, []);
9505
- const openLinkPopoverForActive = (0, import_react8.useCallback)(() => {
11948
+ const openLinkPopoverForActive = (0, import_react9.useCallback)(() => {
9506
11949
  const hrefCtx = getHrefKeyFromElement(activeElRef.current);
9507
11950
  if (!hrefCtx) return;
9508
11951
  bumpLinkPopoverGrace();
@@ -9513,7 +11956,7 @@ function OhhwellsBridge() {
9513
11956
  });
9514
11957
  deactivate();
9515
11958
  }, [deactivate]);
9516
- const openLinkPopoverForSelected = (0, import_react8.useCallback)(() => {
11959
+ const openLinkPopoverForSelected = (0, import_react9.useCallback)(() => {
9517
11960
  const anchor = selectedElRef.current;
9518
11961
  if (!anchor) return;
9519
11962
  const key = anchor.getAttribute("data-ohw-href-key");
@@ -9526,7 +11969,7 @@ function OhhwellsBridge() {
9526
11969
  });
9527
11970
  deselect();
9528
11971
  }, [deselect]);
9529
- const handleLinkPopoverSubmit = (0, import_react8.useCallback)(
11972
+ const handleLinkPopoverSubmit = (0, import_react9.useCallback)(
9530
11973
  (target) => {
9531
11974
  const session = linkPopoverSessionRef.current;
9532
11975
  if (!session) return;
@@ -9585,13 +12028,13 @@ function OhhwellsBridge() {
9585
12028
  const showEditLink = toolbarShowEditLink;
9586
12029
  const currentSections = sectionsByPath[pathname] ?? [];
9587
12030
  linkPopoverOpenRef.current = linkPopover !== null;
9588
- const handleMediaReplace = (0, import_react8.useCallback)(
12031
+ const handleMediaReplace = (0, import_react9.useCallback)(
9589
12032
  (key) => {
9590
12033
  postToParent2({ type: "ow:image-pick", key, elementType: mediaHover?.elementType ?? "image" });
9591
12034
  },
9592
12035
  [postToParent2, mediaHover?.elementType]
9593
12036
  );
9594
- const handleMediaFadeOutComplete = (0, import_react8.useCallback)((key) => {
12037
+ const handleMediaFadeOutComplete = (0, import_react9.useCallback)((key) => {
9595
12038
  setUploadingRects((prev) => {
9596
12039
  if (!(key in prev)) return prev;
9597
12040
  const next = { ...prev };
@@ -9599,7 +12042,7 @@ function OhhwellsBridge() {
9599
12042
  return next;
9600
12043
  });
9601
12044
  }, []);
9602
- const handleVideoSettingsChange = (0, import_react8.useCallback)(
12045
+ const handleVideoSettingsChange = (0, import_react9.useCallback)(
9603
12046
  (key, settings) => {
9604
12047
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
9605
12048
  const video = getVideoEl(el);
@@ -9622,9 +12065,9 @@ function OhhwellsBridge() {
9622
12065
  [postToParent2]
9623
12066
  );
9624
12067
  return bridgeRoot ? (0, import_react_dom3.createPortal)(
9625
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
9626
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
9627
- Object.entries(uploadingRects).map(([key, { rect, fadingOut }]) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
12068
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
12069
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
12070
+ Object.entries(uploadingRects).map(([key, { rect, fadingOut }]) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9628
12071
  MediaOverlay,
9629
12072
  {
9630
12073
  hover: { key, rect, elementType: "image", isDragOver: false, hasTextOverlap: false },
@@ -9635,7 +12078,7 @@ function OhhwellsBridge() {
9635
12078
  },
9636
12079
  `uploading-${key}`
9637
12080
  )),
9638
- mediaHover && !(mediaHover.key in uploadingRects) && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
12081
+ mediaHover && !(mediaHover.key in uploadingRects) && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9639
12082
  MediaOverlay,
9640
12083
  {
9641
12084
  hover: mediaHover,
@@ -9644,49 +12087,93 @@ function OhhwellsBridge() {
9644
12087
  onVideoSettingsChange: handleVideoSettingsChange
9645
12088
  }
9646
12089
  ),
9647
- siblingHintRect && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
9648
- hoveredNavContainerRect && toolbarVariant !== "select-frame" && !linkPopover && !isItemDragging && !siblingHintRect && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
9649
- toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
9650
- NavbarContainerChrome,
12090
+ siblingHintRect && !linkPopover && !isItemDragging && siblingHintRects.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
12091
+ siblingHintRects.length > 0 && !linkPopover && siblingHintRects.map((rect, i) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ItemInteractionLayer, { rect, state: "sibling-hint" }, `sibling-hint-${i}`)),
12092
+ isItemDragging && draggedItemRect && selectedElRef.current !== footerDragRef.current?.draggedEl && selectedElRef.current !== navDragRef.current?.draggedEl && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ItemInteractionLayer, { rect: draggedItemRect, state: "dragging" }),
12093
+ isItemDragging && footerDropSlots.map((slot, i) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
12094
+ "div",
9651
12095
  {
9652
- rect: toolbarRect,
9653
- onAdd: handleAddTopLevelNavItem
9654
- }
9655
- ),
9656
- hoveredItemRect && !hoveredNavContainerRect && !siblingHintRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
9657
- toolbarRect && (toolbarVariant === "link-action" || toolbarVariant === "select-frame") && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
12096
+ className: "pointer-events-none fixed z-2147483646",
12097
+ style: {
12098
+ left: slot.left,
12099
+ top: slot.top,
12100
+ width: slot.width,
12101
+ height: slot.height
12102
+ },
12103
+ children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
12104
+ DropIndicator,
12105
+ {
12106
+ direction: slot.direction,
12107
+ state: activeFooterDropIndex === i ? "dragActive" : "dragIdle",
12108
+ className: "!h-full !w-full"
12109
+ }
12110
+ )
12111
+ },
12112
+ `footer-drop-${slot.direction}-${slot.columnIndex}-${slot.insertIndex}-${i}`
12113
+ )),
12114
+ isItemDragging && navDropSlots.map((slot, i) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
12115
+ "div",
12116
+ {
12117
+ className: "pointer-events-none fixed z-2147483646",
12118
+ style: {
12119
+ left: slot.left,
12120
+ top: slot.top,
12121
+ width: slot.width,
12122
+ height: slot.height
12123
+ },
12124
+ children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
12125
+ DropIndicator,
12126
+ {
12127
+ direction: slot.direction,
12128
+ state: activeNavDropIndex === i ? "dragActive" : "dragIdle",
12129
+ className: "!h-full !w-full"
12130
+ }
12131
+ )
12132
+ },
12133
+ `nav-drop-${slot.direction}-${slot.parentId ?? "root"}-${slot.insertIndex}-${i}`
12134
+ )),
12135
+ hoveredNavContainerRect && toolbarVariant !== "select-frame" && !linkPopover && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
12136
+ hoveredItemRect && !linkPopover && !hoveredNavContainerRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
12137
+ toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isNavbarLinksContainer(selectedElRef.current) && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(NavbarContainerChrome, { rect: toolbarRect, onAdd: handleAddTopLevelNavItem }),
12138
+ toolbarRect && !linkPopover && (toolbarVariant === "link-action" || toolbarVariant === "select-frame") && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9658
12139
  ItemInteractionLayer,
9659
12140
  {
9660
- rect: toolbarRect,
12141
+ rect: isItemDragging && draggedItemRect && (footerDragRef.current?.wasSelected || navDragRef.current?.wasSelected) ? draggedItemRect : toolbarRect,
9661
12142
  elRef: glowElRef,
9662
12143
  state: isItemDragging ? "dragging" : resolveItemInteractionState(toolbarRect, parentScrollRef.current),
9663
- showHandle: toolbarVariant === "link-action" && Boolean(reorderHrefKey),
12144
+ showHandle: toolbarVariant === "link-action" && Boolean(reorderHrefKey) || toolbarVariant === "select-frame" && isFooterFrameSelection,
9664
12145
  dragDisabled: reorderDragDisabled,
9665
12146
  dragHandleLabel: reorderHrefKey ? `Reorder ${reorderHrefKey}` : "Reorder item",
9666
12147
  onDragHandleDragStart: handleItemDragStart,
9667
12148
  onDragHandleDragEnd: handleItemDragEnd,
9668
- toolbar: toolbarVariant === "link-action" && !isItemDragging ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
12149
+ onItemPointerDown: handleItemChromePointerDown,
12150
+ onItemClick: handleItemChromeClick,
12151
+ itemDragSurface: !isFooterFrameSelection,
12152
+ toolbar: toolbarVariant === "link-action" && !isItemDragging ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9669
12153
  ItemActionToolbar,
9670
12154
  {
9671
12155
  onEditLink: openLinkPopoverForSelected,
9672
12156
  addItemDisabled: true,
9673
12157
  editLinkDisabled: false,
9674
- moreDisabled: true
12158
+ moreDisabled: true,
12159
+ showAddItem: !selectedIsCta,
12160
+ showMore: !selectedIsCta
9675
12161
  }
9676
12162
  ) : void 0
9677
12163
  }
9678
12164
  ),
9679
- toolbarRect && toolbarVariant === "rich-text" && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
9680
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
12165
+ toolbarRect && toolbarVariant === "rich-text" && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
12166
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9681
12167
  EditGlowChrome,
9682
12168
  {
9683
12169
  rect: toolbarRect,
9684
12170
  elRef: glowElRef,
9685
12171
  reorderHrefKey,
9686
- dragDisabled: reorderDragDisabled
12172
+ dragDisabled: reorderDragDisabled,
12173
+ hideHandle: isItemDragging
9687
12174
  }
9688
12175
  ),
9689
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
12176
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9690
12177
  FloatingToolbar,
9691
12178
  {
9692
12179
  rect: toolbarRect,
@@ -9699,7 +12186,7 @@ function OhhwellsBridge() {
9699
12186
  }
9700
12187
  )
9701
12188
  ] }),
9702
- maxBadge && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
12189
+ maxBadge && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
9703
12190
  "div",
9704
12191
  {
9705
12192
  "data-ohw-max-badge": "",
@@ -9725,7 +12212,7 @@ function OhhwellsBridge() {
9725
12212
  ]
9726
12213
  }
9727
12214
  ),
9728
- toggleState && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
12215
+ toggleState && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9729
12216
  StateToggle,
9730
12217
  {
9731
12218
  rect: toggleState.rect,
@@ -9734,32 +12221,36 @@ function OhhwellsBridge() {
9734
12221
  onStateChange: handleStateChange
9735
12222
  }
9736
12223
  ),
9737
- sectionGap && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
12224
+ sectionGap && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
9738
12225
  "div",
9739
12226
  {
9740
12227
  "data-ohw-section-insert-line": "",
9741
12228
  className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
9742
12229
  style: { top: sectionGap.y, transform: "translateY(-50%)" },
9743
12230
  children: [
9744
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
9745
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
12231
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
12232
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9746
12233
  Badge,
9747
12234
  {
9748
12235
  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",
9749
12236
  onClick: () => {
9750
12237
  window.parent.postMessage(
9751
- { type: "ow:add-section", insertAfter: sectionGap.insertAfter, insertBefore: sectionGap.insertBefore },
12238
+ {
12239
+ type: "ow:add-section",
12240
+ insertAfter: sectionGap.insertAfter,
12241
+ insertBefore: sectionGap.insertBefore
12242
+ },
9752
12243
  "*"
9753
12244
  );
9754
12245
  },
9755
12246
  children: "Add Section"
9756
12247
  }
9757
12248
  ),
9758
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
12249
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
9759
12250
  ]
9760
12251
  }
9761
12252
  ),
9762
- linkPopover && dialogPortalContainer ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
12253
+ linkPopover && dialogPortalContainer ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9763
12254
  LinkPopover,
9764
12255
  {
9765
12256
  panelRef: linkPopoverPanelRef,
@@ -9786,6 +12277,7 @@ function OhhwellsBridge() {
9786
12277
  CustomToolbarButton,
9787
12278
  CustomToolbarDivider,
9788
12279
  DragHandle,
12280
+ DropIndicator,
9789
12281
  ItemActionToolbar,
9790
12282
  ItemInteractionLayer,
9791
12283
  LinkEditorPanel,
@@ -9800,6 +12292,7 @@ function OhhwellsBridge() {
9800
12292
  TooltipProvider,
9801
12293
  TooltipTrigger,
9802
12294
  buildTarget,
12295
+ dropIndicatorVariants,
9803
12296
  filterAvailablePages,
9804
12297
  getEditModeInitialState,
9805
12298
  isEditSessionActive,