feature: add support for multiple inputs on listeners (#11862) 501b7f488c
* feature: add support for multiple inputs on listeners

Co-authored-by: hernan <hernan@rive.app>
diff --git a/.rive_head b/.rive_head
index cc9d1b5..fb8495d 100644
--- a/.rive_head
+++ b/.rive_head
@@ -1 +1 @@
-89305a5fedb7a617c22dad6d7818c9dcc4f1cf84
+501b7f488c5af1947ae46f5998dba79d829ca7ef
diff --git a/dev/defs/animation/listener_types/listener_input_type.json b/dev/defs/animation/listener_types/listener_input_type.json
new file mode 100644
index 0000000..c9d7e18
--- /dev/null
+++ b/dev/defs/animation/listener_types/listener_input_type.json
@@ -0,0 +1,28 @@
+{
+  "name": "ListenerInputType",
+  "key": {
+    "int": 658,
+    "string": "listenerinputtype"
+  },
+  "properties": {
+    "listenerTypeValue": {
+      "type": "uint",
+      "initialValue": "0",
+      "key": {
+        "int": 965,
+        "string": "listenertypevalue"
+      },
+      "description": "Listener type (hover, click, etc)."
+    },
+    "listenerId": {
+      "type": "Id",
+      "initialValue": "Core.missingId",
+      "key": {
+        "int": 961,
+        "string": "listenerid"
+      },
+      "description": "The state machine listener this listener belongs to",
+      "runtime": false
+    }
+  }
+}
\ No newline at end of file
diff --git a/dev/defs/animation/listener_types/listener_input_type_event.json b/dev/defs/animation/listener_types/listener_input_type_event.json
new file mode 100644
index 0000000..3b8186d
--- /dev/null
+++ b/dev/defs/animation/listener_types/listener_input_type_event.json
@@ -0,0 +1,21 @@
+{
+  "name": "ListenerInputTypeEvent",
+  "key": {
+    "int": 659,
+    "string": "listenerinputtypeevent"
+  },
+  "extends": "animation/listener_types/listener_input_type.json",
+  "properties": {
+    "eventId": {
+      "type": "Id",
+      "typeRuntime": "uint",
+      "initialValue": "Core.missingId",
+      "initialValueRuntime": "-1",
+      "key": {
+        "int": 962,
+        "string": "eventid"
+      },
+      "description": "Event id for the associated event"
+    }
+  }
+}
\ No newline at end of file
diff --git a/dev/defs/animation/listener_types/listener_input_type_viewmodel.json b/dev/defs/animation/listener_types/listener_input_type_viewmodel.json
new file mode 100644
index 0000000..2031b4c
--- /dev/null
+++ b/dev/defs/animation/listener_types/listener_input_type_viewmodel.json
@@ -0,0 +1,31 @@
+{
+  "name": "ListenerInputTypeViewModel",
+  "key": {
+    "int": 660,
+    "string": "listenerinputtypeviewmodel"
+  },
+  "extends": "animation/listener_types/listener_input_type.json",
+  "properties": {
+    "viewModelPathIds": {
+      "type": "List<Id>",
+      "typeRuntime": "Bytes",
+      "encoded": true,
+      "initialValue": "[]",
+      "key": {
+        "int": 963,
+        "string": "viewmodelpathids"
+      },
+      "description": "Path to the selected view model trigger property if listenerType is a viewModel trigger."
+    },
+    "isDataBindPathRelative": {
+      "type": "bool",
+      "initialValue": "false",
+      "key": {
+        "int": 964,
+        "string": "isdatabindpathrelative"
+      },
+      "description": "Whether the data bind path is relative",
+      "runtime": false
+    }
+  }
+}
\ No newline at end of file
diff --git a/dev/defs/animation/state_machine_listener.json b/dev/defs/animation/state_machine_listener.json
index fc6e50a..52274aa 100644
--- a/dev/defs/animation/state_machine_listener.json
+++ b/dev/defs/animation/state_machine_listener.json
@@ -1,8 +1,8 @@
 {
   "name": "StateMachineListener",
   "key": {
-    "int": 114,
-    "string": "stateMachineListener"
+    "int": 654,
+    "string": "statemachinelistener"
   },
   "extends": "animation/state_machine_component.json",
   "properties": {
@@ -16,47 +16,6 @@
         "string": "targetid"
       },
       "description": "Identifier used to track the object use as a target for this listener."
-    },
-    "listenerTypeValue": {
-      "type": "uint",
-      "initialValue": "0",
-      "key": {
-        "int": 225,
-        "string": "listenertypevalue"
-      },
-      "description": "Listener type (hover, click, etc)."
-    },
-    "eventId": {
-      "type": "Id",
-      "typeRuntime": "uint",
-      "initialValue": "Core.missingId",
-      "initialValueRuntime": "-1",
-      "key": {
-        "int": 399,
-        "string": "eventid"
-      },
-      "description": "Event id for the associated event, if listenerType is event"
-    },
-    "viewModelPathIds": {
-      "type": "List<Id>",
-      "typeRuntime": "Bytes",
-      "encoded": true,
-      "initialValue": "[]",
-      "key": {
-        "int": 868,
-        "string": "viewmodelpathids"
-      },
-      "description": "Path to the selected view model trigger property if listenerType is a viewModel trigger."
-    },
-    "isDataBindPathRelative": {
-      "type": "bool",
-      "initialValue": "false",
-      "key": {
-        "int": 924,
-        "string": "isdatabindpathrelative"
-      },
-      "description": "Whether the data bind path is relative",
-      "runtime": false
     }
   }
 }
\ No newline at end of file
diff --git a/dev/defs/animation/state_machine_listener_single.json b/dev/defs/animation/state_machine_listener_single.json
new file mode 100644
index 0000000..4e106a9
--- /dev/null
+++ b/dev/defs/animation/state_machine_listener_single.json
@@ -0,0 +1,51 @@
+{
+  "name": "StateMachineListenerSingle",
+  "key": {
+    "int": 114,
+    "string": "stateMachineListener"
+  },
+  "extends": "animation/state_machine_listener.json",
+  "properties": {
+    "listenerTypeValue": {
+      "type": "uint",
+      "initialValue": "0",
+      "key": {
+        "int": 225,
+        "string": "listenertypevalue"
+      },
+      "description": "Listener type (hover, click, etc)."
+    },
+    "eventId": {
+      "type": "Id",
+      "typeRuntime": "uint",
+      "initialValue": "Core.missingId",
+      "initialValueRuntime": "-1",
+      "key": {
+        "int": 399,
+        "string": "eventid"
+      },
+      "description": "Event id for the associated event, if listenerType is event"
+    },
+    "viewModelPathIds": {
+      "type": "List<Id>",
+      "typeRuntime": "Bytes",
+      "encoded": true,
+      "initialValue": "[]",
+      "key": {
+        "int": 868,
+        "string": "viewmodelpathids"
+      },
+      "description": "Path to the selected view model trigger property if listenerType is a viewModel trigger."
+    },
+    "isDataBindPathRelative": {
+      "type": "bool",
+      "initialValue": "false",
+      "key": {
+        "int": 924,
+        "string": "isdatabindpathrelative"
+      },
+      "description": "Whether the data bind path is relative",
+      "runtime": false
+    }
+  }
+}
\ No newline at end of file
diff --git a/include/rive/animation/focus_listener_group.hpp b/include/rive/animation/focus_listener_group.hpp
index fa1e57c..95fb775 100644
--- a/include/rive/animation/focus_listener_group.hpp
+++ b/include/rive/animation/focus_listener_group.hpp
@@ -30,6 +30,9 @@
     /// Check if this is a focus listener (vs blur listener).
     bool isFocusListener() const { return m_isFocusListener; }
 
+    /// Check if this is a blur listener (vs focus listener).
+    bool isBlurListener() const { return m_isBlurListener; }
+
     // FocusListener interface
     void onFocused() override;
     void onBlurred() override;
@@ -39,6 +42,7 @@
     const StateMachineListener* m_listener;
     StateMachineInstance* m_stateMachineInstance;
     bool m_isFocusListener;
+    bool m_isBlurListener;
 };
 
 } // namespace rive
diff --git a/include/rive/animation/listener_types/listener_input_type.hpp b/include/rive/animation/listener_types/listener_input_type.hpp
new file mode 100644
index 0000000..1d616f4
--- /dev/null
+++ b/include/rive/animation/listener_types/listener_input_type.hpp
@@ -0,0 +1,14 @@
+#ifndef _RIVE_LISTENER_INPUT_TYPE_HPP_
+#define _RIVE_LISTENER_INPUT_TYPE_HPP_
+#include "rive/generated/animation/listener_types/listener_input_type_base.hpp"
+#include <stdio.h>
+namespace rive
+{
+class ListenerInputType : public ListenerInputTypeBase
+{
+public:
+    StatusCode import(ImportStack& importStack) override;
+};
+} // namespace rive
+
+#endif
\ No newline at end of file
diff --git a/include/rive/animation/listener_types/listener_input_type_event.hpp b/include/rive/animation/listener_types/listener_input_type_event.hpp
new file mode 100644
index 0000000..92c3361
--- /dev/null
+++ b/include/rive/animation/listener_types/listener_input_type_event.hpp
@@ -0,0 +1,13 @@
+#ifndef _RIVE_LISTENER_INPUT_TYPE_EVENT_HPP_
+#define _RIVE_LISTENER_INPUT_TYPE_EVENT_HPP_
+#include "rive/generated/animation/listener_types/listener_input_type_event_base.hpp"
+#include <stdio.h>
+namespace rive
+{
+class ListenerInputTypeEvent : public ListenerInputTypeEventBase
+{
+public:
+};
+} // namespace rive
+
+#endif
\ No newline at end of file
diff --git a/include/rive/animation/listener_types/listener_input_type_viewmodel.hpp b/include/rive/animation/listener_types/listener_input_type_viewmodel.hpp
new file mode 100644
index 0000000..0455851
--- /dev/null
+++ b/include/rive/animation/listener_types/listener_input_type_viewmodel.hpp
@@ -0,0 +1,19 @@
+#ifndef _RIVE_LISTENER_INPUT_TYPE_VIEW_MODEL_HPP_
+#define _RIVE_LISTENER_INPUT_TYPE_VIEW_MODEL_HPP_
+#include "rive/generated/animation/listener_types/listener_input_type_viewmodel_base.hpp"
+#include "rive/data_bind_path_referencer.hpp"
+#include <stdio.h>
+namespace rive
+{
+class ListenerInputTypeViewModel : public ListenerInputTypeViewModelBase,
+                                   public DataBindPathReferencer
+{
+public:
+    void decodeViewModelPathIds(Span<const uint8_t> value) override;
+    void copyViewModelPathIds(
+        const ListenerInputTypeViewModelBase& object) override;
+    std::vector<uint32_t> viewModelPathIdsBuffer() const;
+};
+} // namespace rive
+
+#endif
\ No newline at end of file
diff --git a/include/rive/animation/state_machine_listener.hpp b/include/rive/animation/state_machine_listener.hpp
index ac1c9a6..976eb2d 100644
--- a/include/rive/animation/state_machine_listener.hpp
+++ b/include/rive/animation/state_machine_listener.hpp
@@ -3,7 +3,7 @@
 #include "rive/generated/animation/state_machine_listener_base.hpp"
 #include "rive/listener_type.hpp"
 #include "rive/math/vec2d.hpp"
