ListenerAction Script Protocol
Overview
ListenerAction scripts run custom code when a State Machine listener event fires. Unlike Listeners (which are function signatures for addListener()), ListenerAction is a script type that you attach to state machine listener events.
This page is aligned to the September 4, 2026 baseline:
@rive-app/canvas@2.42.0 backed by C++ runtime-v0.1.344, with source pins
tracked in Runtime Compatibility Baseline.
The current Rive Editor analyzer accepts KeyboardEvent, TextInput,
FocusEvent, ReportedEvent, ViewModelChange, NoneEvent, and the three
gamepad payload types below. Older *Invocation names remain relevant when
reading pinned runtime source, but are not the current Editor reference names.
When to use ListenerAction scripts:
- Custom side-effects when a state machine listener triggers
- Logging, analytics, or audio playback on listener events
- Complex logic that needs listener context from the trigger event
Type Definition
export type ListenerAction<T> = {
init: ((self: T, context: Context) -> boolean)?,
performAction: (self: T, listenerContext: ListenerContext) -> (),
perform: ((self: T, pointerEvent: PointerEvent) -> ())?, -- Legacy/deprecated
} & T
Protocol Methods
| Function | Signature | Required | Description |
|---|---|---|---|
init | (self: T, context: Context): boolean | No | One-time initialization |
performAction | (self: T, listenerContext: ListenerContext): () | Yes (preferred) | Runs when the listener fires |
perform | (self: T, pointerEvent: PointerEvent): () | Legacy | Deprecated callback from older scaffolds |
performAction(self, listenerContext) — REQUIRED (Preferred)
Called when the state machine listener event fires. This is the current callback shape recommended for new scripts.
function performAction(self: MyAction, listenerContext: ListenerContext)
if listenerContext:isPointerEvent() then
local evt = listenerContext:asPointerEvent()
if evt then
print("pointer:", evt.type)
end
elseif listenerContext:isKeyboardEvent() then
local evt = listenerContext:asKeyboardEvent()
if evt then
print("keyboard phase:", evt.phase)
end
end
end
ListenerContext Event Kinds
ListenerContext is a tagged payload union. Use is... guards before as... casts.
| Guard | Accessor | Typical fields |
|---|---|---|
isPointerEvent() | asPointerEvent(): PointerEvent? | id, position, type: PointerType |
isKeyboardEvent() | asKeyboardEvent(): KeyboardEvent? | key, shift, control, alt, meta, phase: KeyPhase |
isTextInput() | asTextInput(): TextInput? | text |
isFocus() | asFocus(): FocusEvent? | isFocus |
isReportedEvent() | asReportedEvent(): ReportedEvent? | Presence/kind only in the current Editor reference |
isViewModelChange() | asViewModelChange(): ViewModelChange? | Presence/kind only in the current Editor reference |
isGamepadConnected() | asGamepadConnected(): GamepadConnected? | Full connected-device state |
isGamepadEvent() | asGamepadEvent(): GamepadEvent? | Change metadata plus full device state |
isGamepadDisconnected() | asGamepadDisconnected(): GamepadDisconnected? | deviceId |
isNone() | asNone(): NoneEvent? | Fallback/unknown kind |
For gamepad state, the current reference includes buttons, axes, mapping
metadata, named standard buttons, stick vectors, triggers, and the
buttonPressed, buttonValue, and axis query methods. See the
Events API for the complete field map.
perform(self, pointerEvent) — Legacy/Deprecated
Older examples may still use perform(self, pointerEvent). Keep it only for legacy compatibility work; prefer performAction for all new course code.
init(self, context) — Optional
Called once when the action is created. Use for setup and accessing the ViewModel.
function init(self: MyAction, context: Context): boolean
local vm = context:viewModel()
-- Setup logic
return true
end
Template
export type MyAction = {
clickCount: number,
}
function performAction(self: MyAction, listenerContext: ListenerContext)
self.clickCount += 1
print("Listener fire #" .. tostring(self.clickCount))
end
return function(): ListenerAction<MyAction>
return {
clickCount = 0,
performAction = performAction,
}
end
How to Apply
- Create a ListenerAction script in the Assets panel
- In the State Machine editor, select a Listener
- Add the script as an action on the listener
- The
performActionfunction will run each time the listener triggers
Key Differences from Node Scripts
| Feature | Node Script | ListenerAction |
|---|---|---|
| Lifecycle | init, advance, update, draw | init, performAction |
| Rendering | Has draw() and Renderer access | No rendering |
| When it runs | Every frame (advance/draw) | Only when listener fires |
| Event context | Via pointerDown/pointerMove handlers | Via performAction(listenerContext) |
Practice Exercise
Exercise 1: Click Counter Action ⭐
Premise
ListenerAction scripts are event-driven. They run only when the listener fires, making them ideal for analytics, counters, and side-effects.
By the end of this exercise, you will branch on listenerContext event kind, count pointer events, and print an ANSWER: line after 3 pointer triggers.
Starter Code
export type ClickCounterAction = {
pointerCount: number,
lastKind: string,
}
function performAction(self: ClickCounterAction, listenerContext: ListenerContext)
-- TODO 1: Detect event kind with listenerContext:is...()
-- Set self.lastKind to one of: "pointer", "keyboard", "text", "focus", "other"
-- TODO 2: If kind is pointer, increment self.pointerCount
-- TODO 3: Print a debug line with kind and count
-- print("listener kind", self.lastKind, "count", self.pointerCount)
-- TODO 4: When pointerCount reaches 3, print:
-- "ANSWER: kind=pointer,count=3"
end
return function(): ListenerAction<ClickCounterAction>
return {
performAction = performAction,
pointerCount = 0,
lastKind = "other",
}
end
Assignment
- Attach the script as a listener action in a state machine Listener
- Trigger pointer-driven listener events 3 times
- Copy the
ANSWER:line from Console
Verify Your Answer
Knowledge Check
See Also: Listeners, Script Types Overview, Events API
Next Steps
- Continue to TransitionCondition Protocol
- Verify surface status in Runtime Compatibility Baseline
- Need a refresher? Review Quick Reference