@ohhwells/bridge 0.1.38-next.63 → 0.1.38-next.66

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
@@ -4555,7 +4555,7 @@ function getChromeStyle(state) {
4555
4555
  case "dragging":
4556
4556
  return {
4557
4557
  border: `2px solid ${PRIMARY}`,
4558
- boxShadow: DRAG_SHADOW
4558
+ boxShadow: `${FOCUS_RING}, ${DRAG_SHADOW}`
4559
4559
  };
4560
4560
  default:
4561
4561
  return {};
@@ -4571,6 +4571,8 @@ function ItemInteractionLayer({
4571
4571
  dragHandleLabel = "Reorder item",
4572
4572
  onDragHandleDragStart,
4573
4573
  onDragHandleDragEnd,
4574
+ onItemPointerDown,
4575
+ onItemClick,
4574
4576
  chromeGap = 6,
4575
4577
  className
4576
4578
  }) {
@@ -4578,7 +4580,8 @@ function ItemInteractionLayer({
4578
4580
  const isActive = state === "active-top" || state === "active-bottom";
4579
4581
  const isDragging = state === "dragging";
4580
4582
  const showToolbar = isActive && toolbar;
4581
- const showDragHandle = isActive && showHandle && !isDragging;
4583
+ const showDragHandle = (isActive || isDragging) && showHandle;
4584
+ const itemDragEnabled = isActive && showHandle && !isDragging && !dragDisabled;
4582
4585
  return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
4583
4586
  "div",
4584
4587
  {
@@ -4603,11 +4606,28 @@ function ItemInteractionLayer({
4603
4606
  style: getChromeStyle(state)
4604
4607
  }
4605
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
+ ),
4606
4625
  showDragHandle && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4607
4626
  "div",
4608
4627
  {
4609
4628
  "data-ohw-drag-handle-container": "",
4610
- 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,
4611
4631
  children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4612
4632
  DragHandle,
4613
4633
  {
@@ -6609,6 +6629,319 @@ function insertNavbarItem(href, label, afterAnchor = null) {
6609
6629
  };
6610
6630
  }
6611
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
+
6612
6945
  // src/lib/carousel.ts
6613
6946
  var import_react8 = require("react");
6614
6947
  var CAROUSEL_ATTR = "data-ohw-carousel";
@@ -6737,8 +7070,50 @@ function NavbarContainerChrome({
6737
7070
  );
6738
7071
  }
6739
7072
 
6740
- // src/ui/badge.tsx
7073
+ // src/ui/drop-indicator.tsx
7074
+ var React9 = __toESM(require("react"), 1);
6741
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");
6742
7117
  var badgeVariants = cva(
6743
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",
6744
7119
  {
@@ -6756,12 +7131,12 @@ var badgeVariants = cva(
6756
7131
  }
6757
7132
  );
6758
7133
  function Badge({ className, variant, ...props }) {
6759
- 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 });
6760
7135
  }
6761
7136
 
6762
7137
  // src/OhhwellsBridge.tsx
6763
7138
  var import_lucide_react12 = require("lucide-react");
6764
- var import_jsx_runtime24 = require("react/jsx-runtime");
7139
+ var import_jsx_runtime25 = require("react/jsx-runtime");
6765
7140
  var PRIMARY2 = "#0885FE";
6766
7141
  var IMAGE_FADE_MS = 300;
6767
7142
  function runOpacityFade(el, onDone) {
@@ -6940,7 +7315,7 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
6940
7315
  const root = (0, import_client.createRoot)(container);
6941
7316
  (0, import_react_dom2.flushSync)(() => {
6942
7317
  root.render(
6943
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
7318
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
6944
7319
  SchedulingWidget,
6945
7320
  {
6946
7321
  notifyOnConnect,
@@ -7002,7 +7377,7 @@ function isDragHandleDisabled(el) {
7002
7377
  return raw === "true" || raw === "";
7003
7378
  });
7004
7379
  }
7005
- function collectEditableNodes() {
7380
+ function collectEditableNodes(extraContent) {
7006
7381
  const nodes = Array.from(document.querySelectorAll("[data-ohw-editable]")).map((el) => {
7007
7382
  if (el.dataset.ohwEditable === "image") {
7008
7383
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -7030,6 +7405,14 @@ function collectEditableNodes() {
7030
7405
  if (!key) return;
7031
7406
  nodes.push({ key, type: "link", text: getLinkHref2(el) });
7032
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
+ }
7033
7416
  document.querySelectorAll('[data-ohw-editable="video"]').forEach((el) => {
7034
7417
  const key = el.dataset.ohwKey ?? "";
7035
7418
  const video = getVideoEl(el);
@@ -7128,6 +7511,29 @@ function getHrefKeyFromElement(el) {
7128
7511
  if (!key) return null;
7129
7512
  return { anchor, key };
7130
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
+ }
7131
7537
  function isNavbarButton2(el) {
7132
7538
  return Boolean(el.closest('[data-ohw-role="navbar-button"]'));
7133
7539
  }
@@ -7426,7 +7832,7 @@ function EditGlowChrome({
7426
7832
  dragDisabled = false
7427
7833
  }) {
7428
7834
  const GAP = 6;
7429
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
7835
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
7430
7836
  "div",
7431
7837
  {
7432
7838
  ref: elRef,
@@ -7441,7 +7847,7 @@ function EditGlowChrome({
7441
7847
  zIndex: 2147483646
7442
7848
  },
7443
7849
  children: [
7444
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
7850
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
7445
7851
  "div",
7446
7852
  {
7447
7853
  style: {
@@ -7454,7 +7860,7 @@ function EditGlowChrome({
7454
7860
  }
7455
7861
  }
7456
7862
  ),
7457
- reorderHrefKey && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
7863
+ reorderHrefKey && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
7458
7864
  "div",
7459
7865
  {
7460
7866
  "data-ohw-drag-handle-container": "",
@@ -7466,7 +7872,7 @@ function EditGlowChrome({
7466
7872
  transform: "translate(calc(-100% - 7px), -50%)",
7467
7873
  pointerEvents: dragDisabled ? "none" : "auto"
7468
7874
  },
7469
- children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
7875
+ children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
7470
7876
  DragHandle,
7471
7877
  {
7472
7878
  "aria-label": `Reorder ${reorderHrefKey}`,
@@ -7570,7 +7976,7 @@ function FloatingToolbar({
7570
7976
  onEditLink
7571
7977
  }) {
7572
7978
  const { top, left, transform } = calcToolbarPos(rect, parentScroll, 330);
7573
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
7979
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
7574
7980
  "div",
7575
7981
  {
7576
7982
  ref: elRef,
@@ -7591,12 +7997,12 @@ function FloatingToolbar({
7591
7997
  fontFamily: "sans-serif",
7592
7998
  pointerEvents: "auto"
7593
7999
  },
7594
- children: /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(CustomToolbar, { children: [
7595
- TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_react9.default.Fragment, { children: [
7596
- 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, {}),
7597
8003
  btns.map((btn) => {
7598
8004
  const isActive = activeCommands.has(btn.cmd);
7599
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
8005
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
7600
8006
  CustomToolbarButton,
7601
8007
  {
7602
8008
  title: btn.title,
@@ -7605,7 +8011,7 @@ function FloatingToolbar({
7605
8011
  e.preventDefault();
7606
8012
  onCommand(btn.cmd);
7607
8013
  },
7608
- children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
8014
+ children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
7609
8015
  "svg",
7610
8016
  {
7611
8017
  width: "16",
@@ -7626,7 +8032,7 @@ function FloatingToolbar({
7626
8032
  );
7627
8033
  })
7628
8034
  ] }, gi)),
7629
- showEditLink ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
8035
+ showEditLink ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
7630
8036
  CustomToolbarButton,
7631
8037
  {
7632
8038
  type: "button",
@@ -7640,7 +8046,7 @@ function FloatingToolbar({
7640
8046
  e.preventDefault();
7641
8047
  e.stopPropagation();
7642
8048
  },
7643
- 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 })
7644
8050
  }
7645
8051
  ) : null
7646
8052
  ] })
@@ -7657,7 +8063,7 @@ function StateToggle({
7657
8063
  states,
7658
8064
  onStateChange
7659
8065
  }) {
7660
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
8066
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
7661
8067
  ToggleGroup,
7662
8068
  {
7663
8069
  "data-ohw-state-toggle": "",
@@ -7671,7 +8077,7 @@ function StateToggle({
7671
8077
  left: rect.right - 8,
7672
8078
  transform: "translateX(-100%)"
7673
8079
  },
7674
- 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))
7675
8081
  }
7676
8082
  );
7677
8083
  }
@@ -7795,7 +8201,15 @@ function OhhwellsBridge() {
7795
8201
  const [hoveredItemRect, setHoveredItemRect] = (0, import_react9.useState)(null);
7796
8202
  const siblingHintElRef = (0, import_react9.useRef)(null);
7797
8203
  const [siblingHintRect, setSiblingHintRect] = (0, import_react9.useState)(null);
8204
+ const [siblingHintRects, setSiblingHintRects] = (0, import_react9.useState)([]);
7798
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);
7799
8213
  const [linkPopover, setLinkPopover] = (0, import_react9.useState)(null);
7800
8214
  const linkPopoverSessionRef = (0, import_react9.useRef)(null);
7801
8215
  const addNavAfterAnchorRef = (0, import_react9.useRef)(null);
@@ -7959,8 +8373,10 @@ function OhhwellsBridge() {
7959
8373
  selectedElRef.current = null;
7960
8374
  setReorderHrefKey(null);
7961
8375
  setReorderDragDisabled(false);
8376
+ setIsFooterFrameSelection(false);
7962
8377
  siblingHintElRef.current = null;
7963
8378
  setSiblingHintRect(null);
8379
+ setSiblingHintRects([]);
7964
8380
  setIsItemDragging(false);
7965
8381
  hoveredNavContainerRef.current = null;
7966
8382
  setHoveredNavContainerRect(null);
@@ -8017,90 +8433,345 @@ function OhhwellsBridge() {
8017
8433
  intent: "add-nav"
8018
8434
  });
8019
8435
  }, []);
8020
- const handleItemDragStart = (0, import_react9.useCallback)(() => {
8021
- siblingHintElRef.current = null;
8022
- setSiblingHintRect(null);
8023
- setIsItemDragging(true);
8024
- }, []);
8025
- 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);
8026
8442
  setIsItemDragging(false);
8443
+ unlockFooterDragInteraction();
8027
8444
  }, []);
8028
- reselectNavigationItemRef.current = reselectNavigationItem;
8029
- commitNavigationTextEditRef.current = commitNavigationTextEdit;
8030
- const select = (0, import_react9.useCallback)((anchor) => {
8031
- if (!isNavigationItem(anchor)) return;
8032
- if (activeElRef.current) deactivate();
8033
- selectedElRef.current = anchor;
8034
- clearHrefKeyHover(anchor);
8035
- hoveredNavContainerRef.current = null;
8036
- setHoveredNavContainerRect(null);
8037
- setHoveredItemRect(null);
8038
- hoveredItemElRef.current = null;
8039
- siblingHintElRef.current = null;
8040
- setSiblingHintRect(null);
8041
- setIsItemDragging(false);
8042
- const { key, disabled } = getNavigationItemReorderState(anchor);
8043
- setReorderHrefKey(key);
8044
- setReorderDragDisabled(disabled);
8045
- setToolbarVariant("link-action");
8046
- setToolbarRect(anchor.getBoundingClientRect());
8047
- setToolbarShowEditLink(false);
8048
- setActiveCommands(/* @__PURE__ */ new Set());
8049
- }, [deactivate]);
8050
- const selectFrame = (0, import_react9.useCallback)((el) => {
8051
- if (!isNavigationContainer(el)) return;
8052
- if (activeElRef.current) deactivate();
8053
- selectedElRef.current = el;
8054
- clearHrefKeyHover(el);
8055
- hoveredNavContainerRef.current = null;
8056
- setHoveredNavContainerRect(null);
8057
- setHoveredItemRect(null);
8058
- hoveredItemElRef.current = null;
8059
- siblingHintElRef.current = null;
8060
- setSiblingHintRect(null);
8061
- setIsItemDragging(false);
8062
- setReorderHrefKey(null);
8063
- setReorderDragDisabled(false);
8064
- setToolbarVariant("select-frame");
8065
- setToolbarRect(el.getBoundingClientRect());
8066
- setToolbarShowEditLink(false);
8067
- setActiveCommands(/* @__PURE__ */ new Set());
8068
- }, [deactivate]);
8069
- const activate = (0, import_react9.useCallback)((el, options) => {
8070
- if (activeElRef.current === el) return;
8071
- selectedElRef.current = null;
8072
- deactivate();
8073
- if (hoveredImageRef.current) {
8074
- hoveredImageRef.current = null;
8075
- 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;
8076
8451
  }
8077
- setToolbarVariant("rich-text");
8078
- siblingHintElRef.current = null;
8079
- setSiblingHintRect(null);
8080
- setIsItemDragging(false);
8081
- el.setAttribute("contenteditable", "true");
8082
- el.removeAttribute("data-ohw-hovered");
8083
- el.closest("[data-ohw-href-key]")?.removeAttribute("data-ohw-hovered");
8084
- activeElRef.current = el;
8085
- originalContentRef.current = el.innerHTML;
8086
- el.focus();
8087
- if (options?.caretX !== void 0 && options?.caretY !== void 0) {
8088
- 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;
8089
8474
  }
8090
- setToolbarShowEditLink(Boolean(getHrefKeyFromElement(el)));
8091
- const navAnchor = getNavigationItemAnchor(el);
8092
- 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);
8093
8726
  setReorderHrefKey(null);
8094
8727
  setReorderDragDisabled(false);
8095
- } else {
8096
- const { key: reorderKey, disabled: reorderDisabled } = getReorderHandleState(el);
8097
- setReorderHrefKey(reorderKey);
8098
- setReorderDragDisabled(reorderDisabled);
8099
- }
8100
- setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
8101
- postToParent2({ type: "ow:enter-edit", key: el.dataset.ohwKey });
8102
- requestAnimationFrame(() => refreshActiveCommandsRef.current());
8103
- }, [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
+ );
8104
8775
  activateRef.current = activate;
8105
8776
  deactivateRef.current = deactivate;
8106
8777
  selectRef.current = select;
@@ -8148,6 +8819,7 @@ function OhhwellsBridge() {
8148
8819
  applyLinkByKey(key, val);
8149
8820
  }
8150
8821
  reconcileNavbarItemsFromContent(content);
8822
+ reconcileFooterOrderFromContent(content);
8151
8823
  enforceLinkHrefs();
8152
8824
  initSectionsFromContent(content, true);
8153
8825
  sectionsLoadedRef.current = true;
@@ -8209,6 +8881,7 @@ function OhhwellsBridge() {
8209
8881
  applyLinkByKey(key, val);
8210
8882
  }
8211
8883
  reconcileNavbarItemsFromContent(content);
8884
+ reconcileFooterOrderFromContent(content);
8212
8885
  } finally {
8213
8886
  observer?.observe(document.body, { childList: true, subtree: true });
8214
8887
  }
@@ -8249,15 +8922,42 @@ function OhhwellsBridge() {
8249
8922
  if (!subdomain) return {};
8250
8923
  return contentCache.get(subdomain) ?? {};
8251
8924
  };
8252
- 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
+ };
8253
8950
  run();
8254
8951
  const nav = document.querySelector("nav");
8255
- if (!nav) return;
8256
- const observer = new MutationObserver(() => {
8257
- requestAnimationFrame(run);
8258
- });
8259
- observer.observe(nav, { childList: true, subtree: true });
8260
- 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
+ };
8261
8961
  }, [isEditMode, pathname, subdomain, fetchState]);
8262
8962
  (0, import_react9.useEffect)(() => {
8263
8963
  if (!isEditMode) return;
@@ -8322,6 +9022,29 @@ function OhhwellsBridge() {
8322
9022
  [data-ohw-editable] {
8323
9023
  display: block;
8324
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
+ }
8325
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; }
8326
9049
  [data-ohw-editable="image"], [data-ohw-editable="image"] *,
8327
9050
  [data-ohw-editable="video"], [data-ohw-editable="video"] *,
@@ -8367,12 +9090,39 @@ function OhhwellsBridge() {
8367
9090
  }
8368
9091
  refreshStateRules();
8369
9092
  const handleClick = (e) => {
9093
+ if (suppressNextClickRef.current) {
9094
+ suppressNextClickRef.current = false;
9095
+ e.preventDefault();
9096
+ e.stopPropagation();
9097
+ return;
9098
+ }
8370
9099
  const target = e.target;
8371
9100
  if (target.closest("[data-ohw-toolbar]")) return;
8372
9101
  if (target.closest("[data-ohw-state-toggle]")) return;
8373
9102
  if (target.closest("[data-ohw-max-badge]")) return;
8374
9103
  if (isInsideLinkEditor(target)) return;
8375
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
+ }
8376
9126
  const navFromChrome = resolveNavContainerSelectionTarget(target, e.clientX, e.clientY);
8377
9127
  if (navFromChrome) {
8378
9128
  e.preventDefault();
@@ -8427,10 +9177,26 @@ function OhhwellsBridge() {
8427
9177
  if (hrefAnchor) {
8428
9178
  e.preventDefault();
8429
9179
  e.stopPropagation();
8430
- 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
+ }
8431
9190
  selectRef.current(hrefAnchor);
8432
9191
  return;
8433
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
+ }
8434
9200
  const navContainerToSelect = resolveNavContainerSelectionTarget(target, e.clientX, e.clientY);
8435
9201
  if (navContainerToSelect) {
8436
9202
  e.preventDefault();
@@ -8511,6 +9277,15 @@ function OhhwellsBridge() {
8511
9277
  setHoveredNavContainerRect(null);
8512
9278
  }
8513
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
+ }
8514
9289
  const navAnchor = getNavigationItemAnchor(target);
8515
9290
  if (navAnchor) {
8516
9291
  hoveredNavContainerRef.current = null;
@@ -8547,6 +9322,15 @@ function OhhwellsBridge() {
8547
9322
  hoveredNavContainerRef.current = null;
8548
9323
  setHoveredNavContainerRect(null);
8549
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
+ }
8550
9334
  const navAnchor = getNavigationItemAnchor(target);
8551
9335
  if (navAnchor) {
8552
9336
  const related2 = e.relatedTarget instanceof Element ? e.relatedTarget : null;
@@ -8955,6 +9739,14 @@ function OhhwellsBridge() {
8955
9739
  probeHoverCardsAt(clientX, clientY);
8956
9740
  };
8957
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
+ }
8958
9750
  e.preventDefault();
8959
9751
  const el = findImageAtPoint(e.clientX, e.clientY, false);
8960
9752
  if (!el) {
@@ -9162,6 +9954,7 @@ function OhhwellsBridge() {
9162
9954
  }
9163
9955
  editContentRef.current = { ...editContentRef.current, ...content };
9164
9956
  reconcileNavbarItemsFromContent(editContentRef.current);
9957
+ reconcileFooterOrderFromContent(editContentRef.current);
9165
9958
  enforceLinkHrefs();
9166
9959
  postToParentRef.current({ type: "ow:hydrate-done" });
9167
9960
  };
@@ -9275,6 +10068,17 @@ function OhhwellsBridge() {
9275
10068
  if (siblingHintElRef.current) {
9276
10069
  setSiblingHintRect(siblingHintElRef.current.getBoundingClientRect());
9277
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
+ }
9278
10082
  if (hoveredImageRef.current) {
9279
10083
  const el = hoveredImageRef.current;
9280
10084
  const r2 = el.getBoundingClientRect();
@@ -9285,7 +10089,7 @@ function OhhwellsBridge() {
9285
10089
  };
9286
10090
  const handleSave = (e) => {
9287
10091
  if (e.data?.type !== "ow:save") return;
9288
- const nodes = collectEditableNodes();
10092
+ const nodes = collectEditableNodes(editContentRef.current);
9289
10093
  const tracker = document.querySelector("[data-ohw-sections-tracker]");
9290
10094
  if (tracker?.textContent) nodes.push({ key: "__ohw_sections", type: "sections", text: tracker.textContent });
9291
10095
  postToParentRef.current({ type: "ow:save-result", nodes });
@@ -9625,6 +10429,154 @@ function OhhwellsBridge() {
9625
10429
  if (imageShowTimerRef.current) clearTimeout(imageShowTimerRef.current);
9626
10430
  };
9627
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]);
9628
10580
  (0, import_react9.useEffect)(() => {
9629
10581
  const handler = (e) => {
9630
10582
  if (e.data?.type !== "ow:request-schedule-config") return;
@@ -9662,7 +10614,13 @@ function OhhwellsBridge() {
9662
10614
  const next = { ...prev, [pathKey]: sections };
9663
10615
  return shouldUseDevFixtures() ? { ...DEV_SECTIONS_BY_PATH, ...next } : next;
9664
10616
  });
9665
- 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
+ });
9666
10624
  postToParent2({ type: "ow:request-site-pages" });
9667
10625
  }, 150);
9668
10626
  return () => {
@@ -9779,9 +10737,6 @@ function OhhwellsBridge() {
9779
10737
  },
9780
10738
  [postToParent2, sitePages, sectionsByPath]
9781
10739
  );
9782
- const showEditLink = toolbarShowEditLink;
9783
- const currentSections = sectionsByPath[pathname] ?? [];
9784
- linkPopoverOpenRef.current = linkPopover !== null;
9785
10740
  const handleMediaReplace = (0, import_react9.useCallback)(
9786
10741
  (key) => {
9787
10742
  postToParent2({ type: "ow:image-pick", key, elementType: mediaHover?.elementType ?? "image" });
@@ -9824,10 +10779,13 @@ function OhhwellsBridge() {
9824
10779
  },
9825
10780
  [postToParent2]
9826
10781
  );
10782
+ const showEditLink = toolbarShowEditLink;
10783
+ const currentSections = sectionsByPath[pathname] ?? [];
10784
+ linkPopoverOpenRef.current = linkPopover !== null;
9827
10785
  return bridgeRoot ? (0, import_react_dom3.createPortal)(
9828
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
9829
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
9830
- 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)(
9831
10789
  MediaOverlay,
9832
10790
  {
9833
10791
  hover: { key, rect, elementType: "image", isDragOver: false, hasTextOverlap: false },
@@ -9838,7 +10796,7 @@ function OhhwellsBridge() {
9838
10796
  },
9839
10797
  `uploading-${key}`
9840
10798
  )),
9841
- mediaHover && !(mediaHover.key in uploadingRects) && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
10799
+ mediaHover && !(mediaHover.key in uploadingRects) && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
9842
10800
  MediaOverlay,
9843
10801
  {
9844
10802
  hover: mediaHover,
@@ -9847,63 +10805,33 @@ function OhhwellsBridge() {
9847
10805
  onVideoSettingsChange: handleVideoSettingsChange
9848
10806
  }
9849
10807
  ),
9850
- carouselHover && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(CarouselOverlay, { hover: carouselHover, onEdit: handleEditCarousel }),
9851
- siblingHintRect && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
9852
- hoveredNavContainerRect && toolbarVariant !== "select-frame" && !linkPopover && !isItemDragging && !siblingHintRect && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
9853
- toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9854
- NavbarContainerChrome,
9855
- {
9856
- rect: toolbarRect,
9857
- onAdd: handleAddTopLevelNavItem
9858
- }
9859
- ),
9860
- hoveredItemRect && !hoveredNavContainerRect && !siblingHintRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
9861
- toolbarRect && (toolbarVariant === "link-action" || toolbarVariant === "select-frame") && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9862
- 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",
9863
10814
  {
9864
- rect: toolbarRect,
9865
- elRef: glowElRef,
9866
- state: isItemDragging ? "dragging" : resolveItemInteractionState(toolbarRect, parentScrollRef.current),
9867
- showHandle: toolbarVariant === "link-action" && Boolean(reorderHrefKey),
9868
- dragDisabled: reorderDragDisabled,
9869
- dragHandleLabel: reorderHrefKey ? `Reorder ${reorderHrefKey}` : "Reorder item",
9870
- onDragHandleDragStart: handleItemDragStart,
9871
- onDragHandleDragEnd: handleItemDragEnd,
9872
- toolbar: toolbarVariant === "link-action" && !isItemDragging ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9873
- ItemActionToolbar,
9874
- {
9875
- onEditLink: openLinkPopoverForSelected,
9876
- addItemDisabled: true,
9877
- editLinkDisabled: false,
9878
- moreDisabled: true
9879
- }
9880
- ) : void 0
9881
- }
9882
- ),
9883
- toolbarRect && toolbarVariant === "rich-text" && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
9884
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9885
- EditGlowChrome,
9886
- {
9887
- rect: toolbarRect,
9888
- elRef: glowElRef,
9889
- reorderHrefKey,
9890
- dragDisabled: reorderDragDisabled
9891
- }
9892
- ),
9893
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9894
- FloatingToolbar,
9895
- {
9896
- rect: toolbarRect,
9897
- parentScroll: parentScrollRef.current,
9898
- elRef: toolbarElRef,
9899
- onCommand: handleCommand,
9900
- activeCommands,
9901
- showEditLink,
9902
- onEditLink: openLinkPopoverForActive
9903
- }
9904
- )
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 })
9905
10833
  ] }),
9906
- maxBadge && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
10834
+ maxBadge && /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
9907
10835
  "div",
9908
10836
  {
9909
10837
  "data-ohw-max-badge": "",
@@ -9929,128 +10857,33 @@ function OhhwellsBridge() {
9929
10857
  ]
9930
10858
  }
9931
10859
  ),
9932
- toggleState && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9933
- StateToggle,
9934
- {
9935
- rect: toggleState.rect,
9936
- activeState: toggleState.activeState,
9937
- states: toggleState.states,
9938
- onStateChange: handleStateChange
9939
- }
9940
- ),
9941
- sectionGap && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
9942
- "div",
9943
- {
9944
- "data-ohw-section-insert-line": "",
9945
- className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
9946
- style: { top: sectionGap.y, transform: "translateY(-50%)" },
9947
- children: [
9948
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
9949
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9950
- Badge,
9951
- {
9952
- 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",
9953
- onClick: () => {
9954
- window.parent.postMessage(
9955
- { type: "ow:add-section", insertAfter: sectionGap.insertAfter, insertBefore: sectionGap.insertBefore },
9956
- "*"
9957
- );
9958
- },
9959
- children: "Add Section"
9960
- }
9961
- ),
9962
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
9963
- ]
9964
- }
9965
- ),
9966
- linkPopover && dialogPortalContainer ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9967
- LinkPopover,
9968
- {
9969
- panelRef: linkPopoverPanelRef,
9970
- portalContainer: dialogPortalContainer,
9971
- open: true,
9972
- mode: linkPopover.mode ?? "edit",
9973
- pages: sitePages,
9974
- sections: currentSections,
9975
- sectionsByPath,
9976
- initialTarget: linkPopover.target,
9977
- existingTargets: linkPopover.intent === "add-nav" ? getNavbarExistingTargets() : [],
9978
- onClose: closeLinkPopover,
9979
- onSubmit: handleLinkPopoverSubmit
9980
- },
9981
- linkPopover.key
9982
- ) : null,
9983
- sectionGap && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
9984
- "div",
9985
- {
9986
- "data-ohw-section-insert-line": "",
9987
- className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
9988
- style: { top: sectionGap.y, transform: "translateY(-50%)" },
9989
- children: [
9990
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
9991
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9992
- Badge,
9993
- {
9994
- 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",
9995
- onClick: () => {
9996
- window.parent.postMessage(
9997
- { type: "ow:add-section", insertAfter: sectionGap.insertAfter, insertBefore: sectionGap.insertBefore },
9998
- "*"
9999
- );
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
10000
10873
  },
10001
- children: "Add Section"
10002
- }
10003
- ),
10004
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
10005
- ]
10006
- }
10007
- )
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
10008
10883
  ] }),
