@ohhwells/bridge 0.1.42-next.82 → 0.1.42-next.83

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
@@ -4353,7 +4353,8 @@ var CustomToolbar = React4.forwardRef(({ className, onMouseDown, ...props }, ref
4353
4353
  ref,
4354
4354
  "data-ohw-toolbar": "",
4355
4355
  className: cn(
4356
- "inline-flex items-center gap-1 rounded-lg border border-border bg-background p-0.5 font-sans whitespace-nowrap shadow-[0px_2px_4px_-2px_rgba(0,0,0,0.1),0px_4px_6px_-1px_rgba(0,0,0,0.1)]",
4356
+ // Figma: bg background, radius 8, gap-1, p-0.5, shadow-md no border
4357
+ "inline-flex h-8 items-center gap-1 rounded-[var(--radius,0.5rem)] bg-background p-0.5 font-sans whitespace-nowrap shadow-md",
4357
4358
  className
4358
4359
  ),
4359
4360
  onMouseDown: (e) => {
@@ -4381,8 +4382,8 @@ var CustomToolbarButton = React4.forwardRef(
4381
4382
  ref,
4382
4383
  type,
4383
4384
  className: cn(
4384
- "inline-flex shrink-0 items-center justify-center rounded p-1.5 transition-colors",
4385
- active ? "bg-primary text-primary-foreground" : "bg-transparent text-foreground hover:bg-muted disabled:cursor-not-allowed disabled:text-muted-foreground disabled:opacity-60",
4385
+ "inline-flex size-7 shrink-0 items-center justify-center rounded-[calc(var(--radius,0.5rem)-2px)] text-foreground transition-colors",
4386
+ active ? "bg-primary text-primary-foreground" : "bg-transparent hover:bg-muted disabled:cursor-not-allowed disabled:text-muted-foreground disabled:opacity-60",
4386
4387
  className
4387
4388
  ),
4388
4389
  ...props
@@ -4409,37 +4410,33 @@ function Tooltip({ ...props }) {
4409
4410
  function TooltipTrigger({ ...props }) {
4410
4411
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_radix_ui3.Tooltip.Trigger, { "data-slot": "tooltip-trigger", ...props });
4411
4412
  }
4412
- var arrowClassBySide = {
4413
- top: "before:bottom-0 before:left-1/2 before:-translate-x-1/2 before:translate-y-1/2",
4414
- bottom: "before:top-0 before:left-1/2 before:-translate-x-1/2 before:-translate-y-1/2",
4415
- left: "before:right-0 before:top-1/2 before:-translate-y-1/2 before:translate-x-1/2",
4416
- right: "before:left-0 before:top-1/2 before:-translate-y-1/2 before:-translate-x-1/2"
4417
- };
4418
4413
  function TooltipContent({
4419
4414
  className,
4420
- sideOffset = 6,
4415
+ sideOffset = 3,
4421
4416
  side = "bottom",
4422
4417
  children,
4418
+ hideArrow,
4423
4419
  ...props
4424
4420
  }) {
4425
- const resolvedSide = side ?? "bottom";
4426
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_radix_ui3.Tooltip.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4421
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_radix_ui3.Tooltip.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
4427
4422
  import_radix_ui3.Tooltip.Content,
4428
4423
  {
4429
4424
  "data-slot": "tooltip-content",
4430
4425
  side,
4431
4426
  sideOffset,
4432
4427
  className: cn(
4433
- "relative z-[2147483647] w-fit max-w-xs rounded-md bg-foreground px-3 py-1.5 text-xs text-background shadow-md",
4434
- 'before:pointer-events-none before:absolute before:size-2 before:rotate-45 before:bg-foreground before:content-[""]',
4435
- arrowClassBySide[resolvedSide],
4436
- "animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
4428
+ "z-[2147483647] inline-flex w-fit max-w-xs items-center rounded-md bg-foreground px-3 py-1.5 text-xs text-background",
4429
+ "origin-[var(--radix-tooltip-content-transform-origin)] animate-in fade-in-0 zoom-in-95",
4430
+ "data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
4437
4431
  "data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2",
4438
4432
  "data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2",
4439
4433
  className
4440
4434
  ),
4441
4435
  ...props,
4442
- children
4436
+ children: [
4437
+ children,
4438
+ !hideArrow && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_radix_ui3.Tooltip.Arrow, { className: "z-[2147483647] size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground" })
4439
+ ]
4443
4440
  }
4444
4441
  ) });
4445
4442
  }
@@ -4456,7 +4453,7 @@ function ToolbarActionTooltip({
4456
4453
  const button = /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CustomToolbarButton, { type: "button", "aria-label": label, disabled, ...buttonProps, children });
4457
4454
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Tooltip, { children: [
4458
4455
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(TooltipTrigger, { asChild: true, children: disabled ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "inline-flex", children: button }) : button }),
4459
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(TooltipContent, { side, children: label })
4456
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(TooltipContent, { side, sideOffset: 3, children: label })
4460
4457
  ] });
4461
4458
  }
4462
4459
  function ItemActionToolbar({
@@ -4466,6 +4463,8 @@ function ItemActionToolbar({
4466
4463
  editLinkDisabled = false,
4467
4464
  addItemDisabled = true,
4468
4465
  moreDisabled = true,
4466
+ showAddItem = true,
4467
+ showMore = true,
4469
4468
  tooltipSide = "bottom"
4470
4469
  }) {
4471
4470
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(CustomToolbar, { children: [
@@ -4490,8 +4489,7 @@ function ItemActionToolbar({
4490
4489
  children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react2.Link, { className: "size-4 shrink-0", "aria-hidden": true })
4491
4490
  }
4492
4491
  ),
4493
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CustomToolbarDivider, {}),
4494
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4492
+ showAddItem ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4495
4493
  ToolbarActionTooltip,
4496
4494
  {
4497
4495
  label: "Add item",
@@ -4507,9 +4505,8 @@ function ItemActionToolbar({
4507
4505
  },
4508
4506
  children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react2.Plus, { className: "size-4 shrink-0", "aria-hidden": true })
4509
4507
  }
4510
- ),
4511
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CustomToolbarDivider, {}),
4512
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4508
+ ) : null,
4509
+ showMore ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4513
4510
  ToolbarActionTooltip,
4514
4511
  {
4515
4512
  label: "More",
@@ -4525,15 +4522,19 @@ function ItemActionToolbar({
4525
4522
  },
4526
4523
  children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react2.MoreHorizontal, { className: "size-4 shrink-0", "aria-hidden": true })
4527
4524
  }
4528
- )
4525
+ ) : null
4529
4526
  ] }) });
4530
4527
  }
4531
4528
 
4532
4529
  // src/ui/item-interaction-layer.tsx
4530
+ var React5 = __toESM(require("react"), 1);
4533
4531
  var import_jsx_runtime9 = require("react/jsx-runtime");
4534
4532
  var PRIMARY = "var(--ohw-primary, #0885FE)";
4535
- var FOCUS_RING = "0 0 0 4px rgba(8, 133, 254, 0.12)";
4533
+ var FOCUS_RING = `0 0 0 4px color-mix(in srgb, ${PRIMARY} 12%, transparent)`;
4536
4534
  var DRAG_SHADOW = "0px 4px 6px -1px rgba(0, 0, 0, 0.1), 0px 2px 4px -2px rgba(0, 0, 0, 0.1)";
