@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.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: [
@@ -9690,6 +9709,10 @@ function isSocialsRow(el) {
9690
9709
  const anchors = Array.from(el.querySelectorAll("a"));
9691
9710
  return anchors.length > 0 && anchors.every((anchor) => isSocialItem(anchor));
9692
9711
  }
9712
+ var MAX_SOCIAL_ITEMS_PER_ROW = 7;
9713
+ function canAddSocialItem(row) {
9714
+ return listSocialItems(row).length < MAX_SOCIAL_ITEMS_PER_ROW;
9715
+ }
9693
9716
  function listSocialItems(row) {
9694
9717
  return Array.from(row.children).map((child) => {
9695
9718
  if (!(child instanceof HTMLElement)) return null;
@@ -10049,6 +10072,7 @@ function ensureIconSlot(item) {
10049
10072
  // src/lib/footer-items.ts
10050
10073
  var FOOTER_ORDER_KEY = "__ohw_footer_order";
10051
10074
  var MAX_FOOTER_COLUMNS = 18;
10075
+ var MAX_FOOTER_ITEMS_PER_COLUMN = 7;
10052
10076
  var FOOTER_HREF_RE = /^footer-(\d+)-(\d+)-href$/;
10053
10077
  function parseFooterHrefKey(key) {
10054
10078
  if (!key) return null;
@@ -10266,6 +10290,13 @@ function getNextFooterColumnIndex() {
10266
10290
  function canAddFooterColumn() {
10267
10291
  return listFooterColumns().length < MAX_FOOTER_COLUMNS;
10268
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
+ }
10269
10300
  function buildFooterHeading(colIndex, text) {
10270
10301
  const heading = document.createElement("p");
10271
10302
  heading.setAttribute("data-ohw-editable", "text");
@@ -10995,6 +11026,7 @@ function FloatingPanel({
10995
11026
  e.stopPropagation();
10996
11027
  const el = e.currentTarget;
10997
11028
  el.setPointerCapture(e.pointerId);
11029
+ document.documentElement.setAttribute("data-ohw-panel-dragging", "");
10998
11030
  dragRef.current = {
10999
11031
  pointerId: e.pointerId,
11000
11032
  startX: e.clientX,
@@ -11027,11 +11059,17 @@ function FloatingPanel({
11027
11059
  const drag = dragRef.current;
11028
11060
  if (!drag || drag.pointerId !== e.pointerId) return;
11029
11061
  dragRef.current = null;
11062
+ document.documentElement.removeAttribute("data-ohw-panel-dragging");
11030
11063
  try {
11031
11064
  e.currentTarget.releasePointerCapture(e.pointerId);
11032
11065
  } catch {
11033
11066
  }
11034
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"), []);
11035
11073
  if (!open) return null;
11036
11074
  return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
11037
11075
  "div",
@@ -12810,6 +12848,11 @@ function isNavbarLinksContainer2(el) {
12810
12848
  function getFooterColumn(el) {
12811
12849
  return el.closest("[data-ohw-footer-col], [data-ohw-footer-column]");
12812
12850
  }
12851
+ function isFooterAddItemDisabled(selected) {
12852
+ if (!selected) return false;
12853
+ const column = resolveFooterColumnForAdd(selected);
12854
+ return column ? !canAddFooterItem(column) : false;
12855
+ }
12813
12856
  function resolveFooterColumnSelectionTarget(target, clientX, clientY) {
12814
12857
  if (getNavigationItemAnchor(target)) return null;
12815
12858
  const column = getFooterColumn(target);
@@ -13123,6 +13166,9 @@ var ICONS = {
13123
13166
  var SELECTION_CHROME_GAP2 = 4;
13124
13167
  var TOOLBAR_STROKE_GAP2 = 4;
13125
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})`;
13126
13172
  var TOOLBAR_GROUPS = [
13127
13173
  [
13128
13174
  { cmd: "bold", title: "Bold" },
@@ -13623,6 +13669,28 @@ function OhhwellsBridge() {
13623
13669
  const [hoveredNavContainerRect, setHoveredNavContainerRect] = (0, import_react16.useState)(null);
13624
13670
  const hoveredItemElRef = (0, import_react16.useRef)(null);
13625
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
+ }, []);
13626
13694
  const siblingHintElRef = (0, import_react16.useRef)(null);
13627
13695
  const [siblingHintRect, setSiblingHintRect] = (0, import_react16.useState)(null);
13628
13696
  const [siblingHintRects, setSiblingHintRects] = (0, import_react16.useState)([]);
@@ -14076,6 +14144,14 @@ function OhhwellsBridge() {
14076
14144
  if (!selected) return;
14077
14145
  const socialsRow = isSocialsRow(selected) ? selected : findSocialsRow(selected);
14078
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
+ }
14079
14155
  const after = getSocialItem(selected);
14080
14156
  const result2 = insertSocialItem(socialsRow, after, editContentRef.current);
14081
14157
  if (!result2) return;
@@ -14097,10 +14173,16 @@ function OhhwellsBridge() {
14097
14173
  return;
14098
14174
  }
14099
14175
  if (toolbarVariantRef.current === "select-frame" && isFooterFrameSelection) {
14100
- if (!selected.hasAttribute("data-ohw-footer-col") && !selected.closest("[data-ohw-footer-col]")) {
14101
- }
14102
- 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);
14103
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
+ }
14104
14186
  const result2 = insertFooterItem(column, "/", "New link", null);
14105
14187
  applyLinkByKey(result2.hrefKey, result2.href);
14106
14188
  document.querySelectorAll(`[data-ohw-key="${result2.labelKey}"]`).forEach((el) => {
@@ -15054,9 +15136,12 @@ function OhhwellsBridge() {
15054
15136
  [data-ohw-editable="video"], [data-ohw-editable="video"] *,
15055
15137
  [data-ohw-editable="bg-image"], [data-ohw-editable="bg-image"] * { cursor: pointer !important; }
15056
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. */
15057
15143
  [data-ohw-hovered]:not([contenteditable]):not([data-ohw-href-key]) {
15058
- outline: 2px dashed ${PRIMARY3} !important;
15059
- outline-offset: 4px;
15144
+ outline: none !important;
15060
15145
  }
15061
15146
  [data-ohw-href-key] [data-ohw-hovered],
15062
15147
  [data-ohw-href-key][data-ohw-hovered],
@@ -15109,8 +15194,11 @@ function OhhwellsBridge() {
15109
15194
  stateViews.textContent = `
15110
15195
  [data-ohw-state-view]:not([data-ohw-state-view="default"]) { display: none; }
15111
15196
  [data-ohw-state-view="default"] [data-ohw-editable] { pointer-events: auto !important; }
15112
- [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; }
15113
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; }
15114
15202
  `;
15115
15203
  document.head.appendChild(base);
15116
15204
  document.head.appendChild(forceHover);
@@ -15378,6 +15466,13 @@ function OhhwellsBridge() {
15378
15466
  setHoveredNavContainerRect(null);
15379
15467
  return;
15380
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
+ }
15381
15476
  {
15382
15477
  const selected2 = selectedElRef.current;
15383
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)));
@@ -16027,8 +16122,16 @@ function OhhwellsBridge() {
16027
16122
  setSectionGap(null);
16028
16123
  }
16029
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
+ };
16030
16132
  const handleMouseMove = (e) => {
16031
16133
  const { clientX, clientY } = e;
16134
+ if (pointOwnedByFloatingPanel(clientX, clientY)) return;
16032
16135
  probeSectionGapAt(clientX, clientY);
16033
16136
  probeImageAt(clientX, clientY);
16034
16137
  probeHoverCardsAt(clientX, clientY);
@@ -16037,6 +16140,7 @@ function OhhwellsBridge() {
16037
16140
  if (e.data?.type !== "ow:pointer-sync") return;
16038
16141
  const { clientX, clientY } = e.data;
16039
16142
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
16143
+ if (pointOwnedByFloatingPanel(clientX, clientY)) return;
16040
16144
  probeSectionGapAt(clientX, clientY);
16041
16145
  probeImageAt(clientX, clientY);
16042
16146
  probeHoverCardsAt(clientX, clientY);
@@ -17004,7 +17108,7 @@ function OhhwellsBridge() {
17004
17108
  if (footerDragRef.current) return;
17005
17109
  const target = e.target;
17006
17110
  if (!target) return;
17007
- 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]')) {
17008
17112
  return;
17009
17113
  }
17010
17114
  if (target.closest("[data-ohw-item-drag-surface]")) return;
@@ -17182,7 +17286,7 @@ function OhhwellsBridge() {
17182
17286
  postToParent2({
17183
17287
  type: "ow:ready",
17184
17288
  version: "1",
17185
- bridgeVersion: "0.1.58",
17289
+ bridgeVersion: "0.1.59",
17186
17290
  path: pathname,
17187
17291
  nodes: collectEditableNodes(editContentRef.current),
17188
17292
  sections
@@ -17648,6 +17752,7 @@ function OhhwellsBridge() {
17648
17752
  )),
17649
17753
  hoveredNavContainerRect && (toolbarVariant !== "select-frame" || isFooterFrameSelection) && !linkPopover && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
17650
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" }),
17651
17756
  toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isNavbarLinksContainer2(selectedElRef.current) && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(NavbarContainerChrome, { rect: toolbarRect, onAdd: handleAddTopLevelNavItem }),
17652
17757
  toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isFooterLinksContainer(selectedElRef.current) && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
17653
17758
  FooterContainerChrome,
@@ -17688,7 +17793,10 @@ function OhhwellsBridge() {
17688
17793
  onSelectParent: handleSelectParent,
17689
17794
  onDuplicate: handleDuplicateSelected,
17690
17795
  onDelete: handleDeleteSelected,
17691
- 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
+ })(),
17692
17800
  editLinkDisabled: false,
17693
17801
  moreDisabled: false,
17694
17802
  duplicateDisabled: isFooterFrameSelection || selectedIsSocialsRow,