ice-render 2.2.0 → 2.3.0

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.
Files changed (46) hide show
  1. package/dist/index.cjs +2 -2
  2. package/dist/index.mjs +2 -2
  3. package/dist/index.umd.js +2 -2
  4. package/dist/types/FrameManager.d.mts +16 -1
  5. package/dist/types/FrameManager.d.ts +16 -1
  6. package/dist/types/ICE.d.mts +93 -0
  7. package/dist/types/ICE.d.ts +93 -0
  8. package/dist/types/animation/AnimationManager.d.mts +117 -16
  9. package/dist/types/animation/AnimationManager.d.ts +117 -16
  10. package/dist/types/animation/AnimationTimeline.d.mts +77 -0
  11. package/dist/types/animation/AnimationTimeline.d.ts +77 -0
  12. package/dist/types/animation/easing-registry.d.mts +37 -0
  13. package/dist/types/animation/easing-registry.d.ts +37 -0
  14. package/dist/types/animation/interpolators.d.mts +34 -0
  15. package/dist/types/animation/interpolators.d.ts +34 -0
  16. package/dist/types/animation/validate-animations.d.mts +55 -0
  17. package/dist/types/animation/validate-animations.d.ts +55 -0
  18. package/dist/types/event/DOMEventDispatcher.d.mts +8 -0
  19. package/dist/types/event/DOMEventDispatcher.d.ts +8 -0
  20. package/dist/types/export/SvgExporter.d.mts +9 -2
  21. package/dist/types/export/SvgExporter.d.ts +9 -2
  22. package/dist/types/export/compose-layers.d.mts +30 -0
  23. package/dist/types/export/compose-layers.d.ts +30 -0
  24. package/dist/types/graphic/ICEComponent.d.mts +37 -1
  25. package/dist/types/graphic/ICEComponent.d.ts +37 -1
  26. package/dist/types/graphic/container/ICEGroup.d.mts +3 -1
  27. package/dist/types/graphic/container/ICEGroup.d.ts +3 -1
  28. package/dist/types/graphic/link/ICEPolyLine.d.mts +9 -1
  29. package/dist/types/graphic/link/ICEPolyLine.d.ts +9 -1
  30. package/dist/types/graphic/shape/ICEEllipse.d.mts +3 -1
  31. package/dist/types/graphic/shape/ICEEllipse.d.ts +3 -1
  32. package/dist/types/graphic/shape/ICEIsogon.d.mts +3 -1
  33. package/dist/types/graphic/shape/ICEIsogon.d.ts +3 -1
  34. package/dist/types/graphic/shape/ICERose.d.mts +3 -1
  35. package/dist/types/graphic/shape/ICERose.d.ts +3 -1
  36. package/dist/types/graphic/shape/ICEStar.d.mts +3 -1
  37. package/dist/types/graphic/shape/ICEStar.d.ts +3 -1
  38. package/dist/types/graphic/text/ICEText.d.mts +6 -0
  39. package/dist/types/graphic/text/ICEText.d.ts +6 -0
  40. package/dist/types/index.d.mts +9 -0
  41. package/dist/types/index.d.ts +9 -0
  42. package/dist/types/util/data-util.d.mts +10 -0
  43. package/dist/types/util/data-util.d.ts +10 -0
  44. package/dist/types/util/errors.d.mts +4 -0
  45. package/dist/types/util/errors.d.ts +4 -0
  46. package/package.json +4 -2
@@ -13,14 +13,29 @@
13
13
  */
14
14
  declare const FrameManager: {
15
15
  evtBuses: any[];
16
+ /**
17
+ * 每个总线对应的**宿主**(通常是 ICE 实例),与 `evtBuses` 一一对应。
18
+ * 宿主实现 `needsFrame()`:这一帧还需要继续跑吗?没有宿主(老注册方式)视为"永远需要"。
19
+ */
20
+ hosts: any[];
16
21
  stopped: boolean;
17
22
  frameCallback: () => void;
23
+ /** 是否还有总线需要帧(没有宿主 = 老注册方式,按"需要"处理,保持既有行为)。 */
24
+ needsFrame: () => boolean;
18
25
  start: () => void;
19
26
  stop: () => void;
27
+ /**
28
+ * 唤醒帧循环:空闲停帧之后,只要"又有人需要帧"(组件被置脏、动画被加进来)就调它。
29
+ * 循环已经在跑时是空操作(一次布尔判断),可以放心挂在 `dirty = true` 这类高频路径上。
30
+ */
31
+ wake: () => void;
20
32
  /**
21
33
  * @method registerEvtBus 注册事件总线
34
+ *
35
+ * @param evtBus 事件总线
36
+ * @param host 可选宿主(实现 `needsFrame()`);不传 = 该总线永远需要帧(兼容旧调用)
22
37
  */
23
- registerEvtBus: (evtBus: any) => void;
38
+ registerEvtBus: (evtBus: any, host?: any) => void;
24
39
  delEvtBus: (evtBus: any) => void;
25
40
  };
26
41
  export default FrameManager;
@@ -13,14 +13,29 @@
13
13
  */
