@ohhwells/bridge 0.1.117-next.323 → 0.1.117-next.325

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
@@ -572,6 +572,40 @@ function applyBrandToDom(kit) {
572
572
  }
573
573
  }
574
574
 
575
+ // src/lib/custom-code.ts
576
+ var ALLOWED_HEAD_TAGS = /* @__PURE__ */ new Set(["style", "meta", "link", "script", "noscript", "base"]);
577
+ var CUSTOM_CODE_AFTER_HEAD_KEY = "__ohw_custom_code_after_head";
578
+ var CUSTOM_CODE_BEFORE_HEAD_KEY = "__ohw_custom_code_before_head";
579
+ function clearExisting(slot) {
580
+ document.querySelectorAll(`[data-ohw-custom-code="${slot}"]`).forEach((el) => el.remove());
581
+ }
582
+ function buildNodes(raw, slot) {
583
+ if (!raw || !raw.trim()) return [];
584
+ const parsed = new DOMParser().parseFromString(raw, "text/html");
585
+ const source = [...parsed.head.childNodes, ...parsed.body.childNodes];
586
+ const nodes = [];
587
+ for (const node of source) {
588
+ if (node.nodeType !== Node.ELEMENT_NODE) continue;
589
+ const el = node;
590
+ const tag = el.tagName.toLowerCase();
591
+ if (!ALLOWED_HEAD_TAGS.has(tag)) continue;
592
+ const built = document.createElement(tag);
593
+ for (const attr of Array.from(el.attributes)) built.setAttribute(attr.name, attr.value);
594
+ if (tag === "script" || tag === "style" || tag === "noscript") built.textContent = el.textContent;
595
+ built.dataset.ohwCustomCode = slot;
596
+ nodes.push(built);
597
+ }
598
+ return nodes;
599
+ }
600
+ function applyCustomCodeToHead(content) {
601
+ clearExisting("after");
602
+ clearExisting("before");
603
+ const afterNodes = buildNodes(content[CUSTOM_CODE_AFTER_HEAD_KEY], "after");
604
+ if (afterNodes.length > 0) document.head.prepend(...afterNodes);
605
+ const beforeNodes = buildNodes(content[CUSTOM_CODE_BEFORE_HEAD_KEY], "before");
606
+ if (beforeNodes.length > 0) document.head.append(...beforeNodes);
607
+ }
608
+
575
609
  // src/lib/section-styles.ts
576
610
  var STYLE_STORE_KEY = "__ohw_styles";
577
611
  var STYLE_SHEET_ID = "ohw-section-styles";
@@ -10155,6 +10189,14 @@ function setMediaStripItemAsset(tree, index, assetId) {
10155
10189
  return true;
10156
10190
  });
10157
10191
  }
10192
+ function setMediaStripItemAlt(tree, index, alt) {
10193
+ return withMediaStrip(tree, (block) => {
10194
+ const items = block.items;
10195
+ if (index < 0 || index >= items.length) return false;
10196
+ items[index] = { ...items[index], alt };
10197
+ return true;
10198
+ });
10199
+ }
10158
10200
 
10159
10201
  // src/ui/ai-section/AiMediaStripOverlay.tsx
10160
10202
  var import_jsx_runtime18 = require("react/jsx-runtime");
@@ -10273,6 +10315,74 @@ function Pill({
10273
10315
  }
10274
10316
  );
10275
10317
  }
