dsh-m 0.9.56 → 0.9.58
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/CHANGELOG.md +26 -0
- package/lib/client.js +83 -45
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,19 @@ The full release history of dsh-m, maintained bilingually: **Chinese first, Engl
|
|
|
8
8
|
|
|
9
9
|
## 中文
|
|
10
10
|
|
|
11
|
+
### 0.9.58 优化:+N 动态紧跟末可见分类,并与其框高一致
|
|
12
|
+
|
|
13
|
+
- **位置(`4ade54f`)**:在 0.9.57 的独立安全槽位中,根据末可见分类 chip 的实测右缘设置 `+N` 左缘 = 右缘 + 6px;槽位由 52px 扩为 60px,窄宽时仍不与分类或筛选重叠。精选区没有筛选时不额外预留筛选 gutter;展开态 `⌃` 仍位于列表尾部。
|
|
14
|
+
- **框高(`aeed14c`)**:`+N` 直接采用末可见分类的实测 border-box 高度,并以 inline-flex 居中数字,不再依赖西文数字与中文标签各自的浏览器自动行高。只改 `+N`,不改变分区 chip 的尺寸。
|
|
15
|
+
- **验证**:1224 项测试与 typecheck 全绿;用真实 DSH 目录数据在 880/700/520/420/320px 社区区测得两框同高 21px、顶边差 0、水平间距约 5–6px、相交面积 0;260px 精选区也无重叠,精选↔社区往返正常。
|
|
16
|
+
|
|
17
|
+
### 0.9.57 修复:精选切换后面板崩溃/无法重开 + 折叠筛选双框与 +N 重叠
|
|
18
|
+
|
|
19
|
+
- **根因(0.9.56 真机复现)**:精选区的标签对象可能每次父级重渲染都重建;layout effect 依赖 `chips` 数组引用及自身更新的 `geom`,同步测量反复进入、最终触发 React `Maximum update depth exceeded`(#185)。React 清空面板树但留下 `#dshm-panel-root`,旧入口守卫只检查容器存在,导致点击入口永久无效。叠加折叠筛选定位壳和按钮的双边框、以及 +N 位置夹取/文档流试验在窄宽下压住相邻 chip。
|
|
20
|
+
- **修复**:按实际渲染的分类 id/文案/计数生成稳定内容签名,layout effect 不再同步依赖自身 `geom`;预留槽变化改在上一轮状态提交后的 rAF 复测。回退 0.9.56 的 `display:none`/in-flow 反馈链,恢复分类全量占位 + 越界 `visibility:hidden`,以完整 `offsetTop` 按行纯派生隐藏数;+N 脱离文档流,独占右侧 52px 槽(筛选另占 96px),双类 CSS 选择器确保总预留不被 gutter 覆盖。末可见行顶按**去重行**而非第 N 颗 chip 计算。筛选外壳去边框仅保留定位;若崩溃留下无可见面板的空容器,再次点击时先移除再挂载。
|
|
21
|
+
- **取舍**:+N 优先保证零重叠、正确计数和可重开;不再强求其贴着最后一颗分类。窄宽下边界分类自然少显示,`+N` 如实计入;展开态 `⌃` 仍在分类列表末尾。
|
|
22
|
+
- **验证**:新增 3 个回归断言,累计 1224 测试全绿、typecheck 零错误;真实 DSH Web 加载候选客户端验证精选↔社区连续三次、关闭再开及崩溃残留空容器恢复;260–880px 六档 +N/chip/筛选相交面积恒 0,精选五类不出现错误 +N。
|
|
23
|
+
|
|
11
24
|
### 0.9.56 变更:ZoneChips +N 重叠根治——in-flow 文档流布局 + 折行减类(主人方案)
|
|
12
25
|
|
|
13
26
|
- **缺陷(0.9.55 真机复现)**:折叠态 `+N` 采用「绝对定位 + contentW−48 夹取」,当末可见行剩余空间小于徽章宽时,徽章压住末可见 chip(真机:+9 压住「文档与渲染 61」右端计数)。
|
|
@@ -419,6 +432,19 @@ The full release history of dsh-m, maintained bilingually: **Chinese first, Engl
|
|
|
419
432
|
|
|
420
433
|
## English
|
|
421
434
|
|
|
435
|
+
### Improved in 0.9.58 — the +N toggle follows the last visible category at matching height
|
|
436
|
+
|
|
437
|
+
- **Position (`4ade54f`)**: within the 0.9.57 reserved, overlap-free slot, position `+N` from the measured right edge of the last visible category chip plus 6px. The slot grows from 52px to 60px to preserve clearance at narrow widths. Curated rows without a Filter trigger still reserve no unnecessary Filter gutter; expanded `⌃` remains at list end.
|
|
438
|
+
- **Equal height (`aeed14c`)**: use the last visible chip’s measured border-box height for `+N` and center its text with inline-flex, rather than letting Latin-only text and CJK category labels acquire different automatic line heights. Other chips keep their existing dimensions.
|
|
439
|
+
- **Verification**: 1224 tests and typecheck pass. Against the real DSH catalog, community layouts at 880/700/520/420/320px showed equal 21px boxes, 0px top-edge delta, roughly 5–6px horizontal separation, and zero overlap; the 260px curated layout had zero overlap and curated↔community switching remained stable.
|
|
440
|
+
|
|
441
|
+
### Fixed in 0.9.57 — curated-zone update loop, dead panel entry, and collapsed filter/overflow overlaps
|
|
442
|
+
|
|
443
|
+
- **Root cause (reproduced on 0.9.56)**: curated labels may get a fresh object on every parent render; the layout effect depended on the `chips` array identity and on its own `geom` update. Synchronous remeasurement repeatedly entered the same commit, eventually throwing React `Maximum update depth exceeded` (#185). React emptied the panel tree while leaving `#dshm-panel-root`; the old entry guard checked only for a container and silently blocked every later open. The pinned Filter wrapper also added a second border around its bordered button, while the clamped/inline `+N` iterations overlapped neighboring chips at narrow widths.
|
|
444
|
+
- **Fix**: depend on a stable signature of rendered category IDs, labels and counts; defer geometry-dependent remeasurement to an animation frame after state commits. Retire the 0.9.56 `display:none`/in-flow feedback path in favor of a full in-flow chip layout with `visibility:hidden` beyond the clip; derive the hidden count from all chip row offsets. Give out-of-flow `+N` its own 52px right slot, separate from Filter's 96px, with a more specific selector so the total reservation wins the cascade. Measure the last visible **distinct row** rather than the Nth chip. Strip the redundant Filter-wrapper border; discard an orphaned panel root on the next open.
|
|
445
|
+
- **Tradeoff**: correctness, zero overlap and reopenability take precedence over keeping `+N` flush against the last chip. Its count remains truthful; expanded `⌃` remains at list end.
|
|
446
|
+
- **Verification**: three additional regression assertions, 1224 tests passing and clean typecheck; real DSH Web with the candidate client survived three curated↔community round trips, close/reopen and stale-root recovery; six widths from 260–880px showed zero badge/chip/Filter intersection, and curated's five categories showed no spurious `+N`.
|
|
447
|
+
|
|
422
448
|
### Fixed in 0.9.56 — ZoneChips +N overlap eradicated: in-flow document layout + wrap-yield-a-category (owner's design)
|
|
423
449
|
|
|
424
450
|
- **Defect (reproduced on 0.9.55)**: the collapsed `+N` used "absolute positioning + contentW−48 clamp"; when the last visible row's leftover was narrower than the badge, the badge covered the last visible chip (real panel: +9 covering the tail of 文档与渲染 61).
|
package/lib/client.js
CHANGED
|
@@ -41,6 +41,7 @@ __export(market_state_exports, {
|
|
|
41
41
|
registryNotice: () => registryNotice,
|
|
42
42
|
resetPageOnFilterChange: () => resetPageOnFilterChange,
|
|
43
43
|
searchSourceOf: () => searchSourceOf,
|
|
44
|
+
visibleRowTop: () => visibleRowTop,
|
|
44
45
|
zoneChips: () => zoneChips
|
|
45
46
|
});
|
|
46
47
|
function toSafeInt(value, fallback, min, max) {
|
|
@@ -278,6 +279,20 @@ function clipTopOf(offsets, maxRows) {
|
|
|
278
279
|
}
|
|
279
280
|
return Infinity;
|
|
280
281
|
}
|
|
282
|
+
function visibleRowTop(offsets, maxRows) {
|
|
283
|
+
if (!Array.isArray(offsets) || !Number.isInteger(maxRows) || maxRows < 1) return 0;
|
|
284
|
+
let rows = 0;
|
|
285
|
+
let last = null;
|
|
286
|
+
for (const top of offsets) {
|
|
287
|
+
if (typeof top !== "number" || !Number.isFinite(top)) continue;
|
|
288
|
+
if (last === null || top !== last) {
|
|
289
|
+
rows += 1;
|
|
290
|
+
last = top;
|
|
291
|
+
if (rows === maxRows) return top;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return last ?? 0;
|
|
295
|
+
}
|
|
281
296
|
function autoExpandDecision(activeTop, clipTop, autoRefMatched, expanded) {
|
|
282
297
|
const beyond = typeof activeTop === "number" && Number.isFinite(activeTop) && typeof clipTop === "number" && Number.isFinite(clipTop) && activeTop >= clipTop;
|
|
283
298
|
if (!beyond || autoRefMatched) return { record: false, expand: false };
|
|
@@ -1558,7 +1573,7 @@ var h = React.createElement;
|
|
|
1558
1573
|
var { useState, useEffect, useLayoutEffect, useCallback, useMemo, useRef } = React;
|
|
1559
1574
|
var PLUGIN_ID = "dsh-m";
|
|
1560
1575
|
var API = "/dshm";
|
|
1561
|
-
var { DEFAULT_PAGE_SIZE: DEFAULT_PAGE_SIZE2, MARKET_PAGE_SIZES: MARKET_PAGE_SIZES2, pageItems: pageItems2, createZoneState: createZoneState2, normalizeMarketQuery: normalizeMarketQuery2, resetPageOnFilterChange: resetPageOnFilterChange2, normalizeMarketResponse: normalizeMarketResponse2, registryNotice: registryNotice2, zoneChips: zoneChips2, marketNotice: marketNotice2, searchSourceOf: searchSourceOf2, mergeLatestFields: mergeLatestFields2,
|
|
1576
|
+
var { DEFAULT_PAGE_SIZE: DEFAULT_PAGE_SIZE2, MARKET_PAGE_SIZES: MARKET_PAGE_SIZES2, pageItems: pageItems2, createZoneState: createZoneState2, normalizeMarketQuery: normalizeMarketQuery2, resetPageOnFilterChange: resetPageOnFilterChange2, normalizeMarketResponse: normalizeMarketResponse2, registryNotice: registryNotice2, zoneChips: zoneChips2, marketNotice: marketNotice2, searchSourceOf: searchSourceOf2, mergeLatestFields: mergeLatestFields2, countBeyondRows: countBeyondRows2, clipTopOf: clipTopOf2, visibleRowTop: visibleRowTop2, autoExpandDecision: autoExpandDecision2 } = (init_market_state(), __toCommonJS(market_state_exports));
|
|
1562
1577
|
var { readMarketSnapshot: readMarketSnapshot2, writeMarketSnapshot: writeMarketSnapshot2, isDefaultFirstPageQuery: isDefaultFirstPageQuery2 } = (init_market_snapshot(), __toCommonJS(market_snapshot_exports));
|
|
1563
1578
|
var { backdropCloseHandlers: backdropCloseHandlers2 } = (init_backdrop(), __toCommonJS(backdrop_exports));
|
|
1564
1579
|
var { createMarkdown: createMarkdown2 } = (init_markdown(), __toCommonJS(markdown_exports));
|
|
@@ -2189,7 +2204,11 @@ var CSS = `
|
|
|
2189
2204
|
.dshm-chips{display:flex;flex-wrap:wrap;gap:6px;position:relative}
|
|
2190
2205
|
.dshm-chips-clip{overflow:hidden}
|
|
2191
2206
|
.dshm-chips-gutter{padding-right:var(--dshm-clip-gutter,96px)}
|
|
2192
|
-
|
|
2207
|
+
/* +N/\u7B5B\u9009\u5206\u522B\u5360\u53F3\u4FA7 60/96px\uFF1B\u4FDD\u7559\u4F18\u5148\u7EA7\u9AD8\u4E8E\u5355\u72EC\u7684\u7B5B\u9009 gutter\u3002 */
|
|
2208
|
+
.dshm-chips.dshm-chips-clip.dshm-chips-reserve{padding-right:60px}
|
|
2209
|
+
.dshm-chips.dshm-chips-clip.dshm-chips-gutter.dshm-chips-reserve{padding-right:calc(var(--dshm-clip-gutter,96px) + 60px)}
|
|
2210
|
+
.dsvm-chipmore{position:absolute;right:8px}
|
|
2211
|
+
.dsvm-chipmore-plus{position:absolute;z-index:1;box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;background-color:var(--dsw-alias-bg-base,#fff);background-image:linear-gradient(var(--dsw-alias-bg-base,#fff),var(--dsw-alias-bg-base,#fff)),linear-gradient(var(--dsw-alias-bg-base,#fff),var(--dsw-alias-bg-base,#fff))}
|
|
2193
2212
|
.dsvm-searchrow{display:flex;align-items:center;gap:8px}
|
|
2194
2213
|
.dsvm-searchrow .dshm-search{flex:1;display:flex}
|
|
2195
2214
|
.dshm-search{position:relative}
|
|
@@ -2710,6 +2729,7 @@ function SearchBox({ placeholder, initial, onCommit }) {
|
|
|
2710
2729
|
}
|
|
2711
2730
|
function ZoneChips({ zone, counts, labels, active, onPick, trailing, wrapTitle }) {
|
|
2712
2731
|
const chips = useMemo(() => zoneChips2(counts, labels, zone), [counts, labels, zone]);
|
|
2732
|
+
const chipsSignature = JSON.stringify(chips.map((c) => [c.id, c.labelKey ? lookup(c.labelKey) : c.label, c.count]));
|
|
2713
2733
|
const [expanded, setExpanded] = useState(false);
|
|
2714
2734
|
const [stuck, setStuck] = useState(false);
|
|
2715
2735
|
const [geom, setGeom] = useState(null);
|
|
@@ -2719,38 +2739,33 @@ function ZoneChips({ zone, counts, labels, active, onPick, trailing, wrapTitle }
|
|
|
2719
2739
|
const autoRef = useRef(null);
|
|
2720
2740
|
const maxRows = stuck ? 1 : 2;
|
|
2721
2741
|
const activeIdx = active ? chips.findIndex((c) => c.id === active) : -1;
|
|
2722
|
-
const
|
|
2723
|
-
const maxRowsEff = maxRows + (plusWrapped ? 1 : 0);
|
|
2724
|
-
const maxH = maxRowsEff * (rowHeight || 22) + (maxRowsEff - 1) * 6;
|
|
2742
|
+
const maxH = maxRows * (rowHeight || 22) + (maxRows - 1) * 6;
|
|
2725
2743
|
const measure = useCallback(() => {
|
|
2726
2744
|
const el = wrapRef.current;
|
|
2727
2745
|
if (!el || expanded) return;
|
|
2728
2746
|
const tops = [];
|
|
2729
2747
|
let rh = 0;
|
|
2730
|
-
let plusTopMeas = null;
|
|
2731
|
-
let hiddenMeas = 0;
|
|
2732
2748
|
for (const k of el.children) {
|
|
2733
|
-
if (!(k instanceof HTMLElement)) continue;
|
|
2734
|
-
if (k.getAttribute("data-more") === "1") {
|
|
2735
|
-
plusTopMeas = k.offsetTop;
|
|
2736
|
-
continue;
|
|
2737
|
-
}
|
|
2738
|
-
if (k.getAttribute("data-chip") !== "1") continue;
|
|
2739
|
-
if (getComputedStyle(k).display === "none") {
|
|
2740
|
-
hiddenMeas += 1;
|
|
2741
|
-
continue;
|
|
2742
|
-
}
|
|
2749
|
+
if (!(k instanceof HTMLElement) || k.getAttribute("data-chip") !== "1") continue;
|
|
2743
2750
|
tops.push(k.offsetTop);
|
|
2744
2751
|
if (!rh && k.offsetHeight) rh = k.offsetHeight;
|
|
2745
2752
|
}
|
|
2746
2753
|
if (!tops.length) return;
|
|
2747
|
-
const lastRowTop = tops[tops.length - 1];
|
|
2748
|
-
const plusWrappedNow = plusTopMeas != null && plusTopMeas > lastRowTop;
|
|
2749
2754
|
setGeom((prev) => {
|
|
2750
|
-
const
|
|
2751
|
-
const hiddenCount2 =
|
|
2752
|
-
const
|
|
2753
|
-
|
|
2755
|
+
const clipTopV = clipTopOf2(tops, maxRows);
|
|
2756
|
+
const hiddenCount2 = countBeyondRows2(tops, maxRows);
|
|
2757
|
+
const lastVisTop2 = visibleRowTop2(tops, maxRows);
|
|
2758
|
+
let lastVisRight = 0;
|
|
2759
|
+
let lastVisHeight = rh;
|
|
2760
|
+
for (const k of el.children) {
|
|
2761
|
+
if (!(k instanceof HTMLElement) || k.getAttribute("data-chip") !== "1") continue;
|
|
2762
|
+
if (k.offsetTop < clipTopV) {
|
|
2763
|
+
lastVisRight = k.offsetLeft + k.offsetWidth;
|
|
2764
|
+
lastVisHeight = k.offsetHeight;
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2767
|
+
const next = { tops, clipTop: clipTopV, hiddenCount: hiddenCount2, lastVisTop: lastVisTop2, lastVisRight, lastVisHeight };
|
|
2768
|
+
const same = prev && prev.hiddenCount === next.hiddenCount && prev.clipTop === next.clipTop && prev.lastVisTop === next.lastVisTop && prev.lastVisRight === next.lastVisRight && prev.lastVisHeight === next.lastVisHeight && prev.tops.length === next.tops.length && prev.tops.every((t, i) => t === next.tops[i]);
|
|
2754
2769
|
return same ? prev : next;
|
|
2755
2770
|
});
|
|
2756
2771
|
setRowHeight((prev) => Math.abs(prev - rh) <= 0.5 ? prev : rh);
|
|
@@ -2763,14 +2778,19 @@ function ZoneChips({ zone, counts, labels, active, onPick, trailing, wrapTitle }
|
|
|
2763
2778
|
}, [active]);
|
|
2764
2779
|
useEffect(() => {
|
|
2765
2780
|
if (!active || !geom) return;
|
|
2766
|
-
const
|
|
2767
|
-
const d = autoExpandDecision2(
|
|
2781
|
+
const activeTop = geom.tops[activeIdx + 1];
|
|
2782
|
+
const d = autoExpandDecision2(activeTop, geom.clipTop, autoRef.current === active, expanded);
|
|
2768
2783
|
if (d.record) autoRef.current = active;
|
|
2769
2784
|
if (d.expand) setExpanded(true);
|
|
2770
2785
|
}, [active, activeIdx, expanded, geom]);
|
|
2771
2786
|
useLayoutEffect(() => {
|
|
2772
2787
|
if (!expanded) measure();
|
|
2773
|
-
}, [
|
|
2788
|
+
}, [chipsSignature, active, expanded, zone, stuck, rowHeight, measure]);
|
|
2789
|
+
useEffect(() => {
|
|
2790
|
+
if (expanded || !geom || typeof requestAnimationFrame !== "function") return;
|
|
2791
|
+
const raf = requestAnimationFrame(measure);
|
|
2792
|
+
return () => cancelAnimationFrame(raf);
|
|
2793
|
+
}, [geom, expanded, measure]);
|
|
2774
2794
|
useEffect(() => {
|
|
2775
2795
|
const el = wrapRef.current;
|
|
2776
2796
|
if (!el || expanded || typeof ResizeObserver === "undefined") return;
|
|
@@ -2804,20 +2824,27 @@ function ZoneChips({ zone, counts, labels, active, onPick, trailing, wrapTitle }
|
|
|
2804
2824
|
}, []);
|
|
2805
2825
|
const clip = !expanded;
|
|
2806
2826
|
const filterPinned = clip && trailing != null;
|
|
2827
|
+
const clipTop = geom ? geom.clipTop : Infinity;
|
|
2807
2828
|
const hiddenCount = geom ? geom.hiddenCount : 0;
|
|
2808
|
-
const
|
|
2809
|
-
const
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2829
|
+
const lastVisTop = geom ? geom.lastVisTop : 0;
|
|
2830
|
+
const moreLeft = geom ? geom.lastVisRight + 6 : 0;
|
|
2831
|
+
const moreHeight = geom ? geom.lastVisHeight : rowHeight || 22;
|
|
2832
|
+
const btn = (c, i) => {
|
|
2833
|
+
const top = geom ? geom.tops[i + 1] : null;
|
|
2834
|
+
const clipped = clip && geom && typeof top === "number" && top >= clipTop;
|
|
2835
|
+
return h(
|
|
2836
|
+
"button",
|
|
2837
|
+
{
|
|
2838
|
+
key: c.id,
|
|
2839
|
+
"data-chip": "1",
|
|
2840
|
+
style: clipped ? { visibility: "hidden" } : void 0,
|
|
2841
|
+
className: `dshm-chip${active === c.id ? " on" : ""}${c.count === 0 ? " zero" : ""}`,
|
|
2842
|
+
onClick: () => onPick(active === c.id ? null : c.id)
|
|
2843
|
+
},
|
|
2844
|
+
// 0.9.45 U3/U7:0 计数桶显式渲染「0」+ 降透明(社区区 chip 计数恒 >0,行为不变)
|
|
2845
|
+
`${c.labelKey ? lookup(c.labelKey) : c.label} ${c.count || 0}`
|
|
2846
|
+
);
|
|
2847
|
+
};
|
|
2821
2848
|
return h(
|
|
2822
2849
|
React.Fragment,
|
|
2823
2850
|
null,
|
|
@@ -2834,15 +2861,22 @@ function ZoneChips({ zone, counts, labels, active, onPick, trailing, wrapTitle }
|
|
|
2834
2861
|
"div",
|
|
2835
2862
|
{
|
|
2836
2863
|
ref: wrapRef,
|
|
2837
|
-
className: `dshm-chips${clip ? " dshm-chips-clip" : ""}${filterPinned ? " dshm-chips-gutter" : ""}`,
|
|
2864
|
+
className: `dshm-chips${clip ? " dshm-chips-clip" : ""}${filterPinned ? " dshm-chips-gutter" : ""}${clip && hiddenCount > 0 ? " dshm-chips-reserve" : ""}`,
|
|
2838
2865
|
title: wrapTitle || void 0,
|
|
2839
2866
|
style: { maxHeight: clip ? `${maxH}px` : "none" }
|
|
2840
2867
|
},
|
|
2841
2868
|
h("button", { "data-chip": "1", className: `dshm-chip${active == null ? " on" : ""}`, onClick: () => onPick(null) }, lookup("cat.all")),
|
|
2842
|
-
...chips.
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
//
|
|
2869
|
+
...chips.map((c, i) => btn(c, i)),
|
|
2870
|
+
// 折叠态 +N(0.9.57 终版):绝对定位钉在「末可见行右端预留槽」——出流不挤压 chip、不参与
|
|
2871
|
+
// 行分组(data-more);预留槽由 .dshm-chips-clip 的 padding-right 划出,几何保证零重叠;
|
|
2872
|
+
// 左缘动态紧贴末可见 chip 右侧 6px,仍受右侧预留槽保护;垂直对齐末可见行。
|
|
2873
|
+
clip && geom && hiddenCount > 0 ? h("button", {
|
|
2874
|
+
key: "more",
|
|
2875
|
+
"data-more": "1",
|
|
2876
|
+
className: "dshm-chip dsvm-chipmore-plus",
|
|
2877
|
+
style: { top: `${lastVisTop}px`, left: `${moreLeft}px`, height: `${moreHeight}px` },
|
|
2878
|
+
onClick: () => setExpanded(true)
|
|
2879
|
+
}, `+${hiddenCount}`) : null,
|
|
2846
2880
|
expanded ? [
|
|
2847
2881
|
h("button", {
|
|
2848
2882
|
key: "collapse",
|
|
@@ -4497,7 +4531,11 @@ function MarketPanel({ onClose }) {
|
|
|
4497
4531
|
function mountPanel() {
|
|
4498
4532
|
if (typeof document === "undefined") return;
|
|
4499
4533
|
ensureCss();
|
|
4500
|
-
|
|
4534
|
+
const existing = document.getElementById("dshm-panel-root");
|
|
4535
|
+
if (existing) {
|
|
4536
|
+
if (existing.querySelector(".dshm-panel")) return;
|
|
4537
|
+
existing.remove();
|
|
4538
|
+
}
|
|
4501
4539
|
const container = document.createElement("div");
|
|
4502
4540
|
container.id = "dshm-panel-root";
|
|
4503
4541
|
document.body.appendChild(container);
|