Animation
Steps and cues as the runtime plays them: the keyframe tracks a cue.to declares, the interpolations that read them, and the controller that advances a step.
@internote/canvas-sdk#Functions
(
anchor: CueAnchor,
steps: StepController,
progress: StepProgress,
): number | null(t: number): numberThe one curve, named once. It is what easing: smooth means on a keyframe, what a cue's own window runs on, and what a view glides along — and it had been written out longhand in all three places plus twice more in the library, which is five chances for one of them to be a different curve.
(
value: unknown,
steps: StepController,
kind?: InterpolationKind,
interpolations?: InterpolationRegistry,
): unknown!cue.to keyframe track is right now: the endpoints of its current move and the eased progress between them.(track: KeyframeTrack, steps: StepController): TrackTransitionThe last keyframe to fire is in effect. It holds the previous value for its delayMs lead-in, then eases over its over: window, then settles. Tied fire times keep source order.
track!cue.to chain: a base value and anchored keyframes.steps!cue.to keyframe track, blended by the attribute's interpolation kind while it moves.(
track: KeyframeTrack,
steps: StepController,
kind?: InterpolationKind,
interpolations?: InterpolationRegistry,
): ResolvedTrackValueResolves the transition with resolveTrackTransition; mid-move, the interpolator registered for kind blends the endpoints, and a kind with no interpolator snaps.
track!cue.to chain.stepskindinterpolationskind up in.#Classes
typeof EnvironmentAnimationRuntimeEvery cue construct lives inside the environment it animates and acts only within it; two environments synchronise only by anchoring to the same step indices, so a runtime never sees beyond its own node. The runtime:
- tracks presence: children wrapped in
cue/cue.drawappear when theirin:anchor has fired and disappear whenout:has; - resolves
!cue.tokeyframe tracks on attributes against the shared step controller; - calls the environment's step hook and verb handlers on step transitions — environments implement handlers, they never own timelines.
Headless by construction: no React, no DOM. Render hosts subscribe and re-render; the test host asserts directly. The host builds one per mounted environment and exposes it as ctx.animation.
Interpolator per interpolation kind.typeof InterpolationRegistryEnvironments never own timelines, they only declare an attribute's kind, so this registry is the whole extension point for new value shapes. Interpolators run on refined values (numbers, interval records, coordinate lists…), not source text. Kinds with no meaningful in-between (discrete, and colour at the data layer — palette names are not blendable; a surface that wants visual blending resolves them to CSS and interpolates there) snap at t ≥ 0.5, as does any kind with nothing registered.
typeof StepControllerA scene's step timeline is the partition sequence its step markers (===) carve out of the body: step 0 is the opening partition, step N becomes current when the reader passes the Nth marker. Every drive mode feeds the same controller:
- reading view: the narrative scroll-spy calls
goTowith the detected partition index; - presentation: keyboard step-advance calls
advance/back— presentation is a drive mode over this API, not a fork; - headless tests: call any of them directly.
Steps also partition the scene's animation for the transport: replay plays the current step's choreography again and nothing else.
The controller records when each step was entered so time-relative anchors (at: 3 + 1.5s) and scene-time anchors (at: 1.5s) resolve against real progression. Time-based motion stays subordinate to steps.
#Constants
InterpolationRegistry#Interfaces
attributes(…) attributes in source order, values already refined to their wire shapes.Array<{ identifier: string; value: unknown }>body{…} body: child nodes, inline content or a value, per the node's body policy.unknowndetail[…] detail, where the node has one (a scene's canvas content).unknownidcurve#flight, D22) — absent unless written.stringidentifiercanvas.graph, curve or cue.draw.stringinstance_idstringEnvironmentAnimationRuntime is built from.ctxCtxinterpolationsInterpolationRegistrynodecanvas.<name> node this runtime animates.CompiledNodeonStep(step: number, ctx: Ctx) => voidstepsStepControllerverbsRecord<string, VerbHandler<Ctx>>activeFrameCueKeyframe | nulltransitioningover: transition window.booleanvalueunknownStepController is built from.clock() => numberDate.nowinitialStepnumber0stepCountnumbersceneTimeMsnumberstepnumbertimeInStepMsnumberelapsedMs!cue.zoom box is drawn out of this while the value is still holding.numberframeCueKeyframefromunknowntnumbertofrom once settled or during a lead-in delay.unknowntransitioningover: window and actually moving.booleannodeCompiledNodephasein: or at:) or its out: anchor fired.enterexitstepnumberverbstring#Types
numbernumber-listedgescolourpointintervaldiscreteDeclared per attribute; !cue.to on an attribute with no declared kind is a semantic error, so the animatable surface grows deliberately.
BuiltinInterpolationKind | (string & {})Open on purpose. An environment may declare a shape of its own and supply the interpolator for it through its definition's interpolations — the graph's plane transform is one, and while this union was closed it could only be honoured by adding 'transform' here and its interpolator to the SDK's defaults, which is how a package that knows nothing about graphs came to carry a { kind: 'matrix' } lerp. The intersection with {} is what keeps the built-in names in autocomplete while admitting the rest.
t (0 to 1).(from: unknown, to: unknown, t: number) => unknown(step: number, previous: number) => void(invocation: VerbInvocation, ctx: Ctx) => voidOnly step anchors dispatch verbs; time anchors do not.