feat: 动画类 Animater

This commit is contained in:
unanmed 2026-03-10 15:03:03 +08:00
parent feb6abb5bb
commit 555cf96d76
9 changed files with 1363 additions and 9 deletions

View File

@ -5,6 +5,7 @@
"type": "module",
"scripts": {
"dev": "tsx script/dev.ts",
"test": "vitest",
"preview": "vite preview",
"declare": "tsx script/declare.ts",
"type": "vue-tsc --noEmit",
@ -87,6 +88,7 @@
"vite-plugin-dts": "^4.5.4",
"vitepress": "^1.6.4",
"vitepress-plugin-mermaid": "^2.0.17",
"vitest": "^4.0.18",
"vue-tsc": "^2.2.12",
"ws": "^8.19.0"
}

View File

@ -0,0 +1,505 @@
import { logger } from '@motajs/common';
import {
ExcitationCurve,
IAnimatable,
IAnimater,
IAnimationPlan,
IExcitableController,
IExcitation,
IAnimatePlanIdentifier
} from './types';
import { linear } from './utils';
interface IAnimaterTimedInfo extends IAnimatePlanIdentifier {
/** 等待时长 */
readonly time: number;
}
interface IAnimaterResolvedTimedInfo extends IAnimaterTimedInfo {
/** 当前定时动画计划被唤起的时刻 */
readonly arousedTime: number;
}
interface IAnimaterRawPlan extends IAnimationPlan {
/** 当此动画开始时需要执行的计划 */
readonly when: Set<IAnimaterTimedInfo>;
/** 当此动画结束后需要执行的计划 */
readonly after: Set<IAnimaterTimedInfo>;
/** 兑现函数,当本次动画计划执行完毕后执行 */
readonly resolve: () => void;
}
interface IAnimatingContent extends IAnimaterRawPlan {
/** 动画执行开始的时刻 */
readonly startTime: number;
/** 动画执行的初始值 */
readonly startValue: number;
/** 动画终值与初值的差值 */
readonly diff: number;
}
interface IAnimaterContentPlan {
/** 当前动画对象中所有的动画计划 */
readonly animationPlan: Map<number, IAnimaterRawPlan>;
/** 计数器,用于计算动画计划的索引 */
counter: number;
}
interface IAnimaterPlanGroupBase {
/** 计划执行前的等待时间 */
readonly preTime: number;
/** 计划执行后的等待时间 */
readonly postTime: number;
/** 计划组的首个动画计划 */
readonly planStart: Set<IAnimatePlanIdentifier>;
}
interface IAnimaterPlanGroup extends IAnimaterPlanGroupBase {
/** 当前计划组中所有的动画对象计划 */
readonly contentStore: Map<IAnimatable, IAnimaterContentPlan>;
}
export class Animater implements IAnimater {
/** 当前是否正在定义动画计划 */
private planning: boolean = false;
/** 当前绑定的激励源 */
private excitation: IExcitation<number> | null = null;
/** 当前定义在绑定激励源上的可激励对象 */
private controller: IExcitableController<number> | null = null;
/** 计划组计数器 */
private groupCounter: number = 0;
/** 当前正在计划的动画计划 */
private planningStore: Map<IAnimatable, IAnimaterContentPlan> = new Map();
/** 计划存储 */
private groupStore: Map<number, IAnimaterPlanGroup> = new Map();
/** 需要执行的计划队列 */
private pendingGroups: number[] = [];
/** 当前所有正在等待执行的 `when` 操作 */
private whens: Set<IAnimaterResolvedTimedInfo> = new Set();
/** 当前所有正在等待执行的 `after` 操作 */
private afters: Set<IAnimaterResolvedTimedInfo> = new Set();
/** 当前正在执行的计划组 */
private executingGroup: number = -1;
/** 当前正在执行的计划组对象 */
private executingGroupObj: IAnimaterPlanGroup | null = null;
/** 当前正在执行的动画 */
private executing: Set<IAnimatingContent> = new Set();
/** 当前动画对象正在被哪个动画执行 */
private executingMap: Map<IAnimatable, IAnimatingContent> = new Map();
/** 当前使用的速率曲线 */
private curveStatus: ExcitationCurve = linear();
/** 当前使用的动画对象计划 */
private animatableStatus: IAnimaterContentPlan | null = null;
/** 当前使用的动画对象 */
private currentAnimatable: IAnimatable | null = null;
/** 当前正在计划的计划组的起始动画 */
private planningStart: Set<IAnimatePlanIdentifier> = new Set();
/** 当前的 `when` 计划内容 */
private whenBind: IAnimaterRawPlan | null = null;
/** 当前的 `after` 计划内容 */
private afterBind: IAnimaterRawPlan | null = null;
/** 当前的 `when` 等待时长 */
private whenTime: number = 0;
/** 当前的 `after` 等待时长 */
private afterTime: number = 0;
/** 计划组是否正在进行执行前等待 */
private waitingPre = false;
/** 计划组执行前等待的时间 */
private waitingPreStart = 0;
/** 计划组是否正在进行执行后等待 */
private waitingPost = false;
/** 计划组执行后等待的时间 */
private waitingPostStart = 0;
/** 上一个定义的动画计划 */
private lastAnimatable: IAnimaterRawPlan | null = null;
constructor() {
this.excited = this.excited.bind(this);
}
bindExcitation(excitation: IExcitation<number>): void {
if (excitation === this.excitation) return;
this.unbindExcitation();
this.controller = excitation.add(this);
this.excitation = excitation;
}
unbindExcitation(): void {
if (!this.excitation) return;
this.controller?.revoke();
this.excitation = null;
}
//#region 动画计划
animate(content: IAnimatable): this {
if (!this.planningStore.has(content)) {
const plan: IAnimaterContentPlan = {
animationPlan: new Map(),
counter: 0
};
this.animatableStatus = plan;
this.planningStore.set(content, plan);
} else {
const plan = this.planningStore.get(content)!;
this.animatableStatus = plan;
}
this.currentAnimatable = content;
return this;
}
curve(curve: ExcitationCurve): this {
this.curveStatus = curve;
return this;
}
to(value: number, time: number): this {
if (!this.animatableStatus || !this.currentAnimatable) return this;
this.planning = true;
// 定义动画计划
const index = ++this.animatableStatus.counter;
const { promise, resolve } = Promise.withResolvers<void>();
const plan: IAnimaterRawPlan = {
identifier: { content: this.currentAnimatable, index },
curve: this.curveStatus,
targetValue: value,
time,
promise,
resolve,
when: new Set(),
after: new Set()
};
this.animatableStatus.animationPlan.set(index, plan);
// 检查 when after 状态,如果是在这个状态下,加入到对应的计划中
if (this.whenBind) {
const identifier: IAnimaterTimedInfo = {
content: this.currentAnimatable,
index,
time: this.whenTime
};
this.whenBind.when.add(identifier);
} else if (this.afterBind) {
const identifier: IAnimaterTimedInfo = {
content: this.currentAnimatable,
index,
time: this.afterTime
};
this.afterBind.after.add(identifier);
} else {
const identifier: IAnimatePlanIdentifier = {
content: this.currentAnimatable,
index
};
this.planningStart.add(identifier);
}
// 将上一次的计划设为本计划,用于 after when 的调用
this.lastAnimatable = plan;
return this;
}
after(time: number = 0): this {
if (!this.lastAnimatable) return this;
this.afterBind = this.lastAnimatable;
this.afterTime = time;
this.whenBind = null;
return this;
}
afterPlan(content: IAnimatable, index: number, time: number = 0): this {
const plan = this.queryRaw(content, index);
if (!plan) return this;
this.afterBind = plan;
this.afterTime = time;
this.whenBind = null;
return this;
}
when(time: number = 0): this {
if (!this.lastAnimatable) return this;
this.whenBind = this.lastAnimatable;
this.whenTime = time;
this.afterBind = null;
return this;
}
whenPlan(content: IAnimatable, index: number, time: number = 0): this {
const plan = this.queryRaw(content, index);
if (!plan) return this;
this.whenBind = plan;
this.whenTime = time;
this.afterBind = null;
return this;
}
/**
*
* @param content
* @param index
* @param plan `planEnd`
*/
private queryRaw(
content: IAnimatable,
index: number,
plan: number = -1
): IAnimaterRawPlan | null {
if (plan === -1) {
const animation = this.planningStore.get(content);
if (!animation) return null;
const result = animation.animationPlan.get(index);
return result ?? null;
} else {
const group = this.groupStore.get(plan);
if (!group) return null;
const animation = group.contentStore.get(content);
if (!animation) return null;
const result = animation.animationPlan.get(index);
return result ?? null;
}
}
query(
content: IAnimatable,
index: number,
plan: number = -1
): IAnimationPlan | null {
return this.queryRaw(content, index, plan);
}
wait(
content: IAnimatable,
index: number,
plan: number = this.executingGroup
): Promise<void> | undefined {
const raw = this.query(content, index, plan);
return raw?.promise;
}
planEnd(preTime: number = 0, postTime: number = 0): number {
if (!this.planning) return -1;
const group: IAnimaterPlanGroup = {
contentStore: this.planningStore,
planStart: this.planningStart,
preTime,
postTime
};
this.whenBind = null;
this.afterBind = null;
this.currentAnimatable = null;
this.animatableStatus = null;
this.lastAnimatable = null;
this.planningStart = new Set();
this.planningStore = new Map();
this.planning = false;
const index = ++this.groupCounter;
this.groupStore.set(index, group);
this.pendingGroups.push(index);
this.startPlanGroup();
return index;
}
//#endregion
//#region 动画执行
excited(payload: number): void {
if (this.planning) {
logger.error(50);
return;
}
// 计划组未执行
if (this.executingGroup === -1 || !this.executingGroupObj) return;
// 计划组 preTime 等待
if (this.waitingPre) {
const dt = payload - this.waitingPreStart;
if (dt < this.executingGroupObj.preTime) return;
this.waitingPre = false;
// 启动所有 planStart 动画
for (const identifier of this.executingGroupObj.planStart) {
this.executeAnimate(identifier);
}
}
// 计划组 postTime 等待
if (this.waitingPost) {
const dt = payload - this.waitingPostStart;
if (dt >= this.executingGroupObj.postTime) {
this.waitingPost = false;
this.endPlanGroup();
}
return;
}
// 处理 when/after 等待
const whenToDelete = new Set<IAnimaterResolvedTimedInfo>();
for (const w of this.whens) {
if (payload - w.arousedTime >= w.time) {
whenToDelete.add(w);
this.executeAnimate(w);
}
}
whenToDelete.forEach(v => this.whens.delete(v));
const afterToDelete = new Set<IAnimaterResolvedTimedInfo>();
for (const a of this.afters) {
if (payload - a.arousedTime >= a.time) {
afterToDelete.add(a);
this.executeAnimate(a);
}
}
afterToDelete.forEach(v => this.afters.delete(v));
// 动画执行
const endedAnimate = new Set<IAnimatingContent>();
const afters = new Set<IAnimaterTimedInfo>();
for (const anim of this.executing) {
const progress = (payload - anim.startTime) / anim.time;
const content = anim.identifier.content;
if (progress >= 1) {
// 动画结束
content.value = anim.targetValue;
anim.resolve();
endedAnimate.add(anim);
// 检查 after
anim.after.forEach(v => afters.add(v));
} else {
const completion = anim.curve(progress);
content.value = completion * anim.diff + anim.startValue;
}
}
afters.forEach(v => {
this.startAfter({ ...v, arousedTime: payload });
});
// 必要清理
endedAnimate.forEach(v => {
this.executing.delete(v);
this.executingMap.delete(v.identifier.content);
});
// 检查计划组是否全部结束
if (
this.executing.size === 0 &&
this.whens.size === 0 &&
this.afters.size === 0
) {
this.waitingPost = true;
this.waitingPostStart = payload;
}
}
/**
*
* @param identifier
*/
private executeAnimate(identifier: IAnimatePlanIdentifier) {
if (!this.executingGroupObj || !this.excitation) return;
const plan = this.queryRaw(
identifier.content,
identifier.index,
this.executingGroup
);
if (!plan) return;
// 冲突检测
if (this.executingMap.has(identifier.content)) {
const current = this.executingMap.get(identifier.content)!;
if (current.startTime === this.excitation?.payload()) {
logger.error(51);
return;
}
// 终止前一个动画
current.resolve();
this.executing.delete(current);
}
// 记录动画初始值和开始时间
const startValue = identifier.content.value;
const startTime = this.excitation.payload();
const anim: IAnimatingContent = {
...plan,
startTime,
startValue,
diff: plan.targetValue - startValue
};
this.executing.add(anim);
this.executingMap.set(identifier.content, anim);
// 检查 when
for (const when of plan.when) {
this.startWhen({ ...when, arousedTime: startTime });
}
}
/**
* `when`
* @param when `when`
*/
private startWhen(when: IAnimaterResolvedTimedInfo) {
if (when.time === 0) {
this.executeAnimate(when);
} else {
this.whens.add(when);
}
}
/**
* `after`
* @param after `after`
*/
private startAfter(after: IAnimaterResolvedTimedInfo) {
if (after.time === 0) {
this.executeAnimate(after);
} else {
this.afters.add(after);
}
}
/**
*
*/
private startPlanGroup() {
if (this.executingGroup !== -1) return;
if (this.pendingGroups.length === 0) return;
if (!this.excitation) return;
const group = this.pendingGroups.shift();
if (group === void 0) return;
const obj = this.groupStore.get(group);
if (!obj) return;
this.executingGroup = group;
this.executingGroupObj = obj;
// preTime 等待
if (obj.preTime > 0) {
this.waitingPre = true;
this.waitingPreStart = this.excitation.payload();
} else {
// 立即启动所有 planStart 动画
for (const identifier of this.executingGroupObj.planStart) {
this.executeAnimate(identifier);
}
}
}
/**
*
*/
private endPlanGroup() {
// 清理状态
this.executingGroup = -1;
this.executingGroupObj = null;
this.executing.clear();
this.executingMap.clear();
this.whens.clear();
this.afters.clear();
this.waitingPre = false;
this.waitingPost = false;
// 启动下一个计划组
this.startPlanGroup();
}
//#endregion
}

