@rive-app/react-native 0.1.1-beta.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/README.md +18 -7
  2. package/android/src/main/java/com/margelo/nitro/rive/BaseHybridViewModelProperty.kt +9 -7
  3. package/android/src/main/java/com/margelo/nitro/rive/BaseHybridViewModelPropertyImpl.kt +43 -24
  4. package/android/src/main/java/com/margelo/nitro/rive/HybridRiveFile.kt +0 -1
  5. package/android/src/main/java/com/margelo/nitro/rive/HybridViewModelBooleanProperty.kt +3 -2
  6. package/android/src/main/java/com/margelo/nitro/rive/HybridViewModelColorProperty.kt +3 -2
  7. package/android/src/main/java/com/margelo/nitro/rive/HybridViewModelEnumProperty.kt +3 -2
  8. package/android/src/main/java/com/margelo/nitro/rive/HybridViewModelImageProperty.kt +3 -2
  9. package/android/src/main/java/com/margelo/nitro/rive/HybridViewModelInstance.kt +26 -47
  10. package/android/src/main/java/com/margelo/nitro/rive/HybridViewModelListProperty.kt +64 -0
  11. package/android/src/main/java/com/margelo/nitro/rive/HybridViewModelNumberProperty.kt +3 -2
  12. package/android/src/main/java/com/margelo/nitro/rive/HybridViewModelStringProperty.kt +3 -2
  13. package/android/src/main/java/com/margelo/nitro/rive/HybridViewModelTriggerProperty.kt +3 -2
  14. package/ios/BaseHybridViewModelProperty.swift +22 -6
  15. package/ios/HybridViewModel.swift +1 -6
  16. package/ios/HybridViewModelBooleanProperty.swift +1 -9
  17. package/ios/HybridViewModelColorProperty.swift +3 -12
  18. package/ios/HybridViewModelEnumProperty.swift +1 -9
  19. package/ios/HybridViewModelImageProperty.swift +4 -4
  20. package/ios/HybridViewModelInstance.swift +6 -6
  21. package/ios/HybridViewModelListProperty.swift +62 -0
  22. package/ios/HybridViewModelNumberProperty.swift +2 -13
  23. package/ios/HybridViewModelStringProperty.swift +1 -9
  24. package/ios/HybridViewModelTriggerProperty.swift +5 -14
  25. package/ios/RiveReactNativeView.swift +36 -0
  26. package/lib/module/hooks/useRiveColor.js +0 -1
  27. package/lib/module/hooks/useRiveColor.js.map +1 -1
  28. package/lib/module/hooks/useRiveList.js +71 -0
  29. package/lib/module/hooks/useRiveList.js.map +1 -0
  30. package/lib/module/hooks/useRiveProperty.js +6 -12
  31. package/lib/module/hooks/useRiveProperty.js.map +1 -1
  32. package/lib/module/hooks/useViewModelInstance.js +139 -0
  33. package/lib/module/hooks/useViewModelInstance.js.map +1 -0
  34. package/lib/module/index.js +2 -0
  35. package/lib/module/index.js.map +1 -1
  36. package/lib/typescript/src/hooks/useRiveColor.d.ts +6 -4
  37. package/lib/typescript/src/hooks/useRiveColor.d.ts.map +1 -1
  38. package/lib/typescript/src/hooks/useRiveList.d.ts +11 -0
  39. package/lib/typescript/src/hooks/useRiveList.d.ts.map +1 -0
  40. package/lib/typescript/src/hooks/useRiveProperty.d.ts +6 -1
  41. package/lib/typescript/src/hooks/useRiveProperty.d.ts.map +1 -1
  42. package/lib/typescript/src/hooks/useViewModelInstance.d.ts +86 -0
  43. package/lib/typescript/src/hooks/useViewModelInstance.d.ts.map +1 -0
  44. package/lib/typescript/src/index.d.ts +4 -1
  45. package/lib/typescript/src/index.d.ts.map +1 -1
  46. package/lib/typescript/src/specs/ViewModel.nitro.d.ts +39 -15
  47. package/lib/typescript/src/specs/ViewModel.nitro.d.ts.map +1 -1
  48. package/lib/typescript/src/types.d.ts +47 -3
  49. package/lib/typescript/src/types.d.ts.map +1 -1
  50. package/nitrogen/generated/android/c++/JHybridViewModelBooleanPropertySpec.cpp +14 -4
  51. package/nitrogen/generated/android/c++/JHybridViewModelBooleanPropertySpec.hpp +1 -1
  52. package/nitrogen/generated/android/c++/JHybridViewModelColorPropertySpec.cpp +14 -4
  53. package/nitrogen/generated/android/c++/JHybridViewModelColorPropertySpec.hpp +1 -1
  54. package/nitrogen/generated/android/c++/JHybridViewModelEnumPropertySpec.cpp +14 -4
  55. package/nitrogen/generated/android/c++/JHybridViewModelEnumPropertySpec.hpp +1 -1
  56. package/nitrogen/generated/android/c++/JHybridViewModelImagePropertySpec.cpp +15 -6
  57. package/nitrogen/generated/android/c++/JHybridViewModelImagePropertySpec.hpp +1 -1
  58. package/nitrogen/generated/android/c++/JHybridViewModelInstanceSpec.cpp +9 -0
  59. package/nitrogen/generated/android/c++/JHybridViewModelInstanceSpec.hpp +1 -0
  60. package/nitrogen/generated/android/c++/JHybridViewModelListPropertySpec.cpp +102 -0
  61. package/nitrogen/generated/android/c++/JHybridViewModelListPropertySpec.hpp +73 -0
  62. package/nitrogen/generated/android/c++/JHybridViewModelNumberPropertySpec.cpp +14 -4
  63. package/nitrogen/generated/android/c++/JHybridViewModelNumberPropertySpec.hpp +1 -1
  64. package/nitrogen/generated/android/c++/JHybridViewModelStringPropertySpec.cpp +14 -4
  65. package/nitrogen/generated/android/c++/JHybridViewModelStringPropertySpec.hpp +1 -1
  66. package/nitrogen/generated/android/c++/JHybridViewModelTriggerPropertySpec.cpp +12 -3
  67. package/nitrogen/generated/android/c++/JHybridViewModelTriggerPropertySpec.hpp +1 -1
  68. package/nitrogen/generated/android/kotlin/com/margelo/nitro/rive/HybridViewModelBooleanPropertySpec.kt +3 -3
  69. package/nitrogen/generated/android/kotlin/com/margelo/nitro/rive/HybridViewModelColorPropertySpec.kt +3 -3
  70. package/nitrogen/generated/android/kotlin/com/margelo/nitro/rive/HybridViewModelEnumPropertySpec.kt +3 -3
  71. package/nitrogen/generated/android/kotlin/com/margelo/nitro/rive/HybridViewModelImagePropertySpec.kt +3 -3
  72. package/nitrogen/generated/android/kotlin/com/margelo/nitro/rive/HybridViewModelInstanceSpec.kt +4 -0
  73. package/nitrogen/generated/android/kotlin/com/margelo/nitro/rive/HybridViewModelListPropertySpec.kt +92 -0
  74. package/nitrogen/generated/android/kotlin/com/margelo/nitro/rive/HybridViewModelNumberPropertySpec.kt +3 -3
  75. package/nitrogen/generated/android/kotlin/com/margelo/nitro/rive/HybridViewModelStringPropertySpec.kt +3 -3
  76. package/nitrogen/generated/android/kotlin/com/margelo/nitro/rive/HybridViewModelTriggerPropertySpec.kt +3 -3
  77. package/nitrogen/generated/android/rive+autolinking.cmake +2 -0
  78. package/nitrogen/generated/android/riveOnLoad.cpp +4 -74
  79. package/nitrogen/generated/ios/RNRive-Swift-Cxx-Bridge.cpp +17 -0
  80. package/nitrogen/generated/ios/RNRive-Swift-Cxx-Bridge.hpp +53 -0
  81. package/nitrogen/generated/ios/RNRive-Swift-Cxx-Umbrella.hpp +5 -0
  82. package/nitrogen/generated/ios/RNRiveAutolinking.mm +0 -72
  83. package/nitrogen/generated/ios/RNRiveAutolinking.swift +0 -135
  84. package/nitrogen/generated/ios/c++/HybridViewModelBooleanPropertySpecSwift.hpp +3 -1
  85. package/nitrogen/generated/ios/c++/HybridViewModelColorPropertySpecSwift.hpp +3 -1
  86. package/nitrogen/generated/ios/c++/HybridViewModelEnumPropertySpecSwift.hpp +3 -1
  87. package/nitrogen/generated/ios/c++/HybridViewModelImagePropertySpecSwift.hpp +3 -1
  88. package/nitrogen/generated/ios/c++/HybridViewModelInstanceSpecSwift.hpp +11 -0
  89. package/nitrogen/generated/ios/c++/HybridViewModelListPropertySpecSwift.cpp +11 -0
  90. package/nitrogen/generated/ios/c++/HybridViewModelListPropertySpecSwift.hpp +134 -0
  91. package/nitrogen/generated/ios/c++/HybridViewModelNumberPropertySpecSwift.hpp +3 -1
  92. package/nitrogen/generated/ios/c++/HybridViewModelStringPropertySpecSwift.hpp +3 -1
  93. package/nitrogen/generated/ios/c++/HybridViewModelTriggerPropertySpecSwift.hpp +3 -1
  94. package/nitrogen/generated/ios/swift/HybridViewModelBooleanPropertySpec.swift +1 -1
  95. package/nitrogen/generated/ios/swift/HybridViewModelBooleanPropertySpec_cxx.swift +8 -4
  96. package/nitrogen/generated/ios/swift/HybridViewModelColorPropertySpec.swift +1 -1
  97. package/nitrogen/generated/ios/swift/HybridViewModelColorPropertySpec_cxx.swift +8 -4
  98. package/nitrogen/generated/ios/swift/HybridViewModelEnumPropertySpec.swift +1 -1
  99. package/nitrogen/generated/ios/swift/HybridViewModelEnumPropertySpec_cxx.swift +8 -4
  100. package/nitrogen/generated/ios/swift/HybridViewModelImagePropertySpec.swift +1 -1
  101. package/nitrogen/generated/ios/swift/HybridViewModelImagePropertySpec_cxx.swift +8 -4
  102. package/nitrogen/generated/ios/swift/HybridViewModelInstanceSpec.swift +1 -0
  103. package/nitrogen/generated/ios/swift/HybridViewModelInstanceSpec_cxx.swift +21 -0
  104. package/nitrogen/generated/ios/swift/HybridViewModelListPropertySpec.swift +63 -0
  105. package/nitrogen/generated/ios/swift/HybridViewModelListPropertySpec_cxx.swift +248 -0
  106. package/nitrogen/generated/ios/swift/HybridViewModelNumberPropertySpec.swift +1 -1
  107. package/nitrogen/generated/ios/swift/HybridViewModelNumberPropertySpec_cxx.swift +8 -4
  108. package/nitrogen/generated/ios/swift/HybridViewModelStringPropertySpec.swift +1 -1
  109. package/nitrogen/generated/ios/swift/HybridViewModelStringPropertySpec_cxx.swift +8 -4
  110. package/nitrogen/generated/ios/swift/HybridViewModelTriggerPropertySpec.swift +1 -1
  111. package/nitrogen/generated/ios/swift/HybridViewModelTriggerPropertySpec_cxx.swift +8 -4
  112. package/nitrogen/generated/shared/c++/HybridViewModelBooleanPropertySpec.hpp +1 -1
  113. package/nitrogen/generated/shared/c++/HybridViewModelColorPropertySpec.hpp +1 -1
  114. package/nitrogen/generated/shared/c++/HybridViewModelEnumPropertySpec.hpp +1 -1
  115. package/nitrogen/generated/shared/c++/HybridViewModelImagePropertySpec.hpp +1 -1
  116. package/nitrogen/generated/shared/c++/HybridViewModelInstanceSpec.cpp +1 -0
  117. package/nitrogen/generated/shared/c++/HybridViewModelInstanceSpec.hpp +4 -0
  118. package/nitrogen/generated/shared/c++/HybridViewModelListPropertySpec.cpp +30 -0
  119. package/nitrogen/generated/shared/c++/HybridViewModelListPropertySpec.hpp +76 -0
  120. package/nitrogen/generated/shared/c++/HybridViewModelNumberPropertySpec.hpp +1 -1
  121. package/nitrogen/generated/shared/c++/HybridViewModelStringPropertySpec.hpp +1 -1
  122. package/nitrogen/generated/shared/c++/HybridViewModelTriggerPropertySpec.hpp +1 -1
  123. package/package.json +3 -3
  124. package/src/hooks/useRiveColor.ts +7 -4
  125. package/src/hooks/useRiveList.ts +108 -0
  126. package/src/hooks/useRiveProperty.ts +20 -13
  127. package/src/hooks/useViewModelInstance.ts +195 -0
  128. package/src/index.tsx +4 -0
  129. package/src/specs/ViewModel.nitro.ts +43 -15
  130. package/src/types.tsx +58 -3
