@ohhwells/bridge 0.1.45 → 0.1.46-next.109

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  // src/OhhwellsBridge.tsx
4
- import React11, { useCallback as useCallback4, useEffect as useEffect8, useLayoutEffect as useLayoutEffect3, useRef as useRef7, useState as useState7 } from "react";
4
+ import React11, { useCallback as useCallback5, useEffect as useEffect8, useLayoutEffect as useLayoutEffect3, useRef as useRef7, useState as useState7 } from "react";
5
5
  import { createRoot } from "react-dom/client";
6
6
  import { flushSync } from "react-dom";
7
7
 
@@ -48,12 +48,13 @@ function useLinkHrefGuardian(...deps) {
48
48
  }
49
49
 
50
50
  // src/ui/SchedulingWidget.tsx
51
- import { useEffect, useId, useMemo, useRef, useState as useState2 } from "react";
51
+ import { useCallback, useEffect, useId, useMemo, useRef, useState as useState2 } from "react";
52
52
 
53
53
  // src/ui/EmailCaptureModal.tsx
54
54
  import { useState } from "react";
55
55
  import { Dialog } from "radix-ui";
56
56
  import { jsx, jsxs } from "react/jsx-runtime";
57
+ var isValidEmail = (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
57
58
  function Spinner() {
58
59
  return /* @__PURE__ */ jsxs(
59
60
  "svg",
@@ -87,12 +88,17 @@ function EmailCaptureModal({ title, subtitle, onSubmit, onClose }) {
87
88
  const [error, setError] = useState(null);
88
89
  const isBook = title === "Confirm your spot";
89
90
  const handleSubmit = async () => {
90
- if (!email.trim()) return;
91
+ const trimmed = email.trim();
92
+ if (!trimmed) return;
93
+ if (!isValidEmail(trimmed)) {
94
+ setError("Please enter a valid email address.");
95
+ return;
96
+ }
91
97
  setLoading(true);
92
98
  setError(null);
93
99
  try {
94
- await onSubmit(email.trim());
95
- setSubmittedEmail(email.trim());
100
+ await onSubmit(trimmed);
101
+ setSubmittedEmail(trimmed);
96
102
  setSuccess(true);
97
103
  } catch (e) {
98
104
  setError(e instanceof Error ? e.message : "Something went wrong. Please try again.");
@@ -167,7 +173,10 @@ function EmailCaptureModal({ title, subtitle, onSubmit, onClose }) {
167
173
  {
168
174
  type: "email",
169
175
  value: email,
170
- onChange: (e) => setEmail(e.target.value),
176
+ onChange: (e) => {
177
+ setEmail(e.target.value);
178
+ if (error) setError(null);
179
+ },
171
180
  onKeyDown: handleKeyDown,
172
181
  placeholder: "hello@gmail.com",
173
182
  autoFocus: true,
@@ -224,12 +233,20 @@ function formatClassTime(cls) {
224
233
  const em = endTotal % 60;
225
234
  return `${h}:${cls.startTime.minutes}-${eh}:${String(em).padStart(2, "0")}`;
226
235
  }
236
+ function formatBookingLabel(cls) {
237
+ const price = cls.price;
238
+ const isFree = price === null || price === void 0 || `${price}` === "" || `${price}` === "0";
239
+ return isFree ? "Book for free" : `Book for ${cls.currency ?? ""} ${price}`.trim();
240
+ }
227
241
  function getBookingsOnDate(cls, date) {
228
242
  if (!cls.bookings?.length) return 0;
229
- const ds = date.toISOString().split("T")[0];
243
+ const target = new Date(date);
244
+ target.setHours(0, 0, 0, 0);
230
245
  return cls.bookings.filter((b) => {
231
246
  try {
232
- return new Date(b.classDate).toISOString().split("T")[0] === ds;
247
+ const bookingDate = new Date(b.classDate);
248
+ bookingDate.setHours(0, 0, 0, 0);
249
+ return bookingDate.getTime() === target.getTime();
233
250
  } catch {
234
251
  return false;
235
252
  }
@@ -494,7 +511,7 @@ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal
494
511
  }
495
512
  )
496
513
  ] }),
497
- /* @__PURE__ */ jsx2("div", { className: "sm:hidden flex flex-col gap-px", children: isFull ? /* @__PURE__ */ jsx2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
514
+ cls.showAvailability !== false && /* @__PURE__ */ jsx2("div", { className: "sm:hidden flex flex-col gap-px", children: isFull ? /* @__PURE__ */ jsx2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
498
515
  /* @__PURE__ */ jsxs2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
499
516
  available,
500
517
  "/",
@@ -506,9 +523,17 @@ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal
506
523
  /* @__PURE__ */ jsxs2("div", { className: "flex flex-1 flex-col gap-2 min-w-0 sm:contents", children: [
507
524
  /* @__PURE__ */ jsxs2("div", { className: "flex-1 flex flex-col gap-2 min-w-0", children: [
508
525
  /* @__PURE__ */ jsx2("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block truncate", children: cls.name }),
509
- cls.hostName && /* @__PURE__ */ jsx2("span", { className: "font-body text-base font-normal text-(--color-dark,#200C02) opacity-80 block truncate", children: cls.hostName })
526
+ cls.hostName && /* @__PURE__ */ jsx2("span", { className: "font-body text-base font-normal text-(--color-dark,#200C02) opacity-80 block truncate", children: cls.hostName }),
527
+ cls.description && /* @__PURE__ */ jsx2(
528
+ "span",
529
+ {
530
+ className: "font-body text-base font-normal text-(--color-dark,#200C02) opacity-80 block",
531
+ style: { wordBreak: "break-word" },
532
+ children: cls.description
533
+ }
534
+ )
510
535
  ] }),
511
- /* @__PURE__ */ jsx2("div", { className: "hidden sm:flex w-14 shrink-0 flex-col gap-px", children: isFull ? /* @__PURE__ */ jsx2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
536
+ cls.showAvailability !== false && /* @__PURE__ */ jsx2("div", { className: "hidden sm:flex w-14 shrink-0 flex-col gap-px", children: isFull ? /* @__PURE__ */ jsx2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
512
537
  /* @__PURE__ */ jsxs2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
513
538
  available,
514
539
  "/",
@@ -533,7 +558,7 @@ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal
533
558
  if (!cls.id) return;
534
559
  onOpenModal?.(isFull ? "waitlist" : "book", cls.id, selectedDate);
535
560
  },
536
- children: isFull ? "Join Waitlist" : "Book Now"
561
+ children: isFull ? "Join Waitlist" : formatBookingLabel(cls)
537
562
  }
538
563
  )
539
564
  ] })
@@ -575,6 +600,17 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
575
600
  const [isHovered, setIsHovered] = useState2(false);
576
601
  const [modalState, setModalState] = useState2(null);
577
602
  const switchScheduleIdRef = useRef(null);
603
+ const liveScheduleIdRef = useRef(null);
604
+ const refetchLiveSchedule = useCallback(async () => {
605
+ const id = liveScheduleIdRef.current;
606
+ if (!id) return;
607
+ try {
608
+ const res = await fetch(`${API_URL}/api/schedule/id/${id}`);
609
+ const data = await res.json();
610
+ if (data?.id) setSchedule(data);
611
+ } catch {
612
+ }
613
+ }, []);
578
614
  const dates = useMemo(() => {
579
615
  const today = /* @__PURE__ */ new Date();
580
616
  today.setHours(0, 0, 0, 0);
@@ -594,6 +630,18 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
594
630
  }
595
631
  }
596
632
  }, [schedule, dates]);
633
+ useEffect(() => {
634
+ if (typeof window === "undefined") return;
635
+ const onVisible = () => {
636
+ if (!document.hidden) void refetchLiveSchedule();
637
+ };
638
+ window.addEventListener("focus", refetchLiveSchedule);
639
+ document.addEventListener("visibilitychange", onVisible);
640
+ return () => {
641
+ window.removeEventListener("focus", refetchLiveSchedule);
642
+ document.removeEventListener("visibilitychange", onVisible);
643
+ };
644
+ }, [refetchLiveSchedule]);
597
645
  useEffect(() => {
598
646
  if (typeof window === "undefined") return;
599
647
  const isInEditor = window.self !== window.top;
@@ -655,7 +703,7 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
655
703
  setLoading(false);
656
704
  return;
657
705
  }
658
- ;
706
+ liveScheduleIdRef.current = effectiveId;
659
707
  (async () => {
660
708
  try {
661
709
  const res = await fetch(`${API_URL}/api/schedule/id/${effectiveId}`);
@@ -712,18 +760,14 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
712
760
  const data = await res.json().catch(() => ({}));
713
761
  throw new Error(data?.message ?? "Something went wrong. Please try again.");
714
762
  }
763
+ await refetchLiveSchedule();
715
764
  };
716
765
  const handleReplaceSchedule = () => {
717
766
  setIsHovered(false);
718
- if (schedule) {
719
- window.parent.postMessage({
720
- type: "ow:schedule-connected",
721
- schedule: { id: schedule.id, name: schedule.name },
722
- insertAfter
723
- }, "*");
724
- } else {
725
- window.parent.postMessage({ type: "ow:scheduling-not-connected", insertAfter }, "*");
726
- }
767
+ window.parent.postMessage(
768
+ { type: "ow:scheduling-not-connected", insertAfter, currentScheduleId: schedule?.id },
769
+ "*"
770
+ );
727
771
  };
728
772
  if (!inEditor && !loading && !schedule) return null;
729
773
  const sectionId = `scheduling-${insertAfter}`;
@@ -766,7 +810,19 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
766
810
  ),
767
811
  /* @__PURE__ */ jsxs2("div", { className: "max-w-[1280px] mx-auto flex flex-col items-center gap-16", children: [
768
812
  /* @__PURE__ */ jsxs2("div", { className: "flex flex-col items-center gap-4", children: [
769
- /* @__PURE__ */ jsx2("h2", { className: "font-display text-4xl sm:text-5xl font-bold text-center m-0 text-(--color-dark,#200C02)", children: schedule?.name ?? "Book an appointment" }),
813
+ /* @__PURE__ */ jsx2(
814
+ "h2",
815
+ {
816
+ className: "font-display text-center m-0 text-(--color-dark,#200C02)",
817
+ style: {
818
+ fontSize: "var(--fs-section-h2, clamp(40px, 4.4vw, 64px))",
819
+ fontWeight: "var(--font-weight-heading, 400)",
820
+ lineHeight: 1.05,
821
+ letterSpacing: "-0.02em"
822
+ },
823
+ children: schedule?.name ?? "Book an appointment"
824
+ }
825
+ ),
770
826
  schedule?.description && /* @__PURE__ */ jsx2("p", { className: "font-body text-body text-center m-0 text-(--color-accent,#A89B83)", children: schedule.description })
771
827
  ] }),
772
828
  /* @__PURE__ */ jsxs2("div", { className: "w-full flex flex-col items-end gap-3", children: [
@@ -4826,7 +4882,11 @@ function MediaOverlay({
4826
4882
  variant: "outline",
4827
4883
  size: "sm",
4828
4884
  className: "gap-1.5 cursor-pointer hover:bg-background",
4829
- style: OVERLAY_BUTTON_STYLE,
4885
+ style: {
4886
+ ...OVERLAY_BUTTON_STYLE,
4887
+ // Don't let Replace steal clicks when text (e.g. nav) sits on top of this media.
4888
+ pointerEvents: hover.hasTextOverlap ? "none" : "auto"
4889
+ },
4830
4890
  onMouseDown: (e) => e.preventDefault(),
4831
4891
  onClick: (e) => {
4832
4892
  e.stopPropagation();
@@ -5603,7 +5663,7 @@ function LinkEditorPanel({ state, onClose }) {
5603
5663
  }
5604
5664
 
5605
5665
  // src/ui/link-modal/SectionPickerOverlay.tsx
5606
- import { useCallback, useEffect as useEffect4, useMemo as useMemo2, useRef as useRef5, useState as useState4 } from "react";
5666
+ import { useCallback as useCallback2, useEffect as useEffect4, useMemo as useMemo2, useRef as useRef5, useState as useState4 } from "react";
5607
5667
  import { createPortal } from "react-dom";
5608
5668
  import { ArrowLeft, Check } from "lucide-react";
5609
5669
  import { usePathname, useRouter } from "next/navigation";
@@ -5734,7 +5794,7 @@ function SectionPickerOverlay({
5734
5794
  );
5735
5795
  sectionIdsRef.current = sectionIds;
5736
5796
  const rects = useSectionRects(sectionIdsKey, isOnTargetPage);
5737
- const applyHoverAt = useCallback(
5797
+ const applyHoverAt = useCallback2(
5738
5798
  (x, y, liveRects) => {
5739
5799
  const ids = sectionIdsRef.current;
5740
5800
  const map = liveRects ?? readSectionRects(ids);
@@ -5814,7 +5874,7 @@ function SectionPickerOverlay({
5814
5874
  window.removeEventListener("message", onPointerSync);
5815
5875
  };
5816
5876
  }, [rects, applyHoverAt]);
5817
- const handleSelect = useCallback(
5877
+ const handleSelect = useCallback2(
5818
5878
  (section) => {
5819
5879
  if (selectedId) return;
5820
5880
  setSelectedId(section.id);
@@ -5945,7 +6005,7 @@ function SectionPickerOverlay({
5945
6005
  }
5946
6006
 
5947
6007
  // src/ui/link-modal/useLinkModalState.ts
5948
- import { useCallback as useCallback2, useEffect as useEffect5, useMemo as useMemo3, useState as useState5 } from "react";
6008
+ import { useCallback as useCallback3, useEffect as useEffect5, useMemo as useMemo3, useState as useState5 } from "react";
5949
6009
  function useLinkModalState({
5950
6010
  open,
5951
6011
  mode,
@@ -5964,7 +6024,7 @@ function useLinkModalState({
5964
6024
  const [step, setStep] = useState5("input");
5965
6025
  const [dropdownOpen, setDropdownOpen] = useState5(false);
5966
6026
  const [urlError, setUrlError] = useState5("");
5967
- const reset = useCallback2(() => {
6027
+ const reset = useCallback3(() => {
5968
6028
  setSearchValue("");
5969
6029
  setSelectedPage(null);
5970
6030
  setSelectedSection(null);
@@ -6600,6 +6660,8 @@ function applyNavOrderToContainer(container, order) {
6600
6660
  if (current.length === expected.length && current.every((key, i) => key === expected[i])) {
6601
6661
  return;
6602
6662
  }
6663
+ const lastCurrentLinkEl = current.length > 0 ? findCounterpartByHrefKey(current[current.length - 1], container) : null;
6664
+ const anchor = lastCurrentLinkEl?.parentElement === container ? lastCurrentLinkEl.nextElementSibling : null;
6603
6665
  const seen = /* @__PURE__ */ new Set();
6604
6666
  for (const hrefKey of desired) {
6605
6667
  if (seen.has(hrefKey)) continue;
@@ -6614,7 +6676,9 @@ function applyNavOrderToContainer(container, order) {
6614
6676
  orderedEls.push(el);
6615
6677
  }
6616
6678
  for (const el of orderedEls) {
6617
- if (container.lastElementChild !== el) {
6679
+ if (anchor) {
6680
+ if (el.nextSibling !== anchor) container.insertBefore(el, anchor);
6681
+ } else if (container.lastElementChild !== el) {
6618
6682
  container.appendChild(el);
6619
6683
  }
6620
6684
  }
@@ -6946,7 +7010,195 @@ function parseFooterOrder(content) {
6946
7010
  return null;
6947
7011
  }
6948
7012
  }
7013
+ var FOOTER_HEADING_RE = /^footer-(\d+)-heading$/;
7014
+ var FOOTER_LABEL_RE = /^footer-(\d+)-(\d+)-label$/;
7015
+ var DEFAULT_FOOTER_COLUMN_HEADING = "New";
7016
+ var DEFAULT_FOOTER_COLUMN_LINK_LABEL = "New link";
7017
+ var DEFAULT_FOOTER_COLUMN_LINK_HREF = "";
7018
+ function isFooterLinksContainer(el) {
7019
+ return el.hasAttribute("data-ohw-footer-links") || el.classList.contains("rb-footer-links");
7020
+ }
7021
+ function getFooterColumnIndicesInDom() {
7022
+ const indices = /* @__PURE__ */ new Set();
7023
+ for (const col of listFooterColumns()) {
7024
+ const attr = col.getAttribute("data-ohw-footer-col");
7025
+ if (attr != null) {
7026
+ const n = parseInt(attr, 10);
7027
+ if (Number.isFinite(n)) indices.add(n);
7028
+ }
7029
+ for (const link of listFooterLinksInColumn(col)) {
7030
+ const parsed = parseFooterHrefKey(link.getAttribute("data-ohw-href-key"));
7031
+ if (parsed) indices.add(parsed.col);
7032
+ }
7033
+ const heading = col.querySelector('[data-ohw-key^="footer-"][data-ohw-key$="-heading"]');
7034
+ const headingKey = heading?.getAttribute("data-ohw-key");
7035
+ if (headingKey) {
7036
+ const match = headingKey.match(FOOTER_HEADING_RE);
7037
+ if (match) indices.add(parseInt(match[1], 10));
7038
+ }
7039
+ }
7040
+ return [...indices].sort((a, b) => a - b);
7041
+ }
7042
+ function getNextFooterColumnIndex() {
7043
+ const indices = getFooterColumnIndicesInDom();
7044
+ if (indices.length === 0) return 0;
7045
+ return Math.max(...indices) + 1;
7046
+ }
7047
+ function cloneFooterLinkShell(template) {
7048
+ const anchor = document.createElement("a");
7049
+ if (template) {
7050
+ anchor.style.cssText = template.style.cssText;
7051
+ if (template.className) anchor.className = template.className;
7052
+ }
7053
+ anchor.style.cursor = "pointer";
7054
+ anchor.style.textDecoration = "none";
7055
+ return anchor;
7056
+ }
7057
+ function buildFooterHeading(colIndex, text) {
7058
+ const heading = document.createElement("p");
7059
+ heading.setAttribute("data-ohw-editable", "text");
7060
+ heading.setAttribute("data-ohw-key", `footer-${colIndex}-heading`);
7061
+ heading.textContent = text;
7062
+ heading.style.cssText = [
7063
+ "color: var(--espresso, #3d312b)",
7064
+ "font-size: 14px",
7065
+ "font-weight: 800",
7066
+ "opacity: 0.82",
7067
+ "margin: 0 0 6px",
7068
+ "padding: 0",
7069
+ "line-height: 1.2"
7070
+ ].join(";");
7071
+ return heading;
7072
+ }
7073
+ function buildFooterLink(colIndex, itemIndex, href, label, template) {
7074
+ const anchor = cloneFooterLinkShell(template);
7075
+ anchor.setAttribute("href", href);
7076
+ anchor.setAttribute("data-ohw-href-key", `footer-${colIndex}-${itemIndex}-href`);
7077
+ const span = document.createElement("span");
7078
+ span.setAttribute("data-ohw-editable", "text");
7079
+ span.setAttribute("data-ohw-key", `footer-${colIndex}-${itemIndex}-label`);
7080
+ span.textContent = label;
7081
+ anchor.appendChild(span);
7082
+ return anchor;
7083
+ }
7084
+ function footerColumnExistsInDom(colIndex) {
7085
+ return document.querySelector(`[data-ohw-footer-col="${colIndex}"]`) !== null || document.querySelector(`[data-ohw-href-key^="footer-${colIndex}-"]`) !== null || document.querySelector(`[data-ohw-key="footer-${colIndex}-heading"]`) !== null;
7086
+ }
7087
+ function getFooterLinkTemplate() {
7088
+ for (const col of listFooterColumns()) {
7089
+ const link = listFooterLinksInColumn(col)[0];
7090
+ if (link) return link;
7091
+ }
7092
+ return null;
7093
+ }
7094
+ function getFooterColumnTemplate() {
7095
+ return listFooterColumns()[0] ?? null;
7096
+ }
7097
+ function insertFooterColumnDom(colIndex, heading, items) {
7098
+ const container = getFooterLinksContainer();
7099
+ if (!container) return null;
7100
+ const colTemplate = getFooterColumnTemplate();
7101
+ const linkTemplate = getFooterLinkTemplate();
7102
+ const column = document.createElement("div");
7103
+ if (colTemplate?.className) column.className = colTemplate.className;
7104
+ else column.className = "rb-footer-link-col";
7105
+ column.setAttribute("data-ohw-footer-col", String(colIndex));
7106
+ if (colTemplate) {
7107
+ const gap = getComputedStyle(colTemplate).gap;
7108
+ if (gap && gap !== "normal") column.style.gap = gap;
7109
+ column.style.display = getComputedStyle(colTemplate).display || "flex";
7110
+ column.style.flexDirection = "column";
7111
+ }
7112
+ column.appendChild(buildFooterHeading(colIndex, heading));
7113
+ let firstLink = null;
7114
+ items.forEach((item, itemIndex) => {
7115
+ const link = buildFooterLink(colIndex, itemIndex, item.href, item.label, linkTemplate);
7116
+ column.appendChild(link);
7117
+ if (!firstLink) firstLink = link;
7118
+ });
7119
+ container.appendChild(column);
7120
+ return { column, firstLink };
7121
+ }
7122
+ function insertFooterColumn(heading = DEFAULT_FOOTER_COLUMN_HEADING, linkLabel = DEFAULT_FOOTER_COLUMN_LINK_LABEL, linkHref = DEFAULT_FOOTER_COLUMN_LINK_HREF) {
7123
+ const colIndex = getNextFooterColumnIndex();
7124
+ const inserted = insertFooterColumnDom(colIndex, heading, [{ href: linkHref, label: linkLabel }]);
7125
+ if (!inserted?.firstLink) throw new Error("Failed to insert footer column");
7126
+ syncFooterColumnIndices(listFooterColumns());
7127
+ return {
7128
+ column: inserted.column,
7129
+ firstLink: inserted.firstLink,
7130
+ colIndex,
7131
+ headingKey: `footer-${colIndex}-heading`,
7132
+ hrefKey: `footer-${colIndex}-0-href`,
7133
+ labelKey: `footer-${colIndex}-0-label`,
7134
+ heading,
7135
+ label: linkLabel,
7136
+ href: linkHref,
7137
+ order: getFooterOrderFromDom()
7138
+ };
7139
+ }
7140
+ function collectFooterColumnIndicesFromContent(content) {
7141
+ const indices = /* @__PURE__ */ new Set();
7142
+ for (const key of Object.keys(content)) {
7143
+ const href = parseFooterHrefKey(key);
7144
+ if (href) indices.add(href.col);
7145
+ const heading = key.match(FOOTER_HEADING_RE);
7146
+ if (heading) indices.add(parseInt(heading[1], 10));
7147
+ const label = key.match(FOOTER_LABEL_RE);
7148
+ if (label) indices.add(parseInt(label[1], 10));
7149
+ }
7150
+ const order = parseFooterOrder(content);
7151
+ if (order) {
7152
+ for (const col of order) {
7153
+ for (const hrefKey of col) {
7154
+ const parsed = parseFooterHrefKey(hrefKey);
7155
+ if (parsed) indices.add(parsed.col);
7156
+ }
7157
+ }
7158
+ }
7159
+ return [...indices].sort((a, b) => a - b);
7160
+ }
7161
+ function collectFooterItemsForColumn(content, colIndex) {
7162
+ const itemIndices = /* @__PURE__ */ new Set();
7163
+ for (const key of Object.keys(content)) {
7164
+ const href = parseFooterHrefKey(key);
7165
+ if (href?.col === colIndex) itemIndices.add(href.item);
7166
+ const label = key.match(FOOTER_LABEL_RE);
7167
+ if (label && parseInt(label[1], 10) === colIndex) {
7168
+ itemIndices.add(parseInt(label[2], 10));
7169
+ }
7170
+ }
7171
+ const sorted = [...itemIndices].sort((a, b) => a - b);
7172
+ if (sorted.length === 0) {
7173
+ return [
7174
+ {
7175
+ href: content[`footer-${colIndex}-0-href`] ?? DEFAULT_FOOTER_COLUMN_LINK_HREF,
7176
+ label: content[`footer-${colIndex}-0-label`] ?? DEFAULT_FOOTER_COLUMN_LINK_LABEL
7177
+ }
7178
+ ];
7179
+ }
7180
+ return sorted.map((itemIndex) => ({
7181
+ href: content[`footer-${colIndex}-${itemIndex}-href`] ?? DEFAULT_FOOTER_COLUMN_LINK_HREF,
7182
+ label: content[`footer-${colIndex}-${itemIndex}-label`] ?? DEFAULT_FOOTER_COLUMN_LINK_LABEL
7183
+ }));
7184
+ }
7185
+ function reconcileFooterColumnsFromContent(content) {
7186
+ const indices = collectFooterColumnIndicesFromContent(content);
7187
+ for (const colIndex of indices) {
7188
+ if (footerColumnExistsInDom(colIndex)) continue;
7189
+ const heading = content[`footer-${colIndex}-heading`] ?? DEFAULT_FOOTER_COLUMN_HEADING;
7190
+ const items = collectFooterItemsForColumn(content, colIndex);
7191
+ const hasPayload = Boolean(content[`footer-${colIndex}-heading`]) || items.some(
7192
+ (_, i) => content[`footer-${colIndex}-${i}-href`] !== void 0 || content[`footer-${colIndex}-${i}-label`] !== void 0
7193
+ ) || (parseFooterOrder(content)?.some(
7194
+ (col) => col.some((k) => parseFooterHrefKey(k)?.col === colIndex)
7195
+ ) ?? false);
7196
+ if (!hasPayload) continue;
7197
+ insertFooterColumnDom(colIndex, heading, items);
7198
+ }
7199
+ }
6949
7200
  function reconcileFooterOrderFromContent(content) {
7201
+ reconcileFooterColumnsFromContent(content);
6950
7202
  const order = parseFooterOrder(content);
6951
7203
  if (!order?.length) return;
6952
7204
  const current = getFooterOrderFromDom();
@@ -6956,6 +7208,31 @@ function reconcileFooterOrderFromContent(content) {
6956
7208
  }
6957
7209
  applyFooterOrder(order);
6958
7210
  }
7211
+ function resolveFooterLinksContainerSelectionTarget(target, clientX, clientY, isPointOverItem) {
7212
+ const container = getFooterLinksContainer();
7213
+ if (!container) return null;
7214
+ const inContainer = target === container || container.contains(target) && Boolean(target.closest("[data-ohw-footer-links], .rb-footer-links"));
7215
+ if (!inContainer && target !== container) {
7216
+ const r2 = container.getBoundingClientRect();
7217
+ const over = clientX >= r2.left && clientX <= r2.right && clientY >= r2.top && clientY <= r2.bottom;
7218
+ if (!over) return null;
7219
+ }
7220
+ if (isPointOverItem(container, clientX, clientY)) return null;
7221
+ const column = target.closest("[data-ohw-footer-col], [data-ohw-footer-column]") ?? null;
7222
+ if (column && container.contains(column)) {
7223
+ if (target === column || column.contains(target)) return null;
7224
+ }
7225
+ if (target === container || container.contains(target) || inContainer) {
7226
+ if (target === container || isFooterLinksContainer(target)) return container;
7227
+ if (!column) return container;
7228
+ }
7229
+ return null;
7230
+ }
7231
+ function isRowLayoutColumn(links) {
7232
+ if (links.length < 2) return false;
7233
+ const firstTop = links[0].getBoundingClientRect().top;
7234
+ return links.every((link) => Math.abs(link.getBoundingClientRect().top - firstTop) < 4);
7235
+ }
6959
7236
  function buildLinkDropSlots(column, columnIndex) {
6960
7237
  const links = listFooterLinksInColumn(column);
6961
7238
  const colRect = column.getBoundingClientRect();
@@ -6973,6 +7250,32 @@ function buildLinkDropSlots(column, columnIndex) {
6973
7250
  });
6974
7251
  return slots;
6975
7252
  }
7253
+ if (isRowLayoutColumn(links)) {
7254
+ for (let i = 0; i <= links.length; i++) {
7255
+ let left;
7256
+ if (i === 0) {
7257
+ left = links[0].getBoundingClientRect().left - barThickness / 2;
7258
+ } else if (i === links.length) {
7259
+ const last = links[links.length - 1].getBoundingClientRect();
7260
+ left = last.right - barThickness / 2;
7261
+ } else {
7262
+ const prev = links[i - 1].getBoundingClientRect();
7263
+ const next = links[i].getBoundingClientRect();
7264
+ left = (prev.right + next.left) / 2 - barThickness / 2;
7265
+ }
7266
+ const heightRef = i === 0 ? links[0].getBoundingClientRect() : i === links.length ? links[links.length - 1].getBoundingClientRect() : links[i].getBoundingClientRect();
7267
+ slots.push({
7268
+ insertIndex: i,
7269
+ columnIndex,
7270
+ left,
7271
+ top: heightRef.top,
7272
+ width: barThickness,
7273
+ height: Math.max(heightRef.height, colRect.height * 0.8),
7274
+ direction: "vertical"
7275
+ });
7276
+ }
7277
+ return slots;
7278
+ }
6976
7279
  for (let i = 0; i <= links.length; i++) {
6977
7280
  let top;
6978
7281
  if (i === 0) {
@@ -7056,8 +7359,7 @@ function hitTestLinkDropSlot(clientX, clientY, draggedHrefKey) {
7056
7359
  return true;
7057
7360
  });
7058
7361
  for (const slot of slots) {
7059
- const cy = slot.top + slot.height / 2;
7060
- const dist = Math.abs(clientY - cy) + (inColX ? 0 : 1e3);
7362
+ const dist = slot.direction === "vertical" ? Math.abs(clientX - (slot.left + slot.width / 2)) : Math.abs(clientY - (slot.top + slot.height / 2));
7061
7363
  if (!best || dist < best.dist) best = { slot, dist };
7062
7364
  }
7063
7365
  }
@@ -7081,6 +7383,30 @@ function hitTestColumnDropSlot(clientX, _clientY) {
7081
7383
  return best?.slot ?? null;
7082
7384
  }
7083
7385
 
7386
+ // src/lib/add-footer-column.ts
7387
+ function buildFooterColumnEditContentPatch(result) {
7388
+ return {
7389
+ [result.headingKey]: result.heading,
7390
+ [result.hrefKey]: result.href,
7391
+ [result.labelKey]: result.label,
7392
+ [FOOTER_ORDER_KEY]: JSON.stringify(result.order)
7393
+ };
7394
+ }
7395
+ function addFooterColumnWithPersist({
7396
+ postToParent: postToParent2
7397
+ }) {
7398
+ const result = insertFooterColumn();
7399
+ const patch = buildFooterColumnEditContentPatch(result);
7400
+ setStoredLinkHref(result.hrefKey, result.href);
7401
+ postToParent2({
7402
+ type: "ow:change",
7403
+ nodes: Object.entries(patch).map(([key, text]) => ({ key, text }))
7404
+ });
7405
+ postToParent2({ type: "ow:toast", title: "Item added", toastType: "success" });
7406
+ enforceLinkHrefs();
7407
+ return result;
7408
+ }
7409
+
7084
7410
  // src/lib/item-drag-interaction.ts
7085
7411
  function disableNativeHrefDrag(el) {
7086
7412
  if (el.draggable) el.draggable = false;
@@ -7101,9 +7427,10 @@ function lockItemDuringDrag() {
7101
7427
  clearTextSelection();
7102
7428
  }
7103
7429
  function unlockItemDragInteraction() {
7430
+ const wasDragging = document.documentElement.hasAttribute("data-ohw-item-dragging");
7104
7431
  document.documentElement.removeAttribute("data-ohw-footer-press-drag");
7105
7432
  document.documentElement.removeAttribute("data-ohw-item-dragging");
7106
- clearTextSelection();
7433
+ if (wasDragging) clearTextSelection();
7107
7434
  }
7108
7435
  var armFooterPressDrag = armItemPressDrag;
7109
7436
  var lockFooterDuringDrag = lockItemDuringDrag;
@@ -7277,7 +7604,7 @@ function hitTestNavDropSlot(clientX, clientY, draggedHrefKey) {
7277
7604
  }
7278
7605
 
7279
7606
  // src/useNavItemDrag.ts
7280
- import { useCallback as useCallback3, useEffect as useEffect7, useRef as useRef6, useState as useState6 } from "react";
7607
+ import { useCallback as useCallback4, useEffect as useEffect7, useRef as useRef6, useState as useState6 } from "react";
7281
7608
  function useNavItemDrag({
7282
7609
  isEditMode,
7283
7610
  editContentRef,
@@ -7306,7 +7633,7 @@ function useNavItemDrag({
7306
7633
  const [navDropSlots, setNavDropSlots] = useState6([]);
7307
7634
  const [activeNavDropIndex, setActiveNavDropIndex] = useState6(null);
7308
7635
  const navPointerDragRef = useRef6(null);
7309
- const clearNavDragVisuals = useCallback3(() => {
7636
+ const clearNavDragVisuals = useCallback4(() => {
7310
7637
  navDragRef.current = null;
7311
7638
  setNavDropSlots([]);
7312
7639
  setActiveNavDropIndex(null);
@@ -7315,7 +7642,7 @@ function useNavItemDrag({
7315
7642
  setIsItemDragging(false);
7316
7643
  unlockItemDragInteraction();
7317
7644
  }, [setDraggedItemRect, setIsItemDragging, setSiblingHintRects]);
7318
- const refreshNavDragVisuals = useCallback3(
7645
+ const refreshNavDragVisuals = useCallback4(
7319
7646
  (session, activeSlot, clientX, clientY) => {
7320
7647
  setDraggedItemRect(session.draggedEl.getBoundingClientRect());
7321
7648
  if (typeof clientX === "number" && typeof clientY === "number") {
@@ -7339,7 +7666,7 @@ function useNavItemDrag({
7339
7666
  });
7340
7667
  const beginNavDragRef = useRef6(() => {
7341
7668
  });
7342
- const beginNavDrag = useCallback3(
7669
+ const beginNavDrag = useCallback4(
7343
7670
  (session) => {
7344
7671
  const rect = session.draggedEl.getBoundingClientRect();
7345
7672
  session.lastClientX = session.lastClientX || rect.left + rect.width / 2;
@@ -7366,7 +7693,7 @@ function useNavItemDrag({
7366
7693
  ]
7367
7694
  );
7368
7695
  beginNavDragRef.current = beginNavDrag;
7369
- const commitNavDrag = useCallback3(
7696
+ const commitNavDrag = useCallback4(
7370
7697
  (clientX, clientY) => {
7371
7698
  const session = navDragRef.current;
7372
7699
  if (!session) {
@@ -7427,7 +7754,7 @@ function useNavItemDrag({
7427
7754
  [clearNavDragVisuals, deselectRef, editContentRef, postToParentRef, selectRef]
7428
7755
  );
7429
7756
  commitNavDragRef.current = commitNavDrag;
7430
- const startNavLinkDrag = useCallback3(
7757
+ const startNavLinkDrag = useCallback4(
7431
7758
  (anchor, clientX, clientY, wasSelected) => {
7432
7759
  if (footerDragRef.current) return false;
7433
7760
  const hrefKey = anchor.getAttribute("data-ohw-href-key");
@@ -7445,7 +7772,7 @@ function useNavItemDrag({
7445
7772
  },
7446
7773
  [beginNavDrag, footerDragRef, isDragHandleDisabled2, isNavbarButton3]
7447
7774
  );
7448
- const onNavDragOver = useCallback3(
7775
+ const onNavDragOver = useCallback4(
7449
7776
  (e) => {
7450
7777
  const session = navDragRef.current;
7451
7778
  if (!session) return false;
@@ -7480,7 +7807,6 @@ function useNavItemDrag({
7480
7807
  const hrefKey = anchor?.getAttribute("data-ohw-href-key") ?? null;
7481
7808
  if (!anchor || !isNavbarHrefKey(hrefKey)) return;
7482
7809
  if (isNavbarButton3(anchor) || isDragHandleDisabled2(anchor)) return;
7483
- armItemPressDrag();
7484
7810
  navPointerDragRef.current = {
7485
7811
  el: anchor,
7486
7812
  startX: e.clientX,
@@ -7507,6 +7833,7 @@ function useNavItemDrag({
7507
7833
  if (dx * dx + dy * dy < THRESHOLD * THRESHOLD) return;
7508
7834
  e.preventDefault();
7509
7835
  pending.started = true;
7836
+ armItemPressDrag();
7510
7837
  clearTextSelection();
7511
7838
  try {
7512
7839
  document.body.setPointerCapture(pending.pointerId);
@@ -7534,7 +7861,8 @@ function useNavItemDrag({
7534
7861
  }
7535
7862
  } catch {
7536
7863
  }
7537
- if (!pending?.started) {
7864
+ if (!pending) return;
7865
+ if (!pending.started) {
7538
7866
  unlockItemDragInteraction();
7539
7867
  return;
7540
7868
  }
@@ -7581,12 +7909,11 @@ function useNavItemDrag({
7581
7909
  setLinkPopover,
7582
7910
  suppressNextClickRef
7583
7911
  ]);
7584
- const armNavPressFromChrome = useCallback3(
7912
+ const armNavPressFromChrome = useCallback4(
7585
7913
  (selected, clientX, clientY, pointerId) => {
7586
7914
  const hrefKey = selected.getAttribute("data-ohw-href-key");
7587
7915
  if (!hrefKey || !isNavbarHrefKey(hrefKey)) return false;
7588
7916
  if (isNavbarButton3(selected) || isDragHandleDisabled2(selected)) return false;
7589
- armItemPressDrag();
7590
7917
  navPointerDragRef.current = {
7591
7918
  el: selected,
7592
7919
  startX: clientX,
@@ -7612,15 +7939,60 @@ function useNavItemDrag({
7612
7939
  };
7613
7940
  }
7614
7941
 
7615
- // src/ui/navbar-container-chrome.tsx
7942
+ // src/ui/footer-container-chrome.tsx
7616
7943
  import { Plus as Plus2 } from "lucide-react";
7617
- import { jsx as jsx21 } from "react/jsx-runtime";
7944
+ import { jsx as jsx21, jsxs as jsxs13 } from "react/jsx-runtime";
7945
+ function FooterContainerChrome({ rect, onAdd }) {
7946
+ const chromeGap = 6;
7947
+ const buttonMargin = 7;
7948
+ return /* @__PURE__ */ jsx21(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ jsx21(
7949
+ "div",
7950
+ {
7951
+ "data-ohw-footer-container-chrome": "",
7952
+ "data-ohw-bridge": "",
7953
+ className: "pointer-events-none fixed z-[2147483647]",
7954
+ style: {
7955
+ top: rect.top - chromeGap,
7956
+ left: rect.left - chromeGap,
7957
+ width: rect.width + chromeGap * 2,
7958
+ height: rect.height + chromeGap * 2
7959
+ },
7960
+ children: /* @__PURE__ */ jsxs13(Tooltip, { children: [
7961
+ /* @__PURE__ */ jsx21(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx21(
7962
+ "button",
7963
+ {
7964
+ type: "button",
7965
+ "data-ohw-footer-add-button": "",
7966
+ className: "pointer-events-auto absolute left-1/2 flex size-7 -translate-x-1/2 -translate-y-full items-center justify-center rounded-[10px] border border-border bg-background p-0.5 shadow-sm transition-colors hover:bg-muted/80",
7967
+ style: { top: chromeGap - buttonMargin },
7968
+ "aria-label": "Add item",
7969
+ onMouseDown: (e) => {
7970
+ e.preventDefault();
7971
+ e.stopPropagation();
7972
+ },
7973
+ onClick: (e) => {
7974
+ e.preventDefault();
7975
+ e.stopPropagation();
7976
+ onAdd();
7977
+ },
7978
+ children: /* @__PURE__ */ jsx21(Plus2, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
7979
+ }
7980
+ ) }),
7981
+ /* @__PURE__ */ jsx21(TooltipContent, { side: "bottom", sideOffset: 9, children: "Add item" })
7982
+ ] })
7983
+ }
7984
+ ) });
7985
+ }
7986
+
7987
+ // src/ui/navbar-container-chrome.tsx
7988
+ import { Plus as Plus3 } from "lucide-react";
7989
+ import { jsx as jsx22 } from "react/jsx-runtime";
7618
7990
  function NavbarContainerChrome({
7619
7991
  rect,
7620
7992
  onAdd
7621
7993
  }) {
7622
7994
  const chromeGap = 6;
7623
- return /* @__PURE__ */ jsx21(
7995
+ return /* @__PURE__ */ jsx22(
7624
7996
  "div",
7625
7997
  {
7626
7998
  "data-ohw-navbar-container-chrome": "",
@@ -7632,7 +8004,7 @@ function NavbarContainerChrome({
7632
8004
  width: rect.width + chromeGap * 2,
7633
8005
  height: rect.height + chromeGap * 2
7634
8006
  },
7635
- children: /* @__PURE__ */ jsx21(
8007
+ children: /* @__PURE__ */ jsx22(
7636
8008
  "button",
7637
8009
  {
7638
8010
  type: "button",
@@ -7649,7 +8021,7 @@ function NavbarContainerChrome({
7649
8021
  e.stopPropagation();
7650
8022
  onAdd();
7651
8023
  },
7652
- children: /* @__PURE__ */ jsx21(Plus2, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
8024
+ children: /* @__PURE__ */ jsx22(Plus3, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
7653
8025
  }
7654
8026
  )
7655
8027
  }
@@ -7658,7 +8030,7 @@ function NavbarContainerChrome({
7658
8030
 
7659
8031
  // src/ui/drop-indicator.tsx
7660
8032
  import * as React10 from "react";
7661
- import { jsx as jsx22 } from "react/jsx-runtime";
8033
+ import { jsx as jsx23 } from "react/jsx-runtime";
7662
8034
  var dropIndicatorVariants = cva(
7663
8035
  "ov-gap-line pointer-events-none shrink-0 transition-opacity duration-150",
7664
8036
  {
@@ -7682,7 +8054,7 @@ var dropIndicatorVariants = cva(
7682
8054
  );
7683
8055
  var DropIndicator = React10.forwardRef(
7684
8056
  ({ className, direction, state, ...props }, ref) => {
7685
- return /* @__PURE__ */ jsx22(
8057
+ return /* @__PURE__ */ jsx23(
7686
8058
  "div",
7687
8059
  {
7688
8060
  ref,
@@ -7699,7 +8071,7 @@ var DropIndicator = React10.forwardRef(
7699
8071
  DropIndicator.displayName = "DropIndicator";
7700
8072
 
7701
8073
  // src/ui/badge.tsx
7702
- import { jsx as jsx23 } from "react/jsx-runtime";
8074
+ import { jsx as jsx24 } from "react/jsx-runtime";
7703
8075
  var badgeVariants = cva(
7704
8076
  "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
7705
8077
  {
@@ -7717,12 +8089,12 @@ var badgeVariants = cva(
7717
8089
  }
7718
8090
  );
7719
8091
  function Badge({ className, variant, ...props }) {
7720
- return /* @__PURE__ */ jsx23("div", { className: cn(badgeVariants({ variant }), className), ...props });
8092
+ return /* @__PURE__ */ jsx24("div", { className: cn(badgeVariants({ variant }), className), ...props });
7721
8093
  }
7722
8094
 
7723
8095
  // src/OhhwellsBridge.tsx
7724
8096
  import { Link as Link2 } from "lucide-react";
7725
- import { Fragment as Fragment5, jsx as jsx24, jsxs as jsxs13 } from "react/jsx-runtime";
8097
+ import { Fragment as Fragment5, jsx as jsx25, jsxs as jsxs14 } from "react/jsx-runtime";
7726
8098
  var PRIMARY2 = "#0885FE";
7727
8099
  var IMAGE_FADE_MS = 300;
7728
8100
  function runOpacityFade(el, onDone) {
@@ -7901,7 +8273,7 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
7901
8273
  const root = createRoot(container);
7902
8274
  flushSync(() => {
7903
8275
  root.render(
7904
- /* @__PURE__ */ jsx24(
8276
+ /* @__PURE__ */ jsx25(
7905
8277
  SchedulingWidget,
7906
8278
  {
7907
8279
  notifyOnConnect,
@@ -8102,7 +8474,7 @@ function applyLinkByKey(key, val) {
8102
8474
  }
8103
8475
  function isInsideLinkEditor(target) {
8104
8476
  return Boolean(
8105
- target.closest("[data-ohw-link-popover-root]") || target.closest("[data-ohw-link-modal-root]") || target.closest("[data-ohw-link-page-dropdown]") || target.closest("[data-ohw-section-picker]") || target.closest("[data-ohw-navbar-container-chrome]") || target.closest("[data-ohw-navbar-add-button]") || target.closest('[data-slot="popover-content"]') || target.closest('[data-slot="dialog-content"]') || target.closest('[data-slot="dialog-overlay"]')
8477
+ target.closest("[data-ohw-link-popover-root]") || target.closest("[data-ohw-link-modal-root]") || target.closest("[data-ohw-link-page-dropdown]") || target.closest("[data-ohw-section-picker]") || target.closest("[data-ohw-navbar-container-chrome]") || target.closest("[data-ohw-navbar-add-button]") || target.closest("[data-ohw-footer-container-chrome]") || target.closest("[data-ohw-footer-add-button]") || target.closest('[data-slot="popover-content"]') || target.closest('[data-slot="dialog-content"]') || target.closest('[data-slot="dialog-overlay"]')
8106
8478
  );
8107
8479
  }
8108
8480
  function getHrefKeyFromElement(el) {
@@ -8183,7 +8555,7 @@ function isInferredFooterGroup(el) {
8183
8555
  return countFooterNavItems(el) >= 2;
8184
8556
  }
8185
8557
  function isNavigationContainer(el) {
8186
- return el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isNavigationRoot(el) || isInferredFooterGroup(el);
8558
+ return el.hasAttribute("data-ohw-nav-container") || isFooterLinksContainer(el) || el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isNavigationRoot(el) || isInferredFooterGroup(el);
8187
8559
  }
8188
8560
  function isNavbarLinksContainer(el) {
8189
8561
  return el.hasAttribute("data-ohw-nav-container");
@@ -8202,6 +8574,9 @@ function isPointOverNavigation(x, y) {
8202
8574
  const roots = /* @__PURE__ */ new Set();
8203
8575
  const navRoot = document.querySelector("[data-ohw-nav-root]") ?? document.querySelector("nav");
8204
8576
  if (navRoot) roots.add(navRoot);
8577
+ document.querySelectorAll("[data-ohw-nav-drawer], [data-ohw-nav-container]").forEach((el) => {
8578
+ roots.add(el);
8579
+ });
8205
8580
  const footer = document.querySelector("footer");
8206
8581
  if (footer) roots.add(footer);
8207
8582
  for (const root of roots) {
@@ -8230,6 +8605,12 @@ function isPointOverNavItem(container, x, y) {
8230
8605
  return x >= itemRect.left && x <= itemRect.right && y >= itemRect.top && y <= itemRect.bottom;
8231
8606
  });
8232
8607
  }
8608
+ function isPointOverFooterColumn(x, y) {
8609
+ return listFooterColumns().some((col) => {
8610
+ const r2 = col.getBoundingClientRect();
8611
+ return x >= r2.left && x <= r2.right && y >= r2.top && y <= r2.bottom;
8612
+ });
8613
+ }
8233
8614
  function resolveNavContainerSelectionTarget(target, clientX, clientY) {
8234
8615
  if (getNavigationItemAnchor(target)) return null;
8235
8616
  if (target.closest('[data-ohw-role="navbar-button"]')) return null;
@@ -8260,7 +8641,10 @@ function getNavigationSelectionParent(el) {
8260
8641
  if (footerGroup) return footerGroup;
8261
8642
  return getNavigationRoot(el);
8262
8643
  }
8263
- if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup(el)) {
8644
+ if (!isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isInferredFooterGroup(el))) {
8645
+ return getFooterLinksContainer();
8646
+ }
8647
+ if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-footer-col") || el.hasAttribute("data-ohw-footer-column") || isFooterLinksContainer(el) || isInferredFooterGroup(el)) {
8264
8648
  return getNavigationRoot(el);
8265
8649
  }
8266
8650
  return null;
@@ -8431,9 +8815,16 @@ function sanitizeHtml(html) {
8431
8815
  if (!SAFE_TAGS.has(el.tagName)) {
8432
8816
  parent.replaceChild(document.createTextNode(el.textContent ?? ""), el);
8433
8817
  } else {
8434
- const textAlign = el.style?.textAlign || el.getAttribute("align") || "";
8818
+ const htmlEl = el;
8819
+ const textAlign = htmlEl.style?.textAlign || el.getAttribute("align") || "";
8820
+ const fontWeight = htmlEl.style?.fontWeight || "";
8821
+ const fontStyle = htmlEl.style?.fontStyle || "";
8822
+ const textDecorationLine = htmlEl.style?.textDecorationLine || htmlEl.style?.textDecoration || "";
8435
8823
  for (const attr of Array.from(el.attributes)) el.removeAttribute(attr.name);
8436
- if (textAlign) el.style.textAlign = textAlign;
8824
+ if (textAlign) htmlEl.style.textAlign = textAlign;
8825
+ if (fontWeight) htmlEl.style.fontWeight = fontWeight;
8826
+ if (fontStyle) htmlEl.style.fontStyle = fontStyle;
8827
+ if (textDecorationLine) htmlEl.style.textDecorationLine = textDecorationLine;
8437
8828
  walk(el);
8438
8829
  }
8439
8830
  }
@@ -8480,7 +8871,7 @@ function EditGlowChrome({
8480
8871
  hideHandle = false
8481
8872
  }) {
8482
8873
  const GAP = SELECTION_CHROME_GAP2;
8483
- return /* @__PURE__ */ jsxs13(
8874
+ return /* @__PURE__ */ jsxs14(
8484
8875
  "div",
8485
8876
  {
8486
8877
  ref: elRef,
@@ -8495,7 +8886,7 @@ function EditGlowChrome({
8495
8886
  zIndex: 2147483646
8496
8887
  },
8497
8888
  children: [
8498
- /* @__PURE__ */ jsx24(
8889
+ /* @__PURE__ */ jsx25(
8499
8890
  "div",
8500
8891
  {
8501
8892
  style: {
@@ -8508,7 +8899,7 @@ function EditGlowChrome({
8508
8899
  }
8509
8900
  }
8510
8901
  ),
8511
- reorderHrefKey && !hideHandle && /* @__PURE__ */ jsx24(
8902
+ reorderHrefKey && !hideHandle && /* @__PURE__ */ jsx25(
8512
8903
  "div",
8513
8904
  {
8514
8905
  "data-ohw-drag-handle-container": "",
@@ -8520,7 +8911,7 @@ function EditGlowChrome({
8520
8911
  transform: "translate(calc(-100% - 7px), -50%)",
8521
8912
  pointerEvents: dragDisabled ? "none" : "auto"
8522
8913
  },
8523
- children: /* @__PURE__ */ jsx24(
8914
+ children: /* @__PURE__ */ jsx25(
8524
8915
  DragHandle,
8525
8916
  {
8526
8917
  "aria-label": `Reorder ${reorderHrefKey}`,
@@ -8621,6 +9012,79 @@ function resolveItemInteractionState(rect, parentScroll) {
8621
9012
  const { transform } = calcToolbarPos(rect, parentScroll, 120);
8622
9013
  return transform.includes("translateY(-100%)") ? "active-top" : "active-bottom";
8623
9014
  }
9015
+ function resolveSelectionStartElement(sel) {
9016
+ if (!sel || sel.rangeCount === 0) return null;
9017
+ const range = sel.getRangeAt(0);
9018
+ let startNode = range.startContainer;
9019
+ if (startNode.nodeType === Node.ELEMENT_NODE) {
9020
+ const el = startNode;
9021
+ startNode = el.childNodes[range.startOffset] ?? el.childNodes[range.startOffset - 1] ?? el;
9022
+ }
9023
+ return startNode.nodeType === Node.TEXT_NODE ? startNode.parentElement : startNode;
9024
+ }
9025
+ function detectActiveFormats(startEl, activeRoot) {
9026
+ const formats = /* @__PURE__ */ new Set();
9027
+ const cs = getComputedStyle(startEl);
9028
+ const weight = parseInt(cs.fontWeight, 10);
9029
+ let hasBold = cs.fontWeight === "bold" || !Number.isNaN(weight) && weight >= 600;
9030
+ let hasItalic = cs.fontStyle === "italic" || cs.fontStyle === "oblique";
9031
+ let hasUnderline = false;
9032
+ let hasStrike = false;
9033
+ let hasUl = false;
9034
+ let hasOl = false;
9035
+ for (let el = startEl; el; el = el.parentElement) {
9036
+ if (el.tagName === "B" || el.tagName === "STRONG") hasBold = true;
9037
+ if (el.tagName === "I" || el.tagName === "EM") hasItalic = true;
9038
+ const decoration = getComputedStyle(el).textDecorationLine;
9039
+ if (decoration.includes("underline") || el.tagName === "U" || el.tagName === "INS") hasUnderline = true;
9040
+ if (decoration.includes("line-through") || el.tagName === "S" || el.tagName === "STRIKE" || el.tagName === "DEL") hasStrike = true;
9041
+ if (el.tagName === "UL") hasUl = true;
9042
+ if (el.tagName === "OL") hasOl = true;
9043
+ if (el === activeRoot) break;
9044
+ }
9045
+ if (hasBold) formats.add("bold");
9046
+ if (hasItalic) formats.add("italic");
9047
+ if (hasUnderline) formats.add("underline");
9048
+ if (hasStrike) formats.add("strikeThrough");
9049
+ if (hasUl) formats.add("insertUnorderedList");
9050
+ if (hasOl) formats.add("insertOrderedList");
9051
+ const block = startEl.closest("div, p, h1, h2, h3, h4, h5, h6, li, td, th") ?? startEl;
9052
+ const align = getComputedStyle(block).textAlign;
9053
+ if (align === "center") formats.add("justifyCenter");
9054
+ else if (align === "right" || align === "end") formats.add("justifyRight");
9055
+ else formats.add("justifyLeft");
9056
+ return formats;
9057
+ }
9058
+ function setsEqual(a, b) {
9059
+ if (a.size !== b.size) return false;
9060
+ for (const item of a) {
9061
+ if (!b.has(item)) return false;
9062
+ }
9063
+ return true;
9064
+ }
9065
+ function textOffsetInRoot(root, node, offset) {
9066
+ const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
9067
+ let total = 0;
9068
+ let current;
9069
+ while (current = walker.nextNode()) {
9070
+ if (current === node) return total + offset;
9071
+ total += (current.textContent ?? "").length;
9072
+ }
9073
+ return total;
9074
+ }
9075
+ function pointAtTextOffset(root, targetOffset) {
9076
+ const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
9077
+ let total = 0;
9078
+ let current;
9079
+ let last = null;
9080
+ while (current = walker.nextNode()) {
9081
+ const len = (current.textContent ?? "").length;
9082
+ if (total + len >= targetOffset) return { node: current, offset: targetOffset - total };
9083
+ total += len;
9084
+ last = current;
9085
+ }
9086
+ return last ? { node: last, offset: (last.textContent ?? "").length } : null;
9087
+ }
8624
9088
  function FloatingToolbar({
8625
9089
  rect,
8626
9090
  parentScroll,
@@ -8657,7 +9121,7 @@ function FloatingToolbar({
8657
9121
  return () => ro.disconnect();
8658
9122
  }, [showEditLink, activeCommands]);
8659
9123
  const { top, left, transform } = calcToolbarPos(rect, parentScroll, measuredW);
8660
- return /* @__PURE__ */ jsx24(
9124
+ return /* @__PURE__ */ jsx25(
8661
9125
  "div",
8662
9126
  {
8663
9127
  ref: setRefs,
@@ -8669,12 +9133,12 @@ function FloatingToolbar({
8669
9133
  zIndex: 2147483647,
8670
9134
  pointerEvents: "auto"
8671
9135
  },
8672
- children: /* @__PURE__ */ jsxs13(CustomToolbar, { children: [
8673
- TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ jsxs13(React11.Fragment, { children: [
8674
- gi > 0 && /* @__PURE__ */ jsx24(CustomToolbarDivider, {}),
9136
+ children: /* @__PURE__ */ jsxs14(CustomToolbar, { children: [
9137
+ TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ jsxs14(React11.Fragment, { children: [
9138
+ gi > 0 && /* @__PURE__ */ jsx25(CustomToolbarDivider, {}),
8675
9139
  btns.map((btn) => {
8676
9140
  const isActive = activeCommands.has(btn.cmd);
8677
- return /* @__PURE__ */ jsx24(
9141
+ return /* @__PURE__ */ jsx25(
8678
9142
  CustomToolbarButton,
8679
9143
  {
8680
9144
  title: btn.title,
@@ -8683,7 +9147,7 @@ function FloatingToolbar({
8683
9147
  e.preventDefault();
8684
9148
  onCommand(btn.cmd);
8685
9149
  },
8686
- children: /* @__PURE__ */ jsx24(
9150
+ children: /* @__PURE__ */ jsx25(
8687
9151
  "svg",
8688
9152
  {
8689
9153
  width: "16",
@@ -8704,7 +9168,7 @@ function FloatingToolbar({
8704
9168
  );
8705
9169
  })
8706
9170
  ] }, gi)),
8707
- showEditLink ? /* @__PURE__ */ jsx24(
9171
+ showEditLink ? /* @__PURE__ */ jsx25(
8708
9172
  CustomToolbarButton,
8709
9173
  {
8710
9174
  type: "button",
@@ -8718,7 +9182,7 @@ function FloatingToolbar({
8718
9182
  e.preventDefault();
8719
9183
  e.stopPropagation();
8720
9184
  },
8721
- children: /* @__PURE__ */ jsx24(Link2, { className: "size-4 shrink-0", "aria-hidden": true })
9185
+ children: /* @__PURE__ */ jsx25(Link2, { className: "size-4 shrink-0", "aria-hidden": true })
8722
9186
  }
8723
9187
  ) : null
8724
9188
  ] })
@@ -8735,7 +9199,7 @@ function StateToggle({
8735
9199
  states,
8736
9200
  onStateChange
8737
9201
  }) {
8738
- return /* @__PURE__ */ jsx24(
9202
+ return /* @__PURE__ */ jsx25(
8739
9203
  ToggleGroup,
8740
9204
  {
8741
9205
  "data-ohw-state-toggle": "",
@@ -8749,7 +9213,7 @@ function StateToggle({
8749
9213
  left: rect.right - 8,
8750
9214
  transform: "translateX(-100%)"
8751
9215
  },
8752
- children: states.map((state) => /* @__PURE__ */ jsx24(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
9216
+ children: states.map((state) => /* @__PURE__ */ jsx25(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
8753
9217
  }
8754
9218
  );
8755
9219
  }
@@ -8805,7 +9269,7 @@ function OhhwellsBridge() {
8805
9269
  const subdomainFromQuery = searchParams.get("subdomain");
8806
9270
  const subdomain = resolveSubdomain(subdomainFromQuery);
8807
9271
  useLinkHrefGuardian(pathname, subdomain, isEditMode);
8808
- const postToParent2 = useCallback4((data) => {
9272
+ const postToParent2 = useCallback5((data) => {
8809
9273
  if (typeof window !== "undefined" && window.parent !== window) {
8810
9274
  window.parent.postMessage(data, "*");
8811
9275
  }
@@ -8813,6 +9277,7 @@ function OhhwellsBridge() {
8813
9277
  const [fetchState, setFetchState] = useState7("idle");
8814
9278
  const autoSaveTimers = useRef7(/* @__PURE__ */ new Map());
8815
9279
  const activeElRef = useRef7(null);
9280
+ const pointerHeldRef = useRef7(false);
8816
9281
  const selectedElRef = useRef7(null);
8817
9282
  const selectedHrefKeyRef = useRef7(null);
8818
9283
  const selectedFooterColAttrRef = useRef7(null);
@@ -8821,7 +9286,7 @@ function OhhwellsBridge() {
8821
9286
  const parentScrollRef = useRef7(null);
8822
9287
  const visibleViewportRef = useRef7(null);
8823
9288
  const [dialogPortalContainer, setDialogPortalContainer] = useState7(null);
8824
- const attachVisibleViewport = useCallback4((node) => {
9289
+ const attachVisibleViewport = useCallback5((node) => {
8825
9290
  visibleViewportRef.current = node;
8826
9291
  setDialogPortalContainer(node);
8827
9292
  if (node) applyVisibleViewport(node, parentScrollRef.current);
@@ -8934,7 +9399,7 @@ function OhhwellsBridge() {
8934
9399
  const bumpLinkPopoverGrace = () => {
8935
9400
  linkPopoverGraceUntilRef.current = Date.now() + 350;
8936
9401
  };
8937
- const runSectionsPrefetch = useCallback4((pages) => {
9402
+ const runSectionsPrefetch = useCallback5((pages) => {
8938
9403
  if (!isEditMode || shouldUseDevFixtures() || pages.length === 0) return;
8939
9404
  const gen = ++sectionsPrefetchGenRef.current;
8940
9405
  const paths = pages.map((p) => p.path);
@@ -8997,6 +9462,7 @@ function OhhwellsBridge() {
8997
9462
  document.addEventListener("touchmove", preventBackgroundScroll, scrollOpts);
8998
9463
  postToParent2({ type: "ow:image-unhover" });
8999
9464
  return () => {
9465
+ document.documentElement.removeAttribute("data-ohw-link-popover-open");
9000
9466
  postToParent2({ type: "ow:link-modal-lock", locked: false });
9001
9467
  html.style.overflow = prevHtmlOverflow;
9002
9468
  body.style.overflow = prevBodyOverflow;
@@ -9059,10 +9525,10 @@ function OhhwellsBridge() {
9059
9525
  vvp.removeEventListener("resize", update);
9060
9526
  };
9061
9527
  }, []);
9062
- const refreshStateRules = useCallback4(() => {
9528
+ const refreshStateRules = useCallback5(() => {
9063
9529
  editStylesRef.current?.forceHover && (editStylesRef.current.forceHover.textContent = collectStateRules());
9064
9530
  }, []);
9065
- const processConfigRequest = useCallback4((insertAfterVal) => {
9531
+ const processConfigRequest = useCallback5((insertAfterVal) => {
9066
9532
  const tracker = getSectionsTracker();
9067
9533
  let entries = [];
9068
9534
  try {
@@ -9085,7 +9551,7 @@ function OhhwellsBridge() {
9085
9551
  }
9086
9552
  window.postMessage({ type: "ow:schedule-config", insertAfter: insertAfterVal, scheduleId: null }, "*");
9087
9553
  }, [isEditMode]);
9088
- const deactivate = useCallback4(() => {
9554
+ const deactivate = useCallback5(() => {
9089
9555
  const el = activeElRef.current;
9090
9556
  if (!el) return;
9091
9557
  const key = el.dataset.ohwKey;
@@ -9117,12 +9583,12 @@ function OhhwellsBridge() {
9117
9583
  setToolbarShowEditLink(false);
9118
9584
  postToParent2({ type: "ow:exit-edit" });
9119
9585
  }, [postToParent2]);
9120
- const clearSelectedAttr = useCallback4(() => {
9586
+ const clearSelectedAttr = useCallback5(() => {
9121
9587
  document.querySelectorAll("[data-ohw-selected]").forEach((el) => {
9122
9588
  el.removeAttribute("data-ohw-selected");
9123
9589
  });
9124
9590
  }, []);
9125
- const deselect = useCallback4(() => {
9591
+ const deselect = useCallback5(() => {
9126
9592
  clearSelectedAttr();
9127
9593
  selectedElRef.current = null;
9128
9594
  selectedHrefKeyRef.current = null;
@@ -9142,11 +9608,11 @@ function OhhwellsBridge() {
9142
9608
  setToolbarVariant("none");
9143
9609
  }
9144
9610
  }, [clearSelectedAttr]);
9145
- const markSelected = useCallback4((el) => {
9611
+ const markSelected = useCallback5((el) => {
9146
9612
  clearSelectedAttr();
9147
9613
  el.setAttribute("data-ohw-selected", "");
9148
9614
  }, [clearSelectedAttr]);
9149
- const resolveHrefKeyElement = useCallback4((hrefKey) => {
9615
+ const resolveHrefKeyElement = useCallback5((hrefKey) => {
9150
9616
  if (isFooterHrefKey(hrefKey)) {
9151
9617
  return document.querySelector(
9152
9618
  `footer [data-ohw-href-key="${CSS.escape(hrefKey)}"]`
@@ -9161,7 +9627,7 @@ function OhhwellsBridge() {
9161
9627
  `[data-ohw-href-key="${CSS.escape(hrefKey)}"]`
9162
9628
  );
9163
9629
  }, []);
9164
- const resyncSelectedNavigationItem = useCallback4(() => {
9630
+ const resyncSelectedNavigationItem = useCallback5(() => {
9165
9631
  const hrefKey = selectedHrefKeyRef.current;
9166
9632
  if (hrefKey) {
9167
9633
  const link = resolveHrefKeyElement(hrefKey);
@@ -9189,7 +9655,7 @@ function OhhwellsBridge() {
9189
9655
  );
9190
9656
  }
9191
9657
  }, [resolveHrefKeyElement]);
9192
- const reselectNavigationItem = useCallback4((navAnchor) => {
9658
+ const reselectNavigationItem = useCallback5((navAnchor) => {
9193
9659
  selectedElRef.current = navAnchor;
9194
9660
  selectedHrefKeyRef.current = navAnchor.getAttribute("data-ohw-href-key");
9195
9661
  selectedFooterColAttrRef.current = null;
@@ -9204,7 +9670,7 @@ function OhhwellsBridge() {
9204
9670
  setToolbarShowEditLink(false);
9205
9671
  setActiveCommands(/* @__PURE__ */ new Set());
9206
9672
  }, [markSelected]);
9207
- const commitNavigationTextEdit = useCallback4((navAnchor) => {
9673
+ const commitNavigationTextEdit = useCallback5((navAnchor) => {
9208
9674
  const el = activeElRef.current;
9209
9675
  if (!el) return;
9210
9676
  const key = el.dataset.ohwKey;
@@ -9231,7 +9697,7 @@ function OhhwellsBridge() {
9231
9697
  postToParent2({ type: "ow:exit-edit" });
9232
9698
  reselectNavigationItem(navAnchor);
9233
9699
  }, [postToParent2, reselectNavigationItem]);
9234
- const handleAddTopLevelNavItem = useCallback4(() => {
9700
+ const handleAddTopLevelNavItem = useCallback5(() => {
9235
9701
  const items = listNavbarItems();
9236
9702
  addNavAfterAnchorRef.current = items[items.length - 1] ?? null;
9237
9703
  deselectRef.current();
@@ -9243,7 +9709,19 @@ function OhhwellsBridge() {
9243
9709
  intent: "add-nav"
9244
9710
  });
9245
9711
  }, []);
9246
- const clearFooterDragVisuals = useCallback4(() => {
9712
+ const handleAddFooterColumn = useCallback5(() => {
9713
+ deselectRef.current();
9714
+ deactivateRef.current();
9715
+ const result = addFooterColumnWithPersist({ postToParent: postToParent2 });
9716
+ editContentRef.current = {
9717
+ ...editContentRef.current,
9718
+ ...buildFooterColumnEditContentPatch(result)
9719
+ };
9720
+ requestAnimationFrame(() => {
9721
+ selectRef.current(result.firstLink);
9722
+ });
9723
+ }, [postToParent2]);
9724
+ const clearFooterDragVisuals = useCallback5(() => {
9247
9725
  footerDragRef.current = null;
9248
9726
  setSiblingHintRects([]);
9249
9727
  setFooterDropSlots([]);
@@ -9252,7 +9730,7 @@ function OhhwellsBridge() {
9252
9730
  setIsItemDragging(false);
9253
9731
  unlockFooterDragInteraction();
9254
9732
  }, []);
9255
- const refreshFooterDragVisuals = useCallback4((session, activeSlot, clientX, clientY) => {
9733
+ const refreshFooterDragVisuals = useCallback5((session, activeSlot, clientX, clientY) => {
9256
9734
  const dragged = session.draggedEl;
9257
9735
  setDraggedItemRect(dragged.getBoundingClientRect());
9258
9736
  if (typeof clientX === "number" && typeof clientY === "number") {
@@ -9283,7 +9761,7 @@ function OhhwellsBridge() {
9283
9761
  });
9284
9762
  const beginFooterDragRef = useRef7(() => {
9285
9763
  });
9286
- const beginFooterDrag = useCallback4(
9764
+ const beginFooterDrag = useCallback5(
9287
9765
  (session) => {
9288
9766
  const rect = session.draggedEl.getBoundingClientRect();
9289
9767
  session.lastClientX = session.lastClientX || rect.left + rect.width / 2;
@@ -9303,7 +9781,7 @@ function OhhwellsBridge() {
9303
9781
  [refreshFooterDragVisuals]
9304
9782
  );
9305
9783
  beginFooterDragRef.current = beginFooterDrag;
9306
- const commitFooterDrag = useCallback4(
9784
+ const commitFooterDrag = useCallback5(
9307
9785
  (clientX, clientY) => {
9308
9786
  const session = footerDragRef.current;
9309
9787
  if (!session) {
@@ -9407,7 +9885,7 @@ function OhhwellsBridge() {
9407
9885
  [clearFooterDragVisuals, resolveHrefKeyElement, resyncSelectedNavigationItem]
9408
9886
  );
9409
9887
  commitFooterDragRef.current = commitFooterDrag;
9410
- const startFooterLinkDrag = useCallback4(
9888
+ const startFooterLinkDrag = useCallback5(
9411
9889
  (anchor, clientX, clientY, wasSelected) => {
9412
9890
  const hrefKey = anchor.getAttribute("data-ohw-href-key");
9413
9891
  if (!hrefKey || !isFooterHrefKey(hrefKey)) return false;
@@ -9428,7 +9906,7 @@ function OhhwellsBridge() {
9428
9906
  },
9429
9907
  [beginFooterDrag]
9430
9908
  );
9431
- const startFooterColumnDrag = useCallback4(
9909
+ const startFooterColumnDrag = useCallback5(
9432
9910
  (columnEl, clientX, clientY, wasSelected) => {
9433
9911
  const columns = listFooterColumns();
9434
9912
  const idx = columns.indexOf(columnEl);
@@ -9448,7 +9926,7 @@ function OhhwellsBridge() {
9448
9926
  },
9449
9927
  [beginFooterDrag]
9450
9928
  );
9451
- const handleItemDragStart = useCallback4(
9929
+ const handleItemDragStart = useCallback5(
9452
9930
  (e) => {
9453
9931
  const selected = selectedElRef.current;
9454
9932
  if (!selected) {
@@ -9468,7 +9946,7 @@ function OhhwellsBridge() {
9468
9946
  },
9469
9947
  [startFooterColumnDrag, startFooterLinkDrag, startNavLinkDrag]
9470
9948
  );
9471
- const handleItemDragEnd = useCallback4(
9949
+ const handleItemDragEnd = useCallback5(
9472
9950
  (e) => {
9473
9951
  if (footerDragRef.current) {
9474
9952
  const x = e?.clientX;
@@ -9494,11 +9972,10 @@ function OhhwellsBridge() {
9494
9972
  },
9495
9973
  [commitFooterDrag, commitNavDrag, navDragRef]
9496
9974
  );
9497
- const handleItemChromePointerDown = useCallback4((e) => {
9975
+ const handleItemChromePointerDown = useCallback5((e) => {
9498
9976
  if (e.button !== 0) return;
9499
9977
  const selected = selectedElRef.current;
9500
9978
  if (!selected) return;
9501
- armFooterPressDrag();
9502
9979
  const hrefKey = selected.getAttribute("data-ohw-href-key");
9503
9980
  if (hrefKey && isFooterHrefKey(hrefKey)) {
9504
9981
  footerPointerDragRef.current = {
@@ -9526,7 +10003,7 @@ function OhhwellsBridge() {
9526
10003
  }
9527
10004
  if (armNavPressFromChrome(selected, e.clientX, e.clientY, e.pointerId)) return;
9528
10005
  }, [armNavPressFromChrome]);
9529
- const handleItemChromeClick = useCallback4((clientX, clientY) => {
10006
+ const handleItemChromeClick = useCallback5((clientX, clientY) => {
9530
10007
  if (suppressNextClickRef.current || Date.now() < suppressClickUntilRef.current) {
9531
10008
  suppressNextClickRef.current = false;
9532
10009
  return;
@@ -9539,7 +10016,7 @@ function OhhwellsBridge() {
9539
10016
  }, []);
9540
10017
  reselectNavigationItemRef.current = reselectNavigationItem;
9541
10018
  commitNavigationTextEditRef.current = commitNavigationTextEdit;
9542
- const select = useCallback4((anchor) => {
10019
+ const select = useCallback5((anchor) => {
9543
10020
  if (!isNavigationItem(anchor)) return;
9544
10021
  if (activeElRef.current) deactivate();
9545
10022
  selectedElRef.current = anchor;
@@ -9565,10 +10042,10 @@ function OhhwellsBridge() {
9565
10042
  setToolbarShowEditLink(false);
9566
10043
  setActiveCommands(/* @__PURE__ */ new Set());
9567
10044
  }, [deactivate, markSelected]);
9568
- const selectFrame = useCallback4((el) => {
10045
+ const selectFrame = useCallback5((el) => {
9569
10046
  if (!isNavigationContainer(el)) return;
9570
10047
  if (activeElRef.current) deactivate();
9571
- const isFooterColumn = el.hasAttribute("data-ohw-footer-col") || Boolean(el.closest("footer") && isInferredFooterGroup(el));
10048
+ const isFooterColumn = !isFooterLinksContainer(el) && (el.hasAttribute("data-ohw-footer-col") || Boolean(el.closest("footer") && isInferredFooterGroup(el)));
9572
10049
  selectedElRef.current = el;
9573
10050
  selectedHrefKeyRef.current = null;
9574
10051
  selectedFooterColAttrRef.current = isFooterColumn ? el.getAttribute("data-ohw-footer-col") ?? String(listFooterColumns().indexOf(el)) : null;
@@ -9593,7 +10070,7 @@ function OhhwellsBridge() {
9593
10070
  setToolbarShowEditLink(false);
9594
10071
  setActiveCommands(/* @__PURE__ */ new Set());
9595
10072
  }, [deactivate, markSelected]);
9596
- const activate = useCallback4((el, options) => {
10073
+ const activate = useCallback5((el, options) => {
9597
10074
  if (activeElRef.current === el) return;
9598
10075
  clearSelectedAttr();
9599
10076
  selectedElRef.current = null;
@@ -9608,7 +10085,10 @@ function OhhwellsBridge() {
9608
10085
  setToolbarVariant("rich-text");
9609
10086
  siblingHintElRef.current = null;
9610
10087
  setSiblingHintRect(null);
10088
+ setSiblingHintRects([]);
9611
10089
  setIsItemDragging(false);
10090
+ const preActivationSelection = window.getSelection();
10091
+ const preservedRange = preActivationSelection && preActivationSelection.rangeCount > 0 && !preActivationSelection.isCollapsed && el.contains(preActivationSelection.getRangeAt(0).commonAncestorContainer) ? preActivationSelection.getRangeAt(0).cloneRange() : null;
9612
10092
  el.setAttribute("contenteditable", "true");
9613
10093
  el.setAttribute("data-ohw-editing", "");
9614
10094
  el.removeAttribute("data-ohw-hovered");
@@ -9616,7 +10096,11 @@ function OhhwellsBridge() {
9616
10096
  activeElRef.current = el;
9617
10097
  originalContentRef.current = el.innerHTML;
9618
10098
  el.focus({ preventScroll: true });
9619
- if (options?.caretX !== void 0 && options?.caretY !== void 0) {
10099
+ if (preservedRange) {
10100
+ const selection = window.getSelection();
10101
+ selection?.removeAllRanges();
10102
+ selection?.addRange(preservedRange);
10103
+ } else if (options?.caretX !== void 0 && options?.caretY !== void 0) {
9620
10104
  const { caretX, caretY } = options;
9621
10105
  placeCaretAtPoint(el, caretX, caretY);
9622
10106
  requestAnimationFrame(() => {
@@ -10072,7 +10556,9 @@ function OhhwellsBridge() {
10072
10556
  if (inActive || inActiveNav) {
10073
10557
  e.preventDefault();
10074
10558
  e.stopPropagation();
10075
- if (e.detail < 2) {
10559
+ const selection = window.getSelection();
10560
+ const hasRangeSelection = Boolean(selection && !selection.isCollapsed);
10561
+ if (e.detail < 2 && !hasRangeSelection) {
10076
10562
  placeCaretAtPoint(active, e.clientX, e.clientY);
10077
10563
  refreshActiveCommandsRef.current();
10078
10564
  }
@@ -10115,7 +10601,7 @@ function OhhwellsBridge() {
10115
10601
  }
10116
10602
  e.preventDefault();
10117
10603
  e.stopPropagation();
10118
- activateRef.current(editable);
10604
+ activateRef.current(editable, { caretX: e.clientX, caretY: e.clientY });
10119
10605
  return;
10120
10606
  }
10121
10607
  const hrefAnchor = getNavigationItemAnchor(target);
@@ -10142,6 +10628,18 @@ function OhhwellsBridge() {
10142
10628
  selectFrameRef.current(footerColClick);
10143
10629
  return;
10144
10630
  }
10631
+ const footerLinksToSelect = resolveFooterLinksContainerSelectionTarget(
10632
+ target,
10633
+ e.clientX,
10634
+ e.clientY,
10635
+ isPointOverNavItem
10636
+ );
10637
+ if (footerLinksToSelect) {
10638
+ e.preventDefault();
10639
+ e.stopPropagation();
10640
+ selectFrameRef.current(footerLinksToSelect);
10641
+ return;
10642
+ }
10145
10643
  const navContainerToSelect = resolveNavContainerSelectionTarget(target, e.clientX, e.clientY);
10146
10644
  if (navContainerToSelect) {
10147
10645
  e.preventDefault();
@@ -10200,8 +10698,8 @@ function OhhwellsBridge() {
10200
10698
  return;
10201
10699
  }
10202
10700
  const navLabel = getNavigationLabelEditable(target);
10203
- if (!navLabel) return;
10204
- const { editable } = navLabel;
10701
+ const editable = navLabel?.editable ?? target.closest('[data-ohw-editable="text"], [data-ohw-editable="plain"]');
10702
+ if (!editable || isMediaEditable(editable) || editable.dataset.ohwEditable === "link") return;
10205
10703
  e.preventDefault();
10206
10704
  e.stopPropagation();
10207
10705
  if (activeElRef.current !== editable) {
@@ -10222,16 +10720,38 @@ function OhhwellsBridge() {
10222
10720
  setHoveredNavContainerRect(null);
10223
10721
  return;
10224
10722
  }
10225
- if (toolbarVariantRef.current !== "select-frame") {
10226
- const navContainer = target.closest("[data-ohw-nav-container]");
10227
- if (navContainer && !getNavigationItemAnchor(target)) {
10228
- hoveredNavContainerRef.current = navContainer;
10229
- setHoveredNavContainerRect(navContainer.getBoundingClientRect());
10230
- hoveredItemElRef.current = null;
10231
- setHoveredItemRect(null);
10232
- return;
10723
+ {
10724
+ const selected2 = selectedElRef.current;
10725
+ const selectedIsFooterColumn = Boolean(selected2) && !isFooterLinksContainer(selected2) && (selected2.hasAttribute("data-ohw-footer-col") || selected2.hasAttribute("data-ohw-footer-column") || Boolean(selected2.closest("footer") && isInferredFooterGroup(selected2)));
10726
+ const allowNavContainerHover = toolbarVariantRef.current !== "select-frame";
10727
+ const allowFooterLinksHover = toolbarVariantRef.current !== "select-frame" || selectedIsFooterColumn;
10728
+ if (allowNavContainerHover) {
10729
+ const navContainer = target.closest("[data-ohw-nav-container]");
10730
+ if (navContainer && !getNavigationItemAnchor(target)) {
10731
+ hoveredNavContainerRef.current = navContainer;
10732
+ setHoveredNavContainerRect(navContainer.getBoundingClientRect());
10733
+ hoveredItemElRef.current = null;
10734
+ setHoveredItemRect(null);
10735
+ return;
10736
+ }
10233
10737
  }
10234
- if (!target.closest("[data-ohw-nav-container]")) {
10738
+ if (allowFooterLinksHover) {
10739
+ const navContainer = target.closest("[data-ohw-nav-container]");
10740
+ if (!navContainer) {
10741
+ const footerLinks = target.closest("[data-ohw-footer-links], .rb-footer-links") ?? null;
10742
+ if (footerLinks && selected2 !== footerLinks && !getNavigationItemAnchor(target) && !target.closest("[data-ohw-footer-col], [data-ohw-footer-column]")) {
10743
+ hoveredNavContainerRef.current = footerLinks;
10744
+ setHoveredNavContainerRect(footerLinks.getBoundingClientRect());
10745
+ hoveredItemElRef.current = null;
10746
+ setHoveredItemRect(null);
10747
+ return;
10748
+ }
10749
+ if (!footerLinks) {
10750
+ hoveredNavContainerRef.current = null;
10751
+ setHoveredNavContainerRect(null);
10752
+ }
10753
+ }
10754
+ } else if (toolbarVariantRef.current === "select-frame") {
10235
10755
  hoveredNavContainerRef.current = null;
10236
10756
  setHoveredNavContainerRect(null);
10237
10757
  }
@@ -10273,9 +10793,9 @@ function OhhwellsBridge() {
10273
10793
  };
10274
10794
  const handleMouseOut = (e) => {
10275
10795
  const target = e.target;
10276
- if (target.closest("[data-ohw-nav-container], [data-ohw-footer-col], [data-ohw-footer-column]")) {
10796
+ if (target.closest("[data-ohw-nav-container], [data-ohw-footer-col], [data-ohw-footer-column], [data-ohw-footer-links], .rb-footer-links")) {
10277
10797
  const related2 = e.relatedTarget instanceof Element ? e.relatedTarget : null;
10278
- 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]")) {
10798
+ if (related2?.closest("[data-ohw-nav-container], [data-ohw-footer-col], [data-ohw-footer-column], [data-ohw-footer-links], .rb-footer-links, [data-ohw-navbar-container-chrome], [data-ohw-navbar-add-button], [data-ohw-footer-container-chrome], [data-ohw-footer-add-button]")) {
10279
10799
  return;
10280
10800
  }
10281
10801
  hoveredNavContainerRef.current = null;
@@ -10370,6 +10890,15 @@ function OhhwellsBridge() {
10370
10890
  if (track) track.setAttribute("data-ohw-hover-paused", "");
10371
10891
  };
10372
10892
  const clearImageHover = () => setMediaHover(null);
10893
+ const dismissImageHover = () => {
10894
+ if (hoveredImageRef.current) {
10895
+ hoveredImageRef.current = null;
10896
+ hoveredImageHasTextOverlapRef.current = false;
10897
+ resumeAnimTracks();
10898
+ postToParentRef.current({ type: "ow:image-unhover" });
10899
+ }
10900
+ clearImageHover();
10901
+ };
10373
10902
  const findImageAtPoint = (clientX, clientY, fromParentViewport) => {
10374
10903
  const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
10375
10904
  const images = Array.from(document.querySelectorAll(MEDIA_SELECTOR));
@@ -10413,16 +10942,13 @@ function OhhwellsBridge() {
10413
10942
  }
10414
10943
  return smallest;
10415
10944
  };
10416
- const dismissImageHover = () => {
10417
- if (hoveredImageRef.current) {
10418
- hoveredImageRef.current = null;
10419
- hoveredImageHasTextOverlapRef.current = false;
10420
- resumeAnimTracks();
10421
- postToParentRef.current({ type: "ow:image-unhover" });
10422
- }
10423
- };
10424
10945
  const probeNavigationHoverAt = (x, y) => {
10425
- if (toolbarVariantRef.current === "select-frame") {
10946
+ const selected = selectedElRef.current;
10947
+ const selectedIsFooterColumn = Boolean(selected) && !isFooterLinksContainer(selected) && (selected.hasAttribute("data-ohw-footer-col") || selected.hasAttribute("data-ohw-footer-column") || Boolean(selected.closest("footer") && isInferredFooterGroup(selected)));
10948
+ const selectedIsFooterLinks = Boolean(selected && isFooterLinksContainer(selected));
10949
+ const allowNavContainerHover = toolbarVariantRef.current !== "select-frame";
10950
+ const allowFooterLinksHover = (toolbarVariantRef.current !== "select-frame" || selectedIsFooterColumn) && !selectedIsFooterLinks;
10951
+ if (toolbarVariantRef.current === "select-frame" && !allowFooterLinksHover) {
10426
10952
  hoveredNavContainerRef.current = null;
10427
10953
  setHoveredNavContainerRect(null);
10428
10954
  }
@@ -10444,7 +10970,6 @@ function OhhwellsBridge() {
10444
10970
  if (navItemHit) {
10445
10971
  hoveredNavContainerRef.current = null;
10446
10972
  setHoveredNavContainerRect(null);
10447
- const selected = selectedElRef.current;
10448
10973
  if (selected !== navItemHit) {
10449
10974
  clearHrefKeyHover(navItemHit);
10450
10975
  hoveredItemElRef.current = navItemHit;
@@ -10456,7 +10981,7 @@ function OhhwellsBridge() {
10456
10981
  return;
10457
10982
  }
10458
10983
  }
10459
- if (toolbarVariantRef.current !== "select-frame") {
10984
+ if (allowNavContainerHover) {
10460
10985
  for (const container of navContainers) {
10461
10986
  const containerRect = container.getBoundingClientRect();
10462
10987
  const overContainer = x >= containerRect.left && x <= containerRect.right && y >= containerRect.top && y <= containerRect.bottom;
@@ -10468,12 +10993,23 @@ function OhhwellsBridge() {
10468
10993
  setHoveredItemRect(null);
10469
10994
  return;
10470
10995
  }
10471
- hoveredNavContainerRef.current = null;
10472
- setHoveredNavContainerRect(null);
10473
- } else {
10474
- hoveredNavContainerRef.current = null;
10475
- setHoveredNavContainerRect(null);
10476
10996
  }
10997
+ if (allowFooterLinksHover) {
10998
+ const footerLinks = getFooterLinksContainer();
10999
+ if (footerLinks) {
11000
+ const containerRect = footerLinks.getBoundingClientRect();
11001
+ const overContainer = x >= containerRect.left && x <= containerRect.right && y >= containerRect.top && y <= containerRect.bottom;
11002
+ if (overContainer && !isPointOverNavItem(footerLinks, x, y) && !isPointOverFooterColumn(x, y)) {
11003
+ hoveredNavContainerRef.current = footerLinks;
11004
+ setHoveredNavContainerRect(footerLinks.getBoundingClientRect());
11005
+ hoveredItemElRef.current = null;
11006
+ setHoveredItemRect(null);
11007
+ return;
11008
+ }
11009
+ }
11010
+ }
11011
+ hoveredNavContainerRef.current = null;
11012
+ setHoveredNavContainerRect(null);
10477
11013
  for (const column of footerColumns) {
10478
11014
  const containerRect = column.getBoundingClientRect();
10479
11015
  const overContainer = x >= containerRect.left && x <= containerRect.right && y >= containerRect.top && y <= containerRect.bottom;
@@ -10980,6 +11516,7 @@ function OhhwellsBridge() {
10980
11516
  }
10981
11517
  editContentRef.current = { ...editContentRef.current, ...content };
10982
11518
  reconcileNavbarItemsFromContent(editContentRef.current);
11519
+ reconcileFooterOrderFromContent(editContentRef.current);
10983
11520
  syncNavigationDragCursorAttrs();
10984
11521
  enforceLinkHrefs();
10985
11522
  postToParentRef.current({ type: "ow:hydrate-done" });
@@ -11034,6 +11571,11 @@ function OhhwellsBridge() {
11034
11571
  }
11035
11572
  if (selectedElRef.current) {
11036
11573
  if (toolbarVariantRef.current === "select-frame") {
11574
+ const parent2 = getNavigationSelectionParent(selectedElRef.current);
11575
+ if (parent2 && isNavigationContainer(parent2) && isFooterLinksContainer(parent2) && !isFooterLinksContainer(selectedElRef.current)) {
11576
+ selectFrameRef.current(parent2);
11577
+ return;
11578
+ }
11037
11579
  deselectRef.current();
11038
11580
  return;
11039
11581
  }
@@ -11049,13 +11591,10 @@ function OhhwellsBridge() {
11049
11591
  const handleKeyDown = (e) => {
11050
11592
  if (e.key === "Escape" && document.querySelector("[data-ohw-section-picker]")) return;
11051
11593
  if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "a" && activeElRef.current) {
11052
- const navAnchor = getNavigationItemAnchor(activeElRef.current);
11053
- if (navAnchor) {
11054
- e.preventDefault();
11055
- selectAllTextInEditable(activeElRef.current);
11056
- refreshActiveCommandsRef.current();
11057
- return;
11058
- }
11594
+ e.preventDefault();
11595
+ selectAllTextInEditable(activeElRef.current);
11596
+ refreshActiveCommandsRef.current();
11597
+ return;
11059
11598
  }
11060
11599
  if (e.key === "Escape" && linkPopoverOpenRef.current) {
11061
11600
  setLinkPopoverRef.current(null);
@@ -11063,6 +11602,12 @@ function OhhwellsBridge() {
11063
11602
  }
11064
11603
  if (e.key === "Escape" && selectedElRef.current && !activeElRef.current) {
11065
11604
  if (toolbarVariantRef.current === "select-frame") {
11605
+ const parent2 = getNavigationSelectionParent(selectedElRef.current);
11606
+ if (parent2 && isNavigationContainer(parent2) && isFooterLinksContainer(parent2) && !isFooterLinksContainer(selectedElRef.current)) {
11607
+ e.preventDefault();
11608
+ selectFrameRef.current(parent2);
11609
+ return;
11610
+ }
11066
11611
  deselectRef.current();
11067
11612
  return;
11068
11613
  }
@@ -11225,30 +11770,30 @@ function OhhwellsBridge() {
11225
11770
  const h = document.documentElement.scrollHeight;
11226
11771
  if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
11227
11772
  };
11773
+ let selectionChangeRaf = null;
11774
+ const runSelectionChange = () => {
11775
+ selectionChangeRaf = null;
11776
+ const activeRoot = activeElRef.current;
11777
+ if (!activeRoot) return;
11778
+ const startEl = resolveSelectionStartElement(window.getSelection());
11779
+ const next = startEl ? detectActiveFormats(startEl, activeRoot) : /* @__PURE__ */ new Set();
11780
+ setActiveCommands((prev) => setsEqual(prev, next) ? prev : next);
11781
+ };
11228
11782
  const handleSelectionChange = () => {
11229
- if (!activeElRef.current) return;
11230
- const next = /* @__PURE__ */ new Set();
11231
- for (const cmd of ["bold", "italic", "underline", "strikeThrough", "insertUnorderedList", "insertOrderedList"]) {
11232
- try {
11233
- if (document.queryCommandState(cmd)) next.add(cmd);
11234
- } catch {
11235
- }
11236
- }
11237
- const sel = window.getSelection();
11238
- const anchor = sel?.anchorNode;
11239
- if (anchor) {
11240
- const el = anchor.nodeType === Node.TEXT_NODE ? anchor.parentElement : anchor;
11241
- const block = el?.closest("div, p, h1, h2, h3, h4, h5, h6, li, td, th") ?? el;
11242
- if (block) {
11243
- const align = getComputedStyle(block).textAlign;
11244
- if (align === "center") next.add("justifyCenter");
11245
- else if (align === "right" || align === "end") next.add("justifyRight");
11246
- else next.add("justifyLeft");
11247
- }
11248
- }
11249
- setActiveCommands(next);
11783
+ if (pointerHeldRef.current) return;
11784
+ if (selectionChangeRaf !== null) return;
11785
+ selectionChangeRaf = requestAnimationFrame(runSelectionChange);
11786
+ };
11787
+ const markPointerHeld = (e) => {
11788
+ if (e.button !== 0) return;
11789
+ pointerHeldRef.current = true;
11250
11790
  };
11251
- refreshActiveCommandsRef.current = handleSelectionChange;
11791
+ const markPointerReleased = () => {
11792
+ if (!pointerHeldRef.current) return;
11793
+ pointerHeldRef.current = false;
11794
+ handleSelectionChange();
11795
+ };
11796
+ refreshActiveCommandsRef.current = runSelectionChange;
11252
11797
  const handleDocMouseLeave = () => {
11253
11798
  hoveredImageRef.current = null;
11254
11799
  setMediaHover(null);
@@ -11488,7 +12033,6 @@ function OhhwellsBridge() {
11488
12033
  const anchor = getNavigationItemAnchor(target);
11489
12034
  const hrefKey = anchor?.getAttribute("data-ohw-href-key") ?? null;
11490
12035
  if (anchor && isFooterHrefKey(hrefKey)) {
11491
- armFooterPressDrag();
11492
12036
  footerPointerDragRef.current = {
11493
12037
  el: anchor,
11494
12038
  kind: "link",
@@ -11502,7 +12046,6 @@ function OhhwellsBridge() {
11502
12046
  }
11503
12047
  const col = target.closest("[data-ohw-footer-col]");
11504
12048
  if (col && !getNavigationItemAnchor(target)) {
11505
- armFooterPressDrag();
11506
12049
  footerPointerDragRef.current = {
11507
12050
  el: col,
11508
12051
  kind: "column",
@@ -11531,6 +12074,7 @@ function OhhwellsBridge() {
11531
12074
  if (dx * dx + dy * dy < THRESHOLD * THRESHOLD) return;
11532
12075
  e.preventDefault();
11533
12076
  pending.started = true;
12077
+ armFooterPressDrag();
11534
12078
  clearTextSelection();
11535
12079
  try {
11536
12080
  document.body.setPointerCapture(pending.pointerId);
@@ -11584,7 +12128,8 @@ function OhhwellsBridge() {
11584
12128
  }
11585
12129
  } catch {
11586
12130
  }
11587
- if (!pending?.started) {
12131
+ if (!pending) return;
12132
+ if (!pending.started) {
11588
12133
  unlockFooterDragInteraction();
11589
12134
  return;
11590
12135
  }
@@ -11676,13 +12221,43 @@ function OhhwellsBridge() {
11676
12221
  window.addEventListener("hashchange", onHashChange);
11677
12222
  return () => window.removeEventListener("hashchange", onHashChange);
11678
12223
  }, [pathname]);
11679
- const handleCommand = useCallback4((cmd) => {
12224
+ const handleCommand = useCallback5((cmd) => {
12225
+ const el = activeElRef.current;
12226
+ const selBefore = window.getSelection();
12227
+ let savedOffsets = null;
12228
+ if (el && selBefore && selBefore.rangeCount > 0 && !selBefore.isCollapsed) {
12229
+ const r2 = selBefore.getRangeAt(0);
12230
+ if (el.contains(r2.commonAncestorContainer)) {
12231
+ savedOffsets = {
12232
+ start: textOffsetInRoot(el, r2.startContainer, r2.startOffset),
12233
+ end: textOffsetInRoot(el, r2.endContainer, r2.endOffset)
12234
+ };
12235
+ }
12236
+ }
12237
+ document.execCommand("styleWithCSS", false, true);
11680
12238
  document.execCommand(cmd, false);
11681
- activeElRef.current?.focus();
11682
- if (activeElRef.current) setToolbarRect(getEditMeasureEl(activeElRef.current).getBoundingClientRect());
12239
+ if (el && document.activeElement !== el) {
12240
+ el.focus();
12241
+ }
12242
+ const selAfter = window.getSelection();
12243
+ if (el && savedOffsets && selAfter?.isCollapsed) {
12244
+ const start = pointAtTextOffset(el, savedOffsets.start);
12245
+ const end = pointAtTextOffset(el, savedOffsets.end);
12246
+ if (start && end) {
12247
+ try {
12248
+ const range = document.createRange();
12249
+ range.setStart(start.node, start.offset);
12250
+ range.setEnd(end.node, end.offset);
12251
+ selAfter.removeAllRanges();
12252
+ selAfter.addRange(range);
12253
+ } catch {
12254
+ }
12255
+ }
12256
+ }
12257
+ if (el) setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
11683
12258
  refreshActiveCommandsRef.current();
11684
12259
  }, []);
11685
- const handleStateChange = useCallback4((state) => {
12260
+ const handleStateChange = useCallback5((state) => {
11686
12261
  if (!activeStateElRef.current) return;
11687
12262
  const el = activeStateElRef.current;
11688
12263
  if (state === "Default") {
@@ -11695,11 +12270,11 @@ function OhhwellsBridge() {
11695
12270
  }
11696
12271
  setToggleState((prev) => prev ? { ...prev, activeState: state } : null);
11697
12272
  }, [deactivate]);
11698
- const closeLinkPopover = useCallback4(() => {
12273
+ const closeLinkPopover = useCallback5(() => {
11699
12274
  addNavAfterAnchorRef.current = null;
11700
12275
  setLinkPopover(null);
11701
12276
  }, []);
11702
- const openLinkPopoverForActive = useCallback4(() => {
12277
+ const openLinkPopoverForActive = useCallback5(() => {
11703
12278
  const hrefCtx = getHrefKeyFromElement(activeElRef.current);
11704
12279
  if (!hrefCtx) return;
11705
12280
  bumpLinkPopoverGrace();
@@ -11710,7 +12285,7 @@ function OhhwellsBridge() {
11710
12285
  });
11711
12286
  deactivate();
11712
12287
  }, [deactivate]);
11713
- const openLinkPopoverForSelected = useCallback4(() => {
12288
+ const openLinkPopoverForSelected = useCallback5(() => {
11714
12289
  const anchor = selectedElRef.current;
11715
12290
  if (!anchor) return;
11716
12291
  const key = anchor.getAttribute("data-ohw-href-key");
@@ -11723,7 +12298,7 @@ function OhhwellsBridge() {
11723
12298
  });
11724
12299
  deselect();
11725
12300
  }, [deselect]);
11726
- const handleLinkPopoverSubmit = useCallback4(
12301
+ const handleLinkPopoverSubmit = useCallback5(
11727
12302
  (target) => {
11728
12303
  const session = linkPopoverSessionRef.current;
11729
12304
  if (!session) return;
@@ -11782,13 +12357,13 @@ function OhhwellsBridge() {
11782
12357
  const showEditLink = toolbarShowEditLink;
11783
12358
  const currentSections = sectionsByPath[pathname] ?? [];
11784
12359
  linkPopoverOpenRef.current = linkPopover !== null;
11785
- const handleMediaReplace = useCallback4(
12360
+ const handleMediaReplace = useCallback5(
11786
12361
  (key) => {
11787
12362
  postToParent2({ type: "ow:image-pick", key, elementType: mediaHover?.elementType ?? "image" });
11788
12363
  },
11789
12364
  [postToParent2, mediaHover?.elementType]
11790
12365
  );
11791
- const handleMediaFadeOutComplete = useCallback4((key) => {
12366
+ const handleMediaFadeOutComplete = useCallback5((key) => {
11792
12367
  setUploadingRects((prev) => {
11793
12368
  if (!(key in prev)) return prev;
11794
12369
  const next = { ...prev };
@@ -11796,7 +12371,7 @@ function OhhwellsBridge() {
11796
12371
  return next;
11797
12372
  });
11798
12373
  }, []);
11799
- const handleVideoSettingsChange = useCallback4(
12374
+ const handleVideoSettingsChange = useCallback5(
11800
12375
  (key, settings) => {
11801
12376
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
11802
12377
  const video = getVideoEl(el);
@@ -11819,9 +12394,9 @@ function OhhwellsBridge() {
11819
12394
  [postToParent2]
11820
12395
  );
11821
12396
  return bridgeRoot ? createPortal2(
11822
- /* @__PURE__ */ jsxs13(Fragment5, { children: [
11823
- /* @__PURE__ */ jsx24("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
11824
- Object.entries(uploadingRects).map(([key, { rect, fadingOut }]) => /* @__PURE__ */ jsx24(
12397
+ /* @__PURE__ */ jsxs14(Fragment5, { children: [
12398
+ /* @__PURE__ */ jsx25("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
12399
+ Object.entries(uploadingRects).map(([key, { rect, fadingOut }]) => /* @__PURE__ */ jsx25(
11825
12400
  MediaOverlay,
11826
12401
  {
11827
12402
  hover: { key, rect, elementType: "image", isDragOver: false, hasTextOverlap: false },
@@ -11832,7 +12407,7 @@ function OhhwellsBridge() {
11832
12407
  },
11833
12408
  `uploading-${key}`
11834
12409
  )),
11835
- mediaHover && !(mediaHover.key in uploadingRects) && /* @__PURE__ */ jsx24(
12410
+ mediaHover && !(mediaHover.key in uploadingRects) && /* @__PURE__ */ jsx25(
11836
12411
  MediaOverlay,
11837
12412
  {
11838
12413
  hover: mediaHover,
@@ -11841,10 +12416,10 @@ function OhhwellsBridge() {
11841
12416
  onVideoSettingsChange: handleVideoSettingsChange
11842
12417
  }
11843
12418
  ),
11844
- siblingHintRect && !linkPopover && !isItemDragging && siblingHintRects.length === 0 && /* @__PURE__ */ jsx24(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
11845
- siblingHintRects.length > 0 && !linkPopover && !isItemDragging && siblingHintRects.map((rect, i) => /* @__PURE__ */ jsx24(ItemInteractionLayer, { rect, state: "sibling-hint" }, `sibling-hint-${i}`)),
11846
- isItemDragging && draggedItemRect && selectedElRef.current !== footerDragRef.current?.draggedEl && selectedElRef.current !== navDragRef.current?.draggedEl && /* @__PURE__ */ jsx24(ItemInteractionLayer, { rect: draggedItemRect, state: "dragging" }),
11847
- isItemDragging && footerDropSlots.map((slot, i) => /* @__PURE__ */ jsx24(
12419
+ siblingHintRect && !linkPopover && !isItemDragging && siblingHintRects.length === 0 && /* @__PURE__ */ jsx25(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
12420
+ siblingHintRects.length > 0 && !linkPopover && !isItemDragging && siblingHintRects.map((rect, i) => /* @__PURE__ */ jsx25(ItemInteractionLayer, { rect, state: "sibling-hint" }, `sibling-hint-${i}`)),
12421
+ isItemDragging && draggedItemRect && selectedElRef.current !== footerDragRef.current?.draggedEl && selectedElRef.current !== navDragRef.current?.draggedEl && /* @__PURE__ */ jsx25(ItemInteractionLayer, { rect: draggedItemRect, state: "dragging" }),
12422
+ isItemDragging && footerDropSlots.map((slot, i) => /* @__PURE__ */ jsx25(
11848
12423
  "div",
11849
12424
  {
11850
12425
  className: "pointer-events-none fixed z-2147483646",
@@ -11854,7 +12429,7 @@ function OhhwellsBridge() {
11854
12429
  width: slot.width,
11855
12430
  height: slot.height
11856
12431
  },
11857
- children: /* @__PURE__ */ jsx24(
12432
+ children: /* @__PURE__ */ jsx25(
11858
12433
  DropIndicator,
11859
12434
  {
11860
12435
  direction: slot.direction,
@@ -11865,7 +12440,7 @@ function OhhwellsBridge() {
11865
12440
  },
11866
12441
  `footer-drop-${slot.direction}-${slot.columnIndex}-${slot.insertIndex}-${i}`
11867
12442
  )),
11868
- isItemDragging && navDropSlots.map((slot, i) => /* @__PURE__ */ jsx24(
12443
+ isItemDragging && navDropSlots.map((slot, i) => /* @__PURE__ */ jsx25(
11869
12444
  "div",
11870
12445
  {
11871
12446
  className: "pointer-events-none fixed z-2147483646",
@@ -11875,7 +12450,7 @@ function OhhwellsBridge() {
11875
12450
  width: slot.width,
11876
12451
  height: slot.height
11877
12452
  },
11878
- children: /* @__PURE__ */ jsx24(
12453
+ children: /* @__PURE__ */ jsx25(
11879
12454
  DropIndicator,
11880
12455
  {
11881
12456
  direction: slot.direction,
@@ -11886,10 +12461,11 @@ function OhhwellsBridge() {
11886
12461
  },
11887
12462
  `nav-drop-${slot.direction}-${slot.parentId ?? "root"}-${slot.insertIndex}-${i}`
11888
12463
  )),
11889
- hoveredNavContainerRect && toolbarVariant !== "select-frame" && !linkPopover && !isItemDragging && /* @__PURE__ */ jsx24(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
11890
- hoveredItemRect && !linkPopover && !hoveredNavContainerRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ jsx24(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
11891
- toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isNavbarLinksContainer(selectedElRef.current) && /* @__PURE__ */ jsx24(NavbarContainerChrome, { rect: toolbarRect, onAdd: handleAddTopLevelNavItem }),
11892
- toolbarRect && !linkPopover && (toolbarVariant === "link-action" || toolbarVariant === "select-frame") && /* @__PURE__ */ jsx24(
12464
+ hoveredNavContainerRect && (toolbarVariant !== "select-frame" || isFooterFrameSelection) && !linkPopover && !isItemDragging && /* @__PURE__ */ jsx25(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
12465
+ hoveredItemRect && !linkPopover && !hoveredNavContainerRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ jsx25(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
12466
+ toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isNavbarLinksContainer(selectedElRef.current) && /* @__PURE__ */ jsx25(NavbarContainerChrome, { rect: toolbarRect, onAdd: handleAddTopLevelNavItem }),
12467
+ toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isFooterLinksContainer(selectedElRef.current) && /* @__PURE__ */ jsx25(FooterContainerChrome, { rect: toolbarRect, onAdd: handleAddFooterColumn }),
12468
+ toolbarRect && !linkPopover && (toolbarVariant === "link-action" || toolbarVariant === "select-frame") && /* @__PURE__ */ jsx25(
11893
12469
  ItemInteractionLayer,
11894
12470
  {
11895
12471
  rect: isItemDragging && draggedItemRect && (footerDragRef.current?.wasSelected || navDragRef.current?.wasSelected) ? draggedItemRect : toolbarRect,
@@ -11903,7 +12479,7 @@ function OhhwellsBridge() {
11903
12479
  onItemPointerDown: handleItemChromePointerDown,
11904
12480
  onItemClick: handleItemChromeClick,
11905
12481
  itemDragSurface: !isFooterFrameSelection,
11906
- toolbar: toolbarVariant === "link-action" && !isItemDragging ? /* @__PURE__ */ jsx24(
12482
+ toolbar: toolbarVariant === "link-action" && !isItemDragging ? /* @__PURE__ */ jsx25(
11907
12483
  ItemActionToolbar,
11908
12484
  {
11909
12485
  onEditLink: openLinkPopoverForSelected,
@@ -11916,8 +12492,8 @@ function OhhwellsBridge() {
11916
12492
  ) : void 0
11917
12493
  }
11918
12494
  ),
11919
- toolbarRect && toolbarVariant === "rich-text" && !linkPopover && /* @__PURE__ */ jsxs13(Fragment5, { children: [
11920
- /* @__PURE__ */ jsx24(
12495
+ toolbarRect && toolbarVariant === "rich-text" && !linkPopover && /* @__PURE__ */ jsxs14(Fragment5, { children: [
12496
+ /* @__PURE__ */ jsx25(
11921
12497
  EditGlowChrome,
11922
12498
  {
11923
12499
  rect: toolbarRect,
@@ -11927,7 +12503,7 @@ function OhhwellsBridge() {
11927
12503
  hideHandle: isItemDragging
11928
12504
  }
11929
12505
  ),
11930
- /* @__PURE__ */ jsx24(
12506
+ /* @__PURE__ */ jsx25(
11931
12507
  FloatingToolbar,
11932
12508
  {
11933
12509
  rect: toolbarRect,
@@ -11940,7 +12516,7 @@ function OhhwellsBridge() {
11940
12516
  }
11941
12517
  )
11942
12518
  ] }),
11943
- maxBadge && /* @__PURE__ */ jsxs13(
12519
+ maxBadge && /* @__PURE__ */ jsxs14(
11944
12520
  "div",
11945
12521
  {
11946
12522
  "data-ohw-max-badge": "",
@@ -11966,7 +12542,7 @@ function OhhwellsBridge() {
11966
12542
  ]
11967
12543
  }
11968
12544
  ),
11969
- toggleState && !linkPopover && /* @__PURE__ */ jsx24(
12545
+ toggleState && !linkPopover && /* @__PURE__ */ jsx25(
11970
12546
  StateToggle,
11971
12547
  {
11972
12548
  rect: toggleState.rect,
@@ -11975,15 +12551,15 @@ function OhhwellsBridge() {
11975
12551
  onStateChange: handleStateChange
11976
12552
  }
11977
12553
  ),
11978
- sectionGap && !linkPopover && /* @__PURE__ */ jsxs13(
12554
+ sectionGap && !linkPopover && /* @__PURE__ */ jsxs14(
11979
12555
  "div",
11980
12556
  {
11981
12557
  "data-ohw-section-insert-line": "",
11982
12558
  className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
11983
12559
  style: { top: sectionGap.y, transform: "translateY(-50%)" },
11984
12560
  children: [
11985
- /* @__PURE__ */ jsx24("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
11986
- /* @__PURE__ */ jsx24(
12561
+ /* @__PURE__ */ jsx25("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
12562
+ /* @__PURE__ */ jsx25(
11987
12563
  Badge,
11988
12564
  {
11989
12565
  className: "px-8 py-1 bg-primary hover:bg-primary text-primary-foreground text-xs font-medium shrink-0 rounded-full cursor-pointer pointer-events-auto",
@@ -12000,11 +12576,11 @@ function OhhwellsBridge() {
12000
12576
  children: "Add Section"
12001
12577
  }
12002
12578
  ),
12003
- /* @__PURE__ */ jsx24("div", { className: "flex-1 bg-primary", style: { height: 3 } })
12579
+ /* @__PURE__ */ jsx25("div", { className: "flex-1 bg-primary", style: { height: 3 } })
12004
12580
  ]
12005
12581
  }
12006
12582
  ),
12007
- linkPopover && dialogPortalContainer ? /* @__PURE__ */ jsx24(
12583
+ linkPopover && dialogPortalContainer ? /* @__PURE__ */ jsx25(
12008
12584
  LinkPopover,
12009
12585
  {
12010
12586
  panelRef: linkPopoverPanelRef,