ice-render 2.1.1 → 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 (53) hide show
  1. package/README.md +22 -9
  2. package/dist/index.cjs +3 -3
  3. package/dist/index.mjs +3 -3
  4. package/dist/index.umd.js +3 -3
  5. package/dist/types/FrameManager.d.mts +16 -1
  6. package/dist/types/FrameManager.d.ts +16 -1
  7. package/dist/types/ICE.d.mts +103 -0
  8. package/dist/types/ICE.d.ts +103 -0
  9. package/dist/types/animation/AnimationManager.d.mts +117 -16
  10. package/dist/types/animation/AnimationManager.d.ts +117 -16
  11. package/dist/types/animation/AnimationTimeline.d.mts +77 -0
  12. package/dist/types/animation/AnimationTimeline.d.ts +77 -0
  13. package/dist/types/animation/easing-registry.d.mts +37 -0
  14. package/dist/types/animation/easing-registry.d.ts +37 -0
  15. package/dist/types/animation/interpolators.d.mts +34 -0
  16. package/dist/types/animation/interpolators.d.ts +34 -0
  17. package/dist/types/animation/validate-animations.d.mts +55 -0
  18. package/dist/types/animation/validate-animations.d.ts +55 -0
  19. package/dist/types/event/DOMEventDispatcher.d.mts +8 -0
  20. package/dist/types/event/DOMEventDispatcher.d.ts +8 -0
  21. package/dist/types/export/SvgExporter.d.mts +9 -2
  22. package/dist/types/export/SvgExporter.d.ts +9 -2
  23. package/dist/types/export/compose-layers.d.mts +30 -0
  24. package/dist/types/export/compose-layers.d.ts +30 -0
  25. package/dist/types/graphic/ICEComponent.d.mts +37 -1
  26. package/dist/types/graphic/ICEComponent.d.ts +37 -1
  27. package/dist/types/graphic/container/ICEGroup.d.mts +3 -1
  28. package/dist/types/graphic/container/ICEGroup.d.ts +3 -1
  29. package/dist/types/graphic/link/ICEPolyLine.d.mts +9 -1
  30. package/dist/types/graphic/link/ICEPolyLine.d.ts +9 -1
  31. package/dist/types/graphic/shape/ICEEllipse.d.mts +3 -1
  32. package/dist/types/graphic/shape/ICEEllipse.d.ts +3 -1
  33. package/dist/types/graphic/shape/ICEIsogon.d.mts +3 -1
  34. package/dist/types/graphic/shape/ICEIsogon.d.ts +3 -1
  35. package/dist/types/graphic/shape/ICERose.d.mts +3 -1
  36. package/dist/types/graphic/shape/ICERose.d.ts +3 -1
  37. package/dist/types/graphic/shape/ICEStar.d.mts +3 -1
  38. package/dist/types/graphic/shape/ICEStar.d.ts +3 -1
  39. package/dist/types/graphic/text/ICEText.d.mts +154 -2
  40. package/dist/types/graphic/text/ICEText.d.ts +154 -2
  41. package/dist/types/graphic/text/text-direction.d.mts +28 -0
  42. package/dist/types/graphic/text/text-direction.d.ts +28 -0
  43. package/dist/types/graphic/text/text-style.d.mts +25 -0
  44. package/dist/types/graphic/text/text-style.d.ts +25 -0
  45. package/dist/types/graphic/text/text-wrap.d.mts +26 -0
  46. package/dist/types/graphic/text/text-wrap.d.ts +26 -0
  47. package/dist/types/index.d.mts +11 -0
  48. package/dist/types/index.d.ts +11 -0
  49. package/dist/types/util/data-util.d.mts +10 -0
  50. package/dist/types/util/data-util.d.ts +10 -0
  51. package/dist/types/util/errors.d.mts +72 -0
  52. package/dist/types/util/errors.d.ts +72 -0
  53. package/package.json +5 -3
@@ -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
  }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * 动画时间轴:把「谁、在什么时刻、动什么」组织成一条可播放/暂停/重播的轨道集合。
