@dsh-plugin/dsh-ui-experience 1.0.0-dev.43487914661 → 1.0.0-dev.43488936935

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 CHANGED
@@ -119,11 +119,13 @@ token 的值按原样写入 CSS 变量,因此 `color-mix(...)` 这类表达式
119
119
  | 位移(transform、宽高、内外距、gap、圆角…) | `cubic-bezier(0.05, 0.7, 0.1, 1)`(emphasized decelerate) | 声明时长 ≤200ms 用 `cubic-bezier(0.42, 1.67, 0.21, 0.9)`,否则用 `cubic-bezier(0.38, 1.21, 0.22, 1)` |
120
120
  | 效果(颜色、透明度、阴影、滤镜…) | `cubic-bezier(0.2, 0, 0, 1)`(standard) | `cubic-bezier(0.34, 0.8, 0.34, 1)` |
121
121
 
122
- 两族都把「移动」和「效果」分开,原因在 M3E 上最直观:它的两条位移曲线带过冲,峰值分别为 1.092 与 1.014,效果曲线不过冲。物体移动有惯性,颜色过冲则会闪出调色板里不存在的颜色。M3 的分离幅度小些,但方向一致——加速段的 decelerate 曲线只给移动,其余走 standard。
122
+ 两族都把「移动」和「效果」分开,原因在 M3E 上最直观:它的两条位移曲线带过冲,峰值分别为 1.092 与 1.014,效果曲线不过冲。物体移动有惯性,颜色过冲则会闪出调色板里不存在的颜色。
123
123
 
124
- 关闭「位移与效果使用不同曲线」后,两族都退化为只用效果曲线。
124
+ M3 那侧的分离要说明白:Material 3 的 `emphasized` 整体是两段首尾相接的三次贝塞尔(`pathInterpolator(M 0,0 C 0.05,0,0.133333,0.06,0.166666,0.4 C 0.208333,0.82,0.25,1,1,1)`),`cubic-bezier()` 只能表达一段,所以 Material 官方的 Web token 表把 `emphasized` 标为不可用、回退到 `standard`。这里改用官方单列出来的 `emphasized-decelerate`(`cubic-bezier(0.05, 0.7, 0.1, 1)`)承担位移,效果侧用 `standard`——两个都是 Material 自己发布的 token,不是拼出来的近似。
125
+
126
+ 本层无法区分入场与出场,因此位移一律用 decelerate;Material 对出场建议用对应的 accelerate。
125
127
 
126
- M3 的 emphasized 整体是一条五次贝塞尔,`cubic-bezier()` 表达不了;这里用的是它减速段的等价三次形式,也正是 Material 官方 web token 里的 `emphasized-decelerate` 取值。
128
+ 关闭「位移与效果使用不同曲线」后,两族都退化为只用效果曲线。
127
129
 
128
130
  ### 为什么是改写 CSSOM,而不是注入覆盖样式表
129
131
 
@@ -147,7 +149,9 @@ M3 的 emphasized 整体是一条五次贝塞尔,`cubic-bezier()` 表达不了
147
149
 
148
150
  ### 时长
149
151
 
150
- - 「时长倍率」按 1× / 1.25× / 1.5× / 2× 缩放**写死的字面时长**;引用 `var(--ds-transition-duration*)` 的时长不动它本身,改为把这些自定义属性按同一倍率缩放,效果一致且不用逐条改。
152
+ - 「时长倍率」在界面上提供 0.5× / 0.75× / 1× / 1.25× / 1.5× / 2×。配置里手工写入的其它值(schema 允许 0.5–2)会插入档位表并选中,不会被悄悄折回 1×。
153
+ - 倍率作用于**写死的字面时长**;引用 `var(--ds-transition-duration*)` 的时长不动它本身,改为把这三个自定义属性按同一倍率缩放,效果一致且不用逐条改。缩放前会读取页面当前的计算值,DSH 改了基值也按新基值算。
154
+ - `1ms` 及以下的时长视为「立即」,既不放缩也不吸附——DSH 的减弱动效规则里写的就是 `.01ms`,把它当成待调的时长去放大,本来即时完成的过渡会变成可见的一跳。
151
155
  - 无限循环的时长不缩放——1 秒的旋转是设计选定的速度,不是待调的过渡。
