@ohhwells/bridge 0.1.144 → 0.1.145

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
@@ -2534,6 +2534,49 @@ function socialHrefKey(item) {
2534
2534
  function socialIconKey(item) {
2535
2535
  return item.querySelector(ICON_SELECTOR)?.dataset.ohwKey ?? null;
2536
2536
  }
2537
+ var SOCIAL_PLATFORM_LABELS = {
2538
+ instagram: "Instagram",
2539
+ facebook: "Facebook",
2540
+ x: "X",
2541
+ twitter: "X",
2542
+ tiktok: "TikTok",
2543
+ youtube: "YouTube",
2544
+ linkedin: "LinkedIn",
2545
+ pinterest: "Pinterest",
2546
+ whatsapp: "WhatsApp",
2547
+ telegram: "Telegram",
2548
+ spotify: "Spotify",
2549
+ threads: "Threads",
2550
+ vimeo: "Vimeo",
2551
+ discord: "Discord",
2552
+ medium: "Medium",
2553
+ website: "Website"
2554
+ };
2555
+ function socialPlatformLabel(item, content) {
2556
+ const iconKey = socialIconKey(item);
2557
+ const chosen = iconKey ? (content[socialPlatformKey(iconKey)] ?? "").trim().toLowerCase() : "";
2558
+ if (chosen) return SOCIAL_PLATFORM_LABELS[chosen] ?? chosen.charAt(0).toUpperCase() + chosen.slice(1);
2559
+ const label = socialLabelElement(item);
2560
+ const words = label && !label.hasAttribute(SOCIALS_EMPTY_LABEL_ATTR) ? (label.textContent ?? "").replace(/\s+/g, " ").trim() : "";
2561
+ if (words) return words;
2562
+ const link = item.matches("a") ? item : item.querySelector("a");
2563
+ const aria = (link?.getAttribute("aria-label") ?? item.getAttribute("aria-label") ?? "").trim();
2564
+ if (aria) return aria;
2565
+ const href = (link?.getAttribute("href") ?? "").trim();
2566
+ if (/^https?:\/\//i.test(href)) {
2567
+ let host = "";
2568
+ try {
2569
+ host = new URL(href).hostname.replace(/^www\./i, "").toLowerCase();
2570
+ } catch {
2571
+ host = "";
2572
+ }
2573
+ if (host.includes("x.com") || host.includes("twitter.com")) return "X";
2574
+ for (const [id, name] of Object.entries(SOCIAL_PLATFORM_LABELS)) {
2575
+ if (host.includes(id)) return name;
2576
+ }
2577
+ }
2578
+ return "Social";
2579
+ }
2537
2580
  var SOCIALS_ORDER_KEY = "__ohw_socials_order";
2538
2581
  function fromMarkup(markup) {
2539
2582
  const holder = document.createElement("div");
@@ -25175,36 +25218,72 @@ function OhhwellsBridge() {
25175
25218
  const [selectedMedia, setSelectedMedia] = (0, import_react21.useState)(null);
25176
25219
  const selectedMediaElRef = (0, import_react21.useRef)(null);
25177
25220
  const mediaKeyDropTokenRef = (0, import_react21.useRef)(0);
25221
+ const selectedIconElRef = (0, import_react21.useRef)(null);
25222
+ const selectedSocialElRef = (0, import_react21.useRef)(null);
25223
+ const queueSectionKeyDrop = (0, import_react21.useCallback)((prev) => {
25224
+ const sectionEl = prev?.closest("[data-ohw-section]") ?? null;
25225
+ if (!sectionEl) return;
25226
+ const sectionInstanceId = instanceIdOf(movableUnit(sectionEl)) || null;
25227
+ if (!sectionInstanceId) return;
25228
+ const token = ++mediaKeyDropTokenRef.current;
25229
+ const onQueueDrained = (e) => {
25230
+ if (e.source !== window || e.data?.type !== "ow:media-key-drop" || e.data.token !== token) return;
25231
+ window.removeEventListener("message", onQueueDrained);
25232
+ if (aiSectionApiRef.current?.getSelectedId() !== sectionInstanceId) return;
25233
+ if (selectedMediaElRef.current || selectedIconElRef.current || selectedSocialElRef.current) return;
25234
+ postToParentRef.current({
25235
+ type: "ow:section-selected",
25236
+ sectionId: sectionEl.dataset.ohwSection ?? null,
25237
+ sectionLabel: sectionEl.dataset.ohwSectionLabel ?? titleCaseSectionId(sectionEl.dataset.ohwSection ?? ""),
25238
+ key: null
25239
+ });
25240
+ };
25241
+ window.addEventListener("message", onQueueDrained);
25242
+ window.postMessage({ type: "ow:media-key-drop", token }, "*");
25243
+ }, []);
25178
25244
  const clearMediaSelection = (0, import_react21.useCallback)(() => {
25179
25245
  const prev = selectedMediaElRef.current;
25180
25246
  selectedMediaElRef.current = null;
25181
25247
  setSelectedMedia(null);
25182
- const sectionEl = prev?.closest("[data-ohw-section]") ?? null;
25183
- if (sectionEl) {
25184
- const sectionInstanceId = instanceIdOf(movableUnit(sectionEl)) || null;
25185
- if (!sectionInstanceId) return;
25186
- const token = ++mediaKeyDropTokenRef.current;
25187
- const onQueueDrained = (e) => {
25188
- if (e.source !== window || e.data?.type !== "ow:media-key-drop" || e.data.token !== token) return;
25189
- window.removeEventListener("message", onQueueDrained);
25190
- if (aiSectionApiRef.current?.getSelectedId() !== sectionInstanceId) return;
25191
- if (selectedMediaElRef.current) return;
25192
- postToParentRef.current({
25193
- type: "ow:section-selected",
25194
- sectionId: sectionEl.dataset.ohwSection ?? null,
25195
- sectionLabel: sectionEl.dataset.ohwSectionLabel ?? titleCaseSectionId(sectionEl.dataset.ohwSection ?? ""),
25196
- key: null
25197
- });
25198
- };
25199
- window.addEventListener("message", onQueueDrained);
25200
- window.postMessage({ type: "ow:media-key-drop", token }, "*");
25201
- }
25202
- }, []);
25248
+ queueSectionKeyDrop(prev);
25249
+ }, [queueSectionKeyDrop]);
25203
25250
  const clearMediaSelectionRef = (0, import_react21.useRef)(clearMediaSelection);
25204
25251
  clearMediaSelectionRef.current = clearMediaSelection;
25252
+ const clearIconSelection = (0, import_react21.useCallback)(() => {
25253
+ const prev = selectedIconElRef.current;
25254
+ if (!prev) return;
25255
+ selectedIconElRef.current = null;
25256
+ queueSectionKeyDrop(prev);
25257
+ }, [queueSectionKeyDrop]);
25258
+ const clearIconSelectionRef = (0, import_react21.useRef)(clearIconSelection);
25259
+ clearIconSelectionRef.current = clearIconSelection;
25260
+ const clearSocialSelection = (0, import_react21.useCallback)(() => {
25261
+ const prev = selectedSocialElRef.current;
25262
+ if (!prev) return;
25263
+ selectedSocialElRef.current = null;
25264
+ queueSectionKeyDrop(prev);
25265
+ }, [queueSectionKeyDrop]);
25266
+ const clearSocialSelectionRef = (0, import_react21.useRef)(clearSocialSelection);
25267
+ clearSocialSelectionRef.current = clearSocialSelection;
25268
+ const selectIconElement = (0, import_react21.useCallback)((el) => {
25269
+ if (selectedMediaElRef.current) clearMediaSelectionRef.current();
25270
+ selectedIconElRef.current = el;
25271
+ const sectionEl = el.closest("[data-ohw-section]");
25272
+ aiSectionApiRef.current?.selectFromElement(el, { report: false });
25273
+ postToParentRef.current({
25274
+ type: "ow:section-selected",
25275
+ sectionId: sectionEl?.dataset.ohwSection ?? null,
25276
+ sectionLabel: sectionEl ? sectionEl.dataset.ohwSectionLabel ?? titleCaseSectionId(sectionEl.dataset.ohwSection ?? "") : null,
25277
+ key: el.dataset.ohwKey ?? null,
25278
+ keyLabel: "Icon"
25279
+ });
25280
+ }, []);
25281
+ const selectIconElementRef = (0, import_react21.useRef)(selectIconElement);
25282
+ selectIconElementRef.current = selectIconElement;
25205
25283
  const selectMediaElement = (0, import_react21.useCallback)((el) => {
25206
25284
  const r2 = el.getBoundingClientRect();
25207
25285
  const video = el.dataset.ohwEditable === "video" ? el.querySelector("video") : null;
25286
+ selectedIconElRef.current = null;
25208
25287
  selectedMediaElRef.current = el;
25209
25288
  setSelectedMedia({
25210
25289
  key: el.dataset.ohwKey ?? "",
@@ -25858,6 +25937,7 @@ function OhhwellsBridge() {
25858
25937
  selectedElRef.current = null;
25859
25938
  selectedHrefKeyRef.current = null;
25860
25939
  selectedFooterColAttrRef.current = null;
25940
+ clearSocialSelectionRef.current();
25861
25941
  setSelectedIsCta(false);
25862
25942
  setSelectedIsSocial(false);
25863
25943
  setSelectedIsSocialsRow(false);
@@ -26759,7 +26839,22 @@ function OhhwellsBridge() {
26759
26839
  const select = (0, import_react21.useCallback)((anchor) => {
26760
26840
  if (!isNavigationItem2(anchor)) return;
26761
26841
  if (activeElRef.current) deactivate();
26762
- aiSectionApiRef.current?.selectFromElement(anchor);
26842
+ clearSocialSelectionRef.current();
26843
+ const socialItem = getSocialItem(anchor);
26844
+ if (socialItem) {
26845
+ selectedSocialElRef.current = socialItem;
26846
+ const sectionEl = socialItem.closest("[data-ohw-section]");
26847
+ aiSectionApiRef.current?.selectFromElement(anchor, { report: false });
26848
+ postToParentRef.current({
26849
+ type: "ow:section-selected",
26850
+ sectionId: sectionEl?.dataset.ohwSection ?? null,
26851
+ sectionLabel: sectionEl ? sectionEl.dataset.ohwSectionLabel ?? titleCaseSectionId(sectionEl.dataset.ohwSection ?? "") : null,
26852
+ key: socialIconKey(socialItem) ?? socialItem.getAttribute("data-ohw-href-key"),
26853
+ keyLabel: socialPlatformLabel(socialItem, editContentRef.current)
26854
+ });
26855
+ } else {
26856
+ aiSectionApiRef.current?.selectFromElement(anchor);
26857
+ }
26763
26858
  selectedElRef.current = anchor;
26764
26859
  selectedHrefKeyRef.current = anchor.getAttribute("data-ohw-href-key");
26765
26860
  selectedFooterColAttrRef.current = null;
@@ -27897,8 +27992,11 @@ function OhhwellsBridge() {
27897
27992
  display: none !important;
27898
27993
  }
27899
27994
  /* Body text (no item-action toolbar) \u2014 first click enters text edit \u2192 I-beam.
27900
- Exclude select-first nav/footer labels so grab/default on [data-ohw-href-key] can win. */
27901
- [data-ohw-editable]:not([contenteditable]):not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"]):not([data-ohw-editable="video"]):not([data-ohw-editable="link"]):not(:is([data-ohw-href-key] *)) {
27995
+ Exclude select-first nav/footer labels so grab/default on [data-ohw-href-key] can win.
27996
+ Icons are excluded too: a glyph is replaced by a click, never typed into (OHH-623),
27997
+ so it takes the picker's pointer below, not an I-beam \u2014 this blanket !important used
27998
+ to beat the stylesheet's own non-!important icon rule (OHH-718). */
27999
+ [data-ohw-editable]:not([contenteditable]):not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"]):not([data-ohw-editable="video"]):not([data-ohw-editable="link"]):not([data-ohw-editable="icon"]):not(:is([data-ohw-href-key] *)) {
27902
28000
  cursor: text !important;
27903
28001
  }
27904
28002
  /* Select-first items: default arrow; grab only when drag-start is allowed (data-ohw-can-drag). */
@@ -27919,6 +28017,7 @@ function OhhwellsBridge() {
27919
28017
  [data-ohw-editable="video"], [data-ohw-editable="video"] *,
27920
28018
  [data-ohw-editable="bg-image"], [data-ohw-editable="bg-image"] * { cursor: pointer !important; }
27921
28019
  [data-ohw-editable="link"], [data-ohw-editable="link"] * { cursor: pointer !important; }
28020
+ [data-ohw-editable="icon"], [data-ohw-editable="icon"] * { cursor: pointer !important; }
27922
28021
  /* A text/plain editable nested inside a media editable (e.g. a placeholder's fallback
27923
28022
  label inside its bg-image box) is its own click target, not the media picker \u2014 the
27924
28023
  blanket media cursor rule above would otherwise show pointer where a click actually
@@ -28070,6 +28169,9 @@ function OhhwellsBridge() {
28070
28169
  if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
28071
28170
  clearMediaSelectionRef.current();
28072
28171
  }
28172
+ if (selectedIconElRef.current && !selectedIconElRef.current.contains(target)) {
28173
+ clearIconSelectionRef.current();
28174
+ }
28073
28175
  if (selectedMediaElRef.current && target.closest("[data-ohw-media-overlay]") && !findClosestButtonLike(target)) {
28074
28176
  const hit = findImageAtPoint(e.clientX, e.clientY, false);
28075
28177
  if (hit && hit !== selectedMediaElRef.current) {
@@ -28247,7 +28349,7 @@ function OhhwellsBridge() {
28247
28349
  if (isIconEditable(editable) && !getSocialItem(editable)) {
28248
28350
  e.preventDefault();
28249
28351
  e.stopPropagation();
28250
- aiSectionApiRef.current?.selectFromElement(editable);
28352
+ selectIconElementRef.current(editable);
28251
28353
  const aiIcon = editable.hasAttribute("data-ai-icon");
28252
28354
  const featureIcon = aiIcon && /\.features\.\d+\.icon$/.test(editable.dataset.ohwKey ?? "");
28253
28355
  postToParentRef.current({
@@ -29871,6 +29973,7 @@ function OhhwellsBridge() {
29871
29973
  deactivateRef.current();
29872
29974
  deselectRef.current();
29873
29975
  clearMediaSelectionRef.current();
29976
+ clearIconSelectionRef.current();
29874
29977
  };
29875
29978
  clearInteractionChromeRef.current = clearInteractionChrome;
29876
29979
  const applyTreeToLocalState = (payload) => {
@@ -30288,6 +30391,7 @@ function OhhwellsBridge() {
30288
30391
  deselectRef.current();
30289
30392
  deactivateRef.current();
30290
30393
  clearMediaSelectionRef.current();
30394
+ clearIconSelectionRef.current();
30291
30395
  };
30292
30396
  window.addEventListener("message", handleDeactivate);
30293
30397
  const handleToastAction = (e) => {
@@ -30377,6 +30481,10 @@ function OhhwellsBridge() {
30377
30481
  clearMediaSelectionRef.current();
30378
30482
  return;
30379
30483
  }
30484
+ if (e.key === "Escape" && selectedIconElRef.current) {
30485
+ clearIconSelectionRef.current();
30486
+ return;
30487
+ }
30380
30488
  if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "a" && activeElRef.current) {
30381
30489
  e.preventDefault();
30382
30490
  selectAllTextInEditable(activeElRef.current);
@@ -31398,7 +31506,7 @@ function OhhwellsBridge() {
31398
31506
  postToParent2({
31399
31507
  type: "ow:ready",
31400
31508
  version: "1",
31401
- bridgeVersion: "0.1.143",
31509
+ bridgeVersion: "0.1.144",
31402
31510
  path: pathname,
31403
31511
  nodes: collectEditableNodes(editContentRef.current),
31404
31512
  sections