mirror of
https://github.com/rive-app/rive-react.git
synced 2026-03-13 08:22:30 +08:00
fix: hot reload crash
This commit is contained in:
@@ -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<ViewModel | null>(null);
|
||||
const currentParams = useRef<UseViewModelParameters | null>(null);
|
||||
export default function useViewModel(params: UseViewModelParameters): ViewModel | null {
|
||||
const { rive, name, useDefault = false } = params;
|
||||
const riveRef = useRef<Rive | null>(null);
|
||||
const paramsRef = useRef<UseViewModelParameters>(params);
|
||||
const [viewModel, setViewModel] = useState<ViewModel | null>(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;
|
||||
}
|
||||
@@ -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<ViewModelInstance | null>(null);
|
||||
const currentParams = useRef<UseViewModelInstanceParameters | null>(null);
|
||||
const { viewModel, name, useDefault = false, useNew = false, rive } = params;
|
||||
const [instance, setInstance] = useState<ViewModelInstance | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const parameters = userParameters || defaultParams;
|
||||
const viewModelRef = useRef<ViewModel | null>(viewModel);
|
||||
const paramsRef = useRef<UseViewModelInstanceParameters>(params);
|
||||
const instanceRef = useRef<ViewModelInstance | null>(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;
|
||||
}
|
||||
@@ -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<ViewModelInstanceBoolean, boolean, Omit<UseViewModelInstanceBooleanResult, 'value'>>(
|
||||
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; });
|
||||
}
|
||||
}), [])
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
value: result.value,
|
||||
setValue: result.setValue
|
||||
};
|
||||
}
|
||||
@@ -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<ViewModelInstanceColor, number, Omit<UseViewModelInstanceColorResult, 'value'>>(
|
||||
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); });
|
||||
}
|
||||
}), [])
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
value: result.value,
|
||||
setValue: result.setValue,
|
||||
setRgb: result.setRgb,
|
||||
setRgba: result.setRgba,
|
||||
setAlpha: result.setAlpha,
|
||||
setOpacity: result.setOpacity
|
||||
};
|
||||
}
|
||||
@@ -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<UseViewModelInstanceEnumResult, 'value' | 'values'>,
|
||||
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; });
|
||||
}
|
||||
}), [])
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
value: result.value,
|
||||
values: result.extendedData || [],
|
||||
setValue: result.setValue
|
||||
};
|
||||
}
|
||||
@@ -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
|
||||
})
|
||||
);
|
||||
}
|
||||
const { path, viewModelInstance } = params;
|
||||
|
||||
const result = useViewModelInstanceProperty<ViewModelInstanceNumber, number, Omit<UseViewModelInstanceNumberResult, 'value'>>(
|
||||
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
|
||||
};
|
||||
}
|
||||
@@ -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<ViewModelInstance | null>(null);
|
||||
const currentArguments = useRef<HookArguments | null>(
|
||||
null
|
||||
);
|
||||
export function useViewModelInstanceProperty<P extends ViewModelInstanceValue, V, R, E = undefined>(
|
||||
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<P | null>(null);
|
||||
const [value, setValue] = useState<V | null>(options.defaultValue);
|
||||
const [extendedData, setExtendedData] = useState<E | null>(null);
|
||||
|
||||
const instanceRef = useRef<ViewModelInstance | null | undefined>(null);
|
||||
const pathRef = useRef<string>(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;
|
||||
}
|
||||
@@ -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<string, any>
|
||||
>(
|
||||
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<V | null>(null);
|
||||
const [value, setValueState] = useState<T>(
|
||||
(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);
|
||||
}
|
||||
@@ -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<ViewModelInstanceString, string, Omit<UseViewModelInstanceStringResult, 'value'>>(
|
||||
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; });
|
||||
}
|
||||
}), [])
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
value: result.value,
|
||||
setValue: result.setValue
|
||||
};
|
||||
}
|
||||
@@ -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<ViewModelInstanceTrigger, undefined, UseViewModelInstanceTriggerResult>(
|
||||
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
|
||||
};
|
||||
}
|
||||
return { trigger };
|
||||
}
|
||||
@@ -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<PropertyResult[]>([]);
|
||||
const currentArguments = useRef<HookArguments | null>(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;
|
||||
}
|
||||
@@ -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 = <U extends UseViewModelInstancePropertyType>(
|
||||
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<T>
|
||||
>(name: string, path: string[] = [], userParameters?: T): U | null {
|
||||
const [viewModel, setViewModelValue] = useState<AcceptedVieModelType<T> | null>(null);
|
||||
const currentArguments = useRef<HookArguments | null>(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;
|
||||
}
|
||||
36
src/index.ts
36
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';
|
||||
145
src/types.ts
145
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> =
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user