152
156
  - 「时长对齐到 Material 档位」把结果吸附到 50/100/150/200/250/300/350/400/450/500/550/600/700/800/900/1000ms。
153
157
 
@@ -160,8 +164,9 @@ M3 的 emphasized 整体是一条五次贝塞尔,`cubic-bezier()` 表达不了
160
164
 
161
165
  ### 生效范围
162
166
 
163
- - 覆盖页面上的全部样式表,包括其他插件注入的。首次应用时全量扫描,之后用 `MutationObserver` 监视 `head`,新注入的样式表在下一个微任务内被补上。
164
- - 跨源样式表读不到 `cssRules`,会在 `~/.dsh/ui-experience.log` 记录一行并跳过——这类表的规则无法改写,静默跳过会让部分界面停留在原生曲线而无迹可循。
167
+ - 覆盖页面上的全部样式表,包括其他插件注入的。首次应用时全量扫描,之后用 `MutationObserver` 监视 `head` 与 `body`,新注入的 `<style>` 在下一个微任务内被补上。
168
+ - 通过 `document.adoptedStyleSheets` 挂载的构造式样式表不是 DOM 变更,只在首次应用与下一次 `apply` 时被读取。
169
+ - 跨源样式表读不到 `cssRules`,会在浏览器控制台记录一行并跳过——这类表的规则无法改写,静默跳过会让部分界面停留在原生曲线而无迹可循。(这条诊断走控制台而非 `~/.dsh/ui-experience.log`:后者由宿主半写入,浏览器半够不到文件系统。)
165
170
  - 状态会写在 `<html data-dsh-uix-motion="m3|m3e">` 上,便于区分曲线来源。
166
171
 
167
172
  ## 边框宽度
package/lib/client.js CHANGED
@@ -127,7 +127,6 @@
127
127
  const match = DURATION.exec(value.trim());
128
128
  if (match === null) return void 0;
129
129
  const amount = Number(match[1]);
130
- if (!Number.isFinite(amount) || amount < 0) return void 0;
131
130
  return match[2] === "s" ? amount * 1e3 : amount;
132
131
  }
133
132
  function quantizeMs(ms) {
@@ -143,11 +142,13 @@
143
142
  return best;
144
143
  }
145
144
  var MAX_DURATION_MS = 1e4;
145
+ var NEGLIGIBLE_MS = 1;
146
146
  function retime(value, scale, quantize) {
147
+ if (scale === 1 && !quantize) return void 0;
147
148
  const original = parseDurationMs(value);
148
- if (original === void 0 || original === 0) return void 0;
149
+ if (original === void 0 || original <= NEGLIGIBLE_MS) return void 0;
149
150
  const scaled = Math.round(original * scale);
150
- const target = Math.min(MAX_DURATION_MS, Math.max(1, quantize ? quantizeMs(scaled) : scaled));
151
+ const target = Math.min(MAX_DURATION_MS, Math.max(NEGLIGIBLE_MS, quantize ? quantizeMs(scaled) : scaled));
151
152
  if (target === original) return void 0;
152
153
  return `${target}ms`;
153
154
  }
@@ -175,6 +176,7 @@
175
176
  }
176
177
 
177
178
  // src/motion.ts
179
+ var KEYFRAMES_RULE = 7;
178
180
  var TRANSITIONS = {
179
181
  selector: "transition-property",
180
182
  duration: "transition-duration",
@@ -187,12 +189,13 @@
187
189
  iterations: "animation-iteration-count"
188
190
  };
189
191
  var DURATION_TOKENS = [
190
- ["--ds-transition-duration", 200],
191
- ["--ds-transition-duration-fast", 100],
192
- ["--ds-transition-duration-slow", 300]
192
+ "--ds-transition-duration",
193
+ "--ds-transition-duration-fast",
194
+ "--ds-transition-duration-slow"
193
195
  ];