View File

@ -214,8 +214,10 @@ export class ExcitationVariator
return;
}
const now = excitation.payload();
this.source = excitation;
this.sourceTs = excitation.payload();
this.sourceTs = now;
this.now = now;
this.selfTs = this.sourceTs;
this.speed = 1;
@ -271,14 +273,14 @@ export class ExcitationVariator
return Promise.resolve();
}
return new Promise<void>(resolve => {
this.curveQueue.push({ curve, time, mode, resolve });
const { promise, resolve } = Promise.withResolvers<void>();
this.curveQueue.push({ curve, time, mode, resolve });
// 如果没有正在执行的曲线,立即开始
if (this.currentCurve === null) {
this.startNextCurve();
}
// 如果没有正在执行的曲线,立即开始
if (this.currentCurve === null) {
this.startNextCurve();
}
});
return promise;
}
private startNextCurve(): void {

View File

@ -1,3 +1,4 @@
export * from './animater';
export * from './excitation';
export * from './types';
export * from './utils';

View File

@ -127,3 +127,170 @@ export interface IExcitationVariator extends IExcitation<number> {
*/
endAllCurves(): void;
}
export interface IAnimatable {
/** 动画数值 */
value: number;
}
export interface IAnimatePlanIdentifier {
/** 动画对象 */
readonly content: IAnimatable;
/** 动画对象对应的动画计划 */
readonly index: number;
}
export interface IAnimationPlan {
/** 动画计划的标识符 */
readonly identifier: IAnimatePlanIdentifier;
/** 动画的速率曲线 */
readonly curve: ExcitationCurve;
/** 动画的目标值 */
readonly targetValue: number;
/** 动画时长 */
readonly time: number;
/** 动画结束后兑现的 `Promise` */
readonly promise: Promise<void>;
}
export interface IAnimater extends IExcitable<number> {
/**
*
* @param excitation
*/
bindExcitation(excitation: IExcitation<number>): void;
/**
*
*/
unbindExcitation(): void;
/**
*
* @param content
*/
animate(content: IAnimatable): this;
/**
* 线
* @param curve 线
*/
curve(curve: ExcitationCurve): this;
/**
* {@link query}
*
* @param value
* @param time
*/
to(value: number, time: number): this;
/**
*
*
* ```ts
* const obj1 = { value: 0 };
* const obj2 = { value: 0 };
* animater.animate(obj1)
* .curve(linear())
* .to(100, 500)
* .animate(obj2)
* .after()
* .to(200, 200)
* .planEnd();
* ```
*
* @param time 0
*/
after(time?: number): this;
/**
* {@link query}
*
* ```ts
* const obj1 = { value: 0 };
* const obj2 = { value: 0 };
* animater.animate(obj1)
* .curve(linear())
* .to(100, 500)
* .animate(obj2)
* .to(200, 200)
* .afterObject(obj1, 1)
* ...
* .planEnd();
* ```
*
* @param content
* @param index
* @param time 0
*/
afterPlan(content: IAnimatable, index: number, time?: number): this;
/**
* {@link after}
* @param time
*/
when(time?: number): this;
/**
* {@link afterPlan}
* @param content
* @param index
* @param time 0
*/
whenPlan(content: IAnimatable, index: number, time?: number): this;
/**
*
*
* `plan`
* `index` `content`
* 1
*
* ```ts
* const obj = { value: 0 };
* animater.animate()
* .curve(linear())
* .to(100, 1000) // 索引为 1
* .after()
* .curve(sin(CurveMode.EaseOut))
* .to(200, 500) // 索引为 2
* .animate(obj2)
* ...
* .animate(obj)
* .to(100, 500) // 索引为 3
* .planEnd();
* ```
*
* @param content
* @param index
* @param plan `planEnd`
* `null`退
*/
query(
content: IAnimatable,
index: number,
plan?: number
): IAnimationPlan | null;
/**
* {@link query}
* @param content
* @param index
* @param plan
*/
wait(
content: IAnimatable,
index: number,
plan?: number
): Promise<void> | undefined;
/**
*
*
* {@link query}
* @param preTime
* @param postTime
*/
planEnd(preTime?: number, postTime?: number): number;
}

View File

@ -1,4 +1,13 @@
import { IExcitable } from './types';
import { cumsum } from '@motajs/common';
import {
ExcitationCurve,
ExcitationCurve2D,
ExcitationCurve3D,
GeneralExcitationCurve,
IExcitable
} from './types';
//#region 工具函数
/**
*
@ -15,3 +24,419 @@ export function excited<T>(
return { excited: func };
}
}
//#endregion
//#region 曲线计算
/**
* 线 `a(p) + b(p)`
* @param curve1
* @param curve2
*/
export function addCurve(
curve1: ExcitationCurve,
curve2: ExcitationCurve
): ExcitationCurve {
return p => curve1(p) + curve2(p);
}
/**
* 线 `a(p) - b(p)`
* @param curve1
* @param curve2
*/
export function subCurve(
curve1: ExcitationCurve,
curve2: ExcitationCurve
): ExcitationCurve {
return p => curve1(p) - curve2(p);
}
/**
* 线 `a(p) * b(p)`
* @param curve1
* @param curve2
*/
export function mulCurve(
curve1: ExcitationCurve,
curve2: ExcitationCurve
): ExcitationCurve {
return p => curve1(p) * curve2(p);
}
/**
* 线 `a(p) / b(p)`
* @param curve1
* @param curve2
*/
export function divCurve(
curve1: ExcitationCurve,
curve2: ExcitationCurve
): ExcitationCurve {
return p => curve1(p) / curve2(p);
}
/**
* 线 `a(p) ** b(p)`
* @param curve1
* @param curve2
*/
export function powCurve(
curve1: ExcitationCurve,
curve2: ExcitationCurve
): ExcitationCurve {
return p => curve1(p) ** curve2(p);
}
/**
* 线`a(b(p))`
* @param curve1 线
* @param curve2 线
*/
export function compositeCurve(
curve1: ExcitationCurve,
curve2: ExcitationCurve
): ExcitationCurve {
return p => curve1(curve2(p));
}
/**
* 线`a(p) + b`
* @param curve 线
* @param move
*/
export function moveCurve(
curve: ExcitationCurve,
move: number = 0
): ExcitationCurve {
return p => curve(p) + move;
}
/**
* 线`b - a(p)`
* @param curve 线
* @param move
*/
export function oppsiteCurve(
curve: ExcitationCurve,
move: number = 0
): ExcitationCurve {
return p => move - curve(p);
}
/**
* 线`a(p) * b`
* @param curve 线
* @param scale
*/
export function scaleCurve(
curve: ExcitationCurve,
scale: number = 1
): ExcitationCurve {
return p => curve(p) * scale;
}
/**
* 线`g(x) = b / f(x)`
* @param curve 线
* @param scale
*/
export function reciprocalCurve(
curve: ExcitationCurve,
scale: number = 1
): ExcitationCurve {
return p => scale / curve(p);
}
/**
* 线线> 100
* @param seq 线
* @param duration 线 `[0,1]` 1 1
* @param scale 线
* @param move 线
*/
export function sequenceCurve(
seq: ExcitationCurve[],
duration: number[],
scale: number[],
move: number[]
): ExcitationCurve {
const keep = cumsum(duration);
return p => {
const index = keep.findIndex(sum => p >= sum);
if (index === -1) return 0;
const progress = (p - keep[index]) / duration[index];
return seq[index](progress) * scale[index] + move[index];
};
}
/**
* 线线
* @param curve 线
*/
export function splitCurve2D(
curve: ExcitationCurve2D
): [ExcitationCurve, ExcitationCurve] {
return [p => curve(p)[0], p => curve(p)[1]];
}
/**
* 线线
* @param curve 线
*/
export function splitCurve3D(
curve: ExcitationCurve3D
): [ExcitationCurve, ExcitationCurve, ExcitationCurve] {
return [p => curve(p)[0], p => curve(p)[1], p => curve(p)[2]];
}
/**
* n 线 n 线
* @param curve n 线
*/
export function splitCurve(curve: GeneralExcitationCurve): ExcitationCurve[] {
const n = curve(0).length;
const arr: ExcitationCurve[] = [];
for (let i = 0; i < n; i++) {
arr.push(p => curve(p)[i]);
}
return arr;
}
/**
* 线线
* @param curve1 线
* @param curve2 线
*/
export function stackCurve2D(
curve1: ExcitationCurve,
curve2: ExcitationCurve
): ExcitationCurve2D {
return p => [curve1(p), curve2(p)];
}
/**
* 线线
* @param curve1 线
* @param curve2 线
* @param curve3 线
*/
export function stackCurve3D(
curve1: ExcitationCurve,
curve2: ExcitationCurve,
curve3: ExcitationCurve
): ExcitationCurve3D {
return p => [curve1(p), curve2(p), curve3(p)];
}
/**
* n 线 n 线
* @param curves 线
*/
export function stackCurve(curves: ExcitationCurve[]): GeneralExcitationCurve {
return p => curves.map(v => v(p));
}
/**
* 线线使 `curve(0)` `curve(1)`
*
* - `f(0) > f(1)`: `g(x) = (f(x) - f(0)) / (f(0) - f(1))`
* - `f(0) < f(1)`: `g(x) = (f(x) - f(1)) / (f(1) - f(0))`
* - `f(0) = f(1)`: `g(x) = f(x)`
* @param curve 线
* @returns
*/
export function normalize(curve: ExcitationCurve): ExcitationCurve {
const head = curve(1);
const tail = curve(0);
if (head > tail) {
const diff = head - tail;
return p => (curve(p) - tail) / diff;
} else if (head < tail) {
const diff = tail - head;
return p => (curve(p) - head) / diff;
} else {
return curve;
}
}
//#endregion
//#region 内置曲线
export const enum CurveMode {
/** 缓进快出 */
EaseIn,
/** 快进缓出 */
EaseOut,
/** 缓进缓出,中间快 */
EaseInOut,
/** 快进快出,中间缓 */
EaseCenter
}
/** 输入缓进快出,输出缓进快出 */
function easeIn(curve: ExcitationCurve): ExcitationCurve {
return curve;
}
/** 输入缓进快出,输出快进缓出 */
function easeOut(curve: ExcitationCurve): ExcitationCurve {
return p => 1 - curve(1 - p);
}
/** 输入缓进快出,输出缓进缓出,中间快 */
function easeInOut(curve: ExcitationCurve): ExcitationCurve {
return p => (p < 0.5 ? curve(p * 2) * 0.5 : 1 - curve((1 - p) * 2) * 0.5);
}
/** 输入缓进快出,输出快进快出,中间缓 */
function easeCenter(curve: ExcitationCurve): ExcitationCurve {
return p =>
p < 0.5
? 0.5 - curve(1 - p * 2) * 0.5
: 0.5 + curve((p - 0.5) * 2) * 0.5;
}
/**
* 线线
* - `CurveMode.EaseIn`: `g(x) = f(x)`
* - `CurveMode.EaseOut`: `g(x) = 1 - f(1 - x)`
* - `CurveMode.EaseInOut`: `g(x) = 0.5 * f(2x) if x < 0.5 else 1 - 0.5 * f(2 - 2x)`
* - `CurveMode.EaseCenter`: `g(x) = 0.5 - 0.5 * f(1 - 2x) if x < 0.5 else 0.5 + 0.5 * f(2x - 1)`
* @param func 线
* @param mode 线
* @returns
*/
export function applyCurveMode(func: ExcitationCurve, mode: CurveMode) {
switch (mode) {
case CurveMode.EaseIn:
return easeIn(func);
case CurveMode.EaseOut:
return easeOut(func);
case CurveMode.EaseInOut:
return easeInOut(func);
case CurveMode.EaseCenter:
return easeCenter(func);
}
}
/** f(x) = 1 - cos(x * pi/2), x∈[0,1], f(x)∈[0,1] */
const sinfunc: ExcitationCurve = p => 1 - Math.cos((p * Math.PI) / 2);
/**
* 线EaseIn: `f(x) = 1 - cos(x * pi/2), x∈[0,1], f(x)∈[0,1]`
* @param mode 线
*/
export function sin(mode: CurveMode = CurveMode.EaseIn): ExcitationCurve {
return applyCurveMode(sinfunc, mode);
}
/** f(x) = tan(x * pi/4), x∈[0,1], f(x)∈[0,1] */
const tanfunc: ExcitationCurve = p => Math.tan((p * Math.PI) / 4);
/**
* 线EaseIn: `f(x) = tan(x * pi/4), x∈[0,1], f(x)∈[0,1]`
* @param mode 线
*/
export function tan(mode: CurveMode = CurveMode.EaseIn): ExcitationCurve {
return applyCurveMode(tanfunc, mode);
}
/** f(x) = sec(x * pi/3) - 1, x∈[0,1], f(x)∈[0,1] */
const secfunc: ExcitationCurve = p => 1 / Math.cos((p * Math.PI) / 3) - 1;
/**
* 线EaseIn: `f(x) = sec(x * pi/3)-1, x∈[0,1], f(x)∈[0,1]`
* @param mode 线
*/
export function sec(mode: CurveMode = CurveMode.EaseIn): ExcitationCurve {
return applyCurveMode(secfunc, mode);
}
/**
* 线EaseIn: `f(x) = x ** n, x∈[0,1], f(x)∈[0,1]`
* @param exp
* @param mode 线
*/
export function pow(
exp: number,
mode: CurveMode = CurveMode.EaseIn
): ExcitationCurve {
// f(x) = x ** n, x∈[0,1], f(x)∈[0,1]
const powfunc: ExcitationCurve = p => Math.pow(p, exp);
return applyCurveMode(powfunc, mode);
}
/**
* 线EaseIn: `f(x) = (cosh(x * k) - 1) / (cosh(k) - 1), x∈[0,1], f(x)∈[0,1]`
* @param k
* @param mode 线
*/
export function cosh(
k: number = 2,
mode: CurveMode = CurveMode.EaseIn
): ExcitationCurve {
// f(x) = (cosh(x * k) - 1) / cosh(k), x∈[0,1], f(x)∈[0,1]
const s = Math.cosh(k) - 1;
const coshfunc: ExcitationCurve = p => (Math.cosh(p * k) - 1) / s;
return applyCurveMode(coshfunc, mode);
}
/**
* 线EaseIn: `f(x) = 1 + tanh((x - 1) * k) / tanh(k), x∈[0,1], f(x)∈[0,1]`
* @param k
* @param mode 线
*/
export function tanh(
k: number = 2,
mode: CurveMode = CurveMode.EaseIn
): ExcitationCurve {
// f(x) = 1 + tanh((x - 1) * k) / tanh(k), x∈[0,1], f(x)∈[0,1]
const s = Math.tanh(k);
const tanhfunc: ExcitationCurve = p => 1 + Math.tanh((p - 1) * k) / s;
return applyCurveMode(tanhfunc, mode);
}
/**
* 线EaseIn: `f(x) = 1 - sech(x * k) / sech(k), x∈[0,1], f(x)∈[0,1]`
* @param k
* @param mode 线
*/
export function sech(
k: number = 2,
mode: CurveMode = CurveMode.EaseIn
): ExcitationCurve {
// f(x) = 1 - sech(x * k) / sech(k), x∈[0,1], f(x)∈[0,1]
// sech(x) = 1 / cosh(x)
const s = 1 / Math.cosh(k);
const sechfunc: ExcitationCurve = p => 1 - 1 / Math.cosh(p * k) / s;
return applyCurveMode(sechfunc, mode);
}
/**
* 线`f(x) = b, x∈[0,1], f(x)∈R`
* @param k
*/
export function constant(k: number): ExcitationCurve {
return _ => k;
}
/**
* 线线`f(x) = x, x∈[0,1], f(x)∈[0,1]`
*/
export function linear(): ExcitationCurve {
return p => p;
}
/**
* 线`f(x) = floor(x * k) / k, x∈[0,1], f(x)∈[0,1]`
* @param k
*/
export function step(k: number): ExcitationCurve {
// f(x) = floor(x * k) / k, x∈[0,1], f(x)∈[0,1]
return p => Math.floor(p * k) / k;
}
//#endregion

View File

@ -49,6 +49,8 @@
"47": "Cannot require text area outside the target map.",
"48": "Cannot $1 excitables on destroyed excitation.",
"49": "Cannot $1 on variator without excitation binding.",
"50": "Expected a planEnd call after animation plan calling.",
"51": "Animatable object cannot be animated by plans with the same start time.",
"1201": "Floor-damage extension needs 'floor-binder' extension as dependency."
},
"warn": {

View File

@ -1,3 +1,22 @@
/**
* `Promise`
* @param time
* @example await sleep(1000);
*/
export function sleep(time: number) {
return new Promise(res => setTimeout(res, time));
}
/**
*
* @param seq
* @example cumsum([1, 2, 3, 4]); // [1, 3, 6, 10]
*/
export function cumsum(seq: Iterable<number>): number[] {
const result: number[] = [];
let now = 0;
for (const ele of seq) {
result.push((now += ele));
}
return result;
}

View File

@ -210,6 +210,9 @@ importers:
vitepress-plugin-mermaid:
specifier: ^2.0.17
version: 2.0.17(mermaid@11.12.3)(vitepress@1.6.4(@algolia/client-search@5.49.1)(@types/node@22.19.15)(async-validator@4.2.5)(axios@1.13.6)(less@4.5.1)(markdown-it-mathjax3@4.3.2(encoding@0.1.13))(postcss@8.5.8)(search-insights@2.17.3)(terser@5.46.0)(typescript@5.9.3))
vitest:
specifier: ^4.0.18
version: 4.0.18(@types/node@22.19.15)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0)
vue-tsc:
specifier: ^2.2.12
version: 2.2.12(typescript@5.9.3)
@ -2107,6 +2110,9 @@ packages:
'@simonwep/pickr@1.8.2':
resolution: {integrity: sha512-/l5w8BIkrpP6n1xsetx9MWPWlU6OblN5YgZZphxan0Tq4BByTCETL6lyIeY8lagalS2Nbt4F2W034KHLIiunKA==}
'@standard-schema/spec@1.1.0':
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
'@ts-graphviz/adapter@2.0.6':
resolution: {integrity: sha512-kJ10lIMSWMJkLkkCG5gt927SnGZcBuG0s0HHswGzcHTgvtUe7yk5/3zTEr0bafzsodsOq5Gi6FhQeV775nC35Q==}
engines: {node: '>=18'}
@ -2144,6 +2150,9 @@ packages:
'@types/body-parser@1.19.6':
resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==}
'@types/chai@5.2.3':
resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==}
'@types/connect@3.4.38':
resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}
@ -2240,6 +2249,9 @@ packages:
'@types/d3@7.4.3':
resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==}
'@types/deep-eql@4.0.2':
resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
'@types/estree@1.0.8':
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
@ -2414,6 +2426,35 @@ packages:
vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0
vue: ^3.2.25
'@vitest/expect@4.0.18':
resolution: {integrity: sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==}
'@vitest/mocker@4.0.18':
resolution: {integrity: sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==}
peerDependencies:
msw: ^2.4.9
vite: ^6.0.0 || ^7.0.0-0
peerDependenciesMeta:
msw:
optional: true
vite:
optional: true
'@vitest/pretty-format@4.0.18':
resolution: {integrity: sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==}
'@vitest/runner@4.0.18':
resolution: {integrity: sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==}
'@vitest/snapshot@4.0.18':
resolution: {integrity: sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==}
'@vitest/spy@4.0.18':
resolution: {integrity: sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==}
'@vitest/utils@4.0.18':
resolution: {integrity: sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==}
'@volar/language-core@2.4.15':
resolution: {integrity: sha512-3VHw+QZU0ZG9IuQmzT68IyN4hZNd9GchGPhbD9+pa8CVv7rnoOZwo7T8weIbrRmihqy3ATpdfXFnqRrfPVK6CA==}
@ -2718,6 +2759,10 @@ packages:
resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==}
engines: {node: '>= 0.4'}
assertion-error@2.0.1:
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
engines: {node: '>=12'}
ast-module-types@6.0.1:
resolution: {integrity: sha512-WHw67kLXYbZuHTmcdbIrVArCq5wxo6NEuj3hiYAWr8mwJeC+C2mMCIBIWCiDoCye/OF/xelc+teJ1ERoWmnEIA==}
engines: {node: '>=18'}
@ -2948,6 +2993,10 @@ packages:
ccount@2.0.1:
resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
chai@6.2.2:
resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==}
engines: {node: '>=18'}
chalk@4.1.2:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'}
@ -3553,6 +3602,9 @@ packages:
resolution: {integrity: sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w==}
engines: {node: '>= 0.4'}
es-module-lexer@1.7.0:
resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==}
es-object-atoms@1.1.1:
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
engines: {node: '>= 0.4'}
@ -3693,6 +3745,9 @@ packages:
estree-walker@2.0.2:
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
estree-walker@3.0.3:
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
esutils@2.0.3:
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
engines: {node: '>=0.10.0'}
@ -3715,6 +3770,10 @@ packages:
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
engines: {node: '>=0.8.x'}
expect-type@1.3.0:
resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==}
engines: {node: '>=12.0.0'}
exponential-backoff@3.1.3:
resolution: {integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==}
@ -4771,6 +4830,9 @@ packages:
resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==}
engines: {node: '>= 0.4'}
obug@2.1.1:
resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==}
ogg-opus-decoder@1.7.3:
resolution: {integrity: sha512-w47tiZpkLgdkpa+34VzYD8mHUj8I9kfWVZa82mBbNwDvB1byfLXSSzW/HxA4fI3e9kVlICSpXGFwMLV1LPdjwg==}
@ -5440,6 +5502,9 @@ packages:
resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}
engines: {node: '>= 0.4'}
siginfo@2.0.0:
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
signal-exit@3.0.7:
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
@ -5502,10 +5567,16 @@ packages:
resolution: {integrity: sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
stackback@0.0.2:
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
statuses@2.0.2:
resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
engines: {node: '>= 0.8'}
std-env@3.10.0:
resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==}
stop-iteration-iterator@1.1.0:
resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==}
engines: {node: '>= 0.4'}
@ -5659,6 +5730,9 @@ packages:
through@2.3.8:
resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
tinybench@2.9.0:
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
tinyexec@1.0.2:
resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==}
engines: {node: '>=18'}
@ -5667,6 +5741,10 @@ packages:
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
engines: {node: '>=12.0.0'}
tinyrainbow@3.0.3:
resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==}
engines: {node: '>=14.0.0'}
to-buffer@1.2.2:
resolution: {integrity: sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==}
engines: {node: '>= 0.4'}
@ -5981,6 +6059,40 @@ packages:
postcss:
optional: true
vitest@4.0.18:
resolution: {integrity: sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==}
engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0}
hasBin: true
peerDependencies:
'@edge-runtime/vm': '*'
'@opentelemetry/api': ^1.9.0
'@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0
'@vitest/browser-playwright': 4.0.18
'@vitest/browser-preview': 4.0.18
'@vitest/browser-webdriverio': 4.0.18
'@vitest/ui': 4.0.18
happy-dom: '*'
jsdom: '*'
peerDependenciesMeta:
'@edge-runtime/vm':
optional: true
'@opentelemetry/api':
optional: true
'@types/node':
optional: true
'@vitest/browser-playwright':
optional: true
'@vitest/browser-preview':
optional: true
'@vitest/browser-webdriverio':
optional: true
'@vitest/ui':
optional: true
happy-dom:
optional: true
jsdom:
optional: true
vscode-jsonrpc@8.2.0:
resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==}
engines: {node: '>=14.0.0'}
@ -6073,6 +6185,11 @@ packages:
engines: {node: ^16.13.0 || >=18.0.0}
hasBin: true
why-is-node-running@2.3.0:
resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
engines: {node: '>=8'}
hasBin: true
wicked-good-xpath@1.3.0:
resolution: {integrity: sha512-Gd9+TUn5nXdwj/hFsPVx5cuHHiF5Bwuc30jZ4+ronF1qHK5O7HD0sgmXWSEgwKquT3ClLoKPVbO6qGwVwLzvAw==}
@ -7816,6 +7933,8 @@ snapshots:
core-js: 3.48.0
nanopop: 2.4.2
'@standard-schema/spec@1.1.0': {}
'@ts-graphviz/adapter@2.0.6':
dependencies:
'@ts-graphviz/common': 2.1.5
@ -7863,6 +7982,11 @@ snapshots:
'@types/connect': 3.4.38
'@types/node': 22.19.15
'@types/chai@5.2.3':
dependencies:
'@types/deep-eql': 4.0.2
assertion-error: 2.0.1
'@types/connect@3.4.38':
dependencies:
'@types/node': 22.19.15
@ -7984,6 +8108,8 @@ snapshots:
'@types/d3-transition': 3.0.9
'@types/d3-zoom': 3.0.8
'@types/deep-eql@4.0.2': {}
'@types/estree@1.0.8': {}
'@types/express-serve-static-core@5.1.1':
@ -8214,6 +8340,45 @@ snapshots:
vite: 7.3.1(@types/node@22.19.15)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0)
vue: 3.5.29(typescript@5.9.3)
'@vitest/expect@4.0.18':
dependencies:
'@standard-schema/spec': 1.1.0
'@types/chai': 5.2.3
'@vitest/spy': 4.0.18
'@vitest/utils': 4.0.18
chai: 6.2.2
tinyrainbow: 3.0.3
'@vitest/mocker@4.0.18(vite@7.3.1(@types/node@22.19.15)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0))':
dependencies:
'@vitest/spy': 4.0.18
estree-walker: 3.0.3
magic-string: 0.30.21
optionalDependencies:
vite: 7.3.1(@types/node@22.19.15)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0)
'@vitest/pretty-format@4.0.18':
dependencies:
tinyrainbow: 3.0.3
'@vitest/runner@4.0.18':
dependencies:
'@vitest/utils': 4.0.18
pathe: 2.0.3
'@vitest/snapshot@4.0.18':
dependencies:
'@vitest/pretty-format': 4.0.18
magic-string: 0.30.21
pathe: 2.0.3
'@vitest/spy@4.0.18': {}
'@vitest/utils@4.0.18':
dependencies:
'@vitest/pretty-format': 4.0.18
tinyrainbow: 3.0.3
'@volar/language-core@2.4.15':
dependencies:
'@volar/source-map': 2.4.15
@ -8619,6 +8784,8 @@ snapshots:
get-intrinsic: 1.3.0
is-array-buffer: 3.0.5
assertion-error@2.0.1: {}
ast-module-types@6.0.1: {}
async-function@1.0.0: {}
@ -8875,6 +9042,8 @@ snapshots:
ccount@2.0.1: {}
chai@6.2.2: {}
chalk@4.1.2:
dependencies:
ansi-styles: 4.3.0
@ -9574,6 +9743,8 @@ snapshots:
iterator.prototype: 1.1.5
safe-array-concat: 1.1.3
es-module-lexer@1.7.0: {}
es-object-atoms@1.1.1:
dependencies:
es-errors: 1.3.0
@ -9798,6 +9969,10 @@ snapshots:
estree-walker@2.0.2: {}
estree-walker@3.0.3:
dependencies:
'@types/estree': 1.0.8
esutils@2.0.3: {}
etag@1.8.1: {}
@ -9814,6 +9989,8 @@ snapshots:
events@3.3.0: {}
expect-type@1.3.0: {}
exponential-backoff@3.1.3: {}
express@5.2.1:
@ -10978,6 +11155,8 @@ snapshots:
define-properties: 1.2.1
es-object-atoms: 1.1.1
obug@2.1.1: {}
ogg-opus-decoder@1.7.3:
dependencies:
'@wasm-audio-decoders/common': 9.0.7
@ -11802,6 +11981,8 @@ snapshots:
side-channel-map: 1.0.1
side-channel-weakmap: 1.0.2
siginfo@2.0.0: {}
signal-exit@3.0.7: {}
signal-exit@4.1.0: {}
@ -11854,8 +12035,12 @@ snapshots:
dependencies:
minipass: 7.1.3
stackback@0.0.2: {}
statuses@2.0.2: {}
std-env@3.10.0: {}
stop-iteration-iterator@1.1.0:
dependencies:
es-errors: 1.3.0
@ -12067,6 +12252,8 @@ snapshots:
through@2.3.8: {}
tinybench@2.9.0: {}
tinyexec@1.0.2: {}
tinyglobby@0.2.15:
@ -12074,6 +12261,8 @@ snapshots:
fdir: 6.5.0(picomatch@4.0.3)
picomatch: 4.0.3
tinyrainbow@3.0.3: {}
to-buffer@1.2.2:
dependencies:
isarray: 2.0.5
@ -12445,6 +12634,43 @@ snapshots:
- typescript
- universal-cookie
vitest@4.0.18(@types/node@22.19.15)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0):
dependencies:
'@vitest/expect': 4.0.18
'@vitest/mocker': 4.0.18(vite@7.3.1(@types/node@22.19.15)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0))
'@vitest/pretty-format': 4.0.18
'@vitest/runner': 4.0.18
'@vitest/snapshot': 4.0.18
'@vitest/spy': 4.0.18
'@vitest/utils': 4.0.18
es-module-lexer: 1.7.0
expect-type: 1.3.0
magic-string: 0.30.21
obug: 2.1.1
pathe: 2.0.3
picomatch: 4.0.3
std-env: 3.10.0
tinybench: 2.9.0
tinyexec: 1.0.2
tinyglobby: 0.2.15
tinyrainbow: 3.0.3
vite: 7.3.1(@types/node@22.19.15)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0)
why-is-node-running: 2.3.0
optionalDependencies:
'@types/node': 22.19.15
transitivePeerDependencies:
- jiti
- less
- lightningcss
- msw
- sass
- sass-embedded
- stylus
- sugarss
- terser
- tsx
- yaml
vscode-jsonrpc@8.2.0: {}
vscode-languageserver-protocol@3.17.5:
@ -12573,6 +12799,11 @@ snapshots:
dependencies:
isexe: 3.1.5
why-is-node-running@2.3.0:
dependencies:
siginfo: 2.0.0
stackback: 0.0.2
wicked-good-xpath@1.3.0: {}
word-wrap@1.2.5: {}