10009
10884
  bridgeRoot
10010
10885
  ) : null;
10011
10886
  }
10012
-
10013
- // src/ui/drop-indicator.tsx
10014
- var React10 = __toESM(require("react"), 1);
10015
- var import_jsx_runtime25 = require("react/jsx-runtime");
10016
- var dropIndicatorVariants = cva(
10017
- "ov-gap-line pointer-events-none shrink-0 transition-opacity duration-150",
10018
- {
10019
- variants: {
10020
- direction: {
10021
- vertical: "h-6 w-[3px]",
10022
- horizontal: "h-[3px] w-[200px]"
10023
- },
10024
- state: {
10025
- default: "opacity-0",
10026
- hover: "opacity-100",
10027
- dragIdle: "opacity-40",
10028
- dragActive: "opacity-100"
10029
- }
10030
- },
10031
- defaultVariants: {
10032
- direction: "vertical",
10033
- state: "default"
10034
- }
10035
- }
10036
- );
10037
- var DropIndicator = React10.forwardRef(
10038
- ({ className, direction, state, ...props }, ref) => {
10039
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
10040
- "div",
10041
- {
10042
- ref,
10043
- "data-slot": "drop-indicator",
10044
- "data-direction": direction ?? "vertical",
10045
- "data-state": state ?? "default",
10046
- "aria-hidden": "true",
10047
- className: cn(dropIndicatorVariants({ direction, state }), className),
10048
- ...props
10049
- }
10050
- );
10051
- }
10052
- );
10053
- DropIndicator.displayName = "DropIndicator";
10054
10887
  // Annotate the CommonJS export names for ESM import in node:
10055
10888
  0 && (module.exports = {
10056
10889
  CustomToolbar,