@ohhwells/bridge 0.1.117-next.321 → 0.1.117-next.324

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
@@ -8504,8 +8504,15 @@ function ItemInteractionLayer({
8504
8504
  itemDragSurface = true,
8505
8505
  chromeGap,
8506
8506
  toolbarAlign = "center",
8507
+ hidden = false,
8507
8508
  className
8508
8509
  }) {
8510
+ const localRef = (0, import_react7.useRef)(null);
8511
+ (0, import_react7.useLayoutEffect)(() => {
8512
+ const node = localRef.current;
8513
+ if (!node) return;
8514
+ node.style.display = hidden ? "none" : "block";
8515
+ }, [hidden, rect.top, rect.left, rect.width, rect.height, state]);
8509
8516
  if (state === "default") return null;
8510
8517
  const gap = chromeGap ?? (state === "hover" ? HOVER_CHROME_GAP : SELECTION_CHROME_GAP);
8511
8518
  const isActive = state === "active-top" || state === "active-bottom";
@@ -8514,10 +8521,15 @@ function ItemInteractionLayer({
8514
8521
  const showDragHandle = (isActive || isDragging) && showHandle;
8515
8522
  const itemDragEnabled = itemDragSurface && isActive && !isDragging && !dragDisabled && Boolean(showHandle || onItemPointerDown);
8516
8523
  const useDetachedBelowToolbar = Boolean(showToolbar && state === "active-bottom" && toolbarBelowRect);
8524
+ const setRootRef = (node) => {
8525
+ localRef.current = node;
8526
+ if (typeof elRef === "function") elRef(node);
8527
+ else if (elRef) elRef.current = node;
8528
+ };
8517
8529
  return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
8518
8530
  "div",
8519
8531
  {
8520
- ref: elRef,
8532
+ ref: setRootRef,
8521
8533
  "data-ohw-item-interaction": "",
8522
8534
  "data-ohw-item-interaction-state": state,
8523
8535
  className: cn("pointer-events-none", className),
@@ -8527,7 +8539,8 @@ function ItemInteractionLayer({
8527
8539
  left: rect.left - gap,
8528
8540
  width: rect.width + gap * 2,
8529
8541
  height: rect.height + gap * 2,
8530
- zIndex: getChromeZIndex(state)
8542
+ zIndex: getChromeZIndex(state),
8543
+ display: hidden ? "none" : "block"
8531
8544
  },
8532
8545
  children: [
8533
8546
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
@@ -10142,6 +10155,14 @@ function setMediaStripItemAsset(tree, index, assetId) {
10142
10155
  return true;
10143
10156
  });
10144
10157
  }
10158
+ function setMediaStripItemAlt(tree, index, alt) {
10159
+ return withMediaStrip(tree, (block) => {
10160
+ const items = block.items;
10161
+ if (index < 0 || index >= items.length) return false;
10162
+ items[index] = { ...items[index], alt };
10163
+ return true;
10164
+ });
10165
+ }
10145
10166
 
10146
10167
  // src/ui/ai-section/AiMediaStripOverlay.tsx
10147
10168
  var import_jsx_runtime18 = require("react/jsx-runtime");
@@ -10260,6 +10281,74 @@ function Pill({
10260
10281
  }
10261
10282
  );
10262
10283
  }
10284
+ function AltInput({ value, onCommit }) {
10285
+ const [draft, setDraft] = (0, import_react9.useState)(value);
10286
+ (0, import_react9.useEffect)(() => setDraft(value), [value]);
10287
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
10288
+ "div",
10289
+ {
10290
+ style: {
10291
+ display: "flex",
10292
+ alignItems: "center",
10293
+ gap: 6,
10294
+ height: 28,
10295
+ padding: "0 12px",
10296
+ borderRadius: 999,
10297
+ background: "#ffffff",
10298
+ width: "100%",
10299
+ maxWidth: 260,
10300
+ boxSizing: "border-box"
10301
+ },
10302
+ children: [
10303
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
10304
+ "span",
10305
+ {
10306
+ style: {
10307
+ flex: "0 0 auto",
10308
+ color: OHW_BLUE_DARK,
10309
+ fontSize: 10,
10310
+ fontWeight: 700,
10311
+ textTransform: "uppercase",
10312
+ letterSpacing: 0.4
10313
+ },
10314
+ children: "Alt"
10315
+ }
10316
+ ),
10317
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
10318
+ "input",
10319
+ {
10320
+ type: "text",
10321
+ value: draft,
10322
+ onChange: (e) => setDraft(e.target.value),
10323
+ onBlur: () => {
10324
+ if (draft !== value) onCommit(draft);
10325
+ },
10326
+ onKeyDown: (e) => {
10327
+ if (e.key === "Enter") e.currentTarget.blur();
10328
+ if (e.key === "Escape") {
10329
+ setDraft(value);
10330
+ e.currentTarget.blur();
10331
+ }
10332
+ },
10333
+ placeholder: "Describe this image",
10334
+ "aria-label": "Image alt text",
10335
+ style: {
10336
+ flex: 1,
10337
+ minWidth: 0,
10338
+ border: "none",
10339
+ outline: "none",
10340
+ background: "transparent",
10341
+ fontSize: 12,
10342
+ lineHeight: "16px",
10343
+ fontFamily: "Figtree, ui-sans-serif, system-ui, sans-serif",
10344
+ color: "#1f2937"
10345
+ }
10346
+ }
10347
+ )
10348
+ ]
10349
+ }
10350
+ );
10351
+ }
10263
10352
  var VARIANTS = [
10264
10353
  { value: "logos", label: "Logos" },
10265
10354
  { value: "reel", label: "Reel" }
@@ -10289,6 +10378,7 @@ function MediaStripToolbar({
10289
10378
  onSetHover,
10290
10379
  onSetTreatment,
10291
10380
  onResize,
10381
+ onSetAlt,
10292
10382
  onAddItem,
10293
10383
  onRemoveItem,
10294
10384
  onChangeImage,
@@ -10297,13 +10387,24 @@ function MediaStripToolbar({
10297
10387
  }) {
10298
10388
  const items = mediaStripItems(entry.tree);
10299
10389
  const itemCount = items.length;
10390
+ const currentAlt = items[index]?.alt ?? "";
10300
10391
  const slots = findMediaStripSlots(entry.tree);
10301
10392
  const variant = slots?.variant === "reel" ? "reel" : "logos";
10302
10393
  const align = slots?.align === "left" || slots?.align === "right" ? slots.align : "center";
10303
10394
  const hover = slots?.hover === "none" || slots?.hover === "lift" || slots?.hover === "zoom" || slots?.hover === "fade" ? slots.hover : "none";
10304
10395
  const treatment = slots?.treatment === "color" || slots?.treatment === "grayscale" ? slots.treatment : variant === "logos" ? "grayscale" : "color";
10305
10396
  const sizeValue = variant === "logos" ? typeof slots?.logoMaxHeight === "number" ? slots.logoMaxHeight : 48 : typeof slots?.cardWidth === "number" ? slots.cardWidth : 240;
10306
- const pillWrap = window.innerWidth <= 480 ? "wrap" : "nowrap";
10397
+ const isMobile = window.innerWidth <= 480;
10398
+ const rowStyle = isMobile ? {
10399
+ display: "flex",
10400
+ gap: 6,
10401
+ flexWrap: "nowrap",
10402
+ justifyContent: "center",
10403
+ overflowX: "auto",
10404
+ WebkitOverflowScrolling: "touch",
10405
+ width: "100%",
10406
+ scrollbarWidth: "none"
10407
+ } : { display: "flex", gap: 6, flexWrap: "nowrap", justifyContent: "center" };
10307
10408
  return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
10308
10409
  "div",
10309
10410
  {
@@ -10321,14 +10422,14 @@ function MediaStripToolbar({
10321
10422
  boxShadow: "0 8px 24px rgba(0,0,0,0.25)",
10322
10423
  pointerEvents: pending ? "none" : "auto",
10323
10424
  opacity: pending ? 0.6 : 1,
10324
- maxWidth: pillWrap === "wrap" ? "calc(100vw - 24px)" : "none"
10425
+ maxWidth: isMobile ? "calc(100vw - 24px)" : "none"
10325
10426
  },
10326
10427
  children: [
10327
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { display: "flex", gap: 6, flexWrap: pillWrap, justifyContent: "center" }, children: [
10428
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: rowStyle, children: [
10328
10429
  VARIANTS.map((v) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Pill, { label: v.label, active: variant === v.value, onClick: () => onSetVariant(v.value) }, v.value)),
10329
10430
  ALIGNS.map((a) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Pill, { label: a.label, active: align === a.value, onClick: () => onSetAlign(a.value) }, a.value))
10330
10431
  ] }),
10331
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { display: "flex", gap: 6, flexWrap: pillWrap, justifyContent: "center" }, children: [
10432
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: rowStyle, children: [
10332
10433
  HOVERS.map((h) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Pill, { label: h.label, active: hover === h.value, onClick: () => onSetHover(h.value) }, h.value)),
10333
10434
  TREATMENTS.map((t) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Pill, { label: t.label, active: treatment === t.value, onClick: () => onSetTreatment(t.value) }, t.value))
10334
10435
  ] }),
@@ -10340,10 +10441,13 @@ function MediaStripToolbar({
10340
10441
  ] }),