4535
+ var TOOLBAR_EDGE_MARGIN = 4;
4536
+ var SELECTION_CHROME_GAP = 4;
4537
+ var TOOLBAR_STROKE_GAP = 4;
4537
4538
  function getChromeStyle(state) {
4538
4539
  switch (state) {
4539
4540
  case "hover":
@@ -4561,6 +4562,59 @@ function getChromeStyle(state) {
4561
4562
  return {};
4562
4563
  }
4563
4564
  }
4565
+ function ClampedToolbarSlot({
4566
+ placement,
4567
+ children
4568
+ }) {
4569
+ const slotRef = React5.useRef(null);
4570
+ React5.useLayoutEffect(() => {
4571
+ const slot = slotRef.current;
4572
+ if (!slot) return;
4573
+ const clamp = () => {
4574
+ const toolbar = slot.firstElementChild;
4575
+ if (!toolbar) return;
4576
+ const w = toolbar.offsetWidth;
4577
+ if (w <= 0) return;
4578
+ const parent = slot.offsetParent;
4579
+ if (!parent) return;
4580
+ const parentRect = parent.getBoundingClientRect();
4581
+ const centerX = parentRect.left + parentRect.width / 2;
4582
+ const half = w / 2;
4583
+ const clampedCenter = Math.max(
4584
+ TOOLBAR_EDGE_MARGIN + half,
4585
+ Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN - half)
4586
+ );
4587
+ const offsetX = clampedCenter - centerX;
4588
+ slot.style.transform = `translateX(calc(-50% + ${offsetX}px))`;
4589
+ };
4590
+ clamp();
4591
+ const ro = new ResizeObserver(clamp);
4592
+ ro.observe(slot);
4593
+ if (slot.firstElementChild) ro.observe(slot.firstElementChild);
4594
+ window.addEventListener("resize", clamp);
4595
+ return () => {
4596
+ ro.disconnect();
4597
+ window.removeEventListener("resize", clamp);
4598
+ };
4599
+ }, [placement, children]);
4600
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4601
+ "div",
4602
+ {
4603
+ ref: slotRef,
4604
+ className: cn(
4605
+ "pointer-events-auto absolute left-1/2",
4606
+ placement === "top" ? "bottom-full" : "top-full"
4607
+ ),
4608
+ style: {
4609
+ marginBottom: placement === "top" ? TOOLBAR_STROKE_GAP : void 0,
4610
+ marginTop: placement === "bottom" ? TOOLBAR_STROKE_GAP : void 0,
4611
+ transform: "translateX(-50%)"
4612
+ },
4613
+ "data-ohw-item-toolbar-anchor": placement,
4614
+ children
4615
+ }
4616
+ );
4617
+ }
4564
4618
  function ItemInteractionLayer({
4565
4619
  rect,
4566
4620
  state,
@@ -4573,7 +4627,7 @@ function ItemInteractionLayer({
4573
4627
  onDragHandleDragEnd,
4574
4628
  onItemPointerDown,
4575
4629
  onItemClick,
4576
- chromeGap = 6,
4630
+ chromeGap = SELECTION_CHROME_GAP,
4577
4631
  className
4578
4632
  }) {
4579
4633
  if (state === "default") return null;
@@ -4648,33 +4702,19 @@ function ItemInteractionLayer({
4648
4702
  )
4649
4703
  }
4650
4704
  ),
4651
- showToolbar && state === "active-top" && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4652
- "div",
4653
- {
4654
- className: "pointer-events-auto absolute bottom-full left-1/2 mb-2 -translate-x-1/2",
4655
- "data-ohw-item-toolbar-anchor": "top",
4656
- children: toolbar
4657
- }
4658
- ),
4659
- showToolbar && state === "active-bottom" && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4660
- "div",
4661
- {
4662
- className: "pointer-events-auto absolute left-1/2 top-full mt-2 -translate-x-1/2",
4663
- "data-ohw-item-toolbar-anchor": "bottom",
4664
- children: toolbar
4665
- }
4666
- )
4705
+ showToolbar && state === "active-top" && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ClampedToolbarSlot, { placement: "top", children: toolbar }),
4706
+ showToolbar && state === "active-bottom" && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ClampedToolbarSlot, { placement: "bottom", children: toolbar })
4667
4707
  ]
4668
4708
  }
4669
4709
  );
4670
4710
  }
4671
4711
 
4672
4712
  // src/ui/MediaOverlay.tsx
4673
- var React6 = __toESM(require("react"), 1);
4713
+ var React7 = __toESM(require("react"), 1);
4674
4714
  var import_lucide_react3 = require("lucide-react");
4675
4715
 
4676
4716
  // src/ui/button.tsx
4677
- var React5 = __toESM(require("react"), 1);
4717
+ var React6 = __toESM(require("react"), 1);
4678
4718
  var import_radix_ui4 = require("radix-ui");
4679
4719
  var import_jsx_runtime10 = require("react/jsx-runtime");
4680
4720
  var buttonVariants = cva(
@@ -4697,7 +4737,7 @@ var buttonVariants = cva(
4697
4737
  }
4698
4738
  }
4699
4739
  );
