@dsh-plugin/dsh-ui-experience 1.0.0-dev.43491412024 → 1.0.0-dev.43492422772
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/README.md +17 -1
- package/lib/client.js +261 -7
- package/lib/index.js +12 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -141,6 +141,20 @@ M3 那侧的分离要说明白:Material 3 的 `emphasized` 整体是两段首
|
|
|
141
141
|
- 无需随 DSH 更新维护:新增或改名的组件自动被覆盖。
|
|
142
142
|
- 精确还原:写入前整块记录 `cssText`,关闭时原样写回;若期间被第三方改过则在日志中说明后仍然还原。
|
|
143
143
|
|
|
144
|
+
### 滚动
|
|
145
|
+
|
|
146
|
+
`scroll-behavior: smooth` 与脚本调用的 `scrollTo({behavior:'smooth'})` 都不受 CSS 曲线控制——时长和曲线由浏览器定,CSS 读不到,JS 也拿不到。勾选「滚动使用同一曲线」后,本层接管 `Element.prototype` 的 `scrollTo` / `scrollBy` / `scrollIntoView`,用脚本滚动实现同一套曲线。
|
|
147
|
+
|
|
148
|
+
三条固定的规则,且不可配置:
|
|
149
|
+
|
|
150
|
+
1. **读者一动就交还原生。** 滚轮、触摸、滚动按键直接取消脚本动画,不混合、不恢复。任何本层没写过的位置变化(拖滚动条、辅助技术滚动、容器在动画中重排)也会在下一帧取消。`pointerdown` 不在此列:页面上任何一次点击都取消,会让「输出还在滚动时点一下按钮」把跟随掐断,而原生平滑滚动不会。
|
|
151
|
+
2. **用效果曲线,不用位移曲线。** 位移曲线带过冲,滚动时过冲意味着内容冲过目标位置再退回来,终点错两次。曲线族照样生效,带过冲的那半永远不用于滚动。
|
|
152
|
+
3. **时长按距离增长。** 240px 以内固定 180ms,之后每像素加 0.2ms,封顶 460ms;再乘时长倍率,开了「时长对齐」时同样吸附到 Material 档位。滚动的手感预期和元素过渡不同:读者对「滚了多远、停在哪」有精确期待,长距离用固定时长会显得急促。
|
|
153
|
+
|
|
154
|
+
`scrollIntoView` 的终点由对齐方式、`scroll-margin`、嵌套容器和元素自身盒模型共同决定,自己算就是「滚到错的那一行」的来源。这里的做法是:先向浏览器要一次瞬时跳转,读出每个被移动容器的落点,把它们全部放回原位,再动画到读到的落点。跳转与还原在同一个任务内完成,中间位置不会绘制。
|
|
155
|
+
|
|
156
|
+
`scrollend` 交给浏览器:逐帧赋值 `scrollTop` 是一串瞬时滚动,浏览器自己的滚动结束检测会在最后一帧落下后触发一次,依赖它结算状态的消费方(聊天记录的跟随控制器)看到的序列与原生平滑滚动一致。
|
|
157
|
+
|
|
144
158
|
### 滚动驱动动画不动
|
|
145
159
|
|
|
146
160
|
`scroll-timeline` / `animation-timeline` 驱动的动画,进度来自滚动容器而不是时钟。这类动画的 `animation-timing-function` 不是「一条随时间推进的曲线」,而是把滚动位置映射到迭代进度——给它套缓动,等于让遮罩在滚动前段就淡完然后定住,改的是效果本身而非手感。因此只要某条规则的 `animation-timeline` 不是 `auto` / `none`,本层就整条跳过。
|
|
@@ -190,7 +204,9 @@ DSH 目前只有一处用到(提问卡片标题的溢出遮罩 `--question-cop
|
|
|
190
204
|
- M3 动效的「位移 / 效果」判定依据属性名与关键帧内容,不分析属性的实际取值变化;判定不出来的情况(例如只由 `all` 声明的过渡)按效果处理,宁可不给过冲。
|
|
191
205
|
- 跨源样式表里的规则无法改写,只会在日志中记录。
|
|
192
206
|
- 本层只改写曲线与时长,不新增、不改写关键帧本身;DSH 没定义的动效不会因此出现。
|
|
193
|
-
-
|
|
207
|
+
- CSS 的 `scroll-behavior: smooth`(全仓一处,dock 标签条)不是 transition/animation,本层不碰;只有减弱动效时随压制表置为 `auto`。
|
|
208
|
+
- 脚本滚动由「滚动使用同一曲线」单独控制,与曲线族无关(关掉曲线族就一并停止接管)。
|
|
209
|
+
- 滚动驱动动画(`animation-timeline`)不属于时间轴动画,本层显式跳过,见上一节。
|
|
194
210
|
- 浏览器半在刷新页面时重载,宿主半只在进程重启时重载。两者版本不一致时(刚升级插件但没重启 DSH),M3 动效行会保持无操作并在控制台记录一行原因,配色功能不受影响。
|
|
195
211
|
|
|
196
212
|
## 样式基线
|
package/lib/client.js
CHANGED
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
durationScale: 1,
|
|
12
12
|
spatialSplit: true,
|
|
13
13
|
quantize: false,
|
|
14
|
-
reduced: "system"
|
|
14
|
+
reduced: "system",
|
|
15
|
+
scroll: true
|
|
15
16
|
};
|
|
16
17
|
|
|
17
18
|
// src/motion-plan.ts
|
|
@@ -77,11 +78,36 @@
|
|
|
77
78
|
"border-bottom-left-radius",
|
|
78
79
|
"border-bottom-right-radius"
|
|
79
80
|
]);
|
|
80
|
-
var
|
|
81
|
-
var
|
|
82
|
-
var
|
|
83
|
-
var
|
|
84
|
-
var
|
|
81
|
+
var POINTS_M3_SPATIAL = [0.05, 0.7, 0.1, 1];
|
|
82
|
+
var POINTS_M3_EFFECTS = [0.2, 0, 0, 1];
|
|
83
|
+
var POINTS_M3E_SPATIAL_FAST = [0.42, 1.67, 0.21, 0.9];
|
|
84
|
+
var POINTS_M3E_SPATIAL = [0.38, 1.21, 0.22, 1];
|
|
85
|
+
var POINTS_M3E_EFFECTS = [0.34, 0.8, 0.34, 1];
|
|
86
|
+
var CURVE_M3_SPATIAL = `cubic-bezier(${POINTS_M3_SPATIAL.join(", ")})`;
|
|
87
|
+
var CURVE_M3_EFFECTS = `cubic-bezier(${POINTS_M3_EFFECTS.join(", ")})`;
|
|
88
|
+
var CURVE_M3E_SPATIAL_FAST = `cubic-bezier(${POINTS_M3E_SPATIAL_FAST.join(", ")})`;
|
|
89
|
+
var CURVE_M3E_SPATIAL = `cubic-bezier(${POINTS_M3E_SPATIAL.join(", ")})`;
|
|
90
|
+
var CURVE_M3E_EFFECTS = `cubic-bezier(${POINTS_M3E_EFFECTS.join(", ")})`;
|
|
91
|
+
function effectCurvePoints(mode) {
|
|
92
|
+
return mode === "m3" ? POINTS_M3_EFFECTS : POINTS_M3E_EFFECTS;
|
|
93
|
+
}
|
|
94
|
+
function easeCurve(points, x) {
|
|
95
|
+
const [x1, y1, x2, y2] = points;
|
|
96
|
+
const clamped = Math.min(1, Math.max(0, x));
|
|
97
|
+
let low = 0;
|
|
98
|
+
let high = 1;
|
|
99
|
+
let t = clamped;
|
|
100
|
+
for (let step = 0; step < 24; step += 1) {
|
|
101
|
+
const inverse2 = 1 - t;
|
|
102
|
+
const value = 3 * inverse2 * inverse2 * t * x1 + 3 * inverse2 * t * t * x2 + t * t * t;
|
|
103
|
+
if (Math.abs(value - clamped) < 1e-5) break;
|
|
104
|
+
if (value < clamped) low = t;
|
|
105
|
+
else high = t;
|
|
106
|
+
t = (low + high) / 2;
|
|
107
|
+
}
|
|
108
|
+
const inverse = 1 - t;
|
|
109
|
+
return 3 * inverse * inverse * t * y1 + 3 * inverse * t * t * y2 + t * t * t;
|
|
110
|
+
}
|
|
85
111
|
var FAST_SPATIAL_MAX_MS = 200;
|
|
86
112
|
var MATERIAL_DURATIONS_MS = [
|
|
87
113
|
50,
|
|
@@ -441,6 +467,218 @@
|
|
|
441
467
|
};
|
|
442
468
|
}
|
|
443
469
|
|
|
470
|
+
// src/scroll.ts
|
|
471
|
+
var MIN_MS = 180;
|
|
472
|
+
var MAX_MS = 460;
|
|
473
|
+
var FREE_PX = 240;
|
|
474
|
+
var GROWTH_MS_PER_PX = 0.2;
|
|
475
|
+
var SCROLL_KEYS = /* @__PURE__ */ new Set([
|
|
476
|
+
"ArrowUp",
|
|
477
|
+
"ArrowDown",
|
|
478
|
+
"ArrowLeft",
|
|
479
|
+
"ArrowRight",
|
|
480
|
+
"PageUp",
|
|
481
|
+
"PageDown",
|
|
482
|
+
"Home",
|
|
483
|
+
"End",
|
|
484
|
+
" "
|
|
485
|
+
]);
|
|
486
|
+
var TAKEOVER_EVENTS = ["wheel", "touchstart", "keydown"];
|
|
487
|
+
function scrollDistance(legs) {
|
|
488
|
+
let distance = 0;
|
|
489
|
+
for (const leg of legs) {
|
|
490
|
+
distance = Math.max(distance, Math.abs(leg.toTop - leg.fromTop), Math.abs(leg.toLeft - leg.fromLeft));
|
|
491
|
+
}
|
|
492
|
+
return distance;
|
|
493
|
+
}
|
|
494
|
+
function scrollDurationMs(distance, settings) {
|
|
495
|
+
const grown = MIN_MS + Math.max(0, distance - FREE_PX) * GROWTH_MS_PER_PX;
|
|
496
|
+
const scaled = Math.min(MAX_MS, grown) * settings.durationScale;
|
|
497
|
+
return settings.quantize ? quantizeMs(scaled) : scaled;
|
|
498
|
+
}
|
|
499
|
+
function maxima(element) {
|
|
500
|
+
return {
|
|
501
|
+
top: Math.max(0, element.scrollHeight - element.clientHeight),
|
|
502
|
+
left: Math.max(0, element.scrollWidth - element.clientWidth)
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
function scrollable(element) {
|
|
506
|
+
const max = maxima(element);
|
|
507
|
+
return max.top > 0 || max.left > 0;
|
|
508
|
+
}
|
|
509
|
+
function containers(element) {
|
|
510
|
+
const out = [];
|
|
511
|
+
let node = element.parentElement;
|
|
512
|
+
while (node !== null) {
|
|
513
|
+
if (scrollable(node)) out.push(node);
|
|
514
|
+
node = node.parentElement;
|
|
515
|
+
}
|
|
516
|
+
return out;
|
|
517
|
+
}
|
|
518
|
+
function createScrollMotion() {
|
|
519
|
+
const shipped = {
|
|
520
|
+
scrollTo: Element.prototype.scrollTo,
|
|
521
|
+
scrollBy: Element.prototype.scrollBy,
|
|
522
|
+
scrollIntoView: Element.prototype.scrollIntoView
|
|
523
|
+
};
|
|
524
|
+
let settings;
|
|
525
|
+
let run;
|
|
526
|
+
let installed = false;
|
|
527
|
+
function cancel() {
|
|
528
|
+
if (run === void 0) return;
|
|
529
|
+
window.cancelAnimationFrame(run.frame);
|
|
530
|
+
run = void 0;
|
|
531
|
+
}
|
|
532
|
+
function step(now) {
|
|
533
|
+
const active = run;
|
|
534
|
+
if (active === void 0) return;
|
|
535
|
+
const elapsed = now - active.started;
|
|
536
|
+
const progress = active.duration <= 0 ? 1 : Math.min(1, elapsed / active.duration);
|
|
537
|
+
const eased = easeCurve(effectCurvePoints(settings?.mode ?? "m3e"), progress);
|
|
538
|
+
for (let index = 0; index < active.legs.length; index += 1) {
|
|
539
|
+
const leg = active.legs[index];
|
|
540
|
+
const written = active.written[index];
|
|
541
|
+
if (leg === void 0 || written === void 0) continue;
|
|
542
|
+
if (Math.abs(leg.element.scrollTop - written.top) > 1 || Math.abs(leg.element.scrollLeft - written.left) > 1) {
|
|
543
|
+
cancel();
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
const top = progress >= 1 ? leg.toTop : leg.fromTop + (leg.toTop - leg.fromTop) * eased;
|
|
547
|
+
const left = progress >= 1 ? leg.toLeft : leg.fromLeft + (leg.toLeft - leg.fromLeft) * eased;
|
|
548
|
+
if (leg.element.scrollTop !== top) leg.element.scrollTop = top;
|
|
549
|
+
if (leg.element.scrollLeft !== left) leg.element.scrollLeft = left;
|
|
550
|
+
written.top = leg.element.scrollTop;
|
|
551
|
+
written.left = leg.element.scrollLeft;
|
|
552
|
+
}
|
|
553
|
+
if (progress >= 1) {
|
|
554
|
+
run = void 0;
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
active.frame = window.requestAnimationFrame(step);
|
|
558
|
+
}
|
|
559
|
+
function start(legs) {
|
|
560
|
+
cancel();
|
|
561
|
+
const active = settings;
|
|
562
|
+
if (active === void 0) return;
|
|
563
|
+
run = {
|
|
564
|
+
legs,
|
|
565
|
+
started: performance.now(),
|
|
566
|
+
duration: scrollDurationMs(scrollDistance(legs), active),
|
|
567
|
+
written: legs.map((leg) => ({ top: leg.element.scrollTop, left: leg.element.scrollLeft })),
|
|
568
|
+
frame: window.requestAnimationFrame(step)
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
function defer(behavior) {
|
|
572
|
+
if (settings === void 0) return true;
|
|
573
|
+
if (behavior !== "smooth") return true;
|
|
574
|
+
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
575
|
+
}
|
|
576
|
+
function legsFor(element, top, left) {
|
|
577
|
+
const max = maxima(element);
|
|
578
|
+
const toTop = top === void 0 ? element.scrollTop : Math.min(max.top, Math.max(0, top));
|
|
579
|
+
const toLeft = left === void 0 ? element.scrollLeft : Math.min(max.left, Math.max(0, left));
|
|
580
|
+
if (Math.abs(toTop - element.scrollTop) < 1 && Math.abs(toLeft - element.scrollLeft) < 1) return void 0;
|
|
581
|
+
return [{ element, fromTop: element.scrollTop, fromLeft: element.scrollLeft, toTop, toLeft }];
|
|
582
|
+
}
|
|
583
|
+
function readOffsets(options, y, base) {
|
|
584
|
+
if (typeof options === "number") {
|
|
585
|
+
return { left: base.left + options, top: y === void 0 ? void 0 : base.top + y };
|
|
586
|
+
}
|
|
587
|
+
if (options === void 0 || options === null) return { top: void 0, left: void 0 };
|
|
588
|
+
return {
|
|
589
|
+
left: options.left === void 0 ? void 0 : base.left + options.left,
|
|
590
|
+
top: options.top === void 0 ? void 0 : base.top + options.top
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
function patchedScrollTo(options, y) {
|
|
594
|
+
const behavior = typeof options === "object" && options !== null ? options.behavior : void 0;
|
|
595
|
+
if (defer(behavior)) {
|
|
596
|
+
;
|
|
597
|
+
shipped.scrollTo.call(this, options, y);
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
const request = readOffsets(options, y, { top: 0, left: 0 });
|
|
601
|
+
const legs = legsFor(this, request.top, request.left);
|
|
602
|
+
if (legs !== void 0) start(legs);
|
|
603
|
+
}
|
|
604
|
+
function patchedScrollBy(options, y) {
|
|
605
|
+
const behavior = typeof options === "object" && options !== null ? options.behavior : void 0;
|
|
606
|
+
if (defer(behavior)) {
|
|
607
|
+
;
|
|
608
|
+
shipped.scrollBy.call(this, options, y);
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
const request = readOffsets(options, y, { top: this.scrollTop, left: this.scrollLeft });
|
|
612
|
+
const legs = legsFor(this, request.top, request.left);
|
|
613
|
+
if (legs !== void 0) start(legs);
|
|
614
|
+
}
|
|
615
|
+
function patchedScrollIntoView(options) {
|
|
616
|
+
const behavior = typeof options === "object" && options !== null ? options.behavior : void 0;
|
|
617
|
+
if (defer(behavior)) {
|
|
618
|
+
;
|
|
619
|
+
shipped.scrollIntoView.call(this, options);
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
const instant = { ...typeof options === "object" && options !== null ? options : {}, behavior: "instant" };
|
|
623
|
+
const tracked = containers(this);
|
|
624
|
+
const before = tracked.map((element) => ({ top: element.scrollTop, left: element.scrollLeft }));
|
|
625
|
+
shipped.scrollIntoView.call(this, instant);
|
|
626
|
+
const legs = [];
|
|
627
|
+
for (let index = 0; index < tracked.length; index += 1) {
|
|
628
|
+
const element = tracked[index];
|
|
629
|
+
const origin = before[index];
|
|
630
|
+
if (element === void 0 || origin === void 0) continue;
|
|
631
|
+
const landed = { top: element.scrollTop, left: element.scrollLeft };
|
|
632
|
+
element.scrollTop = origin.top;
|
|
633
|
+
element.scrollLeft = origin.left;
|
|
634
|
+
if (Math.abs(landed.top - origin.top) < 1 && Math.abs(landed.left - origin.left) < 1) continue;
|
|
635
|
+
legs.push({ element, fromTop: origin.top, fromLeft: origin.left, toTop: landed.top, toLeft: landed.left });
|
|
636
|
+
}
|
|
637
|
+
if (legs.length > 0) start(legs);
|
|
638
|
+
}
|
|
639
|
+
function onTakeover(event) {
|
|
640
|
+
if (run === void 0) return;
|
|
641
|
+
if (event.type !== "keydown") {
|
|
642
|
+
cancel();
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
if (event.key !== void 0 && SCROLL_KEYS.has(event.key)) cancel();
|
|
646
|
+
}
|
|
647
|
+
function install() {
|
|
648
|
+
if (installed) return;
|
|
649
|
+
installed = true;
|
|
650
|
+
const prototype = Element.prototype;
|
|
651
|
+
prototype.scrollTo = patchedScrollTo;
|
|
652
|
+
prototype.scrollBy = patchedScrollBy;
|
|
653
|
+
prototype.scrollIntoView = patchedScrollIntoView;
|
|
654
|
+
for (const kind of TAKEOVER_EVENTS) {
|
|
655
|
+
window.addEventListener(kind, onTakeover, true);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
function uninstall() {
|
|
659
|
+
cancel();
|
|
660
|
+
if (!installed) return;
|
|
661
|
+
installed = false;
|
|
662
|
+
const prototype = Element.prototype;
|
|
663
|
+
prototype.scrollTo = shipped.scrollTo;
|
|
664
|
+
prototype.scrollBy = shipped.scrollBy;
|
|
665
|
+
prototype.scrollIntoView = shipped.scrollIntoView;
|
|
666
|
+
for (const kind of TAKEOVER_EVENTS) {
|
|
667
|
+
window.removeEventListener(kind, onTakeover, true);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
return {
|
|
671
|
+
apply(next) {
|
|
672
|
+
uninstall();
|
|
673
|
+
settings = next;
|
|
674
|
+
if (!next.scroll || next.mode === "off") return;
|
|
675
|
+
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
|
|
676
|
+
install();
|
|
677
|
+
},
|
|
678
|
+
restore: uninstall
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
|
|
444
682
|
// src/styles.ts
|
|
445
683
|
var ROW_CSS = `
|
|
446
684
|
/* Row chrome: AppearanceRow.module.css .group \u2014 16/0 padding, hairline
|
|
@@ -622,6 +860,8 @@
|
|
|
622
860
|
"motion.spatial-split-hint": "\u4F4D\u79FB\u66F2\u7EBF\u5E26\u8FC7\u51B2\uFF08\u5FEB\u4F4D\u79FB\u7EA6 9%\uFF0C\u9ED8\u8BA4\u4F4D\u79FB\u7EA6 1.4%\uFF09\uFF0C\u6548\u679C\u66F2\u7EBF\u6CA1\u6709\u3002\u5173\u95ED\u540E\u5168\u90E8\u4F7F\u7528\u6548\u679C\u66F2\u7EBF\u3002",
|
|
623
861
|
"motion.quantize": "\u65F6\u957F\u5BF9\u9F50\u5230 Material \u6863\u4F4D",
|
|
624
862
|
"motion.quantize-hint": "\u628A\u6539\u5199\u540E\u7684\u65F6\u957F\u5438\u9644\u5230 50/100/150/200/250/300/350/400/450/500/550/600/700/800/900/1000ms\u3002",
|
|
863
|
+
"motion.scroll": "\u6EDA\u52A8\u4F7F\u7528\u540C\u4E00\u66F2\u7EBF",
|
|
864
|
+
"motion.scroll-hint": "\u63A5\u7BA1\u811A\u672C\u8C03\u7528\u7684\u5E73\u6ED1\u6EDA\u52A8\uFF08\u804A\u5929\u8DDF\u968F\u3001\u8F68\u8FF9\u5B9A\u4F4D\u3001\u5361\u7247\u5C45\u4E2D\uFF09\uFF0C\u6539\u7528\u672C\u9875\u66F2\u7EBF\u3002\u8BFB\u8005\u4E00\u65E6\u6EDA\u8F6E\u3001\u89E6\u6478\u6216\u6309\u952E\uFF0C\u811A\u672C\u6EDA\u52A8\u7ACB\u5373\u505C\u6B62\u5E76\u4EA4\u8FD8\u539F\u751F\u63A7\u5236\u3002",
|
|
625
865
|
"motion.reduced": "\u59CB\u7EC8\u51CF\u5F31\u52A8\u6548",
|
|
626
866
|
"motion.reduced-hint": "\u5F00\u542F\u540E\u5FFD\u7565\u7CFB\u7EDF\u7684\u300C\u51CF\u5F31\u52A8\u6001\u6548\u679C\u300D\u504F\u597D\uFF0C\u4E00\u5F8B\u628A\u65F6\u957F\u538B\u5230 1ms\u3002\u5173\u95ED\u65F6\u8DDF\u968F\u7CFB\u7EDF\u3002",
|
|
627
867
|
"motion.system-reduced": "\u7CFB\u7EDF\u5DF2\u8BF7\u6C42\u51CF\u5F31\u52A8\u6548\uFF0C\u672C\u9875\u52A8\u753B\u6B63\u5728\u88AB\u538B\u5236\u3002",
|
|
@@ -652,6 +892,8 @@
|
|
|
652
892
|
"motion.spatial-split-hint": "The spatial curves overshoot (about 9% for the fast one, 1.4% for the default); the effects curve does not. Turning this off uses the effects curve everywhere.",
|
|
653
893
|
"motion.quantize": "Snap durations to the Material ladder",
|
|
654
894
|
"motion.quantize-hint": "Pull rewritten durations onto 50/100/150/200/250/300/350/400/450/500/550/600/700/800/900/1000 ms.",
|
|
895
|
+
"motion.scroll": "Scroll on the same curve",
|
|
896
|
+
"motion.scroll-hint": "Take over scripted smooth scrolling \u2014 chat follow, trajectory locate, card centring \u2014 and drive it on this page's curve. Any wheel, touch, or scroll key stops the scripted motion at once and hands control back to the browser.",
|
|
655
897
|
"motion.reduced": "Always reduce motion",
|
|
656
898
|
"motion.reduced-hint": "Ignore the system reduce-motion preference and force every duration to 1 ms. Off follows the system.",
|
|
657
899
|
"motion.system-reduced": "The system asked for reduced motion; this page is being held still.",
|
|
@@ -700,6 +942,7 @@
|
|
|
700
942
|
};
|
|
701
943
|
const listeners = /* @__PURE__ */ new Set();
|
|
702
944
|
const motion = createMotion();
|
|
945
|
+
const scrolling = createScrollMotion();
|
|
703
946
|
const publish = (patch) => {
|
|
704
947
|
view = { ...view, ...patch };
|
|
705
948
|
for (const listener of listeners) listener();
|
|
@@ -715,8 +958,9 @@
|
|
|
715
958
|
const applyMotion = (next) => {
|
|
716
959
|
if (disposed) return;
|
|
717
960
|
motion.apply(next);
|
|
961
|
+
scrolling.apply(next);
|
|
718
962
|
};
|
|
719
|
-
const motionDiffers = (left, right) => left.mode !== right.mode || left.durationScale !== right.durationScale || left.spatialSplit !== right.spatialSplit || left.quantize !== right.quantize || left.reduced !== right.reduced;
|
|
963
|
+
const motionDiffers = (left, right) => left.mode !== right.mode || left.durationScale !== right.durationScale || left.spatialSplit !== right.spatialSplit || left.quantize !== right.quantize || left.reduced !== right.reduced || left.scroll !== right.scroll;
|
|
720
964
|
const clearOverride = () => {
|
|
721
965
|
disposeOverride?.();
|
|
722
966
|
disposeOverride = void 0;
|
|
@@ -1163,6 +1407,15 @@
|
|
|
1163
1407
|
tr("motion.quantize-hint"),
|
|
1164
1408
|
locked
|
|
1165
1409
|
),
|
|
1410
|
+
checkbox(
|
|
1411
|
+
current.scroll,
|
|
1412
|
+
(next) => {
|
|
1413
|
+
void saveMotion({ scroll: next });
|
|
1414
|
+
},
|
|
1415
|
+
tr("motion.scroll"),
|
|
1416
|
+
tr("motion.scroll-hint"),
|
|
1417
|
+
locked
|
|
1418
|
+
),
|
|
1166
1419
|
checkbox(
|
|
1167
1420
|
current.reduced === "always",
|
|
1168
1421
|
(next) => {
|
|
@@ -1245,6 +1498,7 @@
|
|
|
1245
1498
|
disposed = true;
|
|
1246
1499
|
teardown();
|
|
1247
1500
|
motion.restore();
|
|
1501
|
+
scrolling.restore();
|
|
1248
1502
|
};
|
|
1249
1503
|
}, "ui-experience: page-level layers");
|
|
1250
1504
|
void poll();
|
package/lib/index.js
CHANGED
|
@@ -39,7 +39,8 @@ var Config = z.object({
|
|
|
39
39
|
motionDurationScale: z.number().min(0.5).max(2).default(1).volatile(),
|
|
40
40
|
motionSpatialSplit: z.boolean().default(true).volatile(),
|
|
41
41
|
motionQuantize: z.boolean().default(false).volatile(),
|
|
42
|
-
motionReduced: z.string().pattern(MOTION_REDUCED).default("system").volatile()
|
|
42
|
+
motionReduced: z.string().pattern(MOTION_REDUCED).default("system").volatile(),
|
|
43
|
+
motionScroll: z.boolean().default(true).volatile()
|
|
43
44
|
});
|
|
44
45
|
var REQUIRED_COLOR_KEYS = [
|
|
45
46
|
"surface_container_lowest",
|
|
@@ -319,7 +320,8 @@ function effectiveMotion(config) {
|
|
|
319
320
|
durationScale: config.motionDurationScale.get(),
|
|
320
321
|
spatialSplit: config.motionSpatialSplit.get(),
|
|
321
322
|
quantize: config.motionQuantize.get(),
|
|
322
|
-
reduced: config.motionReduced.get()
|
|
323
|
+
reduced: config.motionReduced.get(),
|
|
324
|
+
scroll: config.motionScroll.get()
|
|
323
325
|
};
|
|
324
326
|
}
|
|
325
327
|
var paletteCache;
|
|
@@ -570,7 +572,8 @@ var UiExperienceService = class extends TypertRemoteService {
|
|
|
570
572
|
const scale = args?.durationScale;
|
|
571
573
|
const spatialSplit = args?.spatialSplit;
|
|
572
574
|
const quantize = args?.quantize;
|
|
573
|
-
|
|
575
|
+
const scroll = args?.scroll;
|
|
576
|
+
if (mode === void 0 && reduced === void 0 && scale === void 0 && spatialSplit === void 0 && quantize === void 0 && scroll === void 0) {
|
|
574
577
|
throw new Error("ui-experience: setMotion requires at least one field");
|
|
575
578
|
}
|
|
576
579
|
if (mode !== void 0 && !MOTION_MODE.test(mode)) {
|
|
@@ -588,16 +591,20 @@ var UiExperienceService = class extends TypertRemoteService {
|
|
|
588
591
|
if (quantize !== void 0 && typeof quantize !== "boolean") {
|
|
589
592
|
throw new Error(`ui-experience: quantize must be a boolean, got ${typeof quantize}`);
|
|
590
593
|
}
|
|
594
|
+
if (scroll !== void 0 && typeof scroll !== "boolean") {
|
|
595
|
+
throw new Error(`ui-experience: scroll must be a boolean, got ${typeof scroll}`);
|
|
596
|
+
}
|
|
591
597
|
await this.persist((current) => ({
|
|
592
598
|
...current,
|
|
593
599
|
...mode === void 0 ? {} : { motion: mode },
|
|
594
600
|
...reduced === void 0 ? {} : { motionReduced: reduced },
|
|
595
601
|
...scale === void 0 ? {} : { motionDurationScale: scale },
|
|
596
602
|
...spatialSplit === void 0 ? {} : { motionSpatialSplit: spatialSplit },
|
|
597
|
-
...quantize === void 0 ? {} : { motionQuantize: quantize }
|
|
603
|
+
...quantize === void 0 ? {} : { motionQuantize: quantize },
|
|
604
|
+
...scroll === void 0 ? {} : { motionScroll: scroll }
|
|
598
605
|
}));
|
|
599
606
|
const applied = effectiveMotion(this.config);
|
|
600
|
-
logLine(`setMotion: mode=${applied.mode} durationScale=${String(applied.durationScale)} spatialSplit=${String(applied.spatialSplit)} quantize=${String(applied.quantize)} reduced=${applied.reduced}`);
|
|
607
|
+
logLine(`setMotion: mode=${applied.mode} durationScale=${String(applied.durationScale)} spatialSplit=${String(applied.spatialSplit)} quantize=${String(applied.quantize)} reduced=${applied.reduced} scroll=${String(applied.scroll)}`);
|
|
601
608
|
return applied;
|
|
602
609
|
}
|
|
603
610
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dsh-plugin/dsh-ui-experience",
|
|
3
|
-
"version": "1.0.0-dev.
|
|
3
|
+
"version": "1.0.0-dev.43492422772",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "UI experience enhancements for DeepSeek Harness: follow the system matugen Material You palette as the interface theme, and rewrite interface motion onto Material 3 / Material 3 Expressive curves.",
|
|
6
6
|
"license": "MIT",
|