-#include "rive/data_bind_path_referencer.hpp"
+#include "rive/animation/listener_types/listener_input_type.hpp"
 
 namespace rive
 {
@@ -11,8 +11,7 @@
 class StateMachineListenerImporter;
 class ListenerAction;
 class StateMachineInstance;
-class StateMachineListener : public StateMachineListenerBase,
-                             public DataBindPathReferencer
+class StateMachineListener : public StateMachineListenerBase
 {
     friend class StateMachineListenerImporter;
 
@@ -20,26 +19,31 @@
     StateMachineListener();
     ~StateMachineListener() override;
 
-    ListenerType listenerType() const
-    {
-        return (ListenerType)listenerTypeValue();
-    }
+    // ListenerType listenerType() const
+    // {
+    //     return (ListenerType)listenerTypeValue();
+    // }
+    virtual bool hasListener(ListenerType) const;
     size_t actionCount() const { return m_actions.size(); }
+    size_t listenerInputTypeCount() const
+    {
+        return m_listenerInputTypes.size();
+    }
 
     const ListenerAction* action(size_t index) const;
+    const ListenerInputType* listenerInputType(size_t index) const;
     StatusCode import(ImportStack& importStack) override;
 
     void performChanges(StateMachineInstance* stateMachineInstance,
                         Vec2D position,
                         Vec2D previousPosition,
                         int pointerId) const;
-    void decodeViewModelPathIds(Span<const uint8_t> value) override;
-    void copyViewModelPathIds(const StateMachineListenerBase& object) override;
-    std::vector<uint32_t> viewModelPathIdsBuffer() const;
 
 private:
     void addAction(std::unique_ptr<ListenerAction>);
+    void addListenerInputType(std::unique_ptr<ListenerInputType>);
     std::vector<std::unique_ptr<ListenerAction>> m_actions;
+    std::vector<std::unique_ptr<ListenerInputType>> m_listenerInputTypes;
 };
 } // namespace rive
 
