@ohhwells/bridge 0.1.139-next.383 → 0.1.139-next.386

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
@@ -2479,6 +2479,49 @@ function socialHrefKey(item) {
2479
2479
  function socialIconKey(item) {
2480
2480
  return item.querySelector(ICON_SELECTOR)?.dataset.ohwKey ?? null;
2481
2481
  }
2482
+ var SOCIAL_PLATFORM_LABELS = {
2483
+ instagram: "Instagram",
2484
+ facebook: "Facebook",
2485
+ x: "X",
2486
+ twitter: "X",
2487
+ tiktok: "TikTok",
2488
+ youtube: "YouTube",
2489
+ linkedin: "LinkedIn",
2490
+ pinterest: "Pinterest",
2491
+ whatsapp: "WhatsApp",
2492
+ telegram: "Telegram",
2493
+ spotify: "Spotify",
2494
+ threads: "Threads",
2495
+ vimeo: "Vimeo",
2496
+ discord: "Discord",
2497
+ medium: "Medium",
2498
+ website: "Website"
2499
+ };
2500
+ function socialPlatformLabel(item, content) {
2501
+ const iconKey = socialIconKey(item);
2502
+ const chosen = iconKey ? (content[socialPlatformKey(iconKey)] ?? "").trim().toLowerCase() : "";
2503
+ if (chosen) return SOCIAL_PLATFORM_LABELS[chosen] ?? chosen.charAt(0).toUpperCase() + chosen.slice(1);
2504
+ const label = socialLabelElement(item);
2505
+ const words = label && !label.hasAttribute(SOCIALS_EMPTY_LABEL_ATTR) ? (label.textContent ?? "").replace(/\s+/g, " ").trim() : "";
2506
+ if (words) return words;
2507
+ const link = item.matches("a") ? item : item.querySelector("a");
2508
+ const aria = (link?.getAttribute("aria-label") ?? item.getAttribute("aria-label") ?? "").trim();
2509
+ if (aria) return aria;
2510
+ const href = (link?.getAttribute("href") ?? "").trim();
2511
+ if (/^https?:\/\//i.test(href)) {
2512
+ let host = "";
2513
+ try {
2514
+ host = new URL(href).hostname.replace(/^www\./i, "").toLowerCase();
2515
+ } catch {
2516
+ host = "";
2517
+ }
2518
+ if (host.includes("x.com") || host.includes("twitter.com")) return "X";
2519
+ for (const [id, name] of Object.entries(SOCIAL_PLATFORM_LABELS)) {
2520
+ if (host.includes(id)) return name;
2521
+ }
2522
+ }
2523
+ return "Social";
2524
+ }
2482
2525
  var SOCIALS_ORDER_KEY = "__ohw_socials_order";
2483
2526
  function fromMarkup(markup) {
2484
2527
  const holder = document.createElement("div");
@@ -6558,6 +6601,13 @@ function applyIconNameToTree(tree, path, icon) {
6558
6601
  if (feature) {
6559
6602
  return setPricingCardFeatureIcon(tree, Number(feature[1]), Number(feature[2]), icon);
6560
6603
  }
6604
+ return rewriteIconSlot(tree, path, `lucide:${icon}`);
6605
+ }
6606
+ function applyIconUploadToTree(tree, path, mediaRef) {
6607
+ if (FEATURE_ICON_PATH.test(path)) return null;
6608
+ return rewriteIconSlot(tree, path, `icon:${mediaRef}`);
6609
+ }
6610
+ function rewriteIconSlot(tree, path, ref) {
6561
6611
  const segments = path.split(".");
6562
6612
  const slot = segments.pop();
6563
6613
  if (!slot || NODE_SEGMENT.test(slot)) return null;
@@ -6565,8 +6615,8 @@ function applyIconNameToTree(tree, path, icon) {
6565
6615
  const node = nodeAtPath(next, segments);
6566
6616
  if (!node) return null;
6567
6617
  const current = node.slots?.[slot];
6568
- if (typeof current !== "string" || !/^(lucide|simple):/.test(current)) return null;
6569
- node.slots = { ...node.slots, [slot]: `lucide:${icon}` };
6618
+ if (typeof current !== "string" || !/^(lucide|simple|icon):/.test(current)) return null;
6619
+ node.slots = { ...node.slots, [slot]: ref };
6570
6620
  return next;
6571
6621
  }
6572
6622
 
@@ -7953,7 +8003,7 @@ function MediaBox({
7953
8003
  onError
7954
8004
  }) {
7955
8005
  const url = refValue ? ctx.resolveMedia(refValue) : null;
7956
- const isIcon = /^(lucide|simple):/.test(refValue);
8006
+ const isIcon = /^(lucide|simple|icon):/.test(refValue);
7957
8007
  const aspectRatio = aspect && /^\d+:\d+$/.test(aspect) ? aspect.replace(":", " / ") : void 0;
7958
8008
  const editAttrs = ctx.keyFor && editPath && !isIcon ? {
7959
8009
  "data-ohw-key": ctx.keyFor(editPath),
@@ -7961,6 +8011,7 @@ function MediaBox({
7961
8011
  } : {};
7962
8012
  if (isIcon) {
7963
8013
  const Icon = refValue.startsWith("lucide:") ? lucideByName(refValue.slice(7)) : null;
8014
+ const uploadedIconUrl = refValue.startsWith("icon:") ? ctx.resolveMedia(refValue.slice(5)) : null;
7964
8015
  const iconEditAttrs = ctx.keyFor && editPath && genericImageEdit ? { "data-ohw-key": ctx.keyFor(editPath), "data-ohw-editable": "icon" } : {};
7965
8016
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
7966
8017
  "span",
@@ -7975,7 +8026,7 @@ function MediaBox({
7975
8026
  justifyContent: "center",
7976
8027
  color: ctx.brand.palette.dark
7977
8028
  },
7978
- children: Icon ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { size: 48, color: ctx.brand.palette.dark, strokeWidth: 1.5 }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 24 }, children: "\u25C6" })
8029
+ children: uploadedIconUrl ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: uploadedIconUrl, alt: "", style: { width: 48, height: 48, objectFit: "contain" } }) : Icon ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { size: 48, color: ctx.brand.palette.dark, strokeWidth: 1.5 }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 24 }, children: "\u25C6" })
7979
8030
  }
