@ohhwells/bridge 0.1.59 → 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: [
@@ -9617,6 +9636,10 @@ function isSocialsRow(el) {
9617
9636
  const anchors = Array.from(el.querySelectorAll("a"));
9618
9637
  return anchors.length > 0 && anchors.every((anchor) => isSocialItem(anchor));
9619
9638
  }
9639
+ var MAX_SOCIAL_ITEMS_PER_ROW = 7;
9640
+ function canAddSocialItem(row) {
9641
+ return listSocialItems(row).length < MAX_SOCIAL_ITEMS_PER_ROW;
9642
+ }
9620
9643
  function listSocialItems(row) {
9621
9644
  return Array.from(row.children).map((child) => {
9622
9645
  if (!(child instanceof HTMLElement)) return null;
@@ -9976,6 +9999,7 @@ function ensureIconSlot(item) {
9976
9999
  // src/lib/footer-items.ts
9977
10000
  var FOOTER_ORDER_KEY = "__ohw_footer_order";
9978
10001
  var MAX_FOOTER_COLUMNS = 18;
10002
+ var MAX_FOOTER_ITEMS_PER_COLUMN = 7;
9979
10003
  var FOOTER_HREF_RE = /^footer-(\d+)-(\d+)-href$/;
9980
10004
  function parseFooterHrefKey(key) {
9981
10005
  if (!key) return null;
@@ -10193,6 +10217,13 @@ function getNextFooterColumnIndex() {
10193
10217
  function canAddFooterColumn() {
10194
10218
  return listFooterColumns().length < MAX_FOOTER_COLUMNS;
10195
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
+ }
10196
10227
  function buildFooterHeading(colIndex, text) {
10197
10228
  const heading = document.createElement("p");
10198
10229
  heading.setAttribute("data-ohw-editable", "text");
@@ -10928,6 +10959,7 @@ function FloatingPanel({
10928
10959
  e.stopPropagation();
10929
10960
  const el = e.currentTarget;
10930
10961
  el.setPointerCapture(e.pointerId);
10962
+ document.documentElement.setAttribute("data-ohw-panel-dragging", "");
10931
10963
  dragRef.current = {
10932
10964
  pointerId: e.pointerId,
10933
10965
  startX: e.clientX,
@@ -10960,11 +10992,17 @@ function FloatingPanel({
10960
10992
  const drag = dragRef.current;
10961
10993
  if (!drag || drag.pointerId !== e.pointerId) return;
10962
10994
  dragRef.current = null;
10995
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
10963
10996
  try {
10964
10997
  e.currentTarget.releasePointerCapture(e.pointerId);
10965
10998
  } catch {
10966
10999
  }
10967
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"), []);
10968
11006
  if (!open) return null;
10969
11007
  return /* @__PURE__ */ jsxs16(
10970
11008
  "div",
@@ -12743,6 +12781,11 @@ function isNavbarLinksContainer2(el) {
12743
12781
  function getFooterColumn(el) {
12744
12782
  return el.closest("[data-ohw-footer-col], [data-ohw-footer-column]");
12745
12783
  }
12784
+ function isFooterAddItemDisabled(selected) {
12785
+ if (!selected) return false;
12786
+ const column = resolveFooterColumnForAdd(selected);
12787
+ return column ? !canAddFooterItem(column) : false;
12788
+ }
12746
12789
  function resolveFooterColumnSelectionTarget(target, clientX, clientY) {
12747
12790
  if (getNavigationItemAnchor(target)) return null;
12748
12791
  const column = getFooterColumn(target);
@@ -13056,6 +13099,9 @@ var ICONS = {
13056
13099
  var SELECTION_CHROME_GAP2 = 4;
13057
13100
  var TOOLBAR_STROKE_GAP2 = 4;
13058
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})`;
13059
13105
  var TOOLBAR_GROUPS = [
13060
13106
  [
13061
13107
  { cmd: "bold", title: "Bold" },
@@ -13556,6 +13602,28 @@ function OhhwellsBridge() {
13556
13602
  const [hoveredNavContainerRect, setHoveredNavContainerRect] = useState11(null);
13557
13603
  const hoveredItemElRef = useRef9(null);
13558
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
+ }, []);
13559
13627
  const siblingHintElRef = useRef9(null);
13560
13628
  const [siblingHintRect, setSiblingHintRect] = useState11(null);
13561
13629
  const [siblingHintRects, setSiblingHintRects] = useState11([]);
@@ -14009,6 +14077,14 @@ function OhhwellsBridge() {
14009
14077
  if (!selected) return;
14010
14078
  const socialsRow = isSocialsRow(selected) ? selected : findSocialsRow(selected);
14011
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
+ }
14012
14088
  const after = getSocialItem(selected);
14013
14089
  const result2 = insertSocialItem(socialsRow, after, editContentRef.current);
14014
14090
  if (!result2) return;
@@ -14030,10 +14106,16 @@ function OhhwellsBridge() {
14030
14106
  return;
14031
14107
  }
14032
14108
  if (toolbarVariantRef.current === "select-frame" && isFooterFrameSelection) {
14033
- if (!selected.hasAttribute("data-ohw-footer-col") && !selected.closest("[data-ohw-footer-col]")) {
14034
- }
14035
- 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);
14036
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
+ }
14037
14119
  const result2 = insertFooterItem(column, "/", "New link", null);
14038
14120
  applyLinkByKey(result2.hrefKey, result2.href);
14039
14121
  document.querySelectorAll(`[data-ohw-key="${result2.labelKey}"]`).forEach((el) => {
@@ -14987,9 +15069,12 @@ function OhhwellsBridge() {
14987
15069
  [data-ohw-editable="video"], [data-ohw-editable="video"] *,
14988
15070
  [data-ohw-editable="bg-image"], [data-ohw-editable="bg-image"] * { cursor: pointer !important; }
14989
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. */
14990
15076
  [data-ohw-hovered]:not([contenteditable]):not([data-ohw-href-key]) {
14991
- outline: 2px dashed ${PRIMARY3} !important;
14992
- outline-offset: 4px;
15077
+ outline: none !important;
14993
15078
  }
14994
15079
  [data-ohw-href-key] [data-ohw-hovered],
14995
15080
  [data-ohw-href-key][data-ohw-hovered],
@@ -15042,8 +15127,11 @@ function OhhwellsBridge() {
15042
15127
  stateViews.textContent = `
15043
15128
  [data-ohw-state-view]:not([data-ohw-state-view="default"]) { display: none; }
15044
15129
  [data-ohw-state-view="default"] [data-ohw-editable] { pointer-events: auto !important; }
15045
- [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; }
15046
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; }
15047
15135
  `;
15048
15136
  document.head.appendChild(base);
15049
15137
  document.head.appendChild(forceHover);
@@ -15311,6 +15399,13 @@ function OhhwellsBridge() {
15311
15399
  setHoveredNavContainerRect(null);
15312
15400
  return;
15313
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
+ }
15314
15409
  {
15315
15410
  const selected2 = selectedElRef.current;
15316
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)));
@@ -15960,8 +16055,16 @@ function OhhwellsBridge() {
15960
16055
  setSectionGap(null);
15961
16056
  }
15962
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
+ };
15963
16065
  const handleMouseMove = (e) => {
15964
16066
  const { clientX, clientY } = e;
16067
+ if (pointOwnedByFloatingPanel(clientX, clientY)) return;
15965
16068
  probeSectionGapAt(clientX, clientY);
15966
16069
  probeImageAt(clientX, clientY);
15967
16070
  probeHoverCardsAt(clientX, clientY);
@@ -15970,6 +16073,7 @@ function OhhwellsBridge() {
15970
16073
  if (e.data?.type !== "ow:pointer-sync") return;
15971
16074
  const { clientX, clientY } = e.data;
15972
16075
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
16076
+ if (pointOwnedByFloatingPanel(clientX, clientY)) return;
15973
16077
  probeSectionGapAt(clientX, clientY);
15974
16078
  probeImageAt(clientX, clientY);
15975
16079
  probeHoverCardsAt(clientX, clientY);
@@ -16937,7 +17041,7 @@ function OhhwellsBridge() {
16937
17041
  if (footerDragRef.current) return;
16938
17042
  const target = e.target;
16939
17043
  if (!target) return;
16940
- 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]')) {
16941
17045
  return;
16942
17046
  }
16943
17047
  if (target.closest("[data-ohw-item-drag-surface]")) return;
@@ -17115,7 +17219,7 @@ function OhhwellsBridge() {
17115
17219
  postToParent2({
17116
17220
  type: "ow:ready",
17117
17221
  version: "1",
17118
- bridgeVersion: "0.1.58",
17222
+ bridgeVersion: "0.1.59",
17119
17223
  path: pathname,
17120
17224
  nodes: collectEditableNodes(editContentRef.current),
17121
17225
  sections
@@ -17581,6 +17685,7 @@ function OhhwellsBridge() {
17581
17685
  )),
17582
17686
  hoveredNavContainerRect && (toolbarVariant !== "select-frame" || isFooterFrameSelection) && !linkPopover && !isItemDragging && /* @__PURE__ */ jsx32(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
17583
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" }),
17584
17689
  toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isNavbarLinksContainer2(selectedElRef.current) && /* @__PURE__ */ jsx32(NavbarContainerChrome, { rect: toolbarRect, onAdd: handleAddTopLevelNavItem }),
17585
17690
  toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isFooterLinksContainer(selectedElRef.current) && /* @__PURE__ */ jsx32(
17586
17691
  FooterContainerChrome,
@@ -17621,7 +17726,10 @@ function OhhwellsBridge() {
17621
17726
  onSelectParent: handleSelectParent,
17622
17727
  onDuplicate: handleDuplicateSelected,
17623
17728
  onDelete: handleDeleteSelected,
17624
- 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
+ })(),
17625
17733
  editLinkDisabled: false,
17626
17734
  moreDisabled: false,
17627
17735
  duplicateDisabled: isFooterFrameSelection || selectedIsSocialsRow,