diff --git a/include/rive/animation/state_machine_listener_single.hpp b/include/rive/animation/state_machine_listener_single.hpp
new file mode 100644
index 0000000..f972e02
--- /dev/null
+++ b/include/rive/animation/state_machine_listener_single.hpp
@@ -0,0 +1,23 @@
+#ifndef _RIVE_STATE_MACHINE_LISTENER_SINGLE_HPP_
+#define _RIVE_STATE_MACHINE_LISTENER_SINGLE_HPP_
+#include "rive/generated/animation/state_machine_listener_single_base.hpp"
+#include "rive/data_bind_path_referencer.hpp"
+namespace rive
+{
+class StateMachineListenerSingle : public StateMachineListenerSingleBase,
+                                   public DataBindPathReferencer
+{
+public:
+    StatusCode import(ImportStack& importStack) override;
+    bool hasListener(ListenerType listenerType) const override
+    {
+        return listenerTypeValue() == (int)listenerType;
+    }
+    void decodeViewModelPathIds(Span<const uint8_t> value) override;
+    void copyViewModelPathIds(
+        const StateMachineListenerSingleBase& object) override;
+    std::vector<uint32_t> viewModelPathIdsBuffer() const;
+};
+} // namespace rive
+
+#endif
\ No newline at end of file
diff --git a/include/rive/generated/animation/listener_types/listener_input_type_base.hpp b/include/rive/generated/animation/listener_types/listener_input_type_base.hpp
new file mode 100644
index 0000000..c393a4c
--- /dev/null
+++ b/include/rive/generated/animation/listener_types/listener_input_type_base.hpp
@@ -0,0 +1,69 @@
+#ifndef _RIVE_LISTENER_INPUT_TYPE_BASE_HPP_
+#define _RIVE_LISTENER_INPUT_TYPE_BASE_HPP_
+#include "rive/core.hpp"
+#include "rive/core/field_types/core_uint_type.hpp"
+namespace rive
+{
+class ListenerInputTypeBase : public Core
+{
+protected:
+    typedef Core Super;
+
+public:
+    static const uint16_t typeKey = 658;
+
+    /// Helper to quickly determine if a core object extends another without
+    /// RTTI at runtime.
+    bool isTypeOf(uint16_t typeKey) const override
+    {
+        switch (typeKey)
+        {
+            case ListenerInputTypeBase::typeKey:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    uint16_t coreType() const override { return typeKey; }
+
+    static const uint16_t listenerTypeValuePropertyKey = 965;
+
+protected:
+    uint32_t m_ListenerTypeValue = 0;
+
+public:
+    inline uint32_t listenerTypeValue() const { return m_ListenerTypeValue; }
+    void listenerTypeValue(uint32_t value)
+    {
+        if (m_ListenerTypeValue == value)
+        {
+            return;
+        }
+        m_ListenerTypeValue = value;
+        listenerTypeValueChanged();
+    }
+
+    Core* clone() const override;
+    void copy(const ListenerInputTypeBase& object)
+    {
+        m_ListenerTypeValue = object.m_ListenerTypeValue;
+    }
+
+    bool deserialize(uint16_t propertyKey, BinaryReader& reader) override
+    {
+        switch (propertyKey)
+        {
+            case listenerTypeValuePropertyKey:
+                m_ListenerTypeValue = CoreUintType::deserialize(reader);
+                return true;
+        }
+        return false;
+    }
+
+protected:
+    virtual void listenerTypeValueChanged() {}
+};
+} // namespace rive
+
+#endif
\ No newline at end of file
diff --git a/include/rive/generated/animation/listener_types/listener_input_type_event_base.hpp b/include/rive/generated/animation/listener_types/listener_input_type_event_base.hpp
new file mode 100644
index 0000000..f0de47d
--- /dev/null
+++ b/include/rive/generated/animation/listener_types/listener_input_type_event_base.hpp
@@ -0,0 +1,71 @@
+#ifndef _RIVE_LISTENER_INPUT_TYPE_EVENT_BASE_HPP_
+#define _RIVE_LISTENER_INPUT_TYPE_EVENT_BASE_HPP_
+#include "rive/animation/listener_types/listener_input_type.hpp"
+#include "rive/core/field_types/core_uint_type.hpp"
+namespace rive
+{
+class ListenerInputTypeEventBase : public ListenerInputType
+{
+protected:
+    typedef ListenerInputType Super;
+
+public:
+    static const uint16_t typeKey = 659;
+
+    /// Helper to quickly determine if a core object extends another without
+    /// RTTI at runtime.
+    bool isTypeOf(uint16_t typeKey) const override
+    {
+        switch (typeKey)
+        {
+            case ListenerInputTypeEventBase::typeKey:
+            case ListenerInputTypeBase::typeKey:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    uint16_t coreType() const override { return typeKey; }
+
+    static const uint16_t eventIdPropertyKey = 962;
+
+protected:
+    uint32_t m_EventId = -1;
+
+public:
+    inline uint32_t eventId() const { return m_EventId; }
+    void eventId(uint32_t value)
+    {
+        if (m_EventId == value)
+        {
+            return;
+        }
+        m_EventId = value;
+        eventIdChanged();
+    }
+
+    Core* clone() const override;
+    void copy(const ListenerInputTypeEventBase& object)
+    {
+        m_EventId = object.m_EventId;
+        ListenerInputType::copy(object);
+    }
+
+    bool deserialize(uint16_t propertyKey, BinaryReader& reader) override
+    {
+        switch (propertyKey)
+        {
+            case eventIdPropertyKey:
+                m_EventId = CoreUintType::deserialize(reader);
+                return true;
+        }
+        return ListenerInputType::deserialize(propertyKey, reader);
+    }
+
+protected:
+    virtual void eventIdChanged() {}
+};
+} // namespace rive
+
+#endif
\ No newline at end of file
diff --git a/include/rive/generated/animation/listener_types/listener_input_type_viewmodel_base.hpp b/include/rive/generated/animation/listener_types/listener_input_type_viewmodel_base.hpp
new file mode 100644
index 0000000..e657916
--- /dev/null
+++ b/include/rive/generated/animation/listener_types/listener_input_type_viewmodel_base.hpp
@@ -0,0 +1,62 @@
+#ifndef _RIVE_LISTENER_INPUT_TYPE_VIEW_MODEL_BASE_HPP_
+#define _RIVE_LISTENER_INPUT_TYPE_VIEW_MODEL_BASE_HPP_
+#include "rive/animation/listener_types/listener_input_type.hpp"
+#include "rive/core/field_types/core_bytes_type.hpp"
+#include "rive/span.hpp"
+namespace rive
+{
+class ListenerInputTypeViewModelBase : public ListenerInputType
+{
+protected:
+    typedef ListenerInputType Super;
+
+public:
+    static const uint16_t typeKey = 660;
+
+    /// Helper to quickly determine if a core object extends another without
+    /// RTTI at runtime.
+    bool isTypeOf(uint16_t typeKey) const override
+    {
+        switch (typeKey)
+        {
+            case ListenerInputTypeViewModelBase::typeKey:
+            case ListenerInputTypeBase::typeKey:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    uint16_t coreType() const override { return typeKey; }
+
+    static const uint16_t viewModelPathIdsPropertyKey = 963;
+
+public:
+    virtual void decodeViewModelPathIds(Span<const uint8_t> value) = 0;
+    virtual void copyViewModelPathIds(
+        const ListenerInputTypeViewModelBase& object) = 0;
+
+    Core* clone() const override;
+    void copy(const ListenerInputTypeViewModelBase& object)
+    {
+        copyViewModelPathIds(object);
+        ListenerInputType::copy(object);
+    }
+
+    bool deserialize(uint16_t propertyKey, BinaryReader& reader) override
+    {
+        switch (propertyKey)
+        {
+            case viewModelPathIdsPropertyKey:
+                decodeViewModelPathIds(CoreBytesType::deserialize(reader));
+                return true;
+        }
+        return ListenerInputType::deserialize(propertyKey, reader);
+    }
+
+protected:
+    virtual void viewModelPathIdsChanged() {}
+};
+} // namespace rive
+
+#endif
\ No newline at end of file
diff --git a/include/rive/generated/animation/state_machine_listener_base.hpp b/include/rive/generated/animation/state_machine_listener_base.hpp
index dc80d46..7684abb 100644
--- a/include/rive/generated/animation/state_machine_listener_base.hpp
+++ b/include/rive/generated/animation/state_machine_listener_base.hpp
@@ -1,9 +1,7 @@
 #ifndef _RIVE_STATE_MACHINE_LISTENER_BASE_HPP_
 #define _RIVE_STATE_MACHINE_LISTENER_BASE_HPP_
 #include "rive/animation/state_machine_component.hpp"
-#include "rive/core/field_types/core_bytes_type.hpp"
 #include "rive/core/field_types/core_uint_type.hpp"
-#include "rive/span.hpp"
 namespace rive
 {
 class StateMachineListenerBase : public StateMachineComponent
@@ -12,7 +10,7 @@
     typedef StateMachineComponent Super;
 
 public:
-    static const uint16_t typeKey = 114;
+    static const uint16_t typeKey = 654;
 
     /// Helper to quickly determine if a core object extends another without
     /// RTTI at runtime.
@@ -31,14 +29,9 @@
     uint16_t coreType() const override { return typeKey; }
 
     static const uint16_t targetIdPropertyKey = 224;
-    static const uint16_t listenerTypeValuePropertyKey = 225;
-    static const uint16_t eventIdPropertyKey = 399;
-    static const uint16_t viewModelPathIdsPropertyKey = 868;
 
 protected:
     uint32_t m_TargetId = -1;
-    uint32_t m_ListenerTypeValue = 0;
-    uint32_t m_EventId = -1;
 
 public:
     inline uint32_t targetId() const { return m_TargetId; }
@@ -52,39 +45,10 @@
         targetIdChanged();
     }
 
-    inline uint32_t listenerTypeValue() const { return m_ListenerTypeValue; }
-    void listenerTypeValue(uint32_t value)
-    {
-        if (m_ListenerTypeValue == value)
-        {
-            return;
-        }
-        m_ListenerTypeValue = value;
-        listenerTypeValueChanged();
-    }
-
-    inline uint32_t eventId() const { return m_EventId; }
-    void eventId(uint32_t value)
-    {
-        if (m_EventId == value)
-        {
-            return;
-        }
-        m_EventId = value;
-        eventIdChanged();
-    }
-
-    virtual void decodeViewModelPathIds(Span<const uint8_t> value) = 0;
-    virtual void copyViewModelPathIds(
-        const StateMachineListenerBase& object) = 0;
-
     Core* clone() const override;
     void copy(const StateMachineListenerBase& object)
     {
         m_TargetId = object.m_TargetId;
-        m_ListenerTypeValue = object.m_ListenerTypeValue;
-        m_EventId = object.m_EventId;
-        copyViewModelPathIds(object);
         StateMachineComponent::copy(object);
     }
 
@@ -95,24 +59,12 @@
             case targetIdPropertyKey:
                 m_TargetId = CoreUintType::deserialize(reader);
                 return true;
-            case listenerTypeValuePropertyKey:
-                m_ListenerTypeValue = CoreUintType::deserialize(reader);
-                return true;
-            case eventIdPropertyKey:
-                m_EventId = CoreUintType::deserialize(reader);
-                return true;
-            case viewModelPathIdsPropertyKey:
-                decodeViewModelPathIds(CoreBytesType::deserialize(reader));
-                return true;
         }
         return StateMachineComponent::deserialize(propertyKey, reader);
     }
 
 protected:
     virtual void targetIdChanged() {}
-    virtual void listenerTypeValueChanged() {}
-    virtual void eventIdChanged() {}
-    virtual void viewModelPathIdsChanged() {}
 };
 } // namespace rive
 
diff --git a/include/rive/generated/animation/state_machine_listener_single_base.hpp b/include/rive/generated/animation/state_machine_listener_single_base.hpp
new file mode 100644
index 0000000..6deeb93
--- /dev/null
+++ b/include/rive/generated/animation/state_machine_listener_single_base.hpp
@@ -0,0 +1,102 @@
+#ifndef _RIVE_STATE_MACHINE_LISTENER_SINGLE_BASE_HPP_
+#define _RIVE_STATE_MACHINE_LISTENER_SINGLE_BASE_HPP_
+#include "rive/animation/state_machine_listener.hpp"
+#include "rive/core/field_types/core_bytes_type.hpp"
+#include "rive/core/field_types/core_uint_type.hpp"
+#include "rive/span.hpp"
+namespace rive
+{
+class StateMachineListenerSingleBase : public StateMachineListener
+{
+protected:
+    typedef StateMachineListener Super;
+
+public:
+    static const uint16_t typeKey = 114;
+
+    /// Helper to quickly determine if a core object extends another without
+    /// RTTI at runtime.
+    bool isTypeOf(uint16_t typeKey) const override
+    {
+        switch (typeKey)
+        {
+            case StateMachineListenerSingleBase::typeKey:
+            case StateMachineListenerBase::typeKey:
+            case StateMachineComponentBase::typeKey:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    uint16_t coreType() const override { return typeKey; }
+
+    static const uint16_t listenerTypeValuePropertyKey = 225;
+    static const uint16_t eventIdPropertyKey = 399;
+    static const uint16_t viewModelPathIdsPropertyKey = 868;
+
+protected:
+    uint32_t m_ListenerTypeValue = 0;
+    uint32_t m_EventId = -1;
+
+public:
+    inline uint32_t listenerTypeValue() const { return m_ListenerTypeValue; }
+    void listenerTypeValue(uint32_t value)
+    {
+        if (m_ListenerTypeValue == value)
+        {
+            return;
+        }
+        m_ListenerTypeValue = value;
+        listenerTypeValueChanged();
+    }
+
+    inline uint32_t eventId() const { return m_EventId; }
+    void eventId(uint32_t value)
+    {
+        if (m_EventId == value)
+        {
+            return;
+        }
+        m_EventId = value;
+        eventIdChanged();
+    }
+
+    virtual void decodeViewModelPathIds(Span<const uint8_t> value) = 0;
+    virtual void copyViewModelPathIds(
+        const StateMachineListenerSingleBase& object) = 0;
+
+    Core* clone() const override;
+    void copy(const StateMachineListenerSingleBase& object)
+    {
+        m_ListenerTypeValue = object.m_ListenerTypeValue;
+        m_EventId = object.m_EventId;
+        copyViewModelPathIds(object);
+        StateMachineListener::copy(object);
+    }
+
+    bool deserialize(uint16_t propertyKey, BinaryReader& reader) override
+    {
+        switch (propertyKey)
+        {
+            case listenerTypeValuePropertyKey:
+                m_ListenerTypeValue = CoreUintType::deserialize(reader);
+                return true;
+            case eventIdPropertyKey:
+                m_EventId = CoreUintType::deserialize(reader);
+                return true;
+            case viewModelPathIdsPropertyKey:
+                decodeViewModelPathIds(CoreBytesType::deserialize(reader));
+                return true;
+        }
+        return StateMachineListener::deserialize(propertyKey, reader);
+    }
+
+protected:
+    virtual void listenerTypeValueChanged() {}
+    virtual void eventIdChanged() {}
+    virtual void viewModelPathIdsChanged() {}
+};
+} // namespace rive
+
+#endif
\ No newline at end of file
diff --git a/include/rive/generated/core_registry.hpp b/include/rive/generated/core_registry.hpp
index 4b12bce..a30dc9e 100644
--- a/include/rive/generated/core_registry.hpp
+++ b/include/rive/generated/core_registry.hpp
@@ -42,6 +42,9 @@
 #include "rive/animation/listener_input_change.hpp"
 #include "rive/animation/listener_number_change.hpp"
 #include "rive/animation/listener_trigger_change.hpp"
+#include "rive/animation/listener_types/listener_input_type.hpp"
+#include "rive/animation/listener_types/listener_input_type_event.hpp"
+#include "rive/animation/listener_types/listener_input_type_viewmodel.hpp"
 #include "rive/animation/listener_viewmodel_change.hpp"
 #include "rive/animation/nested_bool.hpp"
 #include "rive/animation/nested_input.hpp"
@@ -63,6 +66,7 @@
 #include "rive/animation/state_machine_layer.hpp"
 #include "rive/animation/state_machine_layer_component.hpp"
 #include "rive/animation/state_machine_listener.hpp"
+#include "rive/animation/state_machine_listener_single.hpp"
 #include "rive/animation/state_machine_number.hpp"
 #include "rive/animation/state_machine_trigger.hpp"
 #include "rive/animation/state_transition.hpp"
@@ -495,14 +499,16 @@
                 return new BlendAnimationDirect();
             case StateMachineNumberBase::typeKey:
                 return new StateMachineNumber();
+            case StateMachineListenerBase::typeKey:
+                return new StateMachineListener();
+            case StateMachineListenerSingleBase::typeKey:
+                return new StateMachineListenerSingle();
             case CubicValueInterpolatorBase::typeKey:
                 return new CubicValueInterpolator();
             case TransitionTriggerConditionBase::typeKey:
                 return new TransitionTriggerCondition();
             case KeyedPropertyBase::typeKey:
                 return new KeyedProperty();
-            case StateMachineListenerBase::typeKey:
-                return new StateMachineListener();
             case TransitionPropertyArtboardComparatorBase::typeKey:
                 return new TransitionPropertyArtboardComparator();
             case TransitionPropertyViewModelComparatorBase::typeKey:
@@ -575,6 +581,12 @@
                 return new NestedStateMachine();
             case ElasticInterpolatorBase::typeKey:
                 return new ElasticInterpolator();
+            case ListenerInputTypeBase::typeKey:
+                return new ListenerInputType();
+            case ListenerInputTypeEventBase::typeKey:
+                return new ListenerInputTypeEvent();
+            case ListenerInputTypeViewModelBase::typeKey:
+                return new ListenerInputTypeViewModel();
             case ExitStateBase::typeKey:
                 return new ExitState();
             case NestedNumberBase::typeKey:
@@ -1229,22 +1241,22 @@
             case BlendAnimationDirectBase::blendSourcePropertyKey:
                 object->as<BlendAnimationDirectBase>()->blendSource(value);
                 break;
+            case StateMachineListenerBase::targetIdPropertyKey:
+                object->as<StateMachineListenerBase>()->targetId(value);
+                break;
+            case StateMachineListenerSingleBase::listenerTypeValuePropertyKey:
+                object->as<StateMachineListenerSingleBase>()->listenerTypeValue(
+                    value);
+                break;
+            case StateMachineListenerSingleBase::eventIdPropertyKey:
+                object->as<StateMachineListenerSingleBase>()->eventId(value);
+                break;
             case TransitionInputConditionBase::inputIdPropertyKey:
                 object->as<TransitionInputConditionBase>()->inputId(value);
                 break;
             case KeyedPropertyBase::propertyKeyPropertyKey:
                 object->as<KeyedPropertyBase>()->propertyKey(value);
                 break;
-            case StateMachineListenerBase::targetIdPropertyKey:
-                object->as<StateMachineListenerBase>()->targetId(value);
-                break;
-            case StateMachineListenerBase::listenerTypeValuePropertyKey:
-                object->as<StateMachineListenerBase>()->listenerTypeValue(
-                    value);
-                break;
-            case StateMachineListenerBase::eventIdPropertyKey:
-                object->as<StateMachineListenerBase>()->eventId(value);
-                break;
             case TransitionPropertyArtboardComparatorBase::
                 propertyTypePropertyKey:
                 object->as<TransitionPropertyArtboardComparatorBase>()
@@ -1320,6 +1332,12 @@
             case ElasticInterpolatorBase::easingValuePropertyKey:
                 object->as<ElasticInterpolatorBase>()->easingValue(value);
                 break;
+            case ListenerInputTypeBase::listenerTypeValuePropertyKey:
+                object->as<ListenerInputTypeBase>()->listenerTypeValue(value);
+                break;
+            case ListenerInputTypeEventBase::eventIdPropertyKey:
+                object->as<ListenerInputTypeEventBase>()->eventId(value);
+                break;
             case BlendStateTransitionBase::exitBlendAnimationIdPropertyKey:
                 object->as<BlendStateTransitionBase>()->exitBlendAnimationId(
                     value);
@@ -2802,17 +2820,17 @@
                 return object->as<BlendAnimationDirectBase>()->inputId();
             case BlendAnimationDirectBase::blendSourcePropertyKey:
                 return object->as<BlendAnimationDirectBase>()->blendSource();
+            case StateMachineListenerBase::targetIdPropertyKey:
+                return object->as<StateMachineListenerBase>()->targetId();
+            case StateMachineListenerSingleBase::listenerTypeValuePropertyKey:
+                return object->as<StateMachineListenerSingleBase>()
+                    ->listenerTypeValue();
+            case StateMachineListenerSingleBase::eventIdPropertyKey:
+                return object->as<StateMachineListenerSingleBase>()->eventId();
             case TransitionInputConditionBase::inputIdPropertyKey:
                 return object->as<TransitionInputConditionBase>()->inputId();
             case KeyedPropertyBase::propertyKeyPropertyKey:
                 return object->as<KeyedPropertyBase>()->propertyKey();
-            case StateMachineListenerBase::targetIdPropertyKey:
-                return object->as<StateMachineListenerBase>()->targetId();
-            case StateMachineListenerBase::listenerTypeValuePropertyKey:
-                return object->as<StateMachineListenerBase>()
-                    ->listenerTypeValue();
-            case StateMachineListenerBase::eventIdPropertyKey:
-                return object->as<StateMachineListenerBase>()->eventId();
             case TransitionPropertyArtboardComparatorBase::
                 propertyTypePropertyKey:
                 return object->as<TransitionPropertyArtboardComparatorBase>()
@@ -2865,6 +2883,10 @@
                 return object->as<LinearAnimationBase>()->workEnd();
             case ElasticInterpolatorBase::easingValuePropertyKey:
                 return object->as<ElasticInterpolatorBase>()->easingValue();
+            case ListenerInputTypeBase::listenerTypeValuePropertyKey:
+                return object->as<ListenerInputTypeBase>()->listenerTypeValue();
+            case ListenerInputTypeEventBase::eventIdPropertyKey:
+                return object->as<ListenerInputTypeEventBase>()->eventId();
             case BlendStateTransitionBase::exitBlendAnimationIdPropertyKey:
                 return object->as<BlendStateTransitionBase>()
                     ->exitBlendAnimationId();
@@ -3808,11 +3830,11 @@
             case BlendAnimationBase::animationIdPropertyKey:
             case BlendAnimationDirectBase::inputIdPropertyKey:
             case BlendAnimationDirectBase::blendSourcePropertyKey:
+            case StateMachineListenerBase::targetIdPropertyKey:
+            case StateMachineListenerSingleBase::listenerTypeValuePropertyKey:
+            case StateMachineListenerSingleBase::eventIdPropertyKey:
             case TransitionInputConditionBase::inputIdPropertyKey:
             case KeyedPropertyBase::propertyKeyPropertyKey:
-            case StateMachineListenerBase::targetIdPropertyKey:
-            case StateMachineListenerBase::listenerTypeValuePropertyKey:
-            case StateMachineListenerBase::eventIdPropertyKey:
             case TransitionPropertyArtboardComparatorBase::
                 propertyTypePropertyKey:
             case KeyFrameIdBase::valuePropertyKey:
@@ -3838,6 +3860,8 @@
             case LinearAnimationBase::workStartPropertyKey:
             case LinearAnimationBase::workEndPropertyKey:
             case ElasticInterpolatorBase::easingValuePropertyKey:
+            case ListenerInputTypeBase::listenerTypeValuePropertyKey:
+            case ListenerInputTypeEventBase::eventIdPropertyKey:
             case BlendStateTransitionBase::exitBlendAnimationIdPropertyKey:
             case ShapePaintBase::blendModeValuePropertyKey:
             case TargetEffectBase::targetIdPropertyKey:
@@ -4226,7 +4250,8 @@
             case ScriptInputViewModelPropertyBase::dataBindPathIdsPropertyKey:
             case NestedArtboardBase::dataBindPathIdsPropertyKey:
             case StateMachineFireTriggerBase::viewModelPathIdsPropertyKey:
-            case StateMachineListenerBase::viewModelPathIdsPropertyKey:
+            case StateMachineListenerSingleBase::viewModelPathIdsPropertyKey:
+            case ListenerInputTypeViewModelBase::viewModelPathIdsPropertyKey:
             case MeshBase::triangleIndexBytesPropertyKey:
             case DataBindPathBase::pathPropertyKey:
             case DataConverterOperationViewModelBase::sourcePathIdsPropertyKey:
@@ -4476,16 +4501,16 @@
                 return object->is<BlendAnimationDirectBase>();
             case BlendAnimationDirectBase::blendSourcePropertyKey:
                 return object->is<BlendAnimationDirectBase>();
+            case StateMachineListenerBase::targetIdPropertyKey:
+                return object->is<StateMachineListenerBase>();
+            case StateMachineListenerSingleBase::listenerTypeValuePropertyKey:
+                return object->is<StateMachineListenerSingleBase>();
+            case StateMachineListenerSingleBase::eventIdPropertyKey:
+                return object->is<StateMachineListenerSingleBase>();
             case TransitionInputConditionBase::inputIdPropertyKey:
                 return object->is<TransitionInputConditionBase>();
             case KeyedPropertyBase::propertyKeyPropertyKey:
                 return object->is<KeyedPropertyBase>();
-            case StateMachineListenerBase::targetIdPropertyKey:
-                return object->is<StateMachineListenerBase>();
-            case StateMachineListenerBase::listenerTypeValuePropertyKey:
-                return object->is<StateMachineListenerBase>();
-            case StateMachineListenerBase::eventIdPropertyKey:
-                return object->is<StateMachineListenerBase>();
             case TransitionPropertyArtboardComparatorBase::
                 propertyTypePropertyKey:
                 return object->is<TransitionPropertyArtboardComparatorBase>();
@@ -4535,6 +4560,10 @@
                 return object->is<LinearAnimationBase>();
             case ElasticInterpolatorBase::easingValuePropertyKey:
                 return object->is<ElasticInterpolatorBase>();
+            case ListenerInputTypeBase::listenerTypeValuePropertyKey:
+                return object->is<ListenerInputTypeBase>();
+            case ListenerInputTypeEventBase::eventIdPropertyKey:
+                return object->is<ListenerInputTypeEventBase>();
             case BlendStateTransitionBase::exitBlendAnimationIdPropertyKey:
                 return object->is<BlendStateTransitionBase>();
             case ShapePaintBase::blendModeValuePropertyKey:
diff --git a/include/rive/importers/state_machine_listener_importer.hpp b/include/rive/importers/state_machine_listener_importer.hpp
index 6dc6404..92c4b5b 100644
--- a/include/rive/importers/state_machine_listener_importer.hpp
+++ b/include/rive/importers/state_machine_listener_importer.hpp
@@ -20,6 +20,7 @@
         return m_StateMachineListener;
     }
     void addAction(std::unique_ptr<ListenerAction>);
+    void addListenerInputType(std::unique_ptr<ListenerInputType>);
     StatusCode resolve() override;
 };
 } // namespace rive
diff --git a/src/animation/focus_listener_group.cpp b/src/animation/focus_listener_group.cpp
index 938488a..a6ecfab 100644
--- a/src/animation/focus_listener_group.cpp
+++ b/src/animation/focus_listener_group.cpp
@@ -12,7 +12,8 @@
     m_focusData(focusData),
     m_listener(listener),
     m_stateMachineInstance(stateMachineInstance),
-    m_isFocusListener(listener->listenerType() == ListenerType::focus)
+    m_isFocusListener(listener->hasListener(ListenerType::focus)),
+    m_isBlurListener(listener->hasListener(ListenerType::blur))
 {
     // Register ourselves as a listener on the FocusData
     m_focusData->addFocusListener(this);
@@ -35,7 +36,7 @@
 void FocusListenerGroup::onBlurred()
 {
     // Only queue if this is a blur listener
-    if (!m_isFocusListener)
+    if (m_isBlurListener)
     {
         m_stateMachineInstance->queueFocusEvent(this, false);
     }
diff --git a/src/animation/listener_types/listener_input_type.cpp b/src/animation/listener_types/listener_input_type.cpp
new file mode 100644
index 0000000..6513691
--- /dev/null
+++ b/src/animation/listener_types/listener_input_type.cpp
@@ -0,0 +1,23 @@
+#include "rive/animation/state_machine_listener.hpp"
+#include "rive/importers/import_stack.hpp"
+#include "rive/importers/state_machine_listener_importer.hpp"
+#include "rive/importers/state_machine_importer.hpp"
+#include "rive/animation/listener_types/listener_input_type.hpp"
+#include "rive/animation/state_machine.hpp"
+
+using namespace rive;
+
+StatusCode ListenerInputType::import(ImportStack& importStack)
+{
+    auto stateMachineListenerImporter =
+        importStack.latest<StateMachineListenerImporter>(
+            StateMachineListenerBase::typeKey);
+    if (stateMachineListenerImporter == nullptr)
+    {
+        return StatusCode::MissingObject;
+    }
+
+    stateMachineListenerImporter->addListenerInputType(
+        std::unique_ptr<ListenerInputType>(this));
+    return Super::import(importStack);
+}
diff --git a/src/animation/listener_types/listener_input_type_viewmodel.cpp b/src/animation/listener_types/listener_input_type_viewmodel.cpp
new file mode 100644
index 0000000..a081281
--- /dev/null
+++ b/src/animation/listener_types/listener_input_type_viewmodel.cpp
@@ -0,0 +1,20 @@
+#include "rive/animation/listener_types/listener_input_type_viewmodel.hpp"
+
+using namespace rive;
+
+void ListenerInputTypeViewModel::decodeViewModelPathIds(
+    Span<const uint8_t> value)
+{
+    decodeDataBindPath(value);
+}
+
+void ListenerInputTypeViewModel::copyViewModelPathIds(
+    const ListenerInputTypeViewModelBase& object)
+{
+    copyDataBindPath(object.as<ListenerInputTypeViewModel>()->dataBindPath());
+}
+
+std::vector<uint32_t> ListenerInputTypeViewModel::viewModelPathIdsBuffer() const
+{
+    return dataBindPath()->path();
+}
\ No newline at end of file
diff --git a/src/animation/state_machine_instance.cpp b/src/animation/state_machine_instance.cpp
index a027ce2..17e77ff 100644
--- a/src/animation/state_machine_instance.cpp
+++ b/src/animation/state_machine_instance.cpp
@@ -16,11 +16,13 @@
 #include "rive/animation/state_machine_instance.hpp"
 #include "rive/animation/state_machine_layer.hpp"
 #include "rive/animation/state_machine_listener.hpp"
+#include "rive/animation/state_machine_listener_single.hpp"
 #include "rive/animation/state_machine_number.hpp"
 #include "rive/animation/state_machine_trigger.hpp"
 #include "rive/animation/state_machine.hpp"
 #include "rive/animation/state_transition.hpp"
 #include "rive/animation/listener_action.hpp"
+#include "rive/animation/listener_types/listener_input_type_viewmodel.hpp"
 #include "rive/animation/scripted_listener_action.hpp"
 #include "rive/animation/transition_condition.hpp"
 #include "rive/animation/transition_comparator.hpp"
@@ -53,9 +55,12 @@
 #include "rive/refcnt.hpp"
 #include "rive/animation/focus_listener_group.hpp"
 #include "rive/animation/text_input_listener_group.hpp"
+#include "rive/animation/listener_types/listener_input_type_event.hpp"
 #include "rive/focus_data.hpp"
 #include "rive/node.hpp"
+#include <memory>
 #include <unordered_map>
+#include <vector>
 #include <chrono>
 
 using namespace rive;
@@ -1014,73 +1019,223 @@
     {}
 };
 
-class ListenerViewModel : public ViewModelValueDependent
+class ListenerViewModel;
+
+// Helper that holds one view model property reference, listens to its dirt,
+// and reports the parent ListenerViewModel when the property changes.
+class ListenerViewModelPropertyBinding : public ViewModelValueDependent
 {
 public:
-    virtual ~ListenerViewModel() = default;
+    ListenerViewModelPropertyBinding(ListenerViewModel* parent,
+                                     ViewModelInstanceValue* vmProp);
+    virtual ~ListenerViewModelPropertyBinding();
+    void addDirt(ComponentDirt value, bool recurse) override;
+    void relinkDataBind() override;
+
+protected:
+    ListenerViewModel* m_parent = nullptr;
+    rive::rcp<ViewModelInstanceValue> m_viewModelInstanceValue = nullptr;
+    void clearDataContext();
+};
+class ListenerViewModelPropertyBindingListener
+    : public ListenerViewModelPropertyBinding
+{
+public:
+    ListenerViewModelPropertyBindingListener(
+        ListenerViewModel* parent,
+        ViewModelInstanceValue* vmProp,
+        const StateMachineListenerSingle* listener);
+    void relinkDataBind() override;
+
+private:
+    const StateMachineListenerSingle* m_listener;
+};
+class ListenerViewModelPropertyBindingInput
+    : public ListenerViewModelPropertyBinding
+{
+public:
+    ListenerViewModelPropertyBindingInput(
+        ListenerViewModel* parent,
+        ViewModelInstanceValue* vmProp,
+        const ListenerInputTypeViewModel* listenerInput);
+    void relinkDataBind() override;
+
+private:
+    const ListenerInputTypeViewModel* m_listenerInput;
+};
+
+class ListenerViewModel
+{
+public:
+    virtual ~ListenerViewModel();
     ListenerViewModel(StateMachineInstance* smInstance,
                       const StateMachineListener* listener) :
         m_stateMachineInstance(smInstance), m_listener(listener)
     {}
-    void clearDataContext()
-    {
-        if (m_viewModelInstanceValue != nullptr)
-        {
-            m_viewModelInstanceValue->removeDependent(this);
-            m_viewModelInstanceValue = nullptr;
-        }
-    }
-    void relinkDataBind()
-    {
-        if (m_dataContext != nullptr)
-        {
-            auto vmProp =
-                m_dataContext->getViewModelProperty(m_listener->dataBindPath());
-            if (vmProp != m_viewModelInstanceValue.get())
-            {
-                clearDataContext();
-                if (vmProp != nullptr)
-                {
-                    m_viewModelInstanceValue = ref_rcp(vmProp);
-                    vmProp->addDependent(this);
-                }
-            }
-        }
-    }
 
+    void clearDataContext() { m_propertyBindings.clear(); }
     void bindFromContext(rcp<DataContext> dataContext)
     {
         m_dataContext = dataContext;
         clearDataContext();
-        auto vmProp =
-            dataContext->getViewModelProperty(m_listener->dataBindPath());
-        if (vmProp != nullptr)
+        if (m_listener->is<StateMachineListenerSingle>())
         {
-            m_viewModelInstanceValue = ref_rcp(vmProp);
-            vmProp->addDependent(this);
-        }
-    }
-    void addDirt(ComponentDirt value, bool recurse)
-    {
-        if (m_viewModelInstanceValue)
-        {
-            if (!m_viewModelInstanceValue->is<ViewModelInstanceTrigger>() ||
-                m_viewModelInstanceValue->as<ViewModelInstanceTrigger>()
-                        ->propertyValue() != 0)
+            auto vmProp = dataContext->getViewModelProperty(
+                m_listener->as<StateMachineListenerSingle>()->dataBindPath());
+            if (vmProp != nullptr)
             {
-                m_stateMachineInstance->reportListenerViewModel(this);
+                m_propertyBindings.push_back(
+                    rivestd::make_unique<
+                        ListenerViewModelPropertyBindingListener>(
+                        this,
+                        vmProp,
+                        m_listener->as<StateMachineListenerSingle>()));
+            }
+        }
+        else
+        {
+            size_t index = 0;
+            while (index < m_listener->listenerInputTypeCount())
+            {
+                auto listenerInputType = m_listener->listenerInputType(index);
+                if (listenerInputType->is<ListenerInputTypeViewModel>())
+                {
+                    auto listenerInputTypeVM =
+                        listenerInputType->as<ListenerInputTypeViewModel>();
+                    auto vmProp = dataContext->getViewModelProperty(
+                        listenerInputTypeVM->dataBindPath());
+                    if (vmProp != nullptr)
+                    {
+                        m_propertyBindings.push_back(
+                            rivestd::make_unique<
+                                ListenerViewModelPropertyBindingInput>(
+                                this,
+                                vmProp,
+                                listenerInputTypeVM));
+                    }
+                }
+                index++;
             }
         }
     }
+    void reportToStateMachine(ViewModelInstanceValue* value)
+    {
+        if (!value->is<ViewModelInstanceTrigger>() ||
+            value->as<ViewModelInstanceTrigger>()->propertyValue() != 0)
+        {
+            m_stateMachineInstance->reportListenerViewModel(this);
+        }
+    }
     const StateMachineListener* listener() { return m_listener; }
+    DataContext* dataContext()
+    {
+        if (m_dataContext)
+        {
+
+            return m_dataContext.get();
+        }
+        return nullptr;
+    }
 
 private:
     StateMachineInstance* m_stateMachineInstance = nullptr;
     const StateMachineListener* m_listener = nullptr;
-    rcp<ViewModelInstanceValue> m_viewModelInstanceValue = nullptr;
     rcp<DataContext> m_dataContext = nullptr;
+    std::vector<std::unique_ptr<ListenerViewModelPropertyBinding>>
+        m_propertyBindings;
 };
 
+ListenerViewModelPropertyBinding::ListenerViewModelPropertyBinding(
+    ListenerViewModel* parent,
+    ViewModelInstanceValue* vmProp) :
+    m_parent(parent), m_viewModelInstanceValue(rive::ref_rcp(vmProp))
+{
+    vmProp->addDependent(this);
+}
+
+void ListenerViewModelPropertyBinding::relinkDataBind() {};
+
+ListenerViewModelPropertyBinding::~ListenerViewModelPropertyBinding()
+{
+    clearDataContext();
+}
+
+void ListenerViewModelPropertyBinding::clearDataContext()
+{
+
+    if (m_viewModelInstanceValue != nullptr)
+    {
+        m_viewModelInstanceValue->removeDependent(this);
+        m_viewModelInstanceValue = nullptr;
+    }
+}
+
+ListenerViewModelPropertyBindingListener::
+    ListenerViewModelPropertyBindingListener(
+        ListenerViewModel* parent,
+        ViewModelInstanceValue* vmProp,
+        const StateMachineListenerSingle* listener) :
+    ListenerViewModelPropertyBinding(parent, vmProp), m_listener(listener)
+{}
+
+void ListenerViewModelPropertyBindingListener::relinkDataBind()
+{
+    auto dataContext = m_parent->dataContext();
+    if (dataContext)
+    {
+
+        auto vmProp =
+            dataContext->getViewModelProperty(m_listener->dataBindPath());
+        if (vmProp != m_viewModelInstanceValue.get())
+        {
+            clearDataContext();
+            if (vmProp != nullptr)
+            {
+                m_viewModelInstanceValue = ref_rcp(vmProp);
+                vmProp->addDependent(this);
+            }
+        }
+    }
+}
+
+ListenerViewModelPropertyBindingInput::ListenerViewModelPropertyBindingInput(
+    ListenerViewModel* parent,
+    ViewModelInstanceValue* vmProp,
+    const ListenerInputTypeViewModel* listenerInput) :
+    ListenerViewModelPropertyBinding(parent, vmProp),
+    m_listenerInput(listenerInput)
+{}
+
+void ListenerViewModelPropertyBindingInput::relinkDataBind()
+{
+    auto dataContext = m_parent->dataContext();
+    if (dataContext)
+    {
+        auto vmProp =
+            dataContext->getViewModelProperty(m_listenerInput->dataBindPath());
+        if (vmProp != m_viewModelInstanceValue.get())
+        {
+            clearDataContext();
+            if (vmProp != nullptr)
+            {
+                m_viewModelInstanceValue = ref_rcp(vmProp);
+                vmProp->addDependent(this);
+            }
+        }
+    }
+}
+
+void ListenerViewModelPropertyBinding::addDirt(ComponentDirt value,
+                                               bool recurse)
+{
+    if (m_parent != nullptr && m_viewModelInstanceValue != nullptr)
+    {
+        m_parent->reportToStateMachine(m_viewModelInstanceValue.get());
+    }
+}
+
+ListenerViewModel::~ListenerViewModel() { clearDataContext(); }
+
 } // namespace rive
 
 HitResult StateMachineInstance::updateListeners(Vec2D position,
@@ -1405,11 +1560,11 @@
     for (std::size_t i = 0; i < machine->listenerCount(); i++)
     {
         auto listener = machine->listener(i);
-        if (listener->listenerType() == ListenerType::event)
+        if (listener->hasListener(ListenerType::event))
         {
             continue;
         }
-        if (listener->listenerType() == ListenerType::viewModel)
+        if (listener->hasListener(ListenerType::viewModel))
         {
             auto vmListener = new ListenerViewModel(this, listener);
             m_listenerViewModels.push_back(vmListener);
@@ -1417,8 +1572,8 @@
         }
         // Handle focus/blur listeners - they're driven by FocusManager,
         // not pointer events.
-        if (listener->listenerType() == ListenerType::focus ||
-            listener->listenerType() == ListenerType::blur)
+        if (listener->hasListener(ListenerType::focus) ||
+            listener->hasListener(ListenerType::blur))
         {
             auto target = m_artboardInstance->resolve(listener->targetId());
             if (target != nullptr && target->is<Node>())
@@ -1838,8 +1993,8 @@
         bool isFocusEvent = event.isFocus;
 
         // Match listener type to event type
-        if ((isFocusEvent && listener->listenerType() == ListenerType::focus) ||
-            (!isFocusEvent && listener->listenerType() == ListenerType::blur))
+        if ((isFocusEvent && listener->hasListener(ListenerType::focus)) ||
+            (!isFocusEvent && listener->hasListener(ListenerType::blur)))
         {
             listener->performChanges(this, Vec2D(), Vec2D(), 0);
         }
@@ -2205,7 +2360,7 @@
             auto listener = m_machine->listener(i);
             auto target = artboard()->resolve(listener->targetId());
             if (listener != nullptr &&
-                listener->listenerType() == ListenerType::event &&
+                listener->hasListener(ListenerType::event) &&
                 (source == nullptr || source == target))
             {
                 for (const auto event : events)
@@ -2240,12 +2395,43 @@
                             continue;
                         }
                     }
-                    auto listenerEvent =
-                        sourceArtboard->resolve(listener->eventId());
-                    if (listenerEvent == event.event())
+                    if (listener->is<StateMachineListenerSingle>())
                     {
-                        listener->performChanges(this, Vec2D(), Vec2D(), 0);
-                        break;
+                        auto listenerEvent = sourceArtboard->resolve(
+                            listener->as<StateMachineListenerSingle>()
+                                ->eventId());
+                        if (listenerEvent == event.event())
+                        {
+                            listener->performChanges(this, Vec2D(), Vec2D(), 0);
+                            break;
+                        }
+                    }
+                    else
+                    {
+                        size_t index = 0;
+                        while (index < listener->listenerInputTypeCount())
+                        {
+                            auto listenerInputType =
+                                listener->listenerInputType(index);
+                            if (listenerInputType->is<ListenerInputTypeEvent>())
+                            {
+
+                                auto listenerInputTypeEvent =
+                                    listenerInputType
+                                        ->as<ListenerInputTypeEvent>();
+                                auto listenerEvent = sourceArtboard->resolve(
+                                    listenerInputTypeEvent->eventId());
+                                if (listenerEvent == event.event())
+                                {
+                                    listener->performChanges(this,
+                                                             Vec2D(),
+                                                             Vec2D(),
+                                                             0);
+                                    break;
+                                }
+                            }
+                            index += 1;
+                        }
                     }
                 }
             }
diff --git a/src/animation/state_machine_listener.cpp b/src/animation/state_machine_listener.cpp
index 7b39e3a..7e93131 100644
--- a/src/animation/state_machine_listener.cpp
+++ b/src/animation/state_machine_listener.cpp
@@ -6,20 +6,38 @@
 #include "rive/shapes/shape.hpp"
 #include "rive/animation/state_machine_instance.hpp"
 #include "rive/animation/listener_input_change.hpp"
+#include "rive/animation/listener_types/listener_input_type.hpp"
 
 using namespace rive;
 
 StateMachineListener::StateMachineListener() {}
 StateMachineListener::~StateMachineListener() {}
 
+bool StateMachineListener::hasListener(ListenerType listenerType) const
+{
+    for (auto& listenerInputType : m_listenerInputTypes)
+    {
+        if (listenerInputType->listenerTypeValue() == (int)listenerType)
+        {
+            return true;
+        }
+    }
+    return false;
+}
+
 void StateMachineListener::addAction(std::unique_ptr<ListenerAction> action)
 {
     m_actions.push_back(std::move(action));
 }
 
+void StateMachineListener::addListenerInputType(
+    std::unique_ptr<ListenerInputType> listenerInputType)
+{
+    m_listenerInputTypes.push_back(std::move(listenerInputType));
+}
+
 StatusCode StateMachineListener::import(ImportStack& importStack)
 {
-    importDataBindPath(importStack);
     auto stateMachineImporter =
         importStack.latest<StateMachineImporter>(StateMachineBase::typeKey);
     if (stateMachineImporter == nullptr)
@@ -41,6 +59,16 @@
     return nullptr;
 }
 
+const ListenerInputType* StateMachineListener::listenerInputType(
+    size_t index) const
+{
+    if (index < m_listenerInputTypes.size())
+    {
+        return m_listenerInputTypes[index].get();
+    }
+    return nullptr;
+}
+
 void StateMachineListener::performChanges(
     StateMachineInstance* stateMachineInstance,
     Vec2D position,
@@ -54,20 +82,4 @@
                         previousPosition,
                         pointerId);
     }
