dsh-m 0.9.55 → 0.9.56

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 CHANGED
@@ -8,6 +8,13 @@ The full release history of dsh-m, maintained bilingually: **Chinese first, Engl
8
8
 
9
9
  ## 中文
10
10
 
11
+ ### 0.9.56 变更:ZoneChips +N 重叠根治——in-flow 文档流布局 + 折行减类(主人方案)
12
+
13
+ - **缺陷(0.9.55 真机复现)**:折叠态 `+N` 采用「绝对定位 + contentW−48 夹取」,当末可见行剩余空间小于徽章宽时,徽章压住末可见 chip(真机:+9 压住「文档与渲染 61」右端计数)。
14
+ - **根治(in-flow 文档流布局)**:`+N` 弃绝对定位,以 in-flow 插在「末可见分类」与「被裁分类」之间——flow 布局与 chip 的重叠在构造上不可能。
15
+ - **折行减类(主人方案)**:被裁分类 `visibility:hidden` → **`display:none`**(不占位,折行减类生效前提);测量发现 `+N` 顶 > 末可见 chip 行顶(被折出)⇒ `hiddenCount +1`,一枚可见分类退位、`+N` 退回末可见行,同时 `max-height` 扩一行给 `+N` 独占;棘轮单向下行,宽度增大时保守不多显(+N 计数仍真实)。
16
+ - **测试**:全量 1221 例零失败、typecheck 零错误;无头探针实证常宽 880/窄宽 420/展开/收起/Q1 回归全部 `chipOverlapPx = 0`、`+N` 全程可见、展开态 23+⌃+筛选全量、收起后 +N 回归蓝筐位。
17
+
11
18
  ### 0.9.55 变更:ZoneChips 折叠/展开切换钮内联化(对齐 dsh-market 参考设计)+ overlay 叠涂 scrim 强化对比度
12
19
 
13
20
  - **切换钮内联化(主人提出,dsh-market v1.66.11 参考设计)**:`+N` 从右侧 overlay 组改为**绝对定位内联**——钉在「末可见分类 chip 右侧 +6px」(即末可见行剩余空位),与展开态 `⌃`(列表末尾 in-flow)构成统一位置语义「紧跟当前列表末尾」,只翻方向不挪位;窄宽度放不下时贴内容右缘收口(不侵入筛选槽)。gutter 随 `+N` 移出由 132px 瘦身至 96px(仅承载钉住筛选),同宽度可多显示约 1 颗分类。overlay 组内 `+N` 移除后仅剩钉住筛选,第④类碰撞(R2-N1)自然消解。
@@ -412,6 +419,13 @@ The full release history of dsh-m, maintained bilingually: **Chinese first, Engl
412
419
 
413
420
  ## English
414
421
 
