@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.cjs CHANGED
@@ -223,6 +223,17 @@ var FEATURE_LINE_CSS = [
223
223
  function textAttrs(ctx, path) {
224
224
  return ctx.keyFor ? { "data-ohw-key": ctx.keyFor(path), "data-ohw-editable": "text" } : {};
225
225
  }
226
+ var AI_RESPONSIVE_CSS = [
227
+ "@media (max-width: 960px) {",
228
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: repeat(2, 1fr) !important; }",
229
+ ' [data-ai-responsive] [data-ai-grid="1"] { grid-template-columns: 1fr !important; }',
230
+ "}",
231
+ "@media (max-width: 640px) {",
232
+ " [data-ai-responsive] [data-ai-grid] { grid-template-columns: 1fr !important; }",
233
+ " [data-ai-responsive] [data-ai-columns] > * { grid-column: 1 / -1 !important; }",
234
+ " [data-ai-responsive] [data-ai-section-inner] { padding-left: 20px !important; padding-right: 20px !important; }",
235
+ "}"
236
+ ].join("\n");
226
237
  var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
227
238
  function MediaBox({
228
239
  refValue,
@@ -965,6 +976,7 @@ function Carousel({ items, itemsPerRow, ctx }) {
965
976
  children: pageGroups.map((group, p) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
966
977
  "div",
967
978
  {
979
+ "data-ai-grid": String(itemsPerRow),
968
980
  style: {
969
981
  flex: "0 0 100%",
970
982
  display: "grid",
@@ -1068,6 +1080,7 @@ function CollectionBlock({ node, ctx, path }) {
1068
1080
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1069
1081
  "div",
1070
1082
  {
1083
+ "data-ai-grid": String(itemsPerRow),
1071
1084
  style: {
1072
1085
  display: "grid",
1073
1086
  gridTemplateColumns: `repeat(${itemsPerRow}, 1fr)`,
@@ -1231,6 +1244,7 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1231
1244
  {
1232
1245
  "data-ai-section": tree.tag ?? "",
1233
1246
  ...bgAttrs,
1247
+ "data-ai-responsive": "",
1234
1248
  style: {
1235
1249
  position: "relative",
1236
1250
  padding: `${pad}px 0`,
@@ -1240,10 +1254,12 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1240
1254
  backgroundPosition: "center"
1241
1255
  },
1242
1256
  children: [
1257
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: AI_RESPONSIVE_CSS }),
1243
1258
  isOverlay && backgroundUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "absolute", inset: 0, background: `rgba(255,255,255,${overlayAlpha})` } }),
1244
1259
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1245
1260
  "div",
1246
1261
  {
1262
+ "data-ai-section-inner": "",
1247
1263
  style: {
1248
1264
  position: "relative",
1249
1265
  maxWidth: AI_TREE_TOKENS.sectionMaxWidth,
@@ -1254,6 +1270,7 @@ function AiTreeRenderer({ tree, brand, resolveMedia, editKeyPrefix }) {
1254
1270
  children: tree.rows.map((row, r2) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1255
1271
  "div",
1256
1272
  {
1273
+ "data-ai-columns": "",
1257
1274
  style: {
1258
1275
  display: "grid",
1259
1276
  gridTemplateColumns: "repeat(12, 1fr)",
@@ -6159,6 +6176,7 @@ var FOCUS_RING = `0 0 0 4px color-mix(in srgb, ${PRIMARY} 12%, transparent)`;
6159
6176
  var DRAG_SHADOW = "0px 4px 6px -1px rgba(0, 0, 0, 0.1), 0px 2px 4px -2px rgba(0, 0, 0, 0.1)";
6160
6177
  var TOOLBAR_EDGE_MARGIN = 4;
6161
6178
  var SELECTION_CHROME_GAP = 4;
6179
+ var HOVER_CHROME_GAP = 2;
6162
6180
  var TOOLBAR_STROKE_GAP = 4;
6163
6181
  function getChromeZIndex(state) {
6164
6182
  switch (state) {
@@ -6314,10 +6332,11 @@ function ItemInteractionLayer({
6314
6332
  onItemPointerDown,
6315
6333
  onItemClick,
6316
6334
  itemDragSurface = true,
6317
- chromeGap = SELECTION_CHROME_GAP,
6335
+ chromeGap,
6318
6336
  className
6319
6337
  }) {
6320
6338
  if (state === "default") return null;
6339
+ const gap = chromeGap ?? (state === "hover" ? HOVER_CHROME_GAP : SELECTION_CHROME_GAP);
6321
6340
  const isActive = state === "active-top" || state === "active-bottom";
6322
6341
  const isDragging = state === "dragging";
6323
6342
  const showToolbar = isActive && toolbar;
@@ -6333,10 +6352,10 @@ function ItemInteractionLayer({
6333
6352
  className: cn("pointer-events-none", className),
6334
6353
  style: {
6335
6354
  position: "fixed",
6336
- top: rect.top - chromeGap,
6337
- left: rect.left - chromeGap,
6338
- width: rect.width + chromeGap * 2,
6339
- height: rect.height + chromeGap * 2,
6355
+ top: rect.top - gap,
6356
+ left: rect.left - gap,
6357
+ width: rect.width + gap * 2,
6358
+ height: rect.height + gap * 2,
6340
6359
  zIndex: getChromeZIndex(state)
6341
6360
  },
6342
6361
  children: [
@@ -7108,7 +7127,14 @@ function findClosestButtonLike(target) {
7108
7127
  function findButtonAtPoint(x, y, media) {
7109
7128
  const hits = Array.from(
7110
7129
  document.querySelectorAll(BUTTON_LIKE_SELECTOR)
7111
- ).filter((el) => containsPoint(el, x, y) && !(media && el.contains(media)));
7130
+ ).filter(
7131
+ (el) => containsPoint(el, x, y) && !(media && el.contains(media)) && // Bridge-injected chrome (e.g. MediaOverlay's own "Replace image" button) is never a
7132
+ // site button. Without this, once the media overlay renders its button lands right
7133
+ // under the cursor, gets detected as "a button on the media", and dismisses the very
7134
+ // overlay it belongs to — which removes the button, so the next hit-test finds nothing
7135
+ // and the overlay reappears. Flip-flops every tick: the "Replace image" button flashes.
7136
+ !el.closest("[data-ohw-bridge]")
7137
+ );
7112
7138
  if (hits.length === 0) return null;
7113
7139
  return hits.reduce((best, el) => best.contains(el) ? el : best);
7114
7140
  }
@@ -9683,6 +9709,10 @@ function isSocialsRow(el) {
9683
9709
  const anchors = Array.from(el.querySelectorAll("a"));
9684
9710
  return anchors.length > 0 && anchors.every((anchor) => isSocialItem(anchor));
9685
9711
  }
9712
+ var MAX_SOCIAL_ITEMS_PER_ROW = 7;
9713
+ function canAddSocialItem(row) {
9714
+ return listSocialItems(row).length < MAX_SOCIAL_ITEMS_PER_ROW;
9715
+ }
9686
9716
  function listSocialItems(row) {
9687
9717
  return Array.from(row.children).map((child) => {
9688
9718
  if (!(child instanceof HTMLElement)) return null;
@@ -10042,6 +10072,7 @@ function ensureIconSlot(item) {
10042
10072
  // src/lib/footer-items.ts
10043
10073
  var FOOTER_ORDER_KEY = "__ohw_footer_order";
10044
10074
  var MAX_FOOTER_COLUMNS = 18;
10075
+ var MAX_FOOTER_ITEMS_PER_COLUMN = 7;
10045
10076
  var FOOTER_HREF_RE = /^footer-(\d+)-(\d+)-href$/;
10046
10077
  function parseFooterHrefKey(key) {
10047
10078
  if (!key) return null;
@@ -10259,6 +10290,13 @@ function getNextFooterColumnIndex() {
10259
10290
  function canAddFooterColumn() {
10260
10291
  return listFooterColumns().length < MAX_FOOTER_COLUMNS;
10261
10292
  }
10293
+ function canAddFooterItem(column) {
10294
+ return listFooterLinksInColumn(column).length < MAX_FOOTER_ITEMS_PER_COLUMN;
10295
+ }
10296
+ function resolveFooterColumnForAdd(selected) {
10297
+ if (selected.hasAttribute("data-ohw-footer-col")) return selected;
10298
+ return selected.closest("[data-ohw-footer-col]") ?? (listFooterColumns().includes(selected) ? selected : null);
10299
+ }
10262
10300
  function buildFooterHeading(colIndex, text) {
10263
10301
  const heading = document.createElement("p");
10264
10302
  heading.setAttribute("data-ohw-editable", "text");
@@ -10988,6 +11026,7 @@ function FloatingPanel({
10988
11026
  e.stopPropagation();
10989
11027
  const el = e.currentTarget;
10990
11028
  el.setPointerCapture(e.pointerId);
11029
+ document.documentElement.setAttribute("data-ohw-panel-dragging", "");
10991
11030
  dragRef.current = {
10992
11031
  pointerId: e.pointerId,
10993
11032
  startX: e.clientX,
@@ -11020,11 +11059,17 @@ function FloatingPanel({
11020
11059
  const drag = dragRef.current;
11021
11060
  if (!drag || drag.pointerId !== e.pointerId) return;
11022
11061
  dragRef.current = null;
11062
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
11023
11063
  try {
11024
11064
  e.currentTarget.releasePointerCapture(e.pointerId);
11025
11065
  } catch {
11026
11066
  }
11027
11067
  }, []);
11068
+ (0, import_react13.useEffect)(() => {
11069
+ if (open) return;
11070
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
11071
+ }, [open]);
11072
+ (0, import_react13.useEffect)(() => () => document.documentElement.removeAttribute("data-ohw-panel-dragging"), []);
11028
11073
  if (!open) return null;
11029
11074
  return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
11030
11075
  "div",
@@ -12803,6 +12848,11 @@ function isNavbarLinksContainer2(el) {
12803
12848
  function getFooterColumn(el) {
12804
12849
  return el.closest("[data-ohw-footer-col], [data-ohw-footer-column]");
12805
12850
  }
12851
+ function isFooterAddItemDisabled(selected) {
12852
+ if (!selected) return false;
12853
+ const column = resolveFooterColumnForAdd(selected);
12854
+ return column ? !canAddFooterItem(column) : false;
12855
+ }
12806
12856
  function resolveFooterColumnSelectionTarget(target, clientX, clientY) {
12807
12857
  if (getNavigationItemAnchor(target)) return null;
12808
12858
  const column = getFooterColumn(target);
@@ -13116,6 +13166,9 @@ var ICONS = {
13116
13166
  var SELECTION_CHROME_GAP2 = 4;
13117
13167
  var TOOLBAR_STROKE_GAP2 = 4;
13118
13168
  var TOOLBAR_OFFSET_FROM_ELEMENT = SELECTION_CHROME_GAP2 + TOOLBAR_STROKE_GAP2;
13169
+ var HOVER_STROKE_WIDTH = 1.5;
13170
+ var TEXT_HOVER_SIDE_PAD = 4;
13171
+ var CHROME_PRIMARY = `var(--ohw-primary, ${PRIMARY3})`;
13119
13172
  var TOOLBAR_GROUPS = [
13120
13173
  [
13121
13174
  { cmd: "bold", title: "Bold" },
@@ -13616,6 +13669,28 @@ function OhhwellsBridge() {
13616
13669
  const [hoveredNavContainerRect, setHoveredNavContainerRect] = (0, import_react16.useState)(null);
13617
13670
  const hoveredItemElRef = (0, import_react16.useRef)(null);
13618
13671
  const [hoveredItemRect, setHoveredItemRect] = (0, import_react16.useState)(null);
13672
+ const [hoveredTextRect, setHoveredTextRect] = (0, import_react16.useState)(null);
13673
+ (0, import_react16.useEffect)(() => {
13674
+ const sync = () => {
13675
+ const el = document.querySelector(
13676
+ "[data-ohw-hovered]:not([contenteditable]):not([data-ohw-href-key])"
13677
+ );
13678
+ const target = el && !el.closest("[data-ohw-href-key]") ? el : null;
13679
+ if (!target) {
13680
+ setHoveredTextRect(null);
13681
+ return;
13682
+ }
13683
+ const r2 = target.getBoundingClientRect();
13684
+ setHoveredTextRect(new DOMRect(r2.x - TEXT_HOVER_SIDE_PAD, r2.y, r2.width + TEXT_HOVER_SIDE_PAD * 2, r2.height));
13685
+ };
13686
+ const observer = new MutationObserver(sync);
13687
+ observer.observe(document.documentElement, {
13688
+ attributes: true,
13689
+ attributeFilter: ["data-ohw-hovered"],
13690
+ subtree: true
13691
+ });
13692
+ return () => observer.disconnect();
13693
+ }, []);
13619
13694
  const siblingHintElRef = (0, import_react16.useRef)(null);
13620
13695
  const [siblingHintRect, setSiblingHintRect] = (0, import_react16.useState)(null);
13621
13696
  const [siblingHintRects, setSiblingHintRects] = (0, import_react16.useState)([]);
@@ -14069,6 +14144,14 @@ function OhhwellsBridge() {
14069
14144
  if (!selected) return;
14070
14145
  const socialsRow = isSocialsRow(selected) ? selected : findSocialsRow(selected);
14071
14146
  if (socialsRow) {
14147
+ if (!canAddSocialItem(socialsRow)) {
14148
+ postToParent2({
14149
+ type: "ow:toast",
14150
+ title: `Maximum ${MAX_SOCIAL_ITEMS_PER_ROW} social icons`,
14151
+ toastType: "error"
14152
+ });
14153
+ return;
14154
+ }
14072
14155
  const after = getSocialItem(selected);
14073
14156
  const result2 = insertSocialItem(socialsRow, after, editContentRef.current);
14074
14157
  if (!result2) return;
@@ -14090,10 +14173,16 @@ function OhhwellsBridge() {
14090
14173
  return;
14091
14174
  }
14092
14175
  if (toolbarVariantRef.current === "select-frame" && isFooterFrameSelection) {
14093
- if (!selected.hasAttribute("data-ohw-footer-col") && !selected.closest("[data-ohw-footer-col]")) {
14094
- }
14095
- const column = (selected.hasAttribute("data-ohw-footer-col") ? selected : null) ?? selected.closest("[data-ohw-footer-col]") ?? (listFooterColumns().includes(selected) ? selected : null);
14176
+ const column = resolveFooterColumnForAdd(selected);
14096
14177
  if (!column) return;
14178
+ if (!canAddFooterItem(column)) {
14179
+ postToParent2({
14180
+ type: "ow:toast",
14181
+ title: `Maximum ${MAX_FOOTER_ITEMS_PER_COLUMN} items per column`,
14182
+ toastType: "error"
14183
+ });
14184
+ return;
14185
+ }
14097
14186
  const result2 = insertFooterItem(column, "/", "New link", null);
14098
14187
  applyLinkByKey(result2.hrefKey, result2.href);
14099
14188
  document.querySelectorAll(`[data-ohw-key="${result2.labelKey}"]`).forEach((el) => {
@@ -15047,9 +15136,12 @@ function OhhwellsBridge() {
15047
15136
  [data-ohw-editable="video"], [data-ohw-editable="video"] *,
15048
15137
  [data-ohw-editable="bg-image"], [data-ohw-editable="bg-image"] * { cursor: pointer !important; }
15049
15138
  [data-ohw-editable="link"], [data-ohw-editable="link"] * { cursor: pointer !important; }
15139
+ /* Text hover chrome is drawn by the overlay (see hoveredTextRect) \u2014 the CSS outline
15140
+ that used to draw it dashes denser than the overlay border, so identical specs
15141
+ still read as two different frames (OHH-695). The attribute stays: hover paths
15142
+ and suppression rules key off it. */
15050
15143
  [data-ohw-hovered]:not([contenteditable]):not([data-ohw-href-key]) {
15051
- outline: 2px dashed ${PRIMARY3} !important;
15052
- outline-offset: 4px;
15144
+ outline: none !important;
15053
15145
  }
15054
15146
  [data-ohw-href-key] [data-ohw-hovered],
15055
15147
  [data-ohw-href-key][data-ohw-hovered],
@@ -15102,8 +15194,11 @@ function OhhwellsBridge() {
15102
15194
  stateViews.textContent = `
15103
15195
  [data-ohw-state-view]:not([data-ohw-state-view="default"]) { display: none; }
15104
15196
  [data-ohw-state-view="default"] [data-ohw-editable] { pointer-events: auto !important; }
15105
- [data-ohw-state-hovered] { outline: 2px dashed ${PRIMARY3} !important; outline-offset: 4px; }
15197
+ [data-ohw-state-hovered] { outline: ${HOVER_STROKE_WIDTH}px dashed ${CHROME_PRIMARY} !important; outline-offset: ${HOVER_CHROME_GAP}px; }
15106
15198
  [data-ohw-state-hovered]:has([data-ohw-hovered]) { outline: none !important; }
15199
+ /* :has() only sees descendants \u2014 when the card itself is the hovered text, the overlay
15200
+ already frames it, and the card outline doubled it (OHH-695). */
15201
+ [data-ohw-state-hovered][data-ohw-hovered] { outline: none !important; }
15107
15202
  `;
15108
15203
  document.head.appendChild(base);
15109
15204
  document.head.appendChild(forceHover);
@@ -15371,6 +15466,13 @@ function OhhwellsBridge() {
15371
15466
  setHoveredNavContainerRect(null);
15372
15467
  return;
15373
15468
  }
15469
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging") || target.closest("[data-ohw-floating-panel]")) {
15470
+ hoveredItemElRef.current = null;
15471
+ setHoveredItemRect(null);
15472
+ hoveredNavContainerRef.current = null;
15473
+ setHoveredNavContainerRect(null);
15474
+ return;
15475
+ }
15374
15476
  {
15375
15477
  const selected2 = selectedElRef.current;
15376
15478
  const selectedIsFooterColumn = Boolean(selected2) && !isFooterLinksContainer(selected2) && (selected2.hasAttribute("data-ohw-footer-col") || selected2.hasAttribute("data-ohw-footer-column") || Boolean(selected2.closest("footer") && isInferredFooterGroup2(selected2)));
@@ -16020,8 +16122,16 @@ function OhhwellsBridge() {
16020
16122
  setSectionGap(null);
16021
16123
  }
16022
16124
  };
16125
+ const pointOwnedByFloatingPanel = (clientX, clientY) => {
16126
+ if (document.documentElement.hasAttribute("data-ohw-panel-dragging")) return true;
16127
+ const panel = document.querySelector("[data-ohw-floating-panel]");
16128
+ if (!panel) return false;
16129
+ const rect = panel.getBoundingClientRect();
16130
+ return clientX >= rect.left && clientX <= rect.right && clientY >= rect.top && clientY <= rect.bottom;
16131
+ };
16023
16132
  const handleMouseMove = (e) => {
16024
16133
  const { clientX, clientY } = e;
16134
+ if (pointOwnedByFloatingPanel(clientX, clientY)) return;
16025
16135
  probeSectionGapAt(clientX, clientY);
16026
16136
  probeImageAt(clientX, clientY);
16027
16137
  probeHoverCardsAt(clientX, clientY);
@@ -16030,6 +16140,7 @@ function OhhwellsBridge() {
16030
16140
  if (e.data?.type !== "ow:pointer-sync") return;
16031
16141
  const { clientX, clientY } = e.data;
16032
16142
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
16143
+ if (pointOwnedByFloatingPanel(clientX, clientY)) return;
16033
16144
  probeSectionGapAt(clientX, clientY);
16034
16145
  probeImageAt(clientX, clientY);
16035
16146
  probeHoverCardsAt(clientX, clientY);
@@ -16997,7 +17108,7 @@ function OhhwellsBridge() {
16997
17108
  if (footerDragRef.current) return;
16998
17109
  const target = e.target;
16999
17110
  if (!target) return;
17000
- 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]')) {
17111
+ 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]')) {
17001
17112
  return;
17002
17113
  }
17003
17114
  if (target.closest("[data-ohw-item-drag-surface]")) return;
@@ -17175,7 +17286,7 @@ function OhhwellsBridge() {
17175
17286
  postToParent2({
17176
17287
  type: "ow:ready",
17177
17288
  version: "1",
17178
- bridgeVersion: "0.1.57",
17289
+ bridgeVersion: "0.1.59",
17179
17290
  path: pathname,
17180
17291
  nodes: collectEditableNodes(editContentRef.current),
17181
17292
  sections
@@ -17641,6 +17752,7 @@ function OhhwellsBridge() {
17641
17752
  )),
17642
17753
  hoveredNavContainerRect && (toolbarVariant !== "select-frame" || isFooterFrameSelection) && !linkPopover && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
17643
17754
  hoveredItemRect && !linkPopover && !hoveredNavContainerRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
17755
+ hoveredTextRect && !isItemDragging && toolbarVariant !== "rich-text" && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(ItemInteractionLayer, { rect: hoveredTextRect, state: "hover" }),
17644
17756
  toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isNavbarLinksContainer2(selectedElRef.current) && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(NavbarContainerChrome, { rect: toolbarRect, onAdd: handleAddTopLevelNavItem }),
17645
17757
  toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isFooterLinksContainer(selectedElRef.current) && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
17646
17758
  FooterContainerChrome,
@@ -17681,7 +17793,10 @@ function OhhwellsBridge() {
17681
17793
  onSelectParent: handleSelectParent,
17682
17794
  onDuplicate: handleDuplicateSelected,
17683
17795
  onDelete: handleDeleteSelected,
17684
- addItemDisabled: false,
17796
+ addItemDisabled: isFooterFrameSelection && isFooterAddItemDisabled(selectedElRef.current) || selectedElRef.current !== null && (() => {
17797
+ const row = isSocialsRow(selectedElRef.current) ? selectedElRef.current : findSocialsRow(selectedElRef.current);
17798
+ return row ? !canAddSocialItem(row) : false;
17799
+ })(),
17685
17800
  editLinkDisabled: false,
17686
17801
  moreDisabled: false,
17687
17802
  duplicateDisabled: isFooterFrameSelection || selectedIsSocialsRow,