194
196
  function createMotion() {
195
197
  const patches = /* @__PURE__ */ new Map();
198
+ const durationBase = /* @__PURE__ */ new Map();
196
199
  const unreadable = /* @__PURE__ */ new Set();
197
200
  const media = window.matchMedia("(prefers-reduced-motion: reduce)");
198
201
  let settings;
@@ -234,7 +237,7 @@
234
237
  const frames = /* @__PURE__ */ new Map();
235
238
  for (const sheet of sheets) {
236
239
  walk(sheet.cssRules, (rule) => {
237
- if (rule.name === void 0 || rule.cssRules === void 0) return;
240
+ if (rule.type !== KEYFRAMES_RULE || rule.name === void 0 || rule.cssRules === void 0) return;
238
241
  const properties = frames.get(rule.name) ?? [];
239
242
  walk(rule.cssRules, (frame) => {
240
243
  const declaration = frame.style;
@@ -286,7 +289,7 @@
286
289
  declaration.setProperty(family.timing, nextTimings.join(", "));
287
290
  changed = true;
288
291
  }
289
- if (durationChanged && nextDurations.length === count) {
292
+ if (durationChanged) {
290
293
  declaration.setProperty(family.duration, nextDurations.join(", "));
291
294
  changed = true;
292
295
  }
@@ -302,8 +305,9 @@
302
305
  const declaration = rule.style;
303
306
  if (declaration === void 0 || patches.has(rule)) return;
304
307
  const before = declaration.cssText;
305
- const touched = rewriteFamily(declaration, TRANSITIONS, frames, active) || rewriteFamily(declaration, ANIMATIONS, frames, active);
306
- if (!touched) return;
308
+ const movedTransition = rewriteFamily(declaration, TRANSITIONS, frames, active);
309
+ const movedAnimation = rewriteFamily(declaration, ANIMATIONS, frames, active);
310
+ if (!movedTransition && !movedAnimation) return;
307
311
  patches.set(rule, { declaration, before, after: declaration.cssText });
308
312
  });
309
313
  }
@@ -311,12 +315,23 @@
311
315
  function applyDurationTokens() {
312
316
  const active = settings;
313
317
  if (active === void 0 || active.durationScale === 1) return;
314
- for (const [token, base] of DURATION_TOKENS) {
315
- document.documentElement.style.setProperty(token, `${Math.round(base * active.durationScale)}ms`);
318
+ const root = document.documentElement;
319
+ const computed = window.getComputedStyle(root);
320
+ for (const token of DURATION_TOKENS) {
321
+ let base = durationBase.get(token);
322
+ if (base === void 0) {
323
+ base = parseDurationMs(computed.getPropertyValue(token).trim());
324
+ if (base === void 0) {
325
+ console.error(`ui-experience: motion cannot read ${token}; durations using it keep their shipped value`);
326
+ continue;
327
+ }
328
+ durationBase.set(token, base);
329
+ }
330
+ root.style.setProperty(token, `${Math.round(base * active.durationScale)}ms`);
316
331
  }
317
332
  }
318
333
  function clearDurationTokens() {
319
- for (const [token] of DURATION_TOKENS) document.documentElement.style.removeProperty(token);
334
+ for (const token of DURATION_TOKENS) document.documentElement.style.removeProperty(token);
320
335
  }
321
336
  function wantsReduced() {
322
337
  const active = settings;
@@ -324,8 +339,7 @@
324
339
  if (active.reduced === "always") return true;
325
340
  return active.reduced === "system" && media.matches;
326
341
  }
327
- function syncReduced() {
328
- const want = wantsReduced();
342
+ function setReducedSheet(want) {
329
343
  if (want === reducedActive) return;
330
344
  reducedActive = want;
331
345
  reducedSheet?.remove();
@@ -336,13 +350,25 @@
336
350
  document.head.appendChild(sheet);
337
351
  reducedSheet = sheet;
338
352
  }
353
+ function realize() {
354
+ const active = settings;
355
+ if (active === void 0 || active.mode === "off") return;
356
+ const want = wantsReduced();
357
+ setReducedSheet(want);
358
+ if (want) {
359
+ restoreDeclarations();
360
+ clearDurationTokens();
361
+ return;
362
+ }
363
+ applyDurationTokens();
364
+ scan();
365
+ observer ??= observeDocument();
366
+ }
339
367
  function refresh() {
340
368
  if (refreshPending) return;
341
369
  refreshPending = true;
342
370
  queueMicrotask(() => {
343
371
  refreshPending = false;
344
- const active = settings;
345
- if (active === void 0 || active.mode === "off" || reducedActive) return;
346
372
  scan();
347
373
  });
348
374
  }
@@ -351,23 +377,24 @@
351
377
  refresh();
352
378
  });
353
379
  instance.observe(document.head, { childList: true, subtree: true });
380
+ instance.observe(document.body, { childList: true, subtree: true });
354
381
  return { disconnect: () => {
355
382
  instance.disconnect();
356
383
  } };
357
384
  }
358
385
  function unwatchPreference() {
359
386
  if (mediaListener === void 0) return;
360
- media.removeEventListener?.("change", mediaListener);
387
+ media.removeEventListener("change", mediaListener);
361
388
  mediaListener = void 0;
362
389
  }
363
390
  function watchPreference() {
364
391
  if (mediaListener !== void 0) return;
365
392
  mediaListener = () => {
366
- syncReduced();
393
+ realize();
367
394
  };
368
- media.addEventListener?.("change", mediaListener);
395
+ media.addEventListener("change", mediaListener);
369
396
  }
370
- function restore() {
397
+ function restoreDeclarations() {
371
398
  for (const patch of patches.values()) {
372
399
  if (patch.declaration.cssText !== patch.after) {
373
400
  console.error("ui-experience: motion restore overwrote a declaration that changed after this layer wrote it");
@@ -375,11 +402,15 @@
375
402
  patch.declaration.cssText = patch.before;
376
403
  }
377
404
  patches.clear();
405
+ }
406
+ function restore() {
407
+ restoreDeclarations();
378
408
  clearDurationTokens();
379
- reducedSheet?.remove();
380
- reducedSheet = void 0;
381
- reducedActive = false;
409
+ setReducedSheet(false);
382
410
  unwatchPreference();
411
+ observer?.disconnect();
412
+ observer = void 0;
413
+ settings = void 0;
383
414
  document.documentElement.removeAttribute(MOTION_ATTRIBUTE);
384
415
  }
385
416
  return {
@@ -388,23 +419,13 @@
388
419
  settings = next;
389
420
  if (next.mode === "off") return;
390
421
  watchPreference();
391
- syncReduced();
392
- if (reducedActive) {
393
- document.documentElement.setAttribute(MOTION_ATTRIBUTE, next.mode);
394
- return;
395
- }
396
- applyDurationTokens();
397
- scan();
398
- observer ??= observeDocument();
399
422
  document.documentElement.setAttribute(MOTION_ATTRIBUTE, next.mode);
423
+ realize();
400
424
  },
401
425
  refresh,
402
426
  restore,
403
427
  dispose() {
404
428
  restore();
405
- observer?.disconnect();
406
- observer = void 0;
407
- settings = void 0;
408
429
  }
409
430
  };
410
431
  }
@@ -579,7 +600,7 @@
579
600
  "palette.reset": "\u6062\u590D\u9ED8\u8BA4",
580
601
  "palette.paths-hint": "\u652F\u6301\u4EE5 ~ \u5F00\u5934\u7684\u8DEF\u5F84\uFF0C\u6216\u7EDD\u5BF9\u8DEF\u5F84\u3002\u989C\u8272\u6587\u4EF6\u63D0\u4F9B\u5B8C\u6574\u6620\u5C04\uFF0C\u4EE4\u724C\u6587\u4EF6\u91CC\u5217\u51FA\u7684 token \u9010\u9879\u8986\u76D6\u3002",
581
602
  "motion.title": "M3 \u52A8\u6548",
582
- "motion.description": "\u6309 Material 3 \u66F2\u7EBF\u6539\u5199\u6574\u4E2A\u754C\u9762\u7684\u8FC7\u6E21\u4E0E\u5173\u952E\u5E27\u52A8\u753B\uFF0C\u539F\u5730\u6539\u5199\u5165\u5DF2\u751F\u6548\u7684\u6837\u5F0F\u89C4\u5219",
603
+ "motion.description": "\u6309 Material 3 \u66F2\u7EBF\u6539\u5199\u6574\u4E2A\u754C\u9762\u7684\u8FC7\u6E21\u4E0E\u5173\u952E\u5E27\u52A8\u753B\uFF0C\u76F4\u63A5\u6539\u5199\u5DF2\u751F\u6548\u7684\u6837\u5F0F\u89C4\u5219",
583
604
  "motion.mode": "\u66F2\u7EBF\u65CF",
584
605
  "motion.mode-off": "\u5173\u95ED",
585
606
  "motion.mode-m3": "M3",
@@ -587,7 +608,7 @@
587
608
  "motion.duration": "\u65F6\u957F\u500D\u7387",
588
609
  "motion.options": "\u9AD8\u7EA7\u9009\u9879",
589
610
  "motion.spatial-split": "\u4F4D\u79FB\u4E0E\u6548\u679C\u4F7F\u7528\u4E0D\u540C\u66F2\u7EBF",
590
- "motion.spatial-split-hint": "M3E \u7684\u4F4D\u79FB\u66F2\u7EBF\u5E26\u6709\u7EA6 9% \u7684\u8FC7\u51B2\uFF0C\u6548\u679C\u66F2\u7EBF\u6CA1\u6709\u3002\u5173\u95ED\u540E\u5168\u90E8\u4F7F\u7528\u6548\u679C\u66F2\u7EBF\u3002",
611
+ "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",
591
612
  "motion.quantize": "\u65F6\u957F\u5BF9\u9F50\u5230 Material \u6863\u4F4D",
592
613
  "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",
593
614
  "motion.reduced": "\u59CB\u7EC8\u51CF\u5F31\u52A8\u6548",
@@ -617,7 +638,7 @@
617
638
  "motion.duration": "Duration scale",
618
639
  "motion.options": "Advanced options",
619
640
  "motion.spatial-split": "Spatial and effects take different curves",
620
- "motion.spatial-split-hint": "The M3E spatial curve overshoots by about 9%, the effects curve does not. Turning this off uses the effects curve everywhere.",
641
+ "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.",
621
642
  "motion.quantize": "Snap durations to the Material ladder",
622
643
  "motion.quantize-hint": "Pull rewritten durations onto 50/100/150/200/250/300/350/400/450/500/550/600/700/800/900/1000 ms.",
623
644
  "motion.reduced": "Always reduce motion",
@@ -1103,12 +1124,13 @@
1103
1124
  { value: "m3", label: tr("motion.mode-m3") },
1104
1125
  { value: "m3e", label: tr("motion.mode-m3e") }
1105
1126
  ];
1106
- const scales = [
1107
- { value: "1", label: "1\xD7" },
1108
- { value: "1.25", label: "1.25\xD7" },
1109
- { value: "1.5", label: "1.5\xD7" },
1110
- { value: "2", label: "2\xD7" }
1111
- ];
1127
+ const ladder = [0.5, 0.75, 1, 1.25, 1.5, 2];
1128
+ if (!ladder.includes(current.durationScale)) ladder.push(current.durationScale);
1129
+ ladder.sort((left, right) => left - right);
1130
+ const scales = ladder.map((value) => ({
1131
+ value: String(value),
1132
+ label: `${value}\xD7`
1133
+ }));
1112
1134
  const policy = open ? h(
1113
1135
  "div",
1114
1136
  { className: "dsh-uix-form" },
package/lib/index.js CHANGED
@@ -579,9 +579,15 @@ var UiExperienceService = class extends TypertRemoteService {
579
579
  if (reduced !== void 0 && !MOTION_REDUCED.test(reduced)) {
580
580
  throw new Error(`ui-experience: unknown reduced-motion policy ${JSON.stringify(reduced)}`);
581
581
  }
582
- if (scale !== void 0 && (!Number.isFinite(scale) || scale < 0.5 || scale > 2)) {
582
+ if (scale !== void 0 && (typeof scale !== "number" || !Number.isFinite(scale) || scale < 0.5 || scale > 2)) {
583
583
  throw new Error(`ui-experience: durationScale ${String(scale)} is outside 0.5\u20132`);
584
584
  }
585
+ if (spatialSplit !== void 0 && typeof spatialSplit !== "boolean") {
586
+ throw new Error(`ui-experience: spatialSplit must be a boolean, got ${typeof spatialSplit}`);
587
+ }
588
+ if (quantize !== void 0 && typeof quantize !== "boolean") {
589
+ throw new Error(`ui-experience: quantize must be a boolean, got ${typeof quantize}`);
590
+ }
585
591
  await this.persist((current) => ({
586
592
  ...current,
587
593
  ...mode === void 0 ? {} : { motion: mode },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dsh-plugin/dsh-ui-experience",
3
- "version": "1.0.0-dev.43487914661",
3
+ "version": "1.0.0-dev.43488936935",
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",