10318
+ function AltInput({ value, onCommit }) {
10319
+ const [draft, setDraft] = (0, import_react9.useState)(value);
10320
+ (0, import_react9.useEffect)(() => setDraft(value), [value]);
10321
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
10322
+ "div",
10323
+ {
10324
+ style: {
10325
+ display: "flex",
10326
+ alignItems: "center",
10327
+ gap: 6,
10328
+ height: 28,
10329
+ padding: "0 12px",
10330
+ borderRadius: 999,
10331
+ background: "#ffffff",
10332
+ width: "100%",
10333
+ maxWidth: 260,
10334
+ boxSizing: "border-box"
10335
+ },
10336
+ children: [
10337
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
10338
+ "span",
10339
+ {
10340
+ style: {
10341
+ flex: "0 0 auto",
10342
+ color: OHW_BLUE_DARK,
10343
+ fontSize: 10,
10344
+ fontWeight: 700,
10345
+ textTransform: "uppercase",
10346
+ letterSpacing: 0.4
10347
+ },
10348
+ children: "Alt"
10349
+ }
10350
+ ),
10351
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
10352
+ "input",
10353
+ {
10354
+ type: "text",
10355
+ value: draft,
10356
+ onChange: (e) => setDraft(e.target.value),
10357
+ onBlur: () => {
10358
+ if (draft !== value) onCommit(draft);
10359
+ },
10360
+ onKeyDown: (e) => {
10361
+ if (e.key === "Enter") e.currentTarget.blur();
10362
+ if (e.key === "Escape") {
10363
+ setDraft(value);
10364
+ e.currentTarget.blur();
10365
+ }
10366
+ },
10367
+ placeholder: "Describe this image",
10368
+ "aria-label": "Image alt text",
10369
+ style: {
10370
+ flex: 1,
10371
+ minWidth: 0,
10372
+ border: "none",
10373
+ outline: "none",
10374
+ background: "transparent",
10375
+ fontSize: 12,
10376
+ lineHeight: "16px",
10377
+ fontFamily: "Figtree, ui-sans-serif, system-ui, sans-serif",
10378
+ color: "#1f2937"
10379
+ }
10380
+ }
10381
+ )
10382
+ ]
10383
+ }
10384
+ );
10385
+ }
10276
10386
  var VARIANTS = [
10277
10387
  { value: "logos", label: "Logos" },
10278
10388
  { value: "reel", label: "Reel" }
@@ -10302,6 +10412,7 @@ function MediaStripToolbar({
10302
10412
  onSetHover,
10303
10413
  onSetTreatment,
10304
10414
  onResize,
10415
+ onSetAlt,
10305
10416
  onAddItem,
10306
10417
  onRemoveItem,
10307
10418
  onChangeImage,
@@ -10310,13 +10421,24 @@ function MediaStripToolbar({
10310
10421
  }) {
10311
10422
  const items = mediaStripItems(entry.tree);
10312
10423
  const itemCount = items.length;
10424
+ const currentAlt = items[index]?.alt ?? "";
10313
10425
  const slots = findMediaStripSlots(entry.tree);
10314
10426
  const variant = slots?.variant === "reel" ? "reel" : "logos";
10315
10427
  const align = slots?.align === "left" || slots?.align === "right" ? slots.align : "center";
10316
10428
  const hover = slots?.hover === "none" || slots?.hover === "lift" || slots?.hover === "zoom" || slots?.hover === "fade" ? slots.hover : "none";
10317
10429
  const treatment = slots?.treatment === "color" || slots?.treatment === "grayscale" ? slots.treatment : variant === "logos" ? "grayscale" : "color";
10318
10430
  const sizeValue = variant === "logos" ? typeof slots?.logoMaxHeight === "number" ? slots.logoMaxHeight : 48 : typeof slots?.cardWidth === "number" ? slots.cardWidth : 240;
10319
- const pillWrap = window.innerWidth <= 480 ? "wrap" : "nowrap";
10431
+ const isMobile = window.innerWidth <= 480;
10432
+ const rowStyle = isMobile ? {
10433
+ display: "flex",
10434
+ gap: 6,
10435
+ flexWrap: "nowrap",
10436
+ justifyContent: "center",
10437
+ overflowX: "auto",
10438
+ WebkitOverflowScrolling: "touch",
10439
+ width: "100%",
10440
+ scrollbarWidth: "none"
10441
+ } : { display: "flex", gap: 6, flexWrap: "nowrap", justifyContent: "center" };
10320
10442
  return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
10321
10443
  "div",
10322
10444
  {
@@ -10334,14 +10456,14 @@ function MediaStripToolbar({
10334
10456
  boxShadow: "0 8px 24px rgba(0,0,0,0.25)",
10335
10457
  pointerEvents: pending ? "none" : "auto",
10336
10458
  opacity: pending ? 0.6 : 1,
10337
- maxWidth: pillWrap === "wrap" ? "calc(100vw - 24px)" : "none"
10459
+ maxWidth: isMobile ? "calc(100vw - 24px)" : "none"
10338
10460
  },
10339
10461
  children: [
10340
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { display: "flex", gap: 6, flexWrap: pillWrap, justifyContent: "center" }, children: [
10462
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: rowStyle, children: [
10341
10463
  VARIANTS.map((v) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Pill, { label: v.label, active: variant === v.value, onClick: () => onSetVariant(v.value) }, v.value)),
10342
10464
  ALIGNS.map((a) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Pill, { label: a.label, active: align === a.value, onClick: () => onSetAlign(a.value) }, a.value))
10343
10465
  ] }),
10344
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { display: "flex", gap: 6, flexWrap: pillWrap, justifyContent: "center" }, children: [
10466
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: rowStyle, children: [
10345
10467
  HOVERS.map((h) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Pill, { label: h.label, active: hover === h.value, onClick: () => onSetHover(h.value) }, h.value)),
10346
10468
  TREATMENTS.map((t) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Pill, { label: t.label, active: treatment === t.value, onClick: () => onSetTreatment(t.value) }, t.value))
10347
10469
  ] }),
@@ -10353,10 +10475,13 @@ function MediaStripToolbar({
10353
10475
  ] }),
