+ ): ViewModelTriggerProperty | undefined;
+
+ enumProperty>(
+ path: P
+ ):
+ | TypedViewModelEnumProperty>>
+ | undefined;
+
+ imageProperty(
+ path: PathsOfKind
+ ): ViewModelImageProperty | undefined;
+
+ listProperty(
+ path: PathsOfKind
+ ): ViewModelListProperty | undefined;
+
+ /** Access a nested ViewModel instance; return type is typed to the referenced ViewModel. */
+ viewModel>(
+ path: P
+ ):
+ | TypedViewModelInstance<
+ T,
+ VMRefName & keyof T['viewModels'] & string
+ >
+ | undefined;
+
+ viewModelAsync>(
+ path: P
+ ): Promise<
+ | TypedViewModelInstance<
+ T,
+ VMRefName & keyof T['viewModels'] & string
+ >
+ | undefined
+ >;
+
+ /** Brand that prevents typed instances from matching untyped hook overloads. */
+ readonly __vmBrand: [T, VMName];
+}
+
+/**
+ * A plain ViewModelInstance with no schema type information.
+ * Used in the untyped hook overloads — typed instances are intentionally
+ * excluded so TypeScript picks the typed overload when a schema is known.
+ */
+export type UntypedViewModelInstance = ViewModelInstance & {
+ __vmBrand?: never;
+};
+
+/**
+ * Convenience alias: infer the ViewModel instance type directly from a RiveAsset import.
+ *
+ * @example
+ * import rewardsRiv from './rewards.riv';
+ * type RewardsInstance = TypedViewModelOf;
+ */
+export type TypedViewModelOf<
+ T extends RiveFileSchema | RiveAsset,
+ VMName extends Extract['viewModels'], string>,
+> = TypedViewModelInstance, VMName>;
diff --git a/src/hooks/useRiveBoolean.ts b/src/hooks/useRiveBoolean.ts
index 657d9ad6..f6449aa0 100644
--- a/src/hooks/useRiveBoolean.ts
+++ b/src/hooks/useRiveBoolean.ts
@@ -4,6 +4,12 @@ import {
} from '../specs/ViewModel.nitro';
import type { UseRivePropertyResult } from '../types';
import { useRiveProperty } from './useRiveProperty';
+import type {
+ PathsOfKind,
+ TypedViewModelInstance,
+ UntypedViewModelInstance,
+} from '../core/TypedViewModelInstance';
+import type { RiveFileSchema } from '../core/TypedRiveFile';
const getBooleanProperty = (vmi: ViewModelInstance, p: string) =>
vmi.booleanProperty(p);
@@ -15,6 +21,17 @@ const getBooleanProperty = (vmi: ViewModelInstance, p: string) =>
* @param viewModelInstance - The ViewModelInstance containing the boolean property to operate on
* @returns An object with the boolean value, a setter function, and an error if the property is not found
*/
+export function useRiveBoolean<
+ T extends RiveFileSchema,
+ N extends Extract,
+>(
+ path: PathsOfKind,
+ viewModelInstance?: TypedViewModelInstance | null
+): UseRivePropertyResult;
+export function useRiveBoolean(
+ path: string,
+ viewModelInstance?: UntypedViewModelInstance | null
+): UseRivePropertyResult;
export function useRiveBoolean(
path: string,
viewModelInstance?: ViewModelInstance | null
diff --git a/src/hooks/useRiveColor.ts b/src/hooks/useRiveColor.ts
index bd6ce38e..f16ccfd8 100644
--- a/src/hooks/useRiveColor.ts
+++ b/src/hooks/useRiveColor.ts
@@ -5,6 +5,12 @@ import type {
} from '../specs/ViewModel.nitro';
import { useRiveProperty } from './useRiveProperty';
import { RiveColor } from '../core/RiveColor';
+import type {
+ PathsOfKind,
+ TypedViewModelInstance,
+ UntypedViewModelInstance,
+} from '../core/TypedViewModelInstance';
+import type { RiveFileSchema } from '../core/TypedRiveFile';
const getColorProperty = (vmi: ViewModelInstance, p: string) =>
vmi.colorProperty(p);
@@ -22,6 +28,17 @@ export interface UseRiveColorResult {
* @param viewModelInstance - The ViewModelInstance containing the color property to operate on
* @returns An object with the color value as RGBA, a setter function that accepts either RGBA or hex string, and an error if the property is not found
*/
+export function useRiveColor<
+ T extends RiveFileSchema,
+ N extends Extract,
+>(
+ path: PathsOfKind,
+ viewModelInstance?: TypedViewModelInstance | null
+): UseRiveColorResult;
+export function useRiveColor(
+ path: string,
+ viewModelInstance?: UntypedViewModelInstance | null
+): UseRiveColorResult;
export function useRiveColor(
path: string,
viewModelInstance?: ViewModelInstance | null
diff --git a/src/hooks/useRiveEnum.ts b/src/hooks/useRiveEnum.ts
index ee5d67ba..fb87d65c 100644
--- a/src/hooks/useRiveEnum.ts
+++ b/src/hooks/useRiveEnum.ts
@@ -3,18 +3,33 @@ import {
type ViewModelInstance,
} from '../specs/ViewModel.nitro';
import type { UseRivePropertyResult } from '../types';
+import type { RiveFileSchema } from '../core/TypedRiveFile';
+import {
+ type EnumValuesOf,
+ type PathsOfKind,
+ type PropTypeAtPath,
+ type TypedViewModelInstance,
+ type UntypedViewModelInstance,
+} from '../core/TypedViewModelInstance';
import { useRiveProperty } from './useRiveProperty';
const getEnumProperty = (vmi: ViewModelInstance, p: string) =>
vmi.enumProperty(p);
-/**
- * Hook for interacting with enum ViewModel instance properties.
- *
- * @param path - The path to the enum property
- * @param viewModelInstance - The ViewModelInstance containing the enum property to operate on
- * @returns An object with the enum value, a setter function, and an error if the property is not found
- */
+export function useRiveEnum<
+ T extends RiveFileSchema,
+ N extends Extract,
+ P extends PathsOfKind,
+>(
+ path: P,
+ viewModelInstance?: TypedViewModelInstance | null
+): UseRivePropertyResult>>;
+
+export function useRiveEnum(
+ path: string,
+ viewModelInstance?: UntypedViewModelInstance | null
+): UseRivePropertyResult;
+
export function useRiveEnum(
path: string,
viewModelInstance?: ViewModelInstance | null
diff --git a/src/hooks/useRiveFile.ts b/src/hooks/useRiveFile.ts
index 945c677b..77449754 100644
--- a/src/hooks/useRiveFile.ts
+++ b/src/hooks/useRiveFile.ts
@@ -12,6 +12,11 @@ import type {
ReferencedAssets,
ResolvedReferencedAssets,
} from '../core/ReferencedAssets';
+import type {
+ RiveAsset,
+ RiveFileSchema,
+ TypedRiveFile,
+} from '../core/TypedRiveFile';
export type { ReferencedAssets, ResolvedReferencedAssets };
export type RiveFileInput = number | { uri: string } | string | ArrayBuffer;
@@ -92,6 +97,17 @@ export type UseRiveFileResult =
| { riveFile: null; isLoading: false; error: Error }
| { riveFile: undefined; isLoading: true; error: null };
+export function useRiveFile(
+ input: RiveAsset,
+ options?: UseRiveFileOptions
+):
+ | { riveFile: TypedRiveFile; isLoading: false; error: null }
+ | { riveFile: null; isLoading: false; error: Error }
+ | { riveFile: undefined; isLoading: true; error: null };
+export function useRiveFile(
+ input: RiveFileInput | undefined,
+ options?: UseRiveFileOptions
+): UseRiveFileResult;
export function useRiveFile(
input: RiveFileInput | undefined,
options: UseRiveFileOptions = {}
diff --git a/src/hooks/useRiveNumber.ts b/src/hooks/useRiveNumber.ts
index 773d30e2..4cda0bb0 100644
--- a/src/hooks/useRiveNumber.ts
+++ b/src/hooks/useRiveNumber.ts
@@ -4,6 +4,12 @@ import {
} from '../specs/ViewModel.nitro';
import type { UseRivePropertyResult } from '../types';
import { useRiveProperty } from './useRiveProperty';
+import type {
+ PathsOfKind,
+ TypedViewModelInstance,
+ UntypedViewModelInstance,
+} from '../core/TypedViewModelInstance';
+import type { RiveFileSchema } from '../core/TypedRiveFile';
const getNumberProperty = (vmi: ViewModelInstance, p: string) =>
vmi.numberProperty(p);
@@ -15,6 +21,17 @@ const getNumberProperty = (vmi: ViewModelInstance, p: string) =>
* @param viewModelInstance - The ViewModelInstance containing the number property to operate on
* @returns An object with the number value, a setter function, and an error if the property is not found
*/
+export function useRiveNumber<
+ T extends RiveFileSchema,
+ N extends Extract,
+>(
+ path: PathsOfKind,
+ viewModelInstance?: TypedViewModelInstance | null
+): UseRivePropertyResult;
+export function useRiveNumber(
+ path: string,
+ viewModelInstance?: UntypedViewModelInstance | null
+): UseRivePropertyResult;
export function useRiveNumber(
path: string,
viewModelInstance?: ViewModelInstance | null
diff --git a/src/hooks/useRiveString.ts b/src/hooks/useRiveString.ts
index e182ddff..c5210e2a 100644
--- a/src/hooks/useRiveString.ts
+++ b/src/hooks/useRiveString.ts
@@ -4,6 +4,12 @@ import {
} from '../specs/ViewModel.nitro';
import type { UseRivePropertyResult } from '../types';
import { useRiveProperty } from './useRiveProperty';
+import type {
+ PathsOfKind,
+ TypedViewModelInstance,
+ UntypedViewModelInstance,
+} from '../core/TypedViewModelInstance';
+import type { RiveFileSchema } from '../core/TypedRiveFile';
const getStringProperty = (vmi: ViewModelInstance, p: string) =>
vmi.stringProperty(p);
@@ -15,6 +21,17 @@ const getStringProperty = (vmi: ViewModelInstance, p: string) =>
* @param viewModelInstance - The ViewModelInstance containing the string property to operate on
* @returns An object with the number value, a setter function, and an error if the property is not found
*/
+export function useRiveString<
+ T extends RiveFileSchema,
+ N extends Extract,
+>(
+ path: PathsOfKind,
+ viewModelInstance?: TypedViewModelInstance | null
+): UseRivePropertyResult;
+export function useRiveString(
+ path: string,
+ viewModelInstance?: UntypedViewModelInstance | null
+): UseRivePropertyResult;
export function useRiveString(
path: string,
viewModelInstance?: ViewModelInstance | null
diff --git a/src/hooks/useRiveTrigger.ts b/src/hooks/useRiveTrigger.ts
index 8f8315c9..9ca2c22d 100644
--- a/src/hooks/useRiveTrigger.ts
+++ b/src/hooks/useRiveTrigger.ts
@@ -8,6 +8,12 @@ import type {
UseViewModelInstanceTriggerParameters,
} from '../types';
import { useDisposableMemo } from './useDisposableMemo';
+import type {
+ PathsOfKind,
+ TypedViewModelInstance,
+ UntypedViewModelInstance,
+} from '../core/TypedViewModelInstance';
+import type { RiveFileSchema } from '../core/TypedRiveFile';
/**
* Hook for interacting with trigger ViewModel instance properties.
@@ -21,6 +27,19 @@ import { useDisposableMemo } from './useDisposableMemo';
* @param params - Optional parameters including onTrigger callback
* @returns A trigger function and any error
*/
+export function useRiveTrigger<
+ T extends RiveFileSchema,
+ N extends Extract,
+>(
+ path: PathsOfKind,
+ viewModelInstance?: TypedViewModelInstance | null,
+ params?: UseViewModelInstanceTriggerParameters
+): UseRiveTriggerResult;
+export function useRiveTrigger(
+ path: string,
+ viewModelInstance?: UntypedViewModelInstance | null,
+ params?: UseViewModelInstanceTriggerParameters
+): UseRiveTriggerResult;
export function useRiveTrigger(
path: string,
viewModelInstance?: ViewModelInstance | null,
diff --git a/src/hooks/useViewModelInstance.ts b/src/hooks/useViewModelInstance.ts
index b05adb5e..778233c7 100644
--- a/src/hooks/useViewModelInstance.ts
+++ b/src/hooks/useViewModelInstance.ts
@@ -3,6 +3,11 @@
import { useMemo, useRef } from 'react';
import type { ViewModel, ViewModelInstance } from '../specs/ViewModel.nitro';
import type { RiveFile } from '../specs/RiveFile.nitro';
+import type { RiveFileSchema } from '../core/TypedRiveFile';
+import type {
+ IsBaseSchema,
+ TypedViewModelInstance,
+} from '../core/TypedViewModelInstance';
import type { RiveViewRef } from '../index';
import { callDispose } from '../core/callDispose';
import { ArtboardByName } from '../specs/ArtboardBy';
@@ -83,6 +88,31 @@ export type UseViewModelInstanceFileParams =
| UseViewModelInstanceFileByArtboard
| UseViewModelInstanceFileByViewModelName;
+/**
+ * Valid `viewModelName` values for a file with schema `S` — any string when
+ * nothing is statically known about the file.
+ */
+type VMNameArg =
+ IsBaseSchema extends true
+ ? string
+ : Extract;
+
+/**
+ * File params whose `viewModelName` is constrained to the file's schema.
+ * Intersecting `N` with the valid names (instead of constraining `N` itself)
+ * makes an invalid name a hard error inside this overload rather than a
+ * constraint failure that would silently fall through to another overload.
+ */
+type UseViewModelInstanceFileParamsFor<
+ S extends RiveFileSchema,
+ N extends string,
+> =
+ | UseViewModelInstanceFileDefault
+ | UseViewModelInstanceFileByArtboard
+ | (Omit & {
+ viewModelName: N & VMNameArg;
+ });
+
export interface UseViewModelInstanceViewModelParams
extends UseViewModelInstanceBaseParams {
/**
@@ -218,6 +248,45 @@ export type UseViewModelInstanceRequiredResult =
| { instance: ViewModelInstance; isLoading: false; error: null }
| { instance: undefined; isLoading: true; error: null };
+/**
+ * File-source result: typed when the schema is statically known and
+ * `viewModelName` names one of its ViewModels, untyped otherwise.
+ */
+type UseViewModelInstanceFileResult<
+ S extends RiveFileSchema,
+ N extends string,
+> =
+ IsBaseSchema extends true
+ ? UseViewModelInstanceResult
+ : N extends Extract
+ ?
+ | {
+ instance: TypedViewModelInstance;
+ isLoading: false;
+ error: null;
+ }
+ | { instance: null; isLoading: false; error: Error }
+ | { instance: null; isLoading: false; error: null }
+ | { instance: undefined; isLoading: true; error: null }
+ : UseViewModelInstanceResult;
+
+/** File-source result with `required: true` — the `null` cases are removed. */
+type UseViewModelInstanceFileRequiredResult<
+ S extends RiveFileSchema,
+ N extends string,
+> =
+ IsBaseSchema extends true
+ ? UseViewModelInstanceRequiredResult
+ : N extends Extract
+ ?
+ | {
+ instance: TypedViewModelInstance;
+ isLoading: false;
+ error: null;
+ }
+ | { instance: undefined; isLoading: true; error: null }
+ : UseViewModelInstanceRequiredResult;
+
/**
* Hook for getting a ViewModelInstance from a RiveFile, ViewModel, or RiveViewRef.
*
@@ -309,25 +378,44 @@ export type UseViewModelInstanceRequiredResult =
* });
* ```
*/
-// RiveFile overloads
-export function useViewModelInstance(
- source: RiveFile,
- params: UseViewModelInstanceFileParams & { async: true; required: true }
-): UseViewModelInstanceRequiredResult;
-export function useViewModelInstance(
- source: RiveFile | null | undefined,
- params: UseViewModelInstanceFileParams & { async: true }
-): UseViewModelInstanceResult;
+// RiveFile overloads — schema-aware. When the file carries a generated schema
+// and `viewModelName` names one of its ViewModels, the instance is typed;
+// files without a generated schema (base RiveFileSchema) degrade to the
+// untyped result. An invalid `viewModelName` on a schema-typed file is a hard
+// error — it must not silently fall through to an untyped overload.
+export function useViewModelInstance<
+ S extends RiveFileSchema = RiveFileSchema,
+ N extends string = string,
+>(
+ source: RiveFile & { readonly __schema?: S },
+ params: UseViewModelInstanceFileParamsFor & {
+ async: true;
+ required: true;
+ }
+): UseViewModelInstanceFileRequiredResult;
+export function useViewModelInstance<
+ S extends RiveFileSchema = RiveFileSchema,
+ N extends string = string,
+>(
+ source: (RiveFile & { readonly __schema?: S }) | null | undefined,
+ params: UseViewModelInstanceFileParamsFor & { async: true }
+): UseViewModelInstanceFileResult;
/** @deprecated Pass `async: true` — without it the instance is created synchronously via deprecated runtime APIs that block the JS thread. `async: true` becomes the default in the next major. If your params object's `async` widened to `boolean`, re-pin it at the call site: `{ ...params, async: true }`. */
-export function useViewModelInstance(
- source: RiveFile,
- params: UseViewModelInstanceFileParams & { required: true }
-): UseViewModelInstanceRequiredResult;
+export function useViewModelInstance<
+ S extends RiveFileSchema = RiveFileSchema,
+ N extends string = string,
+>(
+ source: RiveFile & { readonly __schema?: S },
+ params: UseViewModelInstanceFileParamsFor & { required: true }
+): UseViewModelInstanceFileRequiredResult;
/** @deprecated Pass `async: true` — without it the instance is created synchronously via deprecated runtime APIs that block the JS thread. `async: true` becomes the default in the next major. If your params object's `async` widened to `boolean`, re-pin it at the call site: `{ ...params, async: true }`. */
-export function useViewModelInstance(
- source: RiveFile | null | undefined,
- params?: UseViewModelInstanceFileParams
-): UseViewModelInstanceResult;
+export function useViewModelInstance<
+ S extends RiveFileSchema = RiveFileSchema,
+ N extends string = string,
+>(
+ source: (RiveFile & { readonly __schema?: S }) | null | undefined,
+ params?: UseViewModelInstanceFileParamsFor
+): UseViewModelInstanceFileResult;
// ViewModel overloads
export function useViewModelInstance(
diff --git a/src/index.tsx b/src/index.tsx
index bbcfa4ee..6dc2675c 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -25,6 +25,21 @@ export type {
RiveEnumDefinition,
RiveAssetType,
} from './specs/RiveFile.nitro';
+export type {
+ RiveAsset,
+ RiveFileSchema,
+ SchemaOf,
+ TypedRiveFile,
+} from './core/TypedRiveFile';
+export type {
+ TypedViewModelInstance,
+ TypedViewModelOf,
+ UntypedViewModelInstance,
+ TypedViewModelEnumProperty,
+ PathsOfKind,
+ PropTypeAtPath,
+ EnumValuesOf,
+} from './core/TypedViewModelInstance';
export type {
ViewModel,
ViewModelInstance,
diff --git a/tsconfig.json b/tsconfig.json
index 478a91ad..dd9f69d6 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -24,7 +24,8 @@
"skipLibCheck": true,
"strict": true,
"target": "ESNext",
- "verbatimModuleSyntax": true
+ "verbatimModuleSyntax": true,
+ "allowArbitraryExtensions": true
},
- "exclude": ["expo-example", "expo55-example", "expo57-example", "**/*.test-d.ts"]
+"exclude": ["expo-example", "expo55-example", "expo57-example", "**/*.test-d.ts", "scripts/__tests__"]
}
diff --git a/yarn.lock b/yarn.lock
index 3a1d07da..33e6e616 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -7004,6 +7004,13 @@ __metadata:
languageName: node
linkType: hard
+"@rive-app/canvas@npm:^2.39.0":
+ version: 2.39.0
+ resolution: "@rive-app/canvas@npm:2.39.0"
+ checksum: a21b663691615c9a27253c6dfbf607167693de09d037b9a9e67d14d173edb8720575726b0b7d91dccb8f5321f7abbf948505714b7a88829540b3ed6c875815e2
+ languageName: node
+ linkType: hard
+
"@rive-app/react-native@workspace:.":
version: 0.0.0-use.local
resolution: "@rive-app/react-native@workspace:."
@@ -7015,6 +7022,7 @@ __metadata:
"@react-native/babel-preset": 0.79.2
"@react-native/eslint-config": ^0.78.0
"@release-it/conventional-changelog": ^9.0.2
+ "@rive-app/canvas": ^2.39.0
"@testing-library/react-hooks": ^8.0.1
"@testing-library/react-native": ^13.3.3
"@types/jest": ^29.5.5