@ohhwells/bridge 0.1.58 → 0.1.60

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
@@ -150,6 +150,17 @@ var FEATURE_LINE_CSS = [
150
150
  function textAttrs(ctx, path) {
151
151
  return ctx.keyFor ? { "data-ohw-key": ctx.keyFor(path), "data-ohw-editable": "text" } : {};
152
152
  }
153
+ var AI_RESPONSIVE_CSS = [
154
+ "@media (max-width: 960px) {",
155
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: repeat(2, 1fr) !important; }",
156
+ ' [data-ai-responsive] [data-ai-grid="1"] { grid-template-columns: 1fr !important; }',
157
+ "}",
158
+ "@media (max-width: 640px) {",
159
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: 1fr !important; }",
160
+ " [data-ai-responsive] [data-ai-columns] > * { grid-column: 1 / -1 !important; }",
161
+ " [data-ai-responsive] [data-ai-section-inner] { padding-left: 20px !important; padding-right: 20px !important; }",
162
+ "}"
163
+ ].join("\n");
153
164
  var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
154
165
  function MediaBox({
155
166
  refValue,
@@ -892,6 +903,7 @@ function Carousel({ items, itemsPerRow, ctx }) {
892
903
  children: pageGroups.map((group, p) => /* @__PURE__ */ jsx(
893
904
  "div",
894
905
  {
906
+ "data-ai-grid": String(itemsPerRow),
895
907
  style: {
896
908
  flex: "0 0 100%",
897
909
  display: "grid",
@@ -995,6 +1007,7 @@ function CollectionBlock({ node, ctx, path }) {
995
1007
  return /* @__PURE__ */ jsx(
996
1008
  "div",
997
1009
  {
1010
+ "data-ai-grid": String(itemsPerRow),
998
1011
  style: {
999
1012
  display: "grid",
1000
1013
  gridTemplateColumns: `repeat(${itemsPerRow}, 1fr)`,
@@ -1158,6 +1171,7 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1158
1171
  {
1159
1172
  "data-ai-section": tree.tag ?? "",
1160
1173
  ...bgAttrs,
1174
+ "data-ai-responsive": "",
1161
1175
  style: {
1162
1176
  position: "relative",
1163
1177
  padding: `${pad}px 0`,
@@ -1167,10 +1181,12 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1167
1181
  backgroundPosition: "center"
1168
1182
  },
1169
1183
  children: [
1184
+ /* @__PURE__ */ jsx("style", { children: AI_RESPONSIVE_CSS }),
1170
1185
  isOverlay && backgroundUrl && /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
1171
1186
  /* @__PURE__ */ jsx(
1172
1187
  "div",
1173
1188
  {
1189
+ "data-ai-section-inner": "",
1174
1190
  style: {
1175
1191
  position: "relative",
1176
1192
  maxWidth: AI_TREE_TOKENS.sectionMaxWidth,
@@ -1181,6 +1197,7 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1181
1197
  children: tree.rows.map((row, r2) => /* @__PURE__ */ jsx(
1182
1198
  "div",
1183
1199
  {
1200
+ "data-ai-columns": "",
1184
1201
  style: {
1185
1202
  display: "grid",
1186
1203
  gridTemplateColumns: "repeat(12, 1fr)",
@@ -6086,6 +6103,7 @@ var FOCUS_RING = `0 0 0 4px color-mix(in srgb, ${PRIMARY} 12%, transparent)`;
6086
6103
  var DRAG_SHADOW = "0px 4px 6px -1px rgba(0, 0, 0, 0.1), 0px 2px 4px -2px rgba(0, 0, 0, 0.1)";
6087
6104
  var TOOLBAR_EDGE_MARGIN = 4;
6088
6105
  var SELECTION_CHROME_GAP = 4;
6106
+ var HOVER_CHROME_GAP = 2;
6089
6107
  var TOOLBAR_STROKE_GAP = 4;
6090
6108
  function getChromeZIndex(state) {
6091
6109
  switch (state) {
@@ -6241,10 +6259,11 @@ function ItemInteractionLayer({
6241
6259
  onItemPointerDown,
6242
6260
  onItemClick,
6243
6261
  itemDragSurface = true,
6244
- chromeGap = SELECTION_CHROME_GAP,
6262
+ chromeGap,
6245
6263
  className
6246
6264
  }) {
6247
6265
  if (state === "default") return null;
6266
+ const gap = chromeGap ?? (state === "hover" ? HOVER_CHROME_GAP : SELECTION_CHROME_GAP);
6248
6267
  const isActive = state === "active-top" || state === "active-bottom";
6249
6268
  const isDragging = state === "dragging";
6250
6269
  const showToolbar = isActive && toolbar;
@@ -6260,10 +6279,10 @@ function ItemInteractionLayer({
6260
6279
  className: cn("pointer-events-none", className),
6261
6280
  style: {
6262
6281
  position: "fixed",
6263
- top: rect.top - chromeGap,
6264
- left: rect.left - chromeGap,
6265
- width: rect.width + chromeGap * 2,
6266
- height: rect.height + chromeGap * 2,
6282
+ top: rect.top - gap,
6283
+ left: rect.left - gap,
6284
+ width: rect.width + gap * 2,
6285
+ height: rect.height + gap * 2,
6267
6286
  zIndex: getChromeZIndex(state)
6268
6287
  },
6269
6288
  children: [
@@ -7035,7 +7054,14 @@ function findClosestButtonLike(target) {
7035
7054
  function findButtonAtPoint(x, y, media) {
7036
7055
  const hits = Array.from(
7037
7056
  document.querySelectorAll(BUTTON_LIKE_SELECTOR)
7038
- ).filter((el) => containsPoint(el, x, y) && !(media && el.contains(media)));
7057
+ ).filter(
7058
+ (el) => containsPoint(el, x, y) && !(media && el.contains(media)) && // Bridge-injected chrome (e.g. MediaOverlay's own "Replace image" button) is never a
7059
+ // site button. Without this, once the media overlay renders its button lands right
7060
+ // under the cursor, gets detected as "a button on the media", and dismisses the very
7061
+ // overlay it belongs to — which removes the button, so the next hit-test finds nothing
7062
+ // and the overlay reappears. Flip-flops every tick: the "Replace image" button flashes.
7063
+ !el.closest("[data-ohw-bridge]")
7064
+ );
7039
7065
  if (hits.length === 0) return null;
7040
7066
  return hits.reduce((best, el) => best.contains(el) ? el : best);
7041
7067
  }
@@ -9610,6 +9636,10 @@ function isSocialsRow(el) {
9610
9636
  const anchors = Array.from(el.querySelectorAll("a"));
9611
9637
  return anchors.length > 0 && anchors.every((anchor) => isSocialItem(anchor));
9612
9638
  }
9639
+ var MAX_SOCIAL_ITEMS_PER_ROW = 7;
9640
+ function canAddSocialItem(row) {
9641
+ return listSocialItems(row).length < MAX_SOCIAL_ITEMS_PER_ROW;
9642
+ }
9613
9643
  function listSocialItems(row) {
9614
9644
  return Array.from(row.children).map((child) => {
9615
9645
  if (!(child instanceof HTMLElement)) return null;
@@ -9969,6 +9999,7 @@ function ensureIconSlot(item) {
9969
9999
  // src/lib/footer-items.ts
9970
10000
  var FOOTER_ORDER_KEY = "__ohw_footer_order";
9971
10001
  var MAX_FOOTER_COLUMNS = 18;
10002
+ var MAX_FOOTER_ITEMS_PER_COLUMN = 7;
9972
10003
  var FOOTER_HREF_RE = /^footer-(\d+)-(\d+)-href$/;
9973
10004
  function parseFooterHrefKey(key) {
9974
10005
  if (!key) return null;
@@ -10186,6 +10217,13 @@ function getNextFooterColumnIndex() {
10186
10217
  function canAddFooterColumn() {
10187
10218
  return listFooterColumns().length < MAX_FOOTER_COLUMNS;
10188
10219
  }
10220
+ function canAddFooterItem(column) {
10221
+ return listFooterLinksInColumn(column).length < MAX_FOOTER_ITEMS_PER_COLUMN;
10222
+ }
10223
+ function resolveFooterColumnForAdd(selected) {
10224
+ if (selected.hasAttribute("data-ohw-footer-col")) return selected;
10225
+ return selected.closest("[data-ohw-footer-col]") ?? (listFooterColumns().includes(selected) ? selected : null);
10226
+ }
10189
10227
  function buildFooterHeading(colIndex, text) {
10190
10228
  const heading = document.createElement("p");
10191
10229
  heading.setAttribute("data-ohw-editable", "text");
@@ -10921,6 +10959,7 @@ function FloatingPanel({
10921
10959
  e.stopPropagation();
10922
10960
  const el = e.currentTarget;
10923
10961
  el.setPointerCapture(e.pointerId);
10962
+ document.documentElement.setAttribute("data-ohw-panel-dragging", "");
10924
10963
  dragRef.current = {
10925
10964
  pointerId: e.pointerId,
10926
10965
  startX: e.clientX,
@@ -10953,11 +10992,17 @@ function FloatingPanel({
10953
10992
  const drag = dragRef.current;
10954
10993
  if (!drag || drag.pointerId !== e.pointerId) return;
10955
10994
  dragRef.current = null;
10995
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
10956
10996
  try {
10957
10997
  e.currentTarget.releasePointerCapture(e.pointerId);
10958
10998
  } catch {
10959
10999
  }
10960
11000
  }, []);
11001
+ useEffect9(() => {
11002
+ if (open) return;
11003
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
11004
+ }, [open]);
11005
+ useEffect9(() => () => document.documentElement.removeAttribute("data-ohw-panel-dragging"), []);
10961
11006
  if (!open) return null;
10962
11007
  return /* @__PURE__ */ jsxs16(
10963
11008
  "div",
@@ -12736,6 +12781,11 @@ function isNavbarLinksContainer2(el) {
12736
12781
  function getFooterColumn(el) {
12737
12782
  return el.closest("[data-ohw-footer-col], [data-ohw-footer-column]");
12738
12783
  }
12784
+ function isFooterAddItemDisabled(selected) {
12785
+ if (!selected) return false;
12786
+ const column = resolveFooterColumnForAdd(selected);
12787
+ return column ? !canAddFooterItem(column) : false;
12788
+ }
12739
12789
  function resolveFooterColumnSelectionTarget(target, clientX, clientY) {
12740
12790
  if (getNavigationItemAnchor(target)) return null;
12741
12791
  const column = getFooterColumn(target);
@@ -13049,6 +13099,9 @@ var ICONS = {
13049
13099
  var SELECTION_CHROME_GAP2 = 4;
13050
13100
  var TOOLBAR_STROKE_GAP2 = 4;
13051
13101
  var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
13102
+ var HOVER_STROKE_WIDTH = 1.5;
13103
+ var TEXT_HOVER_SIDE_PAD = 4;
13104
+ var CHROME_PRIMARY = `var(--ohw-primary, ${PRIMARY3})`;
13052
13105
  var TOOLBAR_GROUPS = [
13053
13106
  [
13054
13107
  { cmd: "bold", title: "Bold" },
@@ -13549,6 +13602,28 @@ function OhhwellsBridge() {
13549
13602
  const [hoveredNavContainerRect, setHoveredNavContainerRect] = useState11(null);
13550
13603
  const hoveredItemElRef = useRef9(null);
13551
13604
  const [hoveredItemRect, setHoveredItemRect] = useState11(null);
13605
+ const [hoveredTextRect, setHoveredTextRect] = useState11(null);
13606
+ useEffect12(() => {
13607
+ const sync = () => {
13608
+ const el = document.querySelector(
13609
+ "[data-ohw-hovered]:not([contenteditable]):not([data-ohw-href-key])"
13610
+ );
13611
+ const target = el && !el.closest("[data-ohw-href-key]") ? el : null;
13612
+ if (!target) {
13613
+ setHoveredTextRect(null);
13614
+ return;
13615
+ }
13616
+ const r2 = target.getBoundingClientRect();
13617
+ setHoveredTextRect(new DOMRect(r2.x - TEXT_HOVER_SIDE_PAD, r2.y, r2.width + TEXT_HOVER_SIDE_PAD * 2, r2.height));
13618
+ };
13619
+ const observer = new MutationObserver(sync);
13620
+ observer.observe(document.documentElement, {
13621
+ attributes: true,
13622
+ attributeFilter: ["data-ohw-hovered"],
13623
+ subtree: true
13624
+ });
13625
+ return () => observer.disconnect();
13626
+ }, []);
13552
13627
  const siblingHintElRef = useRef9(null);
13553
13628
  const [siblingHintRect, setSiblingHintRect] = useState11(null);
13554
13629
  const [siblingHintRects, setSiblingHintRects] = useState11([]);
@@ -14002,6 +14077,14 @@ function OhhwellsBridge() {
14002
14077
  if (!selected) return;
14003
14078
  const socialsRow = isSocialsRow(selected) ? selected : findSocialsRow(selected);
14004
14079
  if (socialsRow) {
14080
+ if (!canAddSocialItem(socialsRow)) {
14081
+ postToParent2({
14082
+ type: "ow:toast",
14083
+ title: `Maximum ${MAX_SOCIAL_ITEMS_PER_ROW} social icons`,
14084
+ toastType: "error"
14085
+ });
14086
+ return;
14087
+ }
14005
14088
  const after = getSocialItem(selected);
14006
14089
  const result2 = insertSocialItem(socialsRow, after, editContentRef.current);
14007
14090
  if (!result2) return;
@@ -14023,10 +14106,16 @@ function OhhwellsBridge() {
14023
14106
  return;
14024
14107
  }
14025
14108
  if (toolbarVariantRef.current === "select-frame" && isFooterFrameSelection) {
14026
- if (!selected.hasAttribute("data-ohw-footer-col") && !selected.closest("[data-ohw-footer-col]")) {
14027
- }
14028
- const column = (selected.hasAttribute("data-ohw-footer-col") ? selected : null) ?? selected.closest("[data-ohw-footer-col]") ?? (listFooterColumns().includes(selected) ? selected : null);
14109
+ const column = resolveFooterColumnForAdd(selected);
14029
14110
  if (!column) return;
14111
+ if (!canAddFooterItem(column)) {
14112
+ postToParent2({
14113
+ type: "ow:toast",
14114
+ title: `Maximum ${MAX_FOOTER_ITEMS_PER_COLUMN} items per column`,
14115
+ toastType: "error"
14116
+ });
14117
+ return;
14118
+ }
14030
14119
  const result2 = insertFooterItem(column, "/", "New link", null);
14031
14120
  applyLinkByKey(result2.hrefKey, result2.href);
14032
14121
  document.querySelectorAll(`[data-ohw-key="${result2.labelKey}"]`).forEach((el) => {
@@ -14980,9 +15069,12 @@ function OhhwellsBridge() {
14980
15069
  [data-ohw-editable="video"], [data-ohw-editable="video"] *,
14981
15070
  [data-ohw-editable="bg-image"], [data-ohw-editable="bg-image"] * { cursor: pointer !important; }
14982
15071
  [data-ohw-editable="link"], [data-ohw-editable="link"] * { cursor: pointer !important; }
15072
+ /* Text hover chrome is drawn by the overlay (see hoveredTextRect) \u2014 the CSS outline
15073
+ that used to draw it dashes denser than the overlay border, so identical specs
15074
+ still read as two different frames (OHH-695). The attribute stays: hover paths
15075
+ and suppression rules key off it. */
14983
15076
  [data-ohw-hovered]:not([contenteditable]):not([data-ohw-href-key]) {
14984
- outline: 2px dashed ${PRIMARY3} !important;
14985
- outline-offset: 4px;
15077
+ outline: none !important;
14986
15078
  }
14987
15079
  [data-ohw-href-key] [data-ohw-hovered],
14988
15080
  [data-ohw-href-key][data-ohw-hovered],
@@ -15035,8 +15127,11 @@ function OhhwellsBridge() {
15035
15127
  stateViews.textContent = `
15036
15128
  [data-ohw-state-view]:not([data-ohw-state-view="default"]) { display: none; }
15037
15129
  [data-ohw-state-view="default"] [data-ohw-editable] { pointer-events: auto !important; }
15038
- [data-ohw-state-hovered] { outline: 2px dashed ${PRIMARY3} !important; outline-offset: 4px; }
15130
+ [data-ohw-state-hovered] { outline: ${HOVER_STROKE_WIDTH}px dashed ${CHROME_PRIMARY} !important; outline-offset: ${HOVER_CHROME_GAP}px; }
15039
15131
  [data-ohw-state-hovered]:has([data-ohw-hovered]) { outline: none !important; }
15132
+ /* :has() only sees descendants \u2014 when the card itself is the hovered text, the overlay
15133
+ already frames it, and the card outline doubled it (OHH-695). */
15134
+ [data-ohw-state-hovered][data-ohw-hovered] { outline: none !important; }
15040
15135
  `;
15041
15136
  document.head.appendChild(base);
15042
15137
  document.head.appendChild(forceHover);
@@ -15304,6 +15399,13 @@ function OhhwellsBridge() {
15304
15399
  setHoveredNavContainerRect(null);
15305
15400
  return;
15306
15401
  }
15402
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || target.closest("[data-ohw-floating-panel]")) {
15403
+ hoveredItemElRef.current = null;
15404
+ setHoveredItemRect(null);
15405
+ hoveredNavContainerRef.current = null;
15406
+ setHoveredNavContainerRect(null);
15407
+ return;
15408
+ }
15307
15409
  {
15308
15410
  const selected2 = selectedElRef.current;
15309
15411
  const selectedIsFooterColumn = Boolean(selected2) && !isFooterLinksContainer(selected2) && (selected2.hasAttribute("data-ohw-footer-col") || selected2.hasAttribute("data-ohw-footer-column") || Boolean(selected2.closest("footer") && isInferredFooterGroup2(selected2)));
@@ -15953,8 +16055,16 @@ function OhhwellsBridge() {
15953
16055
  setSectionGap(null);
15954
16056
  }
15955
16057
  };
16058
+ const pointOwnedByFloatingPanel = (clientX, clientY) => {
16059
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging")) return true;
16060
+ const panel = document.querySelector("[data-ohw-floating-panel]");
16061
+ if (!panel) return false;
16062
+ const rect = panel.getBoundingClientRect();
16063
+ return clientX >= rect.left && clientX <= rect.right && clientY >= rect.top && clientY <= rect.bottom;
16064
+ };
15956
16065
  const handleMouseMove = (e) => {
15957
16066
  const { clientX, clientY } = e;
16067
+ if (pointOwnedByFloatingPanel(clientX, clientY)) return;
15958
16068
  probeSectionGapAt(clientX, clientY);
15959
16069
  probeImageAt(clientX, clientY);
15960
16070
  probeHoverCardsAt(clientX, clientY);
@@ -15963,6 +16073,7 @@ function OhhwellsBridge() {
15963
16073
  if (e.data?.type !== "ow:pointer-sync") return;
15964
16074
  const { clientX, clientY } = e.data;
15965
16075
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
16076
+ if (pointOwnedByFloatingPanel(clientX, clientY)) return;
15966
16077
  probeSectionGapAt(clientX, clientY);
15967
16078
  probeImageAt(clientX, clientY);
15968
16079
  probeHoverCardsAt(clientX, clientY);
@@ -16930,7 +17041,7 @@ function OhhwellsBridge() {
16930
17041
  if (footerDragRef.current) return;
16931
17042
  const target = e.target;
16932
17043
  if (!target) return;
16933
- if (target.closest('[data-ohw-drag-handle-container], [data-slot="drag-handle"], [data-ohw-toolbar], [data-ohw-item-toolbar-anchor], [data-ohw-link-popover-root]')) {
17044
+ if (target.closest('[data-ohw-drag-handle-container], [data-slot="drag-handle"], [data-ohw-toolbar], [data-ohw-item-toolbar-anchor], [data-ohw-link-popover-root], [data-ohw-floating-panel]')) {
16934
17045
  return;
16935
17046
  }
16936
17047
  if (target.closest("[data-ohw-item-drag-surface]")) return;
@@ -17108,7 +17219,7 @@ function OhhwellsBridge() {
17108
17219
  postToParent2({
17109
17220
  type: "ow:ready",
17110
17221
  version: "1",
17111
- bridgeVersion: "0.1.57",
17222
+ bridgeVersion: "0.1.59",
17112
17223
  path: pathname,
17113
17224
  nodes: collectEditableNodes(editContentRef.current),
17114
17225
  sections
@@ -17574,6 +17685,7 @@ function OhhwellsBridge() {
17574
17685
  )),
17575
17686
  hoveredNavContainerRect && (toolbarVariant !== "select-frame" || isFooterFrameSelection) && !linkPopover && !isItemDragging && /* @__PURE__ */ jsx32(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
17576
17687
  hoveredItemRect && !linkPopover && !hoveredNavContainerRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ jsx32(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
17688
+ hoveredTextRect && !isItemDragging && toolbarVariant !== "rich-text" && /* @__PURE__ */ jsx32(ItemInteractionLayer, { rect: hoveredTextRect, state: "hover" }),
17577
17689
  toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isNavbarLinksContainer2(selectedElRef.current) && /* @__PURE__ */ jsx32(NavbarContainerChrome, { rect: toolbarRect, onAdd: handleAddTopLevelNavItem }),
17578
17690
  toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isFooterLinksContainer(selectedElRef.current) && /* @__PURE__ */ jsx32(
17579
17691
  FooterContainerChrome,
@@ -17614,7 +17726,10 @@ function OhhwellsBridge() {
17614
17726
  onSelectParent: handleSelectParent,
17615
17727
  onDuplicate: handleDuplicateSelected,
17616
17728
  onDelete: handleDeleteSelected,
17617
- addItemDisabled: false,
17729
+ addItemDisabled: isFooterFrameSelection && isFooterAddItemDisabled(selectedElRef.current) || selectedElRef.current !== null && (() => {
17730
+ const row = isSocialsRow(selectedElRef.current) ? selectedElRef.current : findSocialsRow(selectedElRef.current);
17731
+ return row ? !canAddSocialItem(row) : false;
17732
+ })(),
17618
17733
  editLinkDisabled: false,
17619
17734
  moreDisabled: false,
17620
17735
  duplicateDisabled: isFooterFrameSelection || selectedIsSocialsRow,