@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.js CHANGED
@@ -2440,6 +2440,49 @@ function socialHrefKey(item) {
2440
2440
  function socialIconKey(item) {
2441
2441
  return item.querySelector(ICON_SELECTOR)?.dataset.ohwKey ?? null;
2442
2442
  }
2443
+ var SOCIAL_PLATFORM_LABELS = {
2444
+ instagram: "Instagram",
2445
+ facebook: "Facebook",
2446
+ x: "X",
2447
+ twitter: "X",
2448
+ tiktok: "TikTok",
2449
+ youtube: "YouTube",
2450
+ linkedin: "LinkedIn",
2451
+ pinterest: "Pinterest",
2452
+ whatsapp: "WhatsApp",
2453
+ telegram: "Telegram",
2454
+ spotify: "Spotify",
2455
+ threads: "Threads",
2456
+ vimeo: "Vimeo",
2457
+ discord: "Discord",
2458
+ medium: "Medium",
2459
+ website: "Website"
2460
+ };
2461
+ function socialPlatformLabel(item, content) {
2462
+ const iconKey = socialIconKey(item);
2463
+ const chosen = iconKey ? (content[socialPlatformKey(iconKey)] ?? "").trim().toLowerCase() : "";
2464
+ if (chosen) return SOCIAL_PLATFORM_LABELS[chosen] ?? chosen.charAt(0).toUpperCase() + chosen.slice(1);
2465
+ const label = socialLabelElement(item);
2466
+ const words = label && !label.hasAttribute(SOCIALS_EMPTY_LABEL_ATTR) ? (label.textContent ?? "").replace(/\s+/g, " ").trim() : "";
2467
+ if (words) return words;
2468
+ const link = item.matches("a") ? item : item.querySelector("a");
2469
+ const aria = (link?.getAttribute("aria-label") ?? item.getAttribute("aria-label") ?? "").trim();
2470
+ if (aria) return aria;
2471
+ const href = (link?.getAttribute("href") ?? "").trim();
2472
+ if (/^https?:\/\//i.test(href)) {
2473
+ let host = "";
2474
+ try {
2475
+ host = new URL(href).hostname.replace(/^www\./i, "").toLowerCase();
2476
+ } catch {
2477
+ host = "";
2478
+ }
2479
+ if (host.includes("x.com") || host.includes("twitter.com")) return "X";
2480
+ for (const [id, name] of Object.entries(SOCIAL_PLATFORM_LABELS)) {
2481
+ if (host.includes(id)) return name;
2482
+ }
2483
+ }
2484
+ return "Social";
2485
+ }
2443
2486
  var SOCIALS_ORDER_KEY = "__ohw_socials_order";
2444
2487
  function fromMarkup(markup) {
2445
2488
  const holder = document.createElement("div");
@@ -25087,36 +25130,72 @@ function OhhwellsBridge() {
25087
25130
  const [selectedMedia, setSelectedMedia] = useState18(null);
25088
25131
  const selectedMediaElRef = useRef14(null);
25089
25132
  const mediaKeyDropTokenRef = useRef14(0);
25133
+ const selectedIconElRef = useRef14(null);
25134
+ const selectedSocialElRef = useRef14(null);
25135
+ const queueSectionKeyDrop = useCallback12((prev) => {
25136
+ const sectionEl = prev?.closest("[data-ohw-section]") ?? null;
25137
+ if (!sectionEl) return;
25138
+ const sectionInstanceId = instanceIdOf(movableUnit(sectionEl)) || null;
25139
+ if (!sectionInstanceId) return;
25140
+ const token = ++mediaKeyDropTokenRef.current;
25141
+ const onQueueDrained = (e) => {
25142
+ if (e.source !== window || e.data?.type !== "ow:media-key-drop" || e.data.token !== token) return;
25143
+ window.removeEventListener("message", onQueueDrained);
25144
+ if (aiSectionApiRef.current?.getSelectedId() !== sectionInstanceId) return;
25145
+ if (selectedMediaElRef.current || selectedIconElRef.current || selectedSocialElRef.current) return;
25146
+ postToParentRef.current({
25147
+ type: "ow:section-selected",
25148
+ sectionId: sectionEl.dataset.ohwSection ?? null,
25149
+ sectionLabel: sectionEl.dataset.ohwSectionLabel ?? titleCaseSectionId(sectionEl.dataset.ohwSection ?? ""),
25150
+ key: null
25151
+ });
25152
+ };
25153
+ window.addEventListener("message", onQueueDrained);
25154
+ window.postMessage({ type: "ow:media-key-drop", token }, "*");
25155
+ }, []);
25090
25156
  const clearMediaSelection = useCallback12(() => {
25091
25157
  const prev = selectedMediaElRef.current;
25092
25158
  selectedMediaElRef.current = null;
25093
25159
  setSelectedMedia(null);
25094
- const sectionEl = prev?.closest("[data-ohw-section]") ?? null;
25095
- if (sectionEl) {
25096
- const sectionInstanceId = instanceIdOf(movableUnit(sectionEl)) || null;
25097
- if (!sectionInstanceId) return;
25098
- const token = ++mediaKeyDropTokenRef.current;
25099
- const onQueueDrained = (e) => {
25100
- if (e.source !== window || e.data?.type !== "ow:media-key-drop" || e.data.token !== token) return;
25101
- window.removeEventListener("message", onQueueDrained);
25102
- if (aiSectionApiRef.current?.getSelectedId() !== sectionInstanceId) return;
25103
- if (selectedMediaElRef.current) return;
25104
- postToParentRef.current({
25105
- type: "ow:section-selected",
25106
- sectionId: sectionEl.dataset.ohwSection ?? null,
25107
- sectionLabel: sectionEl.dataset.ohwSectionLabel ?? titleCaseSectionId(sectionEl.dataset.ohwSection ?? ""),
25108
- key: null
25109
- });
25110
- };
25111
- window.addEventListener("message", onQueueDrained);
25112
- window.postMessage({ type: "ow:media-key-drop", token }, "*");
25113
- }
25114
- }, []);
25160
+ queueSectionKeyDrop(prev);
25161
+ }, [queueSectionKeyDrop]);
25115
25162
  const clearMediaSelectionRef = useRef14(clearMediaSelection);
25116
25163
  clearMediaSelectionRef.current = clearMediaSelection;
25164
+ const clearIconSelection = useCallback12(() => {
25165
+ const prev = selectedIconElRef.current;
25166
+ if (!prev) return;
25167
+ selectedIconElRef.current = null;
25168
+ queueSectionKeyDrop(prev);
25169
+ }, [queueSectionKeyDrop]);
25170
+ const clearIconSelectionRef = useRef14(clearIconSelection);
25171
+ clearIconSelectionRef.current = clearIconSelection;
25172
+ const clearSocialSelection = useCallback12(() => {
25173
+ const prev = selectedSocialElRef.current;
25174
+ if (!prev) return;
25175
+ selectedSocialElRef.current = null;
25176
+ queueSectionKeyDrop(prev);
25177
+ }, [queueSectionKeyDrop]);
25178
+ const clearSocialSelectionRef = useRef14(clearSocialSelection);
25179
+ clearSocialSelectionRef.current = clearSocialSelection;
25180
+ const selectIconElement = useCallback12((el) => {
25181
+ if (selectedMediaElRef.current) clearMediaSelectionRef.current();
25182
+ selectedIconElRef.current = el;
25183
+ const sectionEl = el.closest("[data-ohw-section]");
25184
+ aiSectionApiRef.current?.selectFromElement(el, { report: false });
25185
+ postToParentRef.current({
25186
+ type: "ow:section-selected",
25187
+ sectionId: sectionEl?.dataset.ohwSection ?? null,
25188
+ sectionLabel: sectionEl ? sectionEl.dataset.ohwSectionLabel ?? titleCaseSectionId(sectionEl.dataset.ohwSection ?? "") : null,
25189
+ key: el.dataset.ohwKey ?? null,
25190
+ keyLabel: "Icon"
25191
+ });
25192
+ }, []);
25193
+ const selectIconElementRef = useRef14(selectIconElement);
25194
+ selectIconElementRef.current = selectIconElement;
25117
25195
  const selectMediaElement = useCallback12((el) => {
25118
25196
  const r2 = el.getBoundingClientRect();
25119
25197
  const video = el.dataset.ohwEditable === "video" ? el.querySelector("video") : null;
25198
+ selectedIconElRef.current = null;
25120
25199
  selectedMediaElRef.current = el;
25121
25200
  setSelectedMedia({
25122
25201
  key: el.dataset.ohwKey ?? "",
@@ -25770,6 +25849,7 @@ function OhhwellsBridge() {
25770
25849
  selectedElRef.current = null;
25771
25850
  selectedHrefKeyRef.current = null;
25772
25851
  selectedFooterColAttrRef.current = null;
25852
+ clearSocialSelectionRef.current();
25773
25853
  setSelectedIsCta(false);
25774
25854
  setSelectedIsSocial(false);
25775
25855
  setSelectedIsSocialsRow(false);
@@ -26671,7 +26751,22 @@ function OhhwellsBridge() {
26671
26751
  const select = useCallback12((anchor) => {
26672
26752
  if (!isNavigationItem2(anchor)) return;
26673
26753
  if (activeElRef.current) deactivate();
26674
- aiSectionApiRef.current?.selectFromElement(anchor);
26754
+ clearSocialSelectionRef.current();
26755
+ const socialItem = getSocialItem(anchor);
26756
+ if (socialItem) {
26757
+ selectedSocialElRef.current = socialItem;
26758
+ const sectionEl = socialItem.closest("[data-ohw-section]");
26759
+ aiSectionApiRef.current?.selectFromElement(anchor, { report: false });
26760
+ postToParentRef.current({
26761
+ type: "ow:section-selected",
26762
+ sectionId: sectionEl?.dataset.ohwSection ?? null,
26763
+ sectionLabel: sectionEl ? sectionEl.dataset.ohwSectionLabel ?? titleCaseSectionId(sectionEl.dataset.ohwSection ?? "") : null,
26764
+ key: socialIconKey(socialItem) ?? socialItem.getAttribute("data-ohw-href-key"),
26765
+ keyLabel: socialPlatformLabel(socialItem, editContentRef.current)
26766
+ });
26767
+ } else {
26768
+ aiSectionApiRef.current?.selectFromElement(anchor);
26769
+ }
26675
26770
  selectedElRef.current = anchor;
26676
26771
  selectedHrefKeyRef.current = anchor.getAttribute("data-ohw-href-key");
26677
26772
  selectedFooterColAttrRef.current = null;
@@ -27809,8 +27904,11 @@ function OhhwellsBridge() {
27809
27904
  display: none !important;
27810
27905
  }
27811
27906
  /* Body text (no item-action toolbar) \u2014 first click enters text edit \u2192 I-beam.
27812
- Exclude select-first nav/footer labels so grab/default on [data-ohw-href-key] can win. */
27813
- [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] *)) {
27907
+ Exclude select-first nav/footer labels so grab/default on [data-ohw-href-key] can win.
27908
+ Icons are excluded too: a glyph is replaced by a click, never typed into (OHH-623),
27909
+ so it takes the picker's pointer below, not an I-beam \u2014 this blanket !important used
27910
+ to beat the stylesheet's own non-!important icon rule (OHH-718). */
27911
+ [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] *)) {
27814
27912
  cursor: text !important;
27815
27913
  }
27816
27914
  /* Select-first items: default arrow; grab only when drag-start is allowed (data-ohw-can-drag). */
@@ -27831,6 +27929,7 @@ function OhhwellsBridge() {
27831
27929
  [data-ohw-editable="video"], [data-ohw-editable="video"] *,
27832
27930
  [data-ohw-editable="bg-image"], [data-ohw-editable="bg-image"] * { cursor: pointer !important; }
27833
27931
  [data-ohw-editable="link"], [data-ohw-editable="link"] * { cursor: pointer !important; }
27932
+ [data-ohw-editable="icon"], [data-ohw-editable="icon"] * { cursor: pointer !important; }
27834
27933
  /* A text/plain editable nested inside a media editable (e.g. a placeholder's fallback
27835
27934
  label inside its bg-image box) is its own click target, not the media picker \u2014 the
27836
27935
  blanket media cursor rule above would otherwise show pointer where a click actually
@@ -27982,6 +28081,9 @@ function OhhwellsBridge() {
27982
28081
  if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
27983
28082
  clearMediaSelectionRef.current();
27984
28083
  }
28084
+ if (selectedIconElRef.current && !selectedIconElRef.current.contains(target)) {
28085
+ clearIconSelectionRef.current();
28086
+ }
27985
28087
  if (selectedMediaElRef.current && target.closest("[data-ohw-media-overlay]") && !findClosestButtonLike(target)) {
27986
28088
  const hit = findImageAtPoint(e.clientX, e.clientY, false);
27987
28089
  if (hit && hit !== selectedMediaElRef.current) {
@@ -28159,7 +28261,7 @@ function OhhwellsBridge() {
28159
28261
  if (isIconEditable(editable) && !getSocialItem(editable)) {
28160
28262
  e.preventDefault();
28161
28263
  e.stopPropagation();
28162
- aiSectionApiRef.current?.selectFromElement(editable);
28264
+ selectIconElementRef.current(editable);
28163
28265
  const aiIcon = editable.hasAttribute("data-ai-icon");
28164
28266
  const featureIcon = aiIcon && /\.features\.\d+\.icon$/.test(editable.dataset.ohwKey ?? "");
28165
28267
  postToParentRef.current({
@@ -29783,6 +29885,7 @@ function OhhwellsBridge() {
29783
29885
  deactivateRef.current();
29784
29886
  deselectRef.current();
29785
29887
  clearMediaSelectionRef.current();
29888
+ clearIconSelectionRef.current();
29786
29889
  };
29787
29890
  clearInteractionChromeRef.current = clearInteractionChrome;
29788
29891
  const applyTreeToLocalState = (payload) => {
@@ -30200,6 +30303,7 @@ function OhhwellsBridge() {
30200
30303
  deselectRef.current();
30201
30304
  deactivateRef.current();
30202
30305
  clearMediaSelectionRef.current();
30306
+ clearIconSelectionRef.current();
30203
30307
  };
30204
30308
  window.addEventListener("message", handleDeactivate);
30205
30309
  const handleToastAction = (e) => {
@@ -30289,6 +30393,10 @@ function OhhwellsBridge() {
30289
30393
  clearMediaSelectionRef.current();
30290
30394
  return;
30291
30395
  }
30396
+ if (e.key === "Escape" && selectedIconElRef.current) {
30397
+ clearIconSelectionRef.current();
30398
+ return;
30399
+ }
30292
30400
  if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "a" && activeElRef.current) {
30293
30401
  e.preventDefault();
30294
30402
  selectAllTextInEditable(activeElRef.current);
@@ -31310,7 +31418,7 @@ function OhhwellsBridge() {
31310
31418
  postToParent2({
31311
31419
  type: "ow:ready",
31312
31420
  version: "1",
31313
- bridgeVersion: "0.1.143",
31421
+ bridgeVersion: "0.1.144",
31314
31422
  path: pathname,
31315
31423
  nodes: collectEditableNodes(editContentRef.current),
31316
31424
  sections