From 3866cb9e061f87da0c6da4872ec2d4827fd274d9 Mon Sep 17 00:00:00 2001 From: Adam <67035612+damzobridge@users.noreply.github.com> Date: Thu, 24 Apr 2025 15:33:07 -0700 Subject: [PATCH] fix: hot reload crash --- src/hooks/useViewModel.ts | 148 +++++----- src/hooks/useViewModelInstance.ts | 173 ++++++------ src/hooks/useViewModelInstanceBoolean.ts | 50 ++-- src/hooks/useViewModelInstanceColor.ts | 85 +++--- src/hooks/useViewModelInstanceEnum.ts | 60 +++-- src/hooks/useViewModelInstanceNumber.ts | 56 ++-- src/hooks/useViewModelInstanceProperty.ts | 255 +++++++++++------- .../useViewModelInstancePropertyValues.ts | 110 -------- src/hooks/useViewModelInstanceString.ts | 52 ++-- src/hooks/useViewModelInstanceTrigger.ts | 78 +++--- src/hooks/useViewModelProperties.ts | 173 ------------ src/hooks/useViewModelProperty.ts | 112 -------- src/index.ts | 36 ++- src/types.ts | 145 +++++----- 14 files changed, 633 insertions(+), 900 deletions(-) delete mode 100644 src/hooks/useViewModelInstancePropertyValues.ts delete mode 100644 src/hooks/useViewModelProperties.ts delete mode 100644 src/hooks/useViewModelProperty.ts diff --git a/src/hooks/useViewModel.ts b/src/hooks/useViewModel.ts index 9447205..aa3bb90 100644 --- a/src/hooks/useViewModel.ts +++ b/src/hooks/useViewModel.ts @@ -1,83 +1,95 @@ import { useState, useEffect, useRef } from 'react'; -import { EventType, Rive, ViewModel } from '@rive-app/canvas'; +import { Rive, ViewModel, EventType } from '@rive-app/canvas'; import { UseViewModelParameters } from '../types'; -const defaultParams: UseViewModelParameters = { useDefault: true }; -const equal = ( - params: UseViewModelParameters | null, - to: UseViewModelParameters | null -): boolean => { - if (!params || !to) { +function areParamsEqual( + prev?: UseViewModelParameters, + next?: UseViewModelParameters +): boolean { + if (prev === next) return true; + if (!prev || !next) return prev === next; + + if ('name' in prev && 'name' in next) { + return prev.name === next.name; + } + + if ('useDefault' in prev && 'useDefault' in next) { + return prev.useDefault === next.useDefault; + } + return false; - } - - if ('name' in params) { - return 'name' in to && params.name === to.name; - } - - if ('useDefault' in params) { - return 'useDefault' in to && params.useDefault === to.useDefault; - } - - return false; -}; +} /** - * Custom hook for fetching a view model. + * Hook for fetching a ViewModel from a Rive instance. * - * @param rive - Rive instance - * @param userParameters - Parameters to load view model - * @returns The ViewModel instance or null if not available - * - * @example - * // Use the default view model - * const viewModel = useViewModel(rive, { useDefault: true }); - * - * @example - * // Use a named view model - * const viewModel = useViewModel(rive, { name: 'myViewModel' }); + * @param params - Parameters for retrieving a ViewModel + * @param params.rive - The Rive instance to retrieve the ViewModel from + * @param params.name - When provided, specifies the name of the ViewModel to retrieve + * @param params.useDefault - When true, uses the default ViewModel from the Rive instance + * @returns The ViewModel or null if not found */ -export default function useViewModel( - rive: Rive | null, - userParameters?: UseViewModelParameters -): ViewModel | null { - const [viewModel, setViewModel] = useState(null); - const currentParams = useRef(null); +export default function useViewModel(params: UseViewModelParameters): ViewModel | null { + const { rive, name, useDefault = false } = params; + const riveRef = useRef(null); + const paramsRef = useRef(params); + const [viewModel, setViewModel] = useState(null); - useEffect(() => { - const parameters = userParameters || defaultParams; + const shouldUpdate = useRef(true); - function getViewModel(): ViewModel | null { - if (rive) { - if ('name' in parameters && parameters.name) { - return rive.viewModelByName(parameters.name); - } else if ('useDefault' in parameters && parameters.useDefault) { - return rive.defaultViewModel(); + useEffect(() => { + const isRiveChanged = riveRef.current !== rive; + const areParamsChanged = !areParamsEqual(paramsRef.current, params); + + shouldUpdate.current = isRiveChanged || areParamsChanged; + + riveRef.current = rive; + paramsRef.current = params; + }, [rive, name, useDefault]); + + useEffect(() => { + if (!shouldUpdate.current && viewModel) { + return; } - } - return null; - } - function setViewModelValue() { - if (!rive) { - setViewModel(null); - currentParams.current = null; - } else { - const viewModel = getViewModel(); - setViewModel(viewModel); - currentParams.current = parameters; - } - } + function fetchViewModel() { + const currentRive = riveRef.current; + const currentParams = paramsRef.current; - if (!equal(parameters, currentParams.current)) { - rive?.on(EventType.Load, setViewModelValue); - setViewModelValue(); - } - return () => { - rive?.off(EventType.Load, setViewModelValue); - }; - }, [rive, userParameters]); + if (!currentRive) { + setViewModel(null); + return; + } - return viewModel; -} + let model: ViewModel | null = null; + + if (currentParams && 'name' in currentParams && currentParams.name != null) { + model = currentRive.viewModelByName?.(currentParams.name) || null; + } else if (currentParams && currentParams.useDefault) { + const defaultViewModel = currentRive.defaultViewModel(); + if (defaultViewModel) { + model = defaultViewModel; + } + } + + setViewModel(model); + shouldUpdate.current = false; + } + + fetchViewModel(); + + const currentRive = riveRef.current; + if (currentRive) { + currentRive.on(EventType.Load, fetchViewModel); + } + + return () => { + if (currentRive) { + currentRive.off(EventType.Load, fetchViewModel); + } + }; + }, [rive, name, useDefault]); + + return viewModel; +} \ No newline at end of file diff --git a/src/hooks/useViewModelInstance.ts b/src/hooks/useViewModelInstance.ts index 053754c..abe6dc6 100644 --- a/src/hooks/useViewModelInstance.ts +++ b/src/hooks/useViewModelInstance.ts @@ -1,105 +1,104 @@ import { useState, useEffect, useRef } from 'react'; -import { - EventType, - Rive, - ViewModel, - ViewModelInstance, -} from '@rive-app/canvas'; +import { ViewModel, ViewModelInstance } from '@rive-app/canvas'; import { UseViewModelInstanceParameters } from '../types'; -const defaultParams: UseViewModelInstanceParameters = { useNew: true }; -const equal = ( - params: UseViewModelInstanceParameters | null, - to: UseViewModelInstanceParameters | null -): boolean => { - if (!params || !to) { +function areParamsEqual( + prev?: UseViewModelInstanceParameters, + next?: UseViewModelInstanceParameters +): boolean { + if (prev === next) return true; + if (!prev || !next) return prev === next; + + if ('name' in prev && 'name' in next) { + return prev.name === next.name; + } + + if ('useDefault' in prev && 'useDefault' in next) { + return prev.useDefault === next.useDefault; + } + + if ('useNew' in prev && 'useNew' in next) { + return prev.useNew === next.useNew; + } + return false; - } - - if ('name' in params) { - return 'name' in to && params.name === to.name; - } - - if ('useDefault' in params) { - return 'useDefault' in to && params.useDefault === to.useDefault; - } - - if ('useNew' in params) { - return 'useNew' in to && params.useNew === to.useNew; - } - - return false; -}; +} /** - * Custom hook for fetching a view model instance. + * Hook for fetching a ViewModelInstance from a ViewModel. * - * @param rive - Rive instance - * @param viewModel - ViewModel to get an instance from - * @param userParameters - Parameters to load view model instance - * @returns The ViewModelInstance or null if not available - * - * @example - * // Create a new instance of the view model - * const viewModelInstance = useViewModelInstance(rive, viewModel, { useNew: true }); - * - * @example - * // Use the default instance of the view model - * const viewModelInstance = useViewModelInstance(rive, viewModel, { useDefault: true }); - * - * @example - * // Use a named instance of the view model - * const viewModelInstance = useViewModelInstance(rive, viewModel, { name: 'myInstance' }); + * @param params - Parameters for retrieving a ViewModelInstance + * @param params.viewModel - The ViewModel to get an instance from + * @param params.name - When provided, specifies the name of the instance to retrieve + * @param params.useDefault - When true, uses the default instance from the ViewModel + * @param params.useNew - When true, creates a new instance of the ViewModel + * @param params.rive - If provided, automatically binds the instance to this Rive instance + * @returns The ViewModelInstance or null if not found */ export default function useViewModelInstance( - rive: Rive | null, - viewModel: ViewModel | null, - userParameters?: UseViewModelInstanceParameters + params: UseViewModelInstanceParameters ): ViewModelInstance | null { - const [viewModelInstance, setViewModelInstance] = - useState(null); - const currentParams = useRef(null); + const { viewModel, name, useDefault = false, useNew = false, rive } = params; + const [instance, setInstance] = useState(null); - useEffect(() => { - const parameters = userParameters || defaultParams; + const viewModelRef = useRef(viewModel); + const paramsRef = useRef(params); + const instanceRef = useRef(null); - function setInstance(instance: ViewModelInstance | null) { - setViewModelInstance(instance); - rive!.bindViewModelInstance(instance); - currentParams.current = parameters; - } + const shouldUpdate = useRef(true); - function getViewModelInstance(): ViewModelInstance | null { - if (viewModel) { - if ('name' in parameters && parameters.name) { - return viewModel.instanceByName(parameters.name); - } else if ('useDefault' in parameters && parameters.useDefault) { - return viewModel.defaultInstance(); - } else if ('useNew' in parameters && parameters.useNew) { - return viewModel.instance(); + useEffect(() => { + const isViewModelChanged = viewModelRef.current !== viewModel; + const areParamsChanged = !areParamsEqual(paramsRef.current, params); + + shouldUpdate.current = isViewModelChanged || areParamsChanged; + + viewModelRef.current = viewModel; + paramsRef.current = params; + }, [viewModel, name, useDefault, useNew]); + + useEffect(() => { + if (!shouldUpdate.current && instanceRef.current) { + return; } - } - return null; - } - function setViewModelValue() { - if (!rive || !viewModel) { - setViewModelInstance(null); - } else { - const instance = getViewModelInstance(); - setInstance(instance ?? null); - } - } + const currentViewModel = viewModelRef.current; + const currentParams = paramsRef.current; - if (!equal(parameters, currentParams.current)) { - rive?.on(EventType.Load, setViewModelValue); - setViewModelValue(); - } - return () => { - rive?.off(EventType.Load, setViewModelValue); - }; - }, [rive, userParameters]); + if (!currentViewModel) { + setInstance(null); + instanceRef.current = null; + return; + } - return viewModelInstance; -} + let result: ViewModelInstance | null = null; + + if (currentParams) { + if ('name' in currentParams && currentParams.name != null) { + result = currentViewModel.instanceByName?.(currentParams.name) || null; + } else if ('useDefault' in currentParams && currentParams.useDefault) { + result = currentViewModel.defaultInstance?.() || null; + } else if ('useNew' in currentParams && currentParams.useNew) { + result = currentViewModel.instance?.() || null; + } + } else { + // Default to using default instance if no params provided + result = currentViewModel.defaultInstance?.() || null; + } + + instanceRef.current = result; + setInstance(result); + shouldUpdate.current = false; + }, [viewModel, name, useDefault, useNew]); + + // Automatically bind to Rive when requested, if not already bound + useEffect(() => { + if (!rive || !instance) return; + if (rive.viewModelInstance !== instance) { + rive.bindViewModelInstance(instance); + } + }, [rive, instance]); + + return instance; +} \ No newline at end of file diff --git a/src/hooks/useViewModelInstanceBoolean.ts b/src/hooks/useViewModelInstanceBoolean.ts index d707d82..e469b44 100644 --- a/src/hooks/useViewModelInstanceBoolean.ts +++ b/src/hooks/useViewModelInstanceBoolean.ts @@ -1,32 +1,38 @@ +import { useCallback } from 'react'; import { ViewModelInstanceBoolean } from '@rive-app/canvas'; import { UseViewModelInstanceBooleanParameters, UseViewModelInstanceBooleanResult } from '../types'; -import { useViewModelInstancePropertyValues } from './useViewModelInstancePropertyValues'; +import { useViewModelInstanceProperty } from './useViewModelInstanceProperty'; /** * 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 + * + * @param params - Parameters for interacting with a boolean ViewModel instance property + * @param params.path - The path to the boolean property + * @param params.viewModelInstance - The ViewModelInstance containing the boolean property to operate on + * @returns An object with the boolean value and a setter function */ export default function useViewModelInstanceBoolean( - path: string, - userParameters?: UseViewModelInstanceBooleanParameters + params: UseViewModelInstanceBooleanParameters ): UseViewModelInstanceBooleanResult { - return useViewModelInstancePropertyValues< - boolean, - UseViewModelInstanceBooleanParameters, - ViewModelInstanceBoolean, - { value: boolean; setValue: (value: boolean) => void } - >( + const { path, viewModelInstance } = params; + + const result = useViewModelInstanceProperty>( path, - userParameters, - false, - (instance, name) => instance.boolean(name), - (instance) => instance.value, - (_instance, value, setValue) => ({ - value, - setValue - }) + viewModelInstance, + { + getProperty: useCallback((vm, p) => vm.boolean(p), []), + getValue: useCallback((prop) => prop.value, []), + defaultValue: null, + buildPropertyOperations: useCallback((safePropertyAccess) => ({ + setValue: (newValue: boolean) => { + safePropertyAccess(prop => { prop.value = newValue; }); + } + }), []) + } ); -} \ No newline at end of file + + return { + value: result.value, + setValue: result.setValue + }; +} \ No newline at end of file diff --git a/src/hooks/useViewModelInstanceColor.ts b/src/hooks/useViewModelInstanceColor.ts index 00e033d..da1b25e 100644 --- a/src/hooks/useViewModelInstanceColor.ts +++ b/src/hooks/useViewModelInstanceColor.ts @@ -1,43 +1,58 @@ +import { useCallback } from 'react'; import { ViewModelInstanceColor } from '@rive-app/canvas'; -import { UseViewModelInstanceColorResult, UseViewModelInstanceColorParameters } from '../types'; -import { useViewModelInstancePropertyValues } from './useViewModelInstancePropertyValues'; +import { UseViewModelInstanceColorParameters, UseViewModelInstanceColorResult } from '../types'; +import { useViewModelInstanceProperty } from './useViewModelInstanceProperty'; /** - * 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 + * Hook for interacting with color properties of a ViewModelInstance. + * + * @param params - Parameters for interacting with color properties + * @param params.path - Path to the color property + * @param params.viewModelInstance - The ViewModelInstance containing the color property + * @returns An object with the color value and setter functions for different color formats */ export default function useViewModelInstanceColor( - path: string, - userParameters?: UseViewModelInstanceColorParameters + params: UseViewModelInstanceColorParameters ): UseViewModelInstanceColorResult { - return useViewModelInstancePropertyValues< - number, - UseViewModelInstanceColorParameters, - ViewModelInstanceColor, - { - value: number; - setValue: (value: number) => void; - setRgb: (r: number, g: number, b: number) => void; - setRgba: (r: number, g: number, b: number, a: number) => void; - setAlpha: (a: number) => void; - setOpacity: (o: number) => void; - } - >( + const { path, viewModelInstance } = params; + + const result = useViewModelInstanceProperty>( path, - userParameters, - 0, - (instance, name) => instance.color(name), - (instance) => instance.value, - (instance, value, setValue) => ({ - value, - setValue, - setRgb: (r, g, b) => instance?.rgb(r, g, b), - setRgba: (r, g, b, a) => instance?.rgba(r, g, b, a), - setAlpha: (a) => instance?.alpha(a), - setOpacity: (o) => instance?.opacity(o), - }) + viewModelInstance, + { + getProperty: useCallback((vm, p) => vm.color(p), []), + getValue: useCallback((prop) => prop.value, []), + defaultValue: null, + buildPropertyOperations: useCallback((safePropertyAccess) => ({ + setValue: (newValue: number) => { + safePropertyAccess(prop => { prop.value = newValue; }); + }, + + setRgb: (r: number, g: number, b: number) => { + safePropertyAccess(prop => { prop.rgb(r, g, b); }); + }, + + setRgba: (r: number, g: number, b: number, a: number) => { + safePropertyAccess(prop => { prop.rgba(r, g, b, a); }); + }, + + setAlpha: (a: number) => { + safePropertyAccess(prop => { prop.alpha(a); }); + }, + + setOpacity: (o: number) => { + safePropertyAccess(prop => { prop.opacity(o); }); + } + }), []) + } ); -} \ No newline at end of file + + return { + value: result.value, + setValue: result.setValue, + setRgb: result.setRgb, + setRgba: result.setRgba, + setAlpha: result.setAlpha, + setOpacity: result.setOpacity + }; +} \ No newline at end of file diff --git a/src/hooks/useViewModelInstanceEnum.ts b/src/hooks/useViewModelInstanceEnum.ts index ee53ada..196e210 100644 --- a/src/hooks/useViewModelInstanceEnum.ts +++ b/src/hooks/useViewModelInstanceEnum.ts @@ -1,37 +1,45 @@ +import { useCallback } from 'react'; import { ViewModelInstanceEnum } from '@rive-app/canvas'; import { UseViewModelInstanceEnumParameters, UseViewModelInstanceEnumResult } from '../types'; -import { useViewModelInstancePropertyValues } from './useViewModelInstancePropertyValues'; +import { useViewModelInstanceProperty } from './useViewModelInstanceProperty'; /** - * 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 + * Hook for interacting with enum properties of a ViewModelInstance. + * + * @param params - Parameters for interacting with enum properties + * @param params.path - Path to the enum property (e.g. "state" or "group/state") + * @param params.viewModelInstance - The ViewModelInstance containing the enum property + * @returns An object with the enum value, available values, and a setter function */ export default function useViewModelInstanceEnum( - path: string, - userParameters?: UseViewModelInstanceEnumParameters + params: UseViewModelInstanceEnumParameters ): UseViewModelInstanceEnumResult { - return useViewModelInstancePropertyValues< - string, - UseViewModelInstanceEnumParameters, + const { path, viewModelInstance } = params; + + const result = useViewModelInstanceProperty< ViewModelInstanceEnum, - { - value: string; - setValue: (value: string) => void; - values: string[]; - } + string, + Omit, + string[] >( path, - userParameters, - '', - (instance, name) => instance.enum(name), - (instance) => instance.value, - (instance, value, setValue) => ({ - value, - setValue, - values: instance?.values || [] - }) + viewModelInstance, + { + getProperty: useCallback((vm, p) => vm.enum(p), []), + getValue: useCallback((prop) => prop.value, []), + defaultValue: null, + getExtendedData: useCallback((prop) => prop.values, []), + buildPropertyOperations: useCallback((safePropertyAccess) => ({ + setValue: (newValue: string) => { + safePropertyAccess(prop => { prop.value = newValue; }); + } + }), []) + } ); -} \ No newline at end of file + + return { + value: result.value, + values: result.extendedData || [], + setValue: result.setValue + }; +} \ No newline at end of file diff --git a/src/hooks/useViewModelInstanceNumber.ts b/src/hooks/useViewModelInstanceNumber.ts index 6c101fb..70474c4 100644 --- a/src/hooks/useViewModelInstanceNumber.ts +++ b/src/hooks/useViewModelInstanceNumber.ts @@ -1,32 +1,38 @@ +import { useCallback } from 'react'; import { ViewModelInstanceNumber } from '@rive-app/canvas'; import { UseViewModelInstanceNumberParameters, UseViewModelInstanceNumberResult } from '../types'; -import { useViewModelInstancePropertyValues } from './useViewModelInstancePropertyValues'; +import { useViewModelInstanceProperty } from './useViewModelInstanceProperty'; /** - * 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 + * Hook for interacting with number properties of a ViewModelInstance. + * + * @param params - Parameters for interacting with number properties + * @param params.path - Path to the number property (e.g. "speed" or "group/speed") + * @param params.viewModelInstance - The ViewModelInstance containing the number property + * @returns An object with the number value and a setter function */ export default function useViewModelInstanceNumber( - path: string, - userParameters?: UseViewModelInstanceNumberParameters + params: 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 + const { path, viewModelInstance } = params; + + const result = useViewModelInstanceProperty>( + path, + viewModelInstance, + { + getProperty: useCallback((vm, p) => vm.number(p), []), + getValue: useCallback((prop) => prop.value, []), + defaultValue: null, + buildPropertyOperations: useCallback((safePropertyAccess) => ({ + setValue: (newValue: number) => { + safePropertyAccess(prop => { prop.value = newValue; }); + } + }), []) + } + ); + + return { + value: result.value, + setValue: result.setValue + }; +} \ No newline at end of file diff --git a/src/hooks/useViewModelInstanceProperty.ts b/src/hooks/useViewModelInstanceProperty.ts index 537e0ce..ee75ec3 100644 --- a/src/hooks/useViewModelInstanceProperty.ts +++ b/src/hooks/useViewModelInstanceProperty.ts @@ -1,99 +1,172 @@ -import { useState, useEffect, useRef } from 'react'; -import { - EventType, - ViewModelInstance, -} from '@rive-app/canvas'; -import { UseViewModelInstanceValueParameters } from '../types'; - -const defaultParams: UseViewModelInstanceValueParameters = { - viewModelInstance: null, -}; - -const equal = ( - path: string[], - params: UseViewModelInstanceValueParameters | null, - to: HookArguments | null -): boolean => { - if (!params || !to) { - return false; - } - if ( - params.rive !== to.parameters.rive || - params.viewModelInstance !== to.parameters.viewModelInstance || - path.join('') !== to.path.join('') - ) { - return false; - } - return true; -}; - -type HookArguments = { - path: string[], - parameters: UseViewModelInstanceValueParameters, -} +import { useState, useEffect, useCallback, useRef } from 'react'; +import { ViewModelInstance, ViewModelInstanceValue } from '@rive-app/canvas'; /** - * 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 + * Base hook for all ViewModelInstance property interactions. + * + * This hook handles the common tasks needed when working with Rive properties: + * 1. Safely accessing properties (even during hot-reload) + * 2. Keeping React state in sync with property changes + * 3. Providing type safety for all operations + * + * @param path - Property path in the ViewModelInstance + * @param viewModelInstance - The source ViewModelInstance + * @param options - Configuration for working with the property + * @returns Object with the value and operations */ -export default function useViewModelInstanceProperty( - path: string[] = [], - userParameters?: UseViewModelInstanceValueParameters -): ViewModelInstance | null { - const [viewModelInstance, setViewModelValue] = - useState(null); - const currentArguments = useRef( - null - ); +export function useViewModelInstanceProperty

( + path: string, + viewModelInstance: ViewModelInstance | null | undefined, + options: { + /** Function to get the property from a ViewModelInstance */ + getProperty: (vm: ViewModelInstance, path: string) => P | null; - useEffect(() => { - const parameters = { - ...defaultParams, - ...userParameters, - }; + /** Function to get the current value from the property */ + getValue: (prop: P) => V; - function getInstanceValue(): ViewModelInstance | null { - let viewModelInstance: ViewModelInstance | null = null; - if (userParameters?.viewModelInstance) { - viewModelInstance = userParameters?.viewModelInstance; - } else if (userParameters?.rive) { - viewModelInstance = userParameters?.rive?.viewModelInstance; - } - if (viewModelInstance) { - let index = 0; - while (index < path?.length) { - if (!viewModelInstance) { - return null; - } - viewModelInstance = viewModelInstance?.viewModel(path[index]); - index++; + /** Default value to use when property is unavailable */ + defaultValue: V | null; + + /** + * Function to create the property-specific operations + * + * @param safePropertyAccess - Helper function for safely working with properties. Handles stale property references. + * @returns Object with operations like setValue, trigger, etc. + */ + buildPropertyOperations: (safePropertyAccess: (callback: (prop: P) => void) => void) => R; + + /** Optional callback for property events (mainly used by triggers) */ + onPropertyEvent?: () => void; + + /** + * Optional function to extract additional property data (like enum values) + * Returns undefined if not provided + */ + getExtendedData?: (prop: P) => E; + } +): R & { value: V | null } & (E extends undefined ? {} : { extendedData: E | null }) { + const [property, setProperty] = useState

(null); + const [value, setValue] = useState(options.defaultValue); + const [extendedData, setExtendedData] = useState(null); + + const instanceRef = useRef(null); + const pathRef = useRef(path); + const optionsRef = useRef(options); + + useEffect(() => { + optionsRef.current = options; + }, [options]); + + const updateProperty = useCallback(() => { + const currentInstance = instanceRef.current; + const currentPath = pathRef.current; + const currentOptions = optionsRef.current; + + if (!currentInstance || !currentPath) { + setProperty(null); + setValue(currentOptions.defaultValue); + setExtendedData(null); + return () => { }; } - return viewModelInstance; - } - return null; + + const prop = currentOptions.getProperty(currentInstance, currentPath); + if (prop) { + setProperty(prop); + setValue(currentOptions.getValue(prop)); + + if (currentOptions.getExtendedData) { + setExtendedData(currentOptions.getExtendedData(prop)); + } + + const handleChange = () => { + setValue(currentOptions.getValue(prop)); + + if (currentOptions.getExtendedData) { + setExtendedData(currentOptions.getExtendedData(prop)); + } + + if (currentOptions.onPropertyEvent) { + currentOptions.onPropertyEvent(); + } + }; + + prop.on(handleChange); + + return () => { + prop.off(handleChange); + }; + } + + return () => { }; + }, []); + + useEffect(() => { + instanceRef.current = viewModelInstance; + pathRef.current = path; + + // subscribe & get our unsubscribe function + const cleanup = updateProperty(); + return cleanup; + }, [viewModelInstance, path, updateProperty]); + + /** + * Helper function that safely accesses properties, even during hot-reload. + * + * It tries to: + * 1. Use the existing property reference when possible + * 2. Fetch a fresh reference when needed + * 3. Apply the callback to whichever reference works + */ + const safePropertyAccess = useCallback( + (callback: (prop: P) => void) => { + // Try the fast path first + if (property && instanceRef.current === viewModelInstance) { + try { + callback(property); + + // Update extended data after callback if available + if (optionsRef.current.getExtendedData) { + setExtendedData(optionsRef.current.getExtendedData(property)); + } + return; + } catch (e) { + // Property might be stale - so we silently catch and try alternative + // This commonly happens during hot module replacement + } + } + + // Get a fresh property if needed + if (instanceRef.current) { + try { + const freshProp = optionsRef.current.getProperty(instanceRef.current, pathRef.current); + if (freshProp) { + setProperty(freshProp); + callback(freshProp); + + // Update extended data after callback if available + if (optionsRef.current.getExtendedData) { + setExtendedData(optionsRef.current.getExtendedData(freshProp)); + } + } + } catch (e) { + // Silently fail during hot-reload - this is expected behavior + // We don't want to crash the app during development + } + } + }, + [property, viewModelInstance] + ); + + const operations = options.buildPropertyOperations(safePropertyAccess); + + const result = { + value, + ...operations + } as R & { value: V | null } & (E extends undefined ? {} : { extendedData: E | null }); + + if (options.getExtendedData) { + (result as any).extendedData = extendedData; } - function searchViewModelInstance() { - const instanceValue = getInstanceValue(); - setViewModelValue(instanceValue); - currentArguments.current = { - parameters, - path, - }; - } - - if (!equal(path, parameters, currentArguments.current)) { - parameters.rive?.on(EventType.Load, searchViewModelInstance); - searchViewModelInstance(); - } - return () => { - parameters.rive?.off(EventType.Load, searchViewModelInstance); - }; - }, [path, userParameters]); - - return viewModelInstance; -} + return result; +} \ No newline at end of file diff --git a/src/hooks/useViewModelInstancePropertyValues.ts b/src/hooks/useViewModelInstancePropertyValues.ts deleted file mode 100644 index 478bc51..0000000 --- a/src/hooks/useViewModelInstancePropertyValues.ts +++ /dev/null @@ -1,110 +0,0 @@ -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 index cac0279..21c207a 100644 --- a/src/hooks/useViewModelInstanceString.ts +++ b/src/hooks/useViewModelInstanceString.ts @@ -1,32 +1,38 @@ +import { useCallback } from 'react'; import { ViewModelInstanceString } from '@rive-app/canvas'; import { UseViewModelInstanceStringParameters, UseViewModelInstanceStringResult } from '../types'; -import { useViewModelInstancePropertyValues } from './useViewModelInstancePropertyValues'; +import { useViewModelInstanceProperty } from './useViewModelInstanceProperty'; /** - * 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 + * Hook for interacting with string properties of a ViewModelInstance. + * + * @param params - Parameters for interacting with string properties + * @param params.path - Path to the property (e.g. "text" or "nested/text") + * @param params.viewModelInstance - The ViewModelInstance containing the string property + * @returns An object with the string value and a setter function */ export default function useViewModelInstanceString( - path: string, - userParameters?: UseViewModelInstanceStringParameters + params: UseViewModelInstanceStringParameters ): UseViewModelInstanceStringResult { - return useViewModelInstancePropertyValues< - string, - UseViewModelInstanceStringParameters, - ViewModelInstanceString, - { value: string; setValue: (value: string) => void } - >( + const { path, viewModelInstance } = params; + + const result = useViewModelInstanceProperty>( path, - userParameters, - '', - (instance, name) => instance.string(name), - (instance) => instance.value, - (_instance, value, setValue) => ({ - value, - setValue - }) + viewModelInstance, + { + getProperty: useCallback((vm, p) => vm.string(p), []), + getValue: useCallback((prop) => prop.value, []), + defaultValue: null, + buildPropertyOperations: useCallback((safePropertyAccess) => ({ + setValue: (newValue: string) => { + safePropertyAccess(prop => { prop.value = newValue; }); + } + }), []) + } ); -} \ No newline at end of file + + return { + value: result.value, + setValue: result.setValue + }; +} \ No newline at end of file diff --git a/src/hooks/useViewModelInstanceTrigger.ts b/src/hooks/useViewModelInstanceTrigger.ts index 7cdffc3..44aaa3b 100644 --- a/src/hooks/useViewModelInstanceTrigger.ts +++ b/src/hooks/useViewModelInstanceTrigger.ts @@ -1,58 +1,40 @@ -import { useEffect, useCallback } from 'react'; +import { useCallback } from 'react'; import { ViewModelInstanceTrigger } from '@rive-app/canvas'; import { UseViewModelInstanceTriggerParameters, UseViewModelInstanceTriggerResult } from '../types'; -import { useViewModelInstancePropertyValues } from './useViewModelInstancePropertyValues'; +import { useViewModelInstanceProperty } from './useViewModelInstanceProperty'; /** - * 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 + * Hook for interacting with trigger properties of a ViewModelInstance. + * + * @param params - Parameters for interacting with trigger properties + * @param params.path - Path to the trigger property (e.g. "onTap" or "group/onTap") + * @param params.viewModelInstance - The ViewModelInstance containing the trigger property + * @param params.onTrigger - Callback that runs when the trigger is fired + * @returns An object with a trigger function */ export default function useViewModelInstanceTrigger( - path: string, - userParameters?: UseViewModelInstanceTriggerParameters + params: UseViewModelInstanceTriggerParameters ): UseViewModelInstanceTriggerResult { - const result = useViewModelInstancePropertyValues< - void, - UseViewModelInstanceTriggerParameters, - ViewModelInstanceTrigger, - { - trigger: () => void; - instance: ViewModelInstanceTrigger | null; - } - >( + const { path, viewModelInstance, onTrigger } = params; + + const { trigger } = useViewModelInstanceProperty( path, - userParameters, - undefined, - (instance, name) => instance.trigger(name), - () => undefined, - (instance) => ({ - trigger: () => { - instance?.trigger(); - }, - instance - }) + viewModelInstance, + { + getProperty: useCallback((vm, p) => vm.trigger(p), []), + getValue: useCallback(() => undefined, []), // Triggers don't have a 'value' + defaultValue: null, + onPropertyEvent: onTrigger, + buildPropertyOperations: useCallback((safePropertyAccess) => ({ + trigger: () => { + + safePropertyAccess(prop => { + prop.trigger(); + }); + } + }), []) + } ); - const { instance } = result; - - useEffect(() => { - if (instance && userParameters?.onTrigger) { - instance.on(userParameters.onTrigger); - - return () => { - instance.off(userParameters.onTrigger); - }; - } - }, [instance, userParameters?.onTrigger]); - - const trigger = useCallback(() => { - instance?.trigger(); - }, [instance]); - - return { - trigger - }; -} \ No newline at end of file + return { trigger }; +} \ No newline at end of file diff --git a/src/hooks/useViewModelProperties.ts b/src/hooks/useViewModelProperties.ts deleted file mode 100644 index 71a59e1..0000000 --- a/src/hooks/useViewModelProperties.ts +++ /dev/null @@ -1,173 +0,0 @@ -import { useState, useEffect, useRef } from 'react'; -import { - EventType, - ViewModelInstance, - ViewModelInstanceValue, -} from '@rive-app/canvas'; -import { UseViewModelInstanceValueParameters } from '../types'; - -const defaultParams: UseViewModelInstanceValueParameters = { - viewModelInstance: null, -}; - -const equal = ( - properties: string[], - params: UseViewModelInstanceValueParameters | null, - to: HookArguments | null -): boolean => { - if (!params || !to) { - return false; - } - if (properties.length !== to.properties.length) { - return false; - } - for (let i = 0; i < properties.length; i += 1) { - if (properties[i] !== to.properties[i]) { - return false; - } - } - if ( - params.rive !== to.parameters.rive || - params.viewModelInstance !== to.parameters.viewModelInstance - ) { - return false; - } - return true; -}; - -type HookArguments = { - properties: string[]; - parameters: UseViewModelInstanceValueParameters; -}; - -type PropertyResult = { - query: string; - property: ViewModelInstanceValue | null; -}; - -/** - * Custom hook for fetching a view model instance value. - * - * @param properties - list of queries properties - * @param path - Path to reach the required property - * @param userParameters - Parameters to load view model properties - * @returns - */ -export default function useViewModelProperties( - properties: string[], - userParameters?: UseViewModelInstanceValueParameters -): PropertyResult[] { - const [result, setResult] = useState([]); - const currentArguments = useRef(null); - - useEffect(() => { - const parameters = { - ...defaultParams, - ...userParameters, - }; - - function getViewModelInstance() { - if (parameters.viewModelInstance) { - return parameters.viewModelInstance; - } else if (parameters.rive) { - return parameters.rive.viewModelInstance; - } - return null; - } - - function getPropertyViewModelInstance( - path: string - ): ViewModelInstance | null { - const viewModelInstance: ViewModelInstance | null = getViewModelInstance(); - if (path === '') { - return viewModelInstance; - } - return viewModelInstance?.viewModel(path) || null; - } - - function getProperty( - viewModelInstance: ViewModelInstance | null, - name: string - ): ViewModelInstanceValue | null { - if (viewModelInstance) { - const viewModelProperties = viewModelInstance.properties; - const propertyData = viewModelProperties.find((candidate) => { - if (candidate.name === name) { - return candidate; - } - }); - if (propertyData !== null) { - switch (propertyData!.type.toString()) { - case 'number': - return viewModelInstance.number(name); - case 'string': - return viewModelInstance.string(name); - case 'boolean': - return viewModelInstance.boolean(name); - case 'enumType': - return viewModelInstance.enum(name); - case 'color': - return viewModelInstance.color(name); - case 'trigger': - return viewModelInstance.trigger(name); - } - } - } - return null; - } - - function searchViewModelValues() { - const viewModelInstance = getViewModelInstance(); - if (!viewModelInstance) { - setResult([]); - } else { - const result: PropertyResult[] = []; - properties.forEach((propertyQuery) => { - if (propertyQuery === '') { - result.push({ - query: propertyQuery, - property: null, - }); - } else { - const propertyParts = propertyQuery.split('/'); - const propertyName = propertyParts.pop(); - const propertyViewModelPath = propertyParts.join('/'); - const propertyViewModelInstance = getPropertyViewModelInstance( - propertyViewModelPath - ); - const property = getProperty( - propertyViewModelInstance, - propertyName! - ); - if (property) { - result.push({ - query: propertyQuery, - property: property, - }); - } else { - result.push({ - query: propertyQuery, - property: null, - }); - } - } - }); - setResult(result); - } - currentArguments.current = { - properties: properties, - parameters: parameters, - }; - } - - if (!equal(properties, parameters, currentArguments.current)) { - parameters.rive?.on(EventType.Load, searchViewModelValues); - searchViewModelValues(); - } - return () => { - parameters.rive?.off(EventType.Load, searchViewModelValues); - }; - }, [name, userParameters]); - - return result; -} diff --git a/src/hooks/useViewModelProperty.ts b/src/hooks/useViewModelProperty.ts deleted file mode 100644 index ff02e3c..0000000 --- a/src/hooks/useViewModelProperty.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { useState, useEffect, useRef } from 'react'; -import { - EventType, - ViewModelInstance, -} from '@rive-app/canvas'; -import { - UseViewModelInstancePropertyType, - AcceptedVieModelType, -} from '../types'; -import useViewModelInstanceProperty from './useViewModelInstanceProperty'; -import { DataType } from '@rive-app/canvas/rive_advanced.mjs'; - -const defaultParams: UseViewModelInstancePropertyType = { - viewModelInstance: null, -}; - -const equal = ( - name: string, - params: U | 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: UseViewModelInstancePropertyType; - 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 useViewModelProperty< - T extends UseViewModelInstancePropertyType, - U extends AcceptedVieModelType ->(name: string, path: string[] = [], userParameters?: T): U | null { - const [viewModel, setViewModelValue] = useState | null>(null); - const currentArguments = useRef(null); - - const viewModelInstance = useViewModelInstanceProperty(path, userParameters); - - useEffect(() => { - const parameters: T = { - ...defaultParams, - ...(userParameters as T), - }; - - function getVMI(name: string): U | null { - const properties = viewModelInstance!.properties; - const propData = properties.find((value) => value.name === name); - if (propData === null) { - return null; - } - if (propData!.type === DataType.number) { - return viewModelInstance!.number(name) as U; - } else if (propData!.type === DataType.string) { - return viewModelInstance!.string(name) as U; - } else if (propData!.type === DataType.boolean) { - return viewModelInstance!.boolean(name) as U; - } else if (propData!.type === DataType.color) { - return viewModelInstance!.color(name) as U; - } - return null; - } - - function setInitialValue(property: U, params: T) { - if (params.initialValue !== undefined) { - property.value = params.initialValue; - } - } - - function searchViewModelValue() { - const instanceValue = getVMI(name); - if (instanceValue !== null) { - setInitialValue(instanceValue, parameters); - } - 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 as U; -} diff --git a/src/index.ts b/src/index.ts index 13f2c1e..5443893 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,31 +9,29 @@ 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'; export default Rive; export { - useRive, - useStateMachineInput, - useResizeCanvas, - useRiveFile, - useViewModel, - useViewModelInstance, - useViewModelInstanceNumber, - useViewModelInstanceString, - useViewModelInstanceBoolean, - useViewModelInstanceColor, - useViewModelInstanceEnum, - useViewModelInstanceTrigger, - useViewModelProperties, - RiveProps, + useRive, + useStateMachineInput, + useResizeCanvas, + useRiveFile, + useViewModel, + useViewModelInstance, + useViewModelInstanceNumber, + useViewModelInstanceString, + useViewModelInstanceBoolean, + useViewModelInstanceColor, + useViewModelInstanceEnum, + useViewModelInstanceTrigger, + RiveProps, }; export { - RiveState, - UseRiveParameters, - UseRiveFileParameters, - UseRiveOptions, + RiveState, + UseRiveParameters, + UseRiveFileParameters, + UseRiveOptions, } from './types'; export * from '@rive-app/canvas'; \ No newline at end of file diff --git a/src/types.ts b/src/types.ts index 70ebfd7..7a3fa44 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,13 +1,10 @@ import { Rive, + type ViewModel, RiveFile, RiveFileParameters, RiveParameters, - ViewModelInstance, - ViewModelInstanceBoolean, - ViewModelInstanceNumber, - ViewModelInstanceString, - ViewModelInstanceColor, + type ViewModelInstance, } from '@rive-app/canvas'; import { ComponentProps, RefCallback } from 'react'; @@ -63,78 +60,104 @@ export type RiveFileState = { status: FileStatus; }; -export type UseViewModelParameters = { - useDefault?: boolean; - name?: string; -}; +/** + * Parameters for retrieving a ViewModel from a Rive instance. + * + * @property rive - The Rive instance to retrieve the ViewModel from. + * @property name - When provided, specifies the name of the ViewModel to retrieve. + * @property useDefault - When true, uses the default ViewModel from the Rive instance. + */ +export type UseViewModelParameters = + | { rive: Rive | null; name: string; useDefault?: never } + | { rive: Rive | null; useDefault?: boolean; name?: never }; -export type UseViewModelInstanceParameters = { - useNew?: boolean; - useDefault?: boolean; - name?: string; -}; +/** + * Parameters for retrieving a ViewModelInstance. + * + * @property viewModel - The ViewModel to get an instance from. + * @property name - When provided, specifies the name of the instance to retrieve. + * @property useDefault - When true, uses the default instance from the ViewModel. + * @property useNew - When true, creates a new instance of the ViewModel. + * @property rive - When provided, automatically binds the instance to this Rive instance. + */ +export type UseViewModelInstanceParameters = + | { viewModel: ViewModel | null; name: string; rive?: Rive | null; useDefault?: never; useNew?: never } + | { viewModel: ViewModel | null; useDefault?: boolean; rive?: Rive | null; name?: never; useNew?: never } + | { viewModel: ViewModel | null; useNew?: boolean; rive?: Rive | null; name?: never; useDefault?: never }; export type UseViewModelInstanceValueParameters = { viewModelInstance?: ViewModelInstance | null; - rive?: Rive | null; }; -export type UseViewModelInstanceNumberParameters = - UseViewModelInstanceValueParameters & { - initialValue?: number; - }; +/** + * Parameters for interacting with number properties of a ViewModelInstance + * @property path - Path to the number property (e.g. "speed" or "group/speed") + * @property viewModelInstance - The ViewModelInstance containing the number property + */ +export type UseViewModelInstanceNumberParameters = { + path: string; + viewModelInstance?: ViewModelInstance | null; +}; -export type UseViewModelInstanceStringParameters = - UseViewModelInstanceValueParameters & { - initialValue?: string; - }; +/** + * Parameters for interacting with string properties of a ViewModelInstance + * @property path - Path to the string property (e.g. "text" or "nested/text") + * @property viewModelInstance - The ViewModelInstance containing the string property + */ +export type UseViewModelInstanceStringParameters = { + path: string; + viewModelInstance?: ViewModelInstance | null; +}; -export type UseViewModelInstanceBooleanParameters = - UseViewModelInstanceValueParameters & { - initialValue?: boolean; - }; +/** + * Parameters for interacting with boolean properties of a ViewModelInstance + * @property path - Path to the boolean property (e.g. "agreedToTerms" or "group/agreedToTerms") + * @property viewModelInstance - The ViewModelInstance containing the boolean property + */ +export type UseViewModelInstanceBooleanParameters = { + path: string; + viewModelInstance?: ViewModelInstance | null; +}; -export type UseViewModelInstanceColorParameters = - UseViewModelInstanceValueParameters & { - initialValue?: number; - }; +/** + * Parameters for interacting with color properties of a ViewModelInstance + * @property path - Path to the color property (e.g. "color" or "group/color") + * @property viewModelInstance - The ViewModelInstance containing the color property + */ +export type UseViewModelInstanceColorParameters = { + path: string; + viewModelInstance?: ViewModelInstance | null; +}; -export type UseViewModelInstanceEnumParameters = - UseViewModelInstanceValueParameters & { - initialValue?: string; - }; +/** + * Parameters for interacting with enum properties of a ViewModelInstance + * @property path - Path to the enum property (e.g. "state" or "group/state") + * @property viewModelInstance - The ViewModelInstance containing the enum property + */ +export type UseViewModelInstanceEnumParameters = { + path: string; + viewModelInstance?: ViewModelInstance | null; +}; -export type UseViewModelInstanceTriggerParameters = UseViewModelInstanceValueParameters & { - /** - * Callback that runs when the trigger is fired. - */ +/** + * Parameters for interacting with trigger properties of a ViewModelInstance + * @property path - Path to the trigger property (e.g. "onTap" or "group/onTap") + * @property viewModelInstance - The ViewModelInstance containing the trigger + * @property onTrigger - Callback that runs when the trigger fires + */ +export type UseViewModelInstanceTriggerParameters = { + path: string; + viewModelInstance?: ViewModelInstance | null; onTrigger?: () => void; }; -export type UseViewModelInstancePropertyType = - | UseViewModelInstanceNumberParameters - | UseViewModelInstanceStringParameters - | UseViewModelInstanceBooleanParameters - | UseViewModelInstanceColorParameters - | UseViewModelInstanceEnumParameters; - -export type AcceptedVieModelType = - T extends UseViewModelInstanceNumberParameters - ? 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; + value: number | null; /** * Set the value of the number. * @param value - The value to set the number to. @@ -145,7 +168,7 @@ export type UseViewModelInstanceStringResult = { /** * The current value of the string. */ - value: string; + value: string | null; /** * Set the value of the string. * @param value - The value to set the string to. @@ -156,7 +179,7 @@ export type UseViewModelInstanceBooleanResult = { /** * The current value of the boolean. */ - value: boolean; + value: boolean | null; /** * Set the value of the boolean. * @param value - The value to set the boolean to. @@ -168,7 +191,7 @@ export type UseViewModelInstanceColorResult = { /** * The current value of the color. */ - value: number; + value: number | null; /** * Set the value of the color. * @param value - The value to set the color to. @@ -203,7 +226,7 @@ export type UseViewModelInstanceEnumResult = { /** * The current value of the enum. */ - value: string; + value: string | null; /** * Set the value of the enum. * @param value - The value to set the enum to.