{"version":3,"file":"FollowAnimation.mjs","sources":["../../../src/animation/FollowAnimation.ts"],"sourcesContent":["import { cancelFrame, frame } from \"../frameloop\"\nimport { time } from \"../frameloop/sync-time\"\nimport { FrameData } from \"../frameloop/types\"\nimport { DriverControls } from \"./drivers/types\"\nimport { keyframes as keyframesGenerator } from \"./generators/keyframes\"\nimport { calcGeneratorVelocity } from \"./generators/utils/velocity\"\nimport {\n    GeneratorFactory,\n    KeyframeGenerator,\n    MotionValueAnimation,\n    ValueAnimationOptions,\n} from \"./types\"\nimport { replaceTransitionType } from \"./utils/replace-transition-type\"\nimport { WithPromise } from \"./utils/WithPromise\"\n\n/**\n * Every running follower ticks from this one frame callback.\n */\nconst active = new Set<FollowAnimation>()\n\nconst tickAll = ({ timestamp }: FrameData) => {\n    active.forEach((animation) => animation.tick(timestamp))\n}\n\n/**\n * A lean animation for values that follow a target (`followValue`,\n * `springValue`): a single numeric generator, retargeted in place as\n * the target moves and ticked from a frame callback shared by every\n * follower. Only implements the controls `MotionValue.animation`\n * exposes. Repeat options are ignored: a follower only ever heads for\n * its latest target.\n */\nexport class FollowAnimation\n    extends WithPromise\n    implements MotionValueAnimation\n{\n    state: AnimationPlayState = \"idle\"\n\n    private generator: KeyframeGenerator<number>\n\n    private factory: GeneratorFactory\n\n    private options: ValueAnimationOptions<number>\n\n    private startTime = 0\n\n    private currentTime = 0\n\n    /**\n     * Whether the first tick has fired `onPlay`.\n     */\n    private started = false\n\n    /**\n     * A target set since the last tick, applied at the next one so any\n     * number of sets per frame cost one retarget.\n     */\n    private hasNextTarget = false\n\n    private nextTarget = 0\n\n    private nextVelocity?: number\n\n    /**\n     * Only created when a custom driver is passed, otherwise the\n     * follower ticks from the shared frame callback.\n     */\n    private driver?: DriverControls\n\n    constructor(options: ValueAnimationOptions<number>) {\n        super()\n\n        this.options = options\n        replaceTransitionType(options)\n        this.factory = (options.type as GeneratorFactory) || keyframesGenerator\n        this.generator = this.factory(options)\n\n        const { driver } = options\n        if (driver) this.driver = driver((timestamp) => this.tick(timestamp))\n\n        this.startTime = this.now()\n        this.state = \"running\"\n\n        if (this.driver) {\n            this.driver.start()\n        } else {\n            active.size || frame.update(tickAll, true)\n            active.add(this)\n        }\n    }\n\n    /**\n     * Aim at a new target from wherever the animation has reached by the\n     * next tick. `velocity` fixes the velocity to steer with, otherwise\n     * the generator's is used.\n     */\n    setTarget(target: number, velocity?: number) {\n        this.nextTarget = target\n        this.nextVelocity = velocity\n        this.hasNextTarget = true\n    }\n\n    /**\n     * Steer towards new keyframes from the current position and velocity.\n     * Springs are updated in place; other generators are recreated.\n     */\n    retarget(keyframes: number[], velocity: number) {\n        const { options, generator } = this\n        options.keyframes = keyframes\n        options.velocity = velocity\n        this.startTime = this.now()\n        this.currentTime = 0\n\n        if (generator.retarget) {\n            generator.retarget(keyframes, velocity)\n        } else {\n            this.generator = this.factory(options)\n        }\n    }\n\n    tick(timestamp: number) {\n        const { options, hasNextTarget: retargeted } = this\n        const { delay = 0, onUpdate, onPlay } = options\n        const elapsed = Math.round(timestamp - this.startTime) - delay\n        const t = (this.currentTime = Math.max(0, elapsed))\n        const state = this.generator.next(t)\n        // Hold the origin through the delay, even if the generator is already at rest\n        const value = elapsed < 0 ? options.keyframes[0] : state.value\n\n        if (retargeted) {\n            /**\n             * Steer from where the current trajectory has reached this\n             * frame, then render that position as the new origin.\n             */\n            this.hasNextTarget = false\n            const { keyframes } = options\n            keyframes[0] = value\n            keyframes[1] = this.nextTarget\n            this.retarget(\n                keyframes,\n                this.nextVelocity ?? this.getGeneratorVelocity()\n            )\n        }\n\n        if (retargeted || !this.started) {\n            this.started = true\n            onPlay?.()\n        }\n\n        /**\n         * Callbacks may stop this animation or set a new target, so check\n         * the live state rather than what was read at the top of the tick.\n         */\n        if (this.state !== \"running\") return\n\n        onUpdate?.(value)\n\n        if (\n            state.done &&\n            elapsed >= 0 &&\n            !retargeted &&\n            !this.hasNextTarget &&\n            this.state === \"running\"\n        ) {\n            this.notifyFinished()\n            this.teardown()\n            this.state = \"finished\"\n            options.onComplete?.()\n        }\n    }\n\n    getGeneratorVelocity(): number {\n        return calcGeneratorVelocity(\n            this.generator,\n            this.currentTime,\n            this.options.velocity\n        )\n    }\n\n    /**\n     * Bound to the instance so `animation.stop` can be passed around.\n     */\n    stop = () => {\n        if (this.state === \"idle\") return\n        this.teardown()\n        this.options.onStop?.()\n    }\n\n    private now() {\n        return this.driver ? this.driver.now() : time.now()\n    }\n\n    private teardown() {\n        this.state = \"idle\"\n\n        if (this.driver) {\n            this.driver.stop()\n        } else {\n            active.delete(this)\n            active.size || cancelFrame(tickAll)\n        }\n    }\n}\n"],"names":["keyframesGenerator"],"mappings":"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"}