@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.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  // src/OhhwellsBridge.tsx
4
- import React9, { useCallback as useCallback4, useEffect as useEffect8, useLayoutEffect as useLayoutEffect2, useRef as useRef5, useState as useState7 } from "react";
4
+ import React10, { useCallback as useCallback4, useEffect as useEffect8, useLayoutEffect as useLayoutEffect2, useRef as useRef5, useState as useState7 } from "react";
5
5
  import { createRoot } from "react-dom/client";
6
6
  import { flushSync } from "react-dom";
7
7
 
@@ -511,7 +511,7 @@ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal
511
511
  }
512
512
  )
513
513
  ] }),
514
- /* @__PURE__ */ jsx2("div", { className: "sm:hidden flex flex-col gap-px", children: isFull ? /* @__PURE__ */ jsx2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
514
+ cls.showAvailability !== false && /* @__PURE__ */ jsx2("div", { className: "sm:hidden flex flex-col gap-px", children: isFull ? /* @__PURE__ */ jsx2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
515
515
  /* @__PURE__ */ jsxs2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
516
516
  available,
517
517
  "/",
@@ -523,9 +523,10 @@ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal
523
523
  /* @__PURE__ */ jsxs2("div", { className: "flex flex-1 flex-col gap-2 min-w-0 sm:contents", children: [
524
524
  /* @__PURE__ */ jsxs2("div", { className: "flex-1 flex flex-col gap-2 min-w-0", children: [
525
525
  /* @__PURE__ */ jsx2("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block truncate", children: cls.name }),
526
- cls.hostName && /* @__PURE__ */ jsx2("span", { className: "font-body text-base font-normal text-(--color-dark,#200C02) opacity-80 block truncate", children: cls.hostName })
526
+ cls.hostName && /* @__PURE__ */ jsx2("span", { className: "font-body text-base font-normal text-(--color-dark,#200C02) opacity-80 block truncate", children: cls.hostName }),
527
+ cls.description && /* @__PURE__ */ jsx2("span", { className: "font-body text-sm font-normal text-(--color-dark,#200C02) opacity-70 block truncate", children: cls.description })
527
528
  ] }),
528
- /* @__PURE__ */ jsx2("div", { className: "hidden sm:flex w-14 shrink-0 flex-col gap-px", children: isFull ? /* @__PURE__ */ jsx2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
529
+ cls.showAvailability !== false && /* @__PURE__ */ jsx2("div", { className: "hidden sm:flex w-14 shrink-0 flex-col gap-px", children: isFull ? /* @__PURE__ */ jsx2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
529
530
  /* @__PURE__ */ jsxs2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
530
531
  available,
531
532
  "/",
@@ -4492,7 +4493,7 @@ function getChromeStyle(state) {
4492
4493
  case "dragging":
4493
4494
  return {
4494
4495
  border: `2px solid ${PRIMARY}`,
4495
- boxShadow: DRAG_SHADOW
4496
+ boxShadow: `${FOCUS_RING}, ${DRAG_SHADOW}`
4496
4497
  };
4497
4498
  default:
4498
4499
  return {};
@@ -4508,6 +4509,8 @@ function ItemInteractionLayer({
4508
4509
  dragHandleLabel = "Reorder item",
4509
4510
  onDragHandleDragStart,
4510
4511
  onDragHandleDragEnd,
4512
+ onItemPointerDown,
4513
+ onItemClick,
4511
4514
  chromeGap = 6,
4512
4515
  className
4513
4516
  }) {
@@ -4515,7 +4518,8 @@ function ItemInteractionLayer({
4515
4518
  const isActive = state === "active-top" || state === "active-bottom";
4516
4519
  const isDragging = state === "dragging";
4517
4520
  const showToolbar = isActive && toolbar;
4518
- const showDragHandle = isActive && showHandle && !isDragging;
4521
+ const showDragHandle = (isActive || isDragging) && showHandle;
4522
+ const itemDragEnabled = isActive && showHandle && !isDragging && !dragDisabled;
4519
4523
  return /* @__PURE__ */ jsxs4(
4520
4524
  "div",
4521
4525
  {
@@ -4540,11 +4544,28 @@ function ItemInteractionLayer({
4540
4544
  style: getChromeStyle(state)
4541
4545
  }
4542
4546
  ),
4547
+ itemDragEnabled && /* @__PURE__ */ jsx9(
4548
+ "div",
4549
+ {
4550
+ "data-ohw-item-drag-surface": "",
4551
+ className: "pointer-events-auto absolute inset-0 cursor-grab rounded-lg active:cursor-grabbing",
4552
+ onPointerDown: (e) => {
4553
+ if (e.button !== 0) return;
4554
+ onItemPointerDown?.(e);
4555
+ },
4556
+ onClick: (e) => {
4557
+ e.preventDefault();
4558
+ e.stopPropagation();
4559
+ onItemClick?.(e.clientX, e.clientY);
4560
+ }
4561
+ }
4562
+ ),
4543
4563
  showDragHandle && /* @__PURE__ */ jsx9(
4544
4564
  "div",
4545
4565
  {
4546
4566
  "data-ohw-drag-handle-container": "",
4547
- className: "pointer-events-auto absolute left-0 top-1/2 -translate-x-[calc(100%+7px)] -translate-y-1/2",
4567
+ className: "pointer-events-auto absolute left-0 top-1/2 z-10 -translate-x-[calc(100%+7px)] -translate-y-1/2",
4568
+ style: isDragging ? { visibility: "hidden" } : void 0,
4548
4569
  children: /* @__PURE__ */ jsx9(
4549
4570
  DragHandle,
4550
4571
  {
@@ -6546,6 +6567,319 @@ function insertNavbarItem(href, label, afterAnchor = null) {
6546
6567
  };
6547
6568
  }
6548
6569
 
6570
+ // src/lib/footer-items.ts
6571
+ var FOOTER_ORDER_KEY = "__ohw_footer_order";
6572
+ var FOOTER_HREF_RE = /^footer-(\d+)-(\d+)-href$/;
6573
+ function parseFooterHrefKey(key) {
6574
+ if (!key) return null;
6575
+ const match = key.match(FOOTER_HREF_RE);
6576
+ if (!match) return null;
6577
+ return { col: parseInt(match[1], 10), item: parseInt(match[2], 10) };
6578
+ }
6579
+ function isFooterHrefKey(key) {
6580
+ return parseFooterHrefKey(key) !== null;
6581
+ }
6582
+ function getFooterRoot() {
6583
+ return document.querySelector('footer[data-ohw-section="footer"], footer');
6584
+ }
6585
+ function getFooterLinksContainer() {
6586
+ return document.querySelector("[data-ohw-footer-links]") ?? document.querySelector(".rb-footer-links");
6587
+ }
6588
+ function isFooterLinkAnchor(el) {
6589
+ if (!el.matches("[data-ohw-href-key]")) return false;
6590
+ if (!isFooterHrefKey(el.getAttribute("data-ohw-href-key"))) return false;
6591
+ return Boolean(el.querySelector('[data-ohw-editable="text"]'));
6592
+ }
6593
+ function listFooterColumns() {
6594
+ const root = getFooterLinksContainer() ?? getFooterRoot()?.querySelector(".rb-footer-links") ?? null;
6595
+ if (!root) return [];
6596
+ const explicit = Array.from(root.querySelectorAll(":scope > [data-ohw-footer-col]"));
6597
+ if (explicit.length > 0) return explicit;
6598
+ return Array.from(root.children).filter((el) => {
6599
+ if (!(el instanceof HTMLElement)) return false;
6600
+ return Array.from(el.querySelectorAll(":scope > [data-ohw-href-key]")).some(
6601
+ isFooterLinkAnchor
6602
+ );
6603
+ });
6604
+ }
6605
+ function listFooterLinksInColumn(column) {
6606
+ return Array.from(column.querySelectorAll(":scope > [data-ohw-href-key]")).filter(
6607
+ isFooterLinkAnchor
6608
+ );
6609
+ }
6610
+ function findFooterColumnForLink(anchor) {
6611
+ const columns = listFooterColumns();
6612
+ return columns.find((col) => col.contains(anchor)) ?? null;
6613
+ }
6614
+ function getFooterOrderFromDom() {
6615
+ return listFooterColumns().map(
6616
+ (col) => listFooterLinksInColumn(col).map((el) => el.getAttribute("data-ohw-href-key")).filter((key) => Boolean(key))
6617
+ );
6618
+ }
6619
+ function findFooterLinkByKey(hrefKey) {
6620
+ return document.querySelector(
6621
+ `footer [data-ohw-href-key="${CSS.escape(hrefKey)}"]`
6622
+ );
6623
+ }
6624
+ function syncFooterColumnIndices(columns) {
6625
+ columns.forEach((col, index) => {
6626
+ const next = String(index);
6627
+ if (col.getAttribute("data-ohw-footer-col") !== next) {
6628
+ col.setAttribute("data-ohw-footer-col", next);
6629
+ }
6630
+ });
6631
+ }
6632
+ function appendChildIfNeeded(parent, child) {
6633
+ if (parent.lastElementChild !== child) {
6634
+ parent.appendChild(child);
6635
+ }
6636
+ }
6637
+ function insertLinkBeforeNext(col, el, nextEl) {
6638
+ if (nextEl) {
6639
+ if (el.nextElementSibling !== nextEl) {
6640
+ col.insertBefore(el, nextEl);
6641
+ }
6642
+ return;
6643
+ }
6644
+ if (col.lastElementChild !== el) {
6645
+ col.appendChild(el);
6646
+ }
6647
+ }
6648
+ function applyFooterOrder(order) {
6649
+ const container = getFooterLinksContainer();
6650
+ if (!container) return;
6651
+ const columns = listFooterColumns();
6652
+ if (columns.length === 0) return;
6653
+ const currentOrder = getFooterOrderFromDom();
6654
+ if (ordersEqual(order, currentOrder)) {
6655
+ syncFooterColumnIndices(columns);
6656
+ return;
6657
+ }
6658
+ const colCount = Math.max(columns.length, order.length);
6659
+ const working = columns.slice(0, colCount);
6660
+ for (let i = 0; i < order.length && i < working.length; i++) {
6661
+ appendChildIfNeeded(container, working[i]);
6662
+ }
6663
+ for (let i = order.length; i < working.length; i++) {
6664
+ appendChildIfNeeded(container, working[i]);
6665
+ }
6666
+ const freshColumns = listFooterColumns();
6667
+ syncFooterColumnIndices(freshColumns);
6668
+ for (let c = 0; c < order.length; c++) {
6669
+ const col = freshColumns[c];
6670
+ if (!col) continue;
6671
+ const colOrder = order[c];
6672
+ for (let j = 0; j < colOrder.length; j++) {
6673
+ const hrefKey = colOrder[j];
6674
+ const el = findFooterLinkByKey(hrefKey);
6675
+ if (!el) continue;
6676
+ if (el.parentElement !== col) {
6677
+ col.appendChild(el);
6678
+ }
6679
+ const nextKey = colOrder[j + 1];
6680
+ const nextEl = nextKey ? findFooterLinkByKey(nextKey) : null;
6681
+ insertLinkBeforeNext(col, el, nextEl?.parentElement === col ? nextEl : null);
6682
+ }
6683
+ }
6684
+ }
6685
+ function ordersEqual(a, b) {
6686
+ if (a.length !== b.length) return false;
6687
+ for (let i = 0; i < a.length; i++) {
6688
+ const left = a[i];
6689
+ const right = b[i];
6690
+ if (left.length !== right.length) return false;
6691
+ for (let j = 0; j < left.length; j++) {
6692
+ if (left[j] !== right[j]) return false;
6693
+ }
6694
+ }
6695
+ return true;
6696
+ }
6697
+ function planFooterLinkMove(hrefKey, targetColIndex, insertIndex) {
6698
+ const order = getFooterOrderFromDom();
6699
+ if (targetColIndex < 0 || targetColIndex >= order.length) return null;
6700
+ let fromCol = -1;
6701
+ let fromIdx = -1;
6702
+ for (let c = 0; c < order.length; c++) {
6703
+ const idx = order[c].indexOf(hrefKey);
6704
+ if (idx >= 0) {
6705
+ fromCol = c;
6706
+ fromIdx = idx;
6707
+ break;
6708
+ }
6709
+ }
6710
+ if (fromCol < 0) return null;
6711
+ const next = order.map((col) => [...col]);
6712
+ next[fromCol].splice(fromIdx, 1);
6713
+ let adjusted = insertIndex;
6714
+ if (fromCol === targetColIndex && fromIdx < insertIndex) adjusted -= 1;
6715
+ adjusted = Math.max(0, Math.min(adjusted, next[targetColIndex].length));
6716
+ next[targetColIndex].splice(adjusted, 0, hrefKey);
6717
+ if (ordersEqual(next, order)) return null;
6718
+ return next;
6719
+ }
6720
+ function planFooterColumnMove(fromIndex, toIndex) {
6721
+ const order = getFooterOrderFromDom();
6722
+ const columns = listFooterColumns();
6723
+ if (fromIndex < 0 || fromIndex >= columns.length || toIndex < 0 || toIndex > columns.length) {
6724
+ return null;
6725
+ }
6726
+ if (toIndex === fromIndex || toIndex === fromIndex + 1) return null;
6727
+ const nextOrder = order.map((col) => [...col]);
6728
+ const [movedOrder] = nextOrder.splice(fromIndex, 1);
6729
+ if (!movedOrder) return null;
6730
+ let adjusted = toIndex;
6731
+ if (fromIndex < toIndex) adjusted -= 1;
6732
+ adjusted = Math.max(0, Math.min(adjusted, nextOrder.length));
6733
+ nextOrder.splice(adjusted, 0, movedOrder);
6734
+ if (ordersEqual(nextOrder, order)) return null;
6735
+ return nextOrder;
6736
+ }
6737
+ function parseFooterOrder(content) {
6738
+ const raw = content[FOOTER_ORDER_KEY];
6739
+ if (!raw) return null;
6740
+ try {
6741
+ const parsed = JSON.parse(raw);
6742
+ if (!Array.isArray(parsed)) return null;
6743
+ return parsed.filter((col) => Array.isArray(col)).map((col) => col.filter((key) => typeof key === "string" && key.length > 0));
6744
+ } catch {
6745
+ return null;
6746
+ }
6747
+ }
6748
+ function reconcileFooterOrderFromContent(content) {
6749
+ const order = parseFooterOrder(content);
6750
+ if (!order?.length) return;
6751
+ const current = getFooterOrderFromDom();
6752
+ if (ordersEqual(order, current)) {
6753
+ syncFooterColumnIndices(listFooterColumns());
6754
+ return;
6755
+ }
6756
+ applyFooterOrder(order);
6757
+ }
6758
+ function buildLinkDropSlots(column, columnIndex) {
6759
+ const links = listFooterLinksInColumn(column);
6760
+ const colRect = column.getBoundingClientRect();
6761
+ const slots = [];
6762
+ const barThickness = 3;
6763
+ if (links.length === 0) {
6764
+ slots.push({
6765
+ insertIndex: 0,
6766
+ columnIndex,
6767
+ left: colRect.left,
6768
+ top: colRect.top + colRect.height / 2 - barThickness / 2,
6769
+ width: colRect.width,
6770
+ height: barThickness,
6771
+ direction: "horizontal"
6772
+ });
6773
+ return slots;
6774
+ }
6775
+ for (let i = 0; i <= links.length; i++) {
6776
+ let top;
6777
+ if (i === 0) {
6778
+ top = links[0].getBoundingClientRect().top - barThickness / 2;
6779
+ } else if (i === links.length) {
6780
+ const last = links[links.length - 1].getBoundingClientRect();
6781
+ top = last.bottom - barThickness / 2;
6782
+ } else {
6783
+ const prev = links[i - 1].getBoundingClientRect();
6784
+ const next = links[i].getBoundingClientRect();
6785
+ top = (prev.bottom + next.top) / 2 - barThickness / 2;
6786
+ }
6787
+ const widthRef = i === 0 ? links[0].getBoundingClientRect() : i === links.length ? links[links.length - 1].getBoundingClientRect() : links[i].getBoundingClientRect();
6788
+ slots.push({
6789
+ insertIndex: i,
6790
+ columnIndex,
6791
+ left: widthRef.left,
6792
+ top,
6793
+ width: Math.max(widthRef.width, colRect.width * 0.8),
6794
+ height: barThickness,
6795
+ direction: "horizontal"
6796
+ });
6797
+ }
6798
+ return slots;
6799
+ }
6800
+ function buildColumnDropSlots() {
6801
+ const columns = listFooterColumns();
6802
+ const slots = [];
6803
+ const barThickness = 3;
6804
+ if (columns.length === 0) return slots;
6805
+ for (let i = 0; i <= columns.length; i++) {
6806
+ let left;
6807
+ let height;
6808
+ let top;
6809
+ if (i === 0) {
6810
+ const first = columns[0].getBoundingClientRect();
6811
+ left = first.left - barThickness / 2;
6812
+ top = first.top;
6813
+ height = first.height;
6814
+ } else if (i === columns.length) {
6815
+ const last = columns[columns.length - 1].getBoundingClientRect();
6816
+ left = last.right - barThickness / 2;
6817
+ top = last.top;
6818
+ height = last.height;
6819
+ } else {
6820
+ const prev = columns[i - 1].getBoundingClientRect();
6821
+ const next = columns[i].getBoundingClientRect();
6822
+ left = (prev.right + next.left) / 2 - barThickness / 2;
6823
+ top = Math.min(prev.top, next.top);
6824
+ height = Math.max(prev.bottom, next.bottom) - top;
6825
+ }
6826
+ slots.push({
6827
+ insertIndex: i,
6828
+ columnIndex: i,
6829
+ left,
6830
+ top,
6831
+ width: barThickness,
6832
+ height: Math.max(height, 24),
6833
+ direction: "vertical"
6834
+ });
6835
+ }
6836
+ return slots;
6837
+ }
6838
+ function hitTestLinkDropSlot(clientX, clientY, draggedHrefKey) {
6839
+ const columns = listFooterColumns();
6840
+ let best = null;
6841
+ for (let c = 0; c < columns.length; c++) {
6842
+ const col = columns[c];
6843
+ const colRect = col.getBoundingClientRect();
6844
+ const inColX = clientX >= colRect.left - 24 && clientX <= colRect.right + 24;
6845
+ if (!inColX) continue;
6846
+ const slots = buildLinkDropSlots(col, c).filter((slot) => {
6847
+ const links = listFooterLinksInColumn(col);
6848
+ const fromIdx = links.findIndex(
6849
+ (el) => el.getAttribute("data-ohw-href-key") === draggedHrefKey
6850
+ );
6851
+ if (fromIdx < 0) return true;
6852
+ if (c === findColumnIndexForKey(draggedHrefKey) && (slot.insertIndex === fromIdx || slot.insertIndex === fromIdx + 1)) {
6853
+ return true;
6854
+ }
6855
+ return true;
6856
+ });
6857
+ for (const slot of slots) {
6858
+ const cy = slot.top + slot.height / 2;
6859
+ const dist = Math.abs(clientY - cy) + (inColX ? 0 : 1e3);
6860
+ if (!best || dist < best.dist) best = { slot, dist };
6861
+ }
6862
+ }
6863
+ return best?.slot ?? null;
6864
+ }
6865
+ function findColumnIndexForKey(hrefKey) {
6866
+ const order = getFooterOrderFromDom();
6867
+ for (let c = 0; c < order.length; c++) {
6868
+ if (order[c].includes(hrefKey)) return c;
6869
+ }
6870
+ return -1;
6871
+ }
6872
+ function hitTestColumnDropSlot(clientX, _clientY) {
6873
+ const slots = buildColumnDropSlots();
6874
+ let best = null;
6875
+ for (const slot of slots) {
6876
+ const cx2 = slot.left + slot.width / 2;
6877
+ const dist = Math.abs(clientX - cx2);
6878
+ if (!best || dist < best.dist) best = { slot, dist };
6879
+ }
6880
+ return best?.slot ?? null;
6881
+ }
6882
+
6549
6883
  // src/lib/carousel.ts
6550
6884
  import { useEffect as useEffect7, useState as useState6 } from "react";
6551
6885
  var CAROUSEL_ATTR = "data-ohw-carousel";
@@ -6674,8 +7008,50 @@ function NavbarContainerChrome({
6674
7008
  );
6675
7009
  }
6676
7010
 
6677
- // src/ui/badge.tsx
7011
+ // src/ui/drop-indicator.tsx
7012
+ import * as React9 from "react";
6678
7013
  import { jsx as jsx23 } from "react/jsx-runtime";
7014
+ var dropIndicatorVariants = cva(
7015
+ "ov-gap-line pointer-events-none shrink-0 transition-opacity duration-150",
7016
+ {
7017
+ variants: {
7018
+ direction: {
7019
+ vertical: "h-6 w-[3px]",
7020
+ horizontal: "h-[3px] w-[200px]"
7021
+ },
7022
+ state: {
7023
+ default: "opacity-0",
7024
+ hover: "opacity-100",
7025
+ dragIdle: "opacity-40",
7026
+ dragActive: "opacity-100"
7027
+ }
7028
+ },
7029
+ defaultVariants: {
7030
+ direction: "vertical",
7031
+ state: "default"
7032
+ }
7033
+ }
7034
+ );
7035
+ var DropIndicator = React9.forwardRef(
7036
+ ({ className, direction, state, ...props }, ref) => {
7037
+ return /* @__PURE__ */ jsx23(
7038
+ "div",
7039
+ {
7040
+ ref,
7041
+ "data-slot": "drop-indicator",
7042
+ "data-direction": direction ?? "vertical",
7043
+ "data-state": state ?? "default",
7044
+ "aria-hidden": "true",
7045
+ className: cn(dropIndicatorVariants({ direction, state }), className),
7046
+ ...props
7047
+ }
7048
+ );
7049
+ }
7050
+ );
7051
+ DropIndicator.displayName = "DropIndicator";
7052
+
7053
+ // src/ui/badge.tsx
7054
+ import { jsx as jsx24 } from "react/jsx-runtime";
6679
7055
  var badgeVariants = cva(
6680
7056
  "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",
6681
7057
  {
@@ -6693,12 +7069,12 @@ var badgeVariants = cva(
6693
7069
  }
6694
7070
  );
6695
7071
  function Badge({ className, variant, ...props }) {
6696
- return /* @__PURE__ */ jsx23("div", { className: cn(badgeVariants({ variant }), className), ...props });
7072
+ return /* @__PURE__ */ jsx24("div", { className: cn(badgeVariants({ variant }), className), ...props });
6697
7073
  }
6698
7074
 
6699
7075
  // src/OhhwellsBridge.tsx
6700
7076
  import { Link as Link2 } from "lucide-react";
6701
- import { Fragment as Fragment5, jsx as jsx24, jsxs as jsxs14 } from "react/jsx-runtime";
7077
+ import { Fragment as Fragment5, jsx as jsx25, jsxs as jsxs14 } from "react/jsx-runtime";
6702
7078
  var PRIMARY2 = "#0885FE";
6703
7079
  var IMAGE_FADE_MS = 300;
6704
7080
  function runOpacityFade(el, onDone) {
@@ -6877,7 +7253,7 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
6877
7253
  const root = createRoot(container);
6878
7254
  flushSync(() => {
6879
7255
  root.render(
6880
- /* @__PURE__ */ jsx24(
7256
+ /* @__PURE__ */ jsx25(
6881
7257
  SchedulingWidget,
6882
7258
  {
6883
7259
  notifyOnConnect,
@@ -6939,7 +7315,7 @@ function isDragHandleDisabled(el) {
6939
7315
  return raw === "true" || raw === "";
6940
7316
  });
6941
7317
  }
6942
- function collectEditableNodes() {
7318
+ function collectEditableNodes(extraContent) {
6943
7319
  const nodes = Array.from(document.querySelectorAll("[data-ohw-editable]")).map((el) => {
6944
7320
  if (el.dataset.ohwEditable === "image") {
6945
7321
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -6967,6 +7343,14 @@ function collectEditableNodes() {
6967
7343
  if (!key) return;
6968
7344
  nodes.push({ key, type: "link", text: getLinkHref2(el) });
6969
7345
  });
7346
+ if (extraContent) {
7347
+ for (const key of [NAV_ORDER_KEY, FOOTER_ORDER_KEY, NAV_COUNT_KEY]) {
7348
+ const text = extraContent[key];
7349
+ if (typeof text === "string" && text.length > 0) {
7350
+ nodes.push({ key, type: "meta", text });
7351
+ }
7352
+ }
7353
+ }
6970
7354
  document.querySelectorAll('[data-ohw-editable="video"]').forEach((el) => {
6971
7355
  const key = el.dataset.ohwKey ?? "";
6972
7356
  const video = getVideoEl(el);
@@ -7065,6 +7449,29 @@ function getHrefKeyFromElement(el) {
7065
7449
  if (!key) return null;
7066
7450
  return { anchor, key };
7067
7451
  }
7452
+ function disableNativeHrefDrag(el) {
7453
+ if (el.draggable) el.draggable = false;
7454
+ if (el.getAttribute("draggable") !== "false") {
7455
+ el.setAttribute("draggable", "false");
7456
+ }
7457
+ }
7458
+ function clearTextSelection() {
7459
+ const sel = window.getSelection();
7460
+ if (sel && !sel.isCollapsed) sel.removeAllRanges();
7461
+ }
7462
+ function armFooterPressDrag() {
7463
+ document.documentElement.setAttribute("data-ohw-footer-press-drag", "");
7464
+ }
7465
+ function lockFooterDuringDrag() {
7466
+ document.documentElement.removeAttribute("data-ohw-footer-press-drag");
7467
+ document.documentElement.setAttribute("data-ohw-item-dragging", "");
7468
+ clearTextSelection();
7469
+ }
7470
+ function unlockFooterDragInteraction() {
7471
+ document.documentElement.removeAttribute("data-ohw-footer-press-drag");
7472
+ document.documentElement.removeAttribute("data-ohw-item-dragging");
7473
+ clearTextSelection();
7474
+ }
7068
7475
  function isNavbarButton2(el) {
7069
7476
  return Boolean(el.closest('[data-ohw-role="navbar-button"]'));
7070
7477
  }
@@ -7378,7 +7785,7 @@ function EditGlowChrome({
7378
7785
  zIndex: 2147483646
7379
7786
  },
7380
7787
  children: [
7381
- /* @__PURE__ */ jsx24(
7788
+ /* @__PURE__ */ jsx25(
7382
7789
  "div",
7383
7790
  {
7384
7791
  style: {
@@ -7391,7 +7798,7 @@ function EditGlowChrome({
7391
7798
  }
7392
7799
  }
7393
7800
  ),
7394
- reorderHrefKey && /* @__PURE__ */ jsx24(
7801
+ reorderHrefKey && /* @__PURE__ */ jsx25(
7395
7802
  "div",
7396
7803
  {
7397
7804
  "data-ohw-drag-handle-container": "",
@@ -7403,7 +7810,7 @@ function EditGlowChrome({
7403
7810
  transform: "translate(calc(-100% - 7px), -50%)",
7404
7811
  pointerEvents: dragDisabled ? "none" : "auto"
7405
7812
  },
7406
- children: /* @__PURE__ */ jsx24(
7813
+ children: /* @__PURE__ */ jsx25(
7407
7814
  DragHandle,
7408
7815
  {
7409
7816
  "aria-label": `Reorder ${reorderHrefKey}`,
@@ -7507,7 +7914,7 @@ function FloatingToolbar({
7507
7914
  onEditLink
7508
7915
  }) {
7509
7916
  const { top, left, transform } = calcToolbarPos(rect, parentScroll, 330);
7510
- return /* @__PURE__ */ jsx24(
7917
+ return /* @__PURE__ */ jsx25(
7511
7918
  "div",
7512
7919
  {
7513
7920
  ref: elRef,
@@ -7529,11 +7936,11 @@ function FloatingToolbar({
7529
7936
  pointerEvents: "auto"
7530
7937
  },
7531
7938
  children: /* @__PURE__ */ jsxs14(CustomToolbar, { children: [
7532
- TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ jsxs14(React9.Fragment, { children: [
7533
- gi > 0 && /* @__PURE__ */ jsx24(CustomToolbarDivider, {}),
7939
+ TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ jsxs14(React10.Fragment, { children: [
7940
+ gi > 0 && /* @__PURE__ */ jsx25(CustomToolbarDivider, {}),
7534
7941
  btns.map((btn) => {
7535
7942
  const isActive = activeCommands.has(btn.cmd);
7536
- return /* @__PURE__ */ jsx24(
7943
+ return /* @__PURE__ */ jsx25(
7537
7944
  CustomToolbarButton,
7538
7945
  {
7539
7946
  title: btn.title,
@@ -7542,7 +7949,7 @@ function FloatingToolbar({
7542
7949
  e.preventDefault();
7543
7950
  onCommand(btn.cmd);
7544
7951
  },
7545
- children: /* @__PURE__ */ jsx24(
7952
+ children: /* @__PURE__ */ jsx25(
7546
7953
  "svg",
7547
7954
  {
7548
7955
  width: "16",
@@ -7563,7 +7970,7 @@ function FloatingToolbar({
7563
7970
  );
7564
7971
  })
7565
7972
  ] }, gi)),
7566
- showEditLink ? /* @__PURE__ */ jsx24(
7973
+ showEditLink ? /* @__PURE__ */ jsx25(
7567
7974
  CustomToolbarButton,
7568
7975
  {
7569
7976
  type: "button",
@@ -7577,7 +7984,7 @@ function FloatingToolbar({
7577
7984
  e.preventDefault();
7578
7985
  e.stopPropagation();
7579
7986
  },
7580
- children: /* @__PURE__ */ jsx24(Link2, { className: "size-4 shrink-0", "aria-hidden": true })
7987
+ children: /* @__PURE__ */ jsx25(Link2, { className: "size-4 shrink-0", "aria-hidden": true })
7581
7988
  }
7582
7989
  ) : null
7583
7990
  ] })
@@ -7594,7 +8001,7 @@ function StateToggle({
7594
8001
  states,
7595
8002
  onStateChange
7596
8003
  }) {
7597
- return /* @__PURE__ */ jsx24(
8004
+ return /* @__PURE__ */ jsx25(
7598
8005
  ToggleGroup,
7599
8006
  {
7600
8007
  "data-ohw-state-toggle": "",
@@ -7608,7 +8015,7 @@ function StateToggle({
7608
8015
  left: rect.right - 8,
7609
8016
  transform: "translateX(-100%)"
7610
8017
  },
7611
- children: states.map((state) => /* @__PURE__ */ jsx24(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
8018
+ children: states.map((state) => /* @__PURE__ */ jsx25(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
7612
8019
  }
7613
8020
  );
7614
8021
  }
@@ -7732,7 +8139,15 @@ function OhhwellsBridge() {
7732
8139
  const [hoveredItemRect, setHoveredItemRect] = useState7(null);
7733
8140
  const siblingHintElRef = useRef5(null);
7734
8141
  const [siblingHintRect, setSiblingHintRect] = useState7(null);
8142
+ const [siblingHintRects, setSiblingHintRects] = useState7([]);
7735
8143
  const [isItemDragging, setIsItemDragging] = useState7(false);
8144
+ const [isFooterFrameSelection, setIsFooterFrameSelection] = useState7(false);
8145
+ const footerDragRef = useRef5(null);
8146
+ const [footerDropSlots, setFooterDropSlots] = useState7([]);
8147
+ const [activeFooterDropIndex, setActiveFooterDropIndex] = useState7(null);
8148
+ const [draggedItemRect, setDraggedItemRect] = useState7(null);
8149
+ const footerPointerDragRef = useRef5(null);
8150
+ const suppressNextClickRef = useRef5(false);
7736
8151
  const [linkPopover, setLinkPopover] = useState7(null);
7737
8152
  const linkPopoverSessionRef = useRef5(null);
7738
8153
  const addNavAfterAnchorRef = useRef5(null);
@@ -7896,8 +8311,10 @@ function OhhwellsBridge() {
7896
8311
  selectedElRef.current = null;
7897
8312
  setReorderHrefKey(null);
7898
8313
  setReorderDragDisabled(false);
8314
+ setIsFooterFrameSelection(false);
7899
8315
  siblingHintElRef.current = null;
7900
8316
  setSiblingHintRect(null);
8317
+ setSiblingHintRects([]);
7901
8318
  setIsItemDragging(false);
7902
8319
  hoveredNavContainerRef.current = null;
7903
8320
  setHoveredNavContainerRect(null);
@@ -7954,90 +8371,345 @@ function OhhwellsBridge() {
7954
8371
  intent: "add-nav"
7955
8372
  });
7956
8373
  }, []);
7957
- const handleItemDragStart = useCallback4(() => {
7958
- siblingHintElRef.current = null;
7959
- setSiblingHintRect(null);
7960
- setIsItemDragging(true);
7961
- }, []);
7962
- const handleItemDragEnd = useCallback4(() => {
8374
+ const clearFooterDragVisuals = useCallback4(() => {
8375
+ footerDragRef.current = null;
8376
+ setSiblingHintRects([]);
8377
+ setFooterDropSlots([]);
8378
+ setActiveFooterDropIndex(null);
8379
+ setDraggedItemRect(null);
7963
8380
  setIsItemDragging(false);
8381
+ unlockFooterDragInteraction();
7964
8382
  }, []);
7965
- reselectNavigationItemRef.current = reselectNavigationItem;
7966
- commitNavigationTextEditRef.current = commitNavigationTextEdit;
7967
- const select = useCallback4((anchor) => {
7968
- if (!isNavigationItem(anchor)) return;
7969
- if (activeElRef.current) deactivate();
7970
- selectedElRef.current = anchor;
7971
- clearHrefKeyHover(anchor);
7972
- hoveredNavContainerRef.current = null;
7973
- setHoveredNavContainerRect(null);
7974
- setHoveredItemRect(null);
7975
- hoveredItemElRef.current = null;
7976
- siblingHintElRef.current = null;
7977
- setSiblingHintRect(null);
7978
- setIsItemDragging(false);
7979
- const { key, disabled } = getNavigationItemReorderState(anchor);
7980
- setReorderHrefKey(key);
7981
- setReorderDragDisabled(disabled);
7982
- setToolbarVariant("link-action");
7983
- setToolbarRect(anchor.getBoundingClientRect());
7984
- setToolbarShowEditLink(false);
7985
- setActiveCommands(/* @__PURE__ */ new Set());
7986
- }, [deactivate]);
7987
- const selectFrame = useCallback4((el) => {
7988
- if (!isNavigationContainer(el)) return;
7989
- if (activeElRef.current) deactivate();
7990
- selectedElRef.current = el;
7991
- clearHrefKeyHover(el);
7992
- hoveredNavContainerRef.current = null;
7993
- setHoveredNavContainerRect(null);
7994
- setHoveredItemRect(null);
7995
- hoveredItemElRef.current = null;
7996
- siblingHintElRef.current = null;
7997
- setSiblingHintRect(null);
7998
- setIsItemDragging(false);
7999
- setReorderHrefKey(null);
8000
- setReorderDragDisabled(false);
8001
- setToolbarVariant("select-frame");
8002
- setToolbarRect(el.getBoundingClientRect());
8003
- setToolbarShowEditLink(false);
8004
- setActiveCommands(/* @__PURE__ */ new Set());
8005
- }, [deactivate]);
8006
- const activate = useCallback4((el, options) => {
8007
- if (activeElRef.current === el) return;
8008
- selectedElRef.current = null;
8009
- deactivate();
8010
- if (hoveredImageRef.current) {
8011
- hoveredImageRef.current = null;
8012
- setMediaHover(null);
8383
+ const refreshFooterDragVisuals = useCallback4((session, activeSlot, clientX, clientY) => {
8384
+ const dragged = session.draggedEl;
8385
+ setDraggedItemRect(dragged.getBoundingClientRect());
8386
+ if (typeof clientX === "number" && typeof clientY === "number") {
8387
+ session.lastClientX = clientX;
8388
+ session.lastClientY = clientY;
8013
8389
  }
8014
- setToolbarVariant("rich-text");
8015
- siblingHintElRef.current = null;
8016
- setSiblingHintRect(null);
8017
- setIsItemDragging(false);
8018
- el.setAttribute("contenteditable", "true");
8019
- el.removeAttribute("data-ohw-hovered");
8020
- el.closest("[data-ohw-href-key]")?.removeAttribute("data-ohw-hovered");
8021
- activeElRef.current = el;
8022
- originalContentRef.current = el.innerHTML;
8023
- el.focus();
8024
- if (options?.caretX !== void 0 && options?.caretY !== void 0) {
8025
- placeCaretAtPoint(el, options.caretX, options.caretY);
8390
+ session.activeSlot = activeSlot;
8391
+ if (session.kind === "link") {
8392
+ const columns2 = listFooterColumns();
8393
+ const focusCols = /* @__PURE__ */ new Set([session.sourceColumnIndex]);
8394
+ if (activeSlot) focusCols.add(activeSlot.columnIndex);
8395
+ const siblingRects = [];
8396
+ columns2.forEach((col, i) => {
8397
+ if (!focusCols.has(i)) return;
8398
+ for (const link of listFooterLinksInColumn(col)) {
8399
+ if (link === dragged) continue;
8400
+ siblingRects.push(link.getBoundingClientRect());
8401
+ }
8402
+ });
8403
+ setSiblingHintRects(siblingRects);
8404
+ const slots2 = [];
8405
+ columns2.forEach((col, i) => {
8406
+ slots2.push(...buildLinkDropSlots(col, i));
8407
+ });
8408
+ setFooterDropSlots(slots2);
8409
+ const activeIdx2 = activeSlot ? slots2.findIndex((s) => s.columnIndex === activeSlot.columnIndex && s.insertIndex === activeSlot.insertIndex) : -1;
8410
+ setActiveFooterDropIndex(activeIdx2 >= 0 ? activeIdx2 : null);
8411
+ return;
8412
+ }
8413
+ const columns = listFooterColumns();
8414
+ setSiblingHintRects(columns.filter((col) => col !== dragged).map((col) => col.getBoundingClientRect()));
8415
+ const slots = buildColumnDropSlots();
8416
+ setFooterDropSlots(slots);
8417
+ const activeIdx = activeSlot ? slots.findIndex((s) => s.insertIndex === activeSlot.insertIndex) : -1;
8418
+ setActiveFooterDropIndex(activeIdx >= 0 ? activeIdx : null);
8419
+ }, []);
8420
+ const refreshFooterDragVisualsRef = useRef5(refreshFooterDragVisuals);
8421
+ refreshFooterDragVisualsRef.current = refreshFooterDragVisuals;
8422
+ const commitFooterDragRef = useRef5(() => {
8423
+ });
8424
+ const beginFooterDragRef = useRef5(() => {
8425
+ });
8426
+ const beginFooterDrag = useCallback4(
8427
+ (session) => {
8428
+ const rect = session.draggedEl.getBoundingClientRect();
8429
+ session.lastClientX = session.lastClientX || rect.left + rect.width / 2;
8430
+ session.lastClientY = session.lastClientY || rect.top + rect.height / 2;
8431
+ session.activeSlot = session.activeSlot ?? null;
8432
+ footerDragRef.current = session;
8433
+ setIsItemDragging(true);
8434
+ lockFooterDuringDrag();
8435
+ siblingHintElRef.current = null;
8436
+ setSiblingHintRect(null);
8437
+ if (session.wasSelected && selectedElRef.current === session.draggedEl) {
8438
+ setToolbarRect(rect);
8439
+ }
8440
+ const initialSlot = session.kind === "link" && session.hrefKey ? hitTestLinkDropSlot(session.lastClientX, session.lastClientY, session.hrefKey) : hitTestColumnDropSlot(session.lastClientX, session.lastClientY);
8441
+ refreshFooterDragVisuals(session, initialSlot, session.lastClientX, session.lastClientY);
8442
+ },
8443
+ [refreshFooterDragVisuals]
8444
+ );
8445
+ beginFooterDragRef.current = beginFooterDrag;
8446
+ const commitFooterDrag = useCallback4(
8447
+ (clientX, clientY) => {
8448
+ const session = footerDragRef.current;
8449
+ if (!session) {
8450
+ clearFooterDragVisuals();
8451
+ return;
8452
+ }
8453
+ const x = typeof clientX === "number" && (clientX !== 0 || clientY !== 0) ? clientX : session.lastClientX;
8454
+ const y = typeof clientY === "number" && (clientX !== 0 || clientY !== 0) ? clientY : session.lastClientY;
8455
+ let nextOrder = null;
8456
+ const slot = session.activeSlot ?? (session.kind === "link" && session.hrefKey ? hitTestLinkDropSlot(x, y, session.hrefKey) : session.kind === "column" ? hitTestColumnDropSlot(x, y) : null);
8457
+ if (session.kind === "link" && session.hrefKey && slot) {
8458
+ nextOrder = planFooterLinkMove(session.hrefKey, slot.columnIndex, slot.insertIndex);
8459
+ } else if (session.kind === "column" && slot) {
8460
+ nextOrder = planFooterColumnMove(session.sourceColumnIndex, slot.insertIndex);
8461
+ }
8462
+ const wasSelected = session.wasSelected;
8463
+ const draggedEl = session.draggedEl;
8464
+ const hrefKey = session.hrefKey;
8465
+ let movedColumnIndex = null;
8466
+ if (session.kind === "column" && slot && nextOrder) {
8467
+ let adjusted = slot.insertIndex;
8468
+ if (session.sourceColumnIndex < slot.insertIndex) adjusted -= 1;
8469
+ movedColumnIndex = Math.max(0, Math.min(adjusted, nextOrder.length - 1));
8470
+ }
8471
+ clearFooterDragVisuals();
8472
+ if (nextOrder) {
8473
+ const orderJson = JSON.stringify(nextOrder);
8474
+ editContentRef.current = {
8475
+ ...editContentRef.current,
8476
+ [FOOTER_ORDER_KEY]: orderJson
8477
+ };
8478
+ applyFooterOrder(nextOrder);
8479
+ document.querySelectorAll("footer [data-ohw-href-key]").forEach(disableNativeHrefDrag);
8480
+ requestAnimationFrame(() => {
8481
+ if (editContentRef.current[FOOTER_ORDER_KEY] === orderJson) {
8482
+ applyFooterOrder(nextOrder);
8483
+ document.querySelectorAll("footer [data-ohw-href-key]").forEach(disableNativeHrefDrag);
8484
+ }
8485
+ });
8486
+ postToParentRef.current({
8487
+ type: "ow:change",
8488
+ nodes: [{ key: FOOTER_ORDER_KEY, text: orderJson }]
8489
+ });
8490
+ }
8491
+ 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);
8492
+ if (still && isNavigationItem(still)) {
8493
+ selectRef.current(still);
8494
+ } else if (still && isNavigationContainer(still)) {
8495
+ selectFrameRef.current(still);
8496
+ } else if (wasSelected) {
8497
+ deselectRef.current();
8498
+ }
8499
+ },
8500
+ [clearFooterDragVisuals]
8501
+ );
8502
+ commitFooterDragRef.current = commitFooterDrag;
8503
+ const startFooterLinkDrag = useCallback4(
8504
+ (anchor, clientX, clientY, wasSelected) => {
8505
+ const hrefKey = anchor.getAttribute("data-ohw-href-key");
8506
+ if (!hrefKey || !isFooterHrefKey(hrefKey)) return false;
8507
+ const column = findFooterColumnForLink(anchor);
8508
+ const columns = listFooterColumns();
8509
+ beginFooterDrag({
8510
+ kind: "link",
8511
+ hrefKey,
8512
+ columnEl: column,
8513
+ sourceColumnIndex: column ? columns.indexOf(column) : 0,
8514
+ wasSelected,
8515
+ draggedEl: anchor,
8516
+ lastClientX: clientX,
8517
+ lastClientY: clientY,
8518
+ activeSlot: null
8519
+ });
8520
+ return true;
8521
+ },
8522
+ [beginFooterDrag]
8523
+ );
8524
+ const startFooterColumnDrag = useCallback4(
8525
+ (columnEl, clientX, clientY, wasSelected) => {
8526
+ const columns = listFooterColumns();
8527
+ const idx = columns.indexOf(columnEl);
8528
+ if (idx < 0) return false;
8529
+ beginFooterDrag({
8530
+ kind: "column",
8531
+ hrefKey: null,
8532
+ columnEl,
8533
+ sourceColumnIndex: idx,
8534
+ wasSelected,
8535
+ draggedEl: columnEl,
8536
+ lastClientX: clientX,
8537
+ lastClientY: clientY,
8538
+ activeSlot: null
8539
+ });
8540
+ return true;
8541
+ },
8542
+ [beginFooterDrag]
8543
+ );
8544
+ const handleItemDragStart = useCallback4(
8545
+ (e) => {
8546
+ const selected = selectedElRef.current;
8547
+ if (!selected) {
8548
+ setIsItemDragging(true);
8549
+ return;
8550
+ }
8551
+ const clientX = e?.clientX ?? selected.getBoundingClientRect().left + 8;
8552
+ const clientY = e?.clientY ?? selected.getBoundingClientRect().top + 8;
8553
+ if (startFooterLinkDrag(selected, clientX, clientY, true)) return;
8554
+ if (selected.hasAttribute("data-ohw-footer-col") || isInferredFooterGroup(selected) && selected.closest("footer")) {
8555
+ if (startFooterColumnDrag(selected, clientX, clientY, true)) return;
8556
+ }
8557
+ siblingHintElRef.current = null;
8558
+ setSiblingHintRect(null);
8559
+ setIsItemDragging(true);
8560
+ },
8561
+ [startFooterColumnDrag, startFooterLinkDrag]
8562
+ );
8563
+ const handleItemDragEnd = useCallback4(
8564
+ (e) => {
8565
+ if (footerDragRef.current) {
8566
+ const x = e?.clientX;
8567
+ const y = e?.clientY;
8568
+ if (typeof x === "number" && typeof y === "number" && (x !== 0 || y !== 0)) {
8569
+ commitFooterDrag(x, y);
8570
+ } else {
8571
+ commitFooterDrag();
8572
+ }
8573
+ return;
8574
+ }
8575
+ setIsItemDragging(false);
8576
+ },
8577
+ [commitFooterDrag]
8578
+ );
8579
+ const handleItemChromePointerDown = useCallback4((e) => {
8580
+ if (e.button !== 0) return;
8581
+ const selected = selectedElRef.current;
8582
+ if (!selected) return;
8583
+ armFooterPressDrag();
8584
+ const hrefKey = selected.getAttribute("data-ohw-href-key");
8585
+ if (hrefKey && isFooterHrefKey(hrefKey)) {
8586
+ footerPointerDragRef.current = {
8587
+ el: selected,
8588
+ kind: "link",
8589
+ startX: e.clientX,
8590
+ startY: e.clientY,
8591
+ pointerId: e.pointerId,
8592
+ wasSelected: true,
8593
+ started: false
8594
+ };
8595
+ return;
8596
+ }
8597
+ if (selected.hasAttribute("data-ohw-footer-col") || isInferredFooterGroup(selected) && selected.closest("footer")) {
8598
+ footerPointerDragRef.current = {
8599
+ el: selected,
8600
+ kind: "column",
8601
+ startX: e.clientX,
8602
+ startY: e.clientY,
8603
+ pointerId: e.pointerId,
8604
+ wasSelected: true,
8605
+ started: false
8606
+ };
8607
+ }
8608
+ }, []);
8609
+ const handleItemChromeClick = useCallback4((clientX, clientY) => {
8610
+ if (suppressNextClickRef.current) {
8611
+ suppressNextClickRef.current = false;
8612
+ return;
8026
8613
  }
8027
- setToolbarShowEditLink(Boolean(getHrefKeyFromElement(el)));
8028
- const navAnchor = getNavigationItemAnchor(el);
8029
- if (navAnchor) {
8614
+ const selected = selectedElRef.current;
8615
+ if (!selected || !isNavigationItem(selected)) return;
8616
+ const editable = selected.querySelector('[data-ohw-editable="text"]') ?? selected.querySelector("[data-ohw-editable]");
8617
+ if (!editable) return;
8618
+ activateRef.current(editable, { caretX: clientX, caretY: clientY });
8619
+ }, []);
8620
+ reselectNavigationItemRef.current = reselectNavigationItem;
8621
+ commitNavigationTextEditRef.current = commitNavigationTextEdit;
8622
+ const select = useCallback4(
8623
+ (anchor) => {
8624
+ if (!isNavigationItem(anchor)) return;
8625
+ if (activeElRef.current) deactivate();
8626
+ selectedElRef.current = anchor;
8627
+ clearHrefKeyHover(anchor);
8628
+ hoveredNavContainerRef.current = null;
8629
+ setHoveredNavContainerRect(null);
8630
+ setHoveredItemRect(null);
8631
+ hoveredItemElRef.current = null;
8632
+ siblingHintElRef.current = null;
8633
+ setSiblingHintRect(null);
8634
+ setSiblingHintRects([]);
8635
+ setIsItemDragging(false);
8636
+ const { key, disabled } = getNavigationItemReorderState(anchor);
8637
+ setReorderHrefKey(key);
8638
+ setReorderDragDisabled(disabled);
8639
+ setIsFooterFrameSelection(false);
8640
+ setToolbarVariant("link-action");
8641
+ setToolbarRect(anchor.getBoundingClientRect());
8642
+ setToolbarShowEditLink(false);
8643
+ setActiveCommands(/* @__PURE__ */ new Set());
8644
+ },
8645
+ [deactivate]
8646
+ );
8647
+ const selectFrame = useCallback4(
8648
+ (el) => {
8649
+ if (!isNavigationContainer(el)) return;
8650
+ if (activeElRef.current) deactivate();
8651
+ const isFooterColumn = el.hasAttribute("data-ohw-footer-col") || Boolean(el.closest("footer") && isInferredFooterGroup(el));
8652
+ selectedElRef.current = el;
8653
+ clearHrefKeyHover(el);
8654
+ hoveredNavContainerRef.current = null;
8655
+ setHoveredNavContainerRect(null);
8656
+ setHoveredItemRect(null);
8657
+ hoveredItemElRef.current = null;
8658
+ siblingHintElRef.current = null;
8659
+ setSiblingHintRect(null);
8660
+ setSiblingHintRects(
8661
+ isFooterColumn ? listFooterColumns().filter((column) => column !== el).map((column) => column.getBoundingClientRect()) : []
8662
+ );
8663
+ setIsItemDragging(false);
8030
8664
  setReorderHrefKey(null);
8031
8665
  setReorderDragDisabled(false);
8032
- } else {
8033
- const { key: reorderKey, disabled: reorderDisabled } = getReorderHandleState(el);
8034
- setReorderHrefKey(reorderKey);
8035
- setReorderDragDisabled(reorderDisabled);
8036
- }
8037
- setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
8038
- postToParent2({ type: "ow:enter-edit", key: el.dataset.ohwKey });
8039
- requestAnimationFrame(() => refreshActiveCommandsRef.current());
8040
- }, [deactivate, postToParent2]);
8666
+ setIsFooterFrameSelection(isFooterColumn);
8667
+ setToolbarVariant("select-frame");
8668
+ setToolbarRect(el.getBoundingClientRect());
8669
+ setToolbarShowEditLink(false);
8670
+ setActiveCommands(/* @__PURE__ */ new Set());
8671
+ },
8672
+ [deactivate]
8673
+ );
8674
+ const activate = useCallback4(
8675
+ (el, options) => {
8676
+ if (activeElRef.current === el) return;
8677
+ selectedElRef.current = null;
8678
+ deactivate();
8679
+ if (hoveredImageRef.current) {
8680
+ hoveredImageRef.current = null;
8681
+ postToParentRef.current({ type: "ow:image-unhover" });
8682
+ }
8683
+ setToolbarVariant("rich-text");
8684
+ siblingHintElRef.current = null;
8685
+ setSiblingHintRect(null);
8686
+ setSiblingHintRects([]);
8687
+ setIsItemDragging(false);
8688
+ el.setAttribute("contenteditable", "true");
8689
+ el.removeAttribute("data-ohw-hovered");
8690
+ el.closest("[data-ohw-href-key]")?.removeAttribute("data-ohw-hovered");
8691
+ activeElRef.current = el;
8692
+ originalContentRef.current = el.innerHTML;
8693
+ el.focus();
8694
+ if (options?.caretX !== void 0 && options?.caretY !== void 0) {
8695
+ placeCaretAtPoint(el, options.caretX, options.caretY);
8696
+ }
8697
+ setToolbarShowEditLink(Boolean(getHrefKeyFromElement(el)));
8698
+ const navAnchor = getNavigationItemAnchor(el);
8699
+ if (navAnchor) {
8700
+ setReorderHrefKey(null);
8701
+ setReorderDragDisabled(false);
8702
+ } else {
8703
+ const { key: reorderKey, disabled: reorderDisabled } = getReorderHandleState(el);
8704
+ setReorderHrefKey(reorderKey);
8705
+ setReorderDragDisabled(reorderDisabled);
8706
+ }
8707
+ setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
8708
+ postToParent2({ type: "ow:enter-edit", key: el.dataset.ohwKey });
8709
+ requestAnimationFrame(() => refreshActiveCommandsRef.current());
8710
+ },
8711
+ [deactivate, postToParent2]
8712
+ );
8041
8713
  activateRef.current = activate;
8042
8714
  deactivateRef.current = deactivate;
8043
8715
  selectRef.current = select;
@@ -8085,6 +8757,7 @@ function OhhwellsBridge() {
8085
8757
  applyLinkByKey(key, val);
8086
8758
  }
8087
8759
  reconcileNavbarItemsFromContent(content);
8760
+ reconcileFooterOrderFromContent(content);
8088
8761
  enforceLinkHrefs();
8089
8762
  initSectionsFromContent(content, true);
8090
8763
  sectionsLoadedRef.current = true;
@@ -8146,6 +8819,7 @@ function OhhwellsBridge() {
8146
8819
  applyLinkByKey(key, val);
8147
8820
  }
8148
8821
  reconcileNavbarItemsFromContent(content);
8822
+ reconcileFooterOrderFromContent(content);
8149
8823
  } finally {
8150
8824
  observer?.observe(document.body, { childList: true, subtree: true });
8151
8825
  }
@@ -8186,15 +8860,42 @@ function OhhwellsBridge() {
8186
8860
  if (!subdomain) return {};
8187
8861
  return contentCache.get(subdomain) ?? {};
8188
8862
  };
8189
- const run = () => reconcileNavbarItemsFromContent(contentForNav());
8863
+ let applying = false;
8864
+ let rafId = null;
8865
+ const run = () => {
8866
+ if (footerDragRef.current || applying) return;
8867
+ applying = true;
8868
+ try {
8869
+ const content = contentForNav();
8870
+ reconcileNavbarItemsFromContent(content);
8871
+ reconcileFooterOrderFromContent(content);
8872
+ document.querySelectorAll("footer [data-ohw-href-key]").forEach((el) => {
8873
+ if (isFooterHrefKey(el.getAttribute("data-ohw-href-key"))) {
8874
+ disableNativeHrefDrag(el);
8875
+ }
8876
+ });
8877
+ } finally {
8878
+ applying = false;
8879
+ }
8880
+ };
8881
+ const scheduleRun = () => {
8882
+ if (rafId !== null) cancelAnimationFrame(rafId);
8883
+ rafId = requestAnimationFrame(() => {
8884
+ rafId = null;
8885
+ run();
8886
+ });
8887
+ };
8190
8888
  run();
8191
8889
  const nav = document.querySelector("nav");
8192
- if (!nav) return;
8193
- const observer = new MutationObserver(() => {
8194
- requestAnimationFrame(run);
8195
- });
8196
- observer.observe(nav, { childList: true, subtree: true });
8197
- return () => observer.disconnect();
8890
+ const footer = document.querySelector("footer");
8891
+ const observer = new MutationObserver(scheduleRun);
8892
+ if (nav) observer.observe(nav, { childList: true, subtree: true });
8893
+ if (footer) observer.observe(footer, { childList: true, subtree: true });
8894
+ if (!nav && !footer) return;
8895
+ return () => {
8896
+ observer.disconnect();
8897
+ if (rafId !== null) cancelAnimationFrame(rafId);
8898
+ };
8198
8899
  }, [isEditMode, pathname, subdomain, fetchState]);
8199
8900
  useEffect8(() => {
8200
8901
  if (!isEditMode) return;
@@ -8259,6 +8960,29 @@ function OhhwellsBridge() {
8259
8960
  [data-ohw-editable] {
8260
8961
  display: block;
8261
8962
  }
8963
+ /* Native <a> drag steals clicks \u2014 disable for edit links; reorder via press-to-drag / handle. */
8964
+ footer [data-ohw-href-key] {
8965
+ -webkit-user-drag: none !important;
8966
+ }
8967
+ footer [data-ohw-footer-col]:hover {
8968
+ outline: 1.5px dashed ${PRIMARY2} !important;
8969
+ outline-offset: 6px;
8970
+ border-radius: 8px;
8971
+ }
8972
+ html[data-ohw-footer-press-drag] footer,
8973
+ html[data-ohw-footer-press-drag] footer * {
8974
+ user-select: none !important;
8975
+ -webkit-user-select: none !important;
8976
+ }
8977
+ html[data-ohw-item-dragging] footer,
8978
+ html[data-ohw-item-dragging] footer * {
8979
+ user-select: none !important;
8980
+ -webkit-user-select: none !important;
8981
+ pointer-events: none !important;
8982
+ }
8983
+ html[data-ohw-item-dragging] {
8984
+ cursor: grabbing !important;
8985
+ }
8262
8986
  [data-ohw-editable]:not([contenteditable]):not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"]):not([data-ohw-editable="video"]) { cursor: text !important; }
8263
8987
  [data-ohw-editable="image"], [data-ohw-editable="image"] *,
8264
8988
  [data-ohw-editable="video"], [data-ohw-editable="video"] *,
@@ -8304,12 +9028,39 @@ function OhhwellsBridge() {
8304
9028
  }
8305
9029
  refreshStateRules();
8306
9030
  const handleClick = (e) => {
9031
+ if (suppressNextClickRef.current) {
9032
+ suppressNextClickRef.current = false;
9033
+ e.preventDefault();
9034
+ e.stopPropagation();
9035
+ return;
9036
+ }
8307
9037
  const target = e.target;
8308
9038
  if (target.closest("[data-ohw-toolbar]")) return;
8309
9039
  if (target.closest("[data-ohw-state-toggle]")) return;
8310
9040
  if (target.closest("[data-ohw-max-badge]")) return;
8311
9041
  if (isInsideLinkEditor(target)) return;
8312
9042
  if (target.closest('[data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
9043
+ if (target.closest("[data-ohw-item-drag-surface]")) {
9044
+ e.preventDefault();
9045
+ e.stopPropagation();
9046
+ const selected = selectedElRef.current;
9047
+ if (selected && isNavigationItem(selected)) {
9048
+ const editable2 = selected.querySelector('[data-ohw-editable="text"]') ?? selected.querySelector("[data-ohw-editable]");
9049
+ if (editable2) {
9050
+ activateRef.current(editable2, {
9051
+ caretX: e.clientX,
9052
+ caretY: e.clientY
9053
+ });
9054
+ }
9055
+ } else if (selected?.hasAttribute("data-ohw-footer-col")) {
9056
+ const link = listFooterLinksInColumn(selected).find((el) => {
9057
+ const r2 = el.getBoundingClientRect();
9058
+ return e.clientX >= r2.left && e.clientX <= r2.right && e.clientY >= r2.top && e.clientY <= r2.bottom;
9059
+ });
9060
+ if (link) selectRef.current(link);
9061
+ }
9062
+ return;
9063
+ }
8313
9064
  const navFromChrome = resolveNavContainerSelectionTarget(target, e.clientX, e.clientY);
8314
9065
  if (navFromChrome) {
8315
9066
  e.preventDefault();
@@ -8364,10 +9115,26 @@ function OhhwellsBridge() {
8364
9115
  if (hrefAnchor) {
8365
9116
  e.preventDefault();
8366
9117
  e.stopPropagation();
8367
- if (selectedElRef.current === hrefAnchor) return;
9118
+ if (selectedElRef.current === hrefAnchor) {
9119
+ const textEditable = hrefAnchor.querySelector('[data-ohw-editable="text"]') ?? hrefAnchor.querySelector("[data-ohw-editable]");
9120
+ if (textEditable) {
9121
+ activateRef.current(textEditable, {
9122
+ caretX: e.clientX,
9123
+ caretY: e.clientY
9124
+ });
9125
+ }
9126
+ return;
9127
+ }
8368
9128
  selectRef.current(hrefAnchor);
8369
9129
  return;
8370
9130
  }
9131
+ const footerColClick = target.closest("[data-ohw-footer-col]");
9132
+ if (footerColClick) {
9133
+ e.preventDefault();
9134
+ e.stopPropagation();
9135
+ selectFrameRef.current(footerColClick);
9136
+ return;
9137
+ }
8371
9138
  const navContainerToSelect = resolveNavContainerSelectionTarget(target, e.clientX, e.clientY);
8372
9139
  if (navContainerToSelect) {
8373
9140
  e.preventDefault();
@@ -8448,6 +9215,15 @@ function OhhwellsBridge() {
8448
9215
  setHoveredNavContainerRect(null);
8449
9216
  }
8450
9217
  }
9218
+ const footerCol = target.closest("[data-ohw-footer-col]");
9219
+ if (footerCol) {
9220
+ hoveredNavContainerRef.current = null;
9221
+ setHoveredNavContainerRect(null);
9222
+ if (selectedElRef.current === footerCol) return;
9223
+ hoveredItemElRef.current = footerCol;
9224
+ setHoveredItemRect(footerCol.getBoundingClientRect());
9225
+ return;
9226
+ }
8451
9227
  const navAnchor = getNavigationItemAnchor(target);
8452
9228
  if (navAnchor) {
8453
9229
  hoveredNavContainerRef.current = null;
@@ -8484,6 +9260,15 @@ function OhhwellsBridge() {
8484
9260
  hoveredNavContainerRef.current = null;
8485
9261
  setHoveredNavContainerRect(null);
8486
9262
  }
9263
+ const footerCol = target.closest("[data-ohw-footer-col]");
9264
+ if (footerCol && hoveredItemElRef.current === footerCol) {
9265
+ const related2 = e.relatedTarget instanceof Element ? e.relatedTarget : null;
9266
+ if (related2?.closest("[data-ohw-drag-handle-container], [data-ohw-item-interaction]")) return;
9267
+ if (related2?.closest("[data-ohw-footer-col]") === footerCol) return;
9268
+ hoveredItemElRef.current = null;
9269
+ setHoveredItemRect(null);
9270
+ return;
9271
+ }
8487
9272
  const navAnchor = getNavigationItemAnchor(target);
8488
9273
  if (navAnchor) {
8489
9274
  const related2 = e.relatedTarget instanceof Element ? e.relatedTarget : null;
@@ -8892,6 +9677,14 @@ function OhhwellsBridge() {
8892
9677
  probeHoverCardsAt(clientX, clientY);
8893
9678
  };
8894
9679
  const handleDragOver = (e) => {
9680
+ const footerSession = footerDragRef.current;
9681
+ if (footerSession) {
9682
+ e.preventDefault();
9683
+ if (e.dataTransfer) e.dataTransfer.dropEffect = "move";
9684
+ const slot = footerSession.kind === "link" && footerSession.hrefKey ? hitTestLinkDropSlot(e.clientX, e.clientY, footerSession.hrefKey) : hitTestColumnDropSlot(e.clientX, e.clientY);
9685
+ refreshFooterDragVisualsRef.current(footerSession, slot, e.clientX, e.clientY);
9686
+ return;
9687
+ }
8895
9688
  e.preventDefault();
8896
9689
  const el = findImageAtPoint(e.clientX, e.clientY, false);
8897
9690
  if (!el) {
@@ -9099,6 +9892,7 @@ function OhhwellsBridge() {
9099
9892
  }
9100
9893
  editContentRef.current = { ...editContentRef.current, ...content };
9101
9894
  reconcileNavbarItemsFromContent(editContentRef.current);
9895
+ reconcileFooterOrderFromContent(editContentRef.current);
9102
9896
  enforceLinkHrefs();
9103
9897
  postToParentRef.current({ type: "ow:hydrate-done" });
9104
9898
  };
@@ -9212,6 +10006,17 @@ function OhhwellsBridge() {
9212
10006
  if (siblingHintElRef.current) {
9213
10007
  setSiblingHintRect(siblingHintElRef.current.getBoundingClientRect());
9214
10008
  }
10009
+ const selected = selectedElRef.current;
10010
+ if (selected && !footerDragRef.current && (selected.hasAttribute("data-ohw-footer-col") || Boolean(selected.closest("footer") && isInferredFooterGroup(selected)))) {
10011
+ setSiblingHintRects(
10012
+ listFooterColumns().filter((column) => column !== selected).map((column) => column.getBoundingClientRect())
10013
+ );
10014
+ }
10015
+ if (footerDragRef.current) {
10016
+ const session = footerDragRef.current;
10017
+ const slot = session.kind === "link" && session.hrefKey ? hitTestLinkDropSlot(session.lastClientX, session.lastClientY, session.hrefKey) : hitTestColumnDropSlot(session.lastClientX, session.lastClientY);
10018
+ refreshFooterDragVisualsRef.current(session, slot, session.lastClientX, session.lastClientY);
10019
+ }
9215
10020
  if (hoveredImageRef.current) {
9216
10021
  const el = hoveredImageRef.current;
9217
10022
  const r2 = el.getBoundingClientRect();
@@ -9222,7 +10027,7 @@ function OhhwellsBridge() {
9222
10027
  };
9223
10028
  const handleSave = (e) => {
9224
10029
  if (e.data?.type !== "ow:save") return;
9225
- const nodes = collectEditableNodes();
10030
+ const nodes = collectEditableNodes(editContentRef.current);
9226
10031
  const tracker = document.querySelector("[data-ohw-sections-tracker]");
9227
10032
  if (tracker?.textContent) nodes.push({ key: "__ohw_sections", type: "sections", text: tracker.textContent });
9228
10033
  postToParentRef.current({ type: "ow:save-result", nodes });
@@ -9562,6 +10367,154 @@ function OhhwellsBridge() {
9562
10367
  if (imageShowTimerRef.current) clearTimeout(imageShowTimerRef.current);
9563
10368
  };
9564
10369
  }, [isEditMode, refreshStateRules]);
10370
+ useEffect8(() => {
10371
+ if (!isEditMode) return;
10372
+ const THRESHOLD = 10;
10373
+ const resolveWasSelected = (el) => {
10374
+ if (selectedElRef.current === el) return true;
10375
+ const activeAnchor = activeElRef.current ? getNavigationItemAnchor(activeElRef.current) : null;
10376
+ return activeAnchor === el;
10377
+ };
10378
+ const onPointerDown = (e) => {
10379
+ if (e.button !== 0) return;
10380
+ if (footerDragRef.current) return;
10381
+ const target = e.target;
10382
+ if (!target) return;
10383
+ 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]')) {
10384
+ return;
10385
+ }
10386
+ if (target.closest("[data-ohw-item-drag-surface]")) return;
10387
+ const anchor = getNavigationItemAnchor(target);
10388
+ const hrefKey = anchor?.getAttribute("data-ohw-href-key") ?? null;
10389
+ if (anchor && isFooterHrefKey(hrefKey)) {
10390
+ armFooterPressDrag();
10391
+ footerPointerDragRef.current = {
10392
+ el: anchor,
10393
+ kind: "link",
10394
+ startX: e.clientX,
10395
+ startY: e.clientY,
10396
+ pointerId: e.pointerId,
10397
+ wasSelected: resolveWasSelected(anchor),
10398
+ started: false
10399
+ };
10400
+ return;
10401
+ }
10402
+ const col = target.closest("[data-ohw-footer-col]");
10403
+ if (col && !getNavigationItemAnchor(target)) {
10404
+ armFooterPressDrag();
10405
+ footerPointerDragRef.current = {
10406
+ el: col,
10407
+ kind: "column",
10408
+ startX: e.clientX,
10409
+ startY: e.clientY,
10410
+ pointerId: e.pointerId,
10411
+ wasSelected: resolveWasSelected(col),
10412
+ started: false
10413
+ };
10414
+ }
10415
+ };
10416
+ const onPointerMove = (e) => {
10417
+ const pending = footerPointerDragRef.current;
10418
+ if (!pending) return;
10419
+ if (pending.started) {
10420
+ e.preventDefault();
10421
+ clearTextSelection();
10422
+ const session = footerDragRef.current;
10423
+ if (!session) return;
10424
+ const slot = session.kind === "link" && session.hrefKey ? hitTestLinkDropSlot(e.clientX, e.clientY, session.hrefKey) : hitTestColumnDropSlot(e.clientX, e.clientY);
10425
+ refreshFooterDragVisualsRef.current(session, slot, e.clientX, e.clientY);
10426
+ return;
10427
+ }
10428
+ const dx = e.clientX - pending.startX;
10429
+ const dy = e.clientY - pending.startY;
10430
+ if (dx * dx + dy * dy < THRESHOLD * THRESHOLD) return;
10431
+ e.preventDefault();
10432
+ pending.started = true;
10433
+ clearTextSelection();
10434
+ try {
10435
+ document.body.setPointerCapture(pending.pointerId);
10436
+ } catch {
10437
+ }
10438
+ if (linkPopoverOpenRef.current) {
10439
+ setLinkPopoverRef.current(null);
10440
+ }
10441
+ if (activeElRef.current) {
10442
+ deactivateRef.current();
10443
+ }
10444
+ if (pending.kind === "link") {
10445
+ const key = pending.el.getAttribute("data-ohw-href-key");
10446
+ if (!key) return;
10447
+ const column = findFooterColumnForLink(pending.el);
10448
+ const columns2 = listFooterColumns();
10449
+ beginFooterDragRef.current({
10450
+ kind: "link",
10451
+ hrefKey: key,
10452
+ columnEl: column,
10453
+ sourceColumnIndex: column ? columns2.indexOf(column) : 0,
10454
+ wasSelected: pending.wasSelected,
10455
+ draggedEl: pending.el,
10456
+ lastClientX: e.clientX,
10457
+ lastClientY: e.clientY,
10458
+ activeSlot: null
10459
+ });
10460
+ return;
10461
+ }
10462
+ const columns = listFooterColumns();
10463
+ const idx = columns.indexOf(pending.el);
10464
+ if (idx < 0) return;
10465
+ beginFooterDragRef.current({
10466
+ kind: "column",
10467
+ hrefKey: null,
10468
+ columnEl: pending.el,
10469
+ sourceColumnIndex: idx,
10470
+ wasSelected: pending.wasSelected,
10471
+ draggedEl: pending.el,
10472
+ lastClientX: e.clientX,
10473
+ lastClientY: e.clientY,
10474
+ activeSlot: null
10475
+ });
10476
+ };
10477
+ const endPointerDrag = (e) => {
10478
+ const pending = footerPointerDragRef.current;
10479
+ footerPointerDragRef.current = null;
10480
+ try {
10481
+ if (document.body.hasPointerCapture(e.pointerId)) {
10482
+ document.body.releasePointerCapture(e.pointerId);
10483
+ }
10484
+ } catch {
10485
+ }
10486
+ if (!pending?.started) {
10487
+ unlockFooterDragInteraction();
10488
+ return;
10489
+ }
10490
+ suppressNextClickRef.current = true;
10491
+ commitFooterDragRef.current(e.clientX, e.clientY);
10492
+ };
10493
+ const blockSelectStart = (e) => {
10494
+ if (footerDragRef.current || footerPointerDragRef.current?.started || document.documentElement.hasAttribute("data-ohw-footer-press-drag")) {
10495
+ e.preventDefault();
10496
+ }
10497
+ };
10498
+ const onDragEnd = (_e) => {
10499
+ if (!footerDragRef.current) return;
10500
+ commitFooterDragRef.current();
10501
+ };
10502
+ document.addEventListener("pointerdown", onPointerDown, true);
10503
+ document.addEventListener("pointermove", onPointerMove, true);
10504
+ document.addEventListener("pointerup", endPointerDrag, true);
10505
+ document.addEventListener("pointercancel", endPointerDrag, true);
10506
+ document.addEventListener("selectstart", blockSelectStart, true);
10507
+ document.addEventListener("dragend", onDragEnd, true);
10508
+ return () => {
10509
+ document.removeEventListener("pointerdown", onPointerDown, true);
10510
+ document.removeEventListener("pointermove", onPointerMove, true);
10511
+ document.removeEventListener("pointerup", endPointerDrag, true);
10512
+ document.removeEventListener("pointercancel", endPointerDrag, true);
10513
+ document.removeEventListener("selectstart", blockSelectStart, true);
10514
+ document.removeEventListener("dragend", onDragEnd, true);
10515
+ unlockFooterDragInteraction();
10516
+ };
10517
+ }, [isEditMode]);
9565
10518
  useEffect8(() => {
9566
10519
  const handler = (e) => {
9567
10520
  if (e.data?.type !== "ow:request-schedule-config") return;
@@ -9599,7 +10552,13 @@ function OhhwellsBridge() {
9599
10552
  const next = { ...prev, [pathKey]: sections };
9600
10553
  return shouldUseDevFixtures() ? { ...DEV_SECTIONS_BY_PATH, ...next } : next;
9601
10554
  });
9602
- postToParent2({ type: "ow:ready", version: "1", path: pathname, nodes: collectEditableNodes(), sections });
10555
+ postToParent2({
10556
+ type: "ow:ready",
10557
+ version: "1",
10558
+ path: pathname,
10559
+ nodes: collectEditableNodes(editContentRef.current),
10560
+ sections
10561
+ });
9603
10562
  postToParent2({ type: "ow:request-site-pages" });
9604
10563
  }, 150);
9605
10564
  return () => {
@@ -9716,9 +10675,6 @@ function OhhwellsBridge() {
9716
10675
  },
9717
10676
  [postToParent2, sitePages, sectionsByPath]
9718
10677
  );
9719
- const showEditLink = toolbarShowEditLink;
9720
- const currentSections = sectionsByPath[pathname] ?? [];
9721
- linkPopoverOpenRef.current = linkPopover !== null;
9722
10678
  const handleMediaReplace = useCallback4(
9723
10679
  (key) => {
9724
10680
  postToParent2({ type: "ow:image-pick", key, elementType: mediaHover?.elementType ?? "image" });
@@ -9761,10 +10717,13 @@ function OhhwellsBridge() {
9761
10717
  },
9762
10718
  [postToParent2]
9763
10719
  );
10720
+ const showEditLink = toolbarShowEditLink;
10721
+ const currentSections = sectionsByPath[pathname] ?? [];
10722
+ linkPopoverOpenRef.current = linkPopover !== null;
9764
10723
  return bridgeRoot ? createPortal2(
9765
10724
  /* @__PURE__ */ jsxs14(Fragment5, { children: [
9766
- /* @__PURE__ */ jsx24("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
9767
- Object.entries(uploadingRects).map(([key, { rect, fadingOut }]) => /* @__PURE__ */ jsx24(
10725
+ /* @__PURE__ */ jsx25("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
10726
+ Object.entries(uploadingRects).map(([key, { rect, fadingOut }]) => /* @__PURE__ */ jsx25(
9768
10727
  MediaOverlay,
9769
10728
  {
9770
10729
  hover: { key, rect, elementType: "image", isDragOver: false, hasTextOverlap: false },
@@ -9775,7 +10734,7 @@ function OhhwellsBridge() {
9775
10734
  },
9776
10735
  `uploading-${key}`
9777
10736
  )),
9778
- mediaHover && !(mediaHover.key in uploadingRects) && /* @__PURE__ */ jsx24(
10737
+ mediaHover && !(mediaHover.key in uploadingRects) && /* @__PURE__ */ jsx25(
9779
10738
  MediaOverlay,
9780
10739
  {
9781
10740
  hover: mediaHover,
@@ -9784,61 +10743,31 @@ function OhhwellsBridge() {
9784
10743
  onVideoSettingsChange: handleVideoSettingsChange
9785
10744
  }
9786
10745
  ),
9787
- carouselHover && /* @__PURE__ */ jsx24(CarouselOverlay, { hover: carouselHover, onEdit: handleEditCarousel }),
9788
- siblingHintRect && !isItemDragging && /* @__PURE__ */ jsx24(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
9789
- hoveredNavContainerRect && toolbarVariant !== "select-frame" && !linkPopover && !isItemDragging && !siblingHintRect && /* @__PURE__ */ jsx24(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
9790
- toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && /* @__PURE__ */ jsx24(
9791
- NavbarContainerChrome,
9792
- {
9793
- rect: toolbarRect,
9794
- onAdd: handleAddTopLevelNavItem
9795
- }
9796
- ),
9797
- hoveredItemRect && !hoveredNavContainerRect && !siblingHintRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ jsx24(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
9798
- toolbarRect && (toolbarVariant === "link-action" || toolbarVariant === "select-frame") && /* @__PURE__ */ jsx24(
9799
- ItemInteractionLayer,
10746
+ carouselHover && /* @__PURE__ */ jsx25(CarouselOverlay, { hover: carouselHover, onEdit: handleEditCarousel }),
10747
+ siblingHintRect && !isItemDragging && siblingHintRects.length === 0 && /* @__PURE__ */ jsx25(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
10748
+ siblingHintRects.map((rect, i) => /* @__PURE__ */ jsx25(ItemInteractionLayer, { rect, state: "sibling-hint" }, `sibling-hint-${i}`)),
10749
+ isItemDragging && draggedItemRect && selectedElRef.current !== footerDragRef.current?.draggedEl && /* @__PURE__ */ jsx25(ItemInteractionLayer, { rect: draggedItemRect, state: "dragging" }),
10750
+ isItemDragging && footerDropSlots.map((slot, i) => /* @__PURE__ */ jsx25(
10751
+ "div",
9800
10752
  {
9801
- rect: toolbarRect,
9802
- elRef: glowElRef,
9803
- state: isItemDragging ? "dragging" : resolveItemInteractionState(toolbarRect, parentScrollRef.current),
9804
- showHandle: toolbarVariant === "link-action" && Boolean(reorderHrefKey),
9805
- dragDisabled: reorderDragDisabled,
9806
- dragHandleLabel: reorderHrefKey ? `Reorder ${reorderHrefKey}` : "Reorder item",
9807
- onDragHandleDragStart: handleItemDragStart,
9808
- onDragHandleDragEnd: handleItemDragEnd,
9809
- toolbar: toolbarVariant === "link-action" && !isItemDragging ? /* @__PURE__ */ jsx24(
9810
- ItemActionToolbar,
9811
- {
9812
- onEditLink: openLinkPopoverForSelected,
9813
- addItemDisabled: true,
9814
- editLinkDisabled: false,
9815
- moreDisabled: true
9816
- }
9817
- ) : void 0
9818
- }
9819
- ),
10753
+ className: "pointer-events-none fixed z-2147483646",
10754
+ style: {
10755
+ left: slot.left,
10756
+ top: slot.top,
10757
+ width: slot.width,
10758
+ height: slot.height
10759
+ },
10760
+ children: /* @__PURE__ */ jsx25(DropIndicator, { direction: slot.direction, state: activeFooterDropIndex === i ? "dragActive" : "dragIdle", className: slot.direction === "horizontal" ? "!h-full !w-full" : "!h-full !w-full" })
10761
+ },
10762
+ `footer-drop-${slot.direction}-${slot.columnIndex}-${slot.insertIndex}-${i}`
10763
+ )),
10764
+ hoveredNavContainerRect && toolbarVariant !== "select-frame" && !linkPopover && !isItemDragging && !siblingHintRect && siblingHintRects.length === 0 && /* @__PURE__ */ jsx25(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
10765
+ toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && /* @__PURE__ */ jsx25(NavbarContainerChrome, { rect: toolbarRect, onAdd: handleAddTopLevelNavItem }),
10766
+ hoveredItemRect && !hoveredNavContainerRect && !siblingHintRect && siblingHintRects.length === 0 && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ jsx25(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
10767
+ toolbarRect && (toolbarVariant === "link-action" || toolbarVariant === "select-frame") && /* @__PURE__ */ jsx25(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__ */ jsx25(ItemActionToolbar, { onEditLink: openLinkPopoverForSelected, addItemDisabled: true, editLinkDisabled: false, moreDisabled: true }) : void 0 }),
9820
10768
  toolbarRect && toolbarVariant === "rich-text" && /* @__PURE__ */ jsxs14(Fragment5, { children: [
9821
- /* @__PURE__ */ jsx24(
9822
- EditGlowChrome,
9823
- {
9824
- rect: toolbarRect,
9825
- elRef: glowElRef,
9826
- reorderHrefKey,
9827
- dragDisabled: reorderDragDisabled
9828
- }
9829
- ),
9830
- /* @__PURE__ */ jsx24(
9831
- FloatingToolbar,
9832
- {
9833
- rect: toolbarRect,
9834
- parentScroll: parentScrollRef.current,
9835
- elRef: toolbarElRef,
9836
- onCommand: handleCommand,
9837
- activeCommands,
9838
- showEditLink,
9839
- onEditLink: openLinkPopoverForActive
9840
- }
9841
- )
10769
+ /* @__PURE__ */ jsx25(EditGlowChrome, { rect: toolbarRect, elRef: glowElRef, reorderHrefKey, dragDisabled: reorderDragDisabled }),
10770
+ /* @__PURE__ */ jsx25(FloatingToolbar, { rect: toolbarRect, parentScroll: parentScrollRef.current, elRef: toolbarElRef, onCommand: handleCommand, activeCommands, showEditLink, onEditLink: openLinkPopoverForActive })
9842
10771
  ] }),
9843
10772
  maxBadge && /* @__PURE__ */ jsxs14(
9844
10773
  "div",
@@ -9866,128 +10795,33 @@ function OhhwellsBridge() {
9866
10795
  ]
9867
10796
  }
9868
10797
  ),
9869
- toggleState && !linkPopover && /* @__PURE__ */ jsx24(
9870
- StateToggle,
9871
- {
9872
- rect: toggleState.rect,
9873
- activeState: toggleState.activeState,
9874
- states: toggleState.states,
9875
- onStateChange: handleStateChange
9876
- }
9877
- ),
9878
- sectionGap && !linkPopover && /* @__PURE__ */ jsxs14(
9879
- "div",
9880
- {
9881
- "data-ohw-section-insert-line": "",
9882
- className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
9883
- style: { top: sectionGap.y, transform: "translateY(-50%)" },
9884
- children: [
9885
- /* @__PURE__ */ jsx24("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
9886
- /* @__PURE__ */ jsx24(
9887
- Badge,
9888
- {
9889
- 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",
9890
- onClick: () => {
9891
- window.parent.postMessage(
9892
- { type: "ow:add-section", insertAfter: sectionGap.insertAfter, insertBefore: sectionGap.insertBefore },
9893
- "*"
9894
- );
9895
- },
9896
- children: "Add Section"
9897
- }
9898
- ),
9899
- /* @__PURE__ */ jsx24("div", { className: "flex-1 bg-primary", style: { height: 3 } })
9900
- ]
9901
- }
9902
- ),
9903
- linkPopover && dialogPortalContainer ? /* @__PURE__ */ jsx24(
9904
- LinkPopover,
9905
- {
9906
- panelRef: linkPopoverPanelRef,
9907
- portalContainer: dialogPortalContainer,
9908
- open: true,
9909
- mode: linkPopover.mode ?? "edit",
9910
- pages: sitePages,
9911
- sections: currentSections,
9912
- sectionsByPath,
9913
- initialTarget: linkPopover.target,
9914
- existingTargets: linkPopover.intent === "add-nav" ? getNavbarExistingTargets() : [],
9915
- onClose: closeLinkPopover,
9916
- onSubmit: handleLinkPopoverSubmit
9917
- },
9918
- linkPopover.key
9919
- ) : null,
9920
- sectionGap && /* @__PURE__ */ jsxs14(
9921
- "div",
9922
- {
9923
- "data-ohw-section-insert-line": "",
9924
- className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
9925
- style: { top: sectionGap.y, transform: "translateY(-50%)" },
9926
- children: [
9927
- /* @__PURE__ */ jsx24("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
9928
- /* @__PURE__ */ jsx24(
9929
- Badge,
9930
- {
9931
- 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",
9932
- onClick: () => {
9933
- window.parent.postMessage(
9934
- { type: "ow:add-section", insertAfter: sectionGap.insertAfter, insertBefore: sectionGap.insertBefore },
9935
- "*"
9936
- );
10798
+ toggleState && !linkPopover && /* @__PURE__ */ jsx25(StateToggle, { rect: toggleState.rect, activeState: toggleState.activeState, states: toggleState.states, onStateChange: handleStateChange }),
10799
+ sectionGap && !linkPopover && /* @__PURE__ */ jsxs14("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: [
10800
+ /* @__PURE__ */ jsx25("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
10801
+ /* @__PURE__ */ jsx25(
10802
+ Badge,
10803
+ {
10804
+ 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",
10805
+ onClick: () => {
10806
+ window.parent.postMessage(
10807
+ {
10808
+ type: "ow:add-section",
10809
+ insertAfter: sectionGap.insertAfter,
10810
+ insertBefore: sectionGap.insertBefore
9937
10811
  },
9938
- children: "Add Section"
9939
- }
9940
- ),
9941
- /* @__PURE__ */ jsx24("div", { className: "flex-1 bg-primary", style: { height: 3 } })
9942
- ]
9943
- }
9944
- )
10812
+ "*"
10813
+ );
10814
+ },
10815
+ children: "Add Section"
10816
+ }
10817
+ ),
10818
+ /* @__PURE__ */ jsx25("div", { className: "flex-1 bg-primary", style: { height: 3 } })
10819
+ ] }),
10820
+ linkPopover && dialogPortalContainer ? /* @__PURE__ */ jsx25(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
9945
10821
  ] }),
9946
10822
  bridgeRoot
9947
10823
  ) : null;
9948
10824
  }
9949
-
9950
- // src/ui/drop-indicator.tsx
9951
- import * as React10 from "react";
9952
- import { jsx as jsx25 } from "react/jsx-runtime";
9953
- var dropIndicatorVariants = cva(
9954
- "ov-gap-line pointer-events-none shrink-0 transition-opacity duration-150",
9955
- {
9956
- variants: {
9957
- direction: {
9958
- vertical: "h-6 w-[3px]",
9959
- horizontal: "h-[3px] w-[200px]"
9960
- },
9961
- state: {
9962
- default: "opacity-0",
9963
- hover: "opacity-100",
9964
- dragIdle: "opacity-40",
9965
- dragActive: "opacity-100"
9966
- }
9967
- },
9968
- defaultVariants: {
9969
- direction: "vertical",
9970
- state: "default"
9971
- }
9972
- }
9973
- );
9974
- var DropIndicator = React10.forwardRef(
9975
- ({ className, direction, state, ...props }, ref) => {
9976
- return /* @__PURE__ */ jsx25(
9977
- "div",
9978
- {
9979
- ref,
9980
- "data-slot": "drop-indicator",
9981
- "data-direction": direction ?? "vertical",
9982
- "data-state": state ?? "default",
9983
- "aria-hidden": "true",
9984
- className: cn(dropIndicatorVariants({ direction, state }), className),
9985
- ...props
9986
- }
9987
- );
9988
- }
9989
- );
9990
- DropIndicator.displayName = "DropIndicator";
9991
10825
  export {
9992
10826
  CustomToolbar,
9993
10827
  CustomToolbarButton,