dsh-m 0.9.72 → 0.9.74
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 +49 -4
- 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.74 功能:详情截图条滚轮横滚——裸滚轮 deltaY 安全映射 scrollLeft(钢人三红线)
|
|
12
|
+
|
|
13
|
+
- **背景(主人验收 0.9.73 实拍反馈)**:截图条是横滚容器,鼠标滚轮在横滚容器上默认纵滚祖先(弹窗)——图片多时纯鼠标用户只能拖 ~700px 细滚动条(离散目标上的连续输入,精度成本极高);触控板双指横滑(deltaX)与 Shift+滚轮虽有原生路径,但属隐藏知识不可达。
|
|
14
|
+
- **判定(新纯函数 `shotWheelAction`,src/client/wheel-strip.js,Node tests 直测)**:钢人三红线——①可滚才拦:scrollWidth ≤ clientWidth(+1 亚像素容差,0.9.72 同款口径) 零劫持,不满行的多数条目行为与今日一致,杜绝「静态劫持表达动态适用前提」的滚轮黑洞同构病复发;②到头放行:已到 deltaY 方向边界时零动作,纵滚还给弹窗(横向有得滚才借道、借完即还);③deltaX 不碰:|deltaX| ≥ |deltaY|(触控板横滑)不劫持不重复喂。非有限输入安全短路。
|
|
15
|
+
- **接线**:DetailModal 手动 `addEventListener("wheel", …, {passive:false})`(React onWheel 走 document 级 passive 委托,preventDefault 无效);deps=visible.length 随截图条存在性(条件渲染/primary 异步到达/坏图剔除)重挂;布局入参逐事件现读,无缓存失效问题。灯箱、走链、键盘导航零变化。
|
|
16
|
+
- **验证**:1339 项测试全绿(新增 client-wheel-strip 7 项:三红线逐条 + 容差边界 + 安全短路 + 接线源锚三件;img-chain ①锚放宽容 props 前缀)、typecheck 零错误。
|
|
17
|
+
|
|
18
|
+
### 0.9.73 修复:详情截图条统一瓷砖——「大小不一 + 预览图盖住左右邻图」(flex 收缩外溢根治)
|
|
19
|
+
|
|
20
|
+
- **根因(主人详情弹窗实拍:缩略图大小参差,图片压在左右邻图上)**:两处机制缺陷叠加——① `.dsvm-shotbox` 无固定几何(盒 min 120×84 / img max 220×130),跟着图片固有比例走,16:9、4:3、竖图、小原图各渲各的,「大的大小的小」;②盒子显式 `min-width:120px` 顶掉了 flex 的 automatic minimum size——8 张截图自然总宽(~1.7k px)远超弹窗内容宽(~688px)触发 flex 收缩时,盒子被压到 120px 地板而盒内 `<img>`(替换元素)不受盒宽约束保持固有宽度,加上盒子 `overflow:visible`,图片居中外溢每侧盖住邻图 ~44px——且发生在滚动条出现之前,两症状同屏;加载完成瞬间行高还随最高图 84→130px 跳变(CLS)。
|
|
21
|
+
- **修复(统一瓷砖)**:盒子 `flex:0 0 auto`(几何与图片数量/加载态无关,永不收缩)+ 固定 176×110 + `overflow:hidden`(外溢兜底双保险);img 改 `width/height:100%` + `object-fit:cover` + `object-position:top center`(顶锚定保住截图标题栏,裁切只发生在底部,竖图/方图露上半段,全图看灯箱);行加 `scroll-snap-type:x proximity` + 盒 `scroll-snap-align:start`(横滚翻页手感)。灯箱、走链、坏图剔除、键盘导航语义零变化。
|
|
22
|
+
- **验证**:1332 项测试全绿(新增截图条瓷砖 CSS 源锚 2 用例:统一瓷砖形态 + 旧病灶负向锚)、typecheck 零错误;构建产物含新规则逐字锚。方案对比 mock(现状复刻 + cover/contain/网格三案实况)先行经主人选型(A 案 cover)。
|
|
23
|
+
|
|
11
24
|
### 0.9.72 修复:设置页滚轮黑洞——操作记录滚动区/横幅的 contain 改为「可滚才挂」(Chrome 144 规范对齐暴露)
|
|
12
25
|
|
|
13
26
|
- **根因(主人设置页实拍:操作记录条数少、滚动条尚未出现时,鼠标悬停该区域滚轮无法翻页)**:`.dshm-ops-scroll` 恒挂 `overscroll-behavior:contain`(0.9.64 随 220px 滚动区引入)。`overflow-y:auto` 使元素无论内容多少都是滚动容器,而规范口径「没有可滚溢出的滚动容器恒在滚动边界」+ contain「到边界禁止向祖先链滚」→ 内容不满时整块区域吞掉滚轮。Chrome 144 起「Respect overscroll-behavior on non-scrollable scroll containers」落地(此前引擎仅真可滚才执行 contain,病灶被宽容掩盖),潜伏缺陷被引擎升级激活;错误/提示横幅 `.dshm-banner-text`(限高滚动时代引入的同款恒挂)同病。
|
|
@@ -532,6 +545,19 @@ The full release history of dsh-m, maintained bilingually: **Chinese first, Engl
|
|
|
532
545
|
|
|
533
546
|
## English
|
|
534
547
|
|
|
548
|
+
### Feature in 0.9.74 — screenshot-strip wheel hscroll: bare-wheel deltaY safely mapped to scrollLeft (steelman's three red lines)
|
|
549
|
+
|
|
550
|
+
- **Background (owner feedback while verifying 0.9.73)**: the strip is a horizontal scroll container, and a mouse wheel over it scrolls the ancestor (the modal) by default — with many screenshots, mouse-only users must drag a ~700px thin scrollbar (continuous input on a discrete target); trackpad two-finger hscroll (deltaX) and Shift+wheel exist natively but are hidden knowledge.
|
|
551
|
+
- **Decision (new pure predicate `shotWheelAction`, src/client/wheel-strip.js, Node-tested)**: steelman's three red lines — ① intercept only when scrollable: zero hijack while scrollWidth ≤ clientWidth (+1 sub-pixel tolerance, same rule as 0.9.72's useScrollableContain), so most entries (1-2 images) behave exactly as today — no relapse of the "static hijack expressing a dynamic precondition" wheel black-hole disease; ② release at the edge: zero action once the delta direction has no horizontal room left, handing vertical scroll back to the modal (borrow the scroll only while there is room); ③ never touch deltaX: |deltaX| ≥ |deltaY| (trackpad hscroll) passes through untouched. Non-finite inputs short-circuit safely.
|
|
552
|
+
- **Wiring**: DetailModal attaches a manual `addEventListener("wheel", …, {passive:false})` (React's onWheel rides the document-level passive delegation where preventDefault is a no-op); deps=visible.length re-attach as the strip's existence changes (conditional render / async primary shots / broken-shot eviction); layout inputs are read per event — nothing cached. Lightbox, image chain, and keyboard navigation unchanged.
|
|
553
|
+
- **Verification**: 1339 tests green (new client-wheel-strip, 7 cases: three red lines + tolerance edges + safe short-circuit + wiring anchors; the img-chain ① anchor widened to allow props prefixes), typecheck clean.
|
|
554
|
+
|
|
555
|
+
### Fix in 0.9.73 — detail screenshot strip unified tiles: inconsistent sizes and previews overlapping neighboring thumbnails (flex-shrink overflow fixed at the root)
|
|
556
|
+
|
|
557
|
+
- **Root cause (owner screenshot: jagged thumbnails with images spilling over their left/right neighbors)**: two defects stacked — ① `.dsvm-shotbox` had no fixed geometry (box min 120×84 / img max 220×130) and followed each image's intrinsic aspect ratio, so 16:9, 4:3, portrait and small originals all rendered at their own size; ② the explicit `min-width:120px` overrode the flex automatic minimum size — with eight screenshots' natural total width (~1.7k px) far exceeding the modal content width (~688px), flex-shrink squeezed each box to its 120px floor while the inner `<img>` (a replaced element) kept its intrinsic width, and with `overflow:visible` on the box each image spilled out centered, covering ~44px of each neighbor — all before the scrollbar even appeared; the row height also jumped 84→130px when the tallest image loaded (CLS).
|
|
558
|
+
- **Fix (unified tiles)**: boxes get `flex:0 0 auto` (geometry independent of image count/load state, never shrinks) + fixed 176×110 + `overflow:hidden` (belt-and-braces against spill); images switch to `width/height:100%` + `object-fit:cover` + `object-position:top center` (top-anchored so screenshot title bars survive; cropping only trims the bottom — portrait/square shots show their upper half, the full image lives in the lightbox); the row gains `scroll-snap-type:x proximity` and boxes `scroll-snap-align:start`. Lightbox, image chain, broken-shot eviction, and keyboard navigation semantics unchanged.
|
|
559
|
+
- **Verification**: 1332 tests green (new screenshot-strip tile CSS source anchors, 2 cases: unified-tile shape + negative anchors on the retired defect forms), typecheck clean; the built bundle carries the new rules verbatim. A standalone comparison mock (current-bug reproduction + cover/contain/grid variants) was reviewed by the owner beforehand (variant A: cover chosen).
|
|
560
|
+
|
|
535
561
|
### Fix in 0.9.72 — settings-page wheel black hole: ops scroll region and banner now apply contain only when actually scrollable (exposed by Chrome 144 spec alignment)
|
|
536
562
|
|
|
537
563
|
- **Root cause (owner screenshot: with few operation records and no scrollbar yet, wheeling over the scroll region does nothing)**: `.dshm-ops-scroll` carried a permanent `overscroll-behavior:contain` (introduced with the 220px region in 0.9.64). `overflow-y:auto` makes the element a scroll container no matter how little content it has, and per spec a scroll container without scrollable overflow is always at its scroll boundary — with contain forbidding chaining, the whole area swallows the wheel. Chrome 144's "Respect overscroll-behavior on non-scrollable scroll containers" made engines honor this unconditionally (previously contain only applied when actually scrollable), activating the latent defect; the banner text (`.dshm-banner-text`, same permanent contain from the capped-banner era) shares the disease.
|
package/lib/client.js
CHANGED
|
@@ -494,6 +494,28 @@ var init_lightbox = __esm({
|
|
|
494
494
|
}
|
|
495
495
|
});
|
|
496
496
|
|
|
497
|
+
// src/client/wheel-strip.js
|
|
498
|
+
var wheel_strip_exports = {};
|
|
499
|
+
__export(wheel_strip_exports, {
|
|
500
|
+
shotWheelAction: () => shotWheelAction
|
|
501
|
+
});
|
|
502
|
+
function shotWheelAction({ deltaX, deltaY, scrollWidth, clientWidth, scrollLeft }) {
|
|
503
|
+
const inputs = [deltaX, deltaY, scrollWidth, clientWidth, scrollLeft];
|
|
504
|
+
if (!inputs.every(Number.isFinite)) return { scrollBy: 0, preventDefault: false };
|
|
505
|
+
if (scrollWidth <= clientWidth + 1) return { scrollBy: 0, preventDefault: false };
|
|
506
|
+
if (deltaY === 0 || Math.abs(deltaX) >= Math.abs(deltaY)) return { scrollBy: 0, preventDefault: false };
|
|
507
|
+
const maxScrollLeft = scrollWidth - clientWidth;
|
|
508
|
+
const atEnd = scrollLeft >= maxScrollLeft - 1;
|
|
509
|
+
const atStart = scrollLeft <= 0;
|
|
510
|
+
if (deltaY > 0 && atEnd || deltaY < 0 && atStart) return { scrollBy: 0, preventDefault: false };
|
|
511
|
+
return { scrollBy: deltaY, preventDefault: true };
|
|
512
|
+
}
|
|
513
|
+
var init_wheel_strip = __esm({
|
|
514
|
+
"src/client/wheel-strip.js"() {
|
|
515
|
+
"use strict";
|
|
516
|
+
}
|
|
517
|
+
});
|
|
518
|
+
|
|
497
519
|
// src/client/window-drag.js
|
|
498
520
|
var window_drag_exports = {};
|
|
499
521
|
__export(window_drag_exports, {
|
|
@@ -1758,6 +1780,7 @@ var { DEFAULT_PAGE_SIZE: DEFAULT_PAGE_SIZE2, MARKET_PAGE_SIZES: MARKET_PAGE_SIZE
|
|
|
1758
1780
|
var { readMarketSnapshot: readMarketSnapshot2, writeMarketSnapshot: writeMarketSnapshot2, isDefaultFirstPageQuery: isDefaultFirstPageQuery2 } = (init_market_snapshot(), __toCommonJS(market_snapshot_exports));
|
|
1759
1781
|
var { backdropCloseHandlers: backdropCloseHandlers2 } = (init_backdrop(), __toCommonJS(backdrop_exports));
|
|
1760
1782
|
var { lbStep: lbStep2, lbNeighbors: lbNeighbors2, swipeDir: swipeDir2 } = (init_lightbox(), __toCommonJS(lightbox_exports));
|
|
1783
|
+
var { shotWheelAction: shotWheelAction2 } = (init_wheel_strip(), __toCommonJS(wheel_strip_exports));
|
|
1761
1784
|
var { dragSlop: dragSlop2, grabOffset: grabOffset2, titlebarTopInset: titlebarTopInset2, clampPoint: clampPoint2, loadPanelPos: loadPanelPos2, savePanelPos: savePanelPos2, isDragTarget: isDragTarget2 } = (init_window_drag(), __toCommonJS(window_drag_exports));
|
|
1762
1785
|
var { WESERV_TIMEOUT_MS: WESERV_TIMEOUT_MS2, weservUrl: weservUrl2, serviceBucketOf: serviceBucketOf2, tierOrder: tierOrder2, nextTier: nextTier2, needsTimeout: needsTimeout2, preferredTier: preferredTier2, rememberSuccess: rememberSuccess2 } = (init_img_chain(), __toCommonJS(img_chain_exports));
|
|
1763
1786
|
var { createMarkdown: createMarkdown2 } = (init_markdown(), __toCommonJS(markdown_exports));
|
|
@@ -2492,9 +2515,18 @@ var CSS = `
|
|
|
2492
2515
|
.dsvm-cmdrow{display:flex;align-items:center;gap:8px;margin-top:6px;flex-wrap:wrap}
|
|
2493
2516
|
.dsvm-code{background:rgba(127,127,127,.12);padding:3px 8px;border-radius:6px;font-size:11px;overflow-wrap:anywhere}
|
|
2494
2517
|
.dsvm-modalactions{display:flex;justify-content:flex-end;gap:8px}
|
|
2495
|
-
.
|
|
2496
|
-
|
|
2497
|
-
|
|
2518
|
+
/* 0.9.73 \u8BE6\u60C5\u622A\u56FE\u6761\u7EDF\u4E00\u74F7\u7816\uFF1A\u4E24\u75C5\u7076\u2014\u2014\u2460\u76D2\u5B50\u65E0\u56FA\u5B9A\u51E0\u4F55\uFF08min 120\xD784 \u76D2 / img max 220\xD7130\uFF09\u8DDF\u7740
|
|
2519
|
+
\u56FE\u7247\u56FA\u6709\u6BD4\u4F8B\u8D70\uFF0C\u6574\u6761\u53C2\u5DEE\uFF08\u300C\u5927\u7684\u5927\u5C0F\u7684\u5C0F\u300D\uFF09\uFF1B\u2461shotbox \u663E\u5F0F min-width:120px \u9876\u6389 flex \u7684
|
|
2520
|
+
automatic minimum size\uFF0CN \u5F20\u56FE\u81EA\u7136\u603B\u5BBD\u8D85\u884C\uFF088\xD7220px \u226B \u5F39\u7A97\u5185\u5BB9\u5BBD ~688px\uFF09\u89E6\u53D1 flex \u6536\u7F29\u65F6\uFF0C
|
|
2521
|
+
\u76D2\u5B50\u88AB\u538B\u5230 120px \u5730\u677F\u800C\u76D2\u5185 img\uFF08\u66FF\u6362\u5143\u7D20\uFF09\u4E0D\u53D7\u76D2\u5BBD\u7EA6\u675F\u4FDD\u6301\u56FA\u6709\u5BBD\u5EA6\u3001\u76D2\u5B50 overflow:visible
|
|
2522
|
+
\u2014\u2014\u56FE\u7247\u5C45\u4E2D\u5916\u6EA2\u76D6\u4F4F\u5DE6\u53F3\u90BB\u56FE\uFF08\u53D1\u751F\u5728\u6EDA\u52A8\u6761\u51FA\u73B0\u4E4B\u524D\uFF0C\u4E24\u75C7\u72B6\u540C\u5C4F\uFF09\uFF1B\u52A0\u8F7D\u5B8C\u6210\u77AC\u95F4\u884C\u9AD8\u8FD8\u88AB
|
|
2523
|
+
\u6491\u9AD8\u4E00\u6B21\uFF08\u884C\u9AD8\u968F\u6700\u9AD8\u56FE 84\u2192130px \u8DF3\u53D8\uFF0CCLS\uFF09\u3002\u4FEE\u590D = \u7EDF\u4E00\u74F7\u7816 176\xD7110 + flex:0 0 auto
|
|
2524
|
+
\uFF08\u6C38\u4E0D\u6536\u7F29\uFF0C\u51E0\u4F55\u4E0E\u56FE\u7247\u591A\u5C11/\u52A0\u8F7D\u6001\u65E0\u5173\uFF09+ overflow:hidden\uFF08\u5916\u6EA2\u515C\u5E95\u53CC\u4FDD\u9669\uFF09\uFF0Cimg \u6539
|
|
2525
|
+
width/height:100% + object-fit:cover + object-position:top\uFF08\u9876\u951A\u5B9A\u4FDD\u4F4F\u622A\u56FE\u6807\u9898\u680F\uFF0C\u88C1\u5207\u53EA
|
|
2526
|
+
\u53D1\u751F\u5728\u5E95\u90E8\uFF0C\u7AD6\u56FE/\u65B9\u56FE\u9732\u4E0A\u534A\u6BB5\uFF0C\u5168\u56FE\u770B\u706F\u7BB1\uFF09\uFF1Bscroll-snap proximity \u7ED9\u6A2A\u6EDA\u7FFB\u9875\u624B\u611F\u3002 */
|
|
2527
|
+
.dsvm-shotrow{display:flex;gap:6px;overflow-x:auto;padding:2px 0;scroll-snap-type:x proximity}
|
|
2528
|
+
.dsvm-shotbox{flex:0 0 auto;width:176px;height:110px;overflow:hidden;display:flex;align-items:center;justify-content:center;background:rgba(127,127,127,.08);border-radius:6px;cursor:zoom-in;border:1px solid var(--dsw-alias-border-l2,rgba(127,127,127,.25));scroll-snap-align:start}
|
|
2529
|
+
.dsvm-shot{width:100%;height:100%;object-fit:cover;object-position:top center;border-radius:6px;display:block}
|
|
2498
2530
|
/* \u706F\u7BB1 v2\uFF080.9.60\uFF09\uFF1A\u7EC4\u4EF6 createPortal \u6302 body\uFF08\u9003\u51FA\u9762\u677F backdrop-filter \u5305\u542B\u5757\uFF09\uFF0C
|
|
2499
2531
|
\u63A7\u4EF6\u5168\u90E8\u7EDD\u5BF9\u5B9A\u4F4D\u8D34\u89C6\u53E3\u8FB9\u2014\u2014\u2715 \u5E38\u9A7B\u53F3\u4E0A\u3001\u2039\u203A \u4E24\u4FA7\u5C45\u4E2D\u3001\u5E95\u90E8\u8BA1\u6570+\u5706\u70B9 pill\uFF0C
|
|
2500
2532
|
\u4EFB\u4F55\u9762\u677F\u72B6\u6001/\u4EFB\u610F\u56FE\u7247\u9AD8\u5EA6\u4E0B\u90FD\u4E0D\u4F1A\u88AB\u6324\u51FA\u5C4F\u5E55\uFF1B\u56FE\u7247\u964D\u5230 74vh \u7ED9\u5E95\u90E8\u63A7\u4EF6\u7559\u4F4D\u3002
|
|
@@ -3448,6 +3480,19 @@ function DetailModal({ it, labels, busy, onClose, onInstall, onUpgrade, upgradeB
|
|
|
3448
3480
|
useEffect(() => {
|
|
3449
3481
|
if (closeRef.current) closeRef.current.focus();
|
|
3450
3482
|
}, []);
|
|
3483
|
+
const shotRowRef = useRef(null);
|
|
3484
|
+
useEffect(() => {
|
|
3485
|
+
const row = shotRowRef.current;
|
|
3486
|
+
if (!row || typeof row.addEventListener !== "function") return;
|
|
3487
|
+
const onWheel = (e) => {
|
|
3488
|
+
const action = shotWheelAction2({ deltaX: e.deltaX, deltaY: e.deltaY, scrollWidth: row.scrollWidth, clientWidth: row.clientWidth, scrollLeft: row.scrollLeft });
|
|
3489
|
+
if (!action.preventDefault) return;
|
|
3490
|
+
e.preventDefault();
|
|
3491
|
+
row.scrollLeft += action.scrollBy;
|
|
3492
|
+
};
|
|
3493
|
+
row.addEventListener("wheel", onWheel, { passive: false });
|
|
3494
|
+
return () => row.removeEventListener("wheel", onWheel);
|
|
3495
|
+
}, [visible.length]);
|
|
3451
3496
|
useEffect(() => {
|
|
3452
3497
|
const onKey = (e) => {
|
|
3453
3498
|
if (e.key === "Escape") {
|
|
@@ -3545,7 +3590,7 @@ function DetailModal({ it, labels, busy, onClose, onInstall, onUpgrade, upgradeB
|
|
|
3545
3590
|
) : null,
|
|
3546
3591
|
visible.length ? h(
|
|
3547
3592
|
"div",
|
|
3548
|
-
{ className: "dsvm-shotrow" },
|
|
3593
|
+
{ ref: shotRowRef, className: "dsvm-shotrow" },
|
|
3549
3594
|
...shots.map((src, i) => h(Shot, { key: `${i}:${src}`, src, onClick: () => setLb(visible.indexOf(src)), onBroken: breakShot }))
|
|
3550
3595
|
) : null,
|
|
3551
3596
|
it.community === true ? h(
|