-}
-
-void StateMachineListener::decodeViewModelPathIds(Span<const uint8_t> value)
-{
-    decodeDataBindPath(value);
-}
-
-void StateMachineListener::copyViewModelPathIds(
-    const StateMachineListenerBase& object)
-{
-    copyDataBindPath(object.as<StateMachineListener>()->dataBindPath());
-}
-
-std::vector<uint32_t> StateMachineListener::viewModelPathIdsBuffer() const
-{
-    return dataBindPath()->path();
 }
\ No newline at end of file
diff --git a/src/animation/state_machine_listener_single.cpp b/src/animation/state_machine_listener_single.cpp
new file mode 100644
index 0000000..3fd4cf2
--- /dev/null
+++ b/src/animation/state_machine_listener_single.cpp
@@ -0,0 +1,34 @@
+#include "rive/animation/state_machine_listener_single.hpp"
+#include "rive/importers/import_stack.hpp"
+#include "rive/importers/state_machine_importer.hpp"
+#include "rive/generated/animation/state_machine_base.hpp"
+#include "rive/artboard.hpp"
+#include "rive/shapes/shape.hpp"
+#include "rive/animation/state_machine_instance.hpp"
+#include "rive/animation/listener_input_change.hpp"
+#include "rive/animation/listener_types/listener_input_type.hpp"
+
+using namespace rive;
+
+StatusCode StateMachineListenerSingle::import(ImportStack& importStack)
+{
+    importDataBindPath(importStack);
+    return Super::import(importStack);
+}
+
+void StateMachineListenerSingle::decodeViewModelPathIds(
+    Span<const uint8_t> value)
+{
+    decodeDataBindPath(value);
+}
+
+void StateMachineListenerSingle::copyViewModelPathIds(
+    const StateMachineListenerSingleBase& object)
+{
+    copyDataBindPath(object.as<StateMachineListenerSingle>()->dataBindPath());
+}
+
+std::vector<uint32_t> StateMachineListenerSingle::viewModelPathIdsBuffer() const
+{
+    return dataBindPath()->path();
+}
\ No newline at end of file
diff --git a/src/constraints/draggable_constraint.cpp b/src/constraints/draggable_constraint.cpp
index 7a94094..cc4a1f2 100644
--- a/src/constraints/draggable_constraint.cpp
+++ b/src/constraints/draggable_constraint.cpp
@@ -1,4 +1,5 @@
 #include "rive/animation/state_machine_instance.hpp"