7980
8031
  );
7981
8032
  }
@@ -25164,36 +25215,72 @@ function OhhwellsBridge() {
25164
25215
  const [selectedMedia, setSelectedMedia] = (0, import_react21.useState)(null);
25165
25216
  const selectedMediaElRef = (0, import_react21.useRef)(null);
25166
25217
  const mediaKeyDropTokenRef = (0, import_react21.useRef)(0);
25218
+ const selectedIconElRef = (0, import_react21.useRef)(null);
25219
+ const selectedSocialElRef = (0, import_react21.useRef)(null);
25220
+ const queueSectionKeyDrop = (0, import_react21.useCallback)((prev) => {
25221
+ const sectionEl = prev?.closest("[data-ohw-section]") ?? null;
25222
+ if (!sectionEl) return;
25223
+ const sectionInstanceId = instanceIdOf(movableUnit(sectionEl)) || null;
25224
+ if (!sectionInstanceId) return;
25225
+ const token = ++mediaKeyDropTokenRef.current;
25226
+ const onQueueDrained = (e) => {
25227
+ if (e.source !== window || e.data?.type !== "ow:media-key-drop" || e.data.token !== token) return;
25228
+ window.removeEventListener("message", onQueueDrained);
25229
+ if (aiSectionApiRef.current?.getSelectedId() !== sectionInstanceId) return;
25230
+ if (selectedMediaElRef.current || selectedIconElRef.current || selectedSocialElRef.current) return;
25231
+ postToParentRef.current({
25232
+ type: "ow:section-selected",
25233
+ sectionId: sectionEl.dataset.ohwSection ?? null,
25234
+ sectionLabel: sectionEl.dataset.ohwSectionLabel ?? titleCaseSectionId(sectionEl.dataset.ohwSection ?? ""),
25235
+ key: null
25236
+ });
25237
+ };
25238
+ window.addEventListener("message", onQueueDrained);
25239
+ window.postMessage({ type: "ow:media-key-drop", token }, "*");
25240
+ }, []);
25167
25241
  const clearMediaSelection = (0, import_react21.useCallback)(() => {
25168
25242
  const prev = selectedMediaElRef.current;
25169
25243
  selectedMediaElRef.current = null;
25170
25244
  setSelectedMedia(null);
25171
- const sectionEl = prev?.closest("[data-ohw-section]") ?? null;
25172
- if (sectionEl) {
25173
- const sectionInstanceId = instanceIdOf(movableUnit(sectionEl)) || null;
25174
- if (!sectionInstanceId) return;
25175
- const token = ++mediaKeyDropTokenRef.current;
25176
- const onQueueDrained = (e) => {
25177
- if (e.source !== window || e.data?.type !== "ow:media-key-drop" || e.data.token !== token) return;
25178
- window.removeEventListener("message", onQueueDrained);
25179
- if (aiSectionApiRef.current?.getSelectedId() !== sectionInstanceId) return;
25180
- if (selectedMediaElRef.current) return;
25181
- postToParentRef.current({
25182
- type: "ow:section-selected",
25183
- sectionId: sectionEl.dataset.ohwSection ?? null,
25184
- sectionLabel: sectionEl.dataset.ohwSectionLabel ?? titleCaseSectionId(sectionEl.dataset.ohwSection ?? ""),
25185
- key: null
25186
- });
25187
- };
25188
- window.addEventListener("message", onQueueDrained);
25189
- window.postMessage({ type: "ow:media-key-drop", token }, "*");
25190
- }
25191
- }, []);
25245
+ queueSectionKeyDrop(prev);
25246
+ }, [queueSectionKeyDrop]);
25192
25247
  const clearMediaSelectionRef = (0, import_react21.useRef)(clearMediaSelection);