14
14
  declare const FrameManager: {
15
15
  evtBuses: any[];
16
+ /**
17
+ * 每个总线对应的**宿主**(通常是 ICE 实例),与 `evtBuses` 一一对应。
18
+ * 宿主实现 `needsFrame()`:这一帧还需要继续跑吗?没有宿主(老注册方式)视为"永远需要"。
19
+ */
20
+ hosts: any[];
16
21
  stopped: boolean;
17
22
  frameCallback: () => void;
23
+ /** 是否还有总线需要帧(没有宿主 = 老注册方式,按"需要"处理,保持既有行为)。 */
24
+ needsFrame: () => boolean;
18
25
  start: () => void;
19
26
  stop: () => void;
27
+ /**
28
+ * 唤醒帧循环:空闲停帧之后,只要"又有人需要帧"(组件被置脏、动画被加进来)就调它。
29
+ * 循环已经在跑时是空操作(一次布尔判断),可以放心挂在 `dirty = true` 这类高频路径上。
30
+ */
31
+ wake: () => void;
20
32
  /**
21
33
  * @method registerEvtBus 注册事件总线
34
+ *
35
+ * @param evtBus 事件总线
36
+ * @param host 可选宿主(实现 `needsFrame()`);不传 = 该总线永远需要帧(兼容旧调用)
22
37
  */
23
- registerEvtBus: (evtBus: any) => void;
38
+ registerEvtBus: (evtBus: any, host?: any) => void;
24
39
  delEvtBus: (evtBus: any) => void;
25
40
  };
26
41
  export default FrameManager;