+#include "rive/animation/state_machine_listener_single.hpp"
 #include "rive/component.hpp"
 #include "rive/constraints/draggable_constraint.hpp"
 
@@ -9,7 +10,7 @@
     std::vector<ListenerGroupWithTargets*> result;
     for (auto dragProxy : draggables())
     {
-        auto listener = new StateMachineListener();
+        auto listener = new StateMachineListenerSingle();
         listener->listenerTypeValue(
             static_cast<uint32_t>(ListenerType::componentProvided));
         auto* listenerGroup =
diff --git a/src/file.cpp b/src/file.cpp
index 333fdc5..a1a4037 100644
--- a/src/file.cpp
+++ b/src/file.cpp
@@ -452,9 +452,11 @@
                 stackType = StateTransition::typeKey;
                 break;
             case StateMachineListener::typeKey:
+            case StateMachineListenerSingle::typeKey:
                 stackObject =
                     rivestd::make_unique<StateMachineListenerImporter>(
                         object->as<StateMachineListener>());
+                stackType = StateMachineListener::typeKey;
                 break;
             case ImageAsset::typeKey:
             case FontAsset::typeKey:
diff --git a/src/generated/animation/listener_types/listener_input_type_base.cpp b/src/generated/animation/listener_types/listener_input_type_base.cpp
new file mode 100644
index 0000000..dd617a1
--- /dev/null
+++ b/src/generated/animation/listener_types/listener_input_type_base.cpp
@@ -0,0 +1,11 @@
+#include "rive/generated/animation/listener_types/listener_input_type_base.hpp"
+#include "rive/animation/listener_types/listener_input_type.hpp"
+
+using namespace rive;
+
+Core* ListenerInputTypeBase::clone() const
+{
+    auto cloned = new ListenerInputType();
+    cloned->copy(*this);
+    return cloned;
+}
diff --git a/src/generated/animation/listener_types/listener_input_type_event_base.cpp b/src/generated/animation/listener_types/listener_input_type_event_base.cpp
new file mode 100644
index 0000000..befa4e5
--- /dev/null
+++ b/src/generated/animation/listener_types/listener_input_type_event_base.cpp
@@ -0,0 +1,11 @@
+#include "rive/generated/animation/listener_types/listener_input_type_event_base.hpp"
+#include "rive/animation/listener_types/listener_input_type_event.hpp"
+
+using namespace rive;
+
+Core* ListenerInputTypeEventBase::clone() const
+{
+    auto cloned = new ListenerInputTypeEvent();
+    cloned->copy(*this);
+    return cloned;
+}
diff --git a/src/generated/animation/listener_types/listener_input_type_viewmodel_base.cpp b/src/generated/animation/listener_types/listener_input_type_viewmodel_base.cpp
new file mode 100644
index 0000000..0dc9637
--- /dev/null
+++ b/src/generated/animation/listener_types/listener_input_type_viewmodel_base.cpp
@@ -0,0 +1,11 @@
+#include "rive/generated/animation/listener_types/listener_input_type_viewmodel_base.hpp"
+#include "rive/animation/listener_types/listener_input_type_viewmodel.hpp"
+
+using namespace rive;
+
+Core* ListenerInputTypeViewModelBase::clone() const
+{
+    auto cloned = new ListenerInputTypeViewModel();
+    cloned->copy(*this);
+    return cloned;
+}
diff --git a/src/generated/animation/state_machine_listener_single_base.cpp b/src/generated/animation/state_machine_listener_single_base.cpp
new file mode 100644
index 0000000..7fe37ed
--- /dev/null
+++ b/src/generated/animation/state_machine_listener_single_base.cpp
@@ -0,0 +1,11 @@
+#include "rive/generated/animation/state_machine_listener_single_base.hpp"
+#include "rive/animation/state_machine_listener_single.hpp"
+
+using namespace rive;
+
+Core* StateMachineListenerSingleBase::clone() const
+{
+    auto cloned = new StateMachineListenerSingle();
+    cloned->copy(*this);
+    return cloned;
+}
diff --git a/src/importers/state_machine_listener_importer.cpp b/src/importers/state_machine_listener_importer.cpp
index 19257be..82d0dfd 100644
--- a/src/importers/state_machine_listener_importer.cpp
+++ b/src/importers/state_machine_listener_importer.cpp
@@ -1,4 +1,5 @@
 #include "rive/animation/listener_action.hpp"
+#include "rive/animation/listener_types/listener_input_type.hpp"
 #include "rive/importers/state_machine_listener_importer.hpp"
 #include "rive/animation/state_machine_listener.hpp"
 
@@ -15,4 +16,10 @@
     m_StateMachineListener->addAction(std::move(action));
 }
 
