@ohhwells/bridge 0.1.116 → 0.1.117

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
@@ -8898,6 +8898,7 @@ function MediaOverlay({
8898
8898
  fadingOut = false,
8899
8899
  selected = false,
8900
8900
  hovered = false,
8901
+ cueOnly = false,
8901
8902
  onFadeOutComplete,
8902
8903
  onReplace,
8903
8904
  onSelect,
@@ -8906,7 +8907,7 @@ function MediaOverlay({
8906
8907
  const { rect } = hover;
8907
8908
  const skeletonRef = React8.useRef(null);
8908
8909
  const isVideo = hover.elementType === "video";
8909
- const showChrome = !selected || hovered;
8910
+ const showChrome = (!selected || hovered) && !cueOnly;
8910
8911
  const autoplay = hover.videoAutoplay ?? true;
8911
8912
  const muted = hover.videoMuted ?? true;
8912
8913
  const probeRef = React8.useRef(null);
@@ -9022,7 +9023,7 @@ function MediaOverlay({
9022
9023
  // parent this needed a whole `ow:click-at` round-trip to re-hit-test inside the iframe;
9023
9024
  // in-document, pointer-events does it natively. The button below opts back in, so
9024
9025
  // Replace still works.
9025
- pointerEvents: hover.hasTextOverlap ? "none" : "auto",
9026
+ pointerEvents: hover.hasTextOverlap || cueOnly ? "none" : "auto",
9026
9027
  // Selected: a firm component ring with no wash, so the image reads as chosen rather
9027
9028
  // than hovered. Hover keeps the existing tinted preview.
9028
9029
  boxShadow: selected ? "inset 0 0 0 2px var(--color-bridge-primary)" : "inset 0 0 0 1.5px var(--color-bridge-primary)",
@@ -9053,7 +9054,7 @@ function MediaOverlay({
9053
9054
  ]
9054
9055
  }
9055
9056
  ),
9056
- replaceMode === "none" ? null : /* @__PURE__ */ jsxs7(
9057
+ replaceMode === "none" || cueOnly ? null : /* @__PURE__ */ jsxs7(
9057
9058
  Button,
9058
9059
  {
9059
9060
  "data-ohw-media-overlay": "",
@@ -9073,7 +9074,7 @@ function MediaOverlay({
9073
9074
  justifyContent: "center"
9074
9075
  } : null,
9075
9076
  // Don't let Replace steal clicks when text (e.g. nav) sits on top of this media.
9076
- pointerEvents: hover.hasTextOverlap ? "none" : "auto"
9077
+ pointerEvents: hover.hasTextOverlap || cueOnly ? "none" : "auto"
9077
9078
  },
9078
9079
  onMouseDown: (e) => e.preventDefault(),
9079
9080
  onClick: (e) => {
@@ -9103,7 +9104,8 @@ var OVERLAY_BUTTON_STYLE2 = {
9103
9104
  };
9104
9105
  function CarouselOverlay({
9105
9106
  hover,
9106
- onEdit
9107
+ onEdit,
9108
+ cueOnly = false
9107
9109
  }) {
9108
9110
  const { rect } = hover;
9109
9111
  return /* @__PURE__ */ jsx16(
@@ -9119,12 +9121,12 @@ function CarouselOverlay({
9119
9121
  width: rect.width,
9120
9122
  height: rect.height,
9121
9123
  zIndex: 2147483646,
9122
- pointerEvents: "auto",
9124
+ pointerEvents: cueOnly ? "none" : "auto",
9123
9125
  boxShadow: "inset 0 0 0 1.5px var(--color-bridge-primary)",
9124
9126
  background: "color-mix(in srgb, var(--color-bridge-primary) 20%, transparent)"
9125
9127
  },
9126
9128
  onClick: () => onEdit(hover.key),
9127
- children: /* @__PURE__ */ jsxs8(
9129
+ children: cueOnly ? null : /* @__PURE__ */ jsxs8(
9128
9130
  Button,
9129
9131
  {
9130
9132
  "data-ohw-carousel-overlay": "",
@@ -9315,7 +9317,8 @@ function ReviewButton({
9315
9317
  }
9316
9318
  function AiSectionOverlay({
9317
9319
  postToParent: postToParent2,
9318
- apiRef
9320
+ apiRef,
9321
+ onSelectionChange
9319
9322
  }) {
9320
9323
  const [selectedId, setSelectedId] = useState6(null);
9321
9324
  const [reviewId, setReviewId] = useState6(null);
@@ -9324,6 +9327,9 @@ function AiSectionOverlay({
9324
9327
  reviewIdRef.current = reviewId;
9325
9328
  const selectedIdRef = useRef4(null);
9326
9329
  selectedIdRef.current = selectedId;
9330
+ useEffect4(() => {
9331
+ onSelectionChange?.(selectedId);
9332
+ }, [selectedId, onSelectionChange]);
9327
9333
  const report = useCallback2(
9328
9334
  (el) => {
9329
9335
  const labelSrc = el ? sectionElementOf(el) : null;
@@ -9341,6 +9347,7 @@ function AiSectionOverlay({
9341
9347
  const sectionEl = inner ? movableUnit(inner) : null;
9342
9348
  const id = sectionEl ? instanceIdOf(sectionEl) || null : null;
9343
9349
  if (id === selectedIdRef.current) return;
9350
+ selectedIdRef.current = id;
9344
9351
  setSelectedId(id);
9345
9352
  if (options?.report !== false) report(sectionEl);
9346
9353
  },
@@ -9361,9 +9368,11 @@ function AiSectionOverlay({
9361
9368
  return sectionEl != null;
9362
9369
  },
9363
9370
  clear: () => {
9371
+ selectedIdRef.current = null;
9364
9372
  setSelectedId(null);
9365
9373
  report(null);
9366
- }
9374
+ },
9375
+ getSelectedId: () => selectedIdRef.current
9367
9376
  };
9368
9377
  return () => {
9369
9378
  apiRef.current = null;
@@ -9372,6 +9381,7 @@ function AiSectionOverlay({
9372
9381
  useEffect4(() => {
9373
9382
  const onMessage = (e) => {
9374
9383
  if (e.data?.type === "ow:ai-select" && e.data.sectionId === null) {
9384
+ selectedIdRef.current = null;
9375
9385
  setSelectedId(null);
9376
9386
  return;
9377
9387
  }
@@ -9422,6 +9432,7 @@ function AiSectionOverlay({
9422
9432
  if (reviewIdRef.current) return;
9423
9433
  if (document.querySelector("[data-ohw-editing]")) return;
9424
9434
  if (selectedIdRef.current) {
9435
+ selectedIdRef.current = null;
9425
9436
  setSelectedId(null);
9426
9437
  report(null);
9427
9438
  }
@@ -16436,6 +16447,19 @@ function isMediaEditable(el) {
16436
16447
  const t = el.dataset.ohwEditable;
16437
16448
  return t === "image" || t === "bg-image" || t === "video";
16438
16449
  }
16450
+ var FULL_BLEED_MIN_COVERAGE = 0.98;
16451
+ function fullBleedSectionIdOf(media) {
16452
+ const section = media.closest("[data-ohw-section]");
16453
+ if (!section) return null;
16454
+ const s = section.getBoundingClientRect();
16455
+ if (s.width <= 0 || s.height <= 0) return null;
16456
+ const m = media.getBoundingClientRect();
16457
+ const overlapX = Math.min(m.right, s.right) - Math.max(m.left, s.left);
16458
+ const overlapY = Math.min(m.bottom, s.bottom) - Math.max(m.top, s.top);
16459
+ if (overlapX <= 0 || overlapY <= 0) return null;
16460
+ if (overlapX * overlapY / (s.width * s.height) < FULL_BLEED_MIN_COVERAGE) return null;
16461
+ return instanceIdOf(movableUnit(section)) || null;
16462
+ }
16439
16463
  function isIconMarkupValue(value) {
16440
16464
  return /^\s*<(svg|img)\b/i.test(value);
16441
16465
  }
@@ -17609,20 +17633,38 @@ function OhhwellsBridge() {
17609
17633
  const hoveredImageHasTextOverlapRef = useRef10(false);
17610
17634
  const dragOverElRef = useRef10(null);
17611
17635
  const [mediaHover, setMediaHover] = useState14(null);
17636
+ const [selectedAiSectionId, setSelectedAiSectionId] = useState14(null);
17637
+ const selectedAiSectionIdRef = useRef10(null);
17638
+ const handleAiSectionSelectionChange = useCallback8((id) => {
17639
+ selectedAiSectionIdRef.current = id;
17640
+ setSelectedAiSectionId(id);
17641
+ }, []);
17612
17642
  const [selectedMedia, setSelectedMedia] = useState14(null);
17613
17643
  const selectedMediaElRef = useRef10(null);
17644
+ const mediaKeyDropTokenRef = useRef10(0);
17614
17645
  const clearMediaSelection = useCallback8(() => {
17615
17646
  const prev = selectedMediaElRef.current;
17616
17647
  selectedMediaElRef.current = null;
17617
17648
  setSelectedMedia(null);
17618
17649
  const sectionEl = prev?.closest("[data-ohw-section]") ?? null;
17619
17650
  if (sectionEl) {
17620
- postToParentRef.current({
17621
- type: "ow:section-selected",
17622
- sectionId: sectionEl.dataset.ohwSection ?? null,
17623
- sectionLabel: sectionEl.dataset.ohwSectionLabel ?? titleCaseSectionId(sectionEl.dataset.ohwSection ?? ""),
17624
- key: null
17625
- });
17651
+ const sectionInstanceId = instanceIdOf(movableUnit(sectionEl)) || null;
17652
+ if (!sectionInstanceId) return;
17653
+ const token = ++mediaKeyDropTokenRef.current;
17654
+ const onQueueDrained = (e) => {
17655
+ if (e.source !== window || e.data?.type !== "ow:media-key-drop" || e.data.token !== token) return;
17656
+ window.removeEventListener("message", onQueueDrained);
17657
+ if (aiSectionApiRef.current?.getSelectedId() !== sectionInstanceId) return;
17658
+ if (selectedMediaElRef.current) return;
17659
+ postToParentRef.current({
17660
+ type: "ow:section-selected",
17661
+ sectionId: sectionEl.dataset.ohwSection ?? null,
17662
+ sectionLabel: sectionEl.dataset.ohwSectionLabel ?? titleCaseSectionId(sectionEl.dataset.ohwSection ?? ""),
17663
+ key: null
17664
+ });
17665
+ };
17666
+ window.addEventListener("message", onQueueDrained);
17667
+ window.postMessage({ type: "ow:media-key-drop", token }, "*");
17626
17668
  }
17627
17669
  }, []);
17628
17670
  const clearMediaSelectionRef = useRef10(clearMediaSelection);
@@ -20064,6 +20106,23 @@ function OhhwellsBridge() {
20064
20106
  if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
20065
20107
  clearMediaSelectionRef.current();
20066
20108
  }
20109
+ if (selectedMediaElRef.current && target.closest("[data-ohw-media-overlay]") && !findClosestButtonLike(target)) {
20110
+ const hit = findImageAtPoint(e.clientX, e.clientY, false);
20111
+ if (hit && hit !== selectedMediaElRef.current) {
20112
+ e.preventDefault();
20113
+ e.stopPropagation();
20114
+ const fullBleedSectionId = fullBleedSectionIdOf(hit);
20115
+ if (fullBleedSectionId && fullBleedSectionId !== selectedAiSectionIdRef.current) {
20116
+ clearMediaSelectionRef.current();
20117
+ deselectRef.current();
20118
+ deactivateRef.current();
20119
+ aiSectionApiRef.current?.selectFromElement(hit);
20120
+ return;
20121
+ }
20122
+ selectMediaElementRef.current(hit);
20123
+ return;
20124
+ }
20125
+ }
20067
20126
  if (isInsideFloatingPanel(target)) return;
20068
20127
  if (target.closest("[data-ohw-form-toolbar]")) return;
20069
20128
  if (target.closest(
@@ -20245,6 +20304,14 @@ function OhhwellsBridge() {
20245
20304
  if (isMediaEditable(editable) && !buttonOnMedia) {
20246
20305
  e.preventDefault();
20247
20306
  e.stopPropagation();
20307
+ const fullBleedSectionId = fullBleedSectionIdOf(editable);
20308
+ if (fullBleedSectionId && fullBleedSectionId !== selectedAiSectionIdRef.current) {
20309
+ clearMediaSelectionRef.current();
20310
+ deselectRef.current();
20311
+ deactivateRef.current();
20312
+ aiSectionApiRef.current?.selectFromElement(editable);
20313
+ return;
20314
+ }
20248
20315
  if (selectedMediaElRef.current === editable) {
20249
20316
  postToParentRef.current({ type: "ow:image-pick", key: editable.dataset.ohwKey ?? "", elementType: editable.dataset.ohwEditable ?? "image" });
20250
20317
  } else {
@@ -20699,6 +20766,7 @@ function OhhwellsBridge() {
20699
20766
  elementType: imgEl.dataset.ohwEditable ?? "image",
20700
20767
  hasTextOverlap,
20701
20768
  isDragOver,
20769
+ fullBleedSectionId: fullBleedSectionIdOf(imgEl),
20702
20770
  ...video ? { videoAutoplay: video.autoplay, videoMuted: video.muted } : {}
20703
20771
  });
20704
20772
  const track = imgEl.closest("[data-ohw-hover-pause]");
@@ -20871,7 +20939,13 @@ function OhhwellsBridge() {
20871
20939
  setHoveredItemRect(null);
20872
20940
  }
20873
20941
  };
20942
+ const syncSelectedMediaPointerRelease = (released) => {
20943
+ setSelectedMedia(
20944
+ (prev) => prev && prev.hasTextOverlap !== released ? { ...prev, hasTextOverlap: released } : prev
20945
+ );
20946
+ };
20874
20947
  const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
20948
+ syncSelectedMediaPointerRelease(true);
20875
20949
  if (linkPopoverOpenRef.current) {
20876
20950
  if (hoveredImageRef.current) {
20877
20951
  hoveredImageRef.current = null;
@@ -21066,6 +21140,7 @@ function OhhwellsBridge() {
21066
21140
  return;
21067
21141
  }
21068
21142
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
21143
+ syncSelectedMediaPointerRelease(false);
21069
21144
  if (imgEl !== hoveredImageRef.current || hoveredImageHasTextOverlapRef.current) {
21070
21145
  hoveredImageRef.current = imgEl;
21071
21146
  showImageHover(imgEl, isDragOver);
@@ -22539,7 +22614,11 @@ function OhhwellsBridge() {
22539
22614
  if (!key) return;
22540
22615
  const r2 = getVisibleRect(container);
22541
22616
  setCarouselHover(
22542
- (prev) => prev && prev.key === key && prev.rect.top === r2.top && prev.rect.left === r2.left && prev.rect.width === r2.width && prev.rect.height === r2.height ? prev : { key, rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height } }
22617
+ (prev) => prev && prev.key === key && prev.rect.top === r2.top && prev.rect.left === r2.left && prev.rect.width === r2.width && prev.rect.height === r2.height ? prev : {
22618
+ key,
22619
+ rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height },
22620
+ fullBleedSectionId: fullBleedSectionIdOf(container)
22621
+ }
22543
22622
  );
22544
22623
  };
22545
22624
  const applyToolbarPos = (rect) => {
@@ -23001,7 +23080,7 @@ function OhhwellsBridge() {
23001
23080
  postToParent2({
23002
23081
  type: "ow:ready",
23003
23082
  version: "1",
23004
- bridgeVersion: "0.1.115",
23083
+ bridgeVersion: "0.1.116",
23005
23084
  path: pathname,
23006
23085
  nodes: collectEditableNodes(editContentRef.current),
23007
23086
  sections
@@ -23468,7 +23547,14 @@ function OhhwellsBridge() {
23468
23547
  bridgeRoot ? createPortal3(
23469
23548
  /* @__PURE__ */ jsxs20(Fragment8, { children: [
23470
23549
  /* @__PURE__ */ jsx33("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
23471
- isEditMode && /* @__PURE__ */ jsx33(AiSectionOverlay, { postToParent: postToParent2, apiRef: aiSectionApiRef }),
23550
+ isEditMode && /* @__PURE__ */ jsx33(
23551
+ AiSectionOverlay,
23552
+ {
23553
+ postToParent: postToParent2,
23554
+ apiRef: aiSectionApiRef,
23555
+ onSelectionChange: handleAiSectionSelectionChange
23556
+ }
23557
+ ),
23472
23558
  isSectionDragging && sectionDropSlots.map((slot, i) => /* @__PURE__ */ jsx33(
23473
23559
  "div",
23474
23560
  {
@@ -23501,6 +23587,7 @@ function OhhwellsBridge() {
23501
23587
  {
23502
23588
  hover: mediaHover,
23503
23589
  isUploading: false,
23590
+ cueOnly: !mediaHover.isDragOver && !!mediaHover.fullBleedSectionId && mediaHover.fullBleedSectionId !== selectedAiSectionId,
23504
23591
  onReplace: handleMediaReplace,
23505
23592
  onSelect: handleMediaSelect,
23506
23593
  onVideoSettingsChange: handleVideoSettingsChange
@@ -23518,7 +23605,14 @@ function OhhwellsBridge() {
23518
23605
  onVideoSettingsChange: handleVideoSettingsChange
23519
23606
  }
23520
23607
  ),
23521
- carouselHover && !linkPopover && !isItemDragging && /* @__PURE__ */ jsx33(CarouselOverlay, { hover: carouselHover, onEdit: handleEditCarousel }),
23608
+ carouselHover && !linkPopover && !isItemDragging && /* @__PURE__ */ jsx33(
23609
+ CarouselOverlay,
23610
+ {
23611
+ hover: carouselHover,
23612
+ onEdit: handleEditCarousel,
23613
+ cueOnly: !!carouselHover.fullBleedSectionId && carouselHover.fullBleedSectionId !== selectedAiSectionId
23614
+ }
23615
+ ),
23522
23616
  siblingHintRect && !linkPopover && !isItemDragging && siblingHintRects.length === 0 && /* @__PURE__ */ jsx33(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
23523
23617
  siblingHintRects.length > 0 && !linkPopover && !isItemDragging && siblingHintRects.map((rect, i) => /* @__PURE__ */ jsx33(ItemInteractionLayer, { rect, state: "sibling-hint" }, `sibling-hint-${i}`)),
23524
23618
  isItemDragging && draggedItemRect && selectedElRef.current !== footerDragRef.current?.draggedEl && selectedElRef.current !== navDragRef.current?.draggedEl && /* @__PURE__ */ jsx33(ItemInteractionLayer, { rect: draggedItemRect, state: "dragging" }),