422
+ ### Fixed in 0.9.56 — ZoneChips +N overlap eradicated: in-flow document layout + wrap-yield-a-category (owner's design)
423
+
424
+ - **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).
425
+ - **Fix (in-flow document layout)**: `+N` abandons absolute positioning and flows inline between the last visible category and the clipped categories — chip/badge overlap is impossible by construction.
426
+ - **Wrap-yield-a-category (owner's design)**: clipped categories switch `visibility:hidden` → **`display:none`** (no space — the prerequisite for yielding to work); when the measure finds the `+N` top beyond the last visible chip's row top (wrapped), `hiddenCount +1` — one visible category yields, `+N` pulls back into the last visible row, and `max-height` grows one row to give `+N` its own line; the ratchet is single-direction (down), staying conservative on width growth while the count stays truthful.
427
+ - **Tests**: full suite 1221 cases zero failures, typecheck clean; headless probe verified 880px/420px/expanded/collapsed/Q1-regression all at `chipOverlapPx = 0` with `+N` visible throughout, expanded view showing 23+⌃+filter in full, and `+N` returning to its inline slot after collapse.
428
+
415
429
  ### Changed in 0.9.55 — ZoneChips expand/collapse toggle inline (aligned with the dsh-market reference design) + overlay stacked scrim for contrast
416
430
 
417
431
  - **Inline toggle (owner-requested, reference: dsh-market v1.66.11)**: `+N` moves out of the right-side overlay group into an **absolutely-positioned inline slot** pinned at "last visible category chip's right edge + 6px" (the leftover of the last visible row), sharing one position semantic with the expanded `⌃` (in-flow at list end): "hug the current end of the list", flipping direction without relocating; when the leftover is too narrow it clamps to the content right edge without entering the filter gutter. The gutter slims from 132px to 96px (hosting only the pinned filter), showing ~1 more category per width. With `+N` removed, the overlay group hosts only the pinned filter and the R2-N1 4th collision class dissolves naturally.
package/lib/client.js CHANGED
@@ -1558,7 +1558,7 @@ var h = React.createElement;
1558
1558
  var { useState, useEffect, useLayoutEffect, useCallback, useMemo, useRef } = React;
1559
1559
  var PLUGIN_ID = "dsh-m";
1560
1560
  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, chipRows: chipRows2, countBeyondRows: countBeyondRows2, clipTopOf: clipTopOf2, autoExpandDecision: autoExpandDecision2 } = (init_market_state(), __toCommonJS(market_state_exports));
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, chipRows: chipRows2, countBeyondRows: countBeyondRows2, autoExpandDecision: autoExpandDecision2 } = (init_market_state(), __toCommonJS(market_state_exports));
1562
1562
  var { readMarketSnapshot: readMarketSnapshot2, writeMarketSnapshot: writeMarketSnapshot2, isDefaultFirstPageQuery: isDefaultFirstPageQuery2 } = (init_market_snapshot(), __toCommonJS(market_snapshot_exports));
1563
1563
  var { backdropCloseHandlers: backdropCloseHandlers2 } = (init_backdrop(), __toCommonJS(backdrop_exports));
1564
1564
  var { createMarkdown: createMarkdown2 } = (init_markdown(), __toCommonJS(markdown_exports));