+void StateMachineListenerImporter::addListenerInputType(
+    std::unique_ptr<ListenerInputType> listenerInputType)
+{
+    m_StateMachineListener->addListenerInputType(std::move(listenerInputType));
+}
+
 StatusCode StateMachineListenerImporter::resolve() { return StatusCode::Ok; }
\ No newline at end of file
diff --git a/src/listener_group.cpp b/src/listener_group.cpp
index 217e6a1..7d98b3f 100644
--- a/src/listener_group.cpp
+++ b/src/listener_group.cpp
@@ -90,27 +90,24 @@
 
 bool ListenerGroup::canEarlyOut(Component* drawable)
 {
-    auto listenerType = m_listener->listenerType();
-    return !(listenerType == ListenerType::enter ||
-             listenerType == ListenerType::exit ||
-             listenerType == ListenerType::move ||
-             listenerType == ListenerType::drag);
+    return !(m_listener->hasListener(ListenerType::enter) ||
+             m_listener->hasListener(ListenerType::exit) ||
+             m_listener->hasListener(ListenerType::move) ||
+             m_listener->hasListener(ListenerType::drag));
 }
 
 bool ListenerGroup::needsDownListener(Component* drawable)
 {
-    auto listenerType = m_listener->listenerType();
-    return listenerType == ListenerType::down ||
-           listenerType == ListenerType::click ||
-           listenerType == ListenerType::drag;
+    return m_listener->hasListener(ListenerType::down) ||
+           m_listener->hasListener(ListenerType::click) ||
+           m_listener->hasListener(ListenerType::drag);
 }
 
 bool ListenerGroup::needsUpListener(Component* drawable)
 {
-    auto listenerType = m_listener->listenerType();
-    return listenerType == ListenerType::up ||
-           listenerType == ListenerType::click ||
-           listenerType == ListenerType::drag;
+    return m_listener->hasListener(ListenerType::up) ||
+           m_listener->hasListener(ListenerType::click) ||
+           m_listener->hasListener(ListenerType::drag);
 }
 
 ProcessEventResult ListenerGroup::processEvent(
@@ -179,53 +176,37 @@
         m_hasDragged = false;
     }
     auto _listener = listener();
