FlowStates
FlowStates provide a high-level declarative mechanism for managing smooth transitions between numeric or color properties in Aircada.
Instead of writing custom lerp, tween, or frame-tick algorithms, developers define target states, easing curves, and durations in a class configuration and let the underlying engine animate property values automatically.
API & Type Definitions#
type EasingType = "linear" | "easeIn" | "easeOut" | "easeInOut" | "spring" | "easeOutBack" | string;
export interface FlowStateConfig<T> {
duration?: number; // In seconds
ease?: EasingType;
properties: Partial<T>; // Must strictly map to @Property keys
onEvent?: string | string[]; // Global events that trigger this state
}
export type FlowStateMap<T> = Record<string, FlowStateConfig<T>>;Syntax: Mapping & Events#
Property Mapping
The properties object inside a state configuration must contain keys that match your Operator's @Property fields. Values provided here become the animation targets.
Event Listening (onEvent)
By specifying an onEvent, you tell the engine to automatically trigger a transition when a specific signal is detected on the global message bus.
Event Emitting
While states define the visual goal, @Output properties are used to 'emit' signals when transitions occur, allowing the UI to react to state changes.
Code Implementation Template#
import {
Operator, FloatProperty, Input, Output, Context, States,
Setup, Dispose, AirEvent, FlowStateMap, AircadaContext
} from "@aircada/spec";
import { Events, EventPayloads } from "./registry.generated";
@Operator({ type: "smart_door", name: "Smart Door" })
export class SmartDoorOperator {
@Context()
air!: AircadaContext;
@FloatProperty()
doorAngle: number = 0.0;
@FloatProperty()
doorOpacity: number = 1.0;
// 1. DECLARE STATES: Use @States to define goal property values and easing
@States()
states = {
closed: {
duration: 0.5,
ease: "easeOut",
properties: { doorAngle: 0.0, doorOpacity: 1.0 },
onEvent: [Events.RESET_CONFIG]
},
open: {
duration: 1.2,
ease: "spring",
properties: { doorAngle: 90.0, doorOpacity: 1.0 },
onEvent: Events.OPEN_DOORS
}
} satisfies FlowStateMap<SmartDoorOperator>;
@Output({ name: "On Door Opened", emits: Events.DOOR_OPENED })
onDoorOpened = new AirEvent<EventPayloads[typeof Events.DOOR_OPENED]>();
private unsubDoorOpened?: () => void;
@Setup()
setup() {
this.unsubDoorOpened = this.onDoorOpened.addListener(() => {
console.log(`[Local Event Fired] Door reached open state.`);
});
}
@Input({ name: "Force Open" })
forceOpen() {
// 2. TRIGGER TRANSITION: Use the flows.transitionTo helper to start the animation
this.air.flows.transitionTo(this, "open");
this.onDoorOpened.invoke();
}
@Dispose()
dispose() {
if (this.unsubDoorOpened) {
this.unsubDoorOpened();
}
}
}