4700
- var Button = React5.forwardRef(
4740
+ var Button = React6.forwardRef(
4701
4741
  ({ className, variant, size, asChild = false, ...props }, ref) => {
4702
4742
  const Comp = asChild ? import_radix_ui4.Slot.Root : "button";
4703
4743
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
@@ -4749,7 +4789,7 @@ function MediaOverlay({
4749
4789
  onVideoSettingsChange
4750
4790
  }) {
4751
4791
  const { rect } = hover;
4752
- const skeletonRef = React6.useRef(null);
4792
+ const skeletonRef = React7.useRef(null);
4753
4793
  const isVideo = hover.elementType === "video";
4754
4794
  const autoplay = hover.videoAutoplay ?? true;
4755
4795
  const muted = hover.videoMuted ?? true;
@@ -4761,7 +4801,7 @@ function MediaOverlay({
4761
4801
  height: rect.height,
4762
4802
  zIndex: 2147483646
4763
4803
  };
4764
- React6.useEffect(() => {
4804
+ React7.useEffect(() => {
4765
4805
  if (!isUploading || !fadingOut || !skeletonRef.current) return;
4766
4806
  const anim = skeletonRef.current.animate([{ opacity: 1 }, { opacity: 0 }], {
4767
4807
  duration: MEDIA_UPLOAD_FADE_MS,
@@ -5218,7 +5258,7 @@ function scrollToHashSectionWhenReady(behavior = "smooth") {
5218
5258
  var import_react7 = require("react");
5219
5259
 
5220
5260
  // src/ui/dialog.tsx
5221
- var React7 = __toESM(require("react"), 1);
5261
+ var React8 = __toESM(require("react"), 1);
5222
5262
  var import_radix_ui5 = require("radix-ui");
5223
5263
  var import_lucide_react5 = require("lucide-react");
5224
5264
  var import_jsx_runtime13 = require("react/jsx-runtime");
@@ -5246,7 +5286,7 @@ function DialogOverlay({
5246
5286
  }
5247
5287
  );
5248
5288
  }
5249
- var DialogContent = React7.forwardRef(
5289
+ var DialogContent = React8.forwardRef(
5250
5290
  ({ className, children, showCloseButton = true, container, ...props }, ref) => {
5251
5291
  const positionMode = container ? "absolute" : "fixed";
5252
5292
  return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(DialogPortal, { container: container ?? void 0, children: [
@@ -5301,7 +5341,7 @@ function DialogFooter({
5301
5341
  }
5302
5342
  );
5303
5343
  }
5304
- var DialogTitle = React7.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5344
+ var DialogTitle = React8.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5305
5345
  import_radix_ui5.Dialog.Title,
5306
5346
  {
5307
5347
  ref,
@@ -5313,7 +5353,7 @@ var DialogTitle = React7.forwardRef(({ className, ...props }, ref) => /* @__PURE
5313
5353
  }
5314
5354
  ));
5315
5355
  DialogTitle.displayName = import_radix_ui5.Dialog.Title.displayName;
5316
- var DialogDescription = React7.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5356
+ var DialogDescription = React8.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5317
5357
  import_radix_ui5.Dialog.Description,
5318
5358
  {
5319
5359
  ref,
@@ -5418,9 +5458,9 @@ function SectionTreeItem({
5418
5458
  var import_react4 = require("react");
5419
5459
 
5420
5460
  // src/ui/input.tsx
5421
- var React8 = __toESM(require("react"), 1);
5461
+ var React9 = __toESM(require("react"), 1);
5422
5462
  var import_jsx_runtime16 = require("react/jsx-runtime");
5423
- var Input = React8.forwardRef(
5463
+ var Input = React9.forwardRef(
5424
5464
  ({ className, type, ...props }, ref) => {
5425
5465
  return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
5426
5466
  "input",
@@ -6537,9 +6577,19 @@ function insertNavbarItemDom(index, href, label, afterAnchor) {
6537
6577
  }
6538
6578
  return primary;
6539
6579
  }
6580
+ function getNavbarLinkKeysInContainer(container) {
6581
+ return Array.from(container.querySelectorAll("[data-ohw-href-key]")).filter(isNavbarLinkItem).map((el) => el.getAttribute("data-ohw-href-key")).filter((key) => Boolean(key));
6582
+ }
6540
6583
  function applyNavOrderToContainer(container, order) {
6584
+ const desired = order.filter((key, i) => order.indexOf(key) === i);
6585
+ const current = getNavbarLinkKeysInContainer(container);
6586
+ const extras = current.filter((key) => !desired.includes(key));
6587
+ const expected = [...desired.filter((key) => current.includes(key)), ...extras];
6588
+ if (current.length === expected.length && current.every((key, i) => key === expected[i])) {
6589
+ return;
6590
+ }
6541
6591
  const seen = /* @__PURE__ */ new Set();
6542
- for (const hrefKey of order) {
6592
+ for (const hrefKey of desired) {
6543
6593
  if (seen.has(hrefKey)) continue;
6544
6594
  seen.add(hrefKey);
6545
6595
  const el = findCounterpartByHrefKey(hrefKey, container);
@@ -7068,7 +7118,7 @@ function NavbarContainerChrome({
7068
7118
  }
7069
7119
 
7070
7120
  // src/ui/drop-indicator.tsx
7071
- var React9 = __toESM(require("react"), 1);
7121
+ var React10 = __toESM(require("react"), 1);
7072
7122
  var import_jsx_runtime23 = require("react/jsx-runtime");
7073
7123
  var dropIndicatorVariants = cva(
7074
7124
  "ov-gap-line pointer-events-none shrink-0 transition-opacity duration-150",
@@ -7091,7 +7141,7 @@ var dropIndicatorVariants = cva(
7091
7141
  }
7092
7142
  }
7093
7143
  );
7094
- var DropIndicator = React9.forwardRef(
7144
+ var DropIndicator = React10.forwardRef(
7095
7145
  ({ className, direction, state, ...props }, ref) => {
7096
7146
  return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
7097
7147
  "div",
@@ -7374,6 +7424,19 @@ function isDragHandleDisabled(el) {
7374
7424
  return raw === "true" || raw === "";
7375
7425
  });
7376
7426
  }
7427
+ function canDragNavigationItem(anchor) {
7428
+ const { key, disabled } = getNavigationItemReorderState(anchor);
7429
+ return Boolean(key) && !disabled;
7430
+ }
7431
+ function syncNavigationDragCursorAttrs() {
7432
+ document.querySelectorAll("nav [data-ohw-href-key], footer [data-ohw-href-key]").forEach((el) => {
7433
+ if (!isNavigationItem(el) || !canDragNavigationItem(el)) {
7434
+ el.removeAttribute("data-ohw-can-drag");
7435
+ return;
7436
+ }
7437
+ el.setAttribute("data-ohw-can-drag", "");
7438
+ });
7439
+ }
7377
7440
  function collectEditableNodes(extraContent) {
7378
7441
  const nodes = Array.from(document.querySelectorAll("[data-ohw-editable]")).map((el) => {
7379
7442
  if (el.dataset.ohwEditable === "image") {
@@ -7571,18 +7634,20 @@ function getNavigationRoot(el) {
7571
7634
  if (explicit) return explicit;
7572
7635
  return el.closest("nav, footer, aside");
7573
7636
  }
7637
+ function countFooterNavItems(el) {
7638
+ return Array.from(el.querySelectorAll("[data-ohw-href-key]")).filter(isNavigationItem).length;
7639
+ }
7574
7640
  function findFooterItemGroup(item) {
7641
+ const explicit = item.closest("[data-ohw-footer-col]");
7642
+ if (explicit) return explicit;
7575
7643
  const footer = item.closest("footer");
7576
7644
  if (!footer) return null;
7577
7645
  let node = item.parentElement;
7578
7646
  while (node && node !== footer) {
7579
- const hasDirectNavItems = Array.from(
7580
- node.querySelectorAll(":scope > [data-ohw-href-key]")
7581
- ).some(isNavigationItem);
7582
- if (hasDirectNavItems) return node;
7647
+ if (countFooterNavItems(node) >= 2) return node;
7583
7648
  node = node.parentElement;
7584
7649
  }
7585
- return null;
7650
+ return footer;
7586
7651
  }
7587
7652
  function isNavigationRoot(el) {
7588
7653
  return el.hasAttribute("data-ohw-nav-root") || el.matches("nav, footer, aside");
@@ -7590,16 +7655,46 @@ function isNavigationRoot(el) {
7590
7655
  function isInferredFooterGroup(el) {
7591
7656
  const footer = el.closest("footer");
7592
7657
  if (!footer || el === footer) return false;
7593
- return Array.from(el.querySelectorAll(":scope > [data-ohw-href-key]")).some(isNavigationItem);
7658
+ if (el.hasAttribute("data-ohw-footer-col")) return true;
7659
+ return countFooterNavItems(el) >= 2;
7594
7660
  }
7595
7661
  function isNavigationContainer(el) {
7596
7662
  return el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-footer-col") || isNavigationRoot(el) || isInferredFooterGroup(el);
7597
7663
  }
7664
+ function getFooterColumn(el) {
7665
+ return el.closest("[data-ohw-footer-col]");
7666
+ }
7667
+ function resolveFooterColumnSelectionTarget(target, clientX, clientY) {
7668
+ if (getNavigationItemAnchor(target)) return null;
7669
+ const column = getFooterColumn(target);
7670
+ if (!column) return null;
7671
+ if (isPointOverNavItem(column, clientX, clientY)) return null;
7672
+ return column;
7673
+ }
7598
7674
  function isPointOverNavigation(x, y) {
7675
+ const roots = /* @__PURE__ */ new Set();
7599
7676
  const navRoot = document.querySelector("[data-ohw-nav-root]") ?? document.querySelector("nav");
7600
- if (!navRoot) return false;
7601
- const r2 = navRoot.getBoundingClientRect();
7602
- return x >= r2.left && x <= r2.right && y >= r2.top && y <= r2.bottom;
7677
+ if (navRoot) roots.add(navRoot);
7678
+ const footer = document.querySelector("footer");
7679
+ if (footer) roots.add(footer);
7680
+ for (const root of roots) {
7681
+ const r2 = root.getBoundingClientRect();
7682
+ if (x >= r2.left && x <= r2.right && y >= r2.top && y <= r2.bottom) return true;
7683
+ }
7684
+ return false;
7685
+ }
7686
+ function findHoveredNavOrFooterContainer(x, y) {
7687
+ const candidates = [
7688
+ ...document.querySelectorAll("[data-ohw-nav-container]"),
7689
+ ...document.querySelectorAll("[data-ohw-footer-col]")
7690
+ ];
7691
+ for (const container of candidates) {
7692
+ const r2 = container.getBoundingClientRect();
7693
+ if (x < r2.left || x > r2.right || y < r2.top || y > r2.bottom) continue;
7694
+ if (isPointOverNavItem(container, x, y)) continue;
7695
+ return container;
7696
+ }
7697
+ return null;
7603
7698
  }
7604
7699
  function isPointOverNavItem(container, x, y) {
7605
7700
  return Array.from(container.querySelectorAll("[data-ohw-href-key]")).some((el) => {
@@ -7632,11 +7727,13 @@ function getNavigationSelectionParent(el) {
7632
7727
  if (isNavigationItem(el)) {
7633
7728
  const explicit = el.closest("[data-ohw-nav-container]");
7634
7729
  if (explicit) return explicit;
7730
+ const footerColumn = getFooterColumn(el);
7731
+ if (footerColumn) return footerColumn;
7635
7732
  const footerGroup = findFooterItemGroup(el);
7636
7733
  if (footerGroup) return footerGroup;
7637
7734
  return getNavigationRoot(el);
7638
7735
  }
7639
- if (el.hasAttribute("data-ohw-nav-container") || isInferredFooterGroup(el)) {
7736
+ if (el.hasAttribute("data-ohw-nav-container") || el.hasAttribute("data-ohw-footer-col") || isInferredFooterGroup(el)) {
7640
7737
  return getNavigationRoot(el);
7641
7738
  }
7642
7739
  return null;
@@ -7805,6 +7902,9 @@ var ICONS = {
7805
7902
  };
7806
7903
  var TOOLBAR_PILL_PADDING = 2;
7807
7904
  var TOOLBAR_TOGGLE_GAP = 4;
7905
+ var SELECTION_CHROME_GAP2 = 4;
7906
+ var TOOLBAR_STROKE_GAP2 = 4;
7907
+ var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
7808
7908
  var TOOLBAR_GROUPS = [
7809
7909
  [
7810
7910
  { cmd: "bold", title: "Bold" },
@@ -7828,7 +7928,7 @@ function EditGlowChrome({
7828
7928
  reorderHrefKey,
7829
7929
  dragDisabled = false
7830
7930
  }) {
7831
- const GAP = 6;
7931
+ const GAP = SELECTION_CHROME_GAP2;
7832
7932
  return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
7833
7933
  "div",
7834
7934
  {
@@ -7852,7 +7952,7 @@ function EditGlowChrome({
7852
7952
  inset: 0,
7853
7953
  border: "2px solid var(--ohw-primary, #0885FE)",
7854
7954
  borderRadius: 8,
7855
- boxShadow: "0 0 0 4px rgba(8, 133, 254, 0.12)",
7955
+ boxShadow: "0 0 0 4px color-mix(in srgb, var(--ohw-primary, #0885FE) 12%, transparent)",
7856
7956
  pointerEvents: "none"
7857
7957
  }
7858
7958
  }
@@ -7900,6 +8000,7 @@ function applyVisibleViewport(el, parentScroll) {
7900
8000
  el.style.top = `${top}px`;
7901
8001
  el.style.height = `${height}px`;
7902
8002
  }
8003
+ var TOOLBAR_EDGE_MARGIN2 = 4;
7903
8004
  function clampToolbarToClip(top, transform, rect, clip, approxH, gap) {
7904
8005
  const isAbove = transform.includes("translateY(-100%)");
7905
8006
  let visualTop = isAbove ? top - approxH : top;
@@ -7923,10 +8024,16 @@ function clampToolbarToClip(top, transform, rect, clip, approxH, gap) {
7923
8024
  const pinnedTop = Math.min(maxBottom - approxH, Math.max(minTop, clip.top + gap));
7924
8025
  return { top: pinnedTop + approxH, transform: "translateX(-50%) translateY(-100%)" };
7925
8026
  }
7926
- function calcToolbarPos(rect, parentScroll, approxW = 306) {
7927
- const GAP = 8;
8027
+ function clampToolbarLeft(centerX, toolbarWidth) {
8028
+ const half = toolbarWidth / 2;
8029
+ return Math.max(
8030
+ TOOLBAR_EDGE_MARGIN2 + half,
8031
+ Math.min(centerX, window.innerWidth - TOOLBAR_EDGE_MARGIN2 - half)
8032
+ );
8033
+ }
8034
+ function calcToolbarPos(rect, parentScroll, toolbarW = 306) {
8035
+ const GAP = TOOLBAR_OFFSET_FROM_ELEMENT;
7928
8036
  const APPROX_H = 32;
7929
- const APPROX_W = approxW;
7930
8037
  const clip = getIframeVisibleClip(parentScroll);
7931
8038
  const clipTop = clip?.top ?? 0;
7932
8039
  const canvasTopInIframe = parentScroll != null ? parentScroll.headerH - parentScroll.iframeOffsetTop : 0;
@@ -7956,7 +8063,7 @@ function calcToolbarPos(rect, parentScroll, approxW = 306) {
7956
8063
  transform = clamped.transform;
7957
8064
  }
7958
8065
  const rawLeft = rect.left + rect.width / 2;
7959
- const left = Math.max(GAP + APPROX_W / 2, Math.min(rawLeft, window.innerWidth - GAP - APPROX_W / 2));
8066
+ const left = clampToolbarLeft(rawLeft, toolbarW);
7960
8067
  return { top, left, transform };
7961
8068
  }
7962
8069
  function resolveItemInteractionState(rect, parentScroll) {
@@ -7972,11 +8079,37 @@ function FloatingToolbar({
7972
8079
  showEditLink,
7973
8080
  onEditLink
7974
8081
  }) {
7975
- const { top, left, transform } = calcToolbarPos(rect, parentScroll, 330);
8082
+ const localRef = import_react9.default.useRef(null);
8083
+ const [measuredW, setMeasuredW] = import_react9.default.useState(330);
8084
+ const setRefs = import_react9.default.useCallback(
8085
+ (node) => {
8086
+ localRef.current = node;
8087
+ if (typeof elRef === "function") elRef(node);
8088
+ else if (elRef) elRef.current = node;
8089
+ if (node) {
8090
+ const w = node.offsetWidth;
8091
+ if (w > 0) setMeasuredW(w);
8092
+ }
8093
+ },
8094
+ [elRef]
8095
+ );
8096
+ import_react9.default.useLayoutEffect(() => {
8097
+ const node = localRef.current;
8098
+ if (!node) return;
8099
+ const update = () => {
8100
+ const w = node.offsetWidth;
8101
+ if (w > 0) setMeasuredW(w);
8102
+ };
8103
+ update();
8104
+ const ro = new ResizeObserver(update);
8105
+ ro.observe(node);
8106
+ return () => ro.disconnect();
8107
+ }, [showEditLink, activeCommands]);
8108
+ const { top, left, transform } = calcToolbarPos(rect, parentScroll, measuredW);
7976
8109
  return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
7977
8110
  "div",
7978
8111
  {
7979
- ref: elRef,
8112
+ ref: setRefs,
7980
8113
  style: {
7981
8114
  position: "fixed",
7982
8115
  top,
@@ -8186,6 +8319,7 @@ function OhhwellsBridge() {
8186
8319
  const [toolbarVariant, setToolbarVariant] = (0, import_react9.useState)("none");
8187
8320
  const toolbarVariantRef = (0, import_react9.useRef)("none");
8188
8321
  toolbarVariantRef.current = toolbarVariant;
8322
+ const [selectedIsCta, setSelectedIsCta] = (0, import_react9.useState)(false);
8189
8323
  const [reorderHrefKey, setReorderHrefKey] = (0, import_react9.useState)(null);
8190
8324
  const [reorderDragDisabled, setReorderDragDisabled] = (0, import_react9.useState)(false);
8191
8325
  const [toggleState, setToggleState] = (0, import_react9.useState)(null);
@@ -8380,6 +8514,7 @@ function OhhwellsBridge() {
8380
8514
  }
8381
8515
  }
8382
8516
  el.removeAttribute("contenteditable");
8517
+ el.removeAttribute("data-ohw-editing");
8383
8518
  activeElRef.current = null;
8384
8519
  setReorderHrefKey(null);
8385
8520
  setReorderDragDisabled(false);
@@ -8392,8 +8527,15 @@ function OhhwellsBridge() {
8392
8527
  setToolbarShowEditLink(false);
8393
8528
  postToParent2({ type: "ow:exit-edit" });
8394
8529
  }, [postToParent2]);
8530
+ const clearSelectedAttr = (0, import_react9.useCallback)(() => {
8531
+ document.querySelectorAll("[data-ohw-selected]").forEach((el) => {
8532
+ el.removeAttribute("data-ohw-selected");
8533
+ });
8534
+ }, []);
8395
8535
  const deselect = (0, import_react9.useCallback)(() => {
8536
+ clearSelectedAttr();
8396
8537
  selectedElRef.current = null;
8538
+ setSelectedIsCta(false);
8397
8539
  setReorderHrefKey(null);
8398
8540
  setReorderDragDisabled(false);
8399
8541
  setIsFooterFrameSelection(false);
@@ -8407,9 +8549,15 @@ function OhhwellsBridge() {
8407
8549
  setToolbarRect(null);
8408
8550
  setToolbarVariant("none");
8409
8551
  }
8410
- }, []);
8552
+ }, [clearSelectedAttr]);
8553
+ const markSelected = (0, import_react9.useCallback)((el) => {
8554
+ clearSelectedAttr();
8555
+ el.setAttribute("data-ohw-selected", "");
8556
+ }, [clearSelectedAttr]);
8411
8557
  const reselectNavigationItem = (0, import_react9.useCallback)((navAnchor) => {
8412
8558
  selectedElRef.current = navAnchor;
8559
+ markSelected(navAnchor);
8560
+ setSelectedIsCta(isNavbarButton2(navAnchor));
8413
8561
  const { key, disabled } = getNavigationItemReorderState(navAnchor);
8414
8562
  setReorderHrefKey(key);
8415
8563
  setReorderDragDisabled(disabled);
@@ -8417,7 +8565,7 @@ function OhhwellsBridge() {
8417
8565
  setToolbarRect(navAnchor.getBoundingClientRect());
8418
8566
  setToolbarShowEditLink(false);
8419
8567
  setActiveCommands(/* @__PURE__ */ new Set());
8420
- }, []);
8568
+ }, [markSelected]);
8421
8569
  const commitNavigationTextEdit = (0, import_react9.useCallback)((navAnchor) => {
8422
8570
  const el = activeElRef.current;
8423
8571
  if (!el) return;
@@ -8437,6 +8585,7 @@ function OhhwellsBridge() {
8437
8585
  }
8438
8586
  }
8439
8587
  el.removeAttribute("contenteditable");
8588
+ el.removeAttribute("data-ohw-editing");
8440
8589
  activeElRef.current = null;
8441
8590
  setMaxBadge(null);
8442
8591
  setActiveCommands(/* @__PURE__ */ new Set());
@@ -8746,97 +8895,106 @@ function OhhwellsBridge() {
8746
8895
  }, []);
8747
8896
  reselectNavigationItemRef.current = reselectNavigationItem;
8748
8897
  commitNavigationTextEditRef.current = commitNavigationTextEdit;
8749
- const select = (0, import_react9.useCallback)(
8750
- (anchor) => {
8751
- if (!isNavigationItem(anchor)) return;
8752
- if (activeElRef.current) deactivate();
8753
- selectedElRef.current = anchor;
8754
- clearHrefKeyHover(anchor);
8755
- hoveredNavContainerRef.current = null;
8756
- setHoveredNavContainerRect(null);
8757
- setHoveredItemRect(null);
8758
- hoveredItemElRef.current = null;
8759
- siblingHintElRef.current = null;
8760
- setSiblingHintRect(null);
8761
- setSiblingHintRects([]);
8762
- setIsItemDragging(false);
8763
- const { key, disabled } = getNavigationItemReorderState(anchor);
8764
- setReorderHrefKey(key);
8765
- setReorderDragDisabled(disabled);
8766
- setIsFooterFrameSelection(false);
8767
- setToolbarVariant("link-action");
8768
- setToolbarRect(anchor.getBoundingClientRect());
8769
- setToolbarShowEditLink(false);
8770
- setActiveCommands(/* @__PURE__ */ new Set());
8771
- },
8772
- [deactivate]
8773
- );
8774
- const selectFrame = (0, import_react9.useCallback)(
8775
- (el) => {
8776
- if (!isNavigationContainer(el)) return;
8777
- if (activeElRef.current) deactivate();
8778
- const isFooterColumn = el.hasAttribute("data-ohw-footer-col") || Boolean(el.closest("footer") && isInferredFooterGroup(el));
8779
- selectedElRef.current = el;
8780
- clearHrefKeyHover(el);
8781
- hoveredNavContainerRef.current = null;
8782
- setHoveredNavContainerRect(null);
8783
- setHoveredItemRect(null);
8784
- hoveredItemElRef.current = null;
8785
- siblingHintElRef.current = null;
8786
- setSiblingHintRect(null);
8787
- setSiblingHintRects(
8788
- isFooterColumn ? listFooterColumns().filter((column) => column !== el).map((column) => column.getBoundingClientRect()) : []
8789
- );
8790
- setIsItemDragging(false);
8898
+ const select = (0, import_react9.useCallback)((anchor) => {
8899
+ if (!isNavigationItem(anchor)) return;
8900
+ if (activeElRef.current) deactivate();
8901
+ selectedElRef.current = anchor;
8902
+ markSelected(anchor);
8903
+ setSelectedIsCta(isNavbarButton2(anchor));
8904
+ clearHrefKeyHover(anchor);
8905
+ hoveredNavContainerRef.current = null;
8906
+ setHoveredNavContainerRect(null);
8907
+ setHoveredItemRect(null);
8908
+ hoveredItemElRef.current = null;
8909
+ siblingHintElRef.current = null;
8910
+ setSiblingHintRect(null);
8911
+ setSiblingHintRects([]);
8912
+ setIsItemDragging(false);
8913
+ const { key, disabled } = getNavigationItemReorderState(anchor);
8914
+ setReorderHrefKey(key);
8915
+ setReorderDragDisabled(disabled);
8916
+ setIsFooterFrameSelection(false);
8917
+ setToolbarVariant("link-action");
8918
+ setToolbarRect(anchor.getBoundingClientRect());
8919
+ setToolbarShowEditLink(false);
8920
+ setActiveCommands(/* @__PURE__ */ new Set());
8921
+ }, [deactivate, markSelected]);
8922
+ const selectFrame = (0, import_react9.useCallback)((el) => {
8923
+ if (!isNavigationContainer(el)) return;
8924
+ if (activeElRef.current) deactivate();
8925
+ const isFooterColumn = el.hasAttribute("data-ohw-footer-col") || Boolean(el.closest("footer") && isInferredFooterGroup(el));
8926
+ selectedElRef.current = el;
8927
+ markSelected(el);
8928
+ setSelectedIsCta(false);
8929
+ clearHrefKeyHover(el);
8930
+ hoveredNavContainerRef.current = null;
8931
+ setHoveredNavContainerRect(null);
8932
+ setHoveredItemRect(null);
8933
+ hoveredItemElRef.current = null;
8934
+ siblingHintElRef.current = null;
8935
+ setSiblingHintRect(null);
8936
+ setSiblingHintRects(
8937
+ isFooterColumn ? listFooterColumns().filter((column) => column !== el).map((column) => column.getBoundingClientRect()) : []
8938
+ );
8939
+ setIsItemDragging(false);
8940
+ setReorderHrefKey(null);
8941
+ setReorderDragDisabled(false);
8942
+ setIsFooterFrameSelection(isFooterColumn);
8943
+ setToolbarVariant("select-frame");
8944
+ setToolbarRect(el.getBoundingClientRect());
8945
+ setToolbarShowEditLink(false);
8946
+ setActiveCommands(/* @__PURE__ */ new Set());
8947
+ }, [deactivate, markSelected]);
8948
+ const activate = (0, import_react9.useCallback)((el, options) => {
8949
+ if (activeElRef.current === el) return;
8950
+ clearSelectedAttr();
8951
+ selectedElRef.current = null;
8952
+ setSelectedIsCta(false);
8953
+ deactivate();
8954
+ if (hoveredImageRef.current) {
8955
+ hoveredImageRef.current = null;
8956
+ setMediaHover(null);
8957
+ postToParentRef.current({ type: "ow:image-unhover" });
8958
+ }
8959
+ setToolbarVariant("rich-text");
8960
+ siblingHintElRef.current = null;
8961
+ setSiblingHintRect(null);
8962
+ setSiblingHintRects([]);
8963
+ setIsItemDragging(false);
8964
+ setIsFooterFrameSelection(false);
8965
+ el.setAttribute("contenteditable", "true");
8966
+ el.setAttribute("data-ohw-editing", "");
8967
+ el.removeAttribute("data-ohw-hovered");
8968
+ el.closest("[data-ohw-href-key]")?.removeAttribute("data-ohw-hovered");
8969
+ activeElRef.current = el;
8970
+ originalContentRef.current = el.innerHTML;
8971
+ el.focus({ preventScroll: true });
8972
+ if (options?.caretX !== void 0 && options?.caretY !== void 0) {
8973
+ placeCaretAtPoint(el, options.caretX, options.caretY);
8974
+ } else {
8975
+ const selection = window.getSelection();
8976
+ if (selection && selection.rangeCount === 0) {
8977
+ const range = document.createRange();
8978
+ range.selectNodeContents(el);
8979
+ range.collapse(false);
8980
+ selection.removeAllRanges();
8981
+ selection.addRange(range);
8982
+ }
8983
+ }
8984
+ setToolbarShowEditLink(Boolean(getHrefKeyFromElement(el)));
8985
+ const navAnchor = getNavigationItemAnchor(el);
8986
+ if (navAnchor) {
8791
8987
  setReorderHrefKey(null);
8792
8988
  setReorderDragDisabled(false);
8793
- setIsFooterFrameSelection(isFooterColumn);
8794
- setToolbarVariant("select-frame");
8795
- setToolbarRect(el.getBoundingClientRect());
8796
- setToolbarShowEditLink(false);
8797
- setActiveCommands(/* @__PURE__ */ new Set());
8798
- },
8799
- [deactivate]
8800
- );
8801
- const activate = (0, import_react9.useCallback)(
8802
- (el, options) => {
8803
- if (activeElRef.current === el) return;
8804
- selectedElRef.current = null;
8805
- deactivate();
8806
- if (hoveredImageRef.current) {
8807
- hoveredImageRef.current = null;
8808
- postToParentRef.current({ type: "ow:image-unhover" });
8809
- }
8810
- setToolbarVariant("rich-text");
8811
- siblingHintElRef.current = null;
8812
- setSiblingHintRect(null);
8813
- setSiblingHintRects([]);
8814
- setIsItemDragging(false);
8815
- el.setAttribute("contenteditable", "true");
8816
- el.removeAttribute("data-ohw-hovered");
8817
- el.closest("[data-ohw-href-key]")?.removeAttribute("data-ohw-hovered");
8818
- activeElRef.current = el;
8819
- originalContentRef.current = el.innerHTML;
8820
- el.focus();
8821
- if (options?.caretX !== void 0 && options?.caretY !== void 0) {
8822
- placeCaretAtPoint(el, options.caretX, options.caretY);
8823
- }
8824
- setToolbarShowEditLink(Boolean(getHrefKeyFromElement(el)));
8825
- const navAnchor = getNavigationItemAnchor(el);
8826
- if (navAnchor) {
8827
- setReorderHrefKey(null);
8828
- setReorderDragDisabled(false);
8829
- } else {
8830
- const { key: reorderKey, disabled: reorderDisabled } = getReorderHandleState(el);
8831
- setReorderHrefKey(reorderKey);
8832
- setReorderDragDisabled(reorderDisabled);
8833
- }
8834
- setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
8835
- postToParent2({ type: "ow:enter-edit", key: el.dataset.ohwKey });
8836
- requestAnimationFrame(() => refreshActiveCommandsRef.current());
8837
- },
8838
- [deactivate, postToParent2]
8839
- );
8989
+ } else {
8990
+ const { key: reorderKey, disabled: reorderDisabled } = getReorderHandleState(el);
8991
+ setReorderHrefKey(reorderKey);
8992
+ setReorderDragDisabled(reorderDisabled);
8993
+ }
8994
+ setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
8995
+ postToParent2({ type: "ow:enter-edit", key: el.dataset.ohwKey });
8996
+ requestAnimationFrame(() => refreshActiveCommandsRef.current());
8997
+ }, [clearSelectedAttr, deactivate, postToParent2]);
8840
8998
  activateRef.current = activate;
8841
8999
  deactivateRef.current = deactivate;
8842
9000
  selectRef.current = select;
@@ -8987,11 +9145,21 @@ function OhhwellsBridge() {
8987
9145
  if (!subdomain) return {};
8988
9146
  return contentCache.get(subdomain) ?? {};
8989
9147
  };
8990
- let applying = false;
9148
+ const observeRoots = () => [document.querySelector("nav"), document.querySelector("footer")].filter(
9149
+ (el) => Boolean(el)
9150
+ );
8991
9151
  let rafId = null;
9152
+ let applying = false;
9153
+ let observer = null;
9154
+ const attach = () => {
9155
+ for (const root of observeRoots()) {
9156
+ observer?.observe(root, { childList: true, subtree: true });
9157
+ }
9158
+ };
8992
9159
  const run = () => {
8993
9160
  if (footerDragRef.current || applying) return;
8994
9161
  applying = true;
9162
+ observer?.disconnect();
8995
9163
  try {
8996
9164
  const content = contentForNav();
8997
9165
  reconcileNavbarItemsFromContent(content);
@@ -9001,27 +9169,24 @@ function OhhwellsBridge() {
9001
9169
  disableNativeHrefDrag(el);
9002
9170
  }
9003
9171
  });
9172
+ if (isEditMode) syncNavigationDragCursorAttrs();
9004
9173
  } finally {
9005
9174
  applying = false;
9175
+ attach();
9006
9176
  }
9007
9177
  };
9008
- const scheduleRun = () => {
9009
- if (rafId !== null) cancelAnimationFrame(rafId);
9178
+ const schedule = () => {
9179
+ if (applying || rafId != null) return;
9010
9180
  rafId = requestAnimationFrame(() => {
9011
9181
  rafId = null;
9012
9182
  run();
9013
9183
  });
9014
9184
  };
9185
+ observer = new MutationObserver(schedule);
9015
9186
  run();
9016
- const nav = document.querySelector("nav");
9017
- const footer = document.querySelector("footer");
9018
- const observer = new MutationObserver(scheduleRun);
9019
- if (nav) observer.observe(nav, { childList: true, subtree: true });
9020
- if (footer) observer.observe(footer, { childList: true, subtree: true });
9021
- if (!nav && !footer) return;
9022
9187
  return () => {
9023
- observer.disconnect();
9024
- if (rafId !== null) cancelAnimationFrame(rafId);
9188
+ if (rafId != null) cancelAnimationFrame(rafId);
9189
+ observer?.disconnect();
9025
9190
  };
9026
9191
  }, [isEditMode, pathname, subdomain, fetchState]);
9027
9192
  (0, import_react9.useEffect)(() => {
@@ -9070,13 +9235,18 @@ function OhhwellsBridge() {
9070
9235
  editStylesRef.current?.forceHover.remove();
9071
9236
  editStylesRef.current?.stateViews.remove();
9072
9237
  editStylesRef.current = null;
9238
+ document.querySelectorAll("[data-ohw-can-drag]").forEach((el) => {
9239
+ el.removeAttribute("data-ohw-can-drag");
9240
+ });
9073
9241
  return;
9074
9242
  }
9075
- if (!editStylesRef.current) {
9076
- const initialVh = window.innerHeight;
9077
- const base = document.createElement("style");
9078
- base.setAttribute("data-ohw-edit-style", "");
9079
- base.textContent = `
9243
+ const existing = editStylesRef.current;
9244
+ let initialVh = window.innerHeight;
9245
+ if (existing?.base.textContent) {
9246
+ const match = existing.base.textContent.match(/\.min-h-screen[^{]*\{[^}]*min-height:\s*(\d+)px/);
9247
+ if (match) initialVh = parseInt(match[1], 10);
9248
+ }
9249
+ const baseCss = `
9080
9250
  html { height: auto !important; }
9081
9251
  body { height: auto !important; min-height: 0 !important; overflow: hidden !important; }
9082
9252
  .min-h-screen, .min-h-svh, .min-h-dvh { min-height: ${initialVh}px !important; }
@@ -9087,6 +9257,25 @@ function OhhwellsBridge() {
9087
9257
  [data-ohw-editable] {
9088
9258
  display: block;
9089
9259
  }
9260
+ /* Body text (no item-action toolbar) \u2014 first click enters text edit \u2192 I-beam.
9261
+ Exclude select-first nav/footer labels so grab/default on [data-ohw-href-key] can win. */
9262
+ [data-ohw-editable]:not([contenteditable]):not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"]):not([data-ohw-editable="video"]):not([data-ohw-editable="link"]):not(:is([data-ohw-href-key] *)) {
9263
+ cursor: text !important;
9264
+ }
9265
+ /* Select-first items: default arrow; grab only when drag-start is allowed (data-ohw-can-drag). */
9266
+ [data-ohw-href-key]:not([data-ohw-selected]),
9267
+ [data-ohw-href-key]:not([data-ohw-selected]) * {
9268
+ cursor: default !important;
9269
+ }
9270
+ [data-ohw-href-key][data-ohw-can-drag]:not([data-ohw-selected]),
9271
+ [data-ohw-href-key][data-ohw-can-drag]:not([data-ohw-selected]) * {
9272
+ cursor: grab !important;
9273
+ }
9274
+ /* Selected (action-toolbar) \u2014 next click enters text edit at caret. */
9275
+ [data-ohw-href-key][data-ohw-selected],
9276
+ [data-ohw-href-key][data-ohw-selected] * {
9277
+ cursor: text !important;
9278
+ }
9090
9279
  /* Native <a> drag steals clicks \u2014 disable for edit links; reorder via press-to-drag / handle. */
9091
9280
  footer [data-ohw-href-key] {
9092
9281
  -webkit-user-drag: none !important;
@@ -9135,9 +9324,17 @@ function OhhwellsBridge() {
9135
9324
  outline: none !important;
9136
9325
  outline-offset: 0 !important;
9137
9326
  }
9138
- [data-ohw-editable][contenteditable] {
9327
+ /* Text edit wins over grab/default (must beat [data-ohw-can-drag] *). */
9328
+ [data-ohw-editing],
9329
+ [data-ohw-editing] *,
9330
+ [data-ohw-editable][contenteditable],
9331
+ [data-ohw-editable][contenteditable] *,
9332
+ nav [data-ohw-href-key] [data-ohw-editable][contenteditable],
9333
+ nav [data-ohw-href-key] [data-ohw-editable][contenteditable] *,
9334
+ footer [data-ohw-href-key] [data-ohw-editable][contenteditable],
9335
+ footer [data-ohw-href-key] [data-ohw-editable][contenteditable] * {
9139
9336
  outline: none !important;
9140
- caret-color: ${PRIMARY2};
9337
+ caret-color: ${PRIMARY2} !important;
9141
9338
  cursor: text !important;
9142
9339
  }
9143
9340
  [data-ohw-editable][contenteditable]::selection,
@@ -9146,6 +9343,9 @@ function OhhwellsBridge() {
9146
9343
  [data-ohw-editable-state][data-ohw-active-state] [data-ohw-editable] { pointer-events: auto !important; }
9147
9344
  [data-ohw-editable-state][data-ohw-active-state][data-ohw-editable] { pointer-events: auto !important; }
9148
9345
  `;
9346
+ if (!existing) {
9347
+ const base = document.createElement("style");
9348
+ base.setAttribute("data-ohw-edit-style", "");
9149
9349
  const forceHover = document.createElement("style");
9150
9350
  forceHover.setAttribute("data-ohw-active-state-style", "");
9151
9351
  const stateViews = document.createElement("style");
@@ -9161,6 +9361,8 @@ function OhhwellsBridge() {
9161
9361
  document.head.appendChild(stateViews);
9162
9362
  editStylesRef.current = { base, forceHover, stateViews };
9163
9363
  }
9364
+ editStylesRef.current.base.textContent = baseCss;
9365
+ syncNavigationDragCursorAttrs();
9164
9366
  refreshStateRules();
9165
9367
  const handleClick = (e) => {
9166
9368
  if (suppressNextClickRef.current) {
@@ -9203,10 +9405,34 @@ function OhhwellsBridge() {
9203
9405
  selectFrameRef.current(navFromChrome);
9204
9406
  return;
9205
9407
  }
9408
+ const footerFromChrome = resolveFooterColumnSelectionTarget(target, e.clientX, e.clientY);
9409
+ if (footerFromChrome) {
9410
+ e.preventDefault();
9411
+ e.stopPropagation();
9412
+ selectFrameRef.current(footerFromChrome);
9413
+ return;
9414
+ }
9206
9415
  e.preventDefault();
9207
9416
  e.stopPropagation();
9208
9417
  return;
9209
9418
  }
9419
+ if (activeElRef.current) {
9420
+ const active = activeElRef.current;
9421
+ const inActive = active === target || active.contains(target);
9422
+ const navAnchor = getNavigationItemAnchor(active);
9423
+ const inActiveNav = Boolean(
9424
+ navAnchor && (navAnchor === target || navAnchor.contains(target))
9425
+ );
9426
+ if (inActive || inActiveNav) {
9427
+ e.preventDefault();
9428
+ e.stopPropagation();
9429
+ if (e.detail < 2) {
9430
+ placeCaretAtPoint(active, e.clientX, e.clientY);
9431
+ refreshActiveCommandsRef.current();
9432
+ }
9433
+ return;
9434
+ }
9435
+ }
9210
9436
  const editable = target.closest("[data-ohw-editable]");
9211
9437
  if (editable) {
9212
9438
  if (editable.dataset.ohwEditable === "link") {
@@ -9285,6 +9511,13 @@ function OhhwellsBridge() {
9285
9511
  selectFrameRef.current(navContainerToSelect);
9286
9512
  return;
9287
9513
  }
9514
+ const footerColumnToSelect = resolveFooterColumnSelectionTarget(target, e.clientX, e.clientY);
9515
+ if (footerColumnToSelect) {
9516
+ e.preventDefault();
9517
+ e.stopPropagation();
9518
+ selectFrameRef.current(footerColumnToSelect);
9519
+ return;
9520
+ }
9288
9521
  const selectedContainer = selectedElRef.current;
9289
9522
  if (selectedContainer && isNavigationContainer(selectedContainer)) {
9290
9523
  const navItem = getNavigationItemAnchor(target);
@@ -9352,15 +9585,15 @@ function OhhwellsBridge() {
9352
9585
  return;
9353
9586
  }
9354
9587
  if (toolbarVariantRef.current !== "select-frame") {
9355
- const navContainer = target.closest("[data-ohw-nav-container]");
9356
- if (navContainer && !getNavigationItemAnchor(target)) {
9357
- hoveredNavContainerRef.current = navContainer;
9358
- setHoveredNavContainerRect(navContainer.getBoundingClientRect());
9588
+ const frameContainer = target.closest("[data-ohw-nav-container]") ?? target.closest("[data-ohw-footer-col]");
9589
+ if (frameContainer && !getNavigationItemAnchor(target)) {
9590
+ hoveredNavContainerRef.current = frameContainer;
9591
+ setHoveredNavContainerRect(frameContainer.getBoundingClientRect());
9359
9592
  hoveredItemElRef.current = null;
9360
9593
  setHoveredItemRect(null);
9361
9594
  return;
9362
9595
  }
9363
- if (!target.closest("[data-ohw-nav-container]")) {
9596
+ if (!target.closest("[data-ohw-nav-container], [data-ohw-footer-col]")) {
9364
9597
  hoveredNavContainerRef.current = null;
9365
9598
  setHoveredNavContainerRect(null);
9366
9599
  }
@@ -9402,9 +9635,9 @@ function OhhwellsBridge() {
9402
9635
  };
9403
9636
  const handleMouseOut = (e) => {
9404
9637
  const target = e.target;
9405
- if (target.closest("[data-ohw-nav-container]")) {
9638
+ if (target.closest("[data-ohw-nav-container], [data-ohw-footer-col]")) {
9406
9639
  const related2 = e.relatedTarget instanceof Element ? e.relatedTarget : null;
9407
- if (related2?.closest("[data-ohw-nav-container], [data-ohw-navbar-container-chrome], [data-ohw-navbar-add-button]")) {
9640
+ if (related2?.closest("[data-ohw-nav-container], [data-ohw-footer-col], [data-ohw-navbar-container-chrome], [data-ohw-navbar-add-button]")) {
9408
9641
  return;
9409
9642
  }
9410
9643
  hoveredNavContainerRef.current = null;
@@ -9555,39 +9788,43 @@ function OhhwellsBridge() {
9555
9788
  setHoveredNavContainerRect(null);
9556
9789
  return;
9557
9790
  }
9558
- const navContainer = document.querySelector("[data-ohw-nav-container]");
9559
- if (!navContainer) {
9560
- hoveredNavContainerRef.current = null;
9561
- setHoveredNavContainerRect(null);
9562
- return;
9563
- }
9564
- const containerRect = navContainer.getBoundingClientRect();
9565
- const overContainer = x >= containerRect.left && x <= containerRect.right && y >= containerRect.top && y <= containerRect.bottom;
9566
- if (!overContainer) {
9567
- hoveredNavContainerRef.current = null;
9568
- setHoveredNavContainerRect(null);
9569
- return;
9791
+ const frameContainers = [
9792
+ ...document.querySelectorAll("[data-ohw-nav-container]"),
9793
+ ...document.querySelectorAll("[data-ohw-footer-col]")
9794
+ ];
9795
+ let overFrame = null;
9796
+ for (const container of frameContainers) {
9797
+ const containerRect = container.getBoundingClientRect();
9798
+ const overContainer = x >= containerRect.left && x <= containerRect.right && y >= containerRect.top && y <= containerRect.bottom;
9799
+ if (!overContainer) continue;
9800
+ const navItemHit = Array.from(
9801
+ container.querySelectorAll("[data-ohw-href-key]")
9802
+ ).find((el) => {
9803
+ if (!isNavigationItem(el) || isNavbarButton2(el)) return false;
9804
+ const itemRect = el.getBoundingClientRect();
9805
+ return x >= itemRect.left && x <= itemRect.right && y >= itemRect.top && y <= itemRect.bottom;
9806
+ });
9807
+ if (navItemHit) {
9808
+ hoveredNavContainerRef.current = null;
9809
+ setHoveredNavContainerRect(null);
9810
+ const selected = selectedElRef.current;
9811
+ if (selected !== navItemHit && !selected?.contains(navItemHit)) {
9812
+ clearHrefKeyHover(navItemHit);
9813
+ hoveredItemElRef.current = navItemHit;
9814
+ setHoveredItemRect(navItemHit.getBoundingClientRect());
9815
+ }
9816
+ return;
9817
+ }
9818
+ overFrame = container;
9819
+ break;
9570
9820
  }
9571
- const navItemHit = Array.from(
9572
- navContainer.querySelectorAll("[data-ohw-href-key]")
9573
- ).find((el) => {
9574
- if (!isNavigationItem(el) || isNavbarButton2(el)) return false;
9575
- const itemRect = el.getBoundingClientRect();
9576
- return x >= itemRect.left && x <= itemRect.right && y >= itemRect.top && y <= itemRect.bottom;
9577
- });
9578
- if (navItemHit) {
9821
+ if (!overFrame) {
9579
9822
  hoveredNavContainerRef.current = null;
9580
9823
  setHoveredNavContainerRect(null);
9581
- const selected = selectedElRef.current;
9582
- if (selected !== navItemHit && !selected?.contains(navItemHit)) {
9583
- clearHrefKeyHover(navItemHit);
9584
- hoveredItemElRef.current = navItemHit;
9585
- setHoveredItemRect(navItemHit.getBoundingClientRect());
9586
- }
9587
9824
  return;
9588
9825
  }
9589
- hoveredNavContainerRef.current = navContainer;
9590
- setHoveredNavContainerRect(containerRect);
9826
+ hoveredNavContainerRef.current = overFrame;
9827
+ setHoveredNavContainerRect(overFrame.getBoundingClientRect());
9591
9828
  hoveredItemElRef.current = null;
9592
9829
  setHoveredItemRect(null);
9593
9830
  };
@@ -10028,8 +10265,8 @@ function OhhwellsBridge() {
10028
10265
  if (sibling !== el) sibling.innerHTML = html;
10029
10266
  });
10030
10267
  const timers = autoSaveTimers.current;
10031
- const existing = timers.get(key);
10032
- if (existing) clearTimeout(existing);
10268
+ const existing2 = timers.get(key);
10269
+ if (existing2) clearTimeout(existing2);
10033
10270
  timers.set(key, setTimeout(() => {
10034
10271
  timers.delete(key);
10035
10272
  postToParentRef.current({ type: "ow:change", nodes: [{ key, text: html }] });
@@ -10073,6 +10310,7 @@ function OhhwellsBridge() {
10073
10310
  }
10074
10311
  editContentRef.current = { ...editContentRef.current, ...content };
10075
10312
  reconcileNavbarItemsFromContent(editContentRef.current);
10313
+ syncNavigationDragCursorAttrs();
10076
10314
  reconcileFooterOrderFromContent(editContentRef.current);
10077
10315
  enforceLinkHrefs();
10078
10316
  postToParentRef.current({ type: "ow:hydrate-done" });
@@ -10094,6 +10332,48 @@ function OhhwellsBridge() {
10094
10332
  if (e.data?.type !== "ui:escape") return;
10095
10333
  if (linkPopoverOpenRef.current) {
10096
10334
  setLinkPopoverRef.current(null);
10335
+ return;
10336
+ }
10337
+ if (activeElRef.current) {
10338
+ const hrefCtx = getHrefKeyFromElement(activeElRef.current);
10339
+ const navAnchor = hrefCtx ? getNavigationItemAnchor(hrefCtx.anchor) : null;
10340
+ if (navAnchor) {
10341
+ const el = activeElRef.current;
10342
+ if (originalContentRef.current !== null) {
10343
+ el.innerHTML = originalContentRef.current;
10344
+ }
10345
+ el.removeAttribute("contenteditable");
10346
+ el.removeAttribute("data-ohw-editing");
10347
+ activeElRef.current = null;
10348
+ setMaxBadge(null);
10349
+ setActiveCommands(/* @__PURE__ */ new Set());
10350
+ setToolbarShowEditLink(false);
10351
+ postToParentRef.current({ type: "ow:exit-edit" });
10352
+ reselectNavigationItemRef.current(navAnchor);
10353
+ return;
10354
+ }
10355
+ if (originalContentRef.current !== null) {
10356
+ activeElRef.current.innerHTML = originalContentRef.current;
10357
+ const key = activeElRef.current.dataset.ohwKey;
10358
+ if (key) {
10359
+ postToParentRef.current({ type: "ow:change", nodes: [{ key, text: originalContentRef.current }] });
10360
+ }
10361
+ }
10362
+ deselectRef.current();
10363
+ deactivateRef.current();
10364
+ return;
10365
+ }
10366
+ if (selectedElRef.current) {
10367
+ if (toolbarVariantRef.current === "select-frame") {
10368
+ deselectRef.current();
10369
+ return;
10370
+ }
10371
+ const parent = getNavigationSelectionParent(selectedElRef.current);
10372
+ if (parent && isNavigationContainer(parent)) {
10373
+ selectFrameRef.current(parent);
10374
+ } else {
10375
+ deselectRef.current();
10376
+ }
10097
10377
  }
10098
10378
  };
10099
10379
  window.addEventListener("message", handleUiEscape);
@@ -10118,7 +10398,7 @@ function OhhwellsBridge() {
10118
10398
  return;
10119
10399
  }
10120
10400
  const parent = getNavigationSelectionParent(selectedElRef.current);
10121
- if (parent) {
10401
+ if (parent && isNavigationContainer(parent)) {
10122
10402
  e.preventDefault();
10123
10403
  selectFrameRef.current(parent);
10124
10404
  } else {
@@ -10136,6 +10416,7 @@ function OhhwellsBridge() {
10136
10416
  el2.innerHTML = originalContentRef.current;
10137
10417
  }
10138
10418
  el2.removeAttribute("contenteditable");
10419
+ el2.removeAttribute("data-ohw-editing");
10139
10420
  activeElRef.current = null;
10140
10421
  setMaxBadge(null);
10141
10422
  setActiveCommands(/* @__PURE__ */ new Set());
@@ -10397,15 +10678,15 @@ function OhhwellsBridge() {
10397
10678
  };
10398
10679
  const applyToolbarPos = (rect) => {
10399
10680
  const ps = parentScrollRef.current;
10400
- const approxW = 330;
10681
+ const measuredW = toolbarElRef.current?.offsetWidth || 330;
10401
10682
  if (toolbarElRef.current) {
10402
- const { top, left, transform } = calcToolbarPos(rect, ps, approxW);
10683
+ const { top, left, transform } = calcToolbarPos(rect, ps, measuredW);
10403
10684
  toolbarElRef.current.style.top = `${top}px`;
10404
10685
  toolbarElRef.current.style.left = `${left}px`;
10405
10686
  toolbarElRef.current.style.transform = transform;
10406
10687
  }
10407
10688
  if (glowElRef.current) {
10408
- const GAP = 6;
10689
+ const GAP = SELECTION_CHROME_GAP2;
10409
10690
  glowElRef.current.style.top = `${rect.top - GAP}px`;
10410
10691
  glowElRef.current.style.left = `${rect.left - GAP}px`;
10411
10692
  glowElRef.current.style.width = `${rect.width + GAP * 2}px`;
@@ -10493,6 +10774,11 @@ function OhhwellsBridge() {
10493
10774
  return;
10494
10775
  }
10495
10776
  }
10777
+ const footerColumn = findHoveredNavOrFooterContainer(clientX, clientY);
10778
+ if (footerColumn?.hasAttribute("data-ohw-footer-col")) {
10779
+ selectFrameRef.current(footerColumn);
10780
+ return;
10781
+ }
10496
10782
  deactivateRef.current();
10497
10783
  };
10498
10784
  window.addEventListener("message", handleSave);
@@ -10973,7 +11259,32 @@ function OhhwellsBridge() {
10973
11259
  hoveredNavContainerRect && toolbarVariant !== "select-frame" && !linkPopover && !isItemDragging && !siblingHintRect && siblingHintRects.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
10974
11260
  toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(NavbarContainerChrome, { rect: toolbarRect, onAdd: handleAddTopLevelNavItem }),
10975
11261
  hoveredItemRect && !linkPopover && !hoveredNavContainerRect && !siblingHintRect && siblingHintRects.length === 0 && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
10976
- 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 }),
11262
+ toolbarRect && (toolbarVariant === "link-action" || toolbarVariant === "select-frame") && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
11263
+ ItemInteractionLayer,
11264
+ {
11265
+ rect: isItemDragging && draggedItemRect && footerDragRef.current?.wasSelected ? draggedItemRect : toolbarRect,
11266
+ elRef: glowElRef,
11267
+ state: isItemDragging ? "dragging" : resolveItemInteractionState(toolbarRect, parentScrollRef.current),
11268
+ showHandle: toolbarVariant === "link-action" && Boolean(reorderHrefKey) || toolbarVariant === "select-frame" && isFooterFrameSelection,
11269
+ dragDisabled: reorderDragDisabled,
11270
+ dragHandleLabel: reorderHrefKey ? `Reorder ${reorderHrefKey}` : "Reorder item",
11271
+ onDragHandleDragStart: handleItemDragStart,
11272
+ onDragHandleDragEnd: handleItemDragEnd,
11273
+ onItemPointerDown: handleItemChromePointerDown,
11274
+ onItemClick: handleItemChromeClick,
11275
+ toolbar: toolbarVariant === "link-action" && !isItemDragging ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
11276
+ ItemActionToolbar,
11277
+ {
11278
+ onEditLink: openLinkPopoverForSelected,
11279
+ addItemDisabled: true,
11280
+ editLinkDisabled: false,
11281
+ moreDisabled: true,
11282
+ showAddItem: !selectedIsCta,
11283
+ showMore: !selectedIsCta
11284
+ }
11285
+ ) : void 0
11286
+ }
11287
+ ),
10977
11288
  toolbarRect && toolbarVariant === "rich-text" && /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(import_jsx_runtime25.Fragment, { children: [
10978
11289
  /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(EditGlowChrome, { rect: toolbarRect, elRef: glowElRef, reorderHrefKey, dragDisabled: reorderDragDisabled }),
10979
11290
  /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(FloatingToolbar, { rect: toolbarRect, parentScroll: parentScrollRef.current, elRef: toolbarElRef, onCommand: handleCommand, activeCommands, showEditLink, onEditLink: openLinkPopoverForActive })