@ohhwells/bridge 0.1.52-next.132 → 0.1.52-next.133

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/README.md CHANGED
@@ -194,12 +194,23 @@ For links whose **destination URL** is edited in the canvas, use a plain `<a>` w
194
194
  </a>
195
195
  ```
196
196
 
197
+ Section / body CTAs use the same two-phase select + Edit-link toolbar with `data-ohw-role="button"` (preferred). `navbar-button` remains supported as an alias for nav header CTAs:
198
+
199
+ ```tsx
200
+ <a href="/contact" data-ohw-href-key="hero-cta-href" data-ohw-role="button" data-ohw-drag-disabled="true">
201
+ <span data-ohw-editable="text" data-ohw-key="hero-cta-label">
202
+ Get started
203
+ </span>
204
+ </a>
205
+ ```
206
+
197
207
  | Attribute | Purpose |
198
208
  |-----------|---------|
199
209
  | `data-ohw-href-key` | Storage key for the link destination |
200
210
  | `href` | Default URL for SSR / first paint |
201
211
  | `data-ohw-key` + `data-ohw-editable` on inner span | Editable label text |
202
- | `data-ohw-role="navbar-button"` | Navbar CTA — two-phase select + link-action toolbar |
212
+ | `data-ohw-role="button"` | Section CTA — two-phase select + link-action toolbar (no reorder/More) |
213
+ | `data-ohw-role="navbar-button"` | Alias of `button` for navbar / header CTAs |
203
214
 
204
215
  The bridge persists and restores `href` values (including after React re-renders). No template-side wrapper component is required.
205
216
 
package/dist/index.cjs CHANGED
@@ -84,6 +84,9 @@ function getStoredLinkHref(key) {
84
84
  function clearStoredLinkHref(key) {
85
85
  linkHrefStore.delete(key);
86
86
  }
87
+ function listStoredLinkHrefs() {
88
+ return Array.from(linkHrefStore.entries());
89
+ }
87
90
  function enforceLinkHrefs() {
88
91
  document.querySelectorAll("[data-ohw-href-key]").forEach((el) => {
89
92
  const key = el.getAttribute("data-ohw-href-key") ?? "";
@@ -5646,6 +5649,42 @@ function AiSectionOverlay({
5646
5649
  var import_react_dom3 = require("react-dom");
5647
5650
  var import_navigation2 = require("next/navigation");
5648
5651
 
5652
+ // src/lib/cta-button.ts
5653
+ function isCtaButton(el) {
5654
+ return Boolean(
5655
+ el.closest('[data-ohw-role="navbar-button"], [data-ohw-role="button"]')
5656
+ );
5657
+ }
5658
+ var CTA_BUTTON_SELECTOR = '[data-ohw-role="navbar-button"], [data-ohw-role="button"]';
5659
+
5660
+ // src/lib/media-overlay-guards.ts
5661
+ var BRIDGE_CHROME_SELECTOR = [
5662
+ "[data-ohw-toolbar]",
5663
+ "[data-ohw-item-toolbar-anchor]",
5664
+ "[data-ohw-drag-handle-container]",
5665
+ '[data-slot="drag-handle"]',
5666
+ "[data-ohw-more-menu]",
5667
+ '[data-slot="dropdown-menu-content"]'
5668
+ ].join(", ");
5669
+ function containsPoint(el, x, y) {
5670
+ const r2 = el.getBoundingClientRect();
5671
+ if (r2.width <= 0 || r2.height <= 0) return false;
5672
+ return x >= r2.left && x <= r2.right && y >= r2.top && y <= r2.bottom;
5673
+ }
5674
+ function isPointOverBridgeChrome(x, y) {
5675
+ for (const el of document.querySelectorAll(BRIDGE_CHROME_SELECTOR)) {
5676
+ if (containsPoint(el, x, y)) return true;
5677
+ }
5678
+ return false;
5679
+ }
5680
+ function findCtaButtonAtPoint(x, y, media) {
5681
+ const hits = Array.from(
5682
+ document.querySelectorAll(CTA_BUTTON_SELECTOR)
5683
+ ).filter((el) => containsPoint(el, x, y) && !(media && el.contains(media)));
5684
+ if (hits.length === 0) return null;
5685
+ return hits.reduce((best, el) => best.contains(el) ? el : best);
5686
+ }
5687
+
5649
5688
  // src/lib/session-search.ts
5650
5689
  var STORAGE_KEY = "ohw-preserved-search";
5651
5690
  function readPreservedSearch() {
@@ -7239,12 +7278,9 @@ function getLinkHref(el) {
7239
7278
  }
7240
7279
  return el.getAttribute("href") ?? "";
7241
7280
  }
7242
- function isNavbarButton(el) {
7243
- return el.hasAttribute("data-ohw-role") && el.getAttribute("data-ohw-role") === "navbar-button";
7244
- }
7245
7281
  function isNavbarLinkItem(el) {
7246
7282
  if (!el.matches("[data-ohw-href-key]")) return false;
7247
- if (isNavbarButton(el)) return false;
7283
+ if (isCtaButton(el)) return false;
7248
7284
  if (!el.querySelector('[data-ohw-editable="text"]')) return false;
7249
7285
  return Boolean(el.closest("nav, [data-ohw-nav-container], [data-ohw-nav-drawer]"));
7250
7286
  }
@@ -7325,7 +7361,7 @@ function buildNavLink(index, href, label, template) {
7325
7361
  }
7326
7362
  function insertNavLinkInContainer(container, anchor, afterHrefKey) {
7327
7363
  if (!afterHrefKey) {
7328
- const cta = container.querySelector('[data-ohw-role="navbar-button"]');
7364
+ const cta = container.querySelector(CTA_BUTTON_SELECTOR);
7329
7365
  if (cta && cta.parentElement === container) {
7330
7366
  container.insertBefore(anchor, cta);
7331
7367
  return;
@@ -9316,8 +9352,7 @@ function useNavItemDrag({
9316
9352
  setSiblingHintRects,
9317
9353
  setToolbarRect,
9318
9354
  getNavigationItemAnchor: getNavigationItemAnchor2,
9319
- isDragHandleDisabled: isDragHandleDisabled2,
9320
- isNavbarButton: isNavbarButton3
9355
+ isDragHandleDisabled: isDragHandleDisabled2
9321
9356
  }) {
9322
9357
  const navDragRef = (0, import_react11.useRef)(null);
9323
9358
  const [navDropSlots, setNavDropSlots] = (0, import_react11.useState)([]);
@@ -9464,7 +9499,7 @@ function useNavItemDrag({
9464
9499
  if (footerDragRef.current) return false;
9465
9500
  const hrefKey = anchor.getAttribute("data-ohw-href-key");
9466
9501
  if (!hrefKey || !isNavbarHrefKey(hrefKey)) return false;
9467
- if (isNavbarButton3(anchor) || isDragHandleDisabled2(anchor)) return false;
9502
+ if (isCtaButton(anchor) || isDragHandleDisabled2(anchor)) return false;
9468
9503
  beginNavDrag({
9469
9504
  hrefKey,
9470
9505
  wasSelected,
@@ -9475,7 +9510,7 @@ function useNavItemDrag({
9475
9510
  });
9476
9511
  return true;
9477
9512
  },
9478
- [beginNavDrag, footerDragRef, isDragHandleDisabled2, isNavbarButton3]
9513
+ [beginNavDrag, footerDragRef, isDragHandleDisabled2, isCtaButton]
9479
9514
  );
9480
9515
  const onNavDragOver = (0, import_react11.useCallback)(
9481
9516
  (e) => {
@@ -9511,7 +9546,7 @@ function useNavItemDrag({
9511
9546
  const anchor = getNavigationItemAnchor2(target);
9512
9547
  const hrefKey = anchor?.getAttribute("data-ohw-href-key") ?? null;
9513
9548
  if (!anchor || !isNavbarHrefKey(hrefKey)) return;
9514
- if (isNavbarButton3(anchor) || isDragHandleDisabled2(anchor)) return;
9549
+ if (isCtaButton(anchor) || isDragHandleDisabled2(anchor)) return;
9515
9550
  navPointerDragRef.current = {
9516
9551
  el: anchor,
9517
9552
  startX: e.clientX,
@@ -9608,7 +9643,7 @@ function useNavItemDrag({
9608
9643
  getNavigationItemAnchor2,
9609
9644
  isDragHandleDisabled2,
9610
9645
  isEditMode,
9611
- isNavbarButton3,
9646
+ isCtaButton,
9612
9647
  linkPopoverOpenRef,
9613
9648
  selectedElRef,
9614
9649
  setLinkPopover,
@@ -9618,7 +9653,7 @@ function useNavItemDrag({
9618
9653
  (selected, clientX, clientY, pointerId) => {
9619
9654
  const hrefKey = selected.getAttribute("data-ohw-href-key");
9620
9655
  if (!hrefKey || !isNavbarHrefKey(hrefKey)) return false;
9621
- if (isNavbarButton3(selected) || isDragHandleDisabled2(selected)) return false;
9656
+ if (isCtaButton(selected) || isDragHandleDisabled2(selected)) return false;
9622
9657
  navPointerDragRef.current = {
9623
9658
  el: selected,
9624
9659
  startX: clientX,
@@ -9629,7 +9664,7 @@ function useNavItemDrag({
9629
9664
  };
9630
9665
  return true;
9631
9666
  },
9632
- [isDragHandleDisabled2, isNavbarButton3]
9667
+ [isDragHandleDisabled2, isCtaButton]
9633
9668
  );
9634
9669
  return {
9635
9670
  navDragRef,
@@ -9831,7 +9866,10 @@ function collectEditableNodes(extraContent, root = document) {
9831
9866
  if (!key) return;
9832
9867
  nodes.push({ key, type: "link", text: getLinkHref3(el) });
9833
9868
  });
9834
- if (extraContent && !isScoped) {
9869
+ for (const [key, href] of listStoredLinkHrefs()) {
9870
+ nodes.push({ key, type: "link", text: href });
9871
+ }
9872
+ if (extraContent) {
9835
9873
  for (const key of [NAV_ORDER_KEY, FOOTER_ORDER_KEY, NAV_COUNT_KEY]) {
9836
9874
  const text = extraContent[key];
9837
9875
  if (typeof text === "string" && text.length > 0) {
@@ -10565,9 +10603,6 @@ function getHrefKeyFromElement(el) {
10565
10603
  if (!key) return null;
10566
10604
  return { anchor, key };
10567
10605
  }
10568
- function isNavbarButton2(el) {
10569
- return Boolean(el.closest('[data-ohw-role="navbar-button"]'));
10570
- }
10571
10606
  function isNavDropdownPanelOpen(childrenRoot) {
10572
10607
  if (childrenRoot.closest("[data-ohw-nav-drawer]")) return true;
10573
10608
  const group = childrenRoot.closest("[data-ohw-nav-group]");
@@ -10620,7 +10655,7 @@ function listNavigationItems() {
10620
10655
  ).filter((el) => isNavigationItem2(el));
10621
10656
  }
10622
10657
  function getNavigationItemReorderState(anchor) {
10623
- if (isNavbarButton2(anchor)) return { key: null, disabled: false };
10658
+ if (isCtaButton(anchor)) return { key: null, disabled: false };
10624
10659
  return getReorderHandleStateFromAnchor(anchor);
10625
10660
  }
10626
10661
  function getReorderHandleState(el) {
@@ -10722,7 +10757,7 @@ function findHoveredNavOrFooterContainer(x, y) {
10722
10757
  }
10723
10758
  function isPointOverNavItem(container, x, y) {
10724
10759
  return Array.from(container.querySelectorAll("[data-ohw-href-key]")).some((el) => {
10725
- if (!isNavigationItem2(el) || isNavbarButton2(el)) return false;
10760
+ if (!isNavigationItem2(el) || isCtaButton(el)) return false;
10726
10761
  if (!isNavItemPointerTarget(el)) return false;
10727
10762
  const itemRect = el.getBoundingClientRect();
10728
10763
  return x >= itemRect.left && x <= itemRect.right && y >= itemRect.top && y <= itemRect.bottom;
@@ -10736,7 +10771,7 @@ function isPointOverFooterColumn(x, y) {
10736
10771
  }
10737
10772
  function resolveNavContainerSelectionTarget(target, clientX, clientY) {
10738
10773
  if (getNavigationItemAnchor(target)) return null;
10739
- if (target.closest('[data-ohw-role="navbar-button"]')) return null;
10774
+ if (target.closest(CTA_BUTTON_SELECTOR)) return null;
10740
10775
  const plainLink = target.closest("a");
10741
10776
  if (plainLink && !plainLink.hasAttribute("data-ohw-href-key") && !plainLink.closest("[data-ohw-nav-container]")) {
10742
10777
  return null;
@@ -11543,8 +11578,7 @@ function OhhwellsBridge() {
11543
11578
  setSiblingHintRects,
11544
11579
  setToolbarRect,
11545
11580
  getNavigationItemAnchor,
11546
- isDragHandleDisabled,
11547
- isNavbarButton: isNavbarButton2
11581
+ isDragHandleDisabled
11548
11582
  });
11549
11583
  const bumpLinkPopoverGrace = () => {
11550
11584
  linkPopoverGraceUntilRef.current = Date.now() + 350;
@@ -11804,7 +11838,7 @@ function OhhwellsBridge() {
11804
11838
  selectedHrefKeyRef.current = navAnchor.getAttribute("data-ohw-href-key");
11805
11839
  selectedFooterColAttrRef.current = null;
11806
11840
  markSelected(navAnchor);
11807
- setSelectedIsCta(isNavbarButton2(navAnchor));
11841
+ setSelectedIsCta(isCtaButton(navAnchor));
11808
11842
  const isDropdownTrigger = !isNestedNavChild(navAnchor) && (navItemHasDropdownChildren(navAnchor) || navItemOwnsDropdownPanel(navAnchor));
11809
11843
  if (isNestedNavChild(navAnchor)) {
11810
11844
  setNavGroupForceOpen(navAnchor, true);
@@ -12325,7 +12359,7 @@ function OhhwellsBridge() {
12325
12359
  selectedHrefKeyRef.current = anchor.getAttribute("data-ohw-href-key");
12326
12360
  selectedFooterColAttrRef.current = null;
12327
12361
  markSelected(anchor);
12328
- setSelectedIsCta(isNavbarButton2(anchor));
12362
+ setSelectedIsCta(isCtaButton(anchor));
12329
12363
  clearHrefKeyHover(anchor);
12330
12364
  const isDropdownTrigger = !isNestedNavChild(anchor) && (navItemHasDropdownChildren(anchor) || navItemOwnsDropdownPanel(anchor));
12331
12365
  if (isNestedNavChild(anchor)) {
@@ -13408,7 +13442,7 @@ function OhhwellsBridge() {
13408
13442
  const navItemHit = Array.from(
13409
13443
  container.querySelectorAll("[data-ohw-href-key]")
13410
13444
  ).filter((el) => {
13411
- if (!isNavigationItem2(el) || isNavbarButton2(el)) return false;
13445
+ if (!isNavigationItem2(el) || isCtaButton(el)) return false;
13412
13446
  if (!isNavItemPointerTarget(el)) return false;
13413
13447
  const itemRect = el.getBoundingClientRect();
13414
13448
  return x >= itemRect.left && x <= itemRect.right && y >= itemRect.top && y <= itemRect.bottom;
@@ -13550,6 +13584,22 @@ function OhhwellsBridge() {
13550
13584
  }
13551
13585
  return;
13552
13586
  }
13587
+ if (!isDragOver && isPointOverBridgeChrome(x2, y2)) {
13588
+ dismissImageHover();
13589
+ return;
13590
+ }
13591
+ const ctaHit = isDragOver ? null : findCtaButtonAtPoint(x2, y2, imgEl);
13592
+ if (ctaHit) {
13593
+ dismissImageHover();
13594
+ document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
13595
+ const selected = selectedElRef.current;
13596
+ if (selected !== ctaHit && !selected?.contains(ctaHit)) {
13597
+ clearHrefKeyHover(ctaHit);
13598
+ hoveredItemElRef.current = ctaHit;
13599
+ setHoveredItemRect(ctaHit.getBoundingClientRect());
13600
+ }
13601
+ return;
13602
+ }
13553
13603
  const topEl = document.elementFromPoint(x2, y2);
13554
13604
  if (topEl?.closest('[data-ohw-toolbar], [data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-drag-handle-container], [data-slot="drag-handle"], [data-ohw-more-menu], [data-slot="dropdown-menu-content"], [data-slot="dropdown-menu-item"]')) {
13555
13605
  if (hoveredImageRef.current) {
@@ -13673,7 +13723,7 @@ function OhhwellsBridge() {
13673
13723
  const navLinkHit = Array.from(
13674
13724
  document.querySelectorAll("nav [data-ohw-href-key], footer [data-ohw-href-key]")
13675
13725
  ).find((el) => {
13676
- if (!isNavigationItem2(el) || isNavbarButton2(el)) return false;
13726
+ if (!isNavigationItem2(el) || isCtaButton(el)) return false;
13677
13727
  if (!isNavItemPointerTarget(el)) return false;
13678
13728
  const er = el.getBoundingClientRect();
13679
13729
  return x2 >= er.left && x2 <= er.right && y2 >= er.top && y2 <= er.bottom;
@@ -14547,7 +14597,7 @@ function OhhwellsBridge() {
14547
14597
  if (navRoot && navContainer) {
14548
14598
  const rr = navRoot.getBoundingClientRect();
14549
14599
  if (clientX >= rr.left && clientX <= rr.right && clientY >= rr.top && clientY <= rr.bottom && !isPointOverNavItem(navContainer, clientX, clientY)) {
14550
- const book = navRoot.querySelector('[data-ohw-role="navbar-button"]');
14600
+ const book = navRoot.querySelector(CTA_BUTTON_SELECTOR);
14551
14601
  if (book) {
14552
14602
  const br = book.getBoundingClientRect();
14553
14603
  if (clientX >= br.left && clientX <= br.right && clientY >= br.top && clientY <= br.bottom) {
@@ -15145,6 +15195,7 @@ function OhhwellsBridge() {
15145
15195
  return;
15146
15196
  }
15147
15197
  const { key } = session;
15198
+ setStoredLinkHref(key, target);
15148
15199
  applyLinkByKey(key, target);
15149
15200
  editContentRef.current = { ...editContentRef.current, [key]: target };
15150
15201
  postToParent2({ type: "ow:change", nodes: [{ key, text: target }] });