10354
10476
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Pill, { label: "+", onClick: () => onResize(8) })
10355
10477
  ] }),
10356
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { display: "flex", gap: 6, flexWrap: pillWrap, justifyContent: "center" }, children: [
10478
+ /* @__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 }) }),
10479
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: rowStyle, children: [
10357
10480
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Pill, { label: "Change Image", onClick: onChangeImage }),
10358
10481
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Pill, { label: "+ Item", onClick: onAddItem }),
10359
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Pill, { label: "\u2212 Item", onClick: onRemoveItem, disabled: itemCount <= 1 }),
10482
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Pill, { label: "\u2212 Item", onClick: onRemoveItem, disabled: itemCount <= 1 })
10483
+ ] }),
10484
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: rowStyle, children: [
10360
10485
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Pill, { label: "\u2190 Move", onClick: onMoveLeft, disabled: index <= 0 }),
10361
10486
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Pill, { label: "\u2192 Move", onClick: onMoveRight, disabled: index >= itemCount - 1 })
10362
10487
  ] }),
@@ -10377,7 +10502,8 @@ function findMediaStripSlots(tree) {
10377
10502
  function AiMediaStripOverlay({
10378
10503
  postToParent: postToParent2,
10379
10504
  aiSectionsRef,
10380
- applyTreeLocalRef
10505
+ applyTreeLocalRef,
10506
+ clearInteractionChromeRef
10381
10507
  }) {
10382
10508
  const [hover, setHover] = (0, import_react9.useState)(null);
10383
10509
  const [active, setActive] = (0, import_react9.useState)(null);
@@ -10494,7 +10620,10 @@ function AiMediaStripOverlay({
10494
10620
  {
10495
10621
  type: "button",
10496
10622
  "data-ohw-toolbar": "",
10497
- onClick: () => setActive(hover),
10623
+ onClick: () => {
10624
+ clearInteractionChromeRef.current();
10625
+ setActive(hover);
10626
+ },
10498
10627
  "aria-label": "Configure media item",
10499
10628
  style: {
10500
10629
  position: "fixed",
@@ -10539,6 +10668,9 @@ function AiMediaStripOverlay({
10539
10668
  const current = variant === "logos" ? typeof slots?.logoMaxHeight === "number" ? slots.logoMaxHeight : 48 : typeof slots?.cardWidth === "number" ? slots.cardWidth : 240;
10540
10669
  void runMutation(active.sectionId, (tree) => setMediaStripSizing(tree, current + delta));
10541
10670
  },
10671
+ onSetAlt: (alt) => {
10672
+ void runMutation(active.sectionId, (tree) => setMediaStripItemAlt(tree, active.index, alt));
10673
+ },
10542
10674
  onAddItem: () => {
10543
10675
  void requestAssetPick(postToParent2).then((picked) => {
10544
10676
  if (!picked) return;
@@ -20547,6 +20679,8 @@ function OhhwellsBridge() {
20547
20679
  const addNavAfterAnchorRef = (0, import_react18.useRef)(null);
20548
20680
  const editContentRef = (0, import_react18.useRef)({});
20549
20681
  const aiSectionsRef = (0, import_react18.useRef)("");
20682
+ const clearInteractionChromeRef = (0, import_react18.useRef)(() => {
20683
+ });
20550
20684
  const brandKitRef = (0, import_react18.useRef)("");
20551
20685
  const stylesRef = (0, import_react18.useRef)("");
20552
20686
  const appliedDrawerRef = (0, import_react18.useRef)(null);
@@ -22106,6 +22240,7 @@ function OhhwellsBridge() {
22106
22240
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
22107
22241
  }
22108
22242
  applyBrandChrome(content);
22243
+ applyCustomCodeToHead(content);
22109
22244
  initSectionInstancesFromContent(content, window.location.pathname);
22110
22245
  for (const [key, val] of Object.entries(content)) {
22111
22246
  if (key === "__ohw_sections") continue;
@@ -22115,6 +22250,7 @@ function OhhwellsBridge() {
22115
22250
  if (key === BRAND_KIT_KEY) continue;
22116
22251
  if (key === STYLE_STORE_KEY) continue;
22117
22252
  if (BRAND_CHROME_KEYS.has(key)) continue;
22253
+ if (key === CUSTOM_CODE_AFTER_HEAD_KEY || key === CUSTOM_CODE_BEFORE_HEAD_KEY) continue;
22118
22254
  if (applyVideoSettingNode(key, val)) continue;
22119
22255
  if (applyCarouselNode(key, val)) continue;
22120
22256
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -24526,6 +24662,7 @@ function OhhwellsBridge() {
24526
24662
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
24527
24663
  }
24528
24664
  applyBrandChrome(content);
24665
+ applyCustomCodeToHead(content);
24529
24666
  initSectionInstancesFromContent(content, window.location.pathname);
24530
24667
  let sectionsJson = null;
24531
24668
  for (const [key, val] of Object.entries(content)) {
@@ -24539,6 +24676,7 @@ function OhhwellsBridge() {
24539
24676
  if (key === BRAND_KIT_KEY) continue;
24540
24677
  if (key === STYLE_STORE_KEY) continue;
24541
24678
  if (BRAND_CHROME_KEYS.has(key)) continue;
24679
+ if (key === CUSTOM_CODE_AFTER_HEAD_KEY || key === CUSTOM_CODE_BEFORE_HEAD_KEY) continue;
24542
24680
  if (applyVideoSettingNode(key, val)) continue;
24543
24681
  if (applyCarouselNode(key, val)) continue;
24544
24682
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -24660,6 +24798,7 @@ function OhhwellsBridge() {
24660
24798
  deselectRef.current();
24661
24799
  clearMediaSelectionRef.current();
24662
24800
  };
24801
+ clearInteractionChromeRef.current = clearInteractionChrome;
24663
24802
  const applyTreeToLocalState = (payload) => {
24664
24803
  clearInteractionChrome();
24665
24804
  const previous = aiSectionsRef.current;
@@ -25255,6 +25394,16 @@ function OhhwellsBridge() {
25255
25394
  if (brandKitRef.current) {
25256
25395
  nodes.push({ key: BRAND_KIT_KEY, type: "brand", text: brandKitRef.current });
25257
25396
  }
25397
+ nodes.push({
25398
+ key: CUSTOM_CODE_BEFORE_HEAD_KEY,
25399
+ type: "text",
25400
+ text: editContentRef.current[CUSTOM_CODE_BEFORE_HEAD_KEY] ?? ""
25401
+ });
25402
+ nodes.push({
25403
+ key: CUSTOM_CODE_AFTER_HEAD_KEY,
25404
+ type: "text",
25405
+ text: editContentRef.current[CUSTOM_CODE_AFTER_HEAD_KEY] ?? ""
25406
+ });
25258
25407
  document.querySelectorAll('[data-ohw-editable="form"]').forEach((form) => {
25259
25408
  const formKey = formKeyOf(form);
25260
25409
  if (!formKey) return;
@@ -26550,7 +26699,8 @@ function OhhwellsBridge() {
26550
26699
  {
26551
26700
  postToParent: postToParent2,
26552
26701
  aiSectionsRef,
26553
- applyTreeLocalRef
26702
+ applyTreeLocalRef,
26703
+ clearInteractionChromeRef
26554
26704
  }
26555
26705
  ),
26556
26706
  isSectionDragging && sectionDropSlots.map((slot, i) => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(