@ohhwells/bridge 0.1.143 → 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
@@ -1428,6 +1428,52 @@ function assignmentFor(el, content, tokens) {
1428
1428
  function labelElement(el) {
1429
1429
  return el.querySelector('[data-ohw-editable="text"], [data-ohw-editable="plain"]') ?? (el.matches('[data-ohw-editable="text"], [data-ohw-editable="plain"]') ? el : null);
1430
1430
  }
1431
+ var BRAND_TRACK_PRECEDENCE = {
1432
+ background: ["primary", "accent", "background", "surface", "on-primary", "text", "border", "text-muted"],
1433
+ color: ["on-primary", "text", "primary", "accent", "text-muted", "background", "surface", "border"],
1434
+ borderColor: ["border", "primary", "accent", "text", "on-primary", "background", "surface", "text-muted"]
1435
+ };
1436
+ var FONT_TRACK_VARS = ["--font-body", "--font-heading", "--font-display", "--brand-font-body", "--brand-font-heading"];
1437
+ function readRootVar(doc, name) {
1438
+ const root = doc.documentElement;
1439
+ return root.style.getPropertyValue(name).trim() || getComputedStyle(root).getPropertyValue(name).trim();
1440
+ }
1441
+ function sameColor(a, b) {
1442
+ return Math.round(a.r) === Math.round(b.r) && Math.round(a.g) === Math.round(b.g) && Math.round(a.b) === Math.round(b.b) && Math.abs(a.a - b.a) < 0.02;
1443
+ }
1444
+ function brandTrackedColor(doc, color, roles) {
1445
+ if (!color || color.includes("var(")) return color;
1446
+ const parsed = parseColor(color);
1447
+ if (!parsed || isTransparent(parsed)) return color;
1448
+ for (const role of roles) {
1449
+ const name = `--brand-${role}`;
1450
+ const token = parseColor(readRootVar(doc, name));
1451
+ if (token && !isTransparent(token) && sameColor(parsed, token)) return `var(${name}, ${color})`;
1452
+ }
1453
+ return color;
1454
+ }
1455
+ function brandTrackedChrome(doc, chrome) {
1456
+ return {
1457
+ ...chrome,
1458
+ background: brandTrackedColor(doc, chrome.background, BRAND_TRACK_PRECEDENCE.background),
1459
+ color: brandTrackedColor(doc, chrome.color, BRAND_TRACK_PRECEDENCE.color),
1460
+ borderColor: brandTrackedColor(doc, chrome.borderColor, BRAND_TRACK_PRECEDENCE.borderColor)
1461
+ };
1462
+ }
1463
+ function firstFamily(stack) {
1464
+ const first = stack.split(",")[0]?.trim() ?? "";
1465
+ return first.replace(/^['"]|['"]$/g, "").toLowerCase();
1466
+ }
1467
+ function brandTrackedFontFamily(doc, stack) {
1468
+ if (!stack || stack.includes("var(")) return stack;
1469
+ const family = firstFamily(stack);
1470
+ if (!family) return stack;
1471
+ for (const name of FONT_TRACK_VARS) {
1472
+ const raw = readRootVar(doc, name);
1473
+ if (raw && firstFamily(raw) === family) return `var(${name}, ${stack})`;
1474
+ }
1475
+ return stack;
1476
+ }
1431
1477
  var paintLockByEl = /* @__PURE__ */ new WeakMap();
1432
1478
  var paintObserverByEl = /* @__PURE__ */ new WeakMap();
1433
1479
  var paintingButton = false;
@@ -1454,7 +1500,7 @@ function paintButtonChrome(el, assignment, tokens) {
1454
1500
  el.style.borderRadius = px(origin.size.borderRadius);
1455
1501
  el.style.gap = geometry.gap;
1456
1502
  el.style.height = px(geometry.height);
1457
- el.style.fontFamily = typography.fontFamily;
1503
+ el.style.fontFamily = brandTrackedFontFamily(el.ownerDocument, typography.fontFamily);
1458
1504
  el.style.fontWeight = typography.fontWeight;
1459
1505
  el.style.letterSpacing = typography.letterSpacing;
1460
1506
  el.style.textTransform = typography.textTransform;
@@ -1483,9 +1529,18 @@ function paintedTokensOf(el) {
1483
1529
  return paintLockByEl.get(el)?.tokens ?? null;
1484
1530
  }
1485
1531
  function applyButtonStyle(el, assignment, tokens) {
1486
- paintLockByEl.set(el, { assignment, tokens });
1532
+ const doc = el.ownerDocument;
1533
+ const tracked = {
1534
+ ...tokens,
1535
+ variants: {
1536
+ primary: brandTrackedChrome(doc, tokens.variants.primary),
1537
+ secondary: brandTrackedChrome(doc, tokens.variants.secondary),
1538
+ ghost: brandTrackedChrome(doc, tokens.variants.ghost)
1539
+ }
1540
+ };
1541
+ paintLockByEl.set(el, { assignment, tokens: tracked });
1487
1542
  paintingButton = true;
1488
- paintButtonChrome(el, assignment, tokens);
1543
+ paintButtonChrome(el, assignment, tracked);
1489
1544
  paintingButton = false;
1490
1545
  guardTemplateHover(el);
1491
1546
  }
@@ -2385,6 +2440,49 @@ function socialHrefKey(item) {
2385
2440
  function socialIconKey(item) {
2386
2441
  return item.querySelector(ICON_SELECTOR)?.dataset.ohwKey ?? null;
2387
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
+ }
2388
2486
  var SOCIALS_ORDER_KEY = "__ohw_socials_order";
2389
2487
  function fromMarkup(markup) {
2390
2488
  const holder = document.createElement("div");
@@ -6966,8 +7064,8 @@ function applyBrandToDom(kit) {
6966
7064
  if (kit.fonts) {
6967
7065
  appliedFonts = applyFonts ? kit.fonts : null;
6968
7066
  }
7067
+ for (const name of BUTTON_VAR_NAMES) root.style.removeProperty(name);
6969
7068
  if (kit.buttons) {
6970
- for (const name of BUTTON_VAR_NAMES) root.style.removeProperty(name);
6971
7069
  for (const [name, value] of Object.entries(buttonStyleVars({ ...kit, buttons: kit.buttons }))) root.style.setProperty(name, value);
6972
7070
  }
6973
7071
  syncButtonRules(kit.buttons ? kit : null);
@@ -10368,7 +10466,7 @@ function isChromeSection2(el) {
10368
10466
  if (CHROME_SECTION_IDS.has(el.getAttribute("data-ohw-section") ?? "")) return true;
10369
10467
  return el.tagName === "HEADER" || el.tagName === "FOOTER";
10370
10468
  }
10371
- function readRootVar(name) {
10469
+ function readRootVar2(name) {
10372
10470
  if (typeof document === "undefined") return "";
10373
10471
  return getComputedStyle(document.documentElement).getPropertyValue(name).trim();
10374
10472
  }
@@ -10408,14 +10506,14 @@ function humanizeFontFamily(name) {
10408
10506
  return name.replace(/^__/, "").replace(/_[0-9a-f]{6}$/i, "").replace(/_/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2").trim().replace(/\s+/g, " ").replace(/(^|\s)([a-z])/g, (_, sep, c) => sep + c.toUpperCase());
10409
10507
  }
10410
10508
  function readFontVar(name) {
10411
- return humanizeFontFamily(primaryFontFamily(readRootVar(name)));
10509
+ return humanizeFontFamily(primaryFontFamily(readRootVar2(name)));
10412
10510
  }
10413
10511
  function deriveBrandOverride() {
10414
- const dark = readRootVar("--ohw-brand-dark");
10415
- const primary = readRootVar("--ohw-brand-primary");
10416
- const light = readRootVar("--ohw-brand-light");
10512
+ const dark = readRootVar2("--ohw-brand-dark");
10513
+ const primary = readRootVar2("--ohw-brand-primary");
10514
+ const light = readRootVar2("--ohw-brand-light");
10417
10515
  if (!dark || !primary || !light) return null;
10418
- const accent = readRootVar("--ohw-brand-accent");
10516
+ const accent = readRootVar2("--ohw-brand-accent");
10419
10517
  const heading = readFontVar("--font-heading") || readFontVar("--font-display");
10420
10518
  const body = readFontVar("--font-body");
10421
10519
  return {
@@ -10427,11 +10525,11 @@ function deriveBrandOverride() {
10427
10525
  };
10428
10526
  }
10429
10527
  function deriveTemplateBrandLive() {
10430
- const primary = readRootVar("--brand-primary") || readRootVar("--color-primary");
10431
- const dark = readRootVar("--brand-text") || readRootVar("--color-dark");
10432
- const light = readRootVar("--brand-background") || readRootVar("--color-light");
10528
+ const primary = readRootVar2("--brand-primary") || readRootVar2("--color-primary");
10529
+ const dark = readRootVar2("--brand-text") || readRootVar2("--color-dark");
10530
+ const light = readRootVar2("--brand-background") || readRootVar2("--color-light");
10433
10531
  if (!dark || !primary || !light) return null;
10434
- const accent = readRootVar("--brand-accent") || readRootVar("--color-accent");
10532
+ const accent = readRootVar2("--brand-accent") || readRootVar2("--color-accent");
10435
10533
  const heading = readFontVar("--brand-font-heading") || readFontVar("--font-heading") || readFontVar("--font-display");
10436
10534
  const body = readFontVar("--brand-font-body") || readFontVar("--font-body");
10437
10535
  return {
@@ -25032,36 +25130,72 @@ function OhhwellsBridge() {
25032
25130
  const [selectedMedia, setSelectedMedia] = useState18(null);
25033
25131
  const selectedMediaElRef = useRef14(null);
25034
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
+ }, []);
25035
25156
  const clearMediaSelection = useCallback12(() => {
25036
25157
  const prev = selectedMediaElRef.current;
25037
25158
  selectedMediaElRef.current = null;
25038
25159
  setSelectedMedia(null);
25039
- const sectionEl = prev?.closest("[data-ohw-section]") ?? null;
25040
- if (sectionEl) {
25041
- const sectionInstanceId = instanceIdOf(movableUnit(sectionEl)) || null;
25042
- if (!sectionInstanceId) return;
25043
- const token = ++mediaKeyDropTokenRef.current;
25044
- const onQueueDrained = (e) => {
25045
- if (e.source !== window || e.data?.type !== "ow:media-key-drop" || e.data.token !== token) return;
25046
- window.removeEventListener("message", onQueueDrained);
25047
- if (aiSectionApiRef.current?.getSelectedId() !== sectionInstanceId) return;
25048
- if (selectedMediaElRef.current) return;
25049
- postToParentRef.current({
25050
- type: "ow:section-selected",
25051
- sectionId: sectionEl.dataset.ohwSection ?? null,
25052
- sectionLabel: sectionEl.dataset.ohwSectionLabel ?? titleCaseSectionId(sectionEl.dataset.ohwSection ?? ""),
25053
- key: null
25054
- });
25055
- };
25056
- window.addEventListener("message", onQueueDrained);
25057
- window.postMessage({ type: "ow:media-key-drop", token }, "*");
25058
- }
25059
- }, []);
25160
+ queueSectionKeyDrop(prev);
25161
+ }, [queueSectionKeyDrop]);
25060
25162
  const clearMediaSelectionRef = useRef14(clearMediaSelection);
25061
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;
25062
25195
  const selectMediaElement = useCallback12((el) => {
25063
25196
  const r2 = el.getBoundingClientRect();
25064
25197
  const video = el.dataset.ohwEditable === "video" ? el.querySelector("video") : null;
25198
+ selectedIconElRef.current = null;
25065
25199
  selectedMediaElRef.current = el;
25066
25200
  setSelectedMedia({
25067
25201
  key: el.dataset.ohwKey ?? "",
@@ -25715,6 +25849,7 @@ function OhhwellsBridge() {
25715
25849
  selectedElRef.current = null;
25716
25850
  selectedHrefKeyRef.current = null;
25717
25851
  selectedFooterColAttrRef.current = null;
25852
+ clearSocialSelectionRef.current();
25718
25853
  setSelectedIsCta(false);
25719
25854
  setSelectedIsSocial(false);
25720
25855
  setSelectedIsSocialsRow(false);
@@ -26616,7 +26751,22 @@ function OhhwellsBridge() {
26616
26751
  const select = useCallback12((anchor) => {
26617
26752
  if (!isNavigationItem2(anchor)) return;
26618
26753
  if (activeElRef.current) deactivate();
26619
- 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
+ }
26620
26770
  selectedElRef.current = anchor;
26621
26771
  selectedHrefKeyRef.current = anchor.getAttribute("data-ohw-href-key");
26622
26772
  selectedFooterColAttrRef.current = null;
@@ -27754,8 +27904,11 @@ function OhhwellsBridge() {
27754
27904
  display: none !important;
27755
27905
  }
27756
27906
  /* Body text (no item-action toolbar) \u2014 first click enters text edit \u2192 I-beam.
27757
- Exclude select-first nav/footer labels so grab/default on [data-ohw-href-key] can win. */
27758
- [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] *)) {
27759
27912
  cursor: text !important;
27760
27913
  }
27761
27914
  /* Select-first items: default arrow; grab only when drag-start is allowed (data-ohw-can-drag). */
@@ -27776,6 +27929,7 @@ function OhhwellsBridge() {
27776
27929
  [data-ohw-editable="video"], [data-ohw-editable="video"] *,
27777
27930
  [data-ohw-editable="bg-image"], [data-ohw-editable="bg-image"] * { cursor: pointer !important; }
27778
27931
  [data-ohw-editable="link"], [data-ohw-editable="link"] * { cursor: pointer !important; }
27932
+ [data-ohw-editable="icon"], [data-ohw-editable="icon"] * { cursor: pointer !important; }
27779
27933
  /* A text/plain editable nested inside a media editable (e.g. a placeholder's fallback
27780
27934
  label inside its bg-image box) is its own click target, not the media picker \u2014 the
27781
27935
  blanket media cursor rule above would otherwise show pointer where a click actually
@@ -27927,6 +28081,9 @@ function OhhwellsBridge() {
27927
28081
  if (selectedMediaElRef.current && !selectedMediaElRef.current.contains(target) && !target.closest("[data-ohw-media-overlay], [data-ohw-edit-chrome], [data-ohw-item-interaction]")) {
27928
28082
  clearMediaSelectionRef.current();
27929
28083
  }
28084
+ if (selectedIconElRef.current && !selectedIconElRef.current.contains(target)) {
28085
+ clearIconSelectionRef.current();
28086
+ }
27930
28087
  if (selectedMediaElRef.current && target.closest("[data-ohw-media-overlay]") && !findClosestButtonLike(target)) {
27931
28088
  const hit = findImageAtPoint(e.clientX, e.clientY, false);
27932
28089
  if (hit && hit !== selectedMediaElRef.current) {
@@ -28104,7 +28261,7 @@ function OhhwellsBridge() {
28104
28261
  if (isIconEditable(editable) && !getSocialItem(editable)) {
28105
28262
  e.preventDefault();
28106
28263
  e.stopPropagation();
28107
- aiSectionApiRef.current?.selectFromElement(editable);
28264
+ selectIconElementRef.current(editable);
28108
28265
  const aiIcon = editable.hasAttribute("data-ai-icon");
28109
28266
  const featureIcon = aiIcon && /\.features\.\d+\.icon$/.test(editable.dataset.ohwKey ?? "");
28110
28267
  postToParentRef.current({
@@ -29728,6 +29885,7 @@ function OhhwellsBridge() {
29728
29885
  deactivateRef.current();
29729
29886
  deselectRef.current();
29730
29887
  clearMediaSelectionRef.current();
29888
+ clearIconSelectionRef.current();
29731
29889
  };
29732
29890
  clearInteractionChromeRef.current = clearInteractionChrome;
29733
29891
  const applyTreeToLocalState = (payload) => {
@@ -30145,6 +30303,7 @@ function OhhwellsBridge() {
30145
30303
  deselectRef.current();
30146
30304
  deactivateRef.current();
30147
30305
  clearMediaSelectionRef.current();
30306
+ clearIconSelectionRef.current();
30148
30307
  };
30149
30308
  window.addEventListener("message", handleDeactivate);
30150
30309
  const handleToastAction = (e) => {
@@ -30234,6 +30393,10 @@ function OhhwellsBridge() {
30234
30393
  clearMediaSelectionRef.current();
30235
30394
  return;
30236
30395
  }
30396
+ if (e.key === "Escape" && selectedIconElRef.current) {
30397
+ clearIconSelectionRef.current();
30398
+ return;
30399
+ }
30237
30400
  if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "a" && activeElRef.current) {
30238
30401
  e.preventDefault();
30239
30402
  selectAllTextInEditable(activeElRef.current);
@@ -31255,7 +31418,7 @@ function OhhwellsBridge() {
31255
31418
  postToParent2({
31256
31419
  type: "ow:ready",
31257
31420
  version: "1",
31258
- bridgeVersion: "0.1.142",
31421
+ bridgeVersion: "0.1.144",
31259
31422
  path: pathname,
31260
31423
  nodes: collectEditableNodes(editContentRef.current),
31261
31424
  sections