@artenx/dshb 0.1.25 → 0.1.26

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/lib/client.js CHANGED
@@ -1516,7 +1516,8 @@ window.__ModuleLoader__.load({
1516
1516
  document.querySelectorAll(`[${FRAME_ATTR}="stats"]`).forEach((el) => {
1517
1517
  if (!el.isConnected || !STATS_LINE_RE.test((el.textContent ?? "").trim())) el.removeAttribute(FRAME_ATTR);
1518
1518
  });
1519
- const cands = Array.from(document.querySelectorAll("span, div")).filter((el) => {
1519
+ const scope = document.querySelector("[class*=\"composerStack\"]") ?? document.querySelector("[class*=\"composer\"]") ?? document;
1520
+ const cands = Array.from(scope.querySelectorAll("span, div")).filter((el) => {
1520
1521
  if (!isStatsText(el)) return false;
1521
1522
  if (getComputedStyle(el).display === "contents") return false;
1522
1523
  return true;
@@ -1538,15 +1539,22 @@ window.__ModuleLoader__.load({
1538
1539
  }
1539
1540
  };
1540
1541
  mark();
1541
- const mo = new MutationObserver(() => {
1542
- if (narrow.matches) mark();
1543
- });
1542
+ let raf = 0;
1543
+ const scheduleMark = () => {
1544
+ if (raf !== 0 || !narrow.matches) return;
1545
+ raf = requestAnimationFrame(() => {
1546
+ raf = 0;
1547
+ mark();
1548
+ });
1549
+ };
1550
+ const mo = new MutationObserver(scheduleMark);
1544
1551
  mo.observe(document.documentElement, {
1545
1552
  childList: true,
1546
1553
  subtree: true
1547
1554
  });
1548
1555
  narrow.addEventListener("change", mark);
1549
1556
  return () => {
1557
+ if (raf !== 0) cancelAnimationFrame(raf);
1550
1558
  mo.disconnect();
1551
1559
  narrow.removeEventListener("change", mark);
1552
1560
  };
@@ -1566,36 +1574,54 @@ window.__ModuleLoader__.load({
1566
1574
  "overflow-y",
1567
1575
  "box-sizing"
1568
1576
  ];
1577
+ /** 幂等写入 important 内联样式:值未变化时跳过,避免 MutationObserver(attributes) 自触发循环。 */
1578
+ function setImportantIfChanged(el, prop, value) {
1579
+ if (el.style.getPropertyValue(prop) === value && el.style.getPropertyPriority(prop) === "important") return;
1580
+ el.style.setProperty(prop, value, "important");
1581
+ }
1582
+ /** 当前视口下的可视区域(手机键盘弹出时 visualViewport 与布局视口错位)。 */
1583
+ function visibleViewport() {
1584
+ const vv = window.visualViewport;
1585
+ if (vv) return {
1586
+ vw: vv.width,
1587
+ vh: vv.offsetTop + vv.height,
1588
+ top: vv.offsetTop
1589
+ };
1590
+ return {
1591
+ vw: window.innerWidth,
1592
+ vh: window.innerHeight,
1593
+ top: 0
1594
+ };
1595
+ }
1569
1596
  /** Keep the model menu anchored to its trigger while fitting it in the viewport. */
1570
1597
  function positionAnchoredMenu(el, triggerRect, maxWidth, maxHeight) {
1571
1598
  const margin = 8;
1572
- const vw = window.innerWidth;
1573
- const vh = window.innerHeight;
1574
- el.style.setProperty("position", "fixed", "important");
1575
- el.style.setProperty("box-sizing", "border-box", "important");
1576
- el.style.setProperty("left", "0", "important");
1577
- el.style.setProperty("right", "auto", "important");
1578
- el.style.setProperty("top", "0", "important");
1579
- el.style.setProperty("bottom", "auto", "important");
1580
- el.style.setProperty("min-width", "0", "important");
1581
- el.style.setProperty("max-width", `${Math.max(0, Math.min(maxWidth, vw - 16))}px`, "important");
1582
- el.style.setProperty("max-height", `${Math.max(0, Math.min(maxHeight, vh - 16))}px`, "important");
1583
- el.style.setProperty("overflow-y", "auto", "important");
1599
+ const { vw, vh } = visibleViewport();
1600
+ setImportantIfChanged(el, "position", "fixed");
1601
+ setImportantIfChanged(el, "box-sizing", "border-box");
1602
+ setImportantIfChanged(el, "left", "0");
1603
+ setImportantIfChanged(el, "right", "auto");
1604
+ setImportantIfChanged(el, "top", "0");
1605
+ setImportantIfChanged(el, "bottom", "auto");
1606
+ setImportantIfChanged(el, "min-width", "0");
1607
+ setImportantIfChanged(el, "max-width", `${Math.max(0, Math.min(maxWidth, vw - 16))}px`);
1608
+ setImportantIfChanged(el, "max-height", `${Math.max(0, Math.min(maxHeight, vh - 16))}px`);
1609
+ setImportantIfChanged(el, "overflow-y", "auto");
1584
1610
  let width = el.offsetWidth;
1585
1611
  let height = el.offsetHeight;
1586
- const spaceAbove = Math.max(0, triggerRect.top - 16);
1612
+ const { top: viewportTop } = visibleViewport();
1613
+ const spaceAbove = Math.max(0, triggerRect.top - viewportTop - 16);
1587
1614
  const spaceBelow = Math.max(0, vh - triggerRect.bottom - 16);
1588
1615
  const expandUp = height <= spaceAbove || spaceAbove >= spaceBelow;
1589
1616
  const available = expandUp ? spaceAbove : spaceBelow;
1590
1617
  if (height > available) {
1591
- el.style.setProperty("max-height", `${Math.max(0, available - margin)}px`, "important");
1618
+ setImportantIfChanged(el, "max-height", `${Math.max(0, available - margin)}px`);
1592
1619
  height = el.offsetHeight;
1593
1620
  width = el.offsetWidth;
1594
1621
  }
1595
1622
  const top = expandUp ? triggerRect.top - margin - height : triggerRect.bottom + margin;
1596
- const left = Math.max(margin, Math.min(triggerRect.right - width, vw - width - margin));
1597
- el.style.setProperty("left", `${left}px`, "important");
1598
- el.style.setProperty("top", `${Math.max(margin, Math.min(top, vh - height - margin))}px`, "important");
1623
+ setImportantIfChanged(el, "left", `${Math.max(margin, Math.min(triggerRect.right - width, vw - width - margin))}px`);
1624
+ setImportantIfChanged(el, "top", `${Math.max(viewportTop + margin, Math.min(top, vh - height - margin))}px`);
1599
1625
  }
1600
1626
  /**
1601
1627
  * 模型名多为纯连字符词(DeepSeek-V4-Flash-Vision-Exp);white-space:normal 下
@@ -1664,9 +1690,13 @@ window.__ModuleLoader__.load({
1664
1690
  function installModelMenuPosition(ctx) {
1665
1691
  ctx.effect(() => {
1666
1692
  const narrow = window.matchMedia(NARROW_QUERY);
1693
+ let fallbackLast = 0;
1667
1694
  const findMenus = () => {
1668
1695
  const byHash = Array.from(document.querySelectorAll("[class*=\"7KE1Ra_menu\"]"));
1669
1696
  if (byHash.length > 0) return byHash;
1697
+ const now = Date.now();
1698
+ if (now - fallbackLast < 500) return [];
1699
+ fallbackLast = now;
1670
1700
  return Array.from(document.querySelectorAll("[class*=\"_menu\"]")).filter((el) => el.querySelector("[class*=\"cellLabel\"], [class*=\"modelName\"]") !== null);
1671
1701
  };
1672
1702
  const findTrigger = (menu) => {
@@ -1727,16 +1757,42 @@ window.__ModuleLoader__.load({
1727
1757
  const mo = new MutationObserver(() => requestAnimationFrame(sync));
1728
1758
  mo.observe(document.documentElement, {
1729
1759
  childList: true,
1730
- subtree: true
1760
+ subtree: true,
1761
+ attributes: true,
1762
+ attributeFilter: ["style"]
1731
1763
  });
1732
1764
  narrow.addEventListener("change", sync);
1733
1765
  window.addEventListener("resize", sync);
1734
1766
  window.addEventListener("scroll", sync, true);
1767
+ window.visualViewport?.addEventListener("resize", sync);
1768
+ window.visualViewport?.addEventListener("scroll", sync);
1769
+ let lastPointerTarget = null;
1770
+ const keyboardLikelyOpen = () => {
1771
+ const vv = window.visualViewport;
1772
+ if (!vv) return false;
1773
+ return window.innerHeight - vv.height > 150;
1774
+ };
1775
+ const onPointerDown = (event) => {
1776
+ lastPointerTarget = event.target instanceof Element ? event.target : null;
1777
+ };
1778
+ const onFocusIn = (event) => {
1779
+ if (!narrow.matches || !(event.target instanceof Element) || keyboardLikelyOpen()) return;
1780
+ const input = event.target.closest("input");
1781
+ if (!input || input.closest("[class*=\"7KE1Ra_menu\"], [class*=\"_menu\"]") === null) return;
1782
+ if (lastPointerTarget !== null && (lastPointerTarget === input || input.contains(lastPointerTarget))) return;
1783
+ input.blur();
1784
+ };
1785
+ document.addEventListener("pointerdown", onPointerDown, true);
1786
+ document.addEventListener("focusin", onFocusIn, true);
1735
1787
  return () => {
1736
1788
  mo.disconnect();
1737
1789
  narrow.removeEventListener("change", sync);
1738
1790
  window.removeEventListener("resize", sync);
1739
1791
  window.removeEventListener("scroll", sync, true);
1792
+ window.visualViewport?.removeEventListener("resize", sync);
1793
+ window.visualViewport?.removeEventListener("scroll", sync);
1794
+ document.removeEventListener("pointerdown", onPointerDown, true);
1795
+ document.removeEventListener("focusin", onFocusIn, true);
1740
1796
  };
1741
1797
  }, "dshb-mobile: model menu position");
1742
1798
  }
@@ -1757,20 +1813,22 @@ window.__ModuleLoader__.load({
1757
1813
  const rootRect = root.getBoundingClientRect();
1758
1814
  if (rootRect.width === 0 && rootRect.height === 0) continue;
1759
1815
  const gap = 8;
1760
- panel.style.setProperty("position", "fixed", "important");
1761
- panel.style.setProperty("top", "auto", "important");
1762
- panel.style.setProperty("bottom", `${window.innerHeight - rootRect.top + gap}px`, "important");
1763
- panel.style.setProperty("left", "auto", "important");
1764
- panel.style.setProperty("right", `${Math.max(gap, window.innerWidth - rootRect.right)}px`, "important");
1765
- panel.style.setProperty("max-height", `${Math.max(160, rootRect.top - 16)}px`, "important");
1766
- panel.style.setProperty("overflow-y", "auto", "important");
1816
+ setImportantIfChanged(panel, "position", "fixed");
1817
+ setImportantIfChanged(panel, "top", "auto");
1818
+ setImportantIfChanged(panel, "bottom", `${window.innerHeight - rootRect.top + gap}px`);
1819
+ setImportantIfChanged(panel, "left", "auto");
1820
+ setImportantIfChanged(panel, "right", `${Math.max(gap, window.innerWidth - rootRect.right)}px`);
1821
+ setImportantIfChanged(panel, "max-height", `${Math.max(160, rootRect.top - 16)}px`);
1822
+ setImportantIfChanged(panel, "overflow-y", "auto");
1767
1823
  }
1768
1824
  };
1769
1825
  sync();
1770
1826
  const mo = new MutationObserver(() => requestAnimationFrame(sync));
1771
1827
  mo.observe(document.documentElement, {
1772
1828
  childList: true,
1773
- subtree: true
1829
+ subtree: true,
1830
+ attributes: true,
1831
+ attributeFilter: ["style"]
1774
1832
  });
1775
1833
  narrow.addEventListener("change", sync);
1776
1834
  window.addEventListener("resize", sync);
@@ -2112,7 +2170,14 @@ window.__ModuleLoader__.load({
2112
2170
  }
2113
2171
  }
2114
2172
  };
2115
- new MutationObserver(scan).observe(document.body, {
2173
+ let raf = 0;
2174
+ new MutationObserver(() => {
2175
+ if (raf !== 0) return;
2176
+ raf = requestAnimationFrame(() => {
2177
+ raf = 0;
2178
+ scan();
2179
+ });
2180
+ }).observe(document.body, {
2116
2181
  childList: true,
2117
2182
  subtree: true
2118
2183
  });
@@ -4651,20 +4716,25 @@ window.__ModuleLoader__.load({
4651
4716
  "认证": "auth",
4652
4717
  "工作节点": "dshb-nodes"
4653
4718
  };
4719
+ let raf = 0;
4654
4720
  new MutationObserver(() => {
4655
- const cells = document.querySelectorAll("button[class*=\"navCell\"]");
4656
- for (const cell of Array.from(cells)) {
4657
- const label = cell.querySelector("span[class*=\"navLabel\"]");
4658
- if (!label) continue;
4659
- const text = label.textContent ?? "";
4660
- const iconKey = ICON_LABELS[text];
4661
- if (!iconKey) continue;
4662
- const iconSlot = cell.querySelector("[class*=\"navIcon\"]");
4663
- if (!iconSlot) continue;
4664
- if (iconSlot.getAttribute("data-dshb-icon") === iconKey) continue;
4665
- iconSlot.innerHTML = ICON_SVG[iconKey] ?? "";
4666
- iconSlot.setAttribute("data-dshb-icon", iconKey);
4667
- }
4721
+ if (raf !== 0) return;
4722
+ raf = requestAnimationFrame(() => {
4723
+ raf = 0;
4724
+ const cells = document.querySelectorAll("button[class*=\"navCell\"]");
4725
+ for (const cell of Array.from(cells)) {
4726
+ const label = cell.querySelector("span[class*=\"navLabel\"]");
4727
+ if (!label) continue;
4728
+ const text = label.textContent ?? "";
4729
+ const iconKey = ICON_LABELS[text];
4730
+ if (!iconKey) continue;
4731
+ const iconSlot = cell.querySelector("[class*=\"navIcon\"]");
4732
+ if (!iconSlot) continue;
4733
+ if (iconSlot.getAttribute("data-dshb-icon") === iconKey) continue;
4734
+ iconSlot.innerHTML = ICON_SVG[iconKey] ?? "";
4735
+ iconSlot.setAttribute("data-dshb-icon", iconKey);
4736
+ }
4737
+ });
4668
4738
  }).observe(document.body, {
4669
4739
  childList: true,
4670
4740
  subtree: true