25193
25248
  clearMediaSelectionRef.current = clearMediaSelection;
25249
+ const clearIconSelection = (0, import_react21.useCallback)(() => {
25250
+ const prev = selectedIconElRef.current;
25251
+ if (!prev) return;
25252
+ selectedIconElRef.current = null;
25253
+ queueSectionKeyDrop(prev);
25254
+ }, [queueSectionKeyDrop]);
25255
+ const clearIconSelectionRef = (0, import_react21.useRef)(clearIconSelection);
25256
+ clearIconSelectionRef.current = clearIconSelection;
25257
+ const clearSocialSelection = (0, import_react21.useCallback)(() => {
25258
+ const prev = selectedSocialElRef.current;
25259
+ if (!prev) return;
25260
+ selectedSocialElRef.current = null;
25261
+ queueSectionKeyDrop(prev);
25262
+ }, [queueSectionKeyDrop]);
25263
+ const clearSocialSelectionRef = (0, import_react21.useRef)(clearSocialSelection);
25264
+ clearSocialSelectionRef.current = clearSocialSelection;
25265
+ const selectIconElement = (0, import_react21.useCallback)((el) => {
25266
+ if (selectedMediaElRef.current) clearMediaSelectionRef.current();
25267
+ selectedIconElRef.current = el;
25268
+ const sectionEl = el.closest("[data-ohw-section]");
25269
+ aiSectionApiRef.current?.selectFromElement(el, { report: false });
25270
+ postToParentRef.current({
25271
+ type: "ow:section-selected",
25272
+ sectionId: sectionEl?.dataset.ohwSection ?? null,
25273
+ sectionLabel: sectionEl ? sectionEl.dataset.ohwSectionLabel ?? titleCaseSectionId(sectionEl.dataset.ohwSection ?? "") : null,
25274
+ key: el.dataset.ohwKey ?? null,
25275
+ keyLabel: "Icon"
25276
+ });
25277
+ }, []);
25278
+ const selectIconElementRef = (0, import_react21.useRef)(selectIconElement);
25279
+ selectIconElementRef.current = selectIconElement;
25194
25280
  const selectMediaElement = (0, import_react21.useCallback)((el) => {
25195
25281
  const r2 = el.getBoundingClientRect();
25196
25282
  const video = el.dataset.ohwEditable === "video" ? el.querySelector("video") : null;
25283
+ selectedIconElRef.current = null;
25197
25284
  selectedMediaElRef.current = el;
25198
25285
  setSelectedMedia({
25199
25286
  key: el.dataset.ohwKey ?? "",
@@ -25858,6 +25945,7 @@ function OhhwellsBridge() {
25858
25945
  selectedElRef.current = null;
25859
25946
  selectedHrefKeyRef.current = null;
25860
25947
  selectedFooterColAttrRef.current = null;
25948
+ clearSocialSelectionRef.current();
25861
25949
  setSelectedIsCta(false);
25862
25950
  setSelectedIsSocial(false);
25863
25951
  setSelectedIsSocialsRow(false);
@@ -26759,7 +26847,22 @@ function OhhwellsBridge() {
26759
26847
  const select = (0, import_react21.useCallback)((anchor) => {
26760
26848
  if (!isNavigationItem2(anchor)) return;
26761
26849
  if (activeElRef.current) deactivate();
26762
- aiSectionApiRef.current?.selectFromElement(anchor);
26850
+ clearSocialSelectionRef.current();
26851
+ const socialItem = getSocialItem(anchor);
26852
+ if (socialItem) {
26853
+ selectedSocialElRef.current = socialItem;
26854
+ const sectionEl = socialItem.closest("[data-ohw-section]");
26855
+ aiSectionApiRef.current?.selectFromElement(anchor, { report: false });
26856
+ postToParentRef.current({
26857
+ type: "ow:section-selected",
26858
+ sectionId: sectionEl?.dataset.ohwSection ?? null,
26859
+ sectionLabel: sectionEl ? sectionEl.dataset.ohwSectionLabel ?? titleCaseSectionId(sectionEl.dataset.ohwSection ?? "") : null,
26860
+ key: socialIconKey(socialItem) ?? socialItem.getAttribute("data-ohw-href-key"),
26861
+ keyLabel: socialPlatformLabel(socialItem, editContentRef.current)
26862
+ });
26863
+ } else {
26864
+ aiSectionApiRef.current?.selectFromElement(anchor);
26865
+ }
26763
26866
  selectedElRef.current = anchor;
26764
26867
  selectedHrefKeyRef.current = anchor.getAttribute("data-ohw-href-key");
26765
26868
  selectedFooterColAttrRef.current = null;
@@ -28068,6 +28171,9 @@ function OhhwellsBridge() {
28068
28171
  if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
28069
28172
  clearMediaSelectionRef.current();
28070
28173
  }
28174
+ if (selectedIconElRef.current && !selectedIconElRef.current.contains(target)) {
28175
+ clearIconSelectionRef.current();
28176
+ }
28071
28177
  if (selectedMediaElRef.current && target.closest("[data-ohw-media-overlay]") && !findClosestButtonLike(target)) {
28072
28178
  const hit = findImageAtPoint(e.clientX, e.clientY, false);
28073
28179
  if (hit && hit !== selectedMediaElRef.current) {
@@ -28238,9 +28344,13 @@ function OhhwellsBridge() {
28238
28344
  if (isIconEditable(editable) && !getSocialItem(editable)) {
28239
28345
  e.preventDefault();
28240
28346
  e.stopPropagation();
28241
- aiSectionApiRef.current?.selectFromElement(editable);
28242
28347
  const aiIcon = editable.hasAttribute("data-ai-icon");
28243
28348
  const featureIcon = aiIcon && /\.features\.\d+\.icon$/.test(editable.dataset.ohwKey ?? "");
28349
+ if (aiIcon) {
28350
+ aiSectionApiRef.current?.selectFromElement(editable);
28351
+ } else {
28352
+ selectIconElementRef.current(editable);
28353
+ }
28244
28354
  postToParentRef.current({
28245
28355
  type: "ow:icon-pick",
28246
28356
  key: editable.dataset.ohwKey ?? "",
@@ -29519,12 +29629,16 @@ function OhhwellsBridge() {
29519
29629
  };
29520
29630
  const handleIconName = (e) => {
29521
29631
  if (e.data?.type !== "ow:icon-name") return;
29522
- const { key, icon } = e.data;
29523
- if (!key || typeof icon !== "string" || !/^[a-z0-9-]+$/.test(icon)) return;
29632
+ const { key, icon, url } = e.data;
29633
+ const isName = typeof icon === "string" && /^[a-z0-9-]+$/.test(icon);
29634
+ const isUpload = !isName && typeof url === "string" && /^https?:\/\/\S+$/i.test(url);
29635
+ if (!key || !isName && !isUpload) return;
29524
29636
  const state = parseAiSectionsState(aiSectionsRef.current);
29525
29637
  const entry = state.sections.find((s) => key.startsWith(aiSlotKeyPrefixFor(s.id)));
29526
29638
  if (!entry) return;
29527
- const nextTree = applyIconNameToTree(entry.tree, key.slice(aiSlotKeyPrefixFor(entry.id).length), icon);
29639
+ const path = key.slice(aiSlotKeyPrefixFor(entry.id).length);
29640
+ const uploadRef = isUpload ? `icon-${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}` : null;
29641
+ const nextTree = uploadRef ? applyIconUploadToTree(entry.tree, path, uploadRef) : applyIconNameToTree(entry.tree, path, icon);
29528
29642
  if (!nextTree) return;
29529
29643
  void requestValidateTree5(postToParentRef.current, nextTree).then(({ ok }) => {
29530
29644
  if (!ok) return;
@@ -29534,7 +29648,7 @@ function OhhwellsBridge() {
29534
29648
  targetSectionId: entry.id,
29535
29649
  tree: nextTree,
29536
29650
  label: entry.label,
29537
- media: entry.media,
29651
+ media: uploadRef ? { ...entry.media, [uploadRef]: url } : entry.media,
29538
29652
  ...entry.brand ? { brand: entry.brand } : {},
29539
29653
  // The user is looking at the icon they just changed — no need to recenter on it.
29540
29654
  skipScrollIntoView: true
@@ -29874,6 +29988,7 @@ function OhhwellsBridge() {
29874
29988
  deactivateRef.current();
29875
29989
  deselectRef.current();
29876
29990
  clearMediaSelectionRef.current();
29991
+ clearIconSelectionRef.current();
29877
29992
  };
29878
29993
  clearInteractionChromeRef.current = clearInteractionChrome;
29879
29994
  const applyTreeToLocalState = (payload) => {
@@ -30297,6 +30412,7 @@ function OhhwellsBridge() {
30297
30412
  deselectRef.current();
30298
30413
  deactivateRef.current();
30299
30414
  clearMediaSelectionRef.current();
30415
+ clearIconSelectionRef.current();
30300
30416
  };
30301
30417
  window.addEventListener("message", handleDeactivate);
30302
30418
  const handleToastAction = (e) => {
@@ -30386,6 +30502,10 @@ function OhhwellsBridge() {
30386
30502
  clearMediaSelectionRef.current();
30387
30503
  return;
30388
30504
  }
30505
+ if (e.key === "Escape" && selectedIconElRef.current) {
30506
+ clearIconSelectionRef.current();
30507
+ return;
30508
+ }
30389
30509
  if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "a" && activeElRef.current) {
30390
30510
  e.preventDefault();
30391
30511
  selectAllTextInEditable(activeElRef.current);