dsh-m 0.9.57 → 0.9.59

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,18 @@ The full release history of dsh-m, maintained bilingually: **Chinese first, Engl
8
8
 
9
9
  ## 中文
10
10
 
11
+ ### 0.9.59 优化:分类行左缘内缩 8px + 全屏下空转 ⌃ 收起钮按行数派生隐藏
12
+
13
+ - **分类行内缩(`f42e5be`)**:分类 chips 行与搜索框/卡片左缘齐平,小颗粒胶囊贴着大元素左缘视觉憋仄;分类行整体右移 8px 留出呼吸空间。仅作用分类行(ZoneChips),上方分区 chips 行不随动。`offsetLeft` 与 absolute 定位同以 `.dshm-chips` 的 padding 盒为原点 → `+N` 跟随、右侧筛选钉位、`offsetTop` 裁剪几何均不受影响。
14
+ - **⌃ 空转修复(`7cbdf11`)**:全屏等宽容器下分类 chips 可能排进 ≤ maxRows(2)行,展开态 `⌃` 收起钮点击后布局零变化(`hiddenCount=0` 连 `+N` 都不会出现),成死按钮。新增展开态行数测量 `expRows`(复用 `chipRows` 纯函数,measure 展开态分支只写 `expRows` 不动 `geom`),`⌃` 仅当展开行数 > maxRows 时渲染;ResizeObserver 在展开态也挂载——全屏↔窗口切换的宽度变化正是重测触发源,全屏下 `⌃` 自动隐藏、退回窗口自动恢复,行数含 `⌃` 自身故显隐收敛不振荡。
15
+ - **验证**:1224 项测试全绿;真实 DSH Web 实测窗口/全屏两态 `⌃` 显隐与收起行为、分类行 8px 内缩生效。
16
+
17
+ ### 0.9.58 优化:+N 动态紧跟末可见分类,并与其框高一致
18
+
19
+ - **位置(`4ade54f`)**:在 0.9.57 的独立安全槽位中,根据末可见分类 chip 的实测右缘设置 `+N` 左缘 = 右缘 + 6px;槽位由 52px 扩为 60px,窄宽时仍不与分类或筛选重叠。精选区没有筛选时不额外预留筛选 gutter;展开态 `⌃` 仍位于列表尾部。
20
+ - **框高(`aeed14c`)**:`+N` 直接采用末可见分类的实测 border-box 高度,并以 inline-flex 居中数字,不再依赖西文数字与中文标签各自的浏览器自动行高。只改 `+N`,不改变分区 chip 的尺寸。
21
+ - **验证**:1224 项测试与 typecheck 全绿;用真实 DSH 目录数据在 880/700/520/420/320px 社区区测得两框同高 21px、顶边差 0、水平间距约 5–6px、相交面积 0;260px 精选区也无重叠,精选↔社区往返正常。
22
+
11
23
  ### 0.9.57 修复:精选切换后面板崩溃/无法重开 + 折叠筛选双框与 +N 重叠
12
24
 
13
25
  - **根因(0.9.56 真机复现)**:精选区的标签对象可能每次父级重渲染都重建;layout effect 依赖 `chips` 数组引用及自身更新的 `geom`,同步测量反复进入、最终触发 React `Maximum update depth exceeded`(#185)。React 清空面板树但留下 `#dshm-panel-root`,旧入口守卫只检查容器存在,导致点击入口永久无效。叠加折叠筛选定位壳和按钮的双边框、以及 +N 位置夹取/文档流试验在窄宽下压住相邻 chip。
@@ -426,6 +438,18 @@ The full release history of dsh-m, maintained bilingually: **Chinese first, Engl
426
438
 
427
439
  ## English
428
440
 
441
+ ### Improved in 0.9.59 — 8px category-row inset and a derived visibility for the no-op collapse toggle
442
+
443
+ - **Category-row inset (`f42e5be`)**: the category chip row sat flush with the search box and card grid, and small pills hugging large elements felt cramped; the row now starts 8px further right for breathing room. Only the category row (ZoneChips) moves — the zone chip row above stays put. `offsetLeft` and absolute positioning share the `.dshm-chips` padding-box origin, so `+N` tracking, the pinned Filter trigger and `offsetTop`-based clip geometry are all unaffected.
444
+ - **No-op `⌃` fix (`7cbdf11`)**: at fullscreen width all category chips can fit within maxRows (2) rows, where clicking the expanded-state `⌃` collapse toggle changed nothing visually (`hiddenCount=0`, not even a `+N` appears) — a dead button. Added an expanded-state row measurement `expRows` (reusing the pure `chipRows` helper; the expanded measure branch only writes `expRows`, never `geom`), and `⌃` renders only when expanded rows exceed maxRows. The ResizeObserver now also observes while expanded — the width change of fullscreen↔window switches is exactly the remeasure trigger — so `⌃` hides itself in fullscreen and returns back in the window; counting includes `⌃` itself, so visibility converges without oscillation.
445
+ - **Verification**: 1224 tests pass; verified against real DSH Web for `⌃` visibility and collapse behavior in both window and fullscreen, and for the 8px category-row inset.
446
+
447
+ ### Improved in 0.9.58 — the +N toggle follows the last visible category at matching height
448
+
449
+ - **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.
450
+ - **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.
451
+ - **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.
452
+
429
453
  ### Fixed in 0.9.57 — curated-zone update loop, dead panel entry, and collapsed filter/overflow overlaps
430
454
 
431
455
  - **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.
package/lib/client.js CHANGED
@@ -1573,7 +1573,7 @@ var h = React.createElement;
1573
1573
  var { useState, useEffect, useLayoutEffect, useCallback, useMemo, useRef } = React;
1574
1574
  var PLUGIN_ID = "dsh-m";
1575
1575
  var API = "/dshm";
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));
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, chipRows: chipRows2, countBeyondRows: countBeyondRows2, clipTopOf: clipTopOf2, visibleRowTop: visibleRowTop2, autoExpandDecision: autoExpandDecision2 } = (init_market_state(), __toCommonJS(market_state_exports));
1577
1577
  var { readMarketSnapshot: readMarketSnapshot2, writeMarketSnapshot: writeMarketSnapshot2, isDefaultFirstPageQuery: isDefaultFirstPageQuery2 } = (init_market_snapshot(), __toCommonJS(market_snapshot_exports));
