mirror of
https://github.com/rive-app/rive-react.git
synced 2026-03-13 08:22:30 +08:00
Compare commits
34 Commits
v4.21.3
...
feature/in
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dab7a8d6f0 | ||
|
|
d2ccee1220 | ||
|
|
5bf53fc238 | ||
|
|
e49b9ae606 | ||
|
|
fc78d38a0b | ||
|
|
04397e658c | ||
|
|
52519885c6 | ||
|
|
2ad2df085b | ||
|
|
c9308db9dc | ||
|
|
082b9a6323 | ||
|
|
6aaee36ff0 | ||
|
|
b7581e5b25 | ||
|
|
4879b95245 | ||
|
|
4407dec4fc | ||
|
|
b73c3f58b2 | ||
|
|
cee7ca5a43 | ||
|
|
cf1d7c4c1c | ||
|
|
c414f143fd | ||
|
|
1f8afe4635 | ||
|
|
469e9073ce | ||
|
|
43290d1cfd | ||
|
|
37763e11c7 | ||
|
|
384b62c1b7 | ||
|
|
d9e61373b3 | ||
|
|
66f1ae021c | ||
|
|
1fc4fa44e2 | ||
|
|
3866cb9e06 | ||
|
|
bef03fc403 | ||
|
|
c4da27c7b6 | ||
|
|
c2ec32350d | ||
|
|
ead1d8e0e3 | ||
|
|
26cb0b5dec | ||
|
|
0e40840e6f | ||
|
|
480037c399 |
@@ -17,7 +17,8 @@
|
||||
},
|
||||
"scripts": {
|
||||
"storybook": "storybook dev -p 6006",
|
||||
"build-storybook": "storybook build"
|
||||
"build-storybook": "storybook build",
|
||||
"test-storybook": "test-storybook"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
@@ -47,8 +48,9 @@
|
||||
"@storybook/react": "^8.6.12",
|
||||
"@storybook/react-webpack5": "^8.6.12",
|
||||
"@storybook/test": "^8.6.12",
|
||||
"@storybook/test-runner": "^0.22.0",
|
||||
"eslint-plugin-storybook": "^0.12.0",
|
||||
"storybook": "^8.6.12",
|
||||
"webpack": "^5.99.6"
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
examples/public/person_databinding_test.riv
Normal file
BIN
examples/public/person_databinding_test.riv
Normal file
Binary file not shown.
BIN
examples/public/stocks.riv
Normal file
BIN
examples/public/stocks.riv
Normal file
Binary file not shown.
17
examples/src/components/DataBinding.stories.ts
Normal file
17
examples/src/components/DataBinding.stories.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import DataBinding from './DataBinding';
|
||||
|
||||
const meta = {
|
||||
title: 'DataBinding',
|
||||
component: DataBinding,
|
||||
parameters: {
|
||||
layout: 'fullscreen',
|
||||
},
|
||||
args: {},
|
||||
} satisfies Meta<typeof DataBinding>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {};
|
||||
147
examples/src/components/DataBinding.tsx
Normal file
147
examples/src/components/DataBinding.tsx
Normal file
@@ -0,0 +1,147 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import {
|
||||
useRive,
|
||||
useViewModel,
|
||||
useViewModelInstance,
|
||||
useViewModelInstanceColor,
|
||||
useViewModelInstanceNumber,
|
||||
useViewModelInstanceString,
|
||||
useViewModelInstanceEnum,
|
||||
useViewModelInstanceTrigger,
|
||||
} from '@rive-app/react-webgl2';
|
||||
|
||||
const randomValue = () => Math.random() * 200 - 100;
|
||||
|
||||
const DataBinding = () => {
|
||||
const { rive, RiveComponent } = useRive({
|
||||
src: 'stocks.riv',
|
||||
artboard: 'Main',
|
||||
stateMachines: 'State Machine 1',
|
||||
autoplay: true,
|
||||
autoBind: false,
|
||||
});
|
||||
|
||||
// Get the default instance of the view model
|
||||
const viewModel = useViewModel(rive, { name: 'Dashboard' });
|
||||
const viewModelInstance = useViewModelInstance(viewModel, { rive });
|
||||
|
||||
// Get the view model instance properties
|
||||
|
||||
const { setValue: setTitle } = useViewModelInstanceString(
|
||||
'title',
|
||||
viewModelInstance
|
||||
);
|
||||
|
||||
const { setValue: setLogoShape } = useViewModelInstanceEnum(
|
||||
'logoShape',
|
||||
viewModelInstance
|
||||
);
|
||||
|
||||
const { setValue: setRootColor } = useViewModelInstanceColor(
|
||||
'rootColor',
|
||||
viewModelInstance
|
||||
);
|
||||
|
||||
const { trigger: triggerSpinLogo } = useViewModelInstanceTrigger(
|
||||
'triggerSpinLogo',
|
||||
viewModelInstance
|
||||
);
|
||||
|
||||
useViewModelInstanceTrigger('triggerButton', viewModelInstance, {
|
||||
onTrigger: () => console.log('Button Triggered!'),
|
||||
});
|
||||
|
||||
// Apple Values
|
||||
const { setValue: setAppleName } = useViewModelInstanceString(
|
||||
'apple/name',
|
||||
viewModelInstance
|
||||
);
|
||||
const { setValue: setAppleStockChange } = useViewModelInstanceNumber(
|
||||
'apple/stockChange',
|
||||
viewModelInstance
|
||||
);
|
||||
const { value: appleColor } = useViewModelInstanceColor(
|
||||
'apple/currentColor',
|
||||
viewModelInstance
|
||||
);
|
||||
// Apple Values
|
||||
const { setValue: setMicrosoftName } = useViewModelInstanceString(
|
||||
'microsoft/name',
|
||||
viewModelInstance
|
||||
);
|
||||
const { setValue: setMicrosoftStockChange } = useViewModelInstanceNumber(
|
||||
'microsoft/stockChange',
|
||||
viewModelInstance
|
||||
);
|
||||
// Tesla Values
|
||||
const { setValue: setTeslaName } = useViewModelInstanceString(
|
||||
'tesla/name',
|
||||
viewModelInstance
|
||||
);
|
||||
const { setValue: setTeslaStockChange } = useViewModelInstanceNumber(
|
||||
'tesla/stockChange',
|
||||
viewModelInstance
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
// Set initial values for the view model
|
||||
if (
|
||||
setTitle &&
|
||||
setLogoShape &&
|
||||
setRootColor &&
|
||||
setAppleName &&
|
||||
setMicrosoftName &&
|
||||
setTeslaName
|
||||
) {
|
||||
setTitle('Rive Stocks Dashboard');
|
||||
setLogoShape('triangle');
|
||||
setRootColor(parseInt('ffc0ffee', 16));
|
||||
setAppleName('AAPL');
|
||||
setMicrosoftName('MSFT');
|
||||
setTeslaName('TSLA');
|
||||
}
|
||||
|
||||
// randomly generate stock values every 2 seconds
|
||||
const interval = setInterval(() => {
|
||||
const appleValue = randomValue();
|
||||
const microsoftValue = randomValue();
|
||||
const teslaValue = randomValue();
|
||||
|
||||
setAppleStockChange(appleValue);
|
||||
setMicrosoftStockChange(microsoftValue);
|
||||
setTeslaStockChange(teslaValue);
|
||||
|
||||
// If all the stock values are either all positive or all negative, spin the logo
|
||||
if (
|
||||
(appleValue > 0 && microsoftValue > 0 && teslaValue > 0) ||
|
||||
(appleValue < 0 && microsoftValue < 0 && teslaValue < 0)
|
||||
) {
|
||||
triggerSpinLogo();
|
||||
}
|
||||
}, 2000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [
|
||||
setTitle,
|
||||
setLogoShape,
|
||||
setRootColor,
|
||||
setAppleName,
|
||||
setMicrosoftName,
|
||||
setTeslaName,
|
||||
setAppleStockChange,
|
||||
setMicrosoftStockChange,
|
||||
setTeslaStockChange,
|
||||
triggerSpinLogo,
|
||||
]);
|
||||
|
||||
// listen for changes to the AAPL color and log them
|
||||
useEffect(() => {
|
||||
if (appleColor) {
|
||||
console.log('Apple color changed:', appleColor);
|
||||
}
|
||||
}, [appleColor]);
|
||||
|
||||
return <RiveComponent />;
|
||||
};
|
||||
|
||||
export default DataBinding;
|
||||
348
examples/src/components/DataBindingTests.stories.tsx
Normal file
348
examples/src/components/DataBindingTests.stories.tsx
Normal file
@@ -0,0 +1,348 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { within, expect, waitFor, userEvent } from '@storybook/test';
|
||||
|
||||
import { StringPropertyTest, NumberPropertyTest, BooleanPropertyTest, ColorPropertyTest, EnumPropertyTest, NestedViewModelTest, TriggerPropertyTest, PersonForm, PersonInstances } from './DataBindingTests';
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'Tests/DataBinding',
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
|
||||
export const StringPropertyStory: StoryObj = {
|
||||
name: 'String Property',
|
||||
render: () => <StringPropertyTest src="person_databinding_test.riv" />,
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
// Wait for the Rive file to load
|
||||
await waitFor(() => {
|
||||
expect(canvas.getByTestId('name-value')).toBeTruthy();
|
||||
}, { timeout: 3000 });
|
||||
|
||||
const nameInput = canvas.getByTestId<HTMLInputElement>('name-input');
|
||||
await userEvent.clear(nameInput);
|
||||
|
||||
// Wait for the input to be cleared
|
||||
await waitFor(() => {
|
||||
expect(nameInput.value).toBe('');
|
||||
}, { timeout: 1000 });
|
||||
|
||||
await userEvent.click(nameInput);
|
||||
await userEvent.paste('Test User');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(nameInput.value).toBe('Test User');
|
||||
}, { timeout: 2000 });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(canvas.getByTestId('name-value').textContent).toBe('Test User');
|
||||
}, { timeout: 2000 });
|
||||
}
|
||||
};
|
||||
|
||||
export const NumberPropertyStory: StoryObj = {
|
||||
name: 'Number Property',
|
||||
render: () => <NumberPropertyTest src="person_databinding_test.riv" />,
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
// Wait for the Rive file to load
|
||||
await waitFor(() => {
|
||||
expect(canvas.getByTestId('age-value')).toBeTruthy();
|
||||
}, { timeout: 2000 });
|
||||
|
||||
const ageInput = canvas.getByTestId<HTMLInputElement>('age-input');
|
||||
|
||||
const currentValue = ageInput.value;
|
||||
expect(currentValue).toBe('23');
|
||||
|
||||
await userEvent.click(ageInput);
|
||||
await userEvent.clear(ageInput);
|
||||
await waitFor(() => {
|
||||
expect(ageInput.value).toBe('0'); // This is a hack to wait for the input to be cleared
|
||||
}, { timeout: 1000 });
|
||||
|
||||
await userEvent.paste('42');
|
||||
|
||||
|
||||
await waitFor(() => {
|
||||
expect(canvas.getByTestId('age-value').textContent).toBe('42');
|
||||
}, { timeout: 2000 });
|
||||
}
|
||||
};
|
||||
|
||||
export const BooleanPropertyStory: StoryObj = {
|
||||
name: 'Boolean Property',
|
||||
render: () => <BooleanPropertyTest src="person_databinding_test.riv" />,
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
// Wait for the Rive file to load
|
||||
|
||||
await waitFor(() => {
|
||||
expect(canvas.getByTestId('terms-value')).toBeTruthy();
|
||||
}, { timeout: 2000 });
|
||||
|
||||
const termsCheckbox = canvas.getByTestId<HTMLInputElement>('terms-checkbox');
|
||||
|
||||
expect(termsCheckbox.checked).toBe(false);
|
||||
|
||||
expect(canvas.getByTestId('terms-value').textContent).toBe('false');
|
||||
|
||||
await userEvent.click(termsCheckbox);
|
||||
|
||||
// Verify terms update
|
||||
await waitFor(() => {
|
||||
expect(canvas.getByTestId('terms-value').textContent).toBe('true');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const ColorPropertyStory: StoryObj = {
|
||||
name: 'Color Property',
|
||||
render: () => <ColorPropertyTest src="person_databinding_test.riv" />,
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
// Wait for the Rive file to load and the component to render
|
||||
await waitFor(() => {
|
||||
expect(canvas.getByTestId('color-value')).toBeTruthy();
|
||||
expect(canvas.getByTestId('set-color-red')).toBeTruthy();
|
||||
expect(canvas.getByTestId('set-color-blue')).toBeTruthy();
|
||||
}, { timeout: 5000 });
|
||||
|
||||
const numberValueDiv = canvas.getByTestId('number-value');
|
||||
const hexValueDiv = canvas.getByTestId('hex-value');
|
||||
|
||||
// Verify initial state is red
|
||||
await waitFor(() => {
|
||||
expect(hexValueDiv.textContent).toContain('Hex value: #ce2323');
|
||||
expect(numberValueDiv.textContent).toContain('Number value: -3267805');
|
||||
});
|
||||
|
||||
|
||||
// Change color to Blue ---
|
||||
const blueButton = canvas.getByTestId('set-color-blue');
|
||||
await userEvent.click(blueButton);
|
||||
|
||||
// Verify Blue State
|
||||
await waitFor(() => {
|
||||
expect(numberValueDiv.textContent).toContain('Number value: -16776961');
|
||||
expect(hexValueDiv.textContent).toContain('Hex value: #0000ff');
|
||||
});
|
||||
|
||||
|
||||
// Change color back to Red ---
|
||||
const redButton = canvas.getByTestId('set-color-red');
|
||||
await userEvent.click(redButton);
|
||||
|
||||
// Verify Red State
|
||||
await waitFor(() => {
|
||||
expect(numberValueDiv.textContent).toContain('Number value: -65536');
|
||||
expect(hexValueDiv.textContent).toContain('Hex value: #ff0000');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const EnumPropertyStory: StoryObj = {
|
||||
name: 'Enum Property',
|
||||
render: () => <EnumPropertyTest src="person_databinding_test.riv" />,
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
// Wait for the Rive file to load
|
||||
await waitFor(() => {
|
||||
expect(canvas.getByTestId('country-value')).toBeTruthy();
|
||||
});
|
||||
|
||||
// Wait for options to be loaded
|
||||
await waitFor(() => {
|
||||
const countrySelect = canvas.getByTestId<HTMLSelectElement>('country-select');
|
||||
return countrySelect.options.length > 0;
|
||||
});
|
||||
|
||||
const countrySelect = canvas.getByTestId<HTMLSelectElement>('country-select');
|
||||
|
||||
// Verify that the dropdown contains usa, japan, and canada
|
||||
const optionValues = Array.from(countrySelect.options).map(option => option.value);
|
||||
expect(optionValues).toContain('usa');
|
||||
expect(optionValues).toContain('japan');
|
||||
expect(optionValues).toContain('canada');
|
||||
|
||||
const currentValue = countrySelect.value;
|
||||
|
||||
expect(currentValue).toBe('usa');
|
||||
|
||||
let optionToSelect = 'japan';
|
||||
|
||||
await userEvent.selectOptions(countrySelect, optionToSelect);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(canvas.getByTestId('country-value').textContent).toBe(optionToSelect);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const NestedViewModelStory: StoryObj = {
|
||||
name: 'Nested ViewModel Property',
|
||||
render: () => <NestedViewModelTest src="person_databinding_test.riv" />,
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
|
||||
// Wait for the Rive file to load
|
||||
await waitFor(() => {
|
||||
expect(canvas.getByTestId('drink-type-value')).toBeTruthy();
|
||||
});
|
||||
|
||||
// Wait for options to be loaded
|
||||
await waitFor(() => {
|
||||
const drinkTypeSelect = canvas.getByTestId<HTMLSelectElement>('drink-type-select');
|
||||
return drinkTypeSelect.options.length > 0;
|
||||
}, { timeout: 2000 });
|
||||
|
||||
const drinkTypeSelect = canvas.getByTestId<HTMLSelectElement>('drink-type-select');
|
||||
const optionValues = Array.from(drinkTypeSelect.options).map(option => option.value);
|
||||
expect(optionValues).toContain('Coffee');
|
||||
expect(optionValues).toContain('Tea');
|
||||
|
||||
|
||||
expect(drinkTypeSelect.value).toBe('Tea');
|
||||
|
||||
let optionToSelect = 'Coffee';
|
||||
|
||||
await userEvent.selectOptions(drinkTypeSelect, optionToSelect);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(canvas.getByTestId('drink-type-value').textContent).toBe(optionToSelect);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const TriggerPropertyStory: StoryObj = {
|
||||
name: 'Trigger Property',
|
||||
render: () => <TriggerPropertyTest src="person_databinding_test.riv" />,
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
// Wait for the Rive file to load
|
||||
await waitFor(() => {
|
||||
expect(canvas.getByTestId('submit-button')).toBeTruthy();
|
||||
}, { timeout: 2000 });
|
||||
|
||||
expect(canvas.getByTestId('callback-triggered').textContent).toContain('none');
|
||||
|
||||
// Trigger submit action
|
||||
await userEvent.click(canvas.getByTestId('submit-button'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(canvas.getByTestId('callback-triggered').textContent).toContain('submit-callback');
|
||||
});
|
||||
|
||||
await userEvent.click(canvas.getByTestId('reset-button'));
|
||||
|
||||
// Verify onTrigger callback works for reset
|
||||
await waitFor(() => {
|
||||
expect(canvas.getByTestId('callback-triggered').textContent).toContain('reset-callback');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const PersonInstancesStory: StoryObj = {
|
||||
name: 'Person Instances',
|
||||
render: () => <PersonInstances src="person_databinding_test.riv" />,
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
// Wait for the Rive file to load
|
||||
await waitFor(() => {
|
||||
expect(canvas.getByTestId('instance-name')).toBeTruthy();
|
||||
expect(canvas.getByTestId('select-jane')).toBeTruthy();
|
||||
expect(canvas.getByTestId('select-default')).toBeTruthy();
|
||||
}, { timeout: 2000 });
|
||||
|
||||
// Initially should show Steve
|
||||
expect(canvas.getByTestId('instance-name').textContent).toContain('Steve');
|
||||
|
||||
// Switch to Jane
|
||||
const janeButton = canvas.getByTestId('select-jane');
|
||||
await userEvent.click(janeButton);
|
||||
|
||||
// Verify instance changed to Jane
|
||||
await waitFor(() => {
|
||||
expect(canvas.getByTestId('instance-name').textContent).toContain('Jane');
|
||||
});
|
||||
|
||||
// Switch to Default instance
|
||||
const defaultButton = canvas.getByTestId('select-default');
|
||||
await userEvent.click(defaultButton);
|
||||
|
||||
// Verify instance changed to Default
|
||||
await waitFor(() => {
|
||||
expect(canvas.getByTestId('instance-name').textContent).toContain('Default');
|
||||
});
|
||||
|
||||
// Switch back to Steve
|
||||
const steveButton = canvas.getByTestId('select-steve');
|
||||
await userEvent.click(steveButton);
|
||||
|
||||
// Verify instance changed back to Steve
|
||||
await waitFor(() => {
|
||||
expect(canvas.getByTestId('instance-name').textContent).toContain('Steve');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// A configurable form story, so we can test all the properties at once
|
||||
export const PersonFormStory: StoryObj = {
|
||||
name: 'Complete Person Form',
|
||||
render: () => <PersonForm src="person_databinding_test.riv" />,
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
|
||||
// Wait for the Rive file to load
|
||||
await waitFor(() => {
|
||||
expect(canvas.getByTestId('name-value')).toBeTruthy();
|
||||
}, { timeout: 2000 });
|
||||
|
||||
// Update name
|
||||
const nameInput = canvas.getByTestId('name-input');
|
||||
await userEvent.clear(nameInput);
|
||||
await userEvent.type(nameInput, 'Test User');
|
||||
|
||||
// Update age
|
||||
const ageInput = canvas.getByTestId('age-input');
|
||||
await userEvent.clear(ageInput);
|
||||
await userEvent.type(ageInput, '42');
|
||||
|
||||
// Toggle terms agreement
|
||||
const termsCheckbox = canvas.getByTestId('terms-checkbox');
|
||||
await userEvent.click(termsCheckbox);
|
||||
|
||||
// Change color
|
||||
const colorButton = canvas.getByTestId('set-color-red');
|
||||
await userEvent.click(colorButton);
|
||||
|
||||
// Change country
|
||||
const countrySelect = canvas.getByTestId<HTMLSelectElement>('country-select');
|
||||
await userEvent.selectOptions(countrySelect, 'japan');
|
||||
|
||||
// Change drink type
|
||||
const drinkTypeSelect = canvas.getByTestId<HTMLSelectElement>('drink-type-select');
|
||||
await userEvent.selectOptions(drinkTypeSelect, 'Coffee');
|
||||
|
||||
// Submit the form
|
||||
const submitButton = canvas.getByTestId('submit-button');
|
||||
await userEvent.click(submitButton);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
524
examples/src/components/DataBindingTests.tsx
Normal file
524
examples/src/components/DataBindingTests.tsx
Normal file
@@ -0,0 +1,524 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import Rive, {
|
||||
useRive,
|
||||
useViewModel,
|
||||
useViewModelInstance,
|
||||
useViewModelInstanceBoolean,
|
||||
useViewModelInstanceString,
|
||||
useViewModelInstanceNumber,
|
||||
useViewModelInstanceEnum,
|
||||
useViewModelInstanceColor,
|
||||
useViewModelInstanceTrigger
|
||||
} from '@rive-app/react-webgl2';
|
||||
|
||||
|
||||
export const StringPropertyTest = ({ src }: { src: string }) => {
|
||||
const { rive, RiveComponent } = useRive({
|
||||
src,
|
||||
autoplay: true,
|
||||
artboard: "Artboard",
|
||||
autoBind: true,
|
||||
stateMachines: "State Machine 1",
|
||||
});
|
||||
|
||||
const { value: name, setValue: setName } = useViewModelInstanceString('name', rive?.viewModelInstance);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<RiveComponent style={{ width: '400px', height: '400px' }} />
|
||||
{(rive === null) ? <div data-testid="loading-text">Loading…</div> : (
|
||||
<div>
|
||||
<label>
|
||||
Name:
|
||||
<input
|
||||
data-testid="name-input"
|
||||
type="text"
|
||||
value={name || ''}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
autoFocus={false}
|
||||
/>
|
||||
</label>
|
||||
<div data-testid="name-value">{name}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const NumberPropertyTest = ({ src }: { src: string }) => {
|
||||
const { rive, RiveComponent } = useRive({
|
||||
src,
|
||||
autoplay: true,
|
||||
artboard: "Artboard",
|
||||
autoBind: true,
|
||||
stateMachines: "State Machine 1",
|
||||
});
|
||||
|
||||
const { value: age, setValue: setAge } = useViewModelInstanceNumber('age', rive?.viewModelInstance);
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
<RiveComponent style={{ width: '400px', height: '400px' }} />
|
||||
{(rive === null) ? <div data-testid="loading-text">Loading…</div> : (
|
||||
<div>
|
||||
<label>
|
||||
Age:
|
||||
<input
|
||||
data-testid="age-input"
|
||||
type="number"
|
||||
value={age ?? 0}
|
||||
onChange={(e) => setAge(Number(e.target.value))}
|
||||
autoFocus={false}
|
||||
/>
|
||||
</label>
|
||||
<div data-testid="age-value">{age}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const BooleanPropertyTest = ({ src }: { src: string }) => {
|
||||
const { rive, RiveComponent } = useRive({
|
||||
src,
|
||||
autoplay: true,
|
||||
artboard: "Artboard",
|
||||
autoBind: true,
|
||||
stateMachines: "State Machine 1",
|
||||
});
|
||||
|
||||
const { value: agreedToTerms, setValue: setAgreedToTerms } = useViewModelInstanceBoolean('agreedToTerms', rive?.viewModelInstance);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<RiveComponent style={{ width: '400px', height: '400px' }} />
|
||||
{(rive === null) ? <div data-testid="loading-text">Loading…</div> : (
|
||||
<div>
|
||||
<label>
|
||||
<input
|
||||
data-testid="terms-checkbox"
|
||||
type="checkbox"
|
||||
checked={agreedToTerms ?? false}
|
||||
onChange={(e) => setAgreedToTerms(e.target.checked)}
|
||||
/>
|
||||
Agree to Terms
|
||||
</label>
|
||||
<div data-testid="terms-value">{agreedToTerms ? 'true' : 'false'}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const colorNumberToHexString = (colorNum: number | null) => {
|
||||
if (colorNum === null) {
|
||||
return 'N/A';
|
||||
}
|
||||
const unsignedInt = colorNum >>> 0;
|
||||
const r = (unsignedInt >> 16) & 0xff;
|
||||
const g = (unsignedInt >> 8) & 0xff;
|
||||
const b = unsignedInt & 0xff;
|
||||
|
||||
const toHex = (c: number) => c.toString(16).padStart(2, '0');
|
||||
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
||||
};
|
||||
|
||||
export const ColorPropertyTest = ({ src }: { src: string }) => {
|
||||
const { rive, RiveComponent } = useRive({
|
||||
src,
|
||||
autoplay: true,
|
||||
artboard: "Artboard",
|
||||
autoBind: true,
|
||||
stateMachines: "State Machine 1",
|
||||
});
|
||||
|
||||
|
||||
const { value: colorNum, setValue: setColor, setRgb } = useViewModelInstanceColor('favColor', rive?.viewModelInstance);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<RiveComponent style={{ width: '400px', height: '400px' }} />
|
||||
{(rive === null) ? <div data-testid="loading-text">Loading…</div> : (
|
||||
<div>
|
||||
<label>
|
||||
Favorite Color:
|
||||
<div data-testid="color-value" style={{
|
||||
backgroundColor: typeof colorNum === 'string' ? colorNum : colorNumberToHexString(colorNum),
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
display: 'inline-block',
|
||||
marginLeft: '10px'
|
||||
}}></div>
|
||||
<div data-testid="number-value">
|
||||
Number value: {typeof colorNum === 'number' ? colorNum : 'N/A'}
|
||||
</div>
|
||||
<div data-testid="hex-value">
|
||||
Hex value: {colorNumberToHexString(colorNum)}
|
||||
</div>
|
||||
</label>
|
||||
<button
|
||||
data-testid="set-color-red"
|
||||
type="button"
|
||||
onClick={() => setRgb(255, 0, 0)}
|
||||
>
|
||||
Red
|
||||
</button>
|
||||
<button
|
||||
data-testid="set-color-blue"
|
||||
type="button"
|
||||
onClick={() => setRgb(0, 0, 255)}
|
||||
>
|
||||
Blue
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const EnumPropertyTest = ({ src }: { src: string }) => {
|
||||
const { rive, RiveComponent } = useRive({
|
||||
src,
|
||||
autoplay: true,
|
||||
artboard: "Artboard",
|
||||
autoBind: true,
|
||||
stateMachines: "State Machine 1",
|
||||
});
|
||||
|
||||
const { value: country, setValue: setCountry, values: countries } = useViewModelInstanceEnum('country', rive?.viewModelInstance);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<RiveComponent style={{ width: '400px', height: '400px' }} />
|
||||
{(rive === null) ? <div data-testid="loading-text">Loading…</div> : (
|
||||
<div>
|
||||
<label>
|
||||
Country:
|
||||
<select
|
||||
data-testid="country-select"
|
||||
value={country || ''}
|
||||
onChange={(e) => setCountry(e.target.value)}
|
||||
>
|
||||
{countries.map(c => (
|
||||
<option key={c} value={c}>{c}</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<div data-testid="country-value">{country}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const NestedViewModelTest = ({ src }: { src: string }) => {
|
||||
const { rive, RiveComponent } = useRive({
|
||||
src,
|
||||
autoplay: true,
|
||||
artboard: "Artboard",
|
||||
autoBind: true,
|
||||
stateMachines: "State Machine 1",
|
||||
});
|
||||
|
||||
const { value: drinkType, setValue: setDrinkType, values: drinkTypes } = useViewModelInstanceEnum('favDrink/type', rive?.viewModelInstance);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<RiveComponent style={{ width: '400px', height: '400px' }} />
|
||||
{(rive === null) ? <div data-testid="loading-text">Loading…</div> : (
|
||||
<div>
|
||||
<label>
|
||||
Favorite Drink Type:
|
||||
<select
|
||||
data-testid="drink-type-select"
|
||||
value={drinkType || ''}
|
||||
onChange={(e) => setDrinkType(e.target.value)}
|
||||
>
|
||||
{drinkTypes.map(dt => (
|
||||
<option key={dt} value={dt}>{dt}</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<div data-testid="drink-type-value">{drinkType}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const TriggerPropertyTest = ({ src }: { src: string }) => {
|
||||
const [callbackTriggered, setCallbackTriggered] = useState('');
|
||||
|
||||
const { rive, RiveComponent } = useRive({
|
||||
src,
|
||||
autoplay: true,
|
||||
autoBind: true,
|
||||
artboard: "Artboard",
|
||||
stateMachines: "State Machine 1",
|
||||
});
|
||||
|
||||
|
||||
|
||||
const { trigger: onFormSubmit } = useViewModelInstanceTrigger('onFormSubmit', rive?.viewModelInstance,
|
||||
{
|
||||
onTrigger: () => {
|
||||
setCallbackTriggered('submit-callback');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const { trigger: onFormReset } = useViewModelInstanceTrigger('onFormReset', rive?.viewModelInstance,
|
||||
{
|
||||
onTrigger: () => {
|
||||
setCallbackTriggered('reset-callback');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const handleSubmit = () => {
|
||||
onFormSubmit();
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
onFormReset();
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<RiveComponent style={{ width: '400px', height: '400px' }} />
|
||||
{(rive === null) ? <div data-testid="loading-text">Loading…</div> : (
|
||||
<div>
|
||||
<button data-testid="submit-button" type="button" onClick={handleSubmit}>Submit</button>
|
||||
<button data-testid="reset-button" type="button" onClick={handleReset}>Reset</button>
|
||||
|
||||
<div data-testid="callback-triggered">
|
||||
Last callback triggered: {callbackTriggered || 'none'}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const PersonForm = ({ src }: { src: string }) => {
|
||||
const { rive, RiveComponent } = useRive({
|
||||
src,
|
||||
autoplay: true,
|
||||
autoBind: true,
|
||||
artboard: "Artboard",
|
||||
stateMachines: "State Machine 1",
|
||||
});
|
||||
|
||||
const { value: name, setValue: setName } = useViewModelInstanceString('name', rive?.viewModelInstance);
|
||||
const { value: age, setValue: setAge } = useViewModelInstanceNumber('age', rive?.viewModelInstance);
|
||||
const { value: agreedToTerms, setValue: setAgreedToTerms } = useViewModelInstanceBoolean('agreedToTerms', rive?.viewModelInstance);
|
||||
const { value: colorNum, setRgb } = useViewModelInstanceColor('favColor', rive?.viewModelInstance);
|
||||
const { value: country, setValue: setCountry, values: countries } = useViewModelInstanceEnum('country', rive?.viewModelInstance);
|
||||
const { trigger: onFormSubmit } = useViewModelInstanceTrigger('onFormSubmit', rive?.viewModelInstance);
|
||||
const { trigger: onFormReset } = useViewModelInstanceTrigger('onFormReset', rive?.viewModelInstance);
|
||||
|
||||
|
||||
// Drink properties (nested viewmodel)
|
||||
const { value: drinkType, setValue: setDrinkType, values: drinkTypes } = useViewModelInstanceEnum('favDrink/type', rive?.viewModelInstance);
|
||||
|
||||
const handleReset = () => {
|
||||
setName('');
|
||||
setAge(0);
|
||||
setAgreedToTerms(false);
|
||||
setRgb(0, 0, 0);
|
||||
setCountry(countries[0]);
|
||||
setDrinkType(drinkTypes[0]);
|
||||
onFormReset();
|
||||
};
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
onFormSubmit();
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<RiveComponent style={{ width: '400px', height: '400px' }} />
|
||||
|
||||
{(rive === null) ? <div data-testid="loading-text">Loading…</div> : (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div>
|
||||
<label>
|
||||
Name:
|
||||
<input
|
||||
data-testid="name-input"
|
||||
type="text"
|
||||
value={name || ''}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
/>
|
||||
</label>
|
||||
<div data-testid="name-value">{name}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label>
|
||||
Age:
|
||||
<input
|
||||
data-testid="age-input"
|
||||
type="number"
|
||||
value={age || 0}
|
||||
onChange={(e) => setAge(Number(e.target.value))}
|
||||
/>
|
||||
</label>
|
||||
<div data-testid="age-value">{age}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label>
|
||||
<input
|
||||
data-testid="terms-checkbox"
|
||||
type="checkbox"
|
||||
checked={agreedToTerms || false}
|
||||
onChange={(e) => setAgreedToTerms(e.target.checked)}
|
||||
/>
|
||||
Agree to Terms
|
||||
</label>
|
||||
<div data-testid="terms-value">{agreedToTerms ? 'true' : 'false'}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label>
|
||||
Favorite Color:
|
||||
<div data-testid="color-value" style={{
|
||||
backgroundColor: colorNumberToHexString(colorNum),
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
display: 'inline-block',
|
||||
marginLeft: '10px'
|
||||
}}></div>
|
||||
</label>
|
||||
<button
|
||||
data-testid="set-color-red"
|
||||
type="button"
|
||||
onClick={() => setRgb(255, 0, 0)}
|
||||
>
|
||||
Red
|
||||
</button>
|
||||
<button
|
||||
data-testid="set-color-blue"
|
||||
type="button"
|
||||
onClick={() => setRgb(0, 0, 255)}
|
||||
>
|
||||
Blue
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label>
|
||||
Country:
|
||||
<select
|
||||
data-testid="country-select"
|
||||
value={country || ''}
|
||||
onChange={(e) => setCountry(e.target.value)}
|
||||
>
|
||||
{countries.map(c => (
|
||||
<option key={c} value={c}>{c}</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<div data-testid="country-value">{country}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label>
|
||||
Favorite Drink Type:
|
||||
<select
|
||||
data-testid="drink-type-select"
|
||||
value={drinkType || ''}
|
||||
onChange={(e) => setDrinkType(e.target.value)}
|
||||
>
|
||||
{drinkTypes.map(dt => (
|
||||
<option key={dt} value={dt}>{dt}</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<div data-testid="drink-type-value">{drinkType}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button data-testid="submit-button" type="submit">Submit</button>
|
||||
<button data-testid="reset-button" type="button" onClick={handleReset}>Reset</button>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
// Component to demonstrate different viewmodel instances
|
||||
export const PersonInstances = ({ src }: { src: string }) => {
|
||||
const [activeInstance, setActiveInstance] = useState('Steve');
|
||||
const [useDefaultInstance, setUseDefaultInstance] = useState(false);
|
||||
|
||||
const { rive, RiveComponent } = useRive({
|
||||
src,
|
||||
autoplay: true,
|
||||
artboard: "Artboard",
|
||||
stateMachines: "State Machine 1",
|
||||
});
|
||||
|
||||
const viewModel = useViewModel(rive, { name: 'PersonViewModel' });
|
||||
const params = useDefaultInstance ? { useDefault: true, rive } : { name: activeInstance, rive }
|
||||
const viewModelInstance = useViewModelInstance(viewModel, params);
|
||||
|
||||
|
||||
const { value: name } = useViewModelInstanceString('name', viewModelInstance);
|
||||
const { value: age } = useViewModelInstanceNumber('age', viewModelInstance);
|
||||
const { value: country } = useViewModelInstanceEnum('country', viewModelInstance);
|
||||
|
||||
const switchToNamedInstance = (instanceName: string) => {
|
||||
setActiveInstance(instanceName);
|
||||
setUseDefaultInstance(false);
|
||||
};
|
||||
|
||||
const switchToDefaultInstance = () => {
|
||||
setUseDefaultInstance(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<RiveComponent style={{ width: '400px', height: '400px' }} />
|
||||
|
||||
{(rive === null) ? <div data-testid="loading-text">Loading…</div> : (
|
||||
<div>
|
||||
<button
|
||||
data-testid="select-steve"
|
||||
onClick={() => switchToNamedInstance('Steve')}
|
||||
disabled={!useDefaultInstance && activeInstance === 'Steve'}
|
||||
>
|
||||
Steve
|
||||
</button>
|
||||
<button
|
||||
data-testid="select-jane"
|
||||
onClick={() => switchToNamedInstance('Jane')}
|
||||
disabled={!useDefaultInstance && activeInstance === 'Jane'}
|
||||
>
|
||||
Jane
|
||||
</button>
|
||||
<button
|
||||
data-testid="select-default"
|
||||
onClick={switchToDefaultInstance}
|
||||
disabled={useDefaultInstance}
|
||||
>
|
||||
Default
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<h3 data-testid="instance-name">Instance: {useDefaultInstance ? 'Default' : activeInstance}</h3>
|
||||
<p data-testid="person-name">Name: {name}</p>
|
||||
<p data-testid="person-age">Age: {age}</p>
|
||||
<p data-testid="person-country">Country: {country}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
4325
examples/yarn.lock
4325
examples/yarn.lock
File diff suppressed because it is too large
Load Diff
94
src/hooks/useViewModel.ts
Normal file
94
src/hooks/useViewModel.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { Rive, ViewModel, EventType } from '@rive-app/canvas';
|
||||
import { UseViewModelParameters } from '../types';
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for fetching a ViewModel from a Rive instance.
|
||||
*
|
||||
* @param rive - The Rive instance to retrieve the ViewModel from
|
||||
* @param params - Options for retrieving a ViewModel
|
||||
* @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,
|
||||
params?: UseViewModelParameters
|
||||
): ViewModel | null {
|
||||
const { name, useDefault = false } = params ?? {};
|
||||
const riveRef = useRef<Rive | null>(null);
|
||||
const paramsRef = useRef<UseViewModelParameters | undefined>(params);
|
||||
const [viewModel, setViewModel] = useState<ViewModel | null>(null);
|
||||
|
||||
const shouldUpdate = useRef(true);
|
||||
|
||||
useEffect(() => {
|
||||
const isRiveChanged = riveRef.current !== rive;
|
||||
const areParamsChanged = !areParamsEqual(paramsRef.current, params);
|
||||
|
||||
shouldUpdate.current = isRiveChanged || areParamsChanged;
|
||||
riveRef.current = rive;
|
||||
paramsRef.current = params;
|
||||
|
||||
if (!shouldUpdate.current && viewModel) {
|
||||
return;
|
||||
}
|
||||
|
||||
function fetchViewModel() {
|
||||
const currentRive = riveRef.current;
|
||||
const currentParams = paramsRef.current;
|
||||
|
||||
if (!currentRive) {
|
||||
setViewModel(null);
|
||||
return;
|
||||
}
|
||||
|
||||
let model: ViewModel | null = null;
|
||||
|
||||
if (currentParams?.name != null) {
|
||||
model = currentRive.viewModelByName?.(currentParams.name) || null;
|
||||
} else if (currentParams?.useDefault) {
|
||||
model = currentRive.defaultViewModel() || null;
|
||||
} else {
|
||||
model = currentRive.defaultViewModel() || null;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
96
src/hooks/useViewModelInstance.ts
Normal file
96
src/hooks/useViewModelInstance.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { ViewModel, ViewModelInstance } from '@rive-app/canvas';
|
||||
import { UseViewModelInstanceParameters } from '../types';
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for fetching a ViewModelInstance from a ViewModel.
|
||||
*
|
||||
* @param viewModel - The ViewModel to get an instance from
|
||||
* @param params - Options for retrieving a ViewModelInstance
|
||||
* @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(
|
||||
viewModel: ViewModel | null,
|
||||
params?: UseViewModelInstanceParameters
|
||||
): ViewModelInstance | null {
|
||||
const { name, useDefault = false, useNew = false, rive } = params ?? {};
|
||||
const [instance, setInstance] = useState<ViewModelInstance | null>(null);
|
||||
|
||||
const viewModelRef = useRef<ViewModel | null>(viewModel);
|
||||
const paramsRef = useRef<UseViewModelInstanceParameters | undefined>(params);
|
||||
const instanceRef = useRef<ViewModelInstance | null>(null);
|
||||
|
||||
const shouldUpdate = useRef(true);
|
||||
|
||||
useEffect(() => {
|
||||
const isViewModelChanged = viewModelRef.current !== viewModel;
|
||||
const areParamsChanged = !areParamsEqual(paramsRef.current, params);
|
||||
|
||||
shouldUpdate.current = isViewModelChanged || areParamsChanged;
|
||||
viewModelRef.current = viewModel;
|
||||
paramsRef.current = params;
|
||||
|
||||
if (!shouldUpdate.current && instanceRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentViewModel = viewModelRef.current;
|
||||
const currentParams = paramsRef.current;
|
||||
|
||||
if (!currentViewModel) {
|
||||
setInstance(null);
|
||||
instanceRef.current = null;
|
||||
return;
|
||||
}
|
||||
|
||||
let result: ViewModelInstance | null = null;
|
||||
|
||||
if (currentParams?.name != null) {
|
||||
result = currentViewModel.instanceByName(currentParams.name) || null;
|
||||
} else if (currentParams?.useDefault) {
|
||||
result = currentViewModel.defaultInstance?.() || null;
|
||||
} else if (currentParams?.useNew) {
|
||||
result = currentViewModel.instance?.() || null;
|
||||
} else {
|
||||
result = currentViewModel.defaultInstance?.() || null;
|
||||
}
|
||||
|
||||
instanceRef.current = result;
|
||||
setInstance(result);
|
||||
shouldUpdate.current = false;
|
||||
|
||||
// Bind instance to Rive if needed
|
||||
if (rive && result && rive.viewModelInstance !== result) {
|
||||
rive.bindViewModelInstance(result);
|
||||
}
|
||||
}, [viewModel, name, useDefault, useNew, rive]);
|
||||
|
||||
return instance;
|
||||
}
|
||||
36
src/hooks/useViewModelInstanceBoolean.ts
Normal file
36
src/hooks/useViewModelInstanceBoolean.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { useCallback } from 'react';
|
||||
import { ViewModelInstanceBoolean, ViewModelInstance } from '@rive-app/canvas';
|
||||
import { UseViewModelInstanceBooleanResult } from '../types';
|
||||
import { useViewModelInstanceProperty } from './useViewModelInstanceProperty';
|
||||
|
||||
/**
|
||||
* Hook for interacting with boolean ViewModel instance properties.
|
||||
*
|
||||
* @param path - The path to the boolean property
|
||||
* @param 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,
|
||||
viewModelInstance?: ViewModelInstance | null
|
||||
): UseViewModelInstanceBooleanResult {
|
||||
const result = useViewModelInstanceProperty<ViewModelInstanceBoolean, boolean, Omit<UseViewModelInstanceBooleanResult, 'value'>>(
|
||||
path,
|
||||
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
|
||||
};
|
||||
}
|
||||
56
src/hooks/useViewModelInstanceColor.ts
Normal file
56
src/hooks/useViewModelInstanceColor.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { useCallback } from 'react';
|
||||
import { ViewModelInstanceColor, ViewModelInstance } from '@rive-app/canvas';
|
||||
import { UseViewModelInstanceColorResult } from '../types';
|
||||
import { useViewModelInstanceProperty } from './useViewModelInstanceProperty';
|
||||
|
||||
/**
|
||||
* Hook for interacting with color properties of a ViewModelInstance.
|
||||
*
|
||||
* @param path - Path to the color property
|
||||
* @param 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,
|
||||
viewModelInstance?: ViewModelInstance | null
|
||||
): UseViewModelInstanceColorResult {
|
||||
const result = useViewModelInstanceProperty<ViewModelInstanceColor, number, Omit<UseViewModelInstanceColorResult, 'value'>>(
|
||||
path,
|
||||
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
|
||||
};
|
||||
}
|
||||
45
src/hooks/useViewModelInstanceEnum.ts
Normal file
45
src/hooks/useViewModelInstanceEnum.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { useCallback } from 'react';
|
||||
import { ViewModelInstance, ViewModelInstanceEnum } from '@rive-app/canvas';
|
||||
import { UseViewModelInstanceEnumResult } from '../types';
|
||||
import { useViewModelInstanceProperty } from './useViewModelInstanceProperty';
|
||||
|
||||
/**
|
||||
* 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,
|
||||
viewModelInstance?: ViewModelInstance | null
|
||||
): UseViewModelInstanceEnumResult {
|
||||
const result = useViewModelInstanceProperty<
|
||||
ViewModelInstanceEnum,
|
||||
string,
|
||||
Omit<UseViewModelInstanceEnumResult, 'value' | 'values'>,
|
||||
string[]
|
||||
>(path, viewModelInstance, {
|
||||
getProperty: useCallback((vm, p) => vm.enum(p), []),
|
||||
getValue: useCallback((prop) => prop.value, []),
|
||||
defaultValue: null,
|
||||
getExtendedData: useCallback((prop: any) => prop.values, []),
|
||||
buildPropertyOperations: useCallback(
|
||||
(safePropertyAccess) => ({
|
||||
setValue: (newValue: string) => {
|
||||
safePropertyAccess((prop) => {
|
||||
prop.value = newValue;
|
||||
});
|
||||
},
|
||||
}),
|
||||
[]
|
||||
),
|
||||
});
|
||||
|
||||
return {
|
||||
value: result.value,
|
||||
values: result.extendedData || [],
|
||||
setValue: result.setValue,
|
||||
};
|
||||
}
|
||||
37
src/hooks/useViewModelInstanceNumber.ts
Normal file
37
src/hooks/useViewModelInstanceNumber.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { useCallback } from 'react';
|
||||
import { ViewModelInstance, ViewModelInstanceNumber } from '@rive-app/canvas';
|
||||
import { UseViewModelInstanceNumberResult } from '../types';
|
||||
import { useViewModelInstanceProperty } from './useViewModelInstanceProperty';
|
||||
|
||||
/**
|
||||
* 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,
|
||||
viewModelInstance?: ViewModelInstance | null
|
||||
): UseViewModelInstanceNumberResult {
|
||||
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
|
||||
};
|
||||
}
|
||||
175
src/hooks/useViewModelInstanceProperty.ts
Normal file
175
src/hooks/useViewModelInstanceProperty.ts
Normal file
@@ -0,0 +1,175 @@
|
||||
import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
|
||||
import { ViewModelInstance, ViewModelInstanceValue } from '@rive-app/canvas';
|
||||
|
||||
/**
|
||||
* 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 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;
|
||||
|
||||
/** Function to get the current value from the property */
|
||||
getValue: (prop: P) => V;
|
||||
|
||||
/** 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 () => { };
|
||||
}
|
||||
|
||||
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 = useMemo(
|
||||
() => optionsRef.current.buildPropertyOperations(safePropertyAccess),
|
||||
[safePropertyAccess]
|
||||
);
|
||||
|
||||
const result = {
|
||||
value,
|
||||
...operations
|
||||
} as R & { value: V | null } & (E extends undefined ? {} : { extendedData: E | null });
|
||||
|
||||
if (options.getExtendedData) {
|
||||
(result as any).extendedData = extendedData;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
38
src/hooks/useViewModelInstanceString.ts
Normal file
38
src/hooks/useViewModelInstanceString.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { useCallback } from 'react';
|
||||
import { ViewModelInstance, ViewModelInstanceString } from '@rive-app/canvas';
|
||||
import { UseViewModelInstanceStringResult } from '../types';
|
||||
import { useViewModelInstanceProperty } from './useViewModelInstanceProperty';
|
||||
|
||||
/**
|
||||
* 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,
|
||||
viewModelInstance?: ViewModelInstance | null
|
||||
): UseViewModelInstanceStringResult {
|
||||
|
||||
const result = useViewModelInstanceProperty<ViewModelInstanceString, string, Omit<UseViewModelInstanceStringResult, 'value'>>(
|
||||
path,
|
||||
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
|
||||
};
|
||||
}
|
||||
42
src/hooks/useViewModelInstanceTrigger.ts
Normal file
42
src/hooks/useViewModelInstanceTrigger.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { useCallback } from 'react';
|
||||
import { ViewModelInstance, ViewModelInstanceTrigger } from '@rive-app/canvas';
|
||||
import { UseViewModelInstanceTriggerParameters, UseViewModelInstanceTriggerResult } from '../types';
|
||||
import { useViewModelInstanceProperty } from './useViewModelInstanceProperty';
|
||||
|
||||
/**
|
||||
* 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,
|
||||
viewModelInstance?: ViewModelInstance | null,
|
||||
params?: UseViewModelInstanceTriggerParameters
|
||||
): UseViewModelInstanceTriggerResult {
|
||||
const { onTrigger } = params ?? {};
|
||||
|
||||
const { trigger } = useViewModelInstanceProperty<ViewModelInstanceTrigger, undefined, UseViewModelInstanceTriggerResult>(
|
||||
path,
|
||||
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();
|
||||
});
|
||||
}
|
||||
}), [])
|
||||
}
|
||||
);
|
||||
|
||||
return { trigger };
|
||||
}
|
||||
33
src/index.ts
33
src/index.ts
@@ -1,10 +1,37 @@
|
||||
import Rive, { RiveProps } from './components/Rive';
|
||||
import useRive from './hooks/useRive';
|
||||
import useStateMachineInput from './hooks/useStateMachineInput';
|
||||
import useViewModel from './hooks/useViewModel';
|
||||
import useViewModelInstance from './hooks/useViewModelInstance';
|
||||
import useViewModelInstanceNumber from './hooks/useViewModelInstanceNumber';
|
||||
import useViewModelInstanceString from './hooks/useViewModelInstanceString';
|
||||
import useViewModelInstanceBoolean from './hooks/useViewModelInstanceBoolean';
|
||||
import useViewModelInstanceColor from './hooks/useViewModelInstanceColor';
|
||||
import useViewModelInstanceEnum from './hooks/useViewModelInstanceEnum';
|
||||
import useViewModelInstanceTrigger from './hooks/useViewModelInstanceTrigger';
|
||||
import useResizeCanvas from './hooks/useResizeCanvas';
|
||||
import useRiveFile from './hooks/useRiveFile';
|
||||
|
||||
export default Rive;
|
||||
export { useRive, useStateMachineInput, useResizeCanvas, useRiveFile , RiveProps };
|
||||
export { RiveState, UseRiveParameters, UseRiveFileParameters, UseRiveOptions } from './types';
|
||||
export * from '@rive-app/canvas';
|
||||
export {
|
||||
useRive,
|
||||
useStateMachineInput,
|
||||
useResizeCanvas,
|
||||
useRiveFile,
|
||||
useViewModel,
|
||||
useViewModelInstance,
|
||||
useViewModelInstanceNumber,
|
||||
useViewModelInstanceString,
|
||||
useViewModelInstanceBoolean,
|
||||
useViewModelInstanceColor,
|
||||
useViewModelInstanceEnum,
|
||||
useViewModelInstanceTrigger,
|
||||
RiveProps,
|
||||
};
|
||||
export {
|
||||
RiveState,
|
||||
UseRiveParameters,
|
||||
UseRiveFileParameters,
|
||||
UseRiveOptions,
|
||||
} from './types';
|
||||
export * from '@rive-app/canvas';
|
||||
127
src/types.ts
127
src/types.ts
@@ -57,3 +57,130 @@ export type RiveFileState = {
|
||||
riveFile: RiveFile | null;
|
||||
status: FileStatus;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parameters for useViewModel hook.
|
||||
*
|
||||
* @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 =
|
||||
| { name: string; useDefault?: never }
|
||||
| { useDefault?: boolean; name?: never };
|
||||
|
||||
/**
|
||||
* Parameters for useViewModelInstance hook.
|
||||
*
|
||||
* @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 - If provided, automatically binds the instance to this Rive instance.
|
||||
*/
|
||||
export type UseViewModelInstanceParameters =
|
||||
| { name: string; useDefault?: never; useNew?: never; rive?: Rive | null }
|
||||
| { useDefault?: boolean; name?: never; useNew?: never; rive?: Rive | null }
|
||||
| { useNew?: boolean; name?: never; useDefault?: never; rive?: Rive | null };
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Parameters for interacting with trigger properties of a ViewModelInstance
|
||||
* @property onTrigger - Callback that runs when the trigger fires
|
||||
*/
|
||||
export type UseViewModelInstanceTriggerParameters = {
|
||||
onTrigger?: () => void;
|
||||
};
|
||||
|
||||
|
||||
|
||||
export type UseViewModelInstanceNumberResult = {
|
||||
/**
|
||||
* The current value of the number.
|
||||
*/
|
||||
value: number | null;
|
||||
/**
|
||||
* Set the value of the number.
|
||||
* @param value - The value to set the number to.
|
||||
*/
|
||||
setValue: (value: number) => void;
|
||||
};
|
||||
export type UseViewModelInstanceStringResult = {
|
||||
/**
|
||||
* The current value of the string.
|
||||
*/
|
||||
value: string | null;
|
||||
/**
|
||||
* Set the value of the string.
|
||||
* @param value - The value to set the string to.
|
||||
*/
|
||||
setValue: (value: string) => void;
|
||||
};
|
||||
export type UseViewModelInstanceBooleanResult = {
|
||||
/**
|
||||
* The current value of the boolean.
|
||||
*/
|
||||
value: boolean | null;
|
||||
/**
|
||||
* Set the value of the boolean.
|
||||
* @param value - The value to set the boolean to.
|
||||
*/
|
||||
setValue: (value: boolean) => void;
|
||||
};
|
||||
|
||||
export type UseViewModelInstanceColorResult = {
|
||||
/**
|
||||
* The current value of the color.
|
||||
*/
|
||||
value: number | null;
|
||||
/**
|
||||
* Set the value of the color.
|
||||
* @param value - The value to set the color to.
|
||||
*/
|
||||
setValue: (value: number) => void;
|
||||
/**
|
||||
* Set the red value of the color.
|
||||
* @param r - The red value to set the color to.
|
||||
*/
|
||||
setRgb: (r: number, g: number, b: number) => void;
|
||||
/**
|
||||
* Set the red, green, blue, and alpha values of the color.
|
||||
* @param r - The red value to set the color to.
|
||||
* @param g - The green value to set the color to.
|
||||
* @param b - The blue value to set the color to.
|
||||
* @param a - The alpha value to set the color to.
|
||||
*/
|
||||
setRgba: (r: number, g: number, b: number, a: number) => void;
|
||||
/**
|
||||
* Set the alpha value of the color.
|
||||
* @param a - The alpha value to set the color to.
|
||||
*/
|
||||
setAlpha: (a: number) => void;
|
||||
/**
|
||||
* Set the opacity value of the color.
|
||||
* @param o - The opacity value to set the color to.
|
||||
*/
|
||||
setOpacity: (o: number) => void;
|
||||
};
|
||||
|
||||
export type UseViewModelInstanceEnumResult = {
|
||||
/**
|
||||
* The current value of the enum.
|
||||
*/
|
||||
value: string | null;
|
||||
/**
|
||||
* Set the value of the enum.
|
||||
* @param value - The value to set the enum to.
|
||||
*/
|
||||
setValue: (value: string) => void;
|
||||
/**
|
||||
* The values of the enum.
|
||||
*/
|
||||
values: string[];
|
||||
};
|
||||
|
||||
export type UseViewModelInstanceTriggerResult = {
|
||||
/**
|
||||
* Fires the property trigger.
|
||||
*/
|
||||
trigger: () => void;
|
||||
};
|
||||
Reference in New Issue
Block a user