@ohhwells/bridge 0.1.38-next.62 → 0.1.38-next.64

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -573,7 +573,7 @@ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal
573
573
  }
574
574
  )
575
575
  ] }),
576
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "sm:hidden flex flex-col gap-px", children: isFull ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
576
+ cls.showAvailability !== false && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "sm:hidden flex flex-col gap-px", children: isFull ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
577
577
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
578
578
  available,
579
579
  "/",
@@ -585,9 +585,10 @@ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal
585
585
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-1 flex-col gap-2 min-w-0 sm:contents", children: [
586
586
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex-1 flex flex-col gap-2 min-w-0", children: [
587
587
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block truncate", children: cls.name }),
588
- cls.hostName && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-normal text-(--color-dark,#200C02) opacity-80 block truncate", children: cls.hostName })
588
+ cls.hostName && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-normal text-(--color-dark,#200C02) opacity-80 block truncate", children: cls.hostName }),
589
+ cls.description && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm font-normal text-(--color-dark,#200C02) opacity-70 block truncate", children: cls.description })
589
590
  ] }),
590
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hidden sm:flex w-14 shrink-0 flex-col gap-px", children: isFull ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
591
+ cls.showAvailability !== false && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hidden sm:flex w-14 shrink-0 flex-col gap-px", children: isFull ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
591
592
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
592
593
  available,
593
594
  "/",