+    bool shouldPerformChanges = false;
     // Always update hover states regardless of which specific listener type
     // we're trying to trigger.
     // If hover has changed and:
     // - it's hovering and the listener is of type enter
     // - it's not hovering and the listener is of type exit
     if (hoverChange &&
-        ((isGroupHovered && _listener->listenerType() == ListenerType::enter) ||
-         (!isGroupHovered && _listener->listenerType() == ListenerType::exit)))
+        ((isGroupHovered && _listener->hasListener(ListenerType::enter)) ||
+         (!isGroupHovered && _listener->hasListener(ListenerType::exit))))
     {
-        _listener->performChanges(
-            stateMachineInstance,
-            position,
-            Vec2D(previousPosition->x, previousPosition->y),
-            pointerId);
-        stateMachineInstance->markNeedsAdvance();
-        consume();
+        shouldPerformChanges = true;
     }
     // Perform changes if:
     // - the click gesture is complete and the listener is of type click
     // - the event type matches the listener type and it is hovering the
     // group
     if ((pointer->phase == GestureClickPhase::clicked &&
-         _listener->listenerType() == ListenerType::click) ||
-        (isGroupHovered && hitEvent == _listener->listenerType()))
+         _listener->hasListener(ListenerType::click)) ||
+        (isGroupHovered && _listener->hasListener(hitEvent)))
     {
-        _listener->performChanges(
-            stateMachineInstance,
-            position,
-            Vec2D(previousPosition->x, previousPosition->y),
-            pointerId);
-        stateMachineInstance->markNeedsAdvance();
-        consume();
+        shouldPerformChanges = true;
     }
     // Perform changes if:
     // - the listener type is drag
     // - the clickPhase is down
     // - the pointer type is move
     if (pointer->phase == GestureClickPhase::down &&
-        _listener->listenerType() == ListenerType::drag &&
+        _listener->hasListener(ListenerType::drag) &&
         hitEvent == ListenerType::move)
     {
-        _listener->performChanges(
-            stateMachineInstance,
-            position,
-            Vec2D(previousPosition->x, previousPosition->y),
-            pointerId);
-        stateMachineInstance->markNeedsAdvance();
+        shouldPerformChanges = true;
         if (!m_hasDragged)
         {
             stateMachineInstance->dragStart(position,
@@ -234,6 +215,16 @@
                                             pointerId);
             m_hasDragged = true;
         }
+    }
+    if (shouldPerformChanges)
+    {
+
+        _listener->performChanges(
+            stateMachineInstance,
+            position,
+            Vec2D(previousPosition->x, previousPosition->y),
+            pointerId);
+        stateMachineInstance->markNeedsAdvance();
         consume();
     }
     previousPosition->x = position.x;