10341
10442
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Pill, { label: "+", onClick: () => onResize(8) })
10342
10443
  ] }),
10343
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { display: "flex", gap: 6, flexWrap: pillWrap, justifyContent: "center" }, children: [
10444
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { style: { display: "flex", justifyContent: "center", width: "100%" }, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(AltInput, { value: currentAlt, onCommit: onSetAlt }) }),
10445
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: rowStyle, children: [
10344
10446
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Pill, { label: "Change Image", onClick: onChangeImage }),
10345
10447
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Pill, { label: "+ Item", onClick: onAddItem }),
10346
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Pill, { label: "\u2212 Item", onClick: onRemoveItem, disabled: itemCount <= 1 }),
10448
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Pill, { label: "\u2212 Item", onClick: onRemoveItem, disabled: itemCount <= 1 })
10449
+ ] }),
10450
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: rowStyle, children: [
10347
10451
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Pill, { label: "\u2190 Move", onClick: onMoveLeft, disabled: index <= 0 }),
10348
10452
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Pill, { label: "\u2192 Move", onClick: onMoveRight, disabled: index >= itemCount - 1 })
10349
10453
  ] }),
@@ -10364,7 +10468,8 @@ function findMediaStripSlots(tree) {
10364
10468
  function AiMediaStripOverlay({
10365
10469
  postToParent: postToParent2,
10366
10470
  aiSectionsRef,
10367
- applyTreeLocalRef
10471
+ applyTreeLocalRef,
10472
+ clearInteractionChromeRef
10368
10473
  }) {
10369
10474
  const [hover, setHover] = (0, import_react9.useState)(null);
10370
10475
  const [active, setActive] = (0, import_react9.useState)(null);
@@ -10481,7 +10586,10 @@ function AiMediaStripOverlay({
10481
10586
  {
10482
10587
  type: "button",
10483
10588
  "data-ohw-toolbar": "",
10484
- onClick: () => setActive(hover),
10589
+ onClick: () => {
10590
+ clearInteractionChromeRef.current();
10591
+ setActive(hover);
10592
+ },
10485
10593
  "aria-label": "Configure media item",
10486
10594
  style: {
10487
10595
  position: "fixed",
@@ -10526,6 +10634,9 @@ function AiMediaStripOverlay({
10526
10634
  const current = variant === "logos" ? typeof slots?.logoMaxHeight === "number" ? slots.logoMaxHeight : 48 : typeof slots?.cardWidth === "number" ? slots.cardWidth : 240;
10527
10635
  void runMutation(active.sectionId, (tree) => setMediaStripSizing(tree, current + delta));
10528
10636
  },
10637
+ onSetAlt: (alt) => {
10638
+ void runMutation(active.sectionId, (tree) => setMediaStripItemAlt(tree, active.index, alt));
10639
+ },
10529
10640
  onAddItem: () => {
10530
10641
  void requestAssetPick(postToParent2).then((picked) => {
10531
10642
  if (!picked) return;
@@ -15240,14 +15351,18 @@ function clampDragPosition(pos, parentScroll, panelW) {
15240
15351
  y: Math.min(Math.max(pos.y, minY), maxY)
15241
15352
  };
15242
15353
  }
15243
- function stickyPageY(homeY, parentScroll) {
15244
- const clip = getVisibleClip(parentScroll);
15245
- return Math.max(homeY, clip.top + EDGE_MARGIN);
15246
- }
15247
- function availablePanelHeight(parentScroll, top) {
15354
+ function fitPanelInClip(homeY, parentScroll, preferredHeight) {
15248
15355
  const clip = getVisibleClip(parentScroll);
15249
- const available = Math.floor(clip.bottom - top - EDGE_MARGIN);
15250
- return available > HEADER_MIN ? available : void 0;
15356
+ const minY = clip.top + EDGE_MARGIN;
15357
+ const maxBottom = clip.bottom - EDGE_MARGIN;
15358
+ const band = Math.max(HEADER_MIN, maxBottom - minY);
15359
+ let y = Math.max(homeY, minY);
15360
+ const want = Math.min(Math.max(preferredHeight, HEADER_MIN), band);
15361
+ if (y + want > maxBottom) {
15362
+ y = Math.max(minY, maxBottom - want);
15363
+ }
15364
+ const maxHeight = Math.max(HEADER_MIN, Math.floor(maxBottom - y));
15365
+ return { y, maxHeight };
15251
15366
  }
15252
15367
  function FloatingPanel({
15253
15368
  open,
@@ -15275,11 +15390,11 @@ function FloatingPanel({
15275
15390
  }
15276
15391
  const [liveScroll, setLiveScroll] = (0, import_react14.useState)(parentScroll);
15277
15392
  const home = position ?? homeRef.current ?? defaultFloatingPanelPosition(parentScroll);
15393
+ const { y: fittedY, maxHeight } = fitPanelInClip(home.y, liveScroll, measured.h);
15278
15394
  const clamped = {
15279
15395
  x: clampX(home.x, measured.w),
15280
- y: stickyPageY(home.y, liveScroll)
15396
+ y: fittedY
15281
15397
  };
15282
- const maxHeight = availablePanelHeight(liveScroll, clamped.y);
15283
15398
  (0, import_react14.useEffect)(() => {
15284
15399
  if (!open) {
15285
15400
  setLiveScroll(parentScroll);
@@ -15296,7 +15411,10 @@ function FloatingPanel({
15296
15411
  (0, import_react14.useLayoutEffect)(() => {
15297
15412
  if (!open || !panelRef.current) return;
15298
15413
  const el = panelRef.current;
15299
- const next = { w: el.offsetWidth || PANEL_WIDTH, h: el.offsetHeight || 280 };
15414
+ const header = el.querySelector("[data-ohw-floating-panel-header]");
15415
+ const body = el.querySelector("[data-ohw-floating-panel-body]");
15416
+ const contentH = (header?.offsetHeight ?? 0) + (body?.scrollHeight ?? 0) || el.scrollHeight || 280;
15417
+ const next = { w: el.offsetWidth || PANEL_WIDTH, h: contentH };
15300
15418
  setMeasured((prev) => prev.w === next.w && prev.h === next.h ? prev : next);
15301
15419
  }, [open, children, title, context]);
15302
15420
  const onHeaderPointerDown = (0, import_react14.useCallback)(
@@ -16479,6 +16597,11 @@ function rekeyCopy(copy, id) {
16479
16597
  setButtonDragAttr(copy);
16480
16598
  return { labelKey };
16481
16599
  }
16600
+ function stampVariantClass(el, variant) {
16601
+ const raw = typeof el.className === "string" ? el.className : el.getAttribute("class") || "";
16602
+ const without = raw.replace(/\bbtn--(?:primary|secondary|ghost)\b/g, "").replace(/\s+/g, " ").trim();
16603
+ el.className = without ? `${without} btn--${variant}` : `btn btn--${variant}`;
16604
+ }
16482
16605
  function snapshotOf(el) {
16483
16606
  const id = buttonId(el) ?? "";
16484
16607
  const label = labelElement2(el);
@@ -16511,8 +16634,13 @@ function duplicateButton(source, content) {
16511
16634
  const href = buttonHref(source);
16512
16635
  const label = buttonLabel(source);
16513
16636
  if (href) copy.setAttribute("href", href);
16514
- const assignment = readAssignments(content)[sourceId];
16515
- if (assignment) applyButtonStyle(copy, assignment, tokens);
16637
+ const sourceAssignment = readAssignments(content)[sourceId] ?? assignmentFor(source, content, tokens);
16638
+ const copyAssignment = {
16639
+ variant: "secondary",
16640
+ size: sourceAssignment.size
16641
+ };
16642
+ stampVariantClass(copy, "secondary");
16643
+ applyButtonStyle(copy, copyAssignment, tokens);
16516
16644
  const copyLabelEl = labelElement2(copy);
16517
16645
  if (copyLabelEl) copyLabelEl.textContent = label;
16518
16646
  let collection = buttonCollectionOf(source);
@@ -16546,20 +16674,18 @@ function duplicateButton(source, content) {
16546
16674
  ...collections,
16547
16675
  [collectionId]: { id: collectionId, members, gap }
16548
16676
  };
16549
- const assignments = assignment ? withAssignment(content, copyId, assignment) : readAssignments(content);
16677
+ const assignments = withAssignment(content, copyId, copyAssignment);
16550
16678
  const nextContent = {
16551
16679
  ...content,
16552
16680
  [copyId]: href,
16553
16681
  [labelKey]: label,
16554
16682
  [BUTTON_COLLECTIONS_KEY]: JSON.stringify(nextCollections),
16555
- [BUTTON_SNAPSHOTS_KEY]: JSON.stringify(snapshots)
16683
+ [BUTTON_SNAPSHOTS_KEY]: JSON.stringify(snapshots),
16684
+ [BUTTON_ASSIGNMENTS_KEY]: JSON.stringify(assignments)
16556
16685
  };
16557
16686
  if (sourceLabelKey && nextContent[sourceLabelKey] === void 0) {
16558
16687
  nextContent[sourceLabelKey] = label;
16559
16688
  }
16560
- if (Object.keys(assignments).length > 0) {
16561
- nextContent[BUTTON_ASSIGNMENTS_KEY] = JSON.stringify(assignments);
16562
- }
16563
16689
  const nodeKeys = [
16564
16690
  copyId,
16565
16691
  labelKey,
@@ -20519,6 +20645,8 @@ function OhhwellsBridge() {
20519
20645
  const addNavAfterAnchorRef = (0, import_react18.useRef)(null);
20520
20646
  const editContentRef = (0, import_react18.useRef)({});
20521
20647
  const aiSectionsRef = (0, import_react18.useRef)("");
20648
+ const clearInteractionChromeRef = (0, import_react18.useRef)(() => {
20649
+ });
20522
20650
  const brandKitRef = (0, import_react18.useRef)("");
20523
20651
  const stylesRef = (0, import_react18.useRef)("");
20524
20652
  const appliedDrawerRef = (0, import_react18.useRef)(null);
@@ -21619,6 +21747,7 @@ function OhhwellsBridge() {
21619
21747
  setIsFooterFrameSelection(false);
21620
21748
  setToolbarVariant("link-action");
21621
21749
  setToolbarRect(anchor.getBoundingClientRect());
21750
+ if (glowElRef.current) glowElRef.current.style.display = "";
21622
21751
  setToolbarShowEditLink(false);
21623
21752
  setActiveCommands(/* @__PURE__ */ new Set());
21624
21753
  const panel = floatingPanelRef.current;
@@ -21725,6 +21854,7 @@ function OhhwellsBridge() {
21725
21854
  });
21726
21855
  }, []);
21727
21856
  const openSocialsDisplayPanel = (0, import_react18.useCallback)((row) => {
21857
+ setFloatingPanelPos(null);
21728
21858
  setParentScrollSnap(parentScrollRef.current);
21729
21859
  setFloatingPanel({
21730
21860
  key: "socials-display",
@@ -24630,6 +24760,7 @@ function OhhwellsBridge() {
24630
24760
  deselectRef.current();
24631
24761
  clearMediaSelectionRef.current();
24632
24762
  };
24763
+ clearInteractionChromeRef.current = clearInteractionChrome;
24633
24764
  const applyTreeToLocalState = (payload) => {
24634
24765
  clearInteractionChrome();
24635
24766
  const previous = aiSectionsRef.current;
@@ -26520,7 +26651,8 @@ function OhhwellsBridge() {
26520
26651
  {
26521
26652
  postToParent: postToParent2,
26522
26653
  aiSectionsRef,
26523
- applyTreeLocalRef
26654
+ applyTreeLocalRef,
26655
+ clearInteractionChromeRef
26524
26656
  }
26525
26657
  ),
26526
26658
  isSectionDragging && sectionDropSlots.map((slot, i) => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
@@ -26819,6 +26951,10 @@ function OhhwellsBridge() {
26819
26951
  toolbarBelowRect: selectedElRef.current && isNavigationItem2(selectedElRef.current) ? getOpenNavDropdownPanelRect(selectedElRef.current) : null,
26820
26952
  toolbarBelowEl: selectedElRef.current && isNavigationItem2(selectedElRef.current) ? getOpenNavDropdownPanel(selectedElRef.current) : null,
26821
26953
  elRef: glowElRef,
26954
+ hidden: isRectOutsideClip(
26955
+ isItemDragging && draggedItemRect && (footerDragRef.current?.wasSelected || navDragRef.current?.wasSelected || selectedElRef.current === buttonCollectionDragRef.current) ? draggedItemRect : toolbarRect,
26956
+ getIframeVisibleClip(parentScrollRef.current)
26957
+ ),
26822
26958
  state: isItemDragging ? "dragging" : selectedElRef.current && isNavigationItem2(selectedElRef.current) && getOpenNavDropdownPanel(selectedElRef.current) ? "active-bottom" : resolveItemInteractionState(toolbarRect, parentScrollRef.current),
26823
26959
  showHandle: toolbarVariant === "link-action" && Boolean(reorderHrefKey) || toolbarVariant === "select-frame" && isFooterFrameSelection,
26824
26960
  dragDisabled: reorderDragDisabled,