@@ -4554,7 +4555,7 @@ function getChromeStyle(state) {
4554
4555
  case "dragging":
4555
4556
  return {
4556
4557
  border: `2px solid ${PRIMARY}`,
4557
- boxShadow: DRAG_SHADOW
4558
+ boxShadow: `${FOCUS_RING}, ${DRAG_SHADOW}`
4558
4559
  };
4559
4560
  default:
4560
4561
  return {};
@@ -4570,6 +4571,8 @@ function ItemInteractionLayer({
4570
4571
  dragHandleLabel = "Reorder item",
4571
4572
  onDragHandleDragStart,
4572
4573
  onDragHandleDragEnd,
4574
+ onItemPointerDown,
4575
+ onItemClick,
4573
4576
  chromeGap = 6,
4574
4577
  className
4575
4578
  }) {
@@ -4577,7 +4580,8 @@ function ItemInteractionLayer({
4577
4580
  const isActive = state === "active-top" || state === "active-bottom";
4578
4581
  const isDragging = state === "dragging";
4579
4582
  const showToolbar = isActive && toolbar;
4580
- const showDragHandle = isActive && showHandle && !isDragging;
4583
+ const showDragHandle = (isActive || isDragging) && showHandle;
4584
+ const itemDragEnabled = isActive && showHandle && !isDragging && !dragDisabled;
4581
4585
  return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
4582
4586
  "div",
4583
4587
  {
@@ -4602,11 +4606,28 @@ function ItemInteractionLayer({
4602
4606
  style: getChromeStyle(state)
4603
4607
  }
4604
4608
  ),
4609
+ itemDragEnabled && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4610
+ "div",
4611
+ {
4612
+ "data-ohw-item-drag-surface": "",
4613
+ className: "pointer-events-auto absolute inset-0 cursor-grab rounded-lg active:cursor-grabbing",
4614
+ onPointerDown: (e) => {
4615
+ if (e.button !== 0) return;
4616
+ onItemPointerDown?.(e);
4617
+ },
4618
+ onClick: (e) => {
4619
+ e.preventDefault();
4620
+ e.stopPropagation();
4621
+ onItemClick?.(e.clientX, e.clientY);
4622
+ }
4623
+ }
4624
+ ),
4605
4625
  showDragHandle && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4606
4626
  "div",
4607
4627
  {
4608
4628
  "data-ohw-drag-handle-container": "",
4609
- className: "pointer-events-auto absolute left-0 top-1/2 -translate-x-[calc(100%+7px)] -translate-y-1/2",
4629
+ className: "pointer-events-auto absolute left-0 top-1/2 z-10 -translate-x-[calc(100%+7px)] -translate-y-1/2",
4630
+ style: isDragging ? { visibility: "hidden" } : void 0,
4610
4631
  children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4611
4632
  DragHandle,
4612
4633
  {
@@ -6608,6 +6629,319 @@ function insertNavbarItem(href, label, afterAnchor = null) {
6608
6629
  };
6609
6630
  }
6610
6631
 
6632
+ // src/lib/footer-items.ts
6633
+ var FOOTER_ORDER_KEY = "__ohw_footer_order";
6634
+ var FOOTER_HREF_RE = /^footer-(\d+)-(\d+)-href$/;
6635
+ function parseFooterHrefKey(key) {
6636
+ if (!key) return null;
6637
+ const match = key.match(FOOTER_HREF_RE);
6638
+ if (!match) return null;
6639
+ return { col: parseInt(match[1], 10), item: parseInt(match[2], 10) };
6640
+ }
6641
+ function isFooterHrefKey(key) {
6642
+ return parseFooterHrefKey(key) !== null;
6643
+ }
6644
+ function getFooterRoot() {
6645
+ return document.querySelector('footer[data-ohw-section="footer"], footer');
6646
+ }
6647
+ function getFooterLinksContainer() {
6648
+ return document.querySelector("[data-ohw-footer-links]") ?? document.querySelector(".rb-footer-links");
6649
+ }
6650
+ function isFooterLinkAnchor(el) {
6651
+ if (!el.matches("[data-ohw-href-key]")) return false;
6652
+ if (!isFooterHrefKey(el.getAttribute("data-ohw-href-key"))) return false;
6653
+ return Boolean(el.querySelector('[data-ohw-editable="text"]'));
6654
+ }
6655
+ function listFooterColumns() {
6656
+ const root = getFooterLinksContainer() ?? getFooterRoot()?.querySelector(".rb-footer-links") ?? null;
6657
+ if (!root) return [];
6658
+ const explicit = Array.from(root.querySelectorAll(":scope > [data-ohw-footer-col]"));
6659
+ if (explicit.length > 0) return explicit;
6660
+ return Array.from(root.children).filter((el) => {
6661
+ if (!(el instanceof HTMLElement)) return false;
6662
+ return Array.from(el.querySelectorAll(":scope > [data-ohw-href-key]")).some(
6663
+ isFooterLinkAnchor
6664
+ );
6665
+ });
6666
+ }
6667
+ function listFooterLinksInColumn(column) {
6668
+ return Array.from(column.querySelectorAll(":scope > [data-ohw-href-key]")).filter(
6669
+ isFooterLinkAnchor
6670
+ );
6671
+ }
6672
+ function findFooterColumnForLink(anchor) {
6673
+ const columns = listFooterColumns();
6674
+ return columns.find((col) => col.contains(anchor)) ?? null;
6675
+ }
6676
+ function getFooterOrderFromDom() {
6677
+ return listFooterColumns().map(
6678
+ (col) => listFooterLinksInColumn(col).map((el) => el.getAttribute("data-ohw-href-key")).filter((key) => Boolean(key))
6679
+ );
6680
+ }
6681
+ function findFooterLinkByKey(hrefKey) {
6682
+ return document.querySelector(
6683
+ `footer [data-ohw-href-key="${CSS.escape(hrefKey)}"]`
6684
+ );
6685
+ }
6686
+ function syncFooterColumnIndices(columns) {
6687
+ columns.forEach((col, index) => {
6688
+ const next = String(index);
6689
+ if (col.getAttribute("data-ohw-footer-col") !== next) {
6690
+ col.setAttribute("data-ohw-footer-col", next);
6691
+ }
6692
+ });
6693
+ }
6694
+ function appendChildIfNeeded(parent, child) {
6695
+ if (parent.lastElementChild !== child) {
6696
+ parent.appendChild(child);
6697
+ }
6698
+ }
6699
+ function insertLinkBeforeNext(col, el, nextEl) {
6700
+ if (nextEl) {
6701
+ if (el.nextElementSibling !== nextEl) {
6702
+ col.insertBefore(el, nextEl);
6703
+ }
6704
+ return;
6705
+ }
6706
+ if (col.lastElementChild !== el) {
6707
+ col.appendChild(el);
6708
+ }
6709
+ }
6710
+ function applyFooterOrder(order) {
6711
+ const container = getFooterLinksContainer();
6712
+ if (!container) return;
6713
+ const columns = listFooterColumns();
6714
+ if (columns.length === 0) return;
6715
+ const currentOrder = getFooterOrderFromDom();
6716
+ if (ordersEqual(order, currentOrder)) {
6717
+ syncFooterColumnIndices(columns);
6718
+ return;
6719
+ }
6720
+ const colCount = Math.max(columns.length, order.length);
6721
+ const working = columns.slice(0, colCount);
6722
+ for (let i = 0; i < order.length && i < working.length; i++) {
6723
+ appendChildIfNeeded(container, working[i]);
6724
+ }
6725
+ for (let i = order.length; i < working.length; i++) {
6726
+ appendChildIfNeeded(container, working[i]);
6727
+ }
6728
+ const freshColumns = listFooterColumns();
6729
+ syncFooterColumnIndices(freshColumns);
6730
+ for (let c = 0; c < order.length; c++) {
6731
+ const col = freshColumns[c];
6732
+ if (!col) continue;
6733
+ const colOrder = order[c];
6734
+ for (let j = 0; j < colOrder.length; j++) {
6735
+ const hrefKey = colOrder[j];
6736
+ const el = findFooterLinkByKey(hrefKey);
6737
+ if (!el) continue;
6738
+ if (el.parentElement !== col) {
6739
+ col.appendChild(el);
6740
+ }
6741
+ const nextKey = colOrder[j + 1];
6742
+ const nextEl = nextKey ? findFooterLinkByKey(nextKey) : null;
6743
+ insertLinkBeforeNext(col, el, nextEl?.parentElement === col ? nextEl : null);
6744
+ }
6745
+ }
6746
+ }
6747
+ function ordersEqual(a, b) {
6748
+ if (a.length !== b.length) return false;
6749
+ for (let i = 0; i < a.length; i++) {
6750
+ const left = a[i];
6751
+ const right = b[i];
6752
+ if (left.length !== right.length) return false;
6753
+ for (let j = 0; j < left.length; j++) {
6754
+ if (left[j] !== right[j]) return false;
6755
+ }
6756
+ }
6757
+ return true;
6758
+ }
6759
+ function planFooterLinkMove(hrefKey, targetColIndex, insertIndex) {
6760
+ const order = getFooterOrderFromDom();
6761
+ if (targetColIndex < 0 || targetColIndex >= order.length) return null;
6762
+ let fromCol = -1;
6763
+ let fromIdx = -1;
6764
+ for (let c = 0; c < order.length; c++) {
6765
+ const idx = order[c].indexOf(hrefKey);
6766
+ if (idx >= 0) {
6767
+ fromCol = c;
6768
+ fromIdx = idx;
6769
+ break;
6770
+ }
6771
+ }
6772
+ if (fromCol < 0) return null;
6773
+ const next = order.map((col) => [...col]);
6774
+ next[fromCol].splice(fromIdx, 1);
6775
+ let adjusted = insertIndex;
6776
+ if (fromCol === targetColIndex && fromIdx < insertIndex) adjusted -= 1;
6777
+ adjusted = Math.max(0, Math.min(adjusted, next[targetColIndex].length));
6778
+ next[targetColIndex].splice(adjusted, 0, hrefKey);
6779
+ if (ordersEqual(next, order)) return null;
6780
+ return next;
6781
+ }
6782
+ function planFooterColumnMove(fromIndex, toIndex) {
6783
+ const order = getFooterOrderFromDom();
6784
+ const columns = listFooterColumns();
6785
+ if (fromIndex < 0 || fromIndex >= columns.length || toIndex < 0 || toIndex > columns.length) {
6786
+ return null;
6787
+ }
6788
+ if (toIndex === fromIndex || toIndex === fromIndex + 1) return null;
6789
+ const nextOrder = order.map((col) => [...col]);
6790
+ const [movedOrder] = nextOrder.splice(fromIndex, 1);
6791
+ if (!movedOrder) return null;
6792
+ let adjusted = toIndex;
6793
+ if (fromIndex < toIndex) adjusted -= 1;
6794
+ adjusted = Math.max(0, Math.min(adjusted, nextOrder.length));
6795
+ nextOrder.splice(adjusted, 0, movedOrder);
6796
+ if (ordersEqual(nextOrder, order)) return null;
6797
+ return nextOrder;
6798
+ }
6799
+ function parseFooterOrder(content) {
6800
+ const raw = content[FOOTER_ORDER_KEY];
6801
+ if (!raw) return null;
6802
+ try {
6803
+ const parsed = JSON.parse(raw);
6804
+ if (!Array.isArray(parsed)) return null;
6805
+ return parsed.filter((col) => Array.isArray(col)).map((col) => col.filter((key) => typeof key === "string" && key.length > 0));
6806
+ } catch {
6807
+ return null;
6808
+ }
6809
+ }
6810
+ function reconcileFooterOrderFromContent(content) {
6811
+ const order = parseFooterOrder(content);
6812
+ if (!order?.length) return;
6813
+ const current = getFooterOrderFromDom();
6814
+ if (ordersEqual(order, current)) {
6815
+ syncFooterColumnIndices(listFooterColumns());
6816
+ return;
6817
+ }
6818
+ applyFooterOrder(order);
6819
+ }
6820
+ function buildLinkDropSlots(column, columnIndex) {
6821
+ const links = listFooterLinksInColumn(column);
6822
+ const colRect = column.getBoundingClientRect();
6823
+ const slots = [];
6824
+ const barThickness = 3;
6825
+ if (links.length === 0) {
6826
+ slots.push({
6827
+ insertIndex: 0,
6828
+ columnIndex,
6829
+ left: colRect.left,
6830
+ top: colRect.top + colRect.height / 2 - barThickness / 2,
6831
+ width: colRect.width,
6832
+ height: barThickness,
6833
+ direction: "horizontal"
6834
+ });
6835
+ return slots;
6836
+ }
6837
+ for (let i = 0; i <= links.length; i++) {
6838
+ let top;
6839
+ if (i === 0) {
6840
+ top = links[0].getBoundingClientRect().top - barThickness / 2;
6841
+ } else if (i === links.length) {
6842
+ const last = links[links.length - 1].getBoundingClientRect();
6843
+ top = last.bottom - barThickness / 2;
6844
+ } else {
6845
+ const prev = links[i - 1].getBoundingClientRect();
6846
+ const next = links[i].getBoundingClientRect();
6847
+ top = (prev.bottom + next.top) / 2 - barThickness / 2;
6848
+ }
6849
+ const widthRef = i === 0 ? links[0].getBoundingClientRect() : i === links.length ? links[links.length - 1].getBoundingClientRect() : links[i].getBoundingClientRect();
6850
+ slots.push({
6851
+ insertIndex: i,
6852
+ columnIndex,
6853
+ left: widthRef.left,
6854
+ top,
6855
+ width: Math.max(widthRef.width, colRect.width * 0.8),
6856
+ height: barThickness,
6857
+ direction: "horizontal"
6858
+ });
6859
+ }
6860
+ return slots;
6861
+ }
6862
+ function buildColumnDropSlots() {
6863
+ const columns = listFooterColumns();
6864
+ const slots = [];
6865
+ const barThickness = 3;
6866
+ if (columns.length === 0) return slots;
6867
+ for (let i = 0; i <= columns.length; i++) {
6868
+ let left;
6869
+ let height;
6870
+ let top;
6871
+ if (i === 0) {
6872
+ const first = columns[0].getBoundingClientRect();
6873
+ left = first.left - barThickness / 2;
6874
+ top = first.top;
6875
+ height = first.height;
6876
+ } else if (i === columns.length) {
6877
+ const last = columns[columns.length - 1].getBoundingClientRect();
6878
+ left = last.right - barThickness / 2;
6879
+ top = last.top;
6880
+ height = last.height;
6881
+ } else {
6882
+ const prev = columns[i - 1].getBoundingClientRect();
6883
+ const next = columns[i].getBoundingClientRect();
6884
+ left = (prev.right + next.left) / 2 - barThickness / 2;
6885
+ top = Math.min(prev.top, next.top);
6886
+ height = Math.max(prev.bottom, next.bottom) - top;
6887
+ }
6888
+ slots.push({
6889
+ insertIndex: i,
6890
+ columnIndex: i,
6891
+ left,
6892
+ top,
6893
+ width: barThickness,
6894
+ height: Math.max(height, 24),
6895
+ direction: "vertical"
6896
+ });
6897
+ }
6898
+ return slots;
6899
+ }
6900
+ function hitTestLinkDropSlot(clientX, clientY, draggedHrefKey) {
6901
+ const columns = listFooterColumns();
6902
+ let best = null;
6903
+ for (let c = 0; c < columns.length; c++) {
6904
+ const col = columns[c];
6905
+ const colRect = col.getBoundingClientRect();
6906
+ const inColX = clientX >= colRect.left - 24 && clientX <= colRect.right + 24;
6907
+ if (!inColX) continue;
6908
+ const slots = buildLinkDropSlots(col, c).filter((slot) => {
6909
+ const links = listFooterLinksInColumn(col);
6910
+ const fromIdx = links.findIndex(
6911
+ (el) => el.getAttribute("data-ohw-href-key") === draggedHrefKey
6912
+ );
6913
+ if (fromIdx < 0) return true;
6914
+ if (c === findColumnIndexForKey(draggedHrefKey) && (slot.insertIndex === fromIdx || slot.insertIndex === fromIdx + 1)) {
6915
+ return true;
6916
+ }
6917
+ return true;
6918
+ });
6919
+ for (const slot of slots) {
6920
+ const cy = slot.top + slot.height / 2;
6921
+ const dist = Math.abs(clientY - cy) + (inColX ? 0 : 1e3);
6922
+ if (!best || dist < best.dist) best = { slot, dist };
6923
+ }
6924
+ }
6925
+ return best?.slot ?? null;
6926
+ }
6927
+ function findColumnIndexForKey(hrefKey) {
6928
+ const order = getFooterOrderFromDom();
6929
+ for (let c = 0; c < order.length; c++) {
6930
+ if (order[c].includes(hrefKey)) return c;
6931
+ }
6932
+ return -1;
6933
+ }
6934
+ function hitTestColumnDropSlot(clientX, _clientY) {
6935
+ const slots = buildColumnDropSlots();
6936
+ let best = null;
6937
+ for (const slot of slots) {
6938
+ const cx2 = slot.left + slot.width / 2;
6939
+ const dist = Math.abs(clientX - cx2);
6940
+ if (!best || dist < best.dist) best = { slot, dist };
6941
+ }
6942
+ return best?.slot ?? null;
6943
+ }
6944
+
6611
6945
  // src/lib/carousel.ts
6612
6946
  var import_react8 = require("react");
6613
6947
  var CAROUSEL_ATTR = "data-ohw-carousel";
@@ -6736,8 +7070,50 @@ function NavbarContainerChrome({
6736
7070
  );
6737
7071
  }
6738
7072
 
6739
- // src/ui/badge.tsx
7073
+ // src/ui/drop-indicator.tsx
7074
+ var React9 = __toESM(require("react"), 1);
6740
7075
  var import_jsx_runtime23 = require("react/jsx-runtime");
7076
+ var dropIndicatorVariants = cva(
7077
+ "ov-gap-line pointer-events-none shrink-0 transition-opacity duration-150",
7078
+ {
7079
+ variants: {
7080
+ direction: {
7081
+ vertical: "h-6 w-[3px]",
7082
+ horizontal: "h-[3px] w-[200px]"
7083
+ },
7084
+ state: {
7085
+ default: "opacity-0",
7086
+ hover: "opacity-100",
7087
+ dragIdle: "opacity-40",
7088
+ dragActive: "opacity-100"
7089
+ }
7090
+ },
7091
+ defaultVariants: {
7092
+ direction: "vertical",
7093
+ state: "default"
7094
+ }
7095
+ }
7096
+ );
7097
+ var DropIndicator = React9.forwardRef(
7098
+ ({ className, direction, state, ...props }, ref) => {
7099
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
7100
+ "div",
7101
+ {
7102
+ ref,
7103
+ "data-slot": "drop-indicator",
7104
+ "data-direction": direction ?? "vertical",
7105
+ "data-state": state ?? "default",
7106
+ "aria-hidden": "true",
7107
+ className: cn(dropIndicatorVariants({ direction, state }), className),
7108
+ ...props
7109
+ }
7110
+ );
7111
+ }
7112
+ );
7113
+ DropIndicator.displayName = "DropIndicator";
7114
+
7115
+ // src/ui/badge.tsx
7116
+ var import_jsx_runtime24 = require("react/jsx-runtime");
6741
7117
  var badgeVariants = cva(
6742
7118
  "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",
6743
7119
  {
@@ -6755,12 +7131,12 @@ var badgeVariants = cva(
6755
7131
  }
6756
7132
  );
6757
7133
  function Badge({ className, variant, ...props }) {
6758
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: cn(badgeVariants({ variant }), className), ...props });
7134
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: cn(badgeVariants({ variant }), className), ...props });
6759
7135
  }
6760
7136
 
6761
7137
  // src/OhhwellsBridge.tsx
6762
7138
  var import_lucide_react12 = require("lucide-react");
6763
- var import_jsx_runtime24 = require("react/jsx-runtime");
7139
+ var import_jsx_runtime25 = require("react/jsx-runtime");
6764
7140
  var PRIMARY2 = "#0885FE";
6765
7141
  var IMAGE_FADE_MS = 300;
6766
7142
  function runOpacityFade(el, onDone) {
@@ -6939,7 +7315,7 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
6939
7315
  const root = (0, import_client.createRoot)(container);
6940
7316
  (0, import_react_dom2.flushSync)(() => {
6941
7317
  root.render(
6942
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
7318
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
6943
7319
  SchedulingWidget,
6944
7320
  {
6945
7321
  notifyOnConnect,
@@ -7001,7 +7377,7 @@ function isDragHandleDisabled(el) {
7001
7377
  return raw === "true" || raw === "";
7002
7378
  });
7003
7379
  }
7004
- function collectEditableNodes() {
7380
+ function collectEditableNodes(extraContent) {
7005
7381
  const nodes = Array.from(document.querySelectorAll("[data-ohw-editable]")).map((el) => {
7006
7382
  if (el.dataset.ohwEditable === "image") {
7007
7383
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -7029,6 +7405,14 @@ function collectEditableNodes() {
7029
7405
  if (!key) return;
7030
7406
  nodes.push({ key, type: "link", text: getLinkHref2(el) });
7031
7407
  });
7408
+ if (extraContent) {
7409
+ for (const key of [NAV_ORDER_KEY, FOOTER_ORDER_KEY, NAV_COUNT_KEY]) {
7410
+ const text = extraContent[key];
7411
+ if (typeof text === "string" && text.length > 0) {
7412
+ nodes.push({ key, type: "meta", text });
7413
+ }
7414
+ }
7415
+ }
7032
7416
  document.querySelectorAll('[data-ohw-editable="video"]').forEach((el) => {
7033
7417
  const key = el.dataset.ohwKey ?? "";
7034
7418
  const video = getVideoEl(el);
@@ -7127,6 +7511,29 @@ function getHrefKeyFromElement(el) {
7127
7511
  if (!key) return null;
7128
7512
  return { anchor, key };
7129
7513
  }
7514
+ function disableNativeHrefDrag(el) {
7515
+ if (el.draggable) el.draggable = false;
7516
+ if (el.getAttribute("draggable") !== "false") {
7517
+ el.setAttribute("draggable", "false");
7518
+ }
7519
+ }
7520
+ function clearTextSelection() {
7521
+ const sel = window.getSelection();
7522
+ if (sel && !sel.isCollapsed) sel.removeAllRanges();
7523
+ }
7524
+ function armFooterPressDrag() {
7525
+ document.documentElement.setAttribute("data-ohw-footer-press-drag", "");
7526
+ }
7527
+ function lockFooterDuringDrag() {
7528
+ document.documentElement.removeAttribute("data-ohw-footer-press-drag");
7529
+ document.documentElement.setAttribute("data-ohw-item-dragging", "");
7530
+ clearTextSelection();
7531
+ }
7532
+ function unlockFooterDragInteraction() {
7533
+ document.documentElement.removeAttribute("data-ohw-footer-press-drag");
7534
+ document.documentElement.removeAttribute("data-ohw-item-dragging");
7535
+ clearTextSelection();
7536
+ }
7130
7537
  function isNavbarButton2(el) {
7131
7538
  return Boolean(el.closest('[data-ohw-role="navbar-button"]'));
7132
7539
  }
@@ -7425,7 +7832,7 @@ function EditGlowChrome({
7425
7832
  dragDisabled = false
7426
7833
  }) {
7427
7834
  const GAP = 6;
7428
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
7835
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
7429
7836
  "div",
7430
7837
  {
7431
7838
  ref: elRef,
@@ -7440,7 +7847,7 @@ function EditGlowChrome({
7440
7847
  zIndex: 2147483646
7441
7848
  },
7442
7849
  children: [
7443
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
7850
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
7444
7851
  "div",
7445
7852
  {
7446
7853
  style: {
@@ -7453,7 +7860,7 @@ function EditGlowChrome({
7453
7860
  }
7454
7861
  }
7455
7862
  ),
7456
- reorderHrefKey && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
7863
+ reorderHrefKey && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
7457
7864
  "div",
7458
7865
  {
7459
7866
  "data-ohw-drag-handle-container": "",
@@ -7465,7 +7872,7 @@ function EditGlowChrome({
7465
7872
  transform: "translate(calc(-100% - 7px), -50%)",
7466
7873
  pointerEvents: dragDisabled ? "none" : "auto"
7467
7874
  },
7468
- children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
7875
+ children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
7469
7876
  DragHandle,
7470
7877
  {
7471
7878
  "aria-label": `Reorder ${reorderHrefKey}`,
@@ -7569,7 +7976,7 @@ function FloatingToolbar({
7569
7976
  onEditLink
7570
7977
  }) {
7571
7978
  const { top, left, transform } = calcToolbarPos(rect, parentScroll, 330);
7572
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
7979
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
7573
7980
  "div",
7574
7981
  {
7575
7982
  ref: elRef,
@@ -7590,12 +7997,12 @@ function FloatingToolbar({
7590
7997
  fontFamily: "sans-serif",
7591
7998
  pointerEvents: "auto"
7592
7999
  },
7593
- children: /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(CustomToolbar, { children: [
7594
- TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_react9.default.Fragment, { children: [
7595
- gi > 0 && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(CustomToolbarDivider, {}),
8000
+ children: /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(CustomToolbar, { children: [
8001
+ TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(import_react9.default.Fragment, { children: [
8002
+ gi > 0 && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(CustomToolbarDivider, {}),
7596
8003
  btns.map((btn) => {
7597
8004
  const isActive = activeCommands.has(btn.cmd);
7598
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
8005
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
7599
8006
  CustomToolbarButton,
7600
8007
  {
7601
8008
  title: btn.title,
@@ -7604,7 +8011,7 @@ function FloatingToolbar({
7604
8011
  e.preventDefault();
7605
8012
  onCommand(btn.cmd);
7606
8013
  },
7607
- children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
8014
+ children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
7608
8015
  "svg",
7609
8016
  {
7610
8017
  width: "16",
@@ -7625,7 +8032,7 @@ function FloatingToolbar({
7625
8032
  );
7626
8033
  })
7627
8034
  ] }, gi)),
7628
- showEditLink ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
8035
+ showEditLink ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
7629
8036
  CustomToolbarButton,
7630
8037
  {
7631
8038
  type: "button",
@@ -7639,7 +8046,7 @@ function FloatingToolbar({
7639
8046
  e.preventDefault();
7640
8047
  e.stopPropagation();
7641
8048
  },
7642
- children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_lucide_react12.Link, { className: "size-4 shrink-0", "aria-hidden": true })
8049
+ children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_lucide_react12.Link, { className: "size-4 shrink-0", "aria-hidden": true })
7643
8050
  }
7644
8051
  ) : null
7645
8052
  ] })
@@ -7656,7 +8063,7 @@ function StateToggle({
7656
8063
  states,
7657
8064
  onStateChange
7658
8065
  }) {
7659
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
8066
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
7660
8067
  ToggleGroup,
7661
8068
  {
7662
8069
  "data-ohw-state-toggle": "",
@@ -7670,7 +8077,7 @@ function StateToggle({
7670
8077
  left: rect.right - 8,
7671
8078
  transform: "translateX(-100%)"
7672
8079
  },
7673
- children: states.map((state) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
8080
+ children: states.map((state) => /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
7674
8081
  }
7675
8082
  );
7676
8083
  }
@@ -7794,7 +8201,15 @@ function OhhwellsBridge() {
7794
8201
  const [hoveredItemRect, setHoveredItemRect] = (0, import_react9.useState)(null);
7795
8202
  const siblingHintElRef = (0, import_react9.useRef)(null);
7796
8203
  const [siblingHintRect, setSiblingHintRect] = (0, import_react9.useState)(null);
8204
+ const [siblingHintRects, setSiblingHintRects] = (0, import_react9.useState)([]);
7797
8205
  const [isItemDragging, setIsItemDragging] = (0, import_react9.useState)(false);
8206
+ const [isFooterFrameSelection, setIsFooterFrameSelection] = (0, import_react9.useState)(false);
8207
+ const footerDragRef = (0, import_react9.useRef)(null);
8208
+ const [footerDropSlots, setFooterDropSlots] = (0, import_react9.useState)([]);
8209
+ const [activeFooterDropIndex, setActiveFooterDropIndex] = (0, import_react9.useState)(null);
8210
+ const [draggedItemRect, setDraggedItemRect] = (0, import_react9.useState)(null);
8211
+ const footerPointerDragRef = (0, import_react9.useRef)(null);
8212
+ const suppressNextClickRef = (0, import_react9.useRef)(false);
7798
8213
  const [linkPopover, setLinkPopover] = (0, import_react9.useState)(null);
7799
8214
  const linkPopoverSessionRef = (0, import_react9.useRef)(null);
7800
8215
  const addNavAfterAnchorRef = (0, import_react9.useRef)(null);
@@ -7958,8 +8373,10 @@ function OhhwellsBridge() {
7958
8373
  selectedElRef.current = null;
7959
8374
  setReorderHrefKey(null);
7960
8375
  setReorderDragDisabled(false);
8376
+ setIsFooterFrameSelection(false);
7961
8377
  siblingHintElRef.current = null;
7962
8378
  setSiblingHintRect(null);
8379
+ setSiblingHintRects([]);
7963
8380
  setIsItemDragging(false);
7964
8381
  hoveredNavContainerRef.current = null;
7965
8382
  setHoveredNavContainerRect(null);
@@ -8016,90 +8433,345 @@ function OhhwellsBridge() {
8016
8433
  intent: "add-nav"
8017
8434
  });
8018
8435
  }, []);
8019
- const handleItemDragStart = (0, import_react9.useCallback)(() => {
8020
- siblingHintElRef.current = null;
8021
- setSiblingHintRect(null);
8022
- setIsItemDragging(true);
8023
- }, []);
8024
- const handleItemDragEnd = (0, import_react9.useCallback)(() => {
8436
+ const clearFooterDragVisuals = (0, import_react9.useCallback)(() => {
8437
+ footerDragRef.current = null;
8438
+ setSiblingHintRects([]);
8439
+ setFooterDropSlots([]);
8440
+ setActiveFooterDropIndex(null);
8441
+ setDraggedItemRect(null);
8025
8442
  setIsItemDragging(false);
8443
+ unlockFooterDragInteraction();
8026
8444
  }, []);
8027
- reselectNavigationItemRef.current = reselectNavigationItem;
8028
- commitNavigationTextEditRef.current = commitNavigationTextEdit;
8029
- const select = (0, import_react9.useCallback)((anchor) => {
8030
- if (!isNavigationItem(anchor)) return;
8031
- if (activeElRef.current) deactivate();
8032
- selectedElRef.current = anchor;
8033
- clearHrefKeyHover(anchor);
8034
- hoveredNavContainerRef.current = null;
8035
- setHoveredNavContainerRect(null);
8036
- setHoveredItemRect(null);
8037
- hoveredItemElRef.current = null;
8038
- siblingHintElRef.current = null;
8039
- setSiblingHintRect(null);
8040
- setIsItemDragging(false);
8041
- const { key, disabled } = getNavigationItemReorderState(anchor);
8042
- setReorderHrefKey(key);
8043
- setReorderDragDisabled(disabled);
8044
- setToolbarVariant("link-action");
8045
- setToolbarRect(anchor.getBoundingClientRect());
8046
- setToolbarShowEditLink(false);
8047
- setActiveCommands(/* @__PURE__ */ new Set());
8048
- }, [deactivate]);
8049
- const selectFrame = (0, import_react9.useCallback)((el) => {
8050
- if (!isNavigationContainer(el)) return;
8051
- if (activeElRef.current) deactivate();
8052
- selectedElRef.current = el;
8053
- clearHrefKeyHover(el);
8054
- hoveredNavContainerRef.current = null;
8055
- setHoveredNavContainerRect(null);
8056
- setHoveredItemRect(null);
8057
- hoveredItemElRef.current = null;
8058
- siblingHintElRef.current = null;
8059
- setSiblingHintRect(null);
8060
- setIsItemDragging(false);
8061
- setReorderHrefKey(null);
8062
- setReorderDragDisabled(false);
8063
- setToolbarVariant("select-frame");
8064
- setToolbarRect(el.getBoundingClientRect());
8065
- setToolbarShowEditLink(false);
8066
- setActiveCommands(/* @__PURE__ */ new Set());
8067
- }, [deactivate]);
8068
- const activate = (0, import_react9.useCallback)((el, options) => {
8069
- if (activeElRef.current === el) return;
8070
- selectedElRef.current = null;
8071
- deactivate();
8072
- if (hoveredImageRef.current) {
8073
- hoveredImageRef.current = null;
8074
- setMediaHover(null);
8445
+ const refreshFooterDragVisuals = (0, import_react9.useCallback)((session, activeSlot, clientX, clientY) => {
8446
+ const dragged = session.draggedEl;
8447
+ setDraggedItemRect(dragged.getBoundingClientRect());
8448
+ if (typeof clientX === "number" && typeof clientY === "number") {
8449
+ session.lastClientX = clientX;
8450
+ session.lastClientY = clientY;
8075
8451
  }
8076
- setToolbarVariant("rich-text");
8077
- siblingHintElRef.current = null;
8078
- setSiblingHintRect(null);
8079
- setIsItemDragging(false);
8080
- el.setAttribute("contenteditable", "true");
8081
- el.removeAttribute("data-ohw-hovered");
8082
- el.closest("[data-ohw-href-key]")?.removeAttribute("data-ohw-hovered");
8083
- activeElRef.current = el;
8084
- originalContentRef.current = el.innerHTML;
8085
- el.focus();
8086
- if (options?.caretX !== void 0 && options?.caretY !== void 0) {
8087
- placeCaretAtPoint(el, options.caretX, options.caretY);
8452
+ session.activeSlot = activeSlot;
8453
+ if (session.kind === "link") {
8454
+ const columns2 = listFooterColumns();
8455
+ const focusCols = /* @__PURE__ */ new Set([session.sourceColumnIndex]);
8456
+ if (activeSlot) focusCols.add(activeSlot.columnIndex);
8457
+ const siblingRects = [];
8458
+ columns2.forEach((col, i) => {
8459
+ if (!focusCols.has(i)) return;
8460
+ for (const link of listFooterLinksInColumn(col)) {
8461
+ if (link === dragged) continue;
8462
+ siblingRects.push(link.getBoundingClientRect());
8463
+ }
8464
+ });
8465
+ setSiblingHintRects(siblingRects);
8466
+ const slots2 = [];
8467
+ columns2.forEach((col, i) => {
8468
+ slots2.push(...buildLinkDropSlots(col, i));
8469
+ });
8470
+ setFooterDropSlots(slots2);
8471
+ const activeIdx2 = activeSlot ? slots2.findIndex((s) => s.columnIndex === activeSlot.columnIndex && s.insertIndex === activeSlot.insertIndex) : -1;
8472
+ setActiveFooterDropIndex(activeIdx2 >= 0 ? activeIdx2 : null);
8473
+ return;
8088
8474
  }
8089
- setToolbarShowEditLink(Boolean(getHrefKeyFromElement(el)));
8090
- const navAnchor = getNavigationItemAnchor(el);
8091
- if (navAnchor) {
8475
+ const columns = listFooterColumns();
8476
+ setSiblingHintRects(columns.filter((col) => col !== dragged).map((col) => col.getBoundingClientRect()));
8477
+ const slots = buildColumnDropSlots();
8478
+ setFooterDropSlots(slots);
8479
+ const activeIdx = activeSlot ? slots.findIndex((s) => s.insertIndex === activeSlot.insertIndex) : -1;
8480
+ setActiveFooterDropIndex(activeIdx >= 0 ? activeIdx : null);
8481
+ }, []);
8482
+ const refreshFooterDragVisualsRef = (0, import_react9.useRef)(refreshFooterDragVisuals);
8483
+ refreshFooterDragVisualsRef.current = refreshFooterDragVisuals;
8484
+ const commitFooterDragRef = (0, import_react9.useRef)(() => {
8485
+ });
8486
+ const beginFooterDragRef = (0, import_react9.useRef)(() => {
8487
+ });
8488
+ const beginFooterDrag = (0, import_react9.useCallback)(
8489
+ (session) => {
8490
+ const rect = session.draggedEl.getBoundingClientRect();
8491
+ session.lastClientX = session.lastClientX || rect.left + rect.width / 2;
8492
+ session.lastClientY = session.lastClientY || rect.top + rect.height / 2;
8493
+ session.activeSlot = session.activeSlot ?? null;
8494
+ footerDragRef.current = session;
8495
+ setIsItemDragging(true);
8496
+ lockFooterDuringDrag();
8497
+ siblingHintElRef.current = null;
8498
+ setSiblingHintRect(null);
8499
+ if (session.wasSelected && selectedElRef.current === session.draggedEl) {
8500
+ setToolbarRect(rect);
8501
+ }
8502
+ const initialSlot = session.kind === "link" && session.hrefKey ? hitTestLinkDropSlot(session.lastClientX, session.lastClientY, session.hrefKey) : hitTestColumnDropSlot(session.lastClientX, session.lastClientY);
8503
+ refreshFooterDragVisuals(session, initialSlot, session.lastClientX, session.lastClientY);
8504
+ },
8505
+ [refreshFooterDragVisuals]
8506
+ );
8507
+ beginFooterDragRef.current = beginFooterDrag;
8508
+ const commitFooterDrag = (0, import_react9.useCallback)(
8509
+ (clientX, clientY) => {
8510
+ const session = footerDragRef.current;
8511
+ if (!session) {
8512
+ clearFooterDragVisuals();
8513
+ return;
8514
+ }
8515
+ const x = typeof clientX === "number" && (clientX !== 0 || clientY !== 0) ? clientX : session.lastClientX;
8516
+ const y = typeof clientY === "number" && (clientX !== 0 || clientY !== 0) ? clientY : session.lastClientY;
8517
+ let nextOrder = null;
8518
+ const slot = session.activeSlot ?? (session.kind === "link" && session.hrefKey ? hitTestLinkDropSlot(x, y, session.hrefKey) : session.kind === "column" ? hitTestColumnDropSlot(x, y) : null);
8519
+ if (session.kind === "link" && session.hrefKey && slot) {
8520
+ nextOrder = planFooterLinkMove(session.hrefKey, slot.columnIndex, slot.insertIndex);
8521
+ } else if (session.kind === "column" && slot) {
8522
+ nextOrder = planFooterColumnMove(session.sourceColumnIndex, slot.insertIndex);
8523
+ }
8524
+ const wasSelected = session.wasSelected;
8525
+ const draggedEl = session.draggedEl;
8526
+ const hrefKey = session.hrefKey;
8527
+ let movedColumnIndex = null;
8528
+ if (session.kind === "column" && slot && nextOrder) {
8529
+ let adjusted = slot.insertIndex;
8530
+ if (session.sourceColumnIndex < slot.insertIndex) adjusted -= 1;
8531
+ movedColumnIndex = Math.max(0, Math.min(adjusted, nextOrder.length - 1));
8532
+ }
8533
+ clearFooterDragVisuals();
8534
+ if (nextOrder) {
8535
+ const orderJson = JSON.stringify(nextOrder);
8536
+ editContentRef.current = {
8537
+ ...editContentRef.current,
8538
+ [FOOTER_ORDER_KEY]: orderJson
8539
+ };
8540
+ applyFooterOrder(nextOrder);
8541
+ document.querySelectorAll("footer [data-ohw-href-key]").forEach(disableNativeHrefDrag);
8542
+ requestAnimationFrame(() => {
8543
+ if (editContentRef.current[FOOTER_ORDER_KEY] === orderJson) {
8544
+ applyFooterOrder(nextOrder);
8545
+ document.querySelectorAll("footer [data-ohw-href-key]").forEach(disableNativeHrefDrag);
8546
+ }
8547
+ });
8548
+ postToParentRef.current({
8549
+ type: "ow:change",
8550
+ nodes: [{ key: FOOTER_ORDER_KEY, text: orderJson }]
8551
+ });
8552
+ }
8553
+ const still = (hrefKey ? document.querySelector(`footer [data-ohw-href-key="${CSS.escape(hrefKey)}"]`) : null) ?? (movedColumnIndex !== null ? listFooterColumns()[movedColumnIndex] ?? null : null) ?? (document.body.contains(draggedEl) ? draggedEl : null);
8554
+ if (still && isNavigationItem(still)) {
8555
+ selectRef.current(still);
8556
+ } else if (still && isNavigationContainer(still)) {
8557
+ selectFrameRef.current(still);
8558
+ } else if (wasSelected) {
8559
+ deselectRef.current();
8560
+ }
8561
+ },
8562
+ [clearFooterDragVisuals]
8563
+ );
8564
+ commitFooterDragRef.current = commitFooterDrag;
8565
+ const startFooterLinkDrag = (0, import_react9.useCallback)(
8566
+ (anchor, clientX, clientY, wasSelected) => {
8567
+ const hrefKey = anchor.getAttribute("data-ohw-href-key");
8568
+ if (!hrefKey || !isFooterHrefKey(hrefKey)) return false;
8569
+ const column = findFooterColumnForLink(anchor);
8570
+ const columns = listFooterColumns();
8571
+ beginFooterDrag({
8572
+ kind: "link",
8573
+ hrefKey,
8574
+ columnEl: column,
8575
+ sourceColumnIndex: column ? columns.indexOf(column) : 0,
8576
+ wasSelected,
8577
+ draggedEl: anchor,
8578
+ lastClientX: clientX,
8579
+ lastClientY: clientY,
8580
+ activeSlot: null
8581
+ });
8582
+ return true;
8583
+ },
8584
+ [beginFooterDrag]
8585
+ );
8586
+ const startFooterColumnDrag = (0, import_react9.useCallback)(
8587
+ (columnEl, clientX, clientY, wasSelected) => {
8588
+ const columns = listFooterColumns();
8589
+ const idx = columns.indexOf(columnEl);
8590
+ if (idx < 0) return false;
8591
+ beginFooterDrag({
8592
+ kind: "column",
8593
+ hrefKey: null,
8594
+ columnEl,
8595
+ sourceColumnIndex: idx,
8596
+ wasSelected,
8597
+ draggedEl: columnEl,
8598
+ lastClientX: clientX,
8599
+ lastClientY: clientY,
8600
+ activeSlot: null
8601
+ });
8602
+ return true;
8603
+ },
8604
+ [beginFooterDrag]
8605
+ );
8606
+ const handleItemDragStart = (0, import_react9.useCallback)(
8607
+ (e) => {
8608
+ const selected = selectedElRef.current;
8609
+ if (!selected) {
8610
+ setIsItemDragging(true);
8611
+ return;
8612
+ }
8613
+ const clientX = e?.clientX ?? selected.getBoundingClientRect().left + 8;
8614
+ const clientY = e?.clientY ?? selected.getBoundingClientRect().top + 8;
8615
+ if (startFooterLinkDrag(selected, clientX, clientY, true)) return;
8616
+ if (selected.hasAttribute("data-ohw-footer-col") || isInferredFooterGroup(selected) && selected.closest("footer")) {
8617
+ if (startFooterColumnDrag(selected, clientX, clientY, true)) return;
8618
+ }
8619
+ siblingHintElRef.current = null;
8620
+ setSiblingHintRect(null);
8621
+ setIsItemDragging(true);
8622
+ },
8623
+ [startFooterColumnDrag, startFooterLinkDrag]
8624
+ );
8625
+ const handleItemDragEnd = (0, import_react9.useCallback)(
8626
+ (e) => {
8627
+ if (footerDragRef.current) {
8628
+ const x = e?.clientX;
8629
+ const y = e?.clientY;
8630
+ if (typeof x === "number" && typeof y === "number" && (x !== 0 || y !== 0)) {
8631
+ commitFooterDrag(x, y);
8632
+ } else {
8633
+ commitFooterDrag();
8634
+ }
8635
+ return;
8636
+ }
8637
+ setIsItemDragging(false);
8638
+ },
8639
+ [commitFooterDrag]
8640
+ );
8641
+ const handleItemChromePointerDown = (0, import_react9.useCallback)((e) => {
8642
+ if (e.button !== 0) return;
8643
+ const selected = selectedElRef.current;
8644
+ if (!selected) return;
8645
+ armFooterPressDrag();
8646
+ const hrefKey = selected.getAttribute("data-ohw-href-key");
8647
+ if (hrefKey && isFooterHrefKey(hrefKey)) {
8648
+ footerPointerDragRef.current = {
8649
+ el: selected,
8650
+ kind: "link",
8651
+ startX: e.clientX,
8652
+ startY: e.clientY,
8653
+ pointerId: e.pointerId,
8654
+ wasSelected: true,
8655
+ started: false
8656
+ };
8657
+ return;
8658
+ }
8659
+ if (selected.hasAttribute("data-ohw-footer-col") || isInferredFooterGroup(selected) && selected.closest("footer")) {
8660
+ footerPointerDragRef.current = {
8661
+ el: selected,
8662
+ kind: "column",
8663
+ startX: e.clientX,
8664
+ startY: e.clientY,
8665
+ pointerId: e.pointerId,
8666
+ wasSelected: true,
8667
+ started: false
8668
+ };
8669
+ }
8670
+ }, []);
8671
+ const handleItemChromeClick = (0, import_react9.useCallback)((clientX, clientY) => {
8672
+ if (suppressNextClickRef.current) {
8673
+ suppressNextClickRef.current = false;
8674
+ return;
8675
+ }
8676
+ const selected = selectedElRef.current;
8677
+ if (!selected || !isNavigationItem(selected)) return;
8678
+ const editable = selected.querySelector('[data-ohw-editable="text"]') ?? selected.querySelector("[data-ohw-editable]");
8679
+ if (!editable) return;
8680
+ activateRef.current(editable, { caretX: clientX, caretY: clientY });
8681
+ }, []);
8682
+ reselectNavigationItemRef.current = reselectNavigationItem;
8683
+ commitNavigationTextEditRef.current = commitNavigationTextEdit;
8684
+ const select = (0, import_react9.useCallback)(
8685
+ (anchor) => {
8686
+ if (!isNavigationItem(anchor)) return;
8687
+ if (activeElRef.current) deactivate();
8688
+ selectedElRef.current = anchor;
8689
+ clearHrefKeyHover(anchor);
8690
+ hoveredNavContainerRef.current = null;
8691
+ setHoveredNavContainerRect(null);
8692
+ setHoveredItemRect(null);
8693
+ hoveredItemElRef.current = null;
8694
+ siblingHintElRef.current = null;
8695
+ setSiblingHintRect(null);
8696
+ setSiblingHintRects([]);
8697
+ setIsItemDragging(false);
8698
+ const { key, disabled } = getNavigationItemReorderState(anchor);
8699
+ setReorderHrefKey(key);
8700
+ setReorderDragDisabled(disabled);
8701
+ setIsFooterFrameSelection(false);
8702
+ setToolbarVariant("link-action");
8703
+ setToolbarRect(anchor.getBoundingClientRect());
8704
+ setToolbarShowEditLink(false);
8705
+ setActiveCommands(/* @__PURE__ */ new Set());
8706
+ },
8707
+ [deactivate]
8708
+ );
8709
+ const selectFrame = (0, import_react9.useCallback)(
8710
+ (el) => {
8711
+ if (!isNavigationContainer(el)) return;
8712
+ if (activeElRef.current) deactivate();
8713
+ const isFooterColumn = el.hasAttribute("data-ohw-footer-col") || Boolean(el.closest("footer") && isInferredFooterGroup(el));
8714
+ selectedElRef.current = el;
8715
+ clearHrefKeyHover(el);
8716
+ hoveredNavContainerRef.current = null;
8717
+ setHoveredNavContainerRect(null);
8718
+ setHoveredItemRect(null);
8719
+ hoveredItemElRef.current = null;
8720
+ siblingHintElRef.current = null;
8721
+ setSiblingHintRect(null);
8722
+ setSiblingHintRects(
8723
+ isFooterColumn ? listFooterColumns().filter((column) => column !== el).map((column) => column.getBoundingClientRect()) : []
8724
+ );
8725
+ setIsItemDragging(false);
8092
8726
  setReorderHrefKey(null);
8093
8727
  setReorderDragDisabled(false);
8094
- } else {
8095
- const { key: reorderKey, disabled: reorderDisabled } = getReorderHandleState(el);
8096
- setReorderHrefKey(reorderKey);
8097
- setReorderDragDisabled(reorderDisabled);
8098
- }
8099
- setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
8100
- postToParent2({ type: "ow:enter-edit", key: el.dataset.ohwKey });
8101
- requestAnimationFrame(() => refreshActiveCommandsRef.current());
8102
- }, [deactivate, postToParent2]);
8728
+ setIsFooterFrameSelection(isFooterColumn);
8729
+ setToolbarVariant("select-frame");
8730
+ setToolbarRect(el.getBoundingClientRect());
8731
+ setToolbarShowEditLink(false);
8732
+ setActiveCommands(/* @__PURE__ */ new Set());
8733
+ },
8734
+ [deactivate]
8735
+ );
8736
+ const activate = (0, import_react9.useCallback)(
8737
+ (el, options) => {
8738
+ if (activeElRef.current === el) return;
8739
+ selectedElRef.current = null;
8740
+ deactivate();
8741
+ if (hoveredImageRef.current) {
8742
+ hoveredImageRef.current = null;
8743
+ postToParentRef.current({ type: "ow:image-unhover" });
8744
+ }
8745
+ setToolbarVariant("rich-text");
8746
+ siblingHintElRef.current = null;
8747
+ setSiblingHintRect(null);
8748
+ setSiblingHintRects([]);
8749
+ setIsItemDragging(false);
8750
+ el.setAttribute("contenteditable", "true");
8751
+ el.removeAttribute("data-ohw-hovered");
8752
+ el.closest("[data-ohw-href-key]")?.removeAttribute("data-ohw-hovered");
8753
+ activeElRef.current = el;
8754
+ originalContentRef.current = el.innerHTML;
8755
+ el.focus();
8756
+ if (options?.caretX !== void 0 && options?.caretY !== void 0) {
8757
+ placeCaretAtPoint(el, options.caretX, options.caretY);
8758
+ }
8759
+ setToolbarShowEditLink(Boolean(getHrefKeyFromElement(el)));
8760
+ const navAnchor = getNavigationItemAnchor(el);
8761
+ if (navAnchor) {
8762
+ setReorderHrefKey(null);
8763
+ setReorderDragDisabled(false);
8764
+ } else {
8765
+ const { key: reorderKey, disabled: reorderDisabled } = getReorderHandleState(el);
8766
+ setReorderHrefKey(reorderKey);
8767
+ setReorderDragDisabled(reorderDisabled);
8768
+ }
8769
+ setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
8770
+ postToParent2({ type: "ow:enter-edit", key: el.dataset.ohwKey });
8771
+ requestAnimationFrame(() => refreshActiveCommandsRef.current());
8772
+ },
8773
+ [deactivate, postToParent2]
8774
+ );
8103
8775
  activateRef.current = activate;
8104
8776
  deactivateRef.current = deactivate;
8105
8777
  selectRef.current = select;
@@ -8147,6 +8819,7 @@ function OhhwellsBridge() {
8147
8819
  applyLinkByKey(key, val);
8148
8820
  }
8149
8821
  reconcileNavbarItemsFromContent(content);
8822
+ reconcileFooterOrderFromContent(content);
8150
8823
  enforceLinkHrefs();
8151
8824
  initSectionsFromContent(content, true);
8152
8825
  sectionsLoadedRef.current = true;
@@ -8208,6 +8881,7 @@ function OhhwellsBridge() {
8208
8881
  applyLinkByKey(key, val);
8209
8882
  }
8210
8883
  reconcileNavbarItemsFromContent(content);
8884
+ reconcileFooterOrderFromContent(content);
8211
8885
  } finally {
8212
8886
  observer?.observe(document.body, { childList: true, subtree: true });
8213
8887
  }
@@ -8248,15 +8922,42 @@ function OhhwellsBridge() {
8248
8922
  if (!subdomain) return {};
8249
8923
  return contentCache.get(subdomain) ?? {};
8250
8924
  };
8251
- const run = () => reconcileNavbarItemsFromContent(contentForNav());
8925
+ let applying = false;
8926
+ let rafId = null;
8927
+ const run = () => {
8928
+ if (footerDragRef.current || applying) return;
8929
+ applying = true;
8930
+ try {
8931
+ const content = contentForNav();
8932
+ reconcileNavbarItemsFromContent(content);
8933
+ reconcileFooterOrderFromContent(content);
8934
+ document.querySelectorAll("footer [data-ohw-href-key]").forEach((el) => {
8935
+ if (isFooterHrefKey(el.getAttribute("data-ohw-href-key"))) {
8936
+ disableNativeHrefDrag(el);
8937
+ }
8938
+ });
8939
+ } finally {
8940
+ applying = false;
8941
+ }
8942
+ };
8943
+ const scheduleRun = () => {
8944
+ if (rafId !== null) cancelAnimationFrame(rafId);
8945
+ rafId = requestAnimationFrame(() => {
8946
+ rafId = null;
8947
+ run();
8948
+ });
8949
+ };
8252
8950
  run();
8253
8951
  const nav = document.querySelector("nav");
8254
- if (!nav) return;
8255
- const observer = new MutationObserver(() => {
8256
- requestAnimationFrame(run);
8257
- });
8258
- observer.observe(nav, { childList: true, subtree: true });
8259
- return () => observer.disconnect();
8952
+ const footer = document.querySelector("footer");
8953
+ const observer = new MutationObserver(scheduleRun);
8954
+ if (nav) observer.observe(nav, { childList: true, subtree: true });
8955
+ if (footer) observer.observe(footer, { childList: true, subtree: true });
8956
+ if (!nav && !footer) return;
8957
+ return () => {
8958
+ observer.disconnect();
8959
+ if (rafId !== null) cancelAnimationFrame(rafId);
8960
+ };
8260
8961
  }, [isEditMode, pathname, subdomain, fetchState]);
8261
8962
  (0, import_react9.useEffect)(() => {
8262
8963
  if (!isEditMode) return;
@@ -8321,6 +9022,29 @@ function OhhwellsBridge() {
8321
9022
  [data-ohw-editable] {
8322
9023
  display: block;
8323
9024
  }
9025
+ /* Native <a> drag steals clicks \u2014 disable for edit links; reorder via press-to-drag / handle. */
9026
+ footer [data-ohw-href-key] {
9027
+ -webkit-user-drag: none !important;
9028
+ }
9029
+ footer [data-ohw-footer-col]:hover {
9030
+ outline: 1.5px dashed ${PRIMARY2} !important;
9031
+ outline-offset: 6px;
9032
+ border-radius: 8px;
9033
+ }
9034
+ html[data-ohw-footer-press-drag] footer,
9035
+ html[data-ohw-footer-press-drag] footer * {
9036
+ user-select: none !important;
9037
+ -webkit-user-select: none !important;
9038
+ }
9039
+ html[data-ohw-item-dragging] footer,
9040
+ html[data-ohw-item-dragging] footer * {
9041
+ user-select: none !important;
9042
+ -webkit-user-select: none !important;
9043
+ pointer-events: none !important;
9044
+ }
9045
+ html[data-ohw-item-dragging] {
9046
+ cursor: grabbing !important;
9047
+ }
8324
9048
  [data-ohw-editable]:not([contenteditable]):not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"]):not([data-ohw-editable="video"]) { cursor: text !important; }
8325
9049
  [data-ohw-editable="image"], [data-ohw-editable="image"] *,
8326
9050
  [data-ohw-editable="video"], [data-ohw-editable="video"] *,
@@ -8366,12 +9090,39 @@ function OhhwellsBridge() {
8366
9090
  }
8367
9091
  refreshStateRules();
8368
9092
  const handleClick = (e) => {
9093
+ if (suppressNextClickRef.current) {
9094
+ suppressNextClickRef.current = false;
9095
+ e.preventDefault();
9096
+ e.stopPropagation();
9097
+ return;
9098
+ }
8369
9099
  const target = e.target;
8370
9100
  if (target.closest("[data-ohw-toolbar]")) return;
8371
9101
  if (target.closest("[data-ohw-state-toggle]")) return;
8372
9102
  if (target.closest("[data-ohw-max-badge]")) return;
8373
9103
  if (isInsideLinkEditor(target)) return;
8374
9104
  if (target.closest('[data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
9105
+ if (target.closest("[data-ohw-item-drag-surface]")) {
9106
+ e.preventDefault();
9107
+ e.stopPropagation();
9108
+ const selected = selectedElRef.current;
9109
+ if (selected && isNavigationItem(selected)) {
9110
+ const editable2 = selected.querySelector('[data-ohw-editable="text"]') ?? selected.querySelector("[data-ohw-editable]");
9111
+ if (editable2) {
9112
+ activateRef.current(editable2, {
9113
+ caretX: e.clientX,
9114
+ caretY: e.clientY
9115
+ });
9116
+ }
9117
+ } else if (selected?.hasAttribute("data-ohw-footer-col")) {
9118
+ const link = listFooterLinksInColumn(selected).find((el) => {
9119
+ const r2 = el.getBoundingClientRect();
9120
+ return e.clientX >= r2.left && e.clientX <= r2.right && e.clientY >= r2.top && e.clientY <= r2.bottom;
9121
+ });
9122
+ if (link) selectRef.current(link);
9123
+ }
9124
+ return;
9125
+ }
8375
9126
  const navFromChrome = resolveNavContainerSelectionTarget(target, e.clientX, e.clientY);
8376
9127
  if (navFromChrome) {
8377
9128
  e.preventDefault();
@@ -8426,10 +9177,26 @@ function OhhwellsBridge() {
8426
9177
  if (hrefAnchor) {
8427
9178
  e.preventDefault();
8428
9179
  e.stopPropagation();
8429
- if (selectedElRef.current === hrefAnchor) return;
9180
+ if (selectedElRef.current === hrefAnchor) {
9181
+ const textEditable = hrefAnchor.querySelector('[data-ohw-editable="text"]') ?? hrefAnchor.querySelector("[data-ohw-editable]");
9182
+ if (textEditable) {
9183
+ activateRef.current(textEditable, {
9184
+ caretX: e.clientX,
9185
+ caretY: e.clientY
9186
+ });
9187
+ }
9188
+ return;
9189
+ }
8430
9190
  selectRef.current(hrefAnchor);
8431
9191
  return;
8432
9192
  }
9193
+ const footerColClick = target.closest("[data-ohw-footer-col]");
9194
+ if (footerColClick) {
9195
+ e.preventDefault();
9196
+ e.stopPropagation();
9197
+ selectFrameRef.current(footerColClick);
9198
+ return;
9199
+ }
8433
9200
  const navContainerToSelect = resolveNavContainerSelectionTarget(target, e.clientX, e.clientY);
8434
9201
  if (navContainerToSelect) {
8435
9202
  e.preventDefault();
@@ -8510,6 +9277,15 @@ function OhhwellsBridge() {
8510
9277
  setHoveredNavContainerRect(null);
8511
9278
  }
8512
9279
  }
9280
+ const footerCol = target.closest("[data-ohw-footer-col]");
9281
+ if (footerCol) {
9282
+ hoveredNavContainerRef.current = null;
9283
+ setHoveredNavContainerRect(null);
9284
+ if (selectedElRef.current === footerCol) return;
9285
+ hoveredItemElRef.current = footerCol;
9286
+ setHoveredItemRect(footerCol.getBoundingClientRect());
9287
+ return;
9288
+ }
8513
9289
  const navAnchor = getNavigationItemAnchor(target);
8514
9290
  if (navAnchor) {
8515
9291
  hoveredNavContainerRef.current = null;
@@ -8546,6 +9322,15 @@ function OhhwellsBridge() {
8546
9322
  hoveredNavContainerRef.current = null;
8547
9323
  setHoveredNavContainerRect(null);
8548
9324
  }
9325
+ const footerCol = target.closest("[data-ohw-footer-col]");
9326
+ if (footerCol && hoveredItemElRef.current === footerCol) {
9327
+ const related2 = e.relatedTarget instanceof Element ? e.relatedTarget : null;
9328
+ if (related2?.closest("[data-ohw-drag-handle-container], [data-ohw-item-interaction]")) return;
9329
+ if (related2?.closest("[data-ohw-footer-col]") === footerCol) return;
9330
+ hoveredItemElRef.current = null;
9331
+ setHoveredItemRect(null);
9332
+ return;
9333
+ }
8549
9334
  const navAnchor = getNavigationItemAnchor(target);
8550
9335
  if (navAnchor) {
8551
9336
  const related2 = e.relatedTarget instanceof Element ? e.relatedTarget : null;
@@ -8954,6 +9739,14 @@ function OhhwellsBridge() {
8954
9739
  probeHoverCardsAt(clientX, clientY);
8955
9740
  };
8956
9741
  const handleDragOver = (e) => {
9742
+ const footerSession = footerDragRef.current;
9743
+ if (footerSession) {
9744
+ e.preventDefault();
9745
+ if (e.dataTransfer) e.dataTransfer.dropEffect = "move";
9746
+ const slot = footerSession.kind === "link" && footerSession.hrefKey ? hitTestLinkDropSlot(e.clientX, e.clientY, footerSession.hrefKey) : hitTestColumnDropSlot(e.clientX, e.clientY);
9747
+ refreshFooterDragVisualsRef.current(footerSession, slot, e.clientX, e.clientY);
9748
+ return;
9749
+ }
8957
9750
  e.preventDefault();
8958
9751
  const el = findImageAtPoint(e.clientX, e.clientY, false);
8959
9752
  if (!el) {
@@ -9161,6 +9954,7 @@ function OhhwellsBridge() {
9161
9954
  }
9162
9955
  editContentRef.current = { ...editContentRef.current, ...content };
9163
9956
  reconcileNavbarItemsFromContent(editContentRef.current);
9957
+ reconcileFooterOrderFromContent(editContentRef.current);
9164
9958
  enforceLinkHrefs();
9165
9959
  postToParentRef.current({ type: "ow:hydrate-done" });
9166
9960
  };
@@ -9274,6 +10068,17 @@ function OhhwellsBridge() {
9274
10068
  if (siblingHintElRef.current) {
9275
10069
  setSiblingHintRect(siblingHintElRef.current.getBoundingClientRect());
9276
10070
  }
10071
+ const selected = selectedElRef.current;
10072
+ if (selected && !footerDragRef.current && (selected.hasAttribute("data-ohw-footer-col") || Boolean(selected.closest("footer") && isInferredFooterGroup(selected)))) {
10073
+ setSiblingHintRects(
10074
+ listFooterColumns().filter((column) => column !== selected).map((column) => column.getBoundingClientRect())
10075
+ );
10076
+ }
10077
+ if (footerDragRef.current) {
10078
+ const session = footerDragRef.current;
10079
+ const slot = session.kind === "link" && session.hrefKey ? hitTestLinkDropSlot(session.lastClientX, session.lastClientY, session.hrefKey) : hitTestColumnDropSlot(session.lastClientX, session.lastClientY);
10080
+ refreshFooterDragVisualsRef.current(session, slot, session.lastClientX, session.lastClientY);
10081
+ }
9277
10082
  if (hoveredImageRef.current) {
9278
10083
  const el = hoveredImageRef.current;
9279
10084
  const r2 = el.getBoundingClientRect();
@@ -9284,7 +10089,7 @@ function OhhwellsBridge() {
9284
10089
  };
9285
10090
  const handleSave = (e) => {
9286
10091
  if (e.data?.type !== "ow:save") return;
9287
- const nodes = collectEditableNodes();
10092
+ const nodes = collectEditableNodes(editContentRef.current);
9288
10093
  const tracker = document.querySelector("[data-ohw-sections-tracker]");
9289
10094
  if (tracker?.textContent) nodes.push({ key: "__ohw_sections", type: "sections", text: tracker.textContent });
9290
10095
  postToParentRef.current({ type: "ow:save-result", nodes });
@@ -9624,6 +10429,154 @@ function OhhwellsBridge() {
9624
10429
  if (imageShowTimerRef.current) clearTimeout(imageShowTimerRef.current);
9625
10430
  };
9626
10431
  }, [isEditMode, refreshStateRules]);
10432
+ (0, import_react9.useEffect)(() => {
10433
+ if (!isEditMode) return;
10434
+ const THRESHOLD = 10;
10435
+ const resolveWasSelected = (el) => {
10436
+ if (selectedElRef.current === el) return true;
10437
+ const activeAnchor = activeElRef.current ? getNavigationItemAnchor(activeElRef.current) : null;
10438
+ return activeAnchor === el;
10439
+ };
10440
+ const onPointerDown = (e) => {
10441
+ if (e.button !== 0) return;
10442
+ if (footerDragRef.current) return;
10443
+ const target = e.target;
10444
+ if (!target) return;
10445
+ if (target.closest('[data-ohw-drag-handle-container], [data-slot="drag-handle"], [data-ohw-toolbar], [data-ohw-item-toolbar-anchor], [data-ohw-link-popover-root]')) {
10446
+ return;
10447
+ }
10448
+ if (target.closest("[data-ohw-item-drag-surface]")) return;
10449
+ const anchor = getNavigationItemAnchor(target);
10450
+ const hrefKey = anchor?.getAttribute("data-ohw-href-key") ?? null;
10451
+ if (anchor && isFooterHrefKey(hrefKey)) {
10452
+ armFooterPressDrag();
10453
+ footerPointerDragRef.current = {
10454
+ el: anchor,
10455
+ kind: "link",
10456
+ startX: e.clientX,
10457
+ startY: e.clientY,
10458
+ pointerId: e.pointerId,
10459
+ wasSelected: resolveWasSelected(anchor),
10460
+ started: false
10461
+ };
10462
+ return;
10463
+ }
10464
+ const col = target.closest("[data-ohw-footer-col]");
10465
+ if (col && !getNavigationItemAnchor(target)) {
10466
+ armFooterPressDrag();
10467
+ footerPointerDragRef.current = {
10468
+ el: col,
10469
+ kind: "column",
10470
+ startX: e.clientX,
10471
+ startY: e.clientY,
10472
+ pointerId: e.pointerId,
10473
+ wasSelected: resolveWasSelected(col),
10474
+ started: false
10475
+ };
10476
+ }
10477
+ };
10478
+ const onPointerMove = (e) => {
10479
+ const pending = footerPointerDragRef.current;
10480
+ if (!pending) return;
10481
+ if (pending.started) {
10482
+ e.preventDefault();
10483
+ clearTextSelection();
10484
+ const session = footerDragRef.current;
10485
+ if (!session) return;
10486
+ const slot = session.kind === "link" && session.hrefKey ? hitTestLinkDropSlot(e.clientX, e.clientY, session.hrefKey) : hitTestColumnDropSlot(e.clientX, e.clientY);
10487
+ refreshFooterDragVisualsRef.current(session, slot, e.clientX, e.clientY);
10488
+ return;
10489
+ }
10490
+ const dx = e.clientX - pending.startX;
10491
+ const dy = e.clientY - pending.startY;
10492
+ if (dx * dx + dy * dy < THRESHOLD * THRESHOLD) return;
10493
+ e.preventDefault();
10494
+ pending.started = true;
10495
+ clearTextSelection();
10496
+ try {
10497
+ document.body.setPointerCapture(pending.pointerId);
10498
+ } catch {
10499
+ }
10500
+ if (linkPopoverOpenRef.current) {
10501
+ setLinkPopoverRef.current(null);
10502
+ }
10503
+ if (activeElRef.current) {
10504
+ deactivateRef.current();
10505
+ }
10506
+ if (pending.kind === "link") {
10507
+ const key = pending.el.getAttribute("data-ohw-href-key");
10508
+ if (!key) return;
10509
+ const column = findFooterColumnForLink(pending.el);
10510
+ const columns2 = listFooterColumns();
10511
+ beginFooterDragRef.current({
10512
+ kind: "link",
10513
+ hrefKey: key,
10514
+ columnEl: column,
10515
+ sourceColumnIndex: column ? columns2.indexOf(column) : 0,
10516
+ wasSelected: pending.wasSelected,
10517
+ draggedEl: pending.el,
10518
+ lastClientX: e.clientX,
10519
+ lastClientY: e.clientY,
10520
+ activeSlot: null
10521
+ });
10522
+ return;
10523
+ }
10524
+ const columns = listFooterColumns();
10525
+ const idx = columns.indexOf(pending.el);
10526
+ if (idx < 0) return;
10527
+ beginFooterDragRef.current({
10528
+ kind: "column",
10529
+ hrefKey: null,
10530
+ columnEl: pending.el,
10531
+ sourceColumnIndex: idx,
10532
+ wasSelected: pending.wasSelected,
10533
+ draggedEl: pending.el,
10534
+ lastClientX: e.clientX,
10535
+ lastClientY: e.clientY,
10536
+ activeSlot: null
10537
+ });
10538
+ };
10539
+ const endPointerDrag = (e) => {
10540
+ const pending = footerPointerDragRef.current;
10541
+ footerPointerDragRef.current = null;
10542
+ try {
10543
+ if (document.body.hasPointerCapture(e.pointerId)) {
10544
+ document.body.releasePointerCapture(e.pointerId);
10545
+ }
10546
+ } catch {
10547
+ }
10548
+ if (!pending?.started) {
10549
+ unlockFooterDragInteraction();
10550
+ return;
10551
+ }
10552
+ suppressNextClickRef.current = true;
10553
+ commitFooterDragRef.current(e.clientX, e.clientY);
10554
+ };
10555
+ const blockSelectStart = (e) => {
10556
+ if (footerDragRef.current || footerPointerDragRef.current?.started || document.documentElement.hasAttribute("data-ohw-footer-press-drag")) {
10557
+ e.preventDefault();
10558
+ }
10559
+ };
10560
+ const onDragEnd = (_e) => {
10561
+ if (!footerDragRef.current) return;
10562
+ commitFooterDragRef.current();
10563
+ };
10564
+ document.addEventListener("pointerdown", onPointerDown, true);
10565
+ document.addEventListener("pointermove", onPointerMove, true);
10566
+ document.addEventListener("pointerup", endPointerDrag, true);
10567
+ document.addEventListener("pointercancel", endPointerDrag, true);
10568
+ document.addEventListener("selectstart", blockSelectStart, true);
10569
+ document.addEventListener("dragend", onDragEnd, true);
10570
+ return () => {
10571
+ document.removeEventListener("pointerdown", onPointerDown, true);
10572
+ document.removeEventListener("pointermove", onPointerMove, true);
10573
+ document.removeEventListener("pointerup", endPointerDrag, true);
10574
+ document.removeEventListener("pointercancel", endPointerDrag, true);
10575
+ document.removeEventListener("selectstart", blockSelectStart, true);
10576
+ document.removeEventListener("dragend", onDragEnd, true);
10577
+ unlockFooterDragInteraction();
10578
+ };
10579
+ }, [isEditMode]);
9627
10580
  (0, import_react9.useEffect)(() => {
9628
10581
  const handler = (e) => {
9629
10582
  if (e.data?.type !== "ow:request-schedule-config") return;
@@ -9661,7 +10614,13 @@ function OhhwellsBridge() {
9661
10614
  const next = { ...prev, [pathKey]: sections };
9662
10615
  return shouldUseDevFixtures() ? { ...DEV_SECTIONS_BY_PATH, ...next } : next;
9663
10616
  });
9664
- postToParent2({ type: "ow:ready", version: "1", path: pathname, nodes: collectEditableNodes(), sections });
10617
+ postToParent2({
10618
+ type: "ow:ready",
10619
+ version: "1",
10620
+ path: pathname,
10621
+ nodes: collectEditableNodes(editContentRef.current),
10622
+ sections
10623
+ });
9665
10624
  postToParent2({ type: "ow:request-site-pages" });
9666
10625
  }, 150);
9667
10626
  return () => {
@@ -9778,9 +10737,6 @@ function OhhwellsBridge() {
9778
10737
  },
9779
10738
  [postToParent2, sitePages, sectionsByPath]
9780
10739
  );
9781
- const showEditLink = toolbarShowEditLink;
9782
- const currentSections = sectionsByPath[pathname] ?? [];
9783
- linkPopoverOpenRef.current = linkPopover !== null;
9784
10740
  const handleMediaReplace = (0, import_react9.useCallback)(
9785
10741
  (key) => {
9786
10742
  postToParent2({ type: "ow:image-pick", key, elementType: mediaHover?.elementType ?? "image" });
@@ -9823,10 +10779,13 @@ function OhhwellsBridge() {
9823
10779
  },
9824
10780
  [postToParent2]
9825
10781
  );
10782
+ const showEditLink = toolbarShowEditLink;
10783
+ const currentSections = sectionsByPath[pathname] ?? [];
10784
+ linkPopoverOpenRef.current = linkPopover !== null;
9826
10785
  return bridgeRoot ? (0, import_react_dom3.createPortal)(
9827
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
9828
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
9829
- Object.entries(uploadingRects).map(([key, { rect, fadingOut }]) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
10786
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(import_jsx_runtime25.Fragment, { children: [
10787
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
10788
+ Object.entries(uploadingRects).map(([key, { rect, fadingOut }]) => /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
9830
10789
  MediaOverlay,
9831
10790
  {
9832
10791
  hover: { key, rect, elementType: "image", isDragOver: false, hasTextOverlap: false },
@@ -9837,7 +10796,7 @@ function OhhwellsBridge() {
9837
10796
  },
9838
10797
  `uploading-${key}`
9839
10798
  )),
9840
- mediaHover && !(mediaHover.key in uploadingRects) && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
10799
+ mediaHover && !(mediaHover.key in uploadingRects) && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
9841
10800
  MediaOverlay,
9842
10801
  {
9843
10802
  hover: mediaHover,
@@ -9846,63 +10805,33 @@ function OhhwellsBridge() {
9846
10805
  onVideoSettingsChange: handleVideoSettingsChange
9847
10806
  }
9848
10807
  ),
9849
- carouselHover && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(CarouselOverlay, { hover: carouselHover, onEdit: handleEditCarousel }),
9850
- siblingHintRect && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
9851
- hoveredNavContainerRect && toolbarVariant !== "select-frame" && !linkPopover && !isItemDragging && !siblingHintRect && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
9852
- toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9853
- NavbarContainerChrome,
9854
- {
9855
- rect: toolbarRect,
9856
- onAdd: handleAddTopLevelNavItem
9857
- }
9858
- ),
9859
- hoveredItemRect && !hoveredNavContainerRect && !siblingHintRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
9860
- toolbarRect && (toolbarVariant === "link-action" || toolbarVariant === "select-frame") && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9861
- ItemInteractionLayer,
10808
+ carouselHover && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(CarouselOverlay, { hover: carouselHover, onEdit: handleEditCarousel }),
10809
+ siblingHintRect && !isItemDragging && siblingHintRects.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
10810
+ siblingHintRects.map((rect, i) => /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ItemInteractionLayer, { rect, state: "sibling-hint" }, `sibling-hint-${i}`)),
10811
+ isItemDragging && draggedItemRect && selectedElRef.current !== footerDragRef.current?.draggedEl && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ItemInteractionLayer, { rect: draggedItemRect, state: "dragging" }),
10812
+ isItemDragging && footerDropSlots.map((slot, i) => /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
10813
+ "div",
9862
10814
  {
9863
- rect: toolbarRect,
9864
- elRef: glowElRef,
9865
- state: isItemDragging ? "dragging" : resolveItemInteractionState(toolbarRect, parentScrollRef.current),
9866
- showHandle: toolbarVariant === "link-action" && Boolean(reorderHrefKey),
9867
- dragDisabled: reorderDragDisabled,
9868
- dragHandleLabel: reorderHrefKey ? `Reorder ${reorderHrefKey}` : "Reorder item",
9869
- onDragHandleDragStart: handleItemDragStart,
9870
- onDragHandleDragEnd: handleItemDragEnd,
9871
- toolbar: toolbarVariant === "link-action" && !isItemDragging ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9872
- ItemActionToolbar,
9873
- {
9874
- onEditLink: openLinkPopoverForSelected,
9875
- addItemDisabled: true,
9876
- editLinkDisabled: false,
9877
- moreDisabled: true
9878
- }
9879
- ) : void 0
9880
- }
9881
- ),
9882
- toolbarRect && toolbarVariant === "rich-text" && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
9883
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9884
- EditGlowChrome,
9885
- {
9886
- rect: toolbarRect,
9887
- elRef: glowElRef,
9888
- reorderHrefKey,
9889
- dragDisabled: reorderDragDisabled
9890
- }
9891
- ),
9892
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9893
- FloatingToolbar,
9894
- {
9895
- rect: toolbarRect,
9896
- parentScroll: parentScrollRef.current,
9897
- elRef: toolbarElRef,
9898
- onCommand: handleCommand,
9899
- activeCommands,
9900
- showEditLink,
9901
- onEditLink: openLinkPopoverForActive
9902
- }
9903
- )
10815
+ className: "pointer-events-none fixed z-2147483646",
10816
+ style: {
10817
+ left: slot.left,
10818
+ top: slot.top,
10819
+ width: slot.width,
10820
+ height: slot.height
10821
+ },
10822
+ children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(DropIndicator, { direction: slot.direction, state: activeFooterDropIndex === i ? "dragActive" : "dragIdle", className: slot.direction === "horizontal" ? "!h-full !w-full" : "!h-full !w-full" })
10823
+ },
10824
+ `footer-drop-${slot.direction}-${slot.columnIndex}-${slot.insertIndex}-${i}`
10825
+ )),
10826
+ hoveredNavContainerRect && toolbarVariant !== "select-frame" && !linkPopover && !isItemDragging && !siblingHintRect && siblingHintRects.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
10827
+ toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(NavbarContainerChrome, { rect: toolbarRect, onAdd: handleAddTopLevelNavItem }),
10828
+ hoveredItemRect && !hoveredNavContainerRect && !siblingHintRect && siblingHintRects.length === 0 && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
10829
+ toolbarRect && (toolbarVariant === "link-action" || toolbarVariant === "select-frame") && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ItemInteractionLayer, { rect: isItemDragging && draggedItemRect && footerDragRef.current?.wasSelected ? draggedItemRect : toolbarRect, elRef: glowElRef, state: isItemDragging ? "dragging" : resolveItemInteractionState(toolbarRect, parentScrollRef.current), showHandle: toolbarVariant === "link-action" && Boolean(reorderHrefKey) || toolbarVariant === "select-frame" && isFooterFrameSelection, dragDisabled: reorderDragDisabled, dragHandleLabel: reorderHrefKey ? `Reorder ${reorderHrefKey}` : "Reorder item", onDragHandleDragStart: handleItemDragStart, onDragHandleDragEnd: handleItemDragEnd, onItemPointerDown: handleItemChromePointerDown, onItemClick: handleItemChromeClick, toolbar: toolbarVariant === "link-action" && !isItemDragging ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ItemActionToolbar, { onEditLink: openLinkPopoverForSelected, addItemDisabled: true, editLinkDisabled: false, moreDisabled: true }) : void 0 }),
10830
+ toolbarRect && toolbarVariant === "rich-text" && /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(import_jsx_runtime25.Fragment, { children: [
10831
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(EditGlowChrome, { rect: toolbarRect, elRef: glowElRef, reorderHrefKey, dragDisabled: reorderDragDisabled }),
10832
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(FloatingToolbar, { rect: toolbarRect, parentScroll: parentScrollRef.current, elRef: toolbarElRef, onCommand: handleCommand, activeCommands, showEditLink, onEditLink: openLinkPopoverForActive })
9904
10833
  ] }),
9905
- maxBadge && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
10834
+ maxBadge && /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
9906
10835
  "div",
9907
10836
  {
9908
10837
  "data-ohw-max-badge": "",
@@ -9928,128 +10857,33 @@ function OhhwellsBridge() {
9928
10857
  ]
9929
10858
  }
9930
10859
  ),
9931
- toggleState && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9932
- StateToggle,
9933
- {
9934
- rect: toggleState.rect,
9935
- activeState: toggleState.activeState,
9936
- states: toggleState.states,
9937
- onStateChange: handleStateChange
9938
- }
9939
- ),
9940
- sectionGap && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
9941
- "div",
9942
- {
9943
- "data-ohw-section-insert-line": "",
9944
- className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
9945
- style: { top: sectionGap.y, transform: "translateY(-50%)" },
9946
- children: [
9947
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
9948
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9949
- Badge,
9950
- {
9951
- 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",
9952
- onClick: () => {
9953
- window.parent.postMessage(
9954
- { type: "ow:add-section", insertAfter: sectionGap.insertAfter, insertBefore: sectionGap.insertBefore },
9955
- "*"
9956
- );
9957
- },
9958
- children: "Add Section"
9959
- }
9960
- ),
9961
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
9962
- ]
9963
- }
9964
- ),
9965
- linkPopover && dialogPortalContainer ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9966
- LinkPopover,
9967
- {
9968
- panelRef: linkPopoverPanelRef,
9969
- portalContainer: dialogPortalContainer,
9970
- open: true,
9971
- mode: linkPopover.mode ?? "edit",
9972
- pages: sitePages,
9973
- sections: currentSections,
9974
- sectionsByPath,
9975
- initialTarget: linkPopover.target,
9976
- existingTargets: linkPopover.intent === "add-nav" ? getNavbarExistingTargets() : [],
9977
- onClose: closeLinkPopover,
9978
- onSubmit: handleLinkPopoverSubmit
9979
- },
9980
- linkPopover.key
9981
- ) : null,
9982
- sectionGap && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
9983
- "div",
9984
- {
9985
- "data-ohw-section-insert-line": "",
9986
- className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
9987
- style: { top: sectionGap.y, transform: "translateY(-50%)" },
9988
- children: [
9989
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
9990
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9991
- Badge,
9992
- {
9993
- 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",
9994
- onClick: () => {
9995
- window.parent.postMessage(
9996
- { type: "ow:add-section", insertAfter: sectionGap.insertAfter, insertBefore: sectionGap.insertBefore },
9997
- "*"
9998
- );
10860
+ toggleState && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(StateToggle, { rect: toggleState.rect, activeState: toggleState.activeState, states: toggleState.states, onStateChange: handleStateChange }),
10861
+ sectionGap && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { "data-ohw-section-insert-line": "", className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none", style: { top: sectionGap.y, transform: "translateY(-50%)" }, children: [
10862
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
10863
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
10864
+ Badge,
10865
+ {
10866
+ 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",
10867
+ onClick: () => {
10868
+ window.parent.postMessage(
10869
+ {
10870
+ type: "ow:add-section",
10871
+ insertAfter: sectionGap.insertAfter,
10872
+ insertBefore: sectionGap.insertBefore
9999
10873
  },
10000
- children: "Add Section"
10001
- }
10002
- ),
10003
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
10004
- ]
10005
- }
10006
- )
10874
+ "*"
10875
+ );
10876
+ },
10877
+ children: "Add Section"
10878
+ }
10879
+ ),
10880
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
10881
+ ] }),
10882
+ linkPopover && dialogPortalContainer ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(LinkPopover, { panelRef: linkPopoverPanelRef, portalContainer: dialogPortalContainer, open: true, mode: linkPopover.mode ?? "edit", pages: sitePages, sections: currentSections, sectionsByPath, initialTarget: linkPopover.target, existingTargets: linkPopover.intent === "add-nav" ? getNavbarExistingTargets() : [], onClose: closeLinkPopover, onSubmit: handleLinkPopoverSubmit }, linkPopover.key) : null
10007
10883
  ] }),
10008
10884
  bridgeRoot
10009
10885
  ) : null;
10010
10886
  }
10011
-
10012
- // src/ui/drop-indicator.tsx
10013
- var React10 = __toESM(require("react"), 1);
10014
- var import_jsx_runtime25 = require("react/jsx-runtime");
10015
- var dropIndicatorVariants = cva(
10016
- "ov-gap-line pointer-events-none shrink-0 transition-opacity duration-150",
10017
- {
10018
- variants: {
10019
- direction: {
10020
- vertical: "h-6 w-[3px]",
10021
- horizontal: "h-[3px] w-[200px]"
10022
- },
10023
- state: {
10024
- default: "opacity-0",
10025
- hover: "opacity-100",
10026
- dragIdle: "opacity-40",
10027
- dragActive: "opacity-100"
10028
- }
10029
- },
10030
- defaultVariants: {
10031
- direction: "vertical",
10032
- state: "default"
10033
- }
10034
- }
10035
- );
10036
- var DropIndicator = React10.forwardRef(
10037
- ({ className, direction, state, ...props }, ref) => {
10038
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
10039
- "div",
10040
- {
10041
- ref,
10042
- "data-slot": "drop-indicator",
10043
- "data-direction": direction ?? "vertical",
10044
- "data-state": state ?? "default",
10045
- "aria-hidden": "true",
10046
- className: cn(dropIndicatorVariants({ direction, state }), className),
10047
- ...props
10048
- }
10049
- );
10050
- }
10051
- );
10052
- DropIndicator.displayName = "DropIndicator";
10053
10887
  // Annotate the CommonJS export names for ESM import in node:
10054
10888
  0 && (module.exports = {
10055
10889
  CustomToolbar,