3
+ *
4
+ * 为什么要它:`props.animations` 是**每条属性各自为政**的(各自 duration / easing / 自己的 startTime),
5
+ * 表达"卡片 A 入场 → 200ms 后卡片 B 跟上 → 一起停住"这类编排时,应用只能手算 delay —— 这正是
6
+ * Agent / 低代码最不擅长的部分(算时间)。
7
+ *
8
+ * 设计取向(与 18 §3.1 的"不引入新时间模型"一致):时间轴**不是新的求值器**,它只是调度器 ——
9
+ * `play()` 时把每条轨道折算成 `delay` 写进组件的 `props.animations`,剩下的推进仍由 `AnimationManager`
10
+ * 按帧/按时间完成。因此它与既有的缓动、关键帧、量化、缓存复用、空闲停帧完全兼容。
11
+ *
12
+ * ```js
13
+ * const timeline = ice.animationManager.timeline();
14
+ * timeline
15
+ * .add(cardA, { 'transform.translate': { from: [-40, 0], to: [0, 0], duration: 400 } }, { at: 0 })
16
+ * .add(cardB, { 'transform.translate': { from: [-40, 0], to: [0, 0], duration: 400 } }, { at: '+=120' })
17
+ * .stagger(rows, { 'style.globalAlpha': { from: 0, to: 1, duration: 240 } }, { each: 60, at: 300 });
18
+ * timeline.play();
19
+ * // 点击重播:
20
+ * timeline.restart();
21
+ * ```
22
+ */
23
+ import AnimationManager from './AnimationManager.mjs';
24
+ export type TimelineOptions = {
25
+ /** 起始时刻:绝对毫秒,或 `'+=N'`(相对**上一条轨道**的时间) */
26
+ at?: number | string;
27
+ /** `stagger` 用:每条之间的间隔毫秒 */
28
+ each?: number;
29
+ };
30
+ export default class AnimationTimeline {
31
+ private manager;
32
+ private tracks;
33
+ private cursor;
34
+ private playing;
35
+ private paused;
36
+ private pending;
37
+ private finishedPromise;
38
+ private resolveFinished;
39
+ constructor(manager: AnimationManager);
40
+ /** 时间轴总时长(最后一条轨道的起始 + 它自己的时长;用于排版/展示)。 */
41
+ get duration(): number;
42
+ isPlaying(): boolean;
43
+ /** 全部轨道跑完时 resolve(`play()` 之前拿到的是同一个 Promise)。 */
44
+ get finished(): Promise<void>;
45
+ /**
46
+ * 加一条轨道:把 `config`(`{属性路径: 动画配置}`)合并进组件的 `props.animations`,
47
+ * 并记录这条轨道的起始时刻。
48
+ */
49
+ add(component: any, config: Record<string, any>, options?: TimelineOptions): this;
50
+ /**
51
+ * 错峰:同一份配置按 `each` 毫秒依次加到一组组件上("卡片依次滑入")。
52
+ * 返回 this 以便链式继续 `add`。
53
+ */
54
+ stagger(components: any[], config: Record<string, any>, options?: TimelineOptions): this;
55
+ /**
56
+ * 播放:把每条轨道注册进 `AnimationManager`(按 `at` 折算 delay)并唤醒帧循环。
57
+ * 重复调用是幂等的(已在播放则什么都不做)。
58
+ */
59
+ play(): this;
60
+ /** 暂停整条时间轴(等价于 `AnimationManager.pause()`)。 */
61
+ pause(): this;
62
+ /** 继续(从暂停处接上)。 */
63
+ resume(): this;
64
+ /**
65
+ * 停止:把本时间轴注册的动画从管理器里摘掉(还原 onComplete 包装),组件停在当前值。
66
+ */
67
+ stop(): this;
68
+ /**
69
+ * 重播:重置每条动画的运行时状态(startTime / finished / 轮次 / 降频节拍)后重新播放。
70
+ * 这是"点击重播"的直接入口。
71
+ */
72
+ restart(): this;
73
+ /** `'+=N'` → 游标 + N;数字 → 原值;非法 → 当前游标。 */
74
+ private __resolveAt;
75
+ private __onTrackKeyDone;
76
+ private __resolveIfDone;
77
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * 动画时间轴:把「谁、在什么时刻、动什么」组织成一条可播放/暂停/重播的轨道集合。
3
+ *
4
+ * 为什么要它:`props.animations` 是**每条属性各自为政**的(各自 duration / easing / 自己的 startTime),
5
+ * 表达"卡片 A 入场 → 200ms 后卡片 B 跟上 → 一起停住"这类编排时,应用只能手算 delay —— 这正是
6
+ * Agent / 低代码最不擅长的部分(算时间)。
7
+ *
8
+ * 设计取向(与 18 §3.1 的"不引入新时间模型"一致):时间轴**不是新的求值器**,它只是调度器 ——
9
+ * `play()` 时把每条轨道折算成 `delay` 写进组件的 `props.animations`,剩下的推进仍由 `AnimationManager`
10
+ * 按帧/按时间完成。因此它与既有的缓动、关键帧、量化、缓存复用、空闲停帧完全兼容。
11
+ *
12
+ * ```js
13
+ * const timeline = ice.animationManager.timeline();
14
+ * timeline
15
+ * .add(cardA, { 'transform.translate': { from: [-40, 0], to: [0, 0], duration: 400 } }, { at: 0 })
16
+ * .add(cardB, { 'transform.translate': { from: [-40, 0], to: [0, 0], duration: 400 } }, { at: '+=120' })
17
+ * .stagger(rows, { 'style.globalAlpha': { from: 0, to: 1, duration: 240 } }, { each: 60, at: 300 });
18
+ * timeline.play();
19
+ * // 点击重播:
20
+ * timeline.restart();
21
+ * ```
22
+ */
23
+ import AnimationManager from './AnimationManager';
24
+ export type TimelineOptions = {
25
+ /** 起始时刻:绝对毫秒,或 `'+=N'`(相对**上一条轨道**的时间) */
26
+ at?: number | string;
27
+ /** `stagger` 用:每条之间的间隔毫秒 */
28
+ each?: number;
29
+ };
30
+ export default class AnimationTimeline {
31
+ private manager;
32
+ private tracks;
33
+ private cursor;
34
+ private playing;
35
+ private paused;
36
+ private pending;
37
+ private finishedPromise;
38
+ private resolveFinished;
39
+ constructor(manager: AnimationManager);
40
+ /** 时间轴总时长(最后一条轨道的起始 + 它自己的时长;用于排版/展示)。 */
41
+ get duration(): number;
42
+ isPlaying(): boolean;
43
+ /** 全部轨道跑完时 resolve(`play()` 之前拿到的是同一个 Promise)。 */
44
+ get finished(): Promise<void>;
45
+ /**
46
+ * 加一条轨道:把 `config`(`{属性路径: 动画配置}`)合并进组件的 `props.animations`,
47
+ * 并记录这条轨道的起始时刻。
48
+ */
49
+ add(component: any, config: Record<string, any>, options?: TimelineOptions): this;
50
+ /**
51
+ * 错峰:同一份配置按 `each` 毫秒依次加到一组组件上("卡片依次滑入")。
52
+ * 返回 this 以便链式继续 `add`。
53
+ */
54
+ stagger(components: any[], config: Record<string, any>, options?: TimelineOptions): this;
55
+ /**
56
+ * 播放:把每条轨道注册进 `AnimationManager`(按 `at` 折算 delay)并唤醒帧循环。
57
+ * 重复调用是幂等的(已在播放则什么都不做)。
58
+ */
59
+ play(): this;
60
+ /** 暂停整条时间轴(等价于 `AnimationManager.pause()`)。 */
61
+ pause(): this;
62
+ /** 继续(从暂停处接上)。 */
63
+ resume(): this;
64
+ /**
65
+ * 停止:把本时间轴注册的动画从管理器里摘掉(还原 onComplete 包装),组件停在当前值。
66
+ */
67
+ stop(): this;
68
+ /**
69
+ * 重播:重置每条动画的运行时状态(startTime / finished / 轮次 / 降频节拍)后重新播放。
70
+ * 这是"点击重播"的直接入口。
71
+ */
72
+ restart(): this;
73
+ /** `'+=N'` → 游标 + N;数字 → 原值;非法 → 当前游标。 */
74
+ private __resolveAt;
75
+ private __onTrackKeyDone;
76
+ private __resolveIfDone;
77
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * 缓动注册表:内置缓动 + **应用层自定义缓动**。
3
+ *
4
+ * 为什么要开放这一层(用户目标②「应用层有机会自定义动画」):
5
+ * 内置只有十几个 easing,产品想表达自己的手感(品牌回弹、机械式阶跃、物理曲线)时,
6
+ * 最自然的方式是**给一个函数**,而不是"只能从枚举里挑"。
7
+ *
8
+ * 用法:
9
+ * ```js
10
+ * ICE.registerEasing('brandSpring', (t) => 1 - Math.pow(1 - t, 3) * Math.cos(t * 6));
11
+ * new ICERect({ animations: { left: { from: 0, to: 100, duration: 400, easing: 'brandSpring' } } });
12
+ * // 也可以直接传函数(只对这一条动画生效,不用注册):
13
+ * animations: { left: { from: 0, to: 100, duration: 400, easing: (t) => t * t } }
14
+ * ```
15
+ *
16
+ * 注册的**不覆盖内置**(内置是引擎契约的一部分,被偷偷替换会让同一份文档在不同应用里表现不同);
17
+ * 重名注册会明确抛错(`ICE_ANIM_EASING_NAME_CONFLICT`)。
18
+ */
19
+ import { EasingProgress } from './Easing.mjs';
20
+ /**
21
+ * 注册一个自定义缓动。
22
+ *
23
+ * @param name 缓动名(非空字符串;不能与**内置**缓动重名,也不能重复注册同一个名字)
24
+ * @param fn 归一化进度函数 `(t) => number`
25
+ */
26
+ export declare function registerEasing(name: string, fn: EasingProgress): void;
27
+ /** 注销自定义缓动(内置缓动不可注销);返回是否真的移除了。 */
28
+ export declare function unregisterEasing(name: string): boolean;
29
+ /**
30
+ * 解析缓动:**函数**原样返回(临时自定义)、字符串查内置 → 自定义;没有就返回 null
31
+ * (调用方决定是回退 linear 还是报错)。
32
+ */
33
+ export declare function resolveEasing(nameOrFn: any): EasingProgress | null;
34
+ /** 当前可用的缓动名(内置 + 自定义),用于校验提示与文档。 */
35
+ export declare function easingNames(): string[];
36
+ /** 只列出应用层注册的缓动名(调试 / 测试用)。 */
37
+ export declare function customEasingNames(): string[];
@@ -0,0 +1,37 @@
1
+ /**
2
+ * 缓动注册表:内置缓动 + **应用层自定义缓动**。
3
+ *
4
+ * 为什么要开放这一层(用户目标②「应用层有机会自定义动画」):
5
+ * 内置只有十几个 easing,产品想表达自己的手感(品牌回弹、机械式阶跃、物理曲线)时,
6
+ * 最自然的方式是**给一个函数**,而不是"只能从枚举里挑"。
7
+ *
8
+ * 用法:
9
+ * ```js
10
+ * ICE.registerEasing('brandSpring', (t) => 1 - Math.pow(1 - t, 3) * Math.cos(t * 6));
11
+ * new ICERect({ animations: { left: { from: 0, to: 100, duration: 400, easing: 'brandSpring' } } });
12
+ * // 也可以直接传函数(只对这一条动画生效,不用注册):
13
+ * animations: { left: { from: 0, to: 100, duration: 400, easing: (t) => t * t } }
14
+ * ```
15
+ *
16
+ * 注册的**不覆盖内置**(内置是引擎契约的一部分,被偷偷替换会让同一份文档在不同应用里表现不同);
17
+ * 重名注册会明确抛错(`ICE_ANIM_EASING_NAME_CONFLICT`)。
18
+ */
19
+ import { EasingProgress } from './Easing';
20
+ /**
21
+ * 注册一个自定义缓动。
22
+ *
23
+ * @param name 缓动名(非空字符串;不能与**内置**缓动重名,也不能重复注册同一个名字)
24
+ * @param fn 归一化进度函数 `(t) => number`
25
+ */
26
+ export declare function registerEasing(name: string, fn: EasingProgress): void;
27
+ /** 注销自定义缓动(内置缓动不可注销);返回是否真的移除了。 */
28
+ export declare function unregisterEasing(name: string): boolean;
29
+ /**
30
+ * 解析缓动:**函数**原样返回(临时自定义)、字符串查内置 → 自定义;没有就返回 null
31
+ * (调用方决定是回退 linear 还是报错)。
32
+ */
33
+ export declare function resolveEasing(nameOrFn: any): EasingProgress | null;
34
+ /** 当前可用的缓动名(内置 + 自定义),用于校验提示与文档。 */
35
+ export declare function easingNames(): string[];
36
+ /** 只列出应用层注册的缓动名(调试 / 测试用)。 */
37
+ export declare function customEasingNames(): string[];
@@ -0,0 +1,34 @@
1
+ /**
2
+ * 可插值取值的**唯一判定与求值入口**:数值 / 等长数字数组 / **颜色** / 带单位数字串。
3
+ *
4
+ * 为什么要有颜色:`fillStyle` 从红渐变到蓝是界面里最常见的动效之一,而 canvas 只会画字符串。
5
+ * 早先引擎一律拒绝非数值取值(避免写出 NaN 破坏矩阵),代价就是"颜色动画做不了"。
6
+ * 这里把"哪些类型可插值、怎么插值"收敛到一个模块:校验器(`validateAnimations`)与
7
+ * 运行时(`AnimationManager`)都用它,避免"校验通过但跑起来算错"。
8
+ *
9
+ * 支持的类型:
10
+ * - `number`:线性插值;
11
+ * - `number[]`(等长):逐元素线性插值(矩阵/位移/缩放都走这条);
12
+ * - 颜色字符串:`#rgb` / `#rgba` / `#rrggbb` / `#rrggbbaa` / `rgb()` / `rgba()`(两端必须都是颜色);
13
+ * - 带单位数字串:`'12px'` / `'1.5em'` 等(**单位必须一致**,否则视为不可插值)。
14
+ *
15
+ * 注意:插值在 **sRGB 数值空间**做(与 CSS `transition` 的默认行为一致),不做 OKLab 之类感知均匀空间。
16
+ */
17
+ export type AnimationValueKind = 'number' | 'array' | 'color' | 'length' | null;
18
+ /** 解析颜色字符串为 `[r, g, b, a]`(a ∈ [0,1]);不是颜色返回 null。 */
19
+ export declare function parseColor(value: any): [number, number, number, number] | null;
20
+ /** 把 `[r,g,b,a]` 写回 `rgba(...)`(alpha 为 1 时给 `rgb(...)`,与输入形态无关,输出统一)。 */
21
+ export declare function formatColor(rgba: [number, number, number, number]): string;
22
+ /** 解析带单位数字串(`'12px'` / `'1.5em'`)→ `{ number, unit }`;不是就返回 null。 */
23
+ export declare function parseNumberWithUnit(value: any): {
24
+ number: number;
25
+ unit: string;
26
+ } | null;
27
+ /** 取值种类(`null` = 不可插值)。 */
28
+ export declare function classifyValue(value: any): AnimationValueKind;
29
+ /** 两个取值能否互相插值(同型;数组等长;带单位数字串单位一致)。 */
30
+ export declare function isInterpolatable(from: any, to: any): boolean;
31
+ /**
32
+ * 按进度 `p` 求值。不可插值时返回 `from`(调用方负责在校验阶段拦住非法配置)。
33
+ */
34
+ export declare function interpolateValue(from: any, to: any, p: number): any;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * 可插值取值的**唯一判定与求值入口**:数值 / 等长数字数组 / **颜色** / 带单位数字串。
3
+ *
4
+ * 为什么要有颜色:`fillStyle` 从红渐变到蓝是界面里最常见的动效之一,而 canvas 只会画字符串。
5
+ * 早先引擎一律拒绝非数值取值(避免写出 NaN 破坏矩阵),代价就是"颜色动画做不了"。
6
+ * 这里把"哪些类型可插值、怎么插值"收敛到一个模块:校验器(`validateAnimations`)与
7
+ * 运行时(`AnimationManager`)都用它,避免"校验通过但跑起来算错"。
8
+ *
9
+ * 支持的类型:
10
+ * - `number`:线性插值;
11
+ * - `number[]`(等长):逐元素线性插值(矩阵/位移/缩放都走这条);
12
+ * - 颜色字符串:`#rgb` / `#rgba` / `#rrggbb` / `#rrggbbaa` / `rgb()` / `rgba()`(两端必须都是颜色);
13
+ * - 带单位数字串:`'12px'` / `'1.5em'` 等(**单位必须一致**,否则视为不可插值)。
14
+ *
15
+ * 注意:插值在 **sRGB 数值空间**做(与 CSS `transition` 的默认行为一致),不做 OKLab 之类感知均匀空间。
16
+ */
17
+ export type AnimationValueKind = 'number' | 'array' | 'color' | 'length' | null;
18
+ /** 解析颜色字符串为 `[r, g, b, a]`(a ∈ [0,1]);不是颜色返回 null。 */
19
+ export declare function parseColor(value: any): [number, number, number, number] | null;
20
+ /** 把 `[r,g,b,a]` 写回 `rgba(...)`(alpha 为 1 时给 `rgb(...)`,与输入形态无关,输出统一)。 */
21
+ export declare function formatColor(rgba: [number, number, number, number]): string;
22
+ /** 解析带单位数字串(`'12px'` / `'1.5em'`)→ `{ number, unit }`;不是就返回 null。 */
23
+ export declare function parseNumberWithUnit(value: any): {
24
+ number: number;
25
+ unit: string;
26
+ } | null;
27
+ /** 取值种类(`null` = 不可插值)。 */
28
+ export declare function classifyValue(value: any): AnimationValueKind;
29
+ /** 两个取值能否互相插值(同型;数组等长;带单位数字串单位一致)。 */
30
+ export declare function isInterpolatable(from: any, to: any): boolean;
31
+ /**
32
+ * 按进度 `p` 求值。不可插值时返回 `from`(调用方负责在校验阶段拦住非法配置)。
33
+ */
34
+ export declare function interpolateValue(from: any, to: any, p: number): any;
@@ -0,0 +1,55 @@
1
+ import { AnimationValueKind } from './interpolators.mjs';
2
+ export declare const ICE_ANIMATION_DIAGNOSTIC_CODES: {
3
+ /** 键不是合法的属性路径(空串 / 非字符串)。 */
4
+ readonly KEY_INVALID: "ICE_ANIM_KEY_INVALID";
5
+ /** `duration` 缺失 / 非正数 / 超上限 / 不是已注册的 motion token。 */
6
+ readonly DURATION_INVALID: "ICE_ANIM_DURATION_INVALID";
7
+ /** `delay` 不是非负有限数。 */
8
+ readonly DELAY_INVALID: "ICE_ANIM_DELAY_INVALID";
9
+ /** `iterationCount` 不是 ≥1 的整数。 */
10
+ readonly ITERATION_INVALID: "ICE_ANIM_ITERATION_INVALID";
11
+ /** 缓动名既不是内置缓动,也不是主题 motion.easing 里的语义名(运行时会回退 linear)。 */
12
+ readonly EASING_UNKNOWN: "ICE_ANIM_EASING_UNKNOWN";
13
+ /** `from` / `to` 不是可插值的同型数值(数值或等长数字数组)。 */
14
+ readonly VALUE_NOT_INTERPOLATABLE: "ICE_ANIM_VALUE_NOT_INTERPOLATABLE";
15
+ /** `keyframes` 形状非法(少于 2 帧 / offset 非有限数 / 各帧取值不同型或不等长)。 */
16
+ readonly KEYFRAMES_INVALID: "ICE_ANIM_KEYFRAMES_INVALID";
17
+ /** 无限循环(`loop: true`):Agent 生成的界面里要谨慎,建议给 `iterationCount` 或可中断入口。 */
18
+ readonly INFINITE_LOOP: "ICE_ANIM_INFINITE_LOOP";
19
+ /** 该属性会改变派生参数(尺寸 / 点集 / 文本量测)→ 每帧都要重量测,性能上不划算。 */
20
+ readonly KEY_AFFECTS_MEASUREMENT: "ICE_ANIM_KEY_AFFECTS_MEASUREMENT";
21
+ /** 运行期告知:用户开启了「减少动态效果」,这条动画被折叠成终态(未播放过程)。 */
22
+ readonly REDUCED_MOTION: "ICE_ANIM_REDUCED_MOTION";
23
+ /** `direction` 不是 `normal` / `reverse` / `alternate`。 */
24
+ readonly DIRECTION_INVALID: "ICE_ANIM_DIRECTION_INVALID";
25
+ /** 生命周期回调(onStart/onUpdate/onRepeat/onComplete)抛了异常(已忽略,不打断动画)。 */
26
+ readonly CALLBACK_ERROR: "ICE_ANIM_CALLBACK_ERROR";
27
+ };
28
+ export type ICEAnimationDiagnosticCode = (typeof ICE_ANIMATION_DIAGNOSTIC_CODES)[keyof typeof ICE_ANIMATION_DIAGNOSTIC_CODES];
29
+ export interface ICEAnimationDiagnostic {
30
+ severity: 'error' | 'warning';
31
+ code: ICEAnimationDiagnosticCode;
32
+ message: string;
33
+ /** 出问题的位置:属性动画键(如 `transform.translate`),便于 Agent 精确改一处。 */
34
+ path: string;
35
+ }
36
+ export type ValidateAnimationsOptions = {
37
+ /** 主题 motion token(缺省用内置默认主题)——`duration: 'normal'` / `easing: 'out'` 这类语义名靠它解析。 */
38
+ motionTokens?: {
39
+ duration?: Record<string, any>;
40
+ easing?: Record<string, any>;
41
+ } | null;
42
+ /** 判断某个属性键是否"动画安全"(不影响派生参数);给了就会对不安全的键出性能警告。 */
43
+ isSafeKey?: ((path: string) => boolean) | null;
44
+ /** 单条动画的时长上限(ms),默认 60s;Agent 生成的长动画多半是写错了。 */
45
+ maxDuration?: number;
46
+ };
47
+ /**
48
+ * 取值种类:`number` / `array`(等长数字数组)/ `color` / `length`(带单位数字串)/ `null`(不可插值)。
49
+ * 判定与求值都转发到 `interpolators.ts`(运行时同源,避免"校验通过但跑起来算错")。
50
+ */
51
+ export declare function classifyAnimationValue(value: any): AnimationValueKind;
52
+ /** 两个取值能否互相插值(同型;数组等长;颜色互插;带单位数字串单位一致)。 */
53
+ export declare function isInterpolatablePair(from: any, to: any): boolean;
54
+ /** 校验一份 `animations` 配置(键 → 单段/关键帧配置),返回按 `path` 排序的诊断列表(无问题 = 空数组)。 */
55
+ export declare function validateAnimations(animations: any, options?: ValidateAnimationsOptions): ICEAnimationDiagnostic[];
@@ -0,0 +1,55 @@
1
+ import { AnimationValueKind } from './interpolators';
2
+ export declare const ICE_ANIMATION_DIAGNOSTIC_CODES: {
3
+ /** 键不是合法的属性路径(空串 / 非字符串)。 */
4
+ readonly KEY_INVALID: "ICE_ANIM_KEY_INVALID";
5
+ /** `duration` 缺失 / 非正数 / 超上限 / 不是已注册的 motion token。 */
6
+ readonly DURATION_INVALID: "ICE_ANIM_DURATION_INVALID";
7
+ /** `delay` 不是非负有限数。 */
8
+ readonly DELAY_INVALID: "ICE_ANIM_DELAY_INVALID";
9
+ /** `iterationCount` 不是 ≥1 的整数。 */
10
+ readonly ITERATION_INVALID: "ICE_ANIM_ITERATION_INVALID";
11
+ /** 缓动名既不是内置缓动,也不是主题 motion.easing 里的语义名(运行时会回退 linear)。 */
12
+ readonly EASING_UNKNOWN: "ICE_ANIM_EASING_UNKNOWN";
13
+ /** `from` / `to` 不是可插值的同型数值(数值或等长数字数组)。 */
14
+ readonly VALUE_NOT_INTERPOLATABLE: "ICE_ANIM_VALUE_NOT_INTERPOLATABLE";
15
+ /** `keyframes` 形状非法(少于 2 帧 / offset 非有限数 / 各帧取值不同型或不等长)。 */
16
+ readonly KEYFRAMES_INVALID: "ICE_ANIM_KEYFRAMES_INVALID";
17
+ /** 无限循环(`loop: true`):Agent 生成的界面里要谨慎,建议给 `iterationCount` 或可中断入口。 */
18
+ readonly INFINITE_LOOP: "ICE_ANIM_INFINITE_LOOP";
19
+ /** 该属性会改变派生参数(尺寸 / 点集 / 文本量测)→ 每帧都要重量测,性能上不划算。 */
20
+ readonly KEY_AFFECTS_MEASUREMENT: "ICE_ANIM_KEY_AFFECTS_MEASUREMENT";
21
+ /** 运行期告知:用户开启了「减少动态效果」,这条动画被折叠成终态(未播放过程)。 */
22
+ readonly REDUCED_MOTION: "ICE_ANIM_REDUCED_MOTION";
23
+ /** `direction` 不是 `normal` / `reverse` / `alternate`。 */
24
+ readonly DIRECTION_INVALID: "ICE_ANIM_DIRECTION_INVALID";
25
+ /** 生命周期回调(onStart/onUpdate/onRepeat/onComplete)抛了异常(已忽略,不打断动画)。 */
26
+ readonly CALLBACK_ERROR: "ICE_ANIM_CALLBACK_ERROR";
27
+ };
28
+ export type ICEAnimationDiagnosticCode = (typeof ICE_ANIMATION_DIAGNOSTIC_CODES)[keyof typeof ICE_ANIMATION_DIAGNOSTIC_CODES];
29
+ export interface ICEAnimationDiagnostic {
30
+ severity: 'error' | 'warning';
31
+ code: ICEAnimationDiagnosticCode;
32
+ message: string;
33
+ /** 出问题的位置:属性动画键(如 `transform.translate`),便于 Agent 精确改一处。 */
34
+ path: string;
35
+ }
36
+ export type ValidateAnimationsOptions = {
37
+ /** 主题 motion token(缺省用内置默认主题)——`duration: 'normal'` / `easing: 'out'` 这类语义名靠它解析。 */
38
+ motionTokens?: {
39
+ duration?: Record<string, any>;
40
+ easing?: Record<string, any>;
41
+ } | null;
42
+ /** 判断某个属性键是否"动画安全"(不影响派生参数);给了就会对不安全的键出性能警告。 */
43
+ isSafeKey?: ((path: string) => boolean) | null;
44
+ /** 单条动画的时长上限(ms),默认 60s;Agent 生成的长动画多半是写错了。 */
45
+ maxDuration?: number;
46
+ };
47
+ /**
48
+ * 取值种类:`number` / `array`(等长数字数组)/ `color` / `length`(带单位数字串)/ `null`(不可插值)。
49
+ * 判定与求值都转发到 `interpolators.ts`(运行时同源,避免"校验通过但跑起来算错")。
50
+ */
51
+ export declare function classifyAnimationValue(value: any): AnimationValueKind;
52
+ /** 两个取值能否互相插值(同型;数组等长;颜色互插;带单位数字串单位一致)。 */
53
+ export declare function isInterpolatablePair(from: any, to: any): boolean;
54
+ /** 校验一份 `animations` 配置(键 → 单段/关键帧配置),返回按 `path` 排序的诊断列表(无问题 = 空数组)。 */
55
+ export declare function validateAnimations(animations: any, options?: ValidateAnimationsOptions): ICEAnimationDiagnostic[];
@@ -58,6 +58,14 @@ declare class DOMEventDispatcher {
58
58
  * 相对每帧渲染可忽略(见 AGENTS「性能相关铁律」)。
59
59
  */
60
60
  private __resolveCanvasRect;
61
+ /**
62
+ * 事件目标是否属于「别的 canvas」(同页另一个 ICE 实例的绘制面)。
63
+ *
64
+ * - 目标是本实例的 canvas、或本实例 canvas 内的元素 → 不是"外来"事件;
65
+ * - 目标不是 canvas(body / window / 工具栏按钮等)→ 不是"外来"事件(键盘、合成事件照旧);
66
+ * - 目标正是**另一个 canvas 元素** → 外来事件,本实例应忽略。
67
+ */
68
+ private __isForeignCanvasTarget;
61
69
  set stopped(flag: boolean);
62
70
  get stopped(): boolean;
63
71
  /**
@@ -58,6 +58,14 @@ declare class DOMEventDispatcher {
58
58
  * 相对每帧渲染可忽略(见 AGENTS「性能相关铁律」)。
59
59
  */
60
60
  private __resolveCanvasRect;
61
+ /**
62
+ * 事件目标是否属于「别的 canvas」(同页另一个 ICE 实例的绘制面)。
63
+ *
64
+ * - 目标是本实例的 canvas、或本实例 canvas 内的元素 → 不是"外来"事件;
65
+ * - 目标不是 canvas(body / window / 工具栏按钮等)→ 不是"外来"事件(键盘、合成事件照旧);
66
+ * - 目标正是**另一个 canvas 元素** → 外来事件,本实例应忽略。
67
+ */
68
+ private __isForeignCanvasTarget;
61
69
  set stopped(flag: boolean);
62
70
  get stopped(): boolean;
63
71
  /**
@@ -50,5 +50,12 @@ export type SvgExportResult = {
50
50
  *
51
51
  * @param target ICE 实例(导出整幅画布)或任意组件(导出它的子树)
52
52
  */
53
- export declare function exportSvg(target: any, options?: SvgExportOptions): string;
54
- export declare function exportSvgResult(target: any, options?: SvgExportOptions): SvgExportResult;
53
+ /**
54
+ * 导出场景为 SVG 字符串。
55
+ *
56
+ * `target` 可以是 **ICE 实例**、**任意组件**,也可以是**这两个的数组**(多层合成):
57
+ * 分层渲染(静态层 + 动画层)导出时按数组顺序叠加,各层的世界坐标共用同一套 `worldToView`。
58
+ * 单层(传单个 target)的输出与历史版本逐字节一致。
59
+ */
60
+ export declare function exportSvg(target: any | any[], options?: SvgExportOptions): string;
61
+ export declare function exportSvgResult(target: any | any[], options?: SvgExportOptions): SvgExportResult;