@@ -2190,7 +2190,6 @@ var CSS = `
2190
2190
  .dshm-chips-clip{overflow:hidden}
2191
2191
  .dshm-chips-gutter{padding-right:var(--dshm-clip-gutter,96px)}
2192
2192
  .dsvm-chipmore{position:absolute;right:8px;display:flex;align-items:center;gap:8px;min-height:28px;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));border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.18));border-radius:999px;padding:2px 8px;box-sizing:border-box}
2193
- .dsvm-chipmore-plus{position:absolute;z-index:2;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))}
2194
2193
  .dsvm-searchrow{display:flex;align-items:center;gap:8px}
2195
2194
  .dsvm-searchrow .dshm-search{flex:1;display:flex}
2196
2195
  .dshm-search{position:relative}
@@ -2720,44 +2719,42 @@ function ZoneChips({ zone, counts, labels, active, onPick, trailing, wrapTitle }
2720
2719
  const autoRef = useRef(null);
2721
2720
  const maxRows = stuck ? 1 : 2;
2722
2721
  const activeIdx = active ? chips.findIndex((c) => c.id === active) : -1;
2723
- const maxH = maxRows * (rowHeight || 22) + (maxRows - 1) * 6;
2722
+ const plusWrapped = !!(geom && geom.plusWrapped);
2723
+ const maxRowsEff = maxRows + (plusWrapped ? 1 : 0);
2724
+ const maxH = maxRowsEff * (rowHeight || 22) + (maxRowsEff - 1) * 6;
2724
2725
  const measure = useCallback(() => {
2725
2726
  const el = wrapRef.current;
2726
2727
  if (!el || expanded) return;
2727
2728
  const tops = [];
2728
2729
  let rh = 0;
2730
+ let plusTopMeas = null;
2731
+ let hiddenMeas = 0;
2729
2732
  for (const k of el.children) {
2730
- if (!(k instanceof HTMLElement) || k.getAttribute("data-chip") !== "1") continue;
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
+ }
2731
2743
  tops.push(k.offsetTop);
2732
2744
  if (!rh && k.offsetHeight) rh = k.offsetHeight;
2733
2745
  }
2734
2746
  if (!tops.length) return;
2735
- const clipTop2 = clipTopOf2(tops, maxRows);
2736
- let lastVisRight = 0;
2737
- let lastVisTop = 0;
2738
- let idx = -1;
2739
- for (const k of el.children) {
2740
- if (!(k instanceof HTMLElement) || k.getAttribute("data-chip") !== "1") continue;
2741
- idx += 1;
2742
- if (idx === 0) continue;
2743
- if (k.offsetTop < clipTop2) {
2744
- lastVisRight = k.offsetLeft + k.offsetWidth;
2745
- lastVisTop = k.offsetTop;
2746
- }
2747
- }
2748
- const cs = getComputedStyle(el);
2749
- const contentW = el.clientWidth - (parseFloat(cs.paddingRight) || 0);
2750
- const plusLeft = Math.max(0, Math.min(lastVisRight + 6, contentW - 48));
2751
- const next = {
2752
- tops,
2753
- clipTop: clipTop2,
2754
- hiddenCount: countBeyondRows2(tops.slice(1), maxRows),
2755
- plusLeft,
2756
- plusTop: lastVisTop
2757
- };
2758
- setGeom((prev) => prev && prev.tops.length === next.tops.length && prev.clipTop === next.clipTop && prev.hiddenCount === next.hiddenCount && prev.plusLeft === next.plusLeft && prev.plusTop === next.plusTop ? prev : next);
2747
+ const lastRowTop = tops[tops.length - 1];
2748
+ const plusWrappedNow = plusTopMeas != null && plusTopMeas > lastRowTop;
2749
+ setGeom((prev) => {
2750
+ const prevHC = prev ? prev.hiddenCount : countBeyondRows2(tops.slice(1), maxRows);
2751
+ const hiddenCount2 = plusWrappedNow ? prevHC + 1 : prevHC;
2752
+ const next = { tops, hiddenCount: hiddenCount2 };
2753
+ const same = prev && prev.hiddenCount === next.hiddenCount && prev.tops.length === next.tops.length && prev.tops.every((t, i) => t === next.tops[i]);
2754
+ return same ? prev : next;
2755
+ });
2759
2756
  setRowHeight((prev) => Math.abs(prev - rh) <= 0.5 ? prev : rh);
2760
- }, [expanded, maxRows]);
2757
+ }, [expanded, maxRows, chips.length]);
2761
2758
  useEffect(() => {
2762
2759
  if (!active) {
2763
2760
  autoRef.current = null;
@@ -2766,14 +2763,14 @@ function ZoneChips({ zone, counts, labels, active, onPick, trailing, wrapTitle }
2766
2763
  }, [active]);
2767
2764
  useEffect(() => {
2768
2765
  if (!active || !geom) return;
2769
- const activeTop = geom.tops[activeIdx + 1];
2770
- const d = autoExpandDecision2(activeTop, geom.clipTop, autoRef.current === active, expanded);
2766
+ const visCats = geom.tops.length - 1;
2767
+ const d = autoExpandDecision2(activeIdx, visCats, autoRef.current === active, expanded);
2771
2768
  if (d.record) autoRef.current = active;
2772
2769
  if (d.expand) setExpanded(true);
2773
2770
  }, [active, activeIdx, expanded, geom]);
2774
2771
  useLayoutEffect(() => {
2775
2772
  if (!expanded) measure();
2776
- }, [chips, active, expanded, zone, stuck, rowHeight, measure]);
2773
+ }, [chips, active, expanded, zone, stuck, rowHeight, measure, geom]);
2777
2774
  useEffect(() => {
2778
2775
  const el = wrapRef.current;
2779
2776
  if (!el || expanded || typeof ResizeObserver === "undefined") return;
@@ -2807,24 +2804,20 @@ function ZoneChips({ zone, counts, labels, active, onPick, trailing, wrapTitle }
2807
2804
  }, []);
2808
2805
  const clip = !expanded;
2809
2806
  const filterPinned = clip && trailing != null;
2810
- const clipTop = geom ? geom.clipTop : Infinity;
2811
2807
  const hiddenCount = geom ? geom.hiddenCount : 0;
2812
- const btn = (c, i) => {
2813
- const top = geom ? geom.tops[i + 1] : null;
2814
- const clipped = clip && geom && typeof top === "number" && top >= clipTop;
2815
- return h(
2816
- "button",
2817
- {
2818
- key: c.id,
2819
- "data-chip": "1",
2820
- style: clipped ? { visibility: "hidden" } : void 0,
2821
- className: `dshm-chip${active === c.id ? " on" : ""}${c.count === 0 ? " zero" : ""}`,
2822
- onClick: () => onPick(active === c.id ? null : c.id)
2823
- },
2824
- // 0.9.45 U3/U7:0 计数桶显式渲染「0」+ 降透明(社区区 chip 计数恒 >0,行为不变)
2825
- `${c.labelKey ? lookup(c.labelKey) : c.label} ${c.count || 0}`
2826
- );
2827
- };
2808
+ const visCount = geom ? Math.max(0, chips.length - hiddenCount) : chips.length;
2809
+ const btn = (c, i, hide) => h(
2810
+ "button",
2811
+ {
2812
+ key: c.id,
2813
+ "data-chip": "1",
2814
+ style: hide ? { display: "none" } : void 0,
2815
+ className: `dshm-chip${active === c.id ? " on" : ""}${c.count === 0 ? " zero" : ""}`,
2816
+ onClick: () => onPick(active === c.id ? null : c.id)
2817
+ },
2818
+ // 0.9.45 U3/U7:0 计数桶显式渲染「0」+ 降透明(社区区 chip 计数恒 >0,行为不变)
2819
+ `${c.labelKey ? lookup(c.labelKey) : c.label} ${c.count || 0}`
2820
+ );
2828
2821
  return h(
2829
2822
  React.Fragment,
2830
2823
  null,
@@ -2846,16 +2839,10 @@ function ZoneChips({ zone, counts, labels, active, onPick, trailing, wrapTitle }
2846
2839
  style: { maxHeight: clip ? `${maxH}px` : "none" }
2847
2840
  },
2848
2841
  h("button", { "data-chip": "1", className: `dshm-chip${active == null ? " on" : ""}`, onClick: () => onPick(null) }, lookup("cat.all")),
2849
- ...chips.map((c, i) => btn(c, i)),
2850
- // 折叠态 +N(0.9.55 对齐 dsh-market 参考设计):绝对定位内联钉在「末可见分类 chip 右侧 +6px」,
2851
- // 与展开态 ⌃(列表末尾 in-flow)构成同一位置语义——「紧跟当前列表末尾」,只翻方向不挪位。
2852
- // 不带 data-chip(不参与行分组测量);位于可见行区内,不受 overflow 裁剪。
2853
- clip && geom && hiddenCount > 0 ? h("button", {
2854
- key: "more",
2855
- className: "dshm-chip dsvm-chipmore-plus",
2856
- style: { left: `${geom.plusLeft}px`, top: `${geom.plusTop}px` },
2857
- onClick: () => setExpanded(true)
2858
- }, `+${hiddenCount}`) : null,
2842
+ ...chips.slice(0, visCount).map((c, i) => btn(c, i, false)),
2843
+ clip && chips.length > 0 && hiddenCount > 0 ? h("button", { key: "more", "data-more": "1", className: "dshm-chip", onClick: () => setExpanded(true) }, `+${hiddenCount}`) : null,
2844
+ ...chips.slice(visCount).map((c, i) => btn(c, visCount + i, clip)),
2845
+ // hide 仅折叠态生效;展开态全量可见
2859
2846
  expanded ? [
2860
2847
  h("button", {
2861
2848
  key: "collapse",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-m",
3
- "version": "0.9.55",
3
+ "version": "0.9.56",
4
4
  "description": "可自定义收录清单的 DSH 插件市场:收录、安装、卸载、升级一站完成,附 agent 工具与 dshm CLI。",
5
5
  "license": "MIT",
6
6
  "type": "module",