@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.cjs CHANGED
@@ -8971,6 +8971,7 @@ function MediaOverlay({
8971
8971
  fadingOut = false,
8972
8972
  selected = false,
8973
8973
  hovered = false,
8974
+ cueOnly = false,
8974
8975
  onFadeOutComplete,
8975
8976
  onReplace,
8976
8977
  onSelect,
@@ -8979,7 +8980,7 @@ function MediaOverlay({
8979
8980
  const { rect } = hover;
8980
8981
  const skeletonRef = React8.useRef(null);
8981
8982
  const isVideo = hover.elementType === "video";
8982
- const showChrome = !selected || hovered;
8983
+ const showChrome = (!selected || hovered) && !cueOnly;
8983
8984
  const autoplay = hover.videoAutoplay ?? true;
8984
8985
  const muted = hover.videoMuted ?? true;
8985
8986
  const probeRef = React8.useRef(null);
@@ -9095,7 +9096,7 @@ function MediaOverlay({
9095
9096
  // parent this needed a whole `ow:click-at` round-trip to re-hit-test inside the iframe;
9096
9097
  // in-document, pointer-events does it natively. The button below opts back in, so
9097
9098
  // Replace still works.
9098
- pointerEvents: hover.hasTextOverlap ? "none" : "auto",
9099
+ pointerEvents: hover.hasTextOverlap || cueOnly ? "none" : "auto",
9099
9100
  // Selected: a firm component ring with no wash, so the image reads as chosen rather
9100
9101
  // than hovered. Hover keeps the existing tinted preview.
9101
9102
  boxShadow: selected ? "inset 0 0 0 2px var(--color-bridge-primary)" : "inset 0 0 0 1.5px var(--color-bridge-primary)",
@@ -9126,7 +9127,7 @@ function MediaOverlay({
9126
9127
  ]
9127
9128
  }
9128
9129
  ),
9129
- replaceMode === "none" ? null : /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
9130
+ replaceMode === "none" || cueOnly ? null : /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
9130
9131
  Button,
9131
9132
  {
9132
9133
  "data-ohw-media-overlay": "",
@@ -9146,7 +9147,7 @@ function MediaOverlay({
9146
9147
  justifyContent: "center"
9147
9148
  } : null,
9148
9149
  // Don't let Replace steal clicks when text (e.g. nav) sits on top of this media.
9149
- pointerEvents: hover.hasTextOverlap ? "none" : "auto"
9150
+ pointerEvents: hover.hasTextOverlap || cueOnly ? "none" : "auto"
9150
9151
  },
9151
9152
  onMouseDown: (e) => e.preventDefault(),
9152
9153
  onClick: (e) => {
@@ -9176,7 +9177,8 @@ var OVERLAY_BUTTON_STYLE2 = {
9176
9177
  };
9177
9178
  function CarouselOverlay({
9178
9179
  hover,
9179
- onEdit
9180
+ onEdit,
9181
+ cueOnly = false
9180
9182
  }) {
9181
9183
  const { rect } = hover;
9182
9184
  return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
@@ -9192,12 +9194,12 @@ function CarouselOverlay({
9192
9194
  width: rect.width,
9193
9195
  height: rect.height,
9194
9196
  zIndex: 2147483646,
9195
- pointerEvents: "auto",
9197
+ pointerEvents: cueOnly ? "none" : "auto",
9196
9198
  boxShadow: "inset 0 0 0 1.5px var(--color-bridge-primary)",
9197
9199
  background: "color-mix(in srgb, var(--color-bridge-primary) 20%, transparent)"
9198
9200
  },
9199
9201
  onClick: () => onEdit(hover.key),
9200
- children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
9202
+ children: cueOnly ? null : /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
9201
9203
  Button,
9202
9204
  {
9203
9205
  "data-ohw-carousel-overlay": "",
@@ -9388,7 +9390,8 @@ function ReviewButton({
9388
9390
  }
9389
9391
  function AiSectionOverlay({
9390
9392
  postToParent: postToParent2,
9391
- apiRef
9393
+ apiRef,
9394
+ onSelectionChange
9392
9395
  }) {
9393
9396
  const [selectedId, setSelectedId] = (0, import_react8.useState)(null);
9394
9397
  const [reviewId, setReviewId] = (0, import_react8.useState)(null);
@@ -9397,6 +9400,9 @@ function AiSectionOverlay({
9397
9400
  reviewIdRef.current = reviewId;
9398
9401
  const selectedIdRef = (0, import_react8.useRef)(null);
9399
9402
  selectedIdRef.current = selectedId;
9403
+ (0, import_react8.useEffect)(() => {
9404
+ onSelectionChange?.(selectedId);
9405
+ }, [selectedId, onSelectionChange]);
9400
9406
  const report = (0, import_react8.useCallback)(
9401
9407
  (el) => {
9402
9408
  const labelSrc = el ? sectionElementOf(el) : null;
@@ -9414,6 +9420,7 @@ function AiSectionOverlay({
9414
9420
  const sectionEl = inner ? movableUnit(inner) : null;
9415
9421
  const id = sectionEl ? instanceIdOf(sectionEl) || null : null;
9416
9422
  if (id === selectedIdRef.current) return;
9423
+ selectedIdRef.current = id;
9417
9424
  setSelectedId(id);
9418
9425
  if (options?.report !== false) report(sectionEl);
9419
9426
  },
@@ -9434,9 +9441,11 @@ function AiSectionOverlay({
9434
9441
  return sectionEl != null;
9435
9442
  },
9436
9443
  clear: () => {
9444
+ selectedIdRef.current = null;
9437
9445
  setSelectedId(null);
9438
9446
  report(null);
9439
- }
9447
+ },
9448
+ getSelectedId: () => selectedIdRef.current
9440
9449
  };
9441
9450
  return () => {
9442
9451
  apiRef.current = null;
@@ -9445,6 +9454,7 @@ function AiSectionOverlay({
9445
9454
  (0, import_react8.useEffect)(() => {
9446
9455
  const onMessage = (e) => {
9447
9456
  if (e.data?.type === "ow:ai-select" && e.data.sectionId === null) {
9457
+ selectedIdRef.current = null;
9448
9458
  setSelectedId(null);
9449
9459
  return;
9450
9460
  }
@@ -9495,6 +9505,7 @@ function AiSectionOverlay({
9495
9505
  if (reviewIdRef.current) return;
9496
9506
  if (document.querySelector("[data-ohw-editing]")) return;
9497
9507
  if (selectedIdRef.current) {
9508
+ selectedIdRef.current = null;
9498
9509
  setSelectedId(null);
9499
9510
  report(null);
9500
9511
  }
@@ -16503,6 +16514,19 @@ function isMediaEditable(el) {
16503
16514
  const t = el.dataset.ohwEditable;
16504
16515
  return t === "image" || t === "bg-image" || t === "video";
16505
16516
  }
16517
+ var FULL_BLEED_MIN_COVERAGE = 0.98;
16518
+ function fullBleedSectionIdOf(media) {
16519
+ const section = media.closest("[data-ohw-section]");
16520
+ if (!section) return null;
16521
+ const s = section.getBoundingClientRect();
16522
+ if (s.width <= 0 || s.height <= 0) return null;
16523
+ const m = media.getBoundingClientRect();
16524
+ const overlapX = Math.min(m.right, s.right) - Math.max(m.left, s.left);
16525
+ const overlapY = Math.min(m.bottom, s.bottom) - Math.max(m.top, s.top);
16526
+ if (overlapX <= 0 || overlapY <= 0) return null;
16527
+ if (overlapX * overlapY / (s.width * s.height) < FULL_BLEED_MIN_COVERAGE) return null;
16528
+ return instanceIdOf(movableUnit(section)) || null;
16529
+ }
16506
16530
  function isIconMarkupValue(value) {
16507
16531
  return /^\s*<(svg|img)\b/i.test(value);
16508
16532
  }
@@ -17676,20 +17700,38 @@ function OhhwellsBridge() {
17676
17700
  const hoveredImageHasTextOverlapRef = (0, import_react17.useRef)(false);
17677
17701
  const dragOverElRef = (0, import_react17.useRef)(null);
17678
17702
  const [mediaHover, setMediaHover] = (0, import_react17.useState)(null);
17703
+ const [selectedAiSectionId, setSelectedAiSectionId] = (0, import_react17.useState)(null);
17704
+ const selectedAiSectionIdRef = (0, import_react17.useRef)(null);
17705
+ const handleAiSectionSelectionChange = (0, import_react17.useCallback)((id) => {
17706
+ selectedAiSectionIdRef.current = id;
17707
+ setSelectedAiSectionId(id);
17708
+ }, []);
17679
17709
  const [selectedMedia, setSelectedMedia] = (0, import_react17.useState)(null);
17680
17710
  const selectedMediaElRef = (0, import_react17.useRef)(null);
17711
+ const mediaKeyDropTokenRef = (0, import_react17.useRef)(0);
17681
17712
  const clearMediaSelection = (0, import_react17.useCallback)(() => {
17682
17713
  const prev = selectedMediaElRef.current;
17683
17714
  selectedMediaElRef.current = null;
17684
17715
  setSelectedMedia(null);
17685
17716
  const sectionEl = prev?.closest("[data-ohw-section]") ?? null;
17686
17717
  if (sectionEl) {
17687
- postToParentRef.current({
17688
- type: "ow:section-selected",
17689
- sectionId: sectionEl.dataset.ohwSection ?? null,
17690
- sectionLabel: sectionEl.dataset.ohwSectionLabel ?? titleCaseSectionId(sectionEl.dataset.ohwSection ?? ""),
17691
- key: null
17692
- });
17718
+ const sectionInstanceId = instanceIdOf(movableUnit(sectionEl)) || null;
17719
+ if (!sectionInstanceId) return;
17720
+ const token = ++mediaKeyDropTokenRef.current;
17721
+ const onQueueDrained = (e) => {
17722
+ if (e.source !== window || e.data?.type !== "ow:media-key-drop" || e.data.token !== token) return;
17723
+ window.removeEventListener("message", onQueueDrained);
17724
+ if (aiSectionApiRef.current?.getSelectedId() !== sectionInstanceId) return;
17725
+ if (selectedMediaElRef.current) return;
17726
+ postToParentRef.current({
17727
+ type: "ow:section-selected",
17728
+ sectionId: sectionEl.dataset.ohwSection ?? null,
17729
+ sectionLabel: sectionEl.dataset.ohwSectionLabel ?? titleCaseSectionId(sectionEl.dataset.ohwSection ?? ""),
17730
+ key: null
17731
+ });
17732
+ };
17733
+ window.addEventListener("message", onQueueDrained);
17734
+ window.postMessage({ type: "ow:media-key-drop", token }, "*");
17693
17735
  }
17694
17736
  }, []);
17695
17737
  const clearMediaSelectionRef = (0, import_react17.useRef)(clearMediaSelection);
@@ -20131,6 +20173,23 @@ function OhhwellsBridge() {
20131
20173
  if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
20132
20174
  clearMediaSelectionRef.current();
20133
20175
  }
20176
+ if (selectedMediaElRef.current && target.closest("[data-ohw-media-overlay]") && !findClosestButtonLike(target)) {
20177
+ const hit = findImageAtPoint(e.clientX, e.clientY, false);
20178
+ if (hit && hit !== selectedMediaElRef.current) {
20179
+ e.preventDefault();
20180
+ e.stopPropagation();
20181
+ const fullBleedSectionId = fullBleedSectionIdOf(hit);
20182
+ if (fullBleedSectionId && fullBleedSectionId !== selectedAiSectionIdRef.current) {
20183
+ clearMediaSelectionRef.current();
20184
+ deselectRef.current();
20185
+ deactivateRef.current();
20186
+ aiSectionApiRef.current?.selectFromElement(hit);
20187
+ return;
20188
+ }
20189
+ selectMediaElementRef.current(hit);
20190
+ return;
20191
+ }
20192
+ }
20134
20193
  if (isInsideFloatingPanel(target)) return;
20135
20194
  if (target.closest("[data-ohw-form-toolbar]")) return;
20136
20195
  if (target.closest(
@@ -20312,6 +20371,14 @@ function OhhwellsBridge() {
20312
20371
  if (isMediaEditable(editable) && !buttonOnMedia) {
20313
20372
  e.preventDefault();
20314
20373
  e.stopPropagation();
20374
+ const fullBleedSectionId = fullBleedSectionIdOf(editable);
20375
+ if (fullBleedSectionId && fullBleedSectionId !== selectedAiSectionIdRef.current) {
20376
+ clearMediaSelectionRef.current();
20377
+ deselectRef.current();
20378
+ deactivateRef.current();
20379
+ aiSectionApiRef.current?.selectFromElement(editable);
20380
+ return;
20381
+ }
20315
20382
  if (selectedMediaElRef.current === editable) {
20316
20383
  postToParentRef.current({ type: "ow:image-pick", key: editable.dataset.ohwKey ?? "", elementType: editable.dataset.ohwEditable ?? "image" });
20317
20384
  } else {
@@ -20766,6 +20833,7 @@ function OhhwellsBridge() {
20766
20833
  elementType: imgEl.dataset.ohwEditable ?? "image",
20767
20834
  hasTextOverlap,
20768
20835
  isDragOver,
20836
+ fullBleedSectionId: fullBleedSectionIdOf(imgEl),
20769
20837
  ...video ? { videoAutoplay: video.autoplay, videoMuted: video.muted } : {}
20770
20838
  });
20771
20839
  const track = imgEl.closest("[data-ohw-hover-pause]");
@@ -20938,7 +21006,13 @@ function OhhwellsBridge() {
20938
21006
  setHoveredItemRect(null);
20939
21007
  }
20940
21008
  };
21009
+ const syncSelectedMediaPointerRelease = (released) => {
21010
+ setSelectedMedia(
21011
+ (prev) => prev && prev.hasTextOverlap !== released ? { ...prev, hasTextOverlap: released } : prev
21012
+ );
21013
+ };
20941
21014
  const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
21015
+ syncSelectedMediaPointerRelease(true);
20942
21016
  if (linkPopoverOpenRef.current) {
20943
21017
  if (hoveredImageRef.current) {
20944
21018
  hoveredImageRef.current = null;
@@ -21133,6 +21207,7 @@ function OhhwellsBridge() {
21133
21207
  return;
21134
21208
  }
21135
21209
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
21210
+ syncSelectedMediaPointerRelease(false);
21136
21211
  if (imgEl !== hoveredImageRef.current || hoveredImageHasTextOverlapRef.current) {
21137
21212
  hoveredImageRef.current = imgEl;
21138
21213
  showImageHover(imgEl, isDragOver);
@@ -22606,7 +22681,11 @@ function OhhwellsBridge() {
22606
22681
  if (!key) return;
22607
22682
  const r2 = getVisibleRect(container);
22608
22683
  setCarouselHover(
22609
- (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 } }
22684
+ (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 : {
22685
+ key,
22686
+ rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height },
22687
+ fullBleedSectionId: fullBleedSectionIdOf(container)
22688
+ }
22610
22689
  );
22611
22690
  };
22612
22691
  const applyToolbarPos = (rect) => {
@@ -23068,7 +23147,7 @@ function OhhwellsBridge() {
23068
23147
  postToParent2({
23069
23148
  type: "ow:ready",
23070
23149
  version: "1",
23071
- bridgeVersion: "0.1.115",
23150
+ bridgeVersion: "0.1.116",
23072
23151
  path: pathname,
23073
23152
  nodes: collectEditableNodes(editContentRef.current),
23074
23153
  sections
@@ -23535,7 +23614,14 @@ function OhhwellsBridge() {
23535
23614
  bridgeRoot ? (0, import_react_dom5.createPortal)(
23536
23615
  /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(import_jsx_runtime33.Fragment, { children: [
23537
23616
  /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
23538
- isEditMode && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(AiSectionOverlay, { postToParent: postToParent2, apiRef: aiSectionApiRef }),
23617
+ isEditMode && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
23618
+ AiSectionOverlay,
23619
+ {
23620
+ postToParent: postToParent2,
23621
+ apiRef: aiSectionApiRef,
23622
+ onSelectionChange: handleAiSectionSelectionChange
23623
+ }
23624
+ ),
23539
23625
  isSectionDragging && sectionDropSlots.map((slot, i) => /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
23540
23626
  "div",
23541
23627
  {
@@ -23568,6 +23654,7 @@ function OhhwellsBridge() {
23568
23654
  {
23569
23655
  hover: mediaHover,
23570
23656
  isUploading: false,
23657
+ cueOnly: !mediaHover.isDragOver && !!mediaHover.fullBleedSectionId && mediaHover.fullBleedSectionId !== selectedAiSectionId,
23571
23658
  onReplace: handleMediaReplace,
23572
23659
  onSelect: handleMediaSelect,
23573
23660
  onVideoSettingsChange: handleVideoSettingsChange
@@ -23585,7 +23672,14 @@ function OhhwellsBridge() {
23585
23672
  onVideoSettingsChange: handleVideoSettingsChange
23586
23673
  }
23587
23674
  ),
23588
- carouselHover && !linkPopover && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(CarouselOverlay, { hover: carouselHover, onEdit: handleEditCarousel }),
23675
+ carouselHover && !linkPopover && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
23676
+ CarouselOverlay,
23677
+ {
23678
+ hover: carouselHover,
23679
+ onEdit: handleEditCarousel,
23680
+ cueOnly: !!carouselHover.fullBleedSectionId && carouselHover.fullBleedSectionId !== selectedAiSectionId
23681
+ }
23682
+ ),
23589
23683
  siblingHintRect && !linkPopover && !isItemDragging && siblingHintRects.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
23590
23684
  siblingHintRects.length > 0 && !linkPopover && !isItemDragging && siblingHintRects.map((rect, i) => /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(ItemInteractionLayer, { rect, state: "sibling-hint" }, `sibling-hint-${i}`)),
23591
23685
  isItemDragging && draggedItemRect && selectedElRef.current !== footerDragRef.current?.draggedEl && selectedElRef.current !== navDragRef.current?.draggedEl && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(ItemInteractionLayer, { rect: draggedItemRect, state: "dragging" }),