1578
1578
  var { backdropCloseHandlers: backdropCloseHandlers2 } = (init_backdrop(), __toCommonJS(backdrop_exports));
1579
1579
  var { createMarkdown: createMarkdown2 } = (init_markdown(), __toCommonJS(markdown_exports));
@@ -2204,11 +2204,11 @@ var CSS = `
2204
2204
  .dshm-chips{display:flex;flex-wrap:wrap;gap:6px;position:relative}
2205
2205
  .dshm-chips-clip{overflow:hidden}
2206
2206
  .dshm-chips-gutter{padding-right:var(--dshm-clip-gutter,96px)}
2207
- /* +N/\u7B5B\u9009\u5206\u522B\u5360\u53F3\u4FA7 52/96px\uFF0C\u4E0D\u80FD\u8BA9 gutter \u540C\u7279\u5F02\u6027\u89C4\u5219\u8986\u76D6\u603B\u9884\u7559\u5BBD\u5EA6\u3002 */
2208
- .dshm-chips.dshm-chips-clip.dshm-chips-reserve{padding-right:52px}
2209
- .dshm-chips.dshm-chips-clip.dshm-chips-gutter.dshm-chips-reserve{padding-right:calc(var(--dshm-clip-gutter,96px) + 52px)}
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
2210
  .dsvm-chipmore{position:absolute;right:8px}
2211
- .dsvm-chipmore-plus{position:absolute;right:calc(var(--dshm-clip-gutter,96px) + 6px);z-index:1;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))}
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))}
2212
2212
  .dsvm-searchrow{display:flex;align-items:center;gap:8px}
2213
2213
  .dsvm-searchrow .dshm-search{flex:1;display:flex}
2214
2214
  .dshm-search{position:relative}
@@ -2230,6 +2230,10 @@ var CSS = `
2230
2230
  .dshm-chip:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(38,49,72,.06))}
2231
2231
  .dshm-chip.on{background:var(--dsw-specific-sidebar-nav-item-active,rgba(38,49,72,.08));border-color:transparent;color:var(--dsw-alias-label-primary,inherit);font-weight:500}