diff --git a/tests/unit_tests/assets/multi_listeners.riv b/tests/unit_tests/assets/multi_listeners.riv
new file mode 100644
index 0000000..5c44971
--- /dev/null
+++ b/tests/unit_tests/assets/multi_listeners.riv
Binary files differ
diff --git a/tests/unit_tests/runtime/state_machine_test.cpp b/tests/unit_tests/runtime/state_machine_test.cpp
index 3a4d3d4..8c7a065 100644
--- a/tests/unit_tests/runtime/state_machine_test.cpp
+++ b/tests/unit_tests/runtime/state_machine_test.cpp
@@ -15,6 +15,8 @@
 #include <rive/shapes/paint/solid_color.hpp>
 #include <rive/shapes/paint/stroke.hpp>
 #include <rive/shapes/shape.hpp>
+#include <rive/viewmodel/viewmodel_instance_viewmodel.hpp>
+#include <rive/viewmodel/viewmodel_instance_number.hpp>
 #include "catch.hpp"
 #include "rive_file_reader.hpp"
 #include "utils/serializing_factory.hpp"
@@ -590,4 +592,123 @@
     artboard->draw(renderer.get());
 
     CHECK(silver.matches("sorted_listeners"));
+}
+
+TEST_CASE("Listeners with multiple types of events", "[silver]")
+{
+    SerializingFactory silver;
+    auto file = ReadRiveFile("assets/multi_listeners.riv", &silver);
+
+    auto artboard = file->artboardDefault();
+    silver.frameSize(artboard->width(), artboard->height());
+
+    auto stateMachine = artboard->stateMachineAt(0);
+    auto vmi = file->createDefaultViewModelInstance(artboard.get());
+
+    stateMachine->bindViewModelInstance(vmi);
+    auto renderer = silver.makeRenderer();
+
+    stateMachine->advanceAndApply(0.016f);
+    artboard->draw(renderer.get());
+    silver.addFrame();
+
+    // Trigger first timeline events
+    stateMachine->advanceAndApply(0.5f);
+    artboard->draw(renderer.get());
+    silver.addFrame();
+
+    // Trigger second timeline events
+    stateMachine->advanceAndApply(0.5f);
+    artboard->draw(renderer.get());
+    silver.addFrame();
+
+    // Process second timeline events
+    stateMachine->advanceAndApply(0.016f);
+    artboard->draw(renderer.get());
+    silver.addFrame();
+
+    stateMachine->pointerMove(rive::Vec2D(490.0f, 10.0f));
+    stateMachine->advanceAndApply(0.016f);
+    artboard->draw(renderer.get());
+    silver.addFrame();
+
+    stateMachine->pointerDown(rive::Vec2D(490.0f, 10.0f));
+    stateMachine->advanceAndApply(0.016f);
+    artboard->draw(renderer.get());
+    silver.addFrame();
+
+    stateMachine->pointerUp(rive::Vec2D(490.0f, 10.0f));
+    stateMachine->advanceAndApply(0.016f);
+    artboard->draw(renderer.get());
+
+    CHECK(silver.matches("multi_listeners"));
+}
+
+TEST_CASE("Listeners with multiple types of events and rebinding", "[silver]")
+{
+    SerializingFactory silver;
+    auto file = ReadRiveFile("assets/multi_listeners.riv", &silver);
+
+    auto artboard = file->artboardNamed("Rebind");
+    silver.frameSize(artboard->width(), artboard->height());
+
+    auto stateMachine = artboard->stateMachineAt(0);
+    auto vmi = file->createDefaultViewModelInstance(artboard.get());
+
+    auto vmiChildProp =
+        vmi->propertyValue("child")->as<ViewModelInstanceViewModel>();
+    auto vmiChild = vmiChildProp->referenceViewModelInstance();
+
+    stateMachine->bindViewModelInstance(vmi);
+    auto renderer = silver.makeRenderer();
+
+    stateMachine->advanceAndApply(0.016f);
+    artboard->draw(renderer.get());
+    silver.addFrame();
+    {
+        auto num1 =
+            vmiChild->propertyValue("num")->as<ViewModelInstanceNumber>();
+        auto num2 =
+            vmiChild->propertyValue("num2")->as<ViewModelInstanceNumber>();
+        num1->propertyValue(2);
+        stateMachine->advanceAndApply(0.016f);
+        artboard->draw(renderer.get());
+        silver.addFrame();
+
+        num2->propertyValue(2);
+        stateMachine->advanceAndApply(0.016f);
+        artboard->draw(renderer.get());
+        silver.addFrame();
+    }
+
+    // Create a new view model instance for RebindChild that will be used to
+    // replace the vm tree
+    auto vmiChild2 = file->createViewModelInstance("RebindChild");
+    if (vmiChild2)
+    {
+        auto num1 =
+            vmiChild2->propertyValue("num")->as<ViewModelInstanceNumber>();
+        auto num2 =
+            vmiChild2->propertyValue("num2")->as<ViewModelInstanceNumber>();
+        vmi->replaceViewModelByName("child", vmiChild2);
+        stateMachine->advanceAndApply(0.016f);
+        artboard->draw(renderer.get());
+        silver.addFrame();
+        num1->propertyValue(2);
+        stateMachine->advanceAndApply(0.016f);
+        artboard->draw(renderer.get());
+        silver.addFrame();
+        num2->propertyValue(2);
+        stateMachine->advanceAndApply(0.016f);
+        artboard->draw(renderer.get());
+        silver.addFrame();
+        // Nothing should change in this case
+        auto num1Old =
+            vmiChild->propertyValue("num")->as<ViewModelInstanceNumber>();
+        num1Old->propertyValue(3);
+        stateMachine->advanceAndApply(0.016f);
+        artboard->draw(renderer.get());
+    }
+
+    CHECK(silver.matches("multi_listeners-rebind"));
 }
\ No newline at end of file
diff --git a/tests/unit_tests/silvers/multi_listeners-rebind.sriv b/tests/unit_tests/silvers/multi_listeners-rebind.sriv
new file mode 100644
index 0000000..a91e057
--- /dev/null
+++ b/tests/unit_tests/silvers/multi_listeners-rebind.sriv
Binary files differ
diff --git a/tests/unit_tests/silvers/multi_listeners.sriv b/tests/unit_tests/silvers/multi_listeners.sriv
new file mode 100644
index 0000000..4b6f8a5
--- /dev/null
+++ b/tests/unit_tests/silvers/multi_listeners.sriv
Binary files differ