@ohhwells/bridge 0.1.49 → 0.1.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  // src/OhhwellsBridge.tsx
4
- import React11, { useCallback as useCallback4, useEffect as useEffect9, useLayoutEffect as useLayoutEffect3, useRef as useRef7, useState as useState8 } from "react";
4
+ import React11, { useCallback as useCallback5, useEffect as useEffect9, useLayoutEffect as useLayoutEffect3, useRef as useRef7, useState as useState8 } from "react";
5
5
  import { createRoot } from "react-dom/client";
6
6
  import { flushSync } from "react-dom";
7
7
 
@@ -48,12 +48,13 @@ function useLinkHrefGuardian(...deps) {
48
48
  }
49
49
 
50
50
  // src/ui/SchedulingWidget.tsx
51
- import { useEffect, useId, useMemo, useRef, useState as useState2 } from "react";
51
+ import { useCallback, useEffect, useId, useMemo, useRef, useState as useState2 } from "react";
52
52
 
53
53
  // src/ui/EmailCaptureModal.tsx
54
54
  import { useState } from "react";
55
55
  import { Dialog } from "radix-ui";
56
56
  import { jsx, jsxs } from "react/jsx-runtime";
57
+ var isValidEmail = (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
57
58
  function Spinner() {
58
59
  return /* @__PURE__ */ jsxs(
59
60
  "svg",
@@ -87,12 +88,17 @@ function EmailCaptureModal({ title, subtitle, onSubmit, onClose }) {
87
88
  const [error, setError] = useState(null);
88
89
  const isBook = title === "Confirm your spot";
89
90
  const handleSubmit = async () => {
90
- if (!email.trim()) return;
91
+ const trimmed = email.trim();
92
+ if (!trimmed) return;
93
+ if (!isValidEmail(trimmed)) {
94
+ setError("Please enter a valid email address.");
95
+ return;
96
+ }
91
97
  setLoading(true);
92
98
  setError(null);
93
99
  try {
94
- await onSubmit(email.trim());
95
- setSubmittedEmail(email.trim());
100
+ await onSubmit(trimmed);
101
+ setSubmittedEmail(trimmed);
96
102
  setSuccess(true);
97
103
  } catch (e) {
98
104
  setError(e instanceof Error ? e.message : "Something went wrong. Please try again.");
@@ -167,7 +173,10 @@ function EmailCaptureModal({ title, subtitle, onSubmit, onClose }) {
167
173
  {
168
174
  type: "email",
169
175
  value: email,
170
- onChange: (e) => setEmail(e.target.value),
176
+ onChange: (e) => {
177
+ setEmail(e.target.value);
178
+ if (error) setError(null);
179
+ },
171
180
  onKeyDown: handleKeyDown,
172
181
  placeholder: "hello@gmail.com",
173
182
  autoFocus: true,
@@ -224,12 +233,20 @@ function formatClassTime(cls) {
224
233
  const em = endTotal % 60;
225
234
  return `${h}:${cls.startTime.minutes}-${eh}:${String(em).padStart(2, "0")}`;
226
235
  }
236
+ function formatBookingLabel(cls) {
237
+ const price = cls.price;
238
+ const isFree = price === null || price === void 0 || `${price}` === "" || `${price}` === "0";
239
+ return isFree ? "Book for free" : `Book for ${cls.currency ?? ""} ${price}`.trim();
240
+ }
227
241
  function getBookingsOnDate(cls, date) {
228
242
  if (!cls.bookings?.length) return 0;
229
- const ds = date.toISOString().split("T")[0];
243
+ const target = new Date(date);
244
+ target.setHours(0, 0, 0, 0);
230
245
  return cls.bookings.filter((b) => {
231
246
  try {
232
- return new Date(b.classDate).toISOString().split("T")[0] === ds;
247
+ const bookingDate = new Date(b.classDate);
248
+ bookingDate.setHours(0, 0, 0, 0);
249
+ return bookingDate.getTime() === target.getTime();
233
250
  } catch {
234
251
  return false;
235
252
  }
@@ -494,7 +511,7 @@ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal
494
511
  }
495
512
  )
496
513
  ] }),
497
- /* @__PURE__ */ jsx2("div", { className: "sm:hidden flex flex-col gap-px", children: isFull ? /* @__PURE__ */ jsx2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
514
+ cls.showAvailability !== false && /* @__PURE__ */ jsx2("div", { className: "sm:hidden flex flex-col gap-px", children: isFull ? /* @__PURE__ */ jsx2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
498
515
  /* @__PURE__ */ jsxs2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
499
516
  available,
500
517
  "/",
@@ -506,9 +523,17 @@ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal
506
523
  /* @__PURE__ */ jsxs2("div", { className: "flex flex-1 flex-col gap-2 min-w-0 sm:contents", children: [
507
524
  /* @__PURE__ */ jsxs2("div", { className: "flex-1 flex flex-col gap-2 min-w-0", children: [
508
525
  /* @__PURE__ */ jsx2("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block truncate", children: cls.name }),
509
- cls.hostName && /* @__PURE__ */ jsx2("span", { className: "font-body text-base font-normal text-(--color-dark,#200C02) opacity-80 block truncate", children: cls.hostName })
526
+ cls.hostName && /* @__PURE__ */ jsx2("span", { className: "font-body text-base font-normal text-(--color-dark,#200C02) opacity-80 block truncate", children: cls.hostName }),
527
+ cls.description && /* @__PURE__ */ jsx2(
528
+ "span",
529
+ {
530
+ className: "font-body text-base font-normal text-(--color-dark,#200C02) opacity-80 block",
531
+ style: { wordBreak: "break-word" },
532
+ children: cls.description
533
+ }
534
+ )
510
535
  ] }),
511
- /* @__PURE__ */ jsx2("div", { className: "hidden sm:flex w-14 shrink-0 flex-col gap-px", children: isFull ? /* @__PURE__ */ jsx2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
536
+ cls.showAvailability !== false && /* @__PURE__ */ jsx2("div", { className: "hidden sm:flex w-14 shrink-0 flex-col gap-px", children: isFull ? /* @__PURE__ */ jsx2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
512
537
  /* @__PURE__ */ jsxs2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
513
538
  available,
514
539
  "/",
@@ -533,7 +558,7 @@ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal
533
558
  if (!cls.id) return;
534
559
  onOpenModal?.(isFull ? "waitlist" : "book", cls.id, selectedDate);
535
560
  },
536
- children: isFull ? "Join Waitlist" : "Book Now"
561
+ children: isFull ? "Join Waitlist" : formatBookingLabel(cls)
537
562
  }
538
563
  )
539
564
  ] })
@@ -575,6 +600,17 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
575
600
  const [isHovered, setIsHovered] = useState2(false);
576
601
  const [modalState, setModalState] = useState2(null);
577
602
  const switchScheduleIdRef = useRef(null);
603
+ const liveScheduleIdRef = useRef(null);
604
+ const refetchLiveSchedule = useCallback(async () => {
605
+ const id = liveScheduleIdRef.current;
606
+ if (!id) return;
607
+ try {
608
+ const res = await fetch(`${API_URL}/api/schedule/id/${id}`);
609
+ const data = await res.json();
610
+ if (data?.id) setSchedule(data);
611
+ } catch {
612
+ }
613
+ }, []);
578
614
  const dates = useMemo(() => {
579
615
  const today = /* @__PURE__ */ new Date();
580
616
  today.setHours(0, 0, 0, 0);
@@ -594,6 +630,18 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
594
630
  }
595
631
  }
596
632
  }, [schedule, dates]);
633
+ useEffect(() => {
634
+ if (typeof window === "undefined") return;
635
+ const onVisible = () => {
636
+ if (!document.hidden) void refetchLiveSchedule();
637
+ };
638
+ window.addEventListener("focus", refetchLiveSchedule);
639
+ document.addEventListener("visibilitychange", onVisible);
640
+ return () => {
641
+ window.removeEventListener("focus", refetchLiveSchedule);
642
+ document.removeEventListener("visibilitychange", onVisible);
643
+ };
644
+ }, [refetchLiveSchedule]);
597
645
  useEffect(() => {
598
646
  if (typeof window === "undefined") return;
599
647
  const isInEditor = window.self !== window.top;
@@ -655,7 +703,7 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
655
703
  setLoading(false);
656
704
  return;
657
705
  }
658
- ;
706
+ liveScheduleIdRef.current = effectiveId;
659
707
  (async () => {
660
708
  try {
661
709
  const res = await fetch(`${API_URL}/api/schedule/id/${effectiveId}`);
@@ -712,18 +760,14 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
712
760
  const data = await res.json().catch(() => ({}));
713
761
  throw new Error(data?.message ?? "Something went wrong. Please try again.");
714
762
  }
763
+ await refetchLiveSchedule();
715
764
  };
716
765
  const handleReplaceSchedule = () => {
717
766
  setIsHovered(false);
718
- if (schedule) {
719
- window.parent.postMessage({
720
- type: "ow:schedule-connected",
721
- schedule: { id: schedule.id, name: schedule.name },
722
- insertAfter
723
- }, "*");
724
- } else {
725
- window.parent.postMessage({ type: "ow:scheduling-not-connected", insertAfter }, "*");
726
- }
767
+ window.parent.postMessage(
768
+ { type: "ow:scheduling-not-connected", insertAfter, currentScheduleId: schedule?.id },
769
+ "*"
770
+ );
727
771
  };
728
772
  if (!inEditor && !loading && !schedule) return null;
729
773
  const sectionId = `scheduling-${insertAfter}`;
@@ -766,7 +810,19 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
766
810
  ),
767
811
  /* @__PURE__ */ jsxs2("div", { className: "max-w-[1280px] mx-auto flex flex-col items-center gap-16", children: [
768
812
  /* @__PURE__ */ jsxs2("div", { className: "flex flex-col items-center gap-4", children: [
769
- /* @__PURE__ */ jsx2("h2", { className: "font-display text-4xl sm:text-5xl font-bold text-center m-0 text-(--color-dark,#200C02)", children: schedule?.name ?? "Book an appointment" }),
813
+ /* @__PURE__ */ jsx2(
814
+ "h2",
815
+ {
816
+ className: "font-display text-center m-0 text-(--color-dark,#200C02)",
817
+ style: {
818
+ fontSize: "var(--fs-section-h2, clamp(40px, 4.4vw, 64px))",
819
+ fontWeight: "var(--font-weight-heading, 400)",
820
+ lineHeight: 1.05,
821
+ letterSpacing: "-0.02em"
822
+ },
823
+ children: schedule?.name ?? "Book an appointment"
824
+ }
825
+ ),
770
826
  schedule?.description && /* @__PURE__ */ jsx2("p", { className: "font-body text-body text-center m-0 text-(--color-accent,#A89B83)", children: schedule.description })
771
827
  ] }),
772
828
  /* @__PURE__ */ jsxs2("div", { className: "w-full flex flex-col items-end gap-3", children: [
@@ -5658,7 +5714,7 @@ function LinkEditorPanel({ state, onClose }) {
5658
5714
  }
5659
5715
 
5660
5716
  // src/ui/link-modal/SectionPickerOverlay.tsx
5661
- import { useCallback, useEffect as useEffect4, useMemo as useMemo2, useRef as useRef5, useState as useState4 } from "react";
5717
+ import { useCallback as useCallback2, useEffect as useEffect4, useMemo as useMemo2, useRef as useRef5, useState as useState4 } from "react";
5662
5718
  import { createPortal } from "react-dom";
5663
5719
  import { ArrowLeft, Check } from "lucide-react";
5664
5720
  import { usePathname, useRouter } from "next/navigation";
@@ -5789,7 +5845,7 @@ function SectionPickerOverlay({
5789
5845
  );
5790
5846
  sectionIdsRef.current = sectionIds;
5791
5847
  const rects = useSectionRects(sectionIdsKey, isOnTargetPage);
5792
- const applyHoverAt = useCallback(
5848
+ const applyHoverAt = useCallback2(
5793
5849
  (x, y, liveRects) => {
5794
5850
  const ids = sectionIdsRef.current;
5795
5851
  const map = liveRects ?? readSectionRects(ids);
@@ -5869,7 +5925,7 @@ function SectionPickerOverlay({
5869
5925
  window.removeEventListener("message", onPointerSync);
5870
5926
  };
5871
5927
  }, [rects, applyHoverAt]);
5872
- const handleSelect = useCallback(
5928
+ const handleSelect = useCallback2(
5873
5929
  (section) => {
5874
5930
  if (selectedId) return;
5875
5931
  setSelectedId(section.id);
@@ -6000,7 +6056,7 @@ function SectionPickerOverlay({
6000
6056
  }
6001
6057
 
6002
6058
  // src/ui/link-modal/useLinkModalState.ts
6003
- import { useCallback as useCallback2, useEffect as useEffect5, useMemo as useMemo3, useState as useState5 } from "react";
6059
+ import { useCallback as useCallback3, useEffect as useEffect5, useMemo as useMemo3, useState as useState5 } from "react";
6004
6060
  function useLinkModalState({
6005
6061
  open,
6006
6062
  mode,
@@ -6019,7 +6075,7 @@ function useLinkModalState({
6019
6075
  const [step, setStep] = useState5("input");
6020
6076
  const [dropdownOpen, setDropdownOpen] = useState5(false);
6021
6077
  const [urlError, setUrlError] = useState5("");
6022
- const reset = useCallback2(() => {
6078
+ const reset = useCallback3(() => {
6023
6079
  setSearchValue("");
6024
6080
  setSelectedPage(null);
6025
6081
  setSelectedSection(null);
@@ -7367,7 +7423,7 @@ function hitTestNavDropSlot(clientX, clientY, draggedHrefKey) {
7367
7423
  }
7368
7424
 
7369
7425
  // src/useNavItemDrag.ts
7370
- import { useCallback as useCallback3, useEffect as useEffect7, useRef as useRef6, useState as useState6 } from "react";
7426
+ import { useCallback as useCallback4, useEffect as useEffect7, useRef as useRef6, useState as useState6 } from "react";
7371
7427
  function useNavItemDrag({
7372
7428
  isEditMode,
7373
7429
  editContentRef,
@@ -7396,7 +7452,7 @@ function useNavItemDrag({
7396
7452
  const [navDropSlots, setNavDropSlots] = useState6([]);
7397
7453
  const [activeNavDropIndex, setActiveNavDropIndex] = useState6(null);
7398
7454
  const navPointerDragRef = useRef6(null);
7399
- const clearNavDragVisuals = useCallback3(() => {
7455
+ const clearNavDragVisuals = useCallback4(() => {
7400
7456
  navDragRef.current = null;
7401
7457
  setNavDropSlots([]);
7402
7458
  setActiveNavDropIndex(null);
@@ -7405,7 +7461,7 @@ function useNavItemDrag({
7405
7461
  setIsItemDragging(false);
7406
7462
  unlockItemDragInteraction();
7407
7463
  }, [setDraggedItemRect, setIsItemDragging, setSiblingHintRects]);
7408
- const refreshNavDragVisuals = useCallback3(
7464
+ const refreshNavDragVisuals = useCallback4(
7409
7465
  (session, activeSlot, clientX, clientY) => {
7410
7466
  setDraggedItemRect(session.draggedEl.getBoundingClientRect());
7411
7467
  if (typeof clientX === "number" && typeof clientY === "number") {
@@ -7429,7 +7485,7 @@ function useNavItemDrag({
7429
7485
  });
7430
7486
  const beginNavDragRef = useRef6(() => {
7431
7487
  });
7432
- const beginNavDrag = useCallback3(
7488
+ const beginNavDrag = useCallback4(
7433
7489
  (session) => {
7434
7490
  const rect = session.draggedEl.getBoundingClientRect();
7435
7491
  session.lastClientX = session.lastClientX || rect.left + rect.width / 2;
@@ -7456,7 +7512,7 @@ function useNavItemDrag({
7456
7512
  ]
7457
7513
  );
7458
7514
  beginNavDragRef.current = beginNavDrag;
7459
- const commitNavDrag = useCallback3(
7515
+ const commitNavDrag = useCallback4(
7460
7516
  (clientX, clientY) => {
7461
7517
  const session = navDragRef.current;
7462
7518
  if (!session) {
@@ -7517,7 +7573,7 @@ function useNavItemDrag({
7517
7573
  [clearNavDragVisuals, deselectRef, editContentRef, postToParentRef, selectRef]
7518
7574
  );
7519
7575
  commitNavDragRef.current = commitNavDrag;
7520
- const startNavLinkDrag = useCallback3(
7576
+ const startNavLinkDrag = useCallback4(
7521
7577
  (anchor, clientX, clientY, wasSelected) => {
7522
7578
  if (footerDragRef.current) return false;
7523
7579
  const hrefKey = anchor.getAttribute("data-ohw-href-key");
@@ -7535,7 +7591,7 @@ function useNavItemDrag({
7535
7591
  },
7536
7592
  [beginNavDrag, footerDragRef, isDragHandleDisabled2, isNavbarButton3]
7537
7593
  );
7538
- const onNavDragOver = useCallback3(
7594
+ const onNavDragOver = useCallback4(
7539
7595
  (e) => {
7540
7596
  const session = navDragRef.current;
7541
7597
  if (!session) return false;
@@ -7672,7 +7728,7 @@ function useNavItemDrag({
7672
7728
  setLinkPopover,
7673
7729
  suppressNextClickRef
7674
7730
  ]);
7675
- const armNavPressFromChrome = useCallback3(
7731
+ const armNavPressFromChrome = useCallback4(
7676
7732
  (selected, clientX, clientY, pointerId) => {
7677
7733
  const hrefKey = selected.getAttribute("data-ohw-href-key");
7678
7734
  if (!hrefKey || !isNavbarHrefKey(hrefKey)) return false;
@@ -9062,7 +9118,7 @@ function OhhwellsBridge() {
9062
9118
  const subdomainFromQuery = searchParams.get("subdomain");
9063
9119
  const subdomain = resolveSubdomain(subdomainFromQuery);
9064
9120
  useLinkHrefGuardian(pathname, subdomain, isEditMode);
9065
- const postToParent2 = useCallback4((data) => {
9121
+ const postToParent2 = useCallback5((data) => {
9066
9122
  if (typeof window !== "undefined" && window.parent !== window) {
9067
9123
  window.parent.postMessage(data, "*");
9068
9124
  }
@@ -9079,7 +9135,7 @@ function OhhwellsBridge() {
9079
9135
  const parentScrollRef = useRef7(null);
9080
9136
  const visibleViewportRef = useRef7(null);
9081
9137
  const [dialogPortalContainer, setDialogPortalContainer] = useState8(null);
9082
- const attachVisibleViewport = useCallback4((node) => {
9138
+ const attachVisibleViewport = useCallback5((node) => {
9083
9139
  visibleViewportRef.current = node;
9084
9140
  setDialogPortalContainer(node);
9085
9141
  if (node) applyVisibleViewport(node, parentScrollRef.current);
@@ -9193,7 +9249,7 @@ function OhhwellsBridge() {
9193
9249
  const bumpLinkPopoverGrace = () => {
9194
9250
  linkPopoverGraceUntilRef.current = Date.now() + 350;
9195
9251
  };
9196
- const runSectionsPrefetch = useCallback4((pages) => {
9252
+ const runSectionsPrefetch = useCallback5((pages) => {
9197
9253
  if (!isEditMode || shouldUseDevFixtures() || pages.length === 0) return;
9198
9254
  const gen = ++sectionsPrefetchGenRef.current;
9199
9255
  const paths = pages.map((p) => p.path);
@@ -9319,10 +9375,10 @@ function OhhwellsBridge() {
9319
9375
  vvp.removeEventListener("resize", update);
9320
9376
  };
9321
9377
  }, []);
9322
- const refreshStateRules = useCallback4(() => {
9378
+ const refreshStateRules = useCallback5(() => {
9323
9379
  editStylesRef.current?.forceHover && (editStylesRef.current.forceHover.textContent = collectStateRules());
9324
9380
  }, []);
9325
- const processConfigRequest = useCallback4((insertAfterVal) => {
9381
+ const processConfigRequest = useCallback5((insertAfterVal) => {
9326
9382
  const tracker = getSectionsTracker();
9327
9383
  let entries = [];
9328
9384
  try {
@@ -9345,7 +9401,7 @@ function OhhwellsBridge() {
9345
9401
  }
9346
9402
  window.postMessage({ type: "ow:schedule-config", insertAfter: insertAfterVal, scheduleId: null }, "*");
9347
9403
  }, [isEditMode]);
9348
- const deactivate = useCallback4(() => {
9404
+ const deactivate = useCallback5(() => {
9349
9405
  const el = activeElRef.current;
9350
9406
  if (!el) return;
9351
9407
  const key = el.dataset.ohwKey;
@@ -9377,12 +9433,12 @@ function OhhwellsBridge() {
9377
9433
  setToolbarShowEditLink(false);
9378
9434
  postToParent2({ type: "ow:exit-edit" });
9379
9435
  }, [postToParent2]);
9380
- const clearSelectedAttr = useCallback4(() => {
9436
+ const clearSelectedAttr = useCallback5(() => {
9381
9437
  document.querySelectorAll("[data-ohw-selected]").forEach((el) => {
9382
9438
  el.removeAttribute("data-ohw-selected");
9383
9439
  });
9384
9440
  }, []);
9385
- const deselect = useCallback4(() => {
9441
+ const deselect = useCallback5(() => {
9386
9442
  clearSelectedAttr();
9387
9443
  selectedElRef.current = null;
9388
9444
  selectedHrefKeyRef.current = null;
@@ -9402,11 +9458,11 @@ function OhhwellsBridge() {
9402
9458
  setToolbarVariant("none");
9403
9459
  }
9404
9460
  }, [clearSelectedAttr]);
9405
- const markSelected = useCallback4((el) => {
9461
+ const markSelected = useCallback5((el) => {
9406
9462
  clearSelectedAttr();
9407
9463
  el.setAttribute("data-ohw-selected", "");
9408
9464
  }, [clearSelectedAttr]);
9409
- const resolveHrefKeyElement = useCallback4((hrefKey) => {
9465
+ const resolveHrefKeyElement = useCallback5((hrefKey) => {
9410
9466
  if (isFooterHrefKey(hrefKey)) {
9411
9467
  return document.querySelector(
9412
9468
  `footer [data-ohw-href-key="${CSS.escape(hrefKey)}"]`
@@ -9421,7 +9477,7 @@ function OhhwellsBridge() {
9421
9477
  `[data-ohw-href-key="${CSS.escape(hrefKey)}"]`
9422
9478
  );
9423
9479
  }, []);
9424
- const resyncSelectedNavigationItem = useCallback4(() => {
9480
+ const resyncSelectedNavigationItem = useCallback5(() => {
9425
9481
  const hrefKey = selectedHrefKeyRef.current;
9426
9482
  if (hrefKey) {
9427
9483
  const link = resolveHrefKeyElement(hrefKey);
@@ -9449,7 +9505,7 @@ function OhhwellsBridge() {
9449
9505
  );
9450
9506
  }
9451
9507
  }, [resolveHrefKeyElement]);
9452
- const reselectNavigationItem = useCallback4((navAnchor) => {
9508
+ const reselectNavigationItem = useCallback5((navAnchor) => {
9453
9509
  selectedElRef.current = navAnchor;
9454
9510
  selectedHrefKeyRef.current = navAnchor.getAttribute("data-ohw-href-key");
9455
9511
  selectedFooterColAttrRef.current = null;
@@ -9464,7 +9520,7 @@ function OhhwellsBridge() {
9464
9520
  setToolbarShowEditLink(false);
9465
9521
  setActiveCommands(/* @__PURE__ */ new Set());
9466
9522
  }, [markSelected]);
9467
- const commitNavigationTextEdit = useCallback4((navAnchor) => {
9523
+ const commitNavigationTextEdit = useCallback5((navAnchor) => {
9468
9524
  const el = activeElRef.current;
9469
9525
  if (!el) return;
9470
9526
  const key = el.dataset.ohwKey;
@@ -9491,7 +9547,7 @@ function OhhwellsBridge() {
9491
9547
  postToParent2({ type: "ow:exit-edit" });
9492
9548
  reselectNavigationItem(navAnchor);
9493
9549
  }, [postToParent2, reselectNavigationItem]);
9494
- const handleAddTopLevelNavItem = useCallback4(() => {
9550
+ const handleAddTopLevelNavItem = useCallback5(() => {
9495
9551
  const items = listNavbarItems();
9496
9552
  addNavAfterAnchorRef.current = items[items.length - 1] ?? null;
9497
9553
  deselectRef.current();
@@ -9503,7 +9559,7 @@ function OhhwellsBridge() {
9503
9559
  intent: "add-nav"
9504
9560
  });
9505
9561
  }, []);
9506
- const clearFooterDragVisuals = useCallback4(() => {
9562
+ const clearFooterDragVisuals = useCallback5(() => {
9507
9563
  footerDragRef.current = null;
9508
9564
  setSiblingHintRects([]);
9509
9565
  setFooterDropSlots([]);
@@ -9512,7 +9568,7 @@ function OhhwellsBridge() {
9512
9568
  setIsItemDragging(false);
9513
9569
  unlockFooterDragInteraction();
9514
9570
  }, []);
9515
- const refreshFooterDragVisuals = useCallback4((session, activeSlot, clientX, clientY) => {
9571
+ const refreshFooterDragVisuals = useCallback5((session, activeSlot, clientX, clientY) => {
9516
9572
  const dragged = session.draggedEl;
9517
9573
  setDraggedItemRect(dragged.getBoundingClientRect());
9518
9574
  if (typeof clientX === "number" && typeof clientY === "number") {
@@ -9543,7 +9599,7 @@ function OhhwellsBridge() {
9543
9599
  });
9544
9600
  const beginFooterDragRef = useRef7(() => {
9545
9601
  });
9546
- const beginFooterDrag = useCallback4(
9602
+ const beginFooterDrag = useCallback5(
9547
9603
  (session) => {
9548
9604
  const rect = session.draggedEl.getBoundingClientRect();
9549
9605
  session.lastClientX = session.lastClientX || rect.left + rect.width / 2;
@@ -9563,7 +9619,7 @@ function OhhwellsBridge() {
9563
9619
  [refreshFooterDragVisuals]
9564
9620
  );
9565
9621
  beginFooterDragRef.current = beginFooterDrag;
9566
- const commitFooterDrag = useCallback4(
9622
+ const commitFooterDrag = useCallback5(
9567
9623
  (clientX, clientY) => {
9568
9624
  const session = footerDragRef.current;
9569
9625
  if (!session) {
@@ -9667,7 +9723,7 @@ function OhhwellsBridge() {
9667
9723
  [clearFooterDragVisuals, resolveHrefKeyElement, resyncSelectedNavigationItem]
9668
9724
  );
9669
9725
  commitFooterDragRef.current = commitFooterDrag;
9670
- const startFooterLinkDrag = useCallback4(
9726
+ const startFooterLinkDrag = useCallback5(
9671
9727
  (anchor, clientX, clientY, wasSelected) => {
9672
9728
  const hrefKey = anchor.getAttribute("data-ohw-href-key");
9673
9729
  if (!hrefKey || !isFooterHrefKey(hrefKey)) return false;
@@ -9688,7 +9744,7 @@ function OhhwellsBridge() {
9688
9744
  },
9689
9745
  [beginFooterDrag]
9690
9746
  );
9691
- const startFooterColumnDrag = useCallback4(
9747
+ const startFooterColumnDrag = useCallback5(
9692
9748
  (columnEl, clientX, clientY, wasSelected) => {
9693
9749
  const columns = listFooterColumns();
9694
9750
  const idx = columns.indexOf(columnEl);
@@ -9708,7 +9764,7 @@ function OhhwellsBridge() {
9708
9764
  },
9709
9765
  [beginFooterDrag]
9710
9766
  );
9711
- const handleItemDragStart = useCallback4(
9767
+ const handleItemDragStart = useCallback5(
9712
9768
  (e) => {
9713
9769
  const selected = selectedElRef.current;
9714
9770
  if (!selected) {
@@ -9728,7 +9784,7 @@ function OhhwellsBridge() {
9728
9784
  },
9729
9785
  [startFooterColumnDrag, startFooterLinkDrag, startNavLinkDrag]
9730
9786
  );
9731
- const handleItemDragEnd = useCallback4(
9787
+ const handleItemDragEnd = useCallback5(
9732
9788
  (e) => {
9733
9789
  if (footerDragRef.current) {
9734
9790
  const x = e?.clientX;
@@ -9754,7 +9810,7 @@ function OhhwellsBridge() {
9754
9810
  },
9755
9811
  [commitFooterDrag, commitNavDrag, navDragRef]
9756
9812
  );
9757
- const handleItemChromePointerDown = useCallback4((e) => {
9813
+ const handleItemChromePointerDown = useCallback5((e) => {
9758
9814
  if (e.button !== 0) return;
9759
9815
  const selected = selectedElRef.current;
9760
9816
  if (!selected) return;
@@ -9785,7 +9841,7 @@ function OhhwellsBridge() {
9785
9841
  }
9786
9842
  if (armNavPressFromChrome(selected, e.clientX, e.clientY, e.pointerId)) return;
9787
9843
  }, [armNavPressFromChrome]);
9788
- const handleItemChromeClick = useCallback4((clientX, clientY) => {
9844
+ const handleItemChromeClick = useCallback5((clientX, clientY) => {
9789
9845
  if (suppressNextClickRef.current || Date.now() < suppressClickUntilRef.current) {
9790
9846
  suppressNextClickRef.current = false;
9791
9847
  return;
@@ -9798,7 +9854,7 @@ function OhhwellsBridge() {
9798
9854
  }, []);
9799
9855
  reselectNavigationItemRef.current = reselectNavigationItem;
9800
9856
  commitNavigationTextEditRef.current = commitNavigationTextEdit;
9801
- const select = useCallback4((anchor) => {
9857
+ const select = useCallback5((anchor) => {
9802
9858
  if (!isNavigationItem(anchor)) return;
9803
9859
  if (activeElRef.current) deactivate();
9804
9860
  selectedElRef.current = anchor;
@@ -9824,7 +9880,7 @@ function OhhwellsBridge() {
9824
9880
  setToolbarShowEditLink(false);
9825
9881
  setActiveCommands(/* @__PURE__ */ new Set());
9826
9882
  }, [deactivate, markSelected]);
9827
- const selectFrame = useCallback4((el) => {
9883
+ const selectFrame = useCallback5((el) => {
9828
9884
  if (!isNavigationContainer(el)) return;
9829
9885
  if (activeElRef.current) deactivate();
9830
9886
  const isFooterColumn = el.hasAttribute("data-ohw-footer-col") || Boolean(el.closest("footer") && isInferredFooterGroup(el));
@@ -9852,7 +9908,7 @@ function OhhwellsBridge() {
9852
9908
  setToolbarShowEditLink(false);
9853
9909
  setActiveCommands(/* @__PURE__ */ new Set());
9854
9910
  }, [deactivate, markSelected]);
9855
- const activate = useCallback4((el, options) => {
9911
+ const activate = useCallback5((el, options) => {
9856
9912
  if (activeElRef.current === el) return;
9857
9913
  clearSelectedAttr();
9858
9914
  selectedElRef.current = null;
@@ -11995,7 +12051,7 @@ function OhhwellsBridge() {
11995
12051
  window.addEventListener("hashchange", onHashChange);
11996
12052
  return () => window.removeEventListener("hashchange", onHashChange);
11997
12053
  }, [pathname]);
11998
- const handleCommand = useCallback4((cmd) => {
12054
+ const handleCommand = useCallback5((cmd) => {
11999
12055
  const el = activeElRef.current;
12000
12056
  const selBefore = window.getSelection();
12001
12057
  let savedOffsets = null;
@@ -12031,7 +12087,7 @@ function OhhwellsBridge() {
12031
12087
  if (el) setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
12032
12088
  refreshActiveCommandsRef.current();
12033
12089
  }, []);
12034
- const handleStateChange = useCallback4((state) => {
12090
+ const handleStateChange = useCallback5((state) => {
12035
12091
  if (!activeStateElRef.current) return;
12036
12092
  const el = activeStateElRef.current;
12037
12093
  if (state === "Default") {
@@ -12044,11 +12100,11 @@ function OhhwellsBridge() {
12044
12100
  }
12045
12101
  setToggleState((prev) => prev ? { ...prev, activeState: state } : null);
12046
12102
  }, [deactivate]);
12047
- const closeLinkPopover = useCallback4(() => {
12103
+ const closeLinkPopover = useCallback5(() => {
12048
12104
  addNavAfterAnchorRef.current = null;
12049
12105
  setLinkPopover(null);
12050
12106
  }, []);
12051
- const openLinkPopoverForActive = useCallback4(() => {
12107
+ const openLinkPopoverForActive = useCallback5(() => {
12052
12108
  const hrefCtx = getHrefKeyFromElement(activeElRef.current);
12053
12109
  if (!hrefCtx) return;
12054
12110
  bumpLinkPopoverGrace();
@@ -12059,7 +12115,7 @@ function OhhwellsBridge() {
12059
12115
  });
12060
12116
  deactivate();
12061
12117
  }, [deactivate]);
12062
- const openLinkPopoverForSelected = useCallback4(() => {
12118
+ const openLinkPopoverForSelected = useCallback5(() => {
12063
12119
  const anchor = selectedElRef.current;
12064
12120
  if (!anchor) return;
12065
12121
  const key = anchor.getAttribute("data-ohw-href-key");
@@ -12072,7 +12128,7 @@ function OhhwellsBridge() {
12072
12128
  });
12073
12129
  deselect();
12074
12130
  }, [deselect]);
12075
- const handleLinkPopoverSubmit = useCallback4(
12131
+ const handleLinkPopoverSubmit = useCallback5(
12076
12132
  (target) => {
12077
12133
  const session = linkPopoverSessionRef.current;
12078
12134
  if (!session) return;
@@ -12131,19 +12187,19 @@ function OhhwellsBridge() {
12131
12187
  const showEditLink = toolbarShowEditLink;
12132
12188
  const currentSections = sectionsByPath[pathname] ?? [];
12133
12189
  linkPopoverOpenRef.current = linkPopover !== null;
12134
- const handleMediaReplace = useCallback4(
12190
+ const handleMediaReplace = useCallback5(
12135
12191
  (key) => {
12136
12192
  postToParent2({ type: "ow:image-pick", key, elementType: mediaHover?.elementType ?? "image" });
12137
12193
  },
12138
12194
  [postToParent2, mediaHover?.elementType]
12139
12195
  );
12140
- const handleEditCarousel = useCallback4(
12196
+ const handleEditCarousel = useCallback5(
12141
12197
  (key) => {
12142
12198
  postToParent2({ type: "ow:carousel-open", key, images: readCarouselValue(key) });
12143
12199
  },
12144
12200
  [postToParent2]
12145
12201
  );
12146
- const handleMediaFadeOutComplete = useCallback4((key) => {
12202
+ const handleMediaFadeOutComplete = useCallback5((key) => {
12147
12203
  setUploadingRects((prev) => {
12148
12204
  if (!(key in prev)) return prev;
12149
12205
  const next = { ...prev };
@@ -12151,7 +12207,7 @@ function OhhwellsBridge() {
12151
12207
  return next;
12152
12208
  });
12153
12209
  }, []);
12154
- const handleVideoSettingsChange = useCallback4(
12210
+ const handleVideoSettingsChange = useCallback5(
12155
12211
  (key, settings) => {
12156
12212
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
12157
12213
  const video = getVideoEl(el);