diff --git a/src/hooks/useViewModelInstanceBoolean.ts b/src/hooks/useViewModelInstanceBoolean.ts new file mode 100644 index 0000000..d707d82 --- /dev/null +++ b/src/hooks/useViewModelInstanceBoolean.ts @@ -0,0 +1,32 @@ +import { ViewModelInstanceBoolean } from '@rive-app/canvas'; +import { UseViewModelInstanceBooleanParameters, UseViewModelInstanceBooleanResult } from '../types'; +import { useViewModelInstancePropertyValues } from './useViewModelInstancePropertyValues'; + +/** + * Hook for interacting with boolean ViewModel instance properties. + * + * @param path Path to the property (e.g. "isVisible" or "nested/isVisible") + * @param userParameters Optional parameters including initial value + * @returns Object with value and setter function + */ +export default function useViewModelInstanceBoolean( + path: string, + userParameters?: UseViewModelInstanceBooleanParameters +): UseViewModelInstanceBooleanResult { + return useViewModelInstancePropertyValues< + boolean, + UseViewModelInstanceBooleanParameters, + ViewModelInstanceBoolean, + { value: boolean; setValue: (value: boolean) => void } + >( + path, + userParameters, + false, + (instance, name) => instance.boolean(name), + (instance) => instance.value, + (_instance, value, setValue) => ({ + value, + setValue + }) + ); +} \ No newline at end of file diff --git a/src/hooks/useViewModelInstanceColor.ts b/src/hooks/useViewModelInstanceColor.ts new file mode 100644 index 0000000..7566017 --- /dev/null +++ b/src/hooks/useViewModelInstanceColor.ts @@ -0,0 +1,43 @@ +import { ViewModelInstanceColor } from '@rive-app/canvas'; +import { UseViewModelInstanceColorResult, UseViewModelInstanceColorParameters } from '../types'; +import { useViewModelInstancePropertyValues } from './useViewModelInstancePropertyValues'; + +/** + * Hook for interacting with color ViewModel instance properties. + * + * @param path Path to the property (e.g. "color" or "nested/color") + * @param userParameters Optional parameters including initial value + * @returns Object with value, setter function, and color utilities + */ +export default function useViewModelInstanceColor( + path: string, + userParameters?: UseViewModelInstanceColorParameters +): UseViewModelInstanceColorResult { + return useViewModelInstancePropertyValues< + number, + UseViewModelInstanceColorParameters, + ViewModelInstanceColor, + { + value: number; + setValue: (value: number) => void; + rgb: (r: number, g: number, b: number) => void; + rgba: (r: number, g: number, b: number, a: number) => void; + alpha: (a: number) => void; + opacity: (o: number) => void; + } + >( + path, + userParameters, + 0, + (instance, name) => instance.color(name), + (instance) => instance.value, + (instance, value, setValue) => ({ + value, + setValue, + rgb: (r, g, b) => instance?.rgb(r, g, b), + rgba: (r, g, b, a) => instance?.rgba(r, g, b, a), + alpha: (a) => instance?.alpha(a), + opacity: (o) => instance?.opacity(o), + }) + ); +} \ No newline at end of file diff --git a/src/hooks/useViewModelInstanceEnum.ts b/src/hooks/useViewModelInstanceEnum.ts new file mode 100644 index 0000000..ee53ada --- /dev/null +++ b/src/hooks/useViewModelInstanceEnum.ts @@ -0,0 +1,37 @@ +import { ViewModelInstanceEnum } from '@rive-app/canvas'; +import { UseViewModelInstanceEnumParameters, UseViewModelInstanceEnumResult } from '../types'; +import { useViewModelInstancePropertyValues } from './useViewModelInstancePropertyValues'; + +/** + * Hook for interacting with enum ViewModel instance properties. + * + * @param path Path to the property (e.g. "state" or "nested/state") + * @param userParameters Optional parameters including initial value + * @returns Object with value, values array, and setter function + */ +export default function useViewModelInstanceEnum( + path: string, + userParameters?: UseViewModelInstanceEnumParameters +): UseViewModelInstanceEnumResult { + return useViewModelInstancePropertyValues< + string, + UseViewModelInstanceEnumParameters, + ViewModelInstanceEnum, + { + value: string; + setValue: (value: string) => void; + values: string[]; + } + >( + path, + userParameters, + '', + (instance, name) => instance.enum(name), + (instance) => instance.value, + (instance, value, setValue) => ({ + value, + setValue, + values: instance?.values || [] + }) + ); +} \ No newline at end of file diff --git a/src/hooks/useViewModelInstanceNumber.ts b/src/hooks/useViewModelInstanceNumber.ts new file mode 100644 index 0000000..6c101fb --- /dev/null +++ b/src/hooks/useViewModelInstanceNumber.ts @@ -0,0 +1,32 @@ +import { ViewModelInstanceNumber } from '@rive-app/canvas'; +import { UseViewModelInstanceNumberParameters, UseViewModelInstanceNumberResult } from '../types'; +import { useViewModelInstancePropertyValues } from './useViewModelInstancePropertyValues'; + +/** + * Hook for interacting with numeric ViewModel instance properties. + * + * @param path Path to the property (e.g. "itemCount" or "nested/itemCount") + * @param userParameters Optional parameters including initial value + * @returns Object with value and setter function + */ +export default function useViewModelInstanceNumber( + path: string, + userParameters?: UseViewModelInstanceNumberParameters +): UseViewModelInstanceNumberResult { + return useViewModelInstancePropertyValues< + number, + UseViewModelInstanceNumberParameters, + ViewModelInstanceNumber, + { value: number; setValue: (value: number) => void } + >( + path, + userParameters, + 0, + (instance, name) => instance.number(name), + (instance) => instance.value, + (_instance, value, setValue) => ({ + value, + setValue + }) + ); +} \ No newline at end of file diff --git a/src/hooks/useViewModelInstanceProperty.ts b/src/hooks/useViewModelInstanceProperty.ts index f38739b..537e0ce 100644 --- a/src/hooks/useViewModelInstanceProperty.ts +++ b/src/hooks/useViewModelInstanceProperty.ts @@ -66,7 +66,7 @@ export default function useViewModelInstanceProperty( if (viewModelInstance) { let index = 0; while (index < path?.length) { - if(!viewModelInstance) { + if (!viewModelInstance) { return null; } viewModelInstance = viewModelInstance?.viewModel(path[index]); diff --git a/src/hooks/useViewModelInstancePropertyValues.ts b/src/hooks/useViewModelInstancePropertyValues.ts new file mode 100644 index 0000000..478bc51 --- /dev/null +++ b/src/hooks/useViewModelInstancePropertyValues.ts @@ -0,0 +1,110 @@ +import { useState, useEffect, useRef, useCallback } from 'react'; +import { EventCallback, EventType, ViewModelInstance, ViewModelInstanceValue } from '@rive-app/canvas'; +import { UseViewModelInstancePropertyType } from '../types'; +import useViewModelInstanceProperty from './useViewModelInstanceProperty'; + +export function useViewModelInstancePropertyValues< + T extends unknown, + P extends UseViewModelInstancePropertyType, + V extends ViewModelInstanceValue, + R extends Record +>( + path: string, + userParameters: P | undefined, + defaultValue: T, + propertyGetter: (instance: ViewModelInstance, name: string) => V | null, + valueGetter: (propertyInstance: V) => T, + resultBuilder: ( + propertyInstance: V | null, + value: T, + setValue: (value: T) => void + ) => R +): R { + + const [propertyInstance, setPropertyInstance] = useState(null); + const [value, setValueState] = useState( + (userParameters as any)?.initialValue ?? defaultValue + ); + + + const pathSegments = path.includes('/') ? path.split('/') : []; + const propertyName = path.includes('/') ? path.split('/').pop() || path : path; + const basePath = pathSegments.length > 0 ? pathSegments.slice(0, -1) : []; + + const viewModelInstance = useViewModelInstanceProperty(basePath, userParameters); + + // Track current arguments to prevent unnecessary updates + const currentArgs = useRef<{ + path: string, + parameters: P | undefined, + viewModelInstance: ViewModelInstance | null + } | null>(null); + + useEffect(() => { + function searchProperty() { + if (!viewModelInstance) { + setPropertyInstance(null); + return; + } + + const instance = propertyGetter(viewModelInstance, propertyName); + + if (instance !== null) { + if ((userParameters as any)?.initialValue !== undefined) { + (instance as any).value = (userParameters as any).initialValue; + } + + setValueState(valueGetter(instance)); + + setPropertyInstance(instance); + + currentArgs.current = { + parameters: userParameters, + path, + viewModelInstance, + }; + } + } + + const argsChanged = !currentArgs.current || + currentArgs.current.path !== path || + currentArgs.current.viewModelInstance !== viewModelInstance; + + if (argsChanged) { + userParameters?.rive?.on(EventType.Load, searchProperty); + searchProperty(); + } + + return () => { + userParameters?.rive?.off(EventType.Load, searchProperty); + }; + }, [path, userParameters, viewModelInstance, propertyName, valueGetter]); + + // We subscribe to value changes by default with the property hooks. + useEffect(() => { + if (!propertyInstance) return; + + const handleChange: EventCallback = (event) => { + + setValueState(event as unknown as T); + }; + + + propertyInstance.on(handleChange); + + return () => { + propertyInstance.off(handleChange); + }; + }, [propertyInstance]); + + const setValue = useCallback((newValue: T) => { + if (propertyInstance) { + (propertyInstance as any).value = newValue; + } else { + // If no instance yet, just update React state + setValueState(newValue); + } + }, [propertyInstance]); + + return resultBuilder(propertyInstance, value, setValue); +} \ No newline at end of file diff --git a/src/hooks/useViewModelInstanceString.ts b/src/hooks/useViewModelInstanceString.ts new file mode 100644 index 0000000..cac0279 --- /dev/null +++ b/src/hooks/useViewModelInstanceString.ts @@ -0,0 +1,32 @@ +import { ViewModelInstanceString } from '@rive-app/canvas'; +import { UseViewModelInstanceStringParameters, UseViewModelInstanceStringResult } from '../types'; +import { useViewModelInstancePropertyValues } from './useViewModelInstancePropertyValues'; + +/** + * Hook for interacting with string ViewModel instance properties. + * + * @param path Path to the property (e.g. "text" or "nested/text") + * @param userParameters Optional parameters including initial value + * @returns Object with value and setter function + */ +export default function useViewModelInstanceString( + path: string, + userParameters?: UseViewModelInstanceStringParameters +): UseViewModelInstanceStringResult { + return useViewModelInstancePropertyValues< + string, + UseViewModelInstanceStringParameters, + ViewModelInstanceString, + { value: string; setValue: (value: string) => void } + >( + path, + userParameters, + '', + (instance, name) => instance.string(name), + (instance) => instance.value, + (_instance, value, setValue) => ({ + value, + setValue + }) + ); +} \ No newline at end of file diff --git a/src/hooks/useViewModelInstanceTrigger.ts b/src/hooks/useViewModelInstanceTrigger.ts new file mode 100644 index 0000000..92f30d8 --- /dev/null +++ b/src/hooks/useViewModelInstanceTrigger.ts @@ -0,0 +1,48 @@ +import { useEffect, useCallback } from 'react'; +import { ViewModelInstanceTrigger } from '@rive-app/canvas'; +import { UseViewModelInstanceTriggerParameters, UseViewModelInstanceTriggerResult } from '../types'; +import { useViewModelInstancePropertyValues } from './useViewModelInstancePropertyValues'; + +/** + * Hook for interacting with trigger ViewModel instance properties. + * + * @param path Path to the property (e.g. "buttonPress" or "nested/buttonPress") + * @param userParameters Optional parameters including onTrigger callback + * @returns Object with trigger function + */ +export default function useViewModelInstanceTrigger( + path: string, + userParameters?: UseViewModelInstanceTriggerParameters +): UseViewModelInstanceTriggerResult { + return useViewModelInstancePropertyValues< + void, + UseViewModelInstanceTriggerParameters, + ViewModelInstanceTrigger, + { + trigger: () => void; + } + >( + path, + userParameters, + undefined, + (instance, name) => instance.trigger(name), + () => undefined, + (instance, _, __) => { + useEffect(() => { + if (instance && userParameters?.onTrigger) { + instance.on(userParameters.onTrigger); + + return () => { + instance.off(userParameters.onTrigger); + }; + } + }, [instance, userParameters?.onTrigger]); + + return { + trigger: useCallback(() => { + instance?.trigger(); + }, [instance]) + }; + } + ); +} \ No newline at end of file diff --git a/src/hooks/useViewModelNumber.ts b/src/hooks/useViewModelNumber.ts deleted file mode 100644 index cc3c961..0000000 --- a/src/hooks/useViewModelNumber.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { useState, useEffect, useRef } from 'react'; -import { - EventType, - ViewModelInstance, - ViewModelInstanceNumber, -} from '@rive-app/canvas'; -import { UseViewModelInstanceNumberParameters } from '../types'; -import useViewModelInstanceProperty from './useViewModelInstanceProperty'; - -const defaultParams: UseViewModelInstanceNumberParameters = { - viewModelInstance: null, - initialValue: 0, -}; - -const equal = ( - name: string, - params: UseViewModelInstanceNumberParameters | null, - viewModelInstance: ViewModelInstance | null, - to: HookArguments | null -): boolean => { - if (!params || !to) { - return false; - } - if ( - params.initialValue !== to.parameters.initialValue || - name !== to.name || - viewModelInstance !== to.viewModelInstance - ) { - return false; - } - return true; -}; - -type HookArguments = { - name: string, - parameters: UseViewModelInstanceNumberParameters, - viewModelInstance: ViewModelInstance | null, -} - -/** - * Custom hook for fetching a view model instance value. - * - * @param name - name of the propery - * @param path - Path to reach the required property - * @param userParameters - Parameters to load view model instance number - * @returns - */ -export default function useViewModelNumber( - name: string, - path: string[] = [], - userParameters?: UseViewModelInstanceNumberParameters -): ViewModelInstanceNumber | null { - const [viewModel, setViewModelValue] = - useState(null); - const currentArguments = useRef( - null - ); - - const viewModelInstance = useViewModelInstanceProperty(path, userParameters); - - useEffect(() => { - const parameters = { - ...defaultParams, - ...userParameters, - }; - - function searchViewModelValue() { - const instanceValue = viewModelInstance?.number(name) || null; - if(instanceValue !== null && parameters.initialValue !== undefined) { - instanceValue.value = parameters.initialValue; - } - setViewModelValue(instanceValue); - currentArguments.current = { - parameters, - name, - viewModelInstance, - }; - } - - if (!equal(name, parameters, viewModelInstance, currentArguments.current)) { - parameters.rive?.on(EventType.Load, searchViewModelValue); - searchViewModelValue(); - } - return () => { - parameters.rive?.off(EventType.Load, searchViewModelValue); - }; - }, [name, userParameters, viewModelInstance]); - - return viewModel; -} diff --git a/src/index.ts b/src/index.ts index 9173c42..baab06e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,7 +3,12 @@ import useRive from './hooks/useRive'; import useStateMachineInput from './hooks/useStateMachineInput'; import useViewModel from './hooks/useViewModel'; import useViewModelInstance from './hooks/useViewModelInstance'; -import useViewModelNumber from './hooks/useViewModelNumber'; +import useViewModelInstanceNumber from './hooks/useViewModelInstanceNumber'; +import useViewModelInstanceString from './hooks/useViewModelInstanceString'; +import useViewModelInstanceBoolean from './hooks/useViewModelInstanceBoolean'; +import useViewModelInstanceColor from './hooks/useViewModelInstanceColor'; +import useViewModelInstanceEnum from './hooks/useViewModelInstanceEnum'; +import useViewModelInstanceTrigger from './hooks/useViewModelInstanceTrigger'; import useViewModelProperties from './hooks/useViewModelProperties'; import useResizeCanvas from './hooks/useResizeCanvas'; import useRiveFile from './hooks/useRiveFile'; @@ -16,7 +21,12 @@ export { useRiveFile, useViewModel, useViewModelInstance, - useViewModelNumber, + useViewModelInstanceNumber, + useViewModelInstanceString, + useViewModelInstanceBoolean, + useViewModelInstanceColor, + useViewModelInstanceEnum, + useViewModelInstanceTrigger, useViewModelProperties, RiveProps, }; diff --git a/src/types.ts b/src/types.ts index 5f2a130..a39b92c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -99,19 +99,125 @@ export type UseViewModelInstanceColorParameters = initialValue?: number; }; +export type UseViewModelInstanceEnumParameters = + UseViewModelInstanceValueParameters & { + initialValue?: string; + }; + +export type UseViewModelInstanceTriggerParameters = UseViewModelInstanceValueParameters & { + /** + * Callback that runs when the trigger is fired. + */ + onTrigger?: () => void; +}; + + export type UseViewModelInstancePropertyType = | UseViewModelInstanceNumberParameters | UseViewModelInstanceStringParameters | UseViewModelInstanceBooleanParameters - | UseViewModelInstanceColorParameters; + | UseViewModelInstanceColorParameters + | UseViewModelInstanceEnumParameters; export type AcceptedVieModelType = T extends UseViewModelInstanceNumberParameters - ? ViewModelInstanceNumber - : T extends UseViewModelInstanceStringParameters - ? ViewModelInstanceString - : T extends UseViewModelInstanceBooleanParameters - ? ViewModelInstanceBoolean - : T extends UseViewModelInstanceColorParameters - ? ViewModelInstanceColor - : never; + ? ViewModelInstanceNumber + : T extends UseViewModelInstanceStringParameters + ? ViewModelInstanceString + : T extends UseViewModelInstanceBooleanParameters + ? ViewModelInstanceBoolean + : T extends UseViewModelInstanceColorParameters + ? ViewModelInstanceColor + : never; + +export type UseViewModelInstanceNumberResult = { + /** + * The current value of the number. + */ + value: number; + /** + * Set the value of the number. + * @param value - The value to set the number to. + */ + setValue: (value: number) => void; +}; +export type UseViewModelInstanceStringResult = { + /** + * The current value of the string. + */ + value: string; + /** + * Set the value of the string. + * @param value - The value to set the string to. + */ + setValue: (value: string) => void; +}; +export type UseViewModelInstanceBooleanResult = { + /** + * The current value of the boolean. + */ + value: boolean; + /** + * Set the value of the boolean. + * @param value - The value to set the boolean to. + */ + setValue: (value: boolean) => void; +}; + +export type UseViewModelInstanceColorResult = { + /** + * The current value of the color. + */ + value: number; + /** + * Set the value of the color. + * @param value - The value to set the color to. + */ + setValue: (value: number) => void; + /** + * Set the red value of the color. + * @param r - The red value to set the color to. + */ + rgb: (r: number, g: number, b: number) => void; + /** + * Set the red, green, blue, and alpha values of the color. + * @param r - The red value to set the color to. + * @param g - The green value to set the color to. + * @param b - The blue value to set the color to. + * @param a - The alpha value to set the color to. + */ + rgba: (r: number, g: number, b: number, a: number) => void; + /** + * Set the alpha value of the color. + * @param a - The alpha value to set the color to. + */ + alpha: (a: number) => void; + /** + * Set the opacity value of the color. + * @param o - The opacity value to set the color to. + */ + opacity: (o: number) => void; +}; + +export type UseViewModelInstanceEnumResult = { + /** + * The current value of the enum. + */ + value: string; + /** + * Set the value of the enum. + * @param value - The value to set the enum to. + */ + setValue: (value: string) => void; + /** + * The values of the enum. + */ + values: string[]; +}; + +export type UseViewModelInstanceTriggerResult = { + /** + * Fires the property trigger. + */ + trigger: () => void; +}; \ No newline at end of file