@@ -186,6 +186,32 @@ declare class ICE {
186
186
  private __findComponentInTree;
187
187
  set dirty(flag: boolean);
188
188
  get dirty(): boolean;
189
+ /**
190
+ * 「帧需求」:这一帧还需要继续跑吗(`FrameManager` 空闲停帧靠它判断)。
191
+ *
192
+ * 有脏要重绘、或有动画在推进 → 需要;否则可以停帧省电。
193
+ * 应用层若自己监听 `ICE_FRAME_EVENT` 做每帧计算(时钟、呼吸灯…),
194
+ * 调 `ice.setContinuousFrames(true)` 让本实例永远报"需要帧"。
195
+ */
196
+ needsFrame(): boolean;
197
+ /** 是否永远需要帧(见 {@link ICE.needsFrame});默认 false = 允许空闲停帧。 */
198
+ private __continuousFrames;
199
+ /**
200
+ * 让本实例永远需要帧(应用层自己按帧做计算时用;默认关闭 = 空闲停帧省电)。
201
+ * 打开后会立刻唤醒帧循环。
202
+ */
203
+ setContinuousFrames(enabled: boolean): this;
204
+ /** 当前是否"永远需要帧"(见 {@link ICE.setContinuousFrames})。 */
205
+ isContinuousFrames(): boolean;
206
+ /**
207
+ * 显式覆盖「减少动态效果」(默认取系统偏好 `prefers-reduced-motion`,见 `AnimationManager.reducedMotion`)。
208
+ *
209
+ * 为 true 时动画不播放过程、直接落终点(无障碍上最保守的语义)。应用层也可以把它接到自己的
210
+ * 偏好设置里(比如"我的设置 → 降低动效"),而不必改系统设置。
211
+ */
212
+ setReducedMotion(enabled: boolean): this;
213
+ /** 当前是否处于「减少动态效果」(见 {@link ICE.setReducedMotion})。 */
214
+ isReducedMotion(): boolean;
189
215
  /**
190
216
  * @method registerType 注册组件类型
191
217
  *
@@ -296,6 +322,73 @@ declare class ICE {
296
322
  * 只影响渲染结果与命中检测的坐标换算。视口变化会让渲染器回退一次全量重绘并重建快照。
297
323
  */
298
324
  setViewport(scale: number, tx?: number, ty?: number): this;
325
+ /**
326
+ * 视口跟随者(分层渲染用):本实例的 `setViewport` / `zoomAt` 会同步给它们。
327
+ * 用 Set 存放(同一 follower 只同步一次),`unlink` 时删除。
328
+ */
329
+ private __viewportFollowers;
330
+ /** 正在做视口同步:防止「A→B→A」无限回环(链式/双向连接时必需)。 */
331
+ private __syncingViewport;
332
+ /** 覆盖层是否处于「输入穿透」状态(`setInputPassthrough`)。 */
333
+ private __inputPassthrough;
334
+ /**
335
+ * 双向绑定两个实例的视口(分层渲染:静态层 + 动画层必须缩放/平移一致)。
336
+ *
337
+ * 任一侧的 `setViewport` / `zoomAt` / 应用层基于视口的交互都会同步到另一侧;
338
+ * 返回 `unlink()` 解绑(`destroy()` 会自动解绑本实例身上的连接)。
339
+ *
340
+ * @returns 解绑函数
341
+ */
342
+ static linkViewport(a: ICE, b: ICE): () => void;
343
+ /**
344
+ * 单向跟随:`this` 的视口跟随 `source`(source 变 → this 跟着变;this 自己变**不**回流)。
345
+ * 分层场景里通常两层用 {@link ICE.linkViewport} 双向绑定;单向跟随适合「缩略图跟随主视图」这类。
346
+ *
347
+ * @returns 解绑函数
348
+ */
349
+ followViewport(source: ICE): () => void;
350
+ /** 把本实例的视口推给所有跟随者(`setViewport` 内部调用;带防回环标记)。 */
351
+ private __notifyViewportFollowers;
352
+ /**
353
+ * 覆盖层「输入穿透」:把本层 canvas 设为 `pointer-events: none`,指针事件直接落到下层。
354
+ *
355
+ * 分层渲染(静态层 + 动画层)里,动画层往往只是展示、不需要交互 —— 若不给它穿透,
356
+ * 它会吃掉整屏指针事件,下层的选择/拖拽立刻失效。
357
+ *
358
+ * 关闭时把内联样式**还原为空**(而不是写 `auto`),避免覆盖应用自己的 CSS。
359
+ */
360
+ setInputPassthrough(enabled: boolean): this;
361
+ /** 当前是否处于输入穿透(见 {@link ICE.setInputPassthrough})。 */
362
+ isInputPassthrough(): boolean;
363
+ /**
364
+ * 把组件从本实例的树上**摘除但不销毁**(迁移 / 暂存专用)。
365
+ *
366
+ * 与 `removeChild()` 的唯一区别:**不调用 `destory()`** —— 组件自身的事件监听、内部子树、
367
+ * 动画配置都保持完好。跨实例迁移({@link ICE.moveComponentTo})与"重父级"都需要这个语义,
368
+ * 用 `removeChild` 会把组件连同子树一起清空(BPMN 池/泳道曾踩过这个坑)。
369
+ *
370
+ * @returns 是否真的摘除了(组件不属于本实例时返回 false)
371
+ */
372
+ detachChild(component: any, markDirty?: boolean): boolean;
373
+ /**
374
+ * 把组件(连同整棵子树)迁移到另一个 `ICE` 实例 —— 分层渲染里"拖拽期间把元素提升到动画层、
375
+ * 松手放回"这类交互的引擎原语(见 18 · 动画机制 §3.1)。
376
+ *
377
+ * 契约:
378
+ * - **保持世界坐标**:迁移前后组件 origin 的绝对坐标不变(两边的祖先矩阵/视口可能不同,
379
+ * 因此按矩阵换算,而不是照抄 `left/top`);
380
+ * - **不销毁**:组件自身的事件监听、内部子树与动画配置都保留(用 `detachChild` 而非 `removeChild`);
381
+ * - **子树整体切换**:后代的 `ice/ctx/evtBus` 递归指向目标实例(经 `addChild` 的 AFTER_ADD 链);
382
+ * - **目标实例接管**:动画注册迁到目标实例的 AnimationManager;选中态从本实例移除并落到目标实例;
383
+ * - 两个实例的**视口**是否同步由调用方决定(分层场景用 `ICE.linkViewport`)——
384
+ * 本方法只保证*世界坐标*不变,与视口无关。
385
+ *
386
+ * @param component 要迁移的组件(必须属于本实例)
387
+ * @param targetIce 目标实例
388
+ * @param targetParent 目标父级(可选;必须是目标实例树上的容器,缺省挂到目标实例根)
389
+ * @returns 是否迁移成功(参数非法 / 同实例 / 目标父级不属于目标实例 → false,且不改动任何状态)
390
+ */
391
+ moveComponentTo(component: any, targetIce: ICE, targetParent?: any): boolean;
299
392
  /** 屏幕坐标(canvas 像素)→ 世界坐标(受视口逆变换)。 */
300
393
  screenToWorld(sx: number, sy: number): [number, number];
301
394
  /** 世界坐标 → 屏幕坐标(canvas 像素)。 */
@@ -186,6 +186,32 @@ declare class ICE {
186
186
  private __findComponentInTree;
187
187
  set dirty(flag: boolean);
188
188
  get dirty(): boolean;
189
+ /**
190
+ * 「帧需求」:这一帧还需要继续跑吗(`FrameManager` 空闲停帧靠它判断)。
191
+ *
192
+ * 有脏要重绘、或有动画在推进 → 需要;否则可以停帧省电。
193
+ * 应用层若自己监听 `ICE_FRAME_EVENT` 做每帧计算(时钟、呼吸灯…),
194
+ * 调 `ice.setContinuousFrames(true)` 让本实例永远报"需要帧"。
195
+ */
196
+ needsFrame(): boolean;
197
+ /** 是否永远需要帧(见 {@link ICE.needsFrame});默认 false = 允许空闲停帧。 */
198
+ private __continuousFrames;
199
+ /**
200
+ * 让本实例永远需要帧(应用层自己按帧做计算时用;默认关闭 = 空闲停帧省电)。
201
+ * 打开后会立刻唤醒帧循环。
202
+ */
203
+ setContinuousFrames(enabled: boolean): this;
204
+ /** 当前是否"永远需要帧"(见 {@link ICE.setContinuousFrames})。 */
205
+ isContinuousFrames(): boolean;
206
+ /**
207
+ * 显式覆盖「减少动态效果」(默认取系统偏好 `prefers-reduced-motion`,见 `AnimationManager.reducedMotion`)。
208
+ *
209
+ * 为 true 时动画不播放过程、直接落终点(无障碍上最保守的语义)。应用层也可以把它接到自己的
210
+ * 偏好设置里(比如"我的设置 → 降低动效"),而不必改系统设置。
211
+ */
212
+ setReducedMotion(enabled: boolean): this;
213
+ /** 当前是否处于「减少动态效果」(见 {@link ICE.setReducedMotion})。 */
214
+ isReducedMotion(): boolean;
189
215
  /**
190
216
  * @method registerType 注册组件类型
191
217
  *
@@ -296,6 +322,73 @@ declare class ICE {
296
322
  * 只影响渲染结果与命中检测的坐标换算。视口变化会让渲染器回退一次全量重绘并重建快照。
297
323
  */
298
324
  setViewport(scale: number, tx?: number, ty?: number): this;
325
+ /**
326
+ * 视口跟随者(分层渲染用):本实例的 `setViewport` / `zoomAt` 会同步给它们。
327
+ * 用 Set 存放(同一 follower 只同步一次),`unlink` 时删除。
328
+ */
329
+ private __viewportFollowers;
330
+ /** 正在做视口同步:防止「A→B→A」无限回环(链式/双向连接时必需)。 */
331
+ private __syncingViewport;
332
+ /** 覆盖层是否处于「输入穿透」状态(`setInputPassthrough`)。 */
333
+ private __inputPassthrough;
334
+ /**
335
+ * 双向绑定两个实例的视口(分层渲染:静态层 + 动画层必须缩放/平移一致)。
336
+ *
337
+ * 任一侧的 `setViewport` / `zoomAt` / 应用层基于视口的交互都会同步到另一侧;
338
+ * 返回 `unlink()` 解绑(`destroy()` 会自动解绑本实例身上的连接)。
339
+ *
340
+ * @returns 解绑函数
341
+ */
342
+ static linkViewport(a: ICE, b: ICE): () => void;
343
+ /**
344
+ * 单向跟随:`this` 的视口跟随 `source`(source 变 → this 跟着变;this 自己变**不**回流)。
345
+ * 分层场景里通常两层用 {@link ICE.linkViewport} 双向绑定;单向跟随适合「缩略图跟随主视图」这类。
346
+ *
347
+ * @returns 解绑函数
348
+ */
349
+ followViewport(source: ICE): () => void;
350
+ /** 把本实例的视口推给所有跟随者(`setViewport` 内部调用;带防回环标记)。 */
351
+ private __notifyViewportFollowers;
352
+ /**
353
+ * 覆盖层「输入穿透」:把本层 canvas 设为 `pointer-events: none`,指针事件直接落到下层。
354
+ *
355
+ * 分层渲染(静态层 + 动画层)里,动画层往往只是展示、不需要交互 —— 若不给它穿透,
356
+ * 它会吃掉整屏指针事件,下层的选择/拖拽立刻失效。
357
+ *
358
+ * 关闭时把内联样式**还原为空**(而不是写 `auto`),避免覆盖应用自己的 CSS。
359
+ */
360
+ setInputPassthrough(enabled: boolean): this;
361
+ /** 当前是否处于输入穿透(见 {@link ICE.setInputPassthrough})。 */
362
+ isInputPassthrough(): boolean;
363
+ /**
364
+ * 把组件从本实例的树上**摘除但不销毁**(迁移 / 暂存专用)。
365
+ *
366
+ * 与 `removeChild()` 的唯一区别:**不调用 `destory()`** —— 组件自身的事件监听、内部子树、
367
+ * 动画配置都保持完好。跨实例迁移({@link ICE.moveComponentTo})与"重父级"都需要这个语义,
368
+ * 用 `removeChild` 会把组件连同子树一起清空(BPMN 池/泳道曾踩过这个坑)。
369
+ *
370
+ * @returns 是否真的摘除了(组件不属于本实例时返回 false)
371
+ */
372
+ detachChild(component: any, markDirty?: boolean): boolean;
373
+ /**
374
+ * 把组件(连同整棵子树)迁移到另一个 `ICE` 实例 —— 分层渲染里"拖拽期间把元素提升到动画层、
375
+ * 松手放回"这类交互的引擎原语(见 18 · 动画机制 §3.1)。
376
+ *
377
+ * 契约:
378
+ * - **保持世界坐标**:迁移前后组件 origin 的绝对坐标不变(两边的祖先矩阵/视口可能不同,
379
+ * 因此按矩阵换算,而不是照抄 `left/top`);
380
+ * - **不销毁**:组件自身的事件监听、内部子树与动画配置都保留(用 `detachChild` 而非 `removeChild`);
381
+ * - **子树整体切换**:后代的 `ice/ctx/evtBus` 递归指向目标实例(经 `addChild` 的 AFTER_ADD 链);
382
+ * - **目标实例接管**:动画注册迁到目标实例的 AnimationManager;选中态从本实例移除并落到目标实例;
383
+ * - 两个实例的**视口**是否同步由调用方决定(分层场景用 `ICE.linkViewport`)——
384
+ * 本方法只保证*世界坐标*不变,与视口无关。
385
+ *
386
+ * @param component 要迁移的组件(必须属于本实例)
387
+ * @param targetIce 目标实例
388
+ * @param targetParent 目标父级(可选;必须是目标实例树上的容器,缺省挂到目标实例根)
389
+ * @returns 是否迁移成功(参数非法 / 同实例 / 目标父级不属于目标实例 → false,且不改动任何状态)
390
+ */
391
+ moveComponentTo(component: any, targetIce: ICE, targetParent?: any): boolean;
299
392
  /** 屏幕坐标(canvas 像素)→ 世界坐标(受视口逆变换)。 */
300
393
  screenToWorld(sx: number, sy: number): [number, number];
301
394
  /** 世界坐标 → 屏幕坐标(canvas 像素)。 */
@@ -1,22 +1,38 @@
1
1
  import ICEComponent from '../graphic/ICEComponent.mjs';
2
2
  import ICE from '../ICE.mjs';
3
- /**
4
- * @class AnimationManager
5
- *
6
- * 动画管理器
7
- *
8
- * - 全局单例,一个 ICE 实例上只能有一个 AnimationManager 的实例。
9
- *
10
- * @singleton
11
- * @see ICE
12
- * @author 大漠穷秋<damoqiongqiu@126.com>
13
- */
3
+ import { ICEAnimationDiagnostic } from './validate-animations.mjs';
4
+ import AnimationTimeline from './AnimationTimeline.mjs';
14
5
  declare class AnimationManager {
15
6
  private animationMap;
16
7
  private ice;
17
8
  private paused;
18
9
  private pausedAt;
10
+ /**
11
+ * 飞行中的**平移**是否吸附到设备像素栅格(默认开)。
12
+ *
13
+ * 为什么需要它:离屏位图的纯平移复用(`ObjectCache.refreshPosition`)要求位移是**整数设备像素**,
14
+ * 否则会退化成每帧重建位图(实测 1000 个文本:可复用 2.5ms/帧 vs 重建 32~41ms/帧)。
15
+ * 吸附只作用于「纯平移 + 当前可离屏缓存」的组件,且**动画终点值永远精确写入**(配置 100.5 就落在 100.5)。
16
+ * 需要极致平滑的应用可以整体关掉(`ice.animationManager.snapToDevicePixel = false`),
17
+ * 或对单条动画写 `snapToDevicePixel: false`。
18
+ */
19
+ snapToDevicePixel: boolean;
20
+ /**
21
+ * 「减少动态效果」:用户系统偏好(`prefers-reduced-motion: reduce`)为真时,**动画直接落终点**
22
+ * (不播放位移/缩放过程),只保留最终状态 —— 这是无障碍上最保守、最可预期的语义。
23
+ *
24
+ * 默认在构造时读一次 `root.matchMedia`(无 DOM 运行时为 false);应用层可用
25
+ * `ice.setReducedMotion(true/false)` 显式覆盖(也能接自己的偏好设置)。
26
+ */
27
+ reducedMotion: boolean;
19
28
  private warned;
29
+ /** 运行期诊断(见 getDiagnostics):按 code|path 去重。 */
30
+ private __diagnostics;
31
+ private __diagnosticKeys;
32
+ /** 当前正在处理的属性路径(`__easingFn` 记诊断时要用,避免把 path 一层层传下去)。 */
33
+ private __currentPath;
34
+ /** 已告警过的回调异常(key|name),避免写错的回调每帧刷屏。 */
35
+ private __callbackWarned;
20
36
  constructor(ice: ICE);
21
37
  start(): this;
22
38
  stop(): this;
@@ -47,26 +63,84 @@ declare class AnimationManager {
47
63
  * 若按值判定,第一帧过冲就会被误判成结束、动画提前停在过冲点上。
48
64
  */
49
65
  private tween;
66
+ /**
67
+ * 动画写值通道:把本帧的新值提交给组件。
68
+ *
69
+ * 与直接 `setState` 的区别只有一点 —— **只在必要时才置 `paramsDirty`**:
70
+ * 本帧写出的键**全部**落在组件的「动画安全键」白名单里(纯绘制/变换)时跳过派生参数重算。
71
+ * 判定由组件自己给(`isAnimationSafeKey`),未声明白名单的组件/第三方组件一律走旧路径(每帧置脏)。
72
+ */
73
+ private __commitAnimationState;
74
+ /**
75
+ * 飞行中的**纯平移**吸附到设备像素栅格(`1 / 设备缩放` 的整数倍)。
76
+ *
77
+ * 三个前提缺一不可:① 实例开关打开且该动画没写 `snapToDevicePixel: false`;
78
+ * ② 键是纯平移(`left` / `top` / `transform.translate`);③ 该组件当前**可离屏缓存**
79
+ * (只有这时吸附才换得来位图复用;无渲染器的运行时/测试替身自动跳过)。
80
+ */
81
+ private __snapTranslation;
82
+ /** 渲染视口的缩放(= dpr × 视口 scale)——设备像素与世界单位的换算比例。 */
83
+ private __deviceScale;
84
+ /** 该组件当前是否走离屏缓存(只有它才谈得上"位图纯平移复用")。判定权在渲染器,这里只查询。 */
85
+ private __isBitmapReusable;
86
+ /** 数值按设备像素栅格吸附;数组逐元素(`transform.translate`)。 */
87
+ private __snapValue;
50
88
  /** 取进度 p 处的值:单段在 from→to 之间插值;关键帧按段插值(段内进度再经该段缓动)。 */
51
89
  private __sampleValue;
52
- /** 按进度 p 在 from/to 之间插值:数值直接线性,等长数字数组逐元素。 */
90
+ /**
91
+ * 按进度 p 在 from/to 之间插值:数值 / 等长数字数组 / **颜色** / 带单位数字串
92
+ * (判定与求值都在 `interpolators.ts`,与校验器同源)。
93
+ */
53
94
  private __interpolate;
54
- /** 补间取值是否合法:都是数字,或都是**等长**的数字数组。 */
95
+ /** 补间取值是否合法(数值 / 等长数字数组 / 颜色 / 同单位数字串)—— 判定见 interpolators。 */
55
96
  private __isTweenable;
56
- /** 取值种类:number | array(非空且全为数字) | null(不支持)。 */
97
+ /** 取值种类(转发到插值器,保持既有私有方法名可用)。 */
57
98
  private __valueKind;
58
99
  /** 归一化关键帧到 animation.__frames(只做一次):夹紧 offset、排序、校验各帧取值同型。 */
59
100
  private __normalizeKeyframes;
60
101
  /** 解析缓动名 → 归一化进度函数;未知名称回退 linear 并只提示一次。 */
61
102
  private __easingFn;
103
+ /** 动画方向(缺省 `'normal'`;非法的方向由 `validateAnimations` 在运行前拦住)。 */
104
+ private __directionOf;
105
+ /** 把缓动后的进度按方向映射:`reverse` 反转;`alternate` 在奇数轮反向(yoyo)。 */
106
+ private __applyDirection;
107
+ /** 本轮**起点**对应的进度(alternate 的奇数轮从另一端出发)。 */
108
+ private __roundStartProgress;
109
+ /** 本轮**终点**对应的进度。 */
110
+ private __roundEndProgress;
111
+ /** 回调上下文:给应用层足够信息做链式编排(不用再去读 state 反推进度)。 */
112
+ private __callbackContext;
113
+ /**
114
+ * 触发生命周期回调(`onStart` / `onUpdate` / `onRepeat` / `onComplete`)。
115
+ *
116
+ * 回调异常**绝不打断帧循环**(一个写错的回调不该让整个场景卡死):吞掉异常、记一条
117
+ * `ICE_ANIM_CALLBACK_ERROR` 诊断并 `console.warn` 一次。
118
+ */
119
+ private __invokeCallback;
62
120
  /** round: true 时对补间结果取整(数组逐元素)。 */
63
121
  private __roundIfNeeded;
64
122
  /** 供告警信息使用的取值描述。 */
65
123
  private __describe;
66
- /** 拒绝一个非法的动画配置:标记结束(不再每帧重试)并只提示一次。 */
124
+ /** 拒绝一个非法的动画配置:标记结束(不再每帧重试)、只提示一次,并记一条结构化诊断。 */
67
125
  private __reject;
68
- /** 同一个动画配置只告警一次,避免非法配置每帧刷屏。 */
126
+ /**
127
+ * 记录一条诊断(`getDiagnostics()` 可读)并只 `console.warn` 一次,避免非法配置每帧刷屏。
128
+ *
129
+ * 诊断的 code / severity / path 与 `validateAnimations()` 同源:**运行期才发现被跳过的配置,
130
+ * 与编译期校验报出的是同一组码**,Agent / 应用层不需要学两套。
131
+ */
69
132
  private __warnOnce;
133
+ /** 记一条结构化诊断(按 `code|path` 去重;只留文本,不含动画对象本身)。 */
134
+ private __recordDiagnostic;
135
+ /**
136
+ * 运行期累积的动画诊断(非法配置被跳过 / 缓动回退时会记录)。
137
+ *
138
+ * 与 `validateAnimations()` 的分工:那个是**运行前**的纯校验(Agent / DSL 用),
139
+ * 这个是**运行中**真实发生过的拒绝与回退(应用层可上报 / 开发期断言)。
140
+ */
141
+ getDiagnostics(): ICEAnimationDiagnostic[];
142
+ /** 清空运行期诊断(测试 / 重新加载场景时用)。 */
143
+ clearDiagnostics(): void;
70
144
  /**
71
145
  * 把动画配置里的 motion token 语义名解析成实际值(首次触发时执行,结果写回 animation 对象缓存):
72
146
  * - duration: 'fast' | 'normal' | 'slow' | 'slower' → 主题 motion.duration 里的 ms。
@@ -90,6 +164,33 @@ declare class AnimationManager {
90
164
  */
91
165
  resume(): void;
92
166
  isPaused(): boolean;
167
+ /**
168
+ * 是否有"还在推进"的动画(`ICE.needsFrame()` 用它决定要不要继续要帧)。
169
+ *
170
+ * 暂停时返回 false:暂停期间动画进度不推进,继续跑帧纯属空转(空闲停帧会因此把 rAF 停掉,
171
+ * 恢复时 `resume()` 会调整 startTime,进度从暂停处接上)。
172
+ */
173
+ hasActiveAnimations(): boolean;
174
+ /**
175
+ * 新建一条时间轴(编排多个组件/属性的时序)。见 `AnimationTimeline` 的文档与 18 §3。
176
+ *
177
+ * ```js
178
+ * ice.animationManager.timeline()
179
+ * .add(cardA, { left: { from: 0, to: 100, duration: 400 } }, { at: 0 })
180
+ * .add(cardB, { left: { from: 0, to: 100, duration: 400 } }, { at: '+=120' })
181
+ * .play();
182
+ * ```
183
+ */
184
+ timeline(): AnimationTimeline;
185
+ /**
186
+ * 重播某个组件的全部动画:清掉运行时状态(startTime / finished / 轮次 / 降频节拍)后重新纳入管理。
187
+ *
188
+ * 与 `add()` 的区别:`add()` 只是"确保它在管理器的列表里"(跑完的动画不会自己重播),
189
+ * `replay()` 才是"从头再放一遍"。时间轴的 `restart()` 就是用它实现的。
190
+ */
191
+ replay(component: any): this;
192
+ /** 这个组件的动画现在还在推进吗(跑完 / 被 stop / 已从管理器摘除 → false)。 */
193
+ isAnimating(component: any): boolean;
93
194
  add(component: ICEComponent): void;
94
195
  remove(el: any): void;
95
196
  }
@@ -1,22 +1,38 @@
1
1
  import ICEComponent from '../graphic/ICEComponent';
2
2
  import ICE from '../ICE';
3
- /**
4
- * @class AnimationManager
5
- *
6
- * 动画管理器
7
- *
8
- * - 全局单例,一个 ICE 实例上只能有一个 AnimationManager 的实例。
9
- *
10
- * @singleton
11
- * @see ICE
12
- * @author 大漠穷秋<damoqiongqiu@126.com>
13
- */
3
+ import { ICEAnimationDiagnostic } from './validate-animations';
4
+ import AnimationTimeline from './AnimationTimeline';
14
5
  declare class AnimationManager {
15
6
  private animationMap;
16
7
  private ice;
17
8
  private paused;
18
9
  private pausedAt;
10
+ /**
11
+ * 飞行中的**平移**是否吸附到设备像素栅格(默认开)。
12
+ *
13
+ * 为什么需要它:离屏位图的纯平移复用(`ObjectCache.refreshPosition`)要求位移是**整数设备像素**,
14
+ * 否则会退化成每帧重建位图(实测 1000 个文本:可复用 2.5ms/帧 vs 重建 32~41ms/帧)。
15
+ * 吸附只作用于「纯平移 + 当前可离屏缓存」的组件,且**动画终点值永远精确写入**(配置 100.5 就落在 100.5)。
16
+ * 需要极致平滑的应用可以整体关掉(`ice.animationManager.snapToDevicePixel = false`),
17
+ * 或对单条动画写 `snapToDevicePixel: false`。
18
+ */
19
+ snapToDevicePixel: boolean;
20
+ /**
21
+ * 「减少动态效果」:用户系统偏好(`prefers-reduced-motion: reduce`)为真时,**动画直接落终点**
22
+ * (不播放位移/缩放过程),只保留最终状态 —— 这是无障碍上最保守、最可预期的语义。
23
+ *
24
+ * 默认在构造时读一次 `root.matchMedia`(无 DOM 运行时为 false);应用层可用
25
+ * `ice.setReducedMotion(true/false)` 显式覆盖(也能接自己的偏好设置)。
26
+ */
27
+ reducedMotion: boolean;
19
28
  private warned;
29
+ /** 运行期诊断(见 getDiagnostics):按 code|path 去重。 */
30
+ private __diagnostics;
31
+ private __diagnosticKeys;
32
+ /** 当前正在处理的属性路径(`__easingFn` 记诊断时要用,避免把 path 一层层传下去)。 */
33
+ private __currentPath;
34
+ /** 已告警过的回调异常(key|name),避免写错的回调每帧刷屏。 */
35
+ private __callbackWarned;
20
36
  constructor(ice: ICE);
21
37
  start(): this;
22
38
  stop(): this;
@@ -47,26 +63,84 @@ declare class AnimationManager {
47
63
  * 若按值判定,第一帧过冲就会被误判成结束、动画提前停在过冲点上。
48
64
  */
49
65
  private tween;
66
+ /**
67
+ * 动画写值通道:把本帧的新值提交给组件。
68
+ *
69
+ * 与直接 `setState` 的区别只有一点 —— **只在必要时才置 `paramsDirty`**:
70
+ * 本帧写出的键**全部**落在组件的「动画安全键」白名单里(纯绘制/变换)时跳过派生参数重算。
71
+ * 判定由组件自己给(`isAnimationSafeKey`),未声明白名单的组件/第三方组件一律走旧路径(每帧置脏)。
72
+ */
73
+ private __commitAnimationState;
74
+ /**
75
+ * 飞行中的**纯平移**吸附到设备像素栅格(`1 / 设备缩放` 的整数倍)。
76
+ *
77
+ * 三个前提缺一不可:① 实例开关打开且该动画没写 `snapToDevicePixel: false`;
78
+ * ② 键是纯平移(`left` / `top` / `transform.translate`);③ 该组件当前**可离屏缓存**
79
+ * (只有这时吸附才换得来位图复用;无渲染器的运行时/测试替身自动跳过)。
80
+ */
81
+ private __snapTranslation;
82
+ /** 渲染视口的缩放(= dpr × 视口 scale)——设备像素与世界单位的换算比例。 */
83
+ private __deviceScale;
84
+ /** 该组件当前是否走离屏缓存(只有它才谈得上"位图纯平移复用")。判定权在渲染器,这里只查询。 */
85
+ private __isBitmapReusable;
86
+ /** 数值按设备像素栅格吸附;数组逐元素(`transform.translate`)。 */
87
+ private __snapValue;
50
88
  /** 取进度 p 处的值:单段在 from→to 之间插值;关键帧按段插值(段内进度再经该段缓动)。 */
51
89
  private __sampleValue;
52
- /** 按进度 p 在 from/to 之间插值:数值直接线性,等长数字数组逐元素。 */
90
+ /**
91
+ * 按进度 p 在 from/to 之间插值:数值 / 等长数字数组 / **颜色** / 带单位数字串
92
+ * (判定与求值都在 `interpolators.ts`,与校验器同源)。
93
+ */
53
94
  private __interpolate;
54
- /** 补间取值是否合法:都是数字,或都是**等长**的数字数组。 */
95
+ /** 补间取值是否合法(数值 / 等长数字数组 / 颜色 / 同单位数字串)—— 判定见 interpolators。 */
55
96
  private __isTweenable;
56
- /** 取值种类:number | array(非空且全为数字) | null(不支持)。 */
97
+ /** 取值种类(转发到插值器,保持既有私有方法名可用)。 */
57
98
  private __valueKind;
58
99
  /** 归一化关键帧到 animation.__frames(只做一次):夹紧 offset、排序、校验各帧取值同型。 */
59
100
  private __normalizeKeyframes;
60
101
  /** 解析缓动名 → 归一化进度函数;未知名称回退 linear 并只提示一次。 */
61
102
  private __easingFn;
103
+ /** 动画方向(缺省 `'normal'`;非法的方向由 `validateAnimations` 在运行前拦住)。 */
104
+ private __directionOf;
105
+ /** 把缓动后的进度按方向映射:`reverse` 反转;`alternate` 在奇数轮反向(yoyo)。 */
106
+ private __applyDirection;
107
+ /** 本轮**起点**对应的进度(alternate 的奇数轮从另一端出发)。 */
108
+ private __roundStartProgress;
109
+ /** 本轮**终点**对应的进度。 */
110
+ private __roundEndProgress;
111
+ /** 回调上下文:给应用层足够信息做链式编排(不用再去读 state 反推进度)。 */
112
+ private __callbackContext;
113
+ /**
114
+ * 触发生命周期回调(`onStart` / `onUpdate` / `onRepeat` / `onComplete`)。
115
+ *
116
+ * 回调异常**绝不打断帧循环**(一个写错的回调不该让整个场景卡死):吞掉异常、记一条
117
+ * `ICE_ANIM_CALLBACK_ERROR` 诊断并 `console.warn` 一次。
118
+ */
119
+ private __invokeCallback;
62
120
  /** round: true 时对补间结果取整(数组逐元素)。 */
63
121
  private __roundIfNeeded;
64
122
  /** 供告警信息使用的取值描述。 */
65
123
  private __describe;
66
- /** 拒绝一个非法的动画配置:标记结束(不再每帧重试)并只提示一次。 */
124
+ /** 拒绝一个非法的动画配置:标记结束(不再每帧重试)、只提示一次,并记一条结构化诊断。 */
67
125
  private __reject;
68
- /** 同一个动画配置只告警一次,避免非法配置每帧刷屏。 */
126
+ /**
127
+ * 记录一条诊断(`getDiagnostics()` 可读)并只 `console.warn` 一次,避免非法配置每帧刷屏。
128
+ *
129
+ * 诊断的 code / severity / path 与 `validateAnimations()` 同源:**运行期才发现被跳过的配置,
130
+ * 与编译期校验报出的是同一组码**,Agent / 应用层不需要学两套。
131
+ */
69
132
  private __warnOnce;
133
+ /** 记一条结构化诊断(按 `code|path` 去重;只留文本,不含动画对象本身)。 */
134
+ private __recordDiagnostic;
135
+ /**
136
+ * 运行期累积的动画诊断(非法配置被跳过 / 缓动回退时会记录)。
137
+ *
138
+ * 与 `validateAnimations()` 的分工:那个是**运行前**的纯校验(Agent / DSL 用),
139
+ * 这个是**运行中**真实发生过的拒绝与回退(应用层可上报 / 开发期断言)。
140
+ */
141
+ getDiagnostics(): ICEAnimationDiagnostic[];
142
+ /** 清空运行期诊断(测试 / 重新加载场景时用)。 */
143
+ clearDiagnostics(): void;
70
144
  /**
71
145
  * 把动画配置里的 motion token 语义名解析成实际值(首次触发时执行,结果写回 animation 对象缓存):
72
146
  * - duration: 'fast' | 'normal' | 'slow' | 'slower' → 主题 motion.duration 里的 ms。
@@ -90,6 +164,33 @@ declare class AnimationManager {
90
164
  */
91
165
  resume(): void;
92
166
  isPaused(): boolean;
167
+ /**
168
+ * 是否有"还在推进"的动画(`ICE.needsFrame()` 用它决定要不要继续要帧)。
169
+ *
170
+ * 暂停时返回 false:暂停期间动画进度不推进,继续跑帧纯属空转(空闲停帧会因此把 rAF 停掉,
171
+ * 恢复时 `resume()` 会调整 startTime,进度从暂停处接上)。
172
+ */
173
+ hasActiveAnimations(): boolean;
174
+ /**
175
+ * 新建一条时间轴(编排多个组件/属性的时序)。见 `AnimationTimeline` 的文档与 18 §3。
176
+ *
177
+ * ```js
178
+ * ice.animationManager.timeline()
179
+ * .add(cardA, { left: { from: 0, to: 100, duration: 400 } }, { at: 0 })
180
+ * .add(cardB, { left: { from: 0, to: 100, duration: 400 } }, { at: '+=120' })
181
+ * .play();
182
+ * ```
183
+ */
184
+ timeline(): AnimationTimeline;
185
+ /**
186
+ * 重播某个组件的全部动画:清掉运行时状态(startTime / finished / 轮次 / 降频节拍)后重新纳入管理。
187
+ *
188
+ * 与 `add()` 的区别:`add()` 只是"确保它在管理器的列表里"(跑完的动画不会自己重播),
189
+ * `replay()` 才是"从头再放一遍"。时间轴的 `restart()` 就是用它实现的。
190
+ */
191
+ replay(component: any): this;
192
+ /** 这个组件的动画现在还在推进吗(跑完 / 被 stop / 已从管理器摘除 → false)。 */
193
+ isAnimating(component: any): boolean;
93
194
  add(component: ICEComponent): void;
94
195
  remove(el: any): void;
95
196
  }