@@ -0,0 +1,76 @@
1
+ ///
2
+ /// HybridViewModelListPropertySpec.hpp
3
+ /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
4
+ /// https://github.com/mrousavy/nitro
5
+ /// Copyright © 2025 Marc Rousavy @ Margelo
6
+ ///
7
+
8
+ #pragma once
9
+
10
+ #if __has_include(<NitroModules/HybridObject.hpp>)
11
+ #include <NitroModules/HybridObject.hpp>
12
+ #else
13
+ #error NitroModules cannot be found! Are you sure you installed NitroModules properly?
14
+ #endif
15
+
16
+ // Forward declaration of `HybridViewModelInstanceSpec` to properly resolve imports.
17
+ namespace margelo::nitro::rive { class HybridViewModelInstanceSpec; }
18
+ // Forward declaration of `HybridViewModelPropertySpec` to properly resolve imports.
19
+ namespace margelo::nitro::rive { class HybridViewModelPropertySpec; }
20
+
21
+ #include <memory>
22
+ #include "HybridViewModelInstanceSpec.hpp"
23
+ #include <optional>
24
+ #include <functional>
25
+ #include "HybridViewModelPropertySpec.hpp"
26
+
27
+ namespace margelo::nitro::rive {
28
+
29
+ using namespace margelo::nitro;
30
+
31
+ /**
32
+ * An abstract base class for `ViewModelListProperty`
33
+ * Inherit this class to create instances of `HybridViewModelListPropertySpec` in C++.
34
+ * You must explicitly call `HybridObject`'s constructor yourself, because it is virtual.
35
+ * @example
36
+ * ```cpp
37
+ * class HybridViewModelListProperty: public HybridViewModelListPropertySpec {
38
+ * public:
39
+ * HybridViewModelListProperty(...): HybridObject(TAG) { ... }
40
+ * // ...
41
+ * };
42
+ * ```
43
+ */
44
+ class HybridViewModelListPropertySpec: public virtual HybridObject, public virtual HybridViewModelPropertySpec {
45
+ public:
46
+ // Constructor
47
+ explicit HybridViewModelListPropertySpec(): HybridObject(TAG) { }
48
+
49
+ // Destructor
50
+ ~HybridViewModelListPropertySpec() override = default;
51
+
52
+ public:
53
+ // Properties
54
+ virtual double getLength() = 0;
55
+
56
+ public:
57
+ // Methods
58
+ virtual std::optional<std::shared_ptr<HybridViewModelInstanceSpec>> getInstanceAt(double index) = 0;
59
+ virtual void addInstance(const std::shared_ptr<HybridViewModelInstanceSpec>& instance) = 0;
60
+ virtual bool addInstanceAt(const std::shared_ptr<HybridViewModelInstanceSpec>& instance, double index) = 0;
61
+ virtual void removeInstance(const std::shared_ptr<HybridViewModelInstanceSpec>& instance) = 0;
62
+ virtual void removeInstanceAt(double index) = 0;
63
+ virtual bool swap(double index1, double index2) = 0;
64
+ virtual std::function<void()> addListener(const std::function<void()>& onChanged) = 0;
65
+ virtual void removeListeners() = 0;
66
+
67
+ protected:
68
+ // Hybrid Setup
69
+ void loadHybridMethods() override;
70
+
71
+ protected:
72
+ // Tag for logging
73
+ static constexpr auto TAG = "ViewModelListProperty";
74
+ };
75
+
76
+ } // namespace margelo::nitro::rive
@@ -52,7 +52,7 @@ namespace margelo::nitro::rive {
52
52
 
53
53
  public:
54
54
  // Methods
55
- virtual void addListener(const std::function<void(double /* value */)>& onChanged) = 0;
55
+ virtual std::function<void()> addListener(const std::function<void(double /* value */)>& onChanged) = 0;
56
56
  virtual void removeListeners() = 0;
57
57
 
58
58
  protected:
@@ -53,7 +53,7 @@ namespace margelo::nitro::rive {
53
53
 
54
54
  public:
55
55
  // Methods
56
- virtual void addListener(const std::function<void(const std::string& /* value */)>& onChanged) = 0;
56
+ virtual std::function<void()> addListener(const std::function<void(const std::string& /* value */)>& onChanged) = 0;
57
57
  virtual void removeListeners() = 0;
58
58
 
59
59
  protected:
@@ -51,7 +51,7 @@ namespace margelo::nitro::rive {
51
51
 
52
52
  public:
53
53
  // Methods
54
- virtual void addListener(const std::function<void()>& onChanged) = 0;
54
+ virtual std::function<void()> addListener(const std::function<void()>& onChanged) = 0;
55
55
  virtual void trigger() = 0;
56
56
  virtual void removeListeners() = 0;
57
57
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rive-app/react-native",
3
- "version": "0.1.1-beta.1",
3
+ "version": "0.1.2",
4
4
  "description": "Rive React Native",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
@@ -64,8 +64,8 @@
64
64
  },
65
65
  "homepage": "https://github.com/rive-app/rive-nitro-react-native#readme",
66
66
  "runtimeVersions": {
67
- "ios": "6.12.0",
68
- "android": "10.5.0"
67
+ "ios": "6.12.3",
68
+ "android": "10.5.3"
69
69
  },
70
70
  "publishConfig": {
71
71
  "registry": "https://registry.npmjs.org/"
@@ -3,7 +3,6 @@ import type {
3
3
  ViewModelColorProperty,
4
4
  ViewModelInstance,
5
5
  } from '../specs/ViewModel.nitro';
6
- import { type UseRivePropertyResult } from '../types';
7
6
  import { useRiveProperty } from './useRiveProperty';
8
7
  import { RiveColor } from '../core/RiveColor';
9
8
 
@@ -11,6 +10,12 @@ const COLOR_PROPERTY_OPTIONS = {
11
10
  getProperty: (vmi: ViewModelInstance, p: string) => vmi.colorProperty(p),
12
11
  };
13
12
 
13
+ export interface UseRiveColorResult {
14
+ value: RiveColor | undefined;
15
+ setValue: (value: RiveColor | string) => void;
16
+ error: Error | null;
17
+ }
18
+
14
19
  /**
15
20
  * Hook for interacting with color ViewModel instance properties.
16
21
  *
@@ -21,9 +26,7 @@ const COLOR_PROPERTY_OPTIONS = {
21
26
  export function useRiveColor(
22
27
  path: string,
23
28
  viewModelInstance?: ViewModelInstance | null
24
- ): UseRivePropertyResult<RiveColor> & {
25
- setValue: (value: RiveColor | string) => void;
26
- } {
29
+ ): UseRiveColorResult {
27
30
  const [rawValue, setRawValue, error] = useRiveProperty<
28
31
  ViewModelColorProperty,
29
32
  number
@@ -0,0 +1,108 @@
1
+ import { useCallback, useEffect, useState, useMemo } from 'react';
2
+ import type { ViewModelInstance } from '../specs/ViewModel.nitro';
3
+ import type { UseRiveListResult } from '../types';
4
+
5
+ /**
6
+ * Hook for interacting with list ViewModel instance properties.
7
+ *
8
+ * @param path - The path to the list property
9
+ * @param viewModelInstance - The ViewModelInstance containing the list property
10
+ * @returns An object with list length, manipulation methods, and error state
11
+ */
12
+ export function useRiveList(
13
+ path: string,
14
+ viewModelInstance?: ViewModelInstance | null
15
+ ): UseRiveListResult {
16
+ const [error, setError] = useState<Error | null>(null);
17
+ const [revision, setRevision] = useState(0);
18
+
19
+ useEffect(() => {
20
+ setError(null);
21
+ }, [path, viewModelInstance]);
22
+
23
+ const property = useMemo(() => {
24
+ if (!viewModelInstance) return undefined;
25
+ return viewModelInstance.listProperty(path);
26
+ }, [viewModelInstance, path]);
27
+
28
+ useEffect(() => {
29
+ if (viewModelInstance && !property) {
30
+ setError(
31
+ new Error(`List property "${path}" not found in the ViewModel instance`)
32
+ );
33
+ }
34
+ }, [viewModelInstance, property, path]);
35
+
36
+ useEffect(() => {
37
+ if (!property) return;
38
+
39
+ const removeListener = property.addListener(() => {
40
+ setRevision((r) => r + 1);
41
+ });
42
+
43
+ return () => {
44
+ removeListener();
45
+ property.removeListeners();
46
+ property.dispose();
47
+ };
48
+ }, [property]);
49
+
50
+ const length = useMemo(() => {
51
+ // revision is used to trigger re-computation when list changes
52
+ revision;
53
+ return property?.length ?? 0;
54
+ }, [property, revision]);
55
+
56
+ const getInstanceAt = useCallback(
57
+ (index: number) => {
58
+ return property?.getInstanceAt(index);
59
+ },
60
+ [property]
61
+ );
62
+
63
+ const addInstance = useCallback(
64
+ (instance: ViewModelInstance) => {
65
+ property?.addInstance(instance);
66
+ },
67
+ [property]
68
+ );
69
+
70
+ const addInstanceAt = useCallback(
71
+ (instance: ViewModelInstance, index: number) => {
72
+ return property?.addInstanceAt(instance, index) ?? false;
73
+ },
74
+ [property]
75
+ );
76
+
77
+ const removeInstance = useCallback(
78
+ (instance: ViewModelInstance) => {
79
+ property?.removeInstance(instance);
80
+ },
81
+ [property]
82
+ );
83
+
84
+ const removeInstanceAt = useCallback(
85
+ (index: number) => {
86
+ property?.removeInstanceAt(index);
87
+ },
88
+ [property]
89
+ );
90
+
91
+ const swap = useCallback(
92
+ (index1: number, index2: number) => {
93
+ return property?.swap(index1, index2) ?? false;
94
+ },
95
+ [property]
96
+ );
97
+
98
+ return {
99
+ length,
100
+ getInstanceAt,
101
+ addInstance,
102
+ addInstanceAt,
103
+ removeInstance,
104
+ removeInstanceAt,
105
+ swap,
106
+ error,
107
+ };
108
+ }
@@ -28,7 +28,12 @@ export function useRiveProperty<P extends ViewModelProperty, T>(
28
28
  /** Optional override callback for property events (mainly used by triggers) */
29
29
  onPropertyEventOverride?: (...args: any[]) => void;
30
30
  }
31
- ): [T | undefined, (value: T) => void, Error | null, P | undefined] {
31
+ ): [
32
+ T | undefined,
33
+ (value: T | ((prevValue: T | undefined) => T)) => void,
34
+ Error | null,
35
+ P | undefined,
36
+ ] {
32
37
  const [value, setValue] = useState<T | undefined>(undefined);
33
38
  const [error, setError] = useState<Error | null>(null);
34
39
 
@@ -61,25 +66,21 @@ export function useRiveProperty<P extends ViewModelProperty, T>(
61
66
 
62
67
  // If an override callback is provided, use it.
63
68
  // Otherwise, use the default callback.
64
- if (options.onPropertyEventOverride) {
65
- property.addListener(options.onPropertyEventOverride);
66
- } else {
67
- property.addListener((newValue) => {
68
- setValue(newValue);
69
- });
70
- }
69
+ const removeListener = options.onPropertyEventOverride
70
+ ? property.addListener(options.onPropertyEventOverride)
71
+ : property.addListener((newValue) => {
72
+ setValue(newValue);
73
+ });
71
74
 
72
- // Cleanup: Remove listeners and dispose of the property
73
- // This ensures proper cleanup of event listeners and resources
74
75
  return () => {
75
- property.removeListeners();
76
+ removeListener();
76
77
  property.dispose();
77
78
  };
78
79
  }, [options, property]);
79
80
 
80
81
  // Set the value of the property
81
82
  const setPropertyValue = useCallback(
82
- (newValue: T) => {
83
+ (valueOrUpdater: T | ((prevValue: T | undefined) => T)) => {
83
84
  if (!property) {
84
85
  setError(
85
86
  new Error(
@@ -87,6 +88,12 @@ export function useRiveProperty<P extends ViewModelProperty, T>(
87
88
  )
88
89
  );
89
90
  } else {
91
+ const newValue =
92
+ typeof valueOrUpdater === 'function'
93
+ ? (valueOrUpdater as (prevValue: T | undefined) => T)(
94
+ property.value
95
+ )
96
+ : valueOrUpdater;
90
97
  property.value = newValue;
91
98
  }
92
99
  },
@@ -105,6 +112,6 @@ export function useRiveProperty<P extends ViewModelProperty, T>(
105
112
  interface ObservableViewModelProperty<T>
106
113
  extends ViewModelProperty,
107
114
  ObservableProperty {
108
- addListener: (onChanged: (value: T) => void) => void;
115
+ addListener: (onChanged: (value: T) => void) => () => void;
109
116
  value: T;
110
117
  }
@@ -0,0 +1,195 @@
1
+ import { useMemo, useEffect, useRef } from 'react';
2
+ import type { ViewModel, ViewModelInstance } from '../specs/ViewModel.nitro';
3
+ import type { RiveFile } from '../specs/RiveFile.nitro';
4
+ import type { RiveViewRef } from '../index';
5
+ import { callDispose } from '../core/callDispose';
6
+
7
+ export interface UseViewModelInstanceParams {
8
+ /**
9
+ * Get a specifically named instance from the ViewModel.
10
+ */
11
+ name?: string;
12
+ /**
13
+ * Create a new (blank) instance from the ViewModel.
14
+ */
15
+ useNew?: boolean;
16
+ /**
17
+ * If true, throws an error when the instance cannot be obtained.
18
+ * This is useful with Error Boundaries and ensures TypeScript knows
19
+ * the return value is non-null.
20
+ */
21
+ required?: boolean;
22
+ /**
23
+ * Called synchronously when a new instance is created, before the hook returns.
24
+ * Use this to set initial values that need to be available immediately.
25
+ * Note: This callback is excluded from deps - changing it won't recreate the instance.
26
+ */
27
+ onInit?: (instance: ViewModelInstance) => void;
28
+ }
29
+
30
+ type ViewModelSource = ViewModel | RiveFile | RiveViewRef;
31
+
32
+ function isRiveViewRef(source: ViewModelSource | null): source is RiveViewRef {
33
+ return (
34
+ source !== null && source !== undefined && 'getViewModelInstance' in source
35
+ );
36
+ }
37
+
38
+ function isRiveFile(source: ViewModelSource | null): source is RiveFile {
39
+ return (
40
+ source !== null &&
41
+ source !== undefined &&
42
+ 'defaultArtboardViewModel' in source
43
+ );
44
+ }
45
+
46
+ function createInstance(
47
+ source: ViewModelSource | null,
48
+ name: string | undefined,
49
+ useNew: boolean
50
+ ): { instance: ViewModelInstance | null; needsDispose: boolean } {
51
+ if (!source) {
52
+ return { instance: null, needsDispose: false };
53
+ }
54
+
55
+ if (isRiveViewRef(source)) {
56
+ const vmi = source.getViewModelInstance();
57
+ return { instance: vmi ?? null, needsDispose: false };
58
+ }
59
+
60
+ if (isRiveFile(source)) {
61
+ const viewModel = source.defaultArtboardViewModel();
62
+ const vmi = viewModel?.createDefaultInstance();
63
+ return { instance: vmi ?? null, needsDispose: true };
64
+ }
65
+
66
+ // ViewModel source
67
+ let vmi: ViewModelInstance | undefined;
68
+ if (name) {
69
+ vmi = source.createInstanceByName(name);
70
+ } else if (useNew) {
71
+ vmi = source.createInstance();
72
+ } else {
73
+ vmi = source.createDefaultInstance();
74
+ }
75
+ return { instance: vmi ?? null, needsDispose: true };
76
+ }
77
+
78
+ /**
79
+ * Hook for getting a ViewModelInstance from a RiveFile, ViewModel, or RiveViewRef.
80
+ *
81
+ * @param source - The RiveFile, ViewModel, or RiveViewRef to get an instance from
82
+ * @param params - Configuration for which instance to retrieve (only used with ViewModel)
83
+ * @returns The ViewModelInstance or null if not found
84
+ *
85
+ * @example
86
+ * ```tsx
87
+ * // From RiveFile (get default instance)
88
+ * const { riveFile } = useRiveFile(require('./animation.riv'));
89
+ * const instance = useViewModelInstance(riveFile);
90
+ * ```
91
+ *
92
+ * @example
93
+ * ```tsx
94
+ * // From RiveViewRef (get auto-bound instance)
95
+ * const { riveViewRef, setHybridRef } = useRive();
96
+ * const instance = useViewModelInstance(riveViewRef);
97
+ * ```
98
+ *
99
+ * @example
100
+ * ```tsx
101
+ * // From ViewModel
102
+ * const viewModel = file.viewModelByName('main');
103
+ * const instance = useViewModelInstance(viewModel);
104
+ * ```
105
+ *
106
+ * @example
107
+ * ```tsx
108
+ * // Create a new blank instance from ViewModel
109
+ * const viewModel = file.viewModelByName('TodoItem');
110
+ * const newInstance = useViewModelInstance(viewModel, { useNew: true });
111
+ * ```
112
+ *
113
+ * @example
114
+ * ```tsx
115
+ * // With required: true (throws if null, use with Error Boundary)
116
+ * const instance = useViewModelInstance(riveFile, { required: true });
117
+ * // instance is guaranteed to be non-null here
118
+ * ```
119
+ *
120
+ * @example
121
+ * ```tsx
122
+ * // With onInit to set initial values synchronously
123
+ * const instance = useViewModelInstance(riveFile, {
124
+ * onInit: (vmi) => {
125
+ * vmi.numberProperty('count').set(initialCount);
126
+ * vmi.stringProperty('name').set(userName);
127
+ * }
128
+ * });
129
+ * // Values are already set here
130
+ * ```
131
+ */
132
+ export function useViewModelInstance(
133
+ source: ViewModelSource,
134
+ params: UseViewModelInstanceParams & { required: true }
135
+ ): ViewModelInstance;
136
+ export function useViewModelInstance(
137
+ source: ViewModelSource | null,
138
+ params?: UseViewModelInstanceParams
139
+ ): ViewModelInstance | null;
140
+ export function useViewModelInstance(
141
+ source: ViewModelSource | null,
142
+ params?: UseViewModelInstanceParams
143
+ ): ViewModelInstance | null {
144
+ const name = params?.name;
145
+ const useNew = params?.useNew ?? false;
146
+ const required = params?.required ?? false;
147
+ const onInit = params?.onInit;
148
+
149
+ const prevInstanceRef = useRef<{
150
+ instance: ViewModelInstance | null;
151
+ needsDispose: boolean;
152
+ } | null>(null);
153
+
154
+ const result = useMemo(() => {
155
+ const created = createInstance(source, name, useNew);
156
+ if (created.instance && onInit) {
157
+ onInit(created.instance);
158
+ }
159
+ return created;
160
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- onInit excluded intentionally
161
+ }, [source, name, useNew]);
162
+
163
+ // Dispose previous instance if it changed and needed disposal
164
+ if (
165
+ prevInstanceRef.current &&
166
+ prevInstanceRef.current.instance !== result.instance &&
167
+ prevInstanceRef.current.needsDispose &&
168
+ prevInstanceRef.current.instance
169
+ ) {
170
+ callDispose(prevInstanceRef.current.instance);
171
+ }
172
+ prevInstanceRef.current = result;
173
+
174
+ // Cleanup on unmount
175
+ useEffect(() => {
176
+ return () => {
177
+ if (
178
+ prevInstanceRef.current?.needsDispose &&
179
+ prevInstanceRef.current.instance
180
+ ) {
181
+ callDispose(prevInstanceRef.current.instance);
182
+ prevInstanceRef.current = null;
183
+ }
184
+ };
185
+ }, []);
186
+
187
+ if (required && result.instance === null) {
188
+ throw new Error(
189
+ 'useViewModelInstance: Failed to get ViewModelInstance. ' +
190
+ 'Ensure the source has a valid ViewModel and instance available.'
191
+ );
192
+ }
193
+
194
+ return result.instance;
195
+ }
package/src/index.tsx CHANGED
@@ -30,6 +30,7 @@ export type {
30
30
  ViewModelEnumProperty,
31
31
  ViewModelTriggerProperty,
32
32
  ViewModelImageProperty,
33
+ ViewModelListProperty,
33
34
  } from './specs/ViewModel.nitro';
34
35
  export { Fit } from './core/Fit';
35
36
  export { Alignment } from './core/Alignment';
@@ -47,6 +48,9 @@ export { useRiveBoolean } from './hooks/useRiveBoolean';
47
48
  export { useRiveEnum } from './hooks/useRiveEnum';
48
49
  export { useRiveColor } from './hooks/useRiveColor';
49
50
  export { useRiveTrigger } from './hooks/useRiveTrigger';
51
+ export { useRiveList } from './hooks/useRiveList';
52
+ export { useViewModelInstance } from './hooks/useViewModelInstance';
50
53
  export { useRiveFile } from './hooks/useRiveFile';
51
54
  export { type RiveFileInput } from './hooks/useRiveFile';
55
+ export { type SetValueAction } from './types';
52
56
  export { DataBindMode };
@@ -52,13 +52,16 @@ export interface ViewModelInstance
52
52
 
53
53
  /** Get an image property from the view model instance at the given path */
54
54
  imageProperty(path: string): ViewModelImageProperty | undefined;
55
+
56
+ /** Get a list property from the view model instance at the given path */
57
+ listProperty(path: string): ViewModelListProperty | undefined;
55
58
  }
56
59
 
57
60
  export interface ViewModelProperty
58
61
  extends HybridObject<{ ios: 'swift'; android: 'kotlin' }> {}
59
62
 
60
63
  export interface ObservableProperty {
61
- /** Remove all listeners from the view model number property */
64
+ /** Remove all listeners from the property */
62
65
  removeListeners(): void;
63
66
  }
64
67
 
@@ -67,8 +70,8 @@ export interface ViewModelNumberProperty
67
70
  ObservableProperty {
68
71
  /** The value of the view model number property */
69
72
  value: number;
70
- /** Add a listener to the view model number property */
71
- addListener(onChanged: (value: number) => void): void;
73
+ /** Add a listener to the view model number property. Returns a function to remove the listener. */
74
+ addListener(onChanged: (value: number) => void): () => void;
72
75
  }
73
76
 
74
77
  export interface ViewModelStringProperty
@@ -76,8 +79,8 @@ export interface ViewModelStringProperty
76
79
  ObservableProperty {
77
80
  /** The value of the view model string property */
78
81
  value: string;
79
- /** Add a listener to the view model string property */
80
- addListener(onChanged: (value: string) => void): void;
82
+ /** Add a listener to the view model string property. Returns a function to remove the listener. */
83
+ addListener(onChanged: (value: string) => void): () => void;
81
84
  }
82
85
 
83
86
  export interface ViewModelBooleanProperty
@@ -85,8 +88,8 @@ export interface ViewModelBooleanProperty
85
88
  ObservableProperty {
86
89
  /** The value of the view model boolean property */
87
90
  value: boolean;
88
- /** Add a listener to the view model boolean property */
89
- addListener(onChanged: (value: boolean) => void): void;
91
+ /** Add a listener to the view model boolean property. Returns a function to remove the listener. */
92
+ addListener(onChanged: (value: boolean) => void): () => void;
90
93
  }
91
94
 
92
95
  export interface ViewModelColorProperty
@@ -94,8 +97,8 @@ export interface ViewModelColorProperty
94
97
  ObservableProperty {
95
98
  /** The value of the view model color property */
96
99
  value: number;
97
- /** Add a listener to the view model color property */
98
- addListener(onChanged: (value: number) => void): void;
100
+ /** Add a listener to the view model color property. Returns a function to remove the listener. */
101
+ addListener(onChanged: (value: number) => void): () => void;
99
102
  }
100
103
 
101
104
  export interface ViewModelEnumProperty
@@ -103,15 +106,15 @@ export interface ViewModelEnumProperty
103
106
  ObservableProperty {
104
107
  /** The value of the view model enum property */
105
108
  value: string;
106
- /** Add a listener to the view model enum property */
107
- addListener(onChanged: (value: string) => void): void;
109
+ /** Add a listener to the view model enum property. Returns a function to remove the listener. */
110
+ addListener(onChanged: (value: string) => void): () => void;
108
111
  }
109
112
 
110
113
  export interface ViewModelTriggerProperty
111
114
  extends ViewModelProperty,
112
115
  ObservableProperty {
113
- /** Add a listener to the view model trigger property */
114
- addListener(onChanged: () => void): void;
116
+ /** Add a listener to the view model trigger property. Returns a function to remove the listener. */
117
+ addListener(onChanged: () => void): () => void;
115
118
  /** Trigger the view model trigger property */
116
119
  trigger(): void;
117
120
  }
@@ -121,6 +124,31 @@ export interface ViewModelImageProperty
121
124
  ObservableProperty {
122
125
  /** Set the image property value */
123
126
  set(image: RiveImage | undefined): void;
124
- /** Add a listener to the view model image property */
125
- addListener(onChanged: () => void): void;
127
+ /** Add a listener to the view model image property. Returns a function to remove the listener. */
128
+ addListener(onChanged: () => void): () => void;
129
+ }
130
+
131
+ /**
132
+ * A list property that contains a dynamic collection of {@link ViewModelInstance} objects.
133
+ * @see {@link https://rive.app/docs/runtimes/data-binding#lists Rive Data Binding Lists}
134
+ */
135
+ export interface ViewModelListProperty
136
+ extends ViewModelProperty,
137
+ ObservableProperty {
138
+ /** The number of instances in the list */
139
+ readonly length: number;
140
+ /** Get the instance at the given index */
141
+ getInstanceAt(index: number): ViewModelInstance | undefined;
142
+ /** Add an instance to the end of the list */
143
+ addInstance(instance: ViewModelInstance): void;
144
+ /** Add an instance at the given index, returns true if successful */
145
+ addInstanceAt(instance: ViewModelInstance, index: number): boolean;
146
+ /** Remove an instance from the list */
147
+ removeInstance(instance: ViewModelInstance): void;
148
+ /** Remove the instance at the given index */
149
+ removeInstanceAt(index: number): void;
150
+ /** Swap the instances at the given indices, returns true if successful */
151
+ swap(index1: number, index2: number): boolean;
152
+ /** Add a listener to be notified when the list changes. Returns a function to remove the listener. */
153
+ addListener(onChanged: () => void): () => void;
126
154
  }