2232
2232
  .dsvm-chipswrap{position:sticky;top:-14px;z-index:5;background:var(--dsw-alias-bg-base,#fff);padding:8px 0;margin:-8px 0;border-bottom:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.14))}
2233
+ /* 0.9.59 \u5206\u7C7B\u884C\u5DE6\u7F18\u5185\u7F29 8px\uFF1A\u4E0E\u641C\u7D22\u6846/\u5361\u7247\u5DE6\u7F18\u9F50\u5E73\u65F6\u80F6\u56CA\u884C\u89C6\u89C9\u618B\u4EC4\uFF0C\u6574\u4F53\u53F3\u79FB\u7559\u51FA\u547C\u5438\u7A7A\u95F4\u3002
2234
+ \u4EC5\u4F5C\u7528\u5206\u7C7B\u884C\uFF08ZoneChips\uFF09\uFF0C\u4E0A\u65B9\u5206\u533A chips \u884C\u4E0D\u968F\u52A8\uFF1BoffsetLeft \u4E0E absolute \u5B9A\u4F4D\u540C\u4EE5
2235
+ .dshm-chips \u7684 padding \u76D2\u4E3A\u539F\u70B9 \u2192 +N \u8DDF\u968F\u3001\u53F3\u4FA7\u7B5B\u9009\u9489\u4F4D\u3001offsetTop \u88C1\u526A\u51E0\u4F55\u5747\u4E0D\u53D7\u5F71\u54CD\u3002 */
2236
+ .dsvm-chipswrap .dshm-chips{padding-left:8px}
2233
2237
  .dsvm-pagejump{display:inline-flex;align-items:center;gap:4px;margin-left:8px}
2234
2238
  .dsvm-pagejump-input{width:56px;height:24px;box-sizing:border-box;border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.28));background:transparent;color:var(--dsw-alias-label-secondary,#4b5563);border-radius:999px;padding:0 10px;font:inherit;font-size:12px;outline:none;text-align:center;transition:border-color .15s,color .15s}
2235
2239
  .dsvm-pagejump-input:hover{color:var(--dsw-alias-label-primary,inherit)}
@@ -2732,6 +2736,7 @@ function ZoneChips({ zone, counts, labels, active, onPick, trailing, wrapTitle }
2732
2736
  const chipsSignature = JSON.stringify(chips.map((c) => [c.id, c.labelKey ? lookup(c.labelKey) : c.label, c.count]));
2733
2737
  const [expanded, setExpanded] = useState(false);
2734
2738
  const [stuck, setStuck] = useState(false);
2739
+ const [expRows, setExpRows] = useState(0);
2735
2740
  const [geom, setGeom] = useState(null);
2736
2741
  const [rowHeight, setRowHeight] = useState(0);
2737
2742
  const wrapRef = useRef(null);
@@ -2742,7 +2747,7 @@ function ZoneChips({ zone, counts, labels, active, onPick, trailing, wrapTitle }
2742
2747
  const maxH = maxRows * (rowHeight || 22) + (maxRows - 1) * 6;
2743
2748
  const measure = useCallback(() => {
2744
2749
  const el = wrapRef.current;
2745
- if (!el || expanded) return;
2750
+ if (!el) return;
2746
2751
  const tops = [];
2747
2752
  let rh = 0;
2748
2753
  for (const k of el.children) {
@@ -2751,12 +2756,26 @@ function ZoneChips({ zone, counts, labels, active, onPick, trailing, wrapTitle }
2751
2756
  if (!rh && k.offsetHeight) rh = k.offsetHeight;
2752
2757
  }
2753
2758
  if (!tops.length) return;
2759
+ if (expanded) {
2760
+ const rows = chipRows2(tops);
2761
+ setExpRows((prev) => prev === rows ? prev : rows);
2762
+ return;
2763
+ }
2754
2764
  setGeom((prev) => {
2755
2765
  const clipTopV = clipTopOf2(tops, maxRows);
2756
2766
  const hiddenCount2 = countBeyondRows2(tops, maxRows);
2757
2767
  const lastVisTop2 = visibleRowTop2(tops, maxRows);
2758
- const next = { tops, clipTop: clipTopV, hiddenCount: hiddenCount2, lastVisTop: lastVisTop2 };
2759
- const same = prev && prev.hiddenCount === next.hiddenCount && prev.clipTop === next.clipTop && prev.lastVisTop === next.lastVisTop && prev.tops.length === next.tops.length && prev.tops.every((t, i) => t === next.tops[i]);
2768
+ let lastVisRight = 0;
2769
+ let lastVisHeight = rh;
2770
+ for (const k of el.children) {
2771
+ if (!(k instanceof HTMLElement) || k.getAttribute("data-chip") !== "1") continue;
2772
+ if (k.offsetTop < clipTopV) {
2773
+ lastVisRight = k.offsetLeft + k.offsetWidth;
2774
+ lastVisHeight = k.offsetHeight;
2775
+ }
2776
+ }
2777
+ const next = { tops, clipTop: clipTopV, hiddenCount: hiddenCount2, lastVisTop: lastVisTop2, lastVisRight, lastVisHeight };
2778
+ 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]);
2760
2779
  return same ? prev : next;
2761
2780
  });
2762
2781
  setRowHeight((prev) => Math.abs(prev - rh) <= 0.5 ? prev : rh);
@@ -2775,7 +2794,7 @@ function ZoneChips({ zone, counts, labels, active, onPick, trailing, wrapTitle }
2775
2794
  if (d.expand) setExpanded(true);
2776
2795
  }, [active, activeIdx, expanded, geom]);
2777
2796
  useLayoutEffect(() => {
2778
- if (!expanded) measure();
2797
+ measure();
2779
2798
  }, [chipsSignature, active, expanded, zone, stuck, rowHeight, measure]);
2780
2799
  useEffect(() => {
2781
2800
  if (expanded || !geom || typeof requestAnimationFrame !== "function") return;
@@ -2784,7 +2803,7 @@ function ZoneChips({ zone, counts, labels, active, onPick, trailing, wrapTitle }
2784
2803
  }, [geom, expanded, measure]);
2785
2804
  useEffect(() => {
2786
2805
  const el = wrapRef.current;
2787
- if (!el || expanded || typeof ResizeObserver === "undefined") return;
2806
+ if (!el || typeof ResizeObserver === "undefined") return;
2788
2807
  let raf = 0;
2789
2808
  const ro = new ResizeObserver(() => {
2790
2809
  cancelAnimationFrame(raf);
@@ -2795,7 +2814,7 @@ function ZoneChips({ zone, counts, labels, active, onPick, trailing, wrapTitle }
2795
2814
  ro.disconnect();
2796
2815
  cancelAnimationFrame(raf);
2797
2816
  };
2798
- }, [expanded, measure]);
2817
+ }, [measure]);
2799
2818
  useEffect(() => {
2800
2819
  if (expanded || typeof document === "undefined" || !document.fonts?.ready) return;
2801
2820
  let cancelled = false;
@@ -2818,6 +2837,8 @@ function ZoneChips({ zone, counts, labels, active, onPick, trailing, wrapTitle }
2818
2837
  const clipTop = geom ? geom.clipTop : Infinity;
2819
2838
  const hiddenCount = geom ? geom.hiddenCount : 0;
2820
2839
  const lastVisTop = geom ? geom.lastVisTop : 0;
2840
+ const moreLeft = geom ? geom.lastVisRight + 6 : 0;
2841
+ const moreHeight = geom ? geom.lastVisHeight : rowHeight || 22;
2821
2842
  const btn = (c, i) => {
2822
2843
  const top = geom ? geom.tops[i + 1] : null;
2823
2844
  const clipped = clip && geom && typeof top === "number" && top >= clipTop;
@@ -2858,16 +2879,18 @@ function ZoneChips({ zone, counts, labels, active, onPick, trailing, wrapTitle }
2858
2879
  ...chips.map((c, i) => btn(c, i)),
2859
2880
  // 折叠态 +N(0.9.57 终版):绝对定位钉在「末可见行右端预留槽」——出流不挤压 chip、不参与
2860
2881
  // 行分组(data-more);预留槽由 .dshm-chips-clip 的 padding-right 划出,几何保证零重叠;
2861
- // 位置右对齐紧贴筛选 gutter 左侧,垂直对齐末可见行(top = lastVisTop,测量派生)。
2882
+ // 左缘动态紧贴末可见 chip 右侧 6px,仍受右侧预留槽保护;垂直对齐末可见行。
2862
2883
  clip && geom && hiddenCount > 0 ? h("button", {
2863
2884
  key: "more",
2864
2885
  "data-more": "1",
2865
2886
  className: "dshm-chip dsvm-chipmore-plus",
2866
- style: { top: `${lastVisTop}px` },
2887
+ style: { top: `${lastVisTop}px`, left: `${moreLeft}px`, height: `${moreHeight}px` },
2867
2888
  onClick: () => setExpanded(true)
2868
2889
  }, `+${hiddenCount}`) : null,
2869
2890
  expanded ? [
2870
- h("button", {
2891
+ // 0.9.59:仅当展开布局真实超过 maxRows 行才渲染 ⌃——否则收起是零变化空操作(死按钮)。
2892
+ // 行数含 ⌃ 自身:若它恰好独占一行,隐藏后行数回落仍 ≤ maxRows,显隐收敛不振荡。
2893
+ expRows > maxRows ? h("button", {
2871
2894
  key: "collapse",
2872
2895
  "data-chip": "1",
2873
2896
  className: "dshm-chip",
@@ -2877,7 +2900,7 @@ function ZoneChips({ zone, counts, labels, active, onPick, trailing, wrapTitle }
2877
2900
  if (active) autoRef.current = active;
2878
2901
  setExpanded(false);
2879
2902
  }
2880
- }, "\u2303"),
2903
+ }, "\u2303") : null,
2881
2904
  trailing || null
2882
2905
  ] : null
2883
2906
  ),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-m",
3
- "version": "0.9.57",
3
+ "version": "0.9.59",
4
4
  "description": "可自定义收录清单的 DSH 插件市场:收录、安装、卸载、升级一站完成,附 agent 工具与 dshm CLI。",
5
5
  "license": "MIT",
6
6
  "type": "module",