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

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,9 +189,9 @@
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();
@@ -234,7 +236,7 @@
234
236
  const frames = /* @__PURE__ */ new Map();
235
237
  for (const sheet of sheets) {
236
238
  walk(sheet.cssRules, (rule) => {
237
- if (rule.name === void 0 || rule.cssRules === void 0) return;
239
+ if (rule.type !== KEYFRAMES_RULE || rule.name === void 0 || rule.cssRules === void 0) return;
238
240
  const properties = frames.get(rule.name) ?? [];
239
241
  walk(rule.cssRules, (frame) => {
240
242
  const declaration = frame.style;
@@ -286,7 +288,7 @@
286
288
  declaration.setProperty(family.timing, nextTimings.join(", "));
287
289
  changed = true;
288
290
  }
289
- if (durationChanged && nextDurations.length === count) {
291
+ if (durationChanged) {
290
292
  declaration.setProperty(family.duration, nextDurations.join(", "));
291
293
  changed = true;
292
294
  }
@@ -302,8 +304,9 @@
302
304
  const declaration = rule.style;
303
305
  if (declaration === void 0 || patches.has(rule)) return;
304
306
  const before = declaration.cssText;
305
- const touched = rewriteFamily(declaration, TRANSITIONS, frames, active) || rewriteFamily(declaration, ANIMATIONS, frames, active);
306
- if (!touched) return;
307
+ const movedTransition = rewriteFamily(declaration, TRANSITIONS, frames, active);
308
+ const movedAnimation = rewriteFamily(declaration, ANIMATIONS, frames, active);
309
+ if (!movedTransition && !movedAnimation) return;
307
310
  patches.set(rule, { declaration, before, after: declaration.cssText });
308
311
  });
309
312
  }
@@ -311,12 +314,19 @@
311
314
  function applyDurationTokens() {
312
315
  const active = settings;
313
316
  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`);
317
+ const root = document.documentElement;
318
+ const computed = window.getComputedStyle(root);
319
+ for (const token of DURATION_TOKENS) {
320
+ const base = parseDurationMs(computed.getPropertyValue(token).trim());
321
+ if (base === void 0) {
322
+ console.error(`ui-experience: motion cannot read ${token}; durations using it keep their shipped value`);
323
+ continue;
324
+ }
325
+ root.style.setProperty(token, `${Math.round(base * active.durationScale)}ms`);
316
326
  }
317
327
  }
318
328
  function clearDurationTokens() {
319
- for (const [token] of DURATION_TOKENS) document.documentElement.style.removeProperty(token);
329
+ for (const token of DURATION_TOKENS) document.documentElement.style.removeProperty(token);
320
330
  }
321
331
  function wantsReduced() {
322
332
  const active = settings;
@@ -324,8 +334,7 @@
324
334
  if (active.reduced === "always") return true;
325
335
  return active.reduced === "system" && media.matches;
326
336
  }
327
- function syncReduced() {
328
- const want = wantsReduced();
337
+ function setReducedSheet(want) {
329
338
  if (want === reducedActive) return;
330
339
  reducedActive = want;
331
340
  reducedSheet?.remove();
@@ -336,13 +345,25 @@
336
345
  document.head.appendChild(sheet);
337
346
  reducedSheet = sheet;
338
347
  }
348
+ function realize() {
349
+ const active = settings;
350
+ if (active === void 0 || active.mode === "off") return;
351
+ const want = wantsReduced();
352
+ setReducedSheet(want);
353
+ if (want) {
354
+ restoreDeclarations();
355
+ clearDurationTokens();
356
+ return;
357
+ }
358
+ applyDurationTokens();
359
+ scan();
360
+ observer ??= observeDocument();
361
+ }
339
362
  function refresh() {
340
363
  if (refreshPending) return;
341
364
  refreshPending = true;
342
365
  queueMicrotask(() => {
343
366
  refreshPending = false;
344
- const active = settings;
345
- if (active === void 0 || active.mode === "off" || reducedActive) return;
346
367
  scan();
347
368
  });
348
369
  }
@@ -351,23 +372,24 @@
351
372
  refresh();
352
373
  });
353
374
  instance.observe(document.head, { childList: true, subtree: true });
375
+ instance.observe(document.body, { childList: true, subtree: true });
354
376
  return { disconnect: () => {
355
377
  instance.disconnect();
356
378
  } };
357
379
  }
358
380
  function unwatchPreference() {
359
381
  if (mediaListener === void 0) return;
360
- media.removeEventListener?.("change", mediaListener);
382
+ media.removeEventListener("change", mediaListener);
361
383
  mediaListener = void 0;
362
384
  }
363
385
  function watchPreference() {
364
386
  if (mediaListener !== void 0) return;
365
387
  mediaListener = () => {
366
- syncReduced();
388
+ realize();
367
389
  };
368
- media.addEventListener?.("change", mediaListener);
390
+ media.addEventListener("change", mediaListener);
369
391
  }
370
- function restore() {
392
+ function restoreDeclarations() {
371
393
  for (const patch of patches.values()) {
372
394
  if (patch.declaration.cssText !== patch.after) {
373
395
  console.error("ui-experience: motion restore overwrote a declaration that changed after this layer wrote it");
@@ -375,11 +397,15 @@
375
397
  patch.declaration.cssText = patch.before;
376
398
  }
377
399
  patches.clear();
400
+ }
401
+ function restore() {
402
+ restoreDeclarations();
378
403
  clearDurationTokens();
379
- reducedSheet?.remove();
380
- reducedSheet = void 0;
381
- reducedActive = false;
404
+ setReducedSheet(false);
382
405
  unwatchPreference();
406
+ observer?.disconnect();
407
+ observer = void 0;
408
+ settings = void 0;
383
409
  document.documentElement.removeAttribute(MOTION_ATTRIBUTE);
384
410
  }
385
411
  return {
@@ -388,23 +414,13 @@
388
414
  settings = next;
389
415
  if (next.mode === "off") return;
390
416
  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
417
  document.documentElement.setAttribute(MOTION_ATTRIBUTE, next.mode);
418
+ realize();
400
419
  },
401
420
  refresh,
402
421
  restore,
403
422
  dispose() {
404
423
  restore();
405
- observer?.disconnect();
406
- observer = void 0;
407
- settings = void 0;
408
424
  }
409
425
  };
410
426
  }
@@ -579,7 +595,7 @@
579
595
  "palette.reset": "\u6062\u590D\u9ED8\u8BA4",
580
596
  "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
597
  "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",
598
+ "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
599
  "motion.mode": "\u66F2\u7EBF\u65CF",
584
600
  "motion.mode-off": "\u5173\u95ED",
585
601
  "motion.mode-m3": "M3",
@@ -587,7 +603,7 @@
587
603
  "motion.duration": "\u65F6\u957F\u500D\u7387",
588
604
  "motion.options": "\u9AD8\u7EA7\u9009\u9879",
589
605
  "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",
606
+ "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
607
  "motion.quantize": "\u65F6\u957F\u5BF9\u9F50\u5230 Material \u6863\u4F4D",
592
608
  "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
609
  "motion.reduced": "\u59CB\u7EC8\u51CF\u5F31\u52A8\u6548",
@@ -617,7 +633,7 @@
617
633
  "motion.duration": "Duration scale",
618
634
  "motion.options": "Advanced options",
619
635
  "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.",
636
+ "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
637
  "motion.quantize": "Snap durations to the Material ladder",
622
638
  "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
639
  "motion.reduced": "Always reduce motion",
@@ -1103,12 +1119,13 @@
1103
1119
  { value: "m3", label: tr("motion.mode-m3") },
1104
1120
  { value: "m3e", label: tr("motion.mode-m3e") }
1105
1121
  ];
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
- ];
1122
+ const ladder = [0.5, 0.75, 1, 1.25, 1.5, 2];
1123
+ if (!ladder.includes(current.durationScale)) ladder.push(current.durationScale);
1124
+ ladder.sort((left, right) => left - right);
1125
+ const scales = ladder.map((value) => ({
1126
+ value: String(value),
1127
+ label: `${value}\xD7`
1128
+ }));
1112
1129
  const policy = open ? h(
1113
1130
  "div",
1114
1131
  { 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.43488816373",
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",