mirror of
https://github.com/rive-app/rive-react.git
synced 2026-03-13 08:22:30 +08:00
Compare commits
28 Commits
v4.21.1
...
ci/fix-ver
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3af6b41f17 | ||
|
|
76fe777fb1 | ||
|
|
a958bdaa72 | ||
|
|
69658c204a | ||
|
|
7249fa36e7 | ||
|
|
52dd934e43 | ||
|
|
c151ee37b5 | ||
|
|
c660a675c2 | ||
|
|
74e1d5a5f2 | ||
|
|
963ecc43b8 | ||
|
|
85807f2166 | ||
|
|
9a33504d3a | ||
|
|
1a4d7e7168 | ||
|
|
b3d0fd4339 | ||
|
|
c4239ab6b2 | ||
|
|
22f8d5a945 | ||
|
|
721ed786dc | ||
|
|
eef56fb641 | ||
|
|
4bc0f496f8 | ||
|
|
10bb4c69ea | ||
|
|
c5b6826996 | ||
|
|
ec4875933c | ||
|
|
d808a8bdea | ||
|
|
7b174f7f51 | ||
|
|
eecd0d3c5b | ||
|
|
6c00364e60 | ||
|
|
d310f1c96d | ||
|
|
68e8fbe46d |
9
.github/workflows/publish.yml
vendored
9
.github/workflows/publish.yml
vendored
@@ -14,9 +14,10 @@ jobs:
|
||||
publish_job:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.PAT_GITHUB }}
|
||||
- name: Setup Git config
|
||||
run: |
|
||||
git config --local user.email 'hello@rive.app'
|
||||
@@ -39,20 +40,14 @@ jobs:
|
||||
name: Major Release - Bump version number, update changelog, push and tag
|
||||
run: npm run release:major
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.REPO_TOKEN }}
|
||||
PAT_GITHUB: ${{ secrets.PAT_GITHUB }}
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
- if: ${{inputs.major == false && inputs.minor == true}}
|
||||
name: Minor release - Bump version number, update changelog, push and tag
|
||||
run: npm run release:minor
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.REPO_TOKEN }}
|
||||
PAT_GITHUB: ${{ secrets.PAT_GITHUB }}
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
- if: ${{inputs.major == false && inputs.minor == false}}
|
||||
name: Patch release - Bump version number, update changelog, push and tag
|
||||
run: npm run release:patch
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.REPO_TOKEN }}
|
||||
PAT_GITHUB: ${{ secrets.PAT_GITHUB }}
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
59
CHANGELOG.md
59
CHANGELOG.md
@@ -4,8 +4,67 @@ All notable changes to this project will be documented in this file. Dates are d
|
||||
|
||||
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
||||
|
||||
#### [v4.23.0](https://github.com/rive-app/rive-react/compare/v4.22.1...v4.23.0)
|
||||
|
||||
- chore: bump rive wasm 2.31.0 [`7249fa3`](https://github.com/rive-app/rive-react/commit/7249fa36e7b6a2184ec60fb1e34a68f28b4eeb6d)
|
||||
|
||||
#### [v4.22.1](https://github.com/rive-app/rive-react/compare/v4.22.0...v4.22.1)
|
||||
|
||||
> 18 July 2025
|
||||
|
||||
- chore: release 4.22.1 [`52dd934`](https://github.com/rive-app/rive-react/commit/52dd934e439507d079bf4f5009372857dfbb97a6)
|
||||
- bump rive to 2.30.4 [`c151ee3`](https://github.com/rive-app/rive-react/commit/c151ee37b5482cb7eee258c84f6c52182dbe9db5)
|
||||
|
||||
#### [v4.22.0](https://github.com/rive-app/rive-react/compare/v4.21.6...v4.22.0)
|
||||
|
||||
> 15 July 2025
|
||||
|
||||
- feat: add tests for artboard binding [`74e1d5a`](https://github.com/rive-app/rive-react/commit/74e1d5a5f29f14f46be3af3d052bb51c3d833799)
|
||||
- feat: add useViewModelInstanceArtboard hook [`963ecc4`](https://github.com/rive-app/rive-react/commit/963ecc43b80e6465d159621d014b70b8cbfee9d4)
|
||||
- chore: release 4.22.0 [`c660a67`](https://github.com/rive-app/rive-react/commit/c660a675c246af9fca50795ff88b7935c2d2a101)
|
||||
|
||||
#### [v4.21.6](https://github.com/rive-app/rive-react/compare/v4.21.5...v4.21.6)
|
||||
|
||||
> 15 July 2025
|
||||
|
||||
- chore: release 4.21.6 [`85807f2`](https://github.com/rive-app/rive-react/commit/85807f2166fcfba01e4556ac346b769d6fa08341)
|
||||
- rive_canvas_2.30.3 [`9a33504`](https://github.com/rive-app/rive-react/commit/9a33504d3a315ce2f3dff753192b0ae491a56a04)
|
||||
|
||||
#### [v4.21.5](https://github.com/rive-app/rive-react/compare/v4.21.4...v4.21.5)
|
||||
|
||||
> 14 July 2025
|
||||
|
||||
- feat: add tests for list property [`22f8d5a`](https://github.com/rive-app/rive-react/commit/22f8d5a945c74974b7dabcfe16aaa019f6141326)
|
||||
- feat: add useViewModelInstanceImage hook [`eef56fb`](https://github.com/rive-app/rive-react/commit/eef56fb641839b55806296873186aa53b3e1d068)
|
||||
- feat: add useViewModelInstanceList hook [`721ed78`](https://github.com/rive-app/rive-react/commit/721ed786dc43a526eafb54108bfb54f353d7430d)
|
||||
|
||||
#### [v4.21.4](https://github.com/rive-app/rive-react/compare/v4.21.3...v4.21.4)
|
||||
|
||||
> 25 June 2025
|
||||
|
||||
- chore: release 4.21.4 [`4bc0f49`](https://github.com/rive-app/rive-react/commit/4bc0f496f87a54ffda673acb7b9be4b7a8b311c0)
|
||||
- cleanup rive on unmount [`7b174f7`](https://github.com/rive-app/rive-react/commit/7b174f7f5106b1b863969bd7318a8a6cb1a12b67)
|
||||
- refactor: change onLoad to onRiveReady [`ec48759`](https://github.com/rive-app/rive-react/commit/ec4875933cad45a3d338290951d55ac9c72df9d0)
|
||||
|
||||
#### [v4.21.3](https://github.com/rive-app/rive-react/compare/v4.21.2...v4.21.3)
|
||||
|
||||
> 8 June 2025
|
||||
|
||||
- chore: release 4.21.3 [`eecd0d3`](https://github.com/rive-app/rive-react/commit/eecd0d3c5be011fe9865e45b05435fbd45e7395d)
|
||||
- rive react 2.29.3 [`6c00364`](https://github.com/rive-app/rive-react/commit/6c00364e60e91a7a6556e763ebf9ebee4793b336)
|
||||
|
||||
#### [v4.21.2](https://github.com/rive-app/rive-react/compare/v4.21.1...v4.21.2)
|
||||
|
||||
> 5 June 2025
|
||||
|
||||
- chore: release 4.21.2 [`d310f1c`](https://github.com/rive-app/rive-react/commit/d310f1c96dbff6cbb7397d4bea2687c8d3f271f4)
|
||||
- chore: bump Rive wasm 2.29.2 [`68e8fbe`](https://github.com/rive-app/rive-react/commit/68e8fbe46d4f1824a6228ce2ea0a02735dced5ba)
|
||||
|
||||
#### [v4.21.1](https://github.com/rive-app/rive-react/compare/v4.21.0...v4.21.1)
|
||||
|
||||
> 28 May 2025
|
||||
|
||||
- chore: release 4.21.1 [`8ff9a84`](https://github.com/rive-app/rive-react/commit/8ff9a844fe5b02a2eb1964cf01814479f6c72248)
|
||||
- bump rive to 2.29.0 [`a565795`](https://github.com/rive-app/rive-react/commit/a565795452444205e88083cba272bc8ca6c9968f)
|
||||
|
||||
#### [v4.21.0](https://github.com/rive-app/rive-react/compare/v4.20.2...v4.21.0)
|
||||
|
||||
@@ -6,9 +6,14 @@
|
||||
|
||||

|
||||
|
||||
A React runtime library for [Rive](https://rive.app).
|
||||
[Rive](https://rive.app) combines an interactive design tool, a new stateful graphics format, a lightweight multi-platform runtime, and a blazing-fast vector renderer. This end-to-end pipeline guarantees that what you build in the Rive Editor is exactly what ships in your apps, games, and websites.
|
||||
|
||||
This library is a wrapper around the [JS/Wasm runtime](https://github.com/rive-app/rive-wasm), giving full control over the js runtime while providing components and hooks for React applications.
|
||||
For more information, check out the following resources:
|
||||
|
||||
- [Homepage](https://rive.app/)
|
||||
- [General Docs](https://rive.app/docs/)
|
||||
- [React Docs](https://rive.app/docs/runtimes/react/react)
|
||||
- [Rive Community / Support](https://community.rive.app/c/support/)
|
||||
|
||||
## Table of contents
|
||||
|
||||
|
||||
BIN
examples/public/artboard_db_test.riv
Normal file
BIN
examples/public/artboard_db_test.riv
Normal file
Binary file not shown.
BIN
examples/public/db_list_test.riv
Normal file
BIN
examples/public/db_list_test.riv
Normal file
Binary file not shown.
BIN
examples/public/image_db_test.riv
Normal file
BIN
examples/public/image_db_test.riv
Normal file
Binary file not shown.
@@ -2,7 +2,7 @@ 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';
|
||||
import { StringPropertyTest, NumberPropertyTest, BooleanPropertyTest, ColorPropertyTest, EnumPropertyTest, NestedViewModelTest, TriggerPropertyTest, PersonForm, PersonInstances, ImagePropertyTest, TodoListTest, ArtboardPropertyTest } from './DataBindingTests';
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'Tests/DataBinding',
|
||||
@@ -345,4 +345,183 @@ export const PersonFormStory: StoryObj = {
|
||||
};
|
||||
|
||||
|
||||
export const ImagePropertyStory: StoryObj = {
|
||||
name: 'Image Property',
|
||||
render: () => <ImagePropertyTest src="image_db_test.riv" />,
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
// Wait for the Rive file to load
|
||||
await waitFor(() => {
|
||||
expect(canvas.getByTestId('load-random-image')).toBeTruthy();
|
||||
expect(canvas.getByTestId('clear-image')).toBeTruthy();
|
||||
}, { timeout: 3000 });
|
||||
|
||||
const loadImageButton = canvas.getByTestId('load-random-image');
|
||||
const clearImageButton = canvas.getByTestId('clear-image');
|
||||
|
||||
expect(canvas.queryByTestId('current-image-url')).toBeNull();
|
||||
|
||||
// Load a random image
|
||||
await userEvent.click(loadImageButton);
|
||||
|
||||
// Wait for the image to load and URL to appear
|
||||
await waitFor(() => {
|
||||
expect(canvas.getByTestId('current-image-url')).toBeTruthy();
|
||||
}, { timeout: 5000 });
|
||||
|
||||
// Verify the image URL is displayed
|
||||
const imageUrlElement = canvas.getByTestId('current-image-url');
|
||||
expect(imageUrlElement.textContent).toContain('Current image: https://picsum.photos');
|
||||
|
||||
// Clear the image
|
||||
await userEvent.click(clearImageButton);
|
||||
|
||||
// Load another image to test it works multiple times
|
||||
await userEvent.click(loadImageButton);
|
||||
|
||||
// Wait for the new image to load
|
||||
await waitFor(() => {
|
||||
expect(canvas.getByTestId('current-image-url')).toBeTruthy();
|
||||
}, { timeout: 5000 });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export const TodoListStory: StoryObj = {
|
||||
name: 'Todo List Property',
|
||||
render: () => <TodoListTest src="db_list_test.riv" />,
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
// Wait for the Rive file to load
|
||||
await waitFor(() => {
|
||||
expect(canvas.getByTestId('list-length')).toBeTruthy();
|
||||
}, { timeout: 3000 });
|
||||
|
||||
const initialLengthText = canvas.getByTestId('list-length').textContent;
|
||||
const initialCount = parseInt(initialLengthText?.match(/Items: (\d+)/)?.[1] || '0');
|
||||
|
||||
// Test 1: addInstance - Add item to end
|
||||
const addButton = canvas.getByTestId('add-item-button');
|
||||
await userEvent.click(addButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(canvas.getByTestId('list-length').textContent).toContain(`Items: ${initialCount + 1}`);
|
||||
});
|
||||
|
||||
// Test 2: addInstanceAt - Add item at specific index (if we have items)
|
||||
if (initialCount > 0) {
|
||||
const addAtButton = canvas.getByTestId('add-item-at-button');
|
||||
await userEvent.click(addAtButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(canvas.getByTestId('list-length').textContent).toContain(`Items: ${initialCount + 2}`);
|
||||
});
|
||||
}
|
||||
|
||||
// Test 3: getInstanceAt - Interact with specific items
|
||||
const currentCount = initialCount + (initialCount > 0 ? 2 : 1);
|
||||
if (currentCount > 0) {
|
||||
await waitFor(() => {
|
||||
expect(canvas.getByTestId('todo-item-0')).toBeTruthy();
|
||||
});
|
||||
|
||||
// Edit the first item
|
||||
const todoText = canvas.getByTestId('todo-text-0');
|
||||
await userEvent.clear(todoText);
|
||||
|
||||
// Wait for the input to be cleared to avoid issues with autocomplete
|
||||
await waitFor(() => {
|
||||
expect((todoText as HTMLInputElement).value).toBe('');
|
||||
}, { timeout: 2000 });
|
||||
|
||||
await userEvent.click(todoText);
|
||||
await userEvent.paste('Test Item');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(canvas.getByTestId('todo-text-value-0').textContent).toContain('Test Item');
|
||||
}, { timeout: 3000 });
|
||||
|
||||
}
|
||||
|
||||
// Test 4: swap - Swap first two items
|
||||
if (currentCount >= 2) {
|
||||
const firstText = canvas.getByTestId<HTMLInputElement>('todo-text-0').value;
|
||||
const secondText = canvas.getByTestId<HTMLInputElement>('todo-text-1').value;
|
||||
|
||||
const swapButton = canvas.getByTestId('swap-button');
|
||||
await userEvent.click(swapButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(canvas.getByTestId('todo-text-0')).toHaveValue(secondText);
|
||||
expect(canvas.getByTestId('todo-text-1')).toHaveValue(firstText);
|
||||
}, { timeout: 3000 });
|
||||
}
|
||||
|
||||
// Test 5: removeInstance - Remove by instance reference
|
||||
if (currentCount > 0) {
|
||||
const removeInstanceButton = canvas.getByTestId('remove-instance-button');
|
||||
await userEvent.click(removeInstanceButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(canvas.getByTestId('list-length').textContent).toContain(`Items: ${currentCount - 1}`);
|
||||
}, { timeout: 3000 });
|
||||
}
|
||||
|
||||
// Test 6: removeInstanceAt - Remove by index
|
||||
const afterRemoveInstance = currentCount > 0 ? currentCount - 1 : 0;
|
||||
if (afterRemoveInstance > 0) {
|
||||
const removeIndexButton = canvas.getByTestId('remove-index-button');
|
||||
await userEvent.click(removeIndexButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(canvas.getByTestId('list-length').textContent).toContain(`Items: ${afterRemoveInstance - 1}`);
|
||||
}, { timeout: 3000 });
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
export const ArtboardPropertyStory: StoryObj = {
|
||||
name: 'Artboard Property',
|
||||
render: () => <ArtboardPropertyTest src="artboard_db_test.riv" />,
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
// Wait for the Rive file to load
|
||||
await waitFor(() => {
|
||||
expect(canvas.getByTestId('set-artboard1-blue')).toBeTruthy();
|
||||
expect(canvas.getByTestId('set-artboard1-red')).toBeTruthy();
|
||||
expect(canvas.getByTestId('set-artboard1-green')).toBeTruthy();
|
||||
}, { timeout: 3000 });
|
||||
|
||||
// Initially should show None
|
||||
expect(canvas.getByTestId('artboard1-current').textContent).toBe('Current: None');
|
||||
expect(canvas.getByTestId('artboard2-current').textContent).toBe('Current: None');
|
||||
|
||||
// Set artboard 1 to blue
|
||||
await userEvent.click(canvas.getByTestId('set-artboard1-blue'));
|
||||
await waitFor(() => {
|
||||
expect(canvas.getByTestId('artboard1-current').textContent).toBe('Current: ArtboardBlue');
|
||||
});
|
||||
|
||||
// Set artboard 2 to red
|
||||
await userEvent.click(canvas.getByTestId('set-artboard2-red'));
|
||||
await waitFor(() => {
|
||||
expect(canvas.getByTestId('artboard2-current').textContent).toBe('Current: ArtboardRed');
|
||||
});
|
||||
|
||||
// Switch artboard 1 to green
|
||||
await userEvent.click(canvas.getByTestId('set-artboard1-green'));
|
||||
await waitFor(() => {
|
||||
expect(canvas.getByTestId('artboard1-current').textContent).toBe('Current: ArtboardGreen');
|
||||
});
|
||||
|
||||
// Switch artboard 2 to blue
|
||||
await userEvent.click(canvas.getByTestId('set-artboard2-blue'));
|
||||
await waitFor(() => {
|
||||
expect(canvas.getByTestId('artboard2-current').textContent).toBe('Current: ArtboardBlue');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -9,7 +9,12 @@ import Rive, {
|
||||
useViewModelInstanceNumber,
|
||||
useViewModelInstanceEnum,
|
||||
useViewModelInstanceColor,
|
||||
useViewModelInstanceTrigger
|
||||
useViewModelInstanceTrigger,
|
||||
useViewModelInstanceImage,
|
||||
decodeImage,
|
||||
ViewModelInstance,
|
||||
useViewModelInstanceList,
|
||||
useViewModelInstanceArtboard
|
||||
} from '@rive-app/react-webgl2';
|
||||
|
||||
|
||||
@@ -522,3 +527,355 @@ export const PersonInstances = ({ src }: { src: string }) => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const ImagePropertyTest = ({ src }: { src: string }) => {
|
||||
const [currentImageUrl, setCurrentImageUrl] = useState<string>('');
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
|
||||
const { rive, RiveComponent } = useRive({
|
||||
src,
|
||||
artboard: "Artboard",
|
||||
stateMachines: "State Machine 1",
|
||||
autoplay: true,
|
||||
autoBind: false,
|
||||
});
|
||||
|
||||
const viewModel = useViewModel(rive, { name: 'Post' });
|
||||
const viewModelInstance = useViewModelInstance(viewModel, { rive });
|
||||
|
||||
const { setValue: setImage } = useViewModelInstanceImage(
|
||||
'image',
|
||||
viewModelInstance
|
||||
);
|
||||
|
||||
const loadRandomImage = async () => {
|
||||
if (!setImage) return;
|
||||
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const imageUrl = `https://picsum.photos/400/300?random=${Date.now()}`;
|
||||
setCurrentImageUrl(imageUrl);
|
||||
|
||||
const response = await fetch(imageUrl);
|
||||
const imageBuffer = await response.arrayBuffer();
|
||||
const decodedImage = await decodeImage(new Uint8Array(imageBuffer));
|
||||
|
||||
setImage(decodedImage);
|
||||
|
||||
decodedImage.unref();
|
||||
} catch (error) {
|
||||
console.error('Failed to load image:', error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const clearImage = () => {
|
||||
if (setImage) {
|
||||
setImage(null);
|
||||
setCurrentImageUrl('');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '20px' }}>
|
||||
<div style={{ width: '400px', height: '300px', border: '1px solid #ccc' }}>
|
||||
<RiveComponent />
|
||||
</div>
|
||||
|
||||
{rive === null ? (
|
||||
<div data-testid="loading-text">Loading…</div>
|
||||
) : (
|
||||
<div style={{ display: 'flex', gap: '10px', alignItems: 'center' }}>
|
||||
<button
|
||||
onClick={loadRandomImage}
|
||||
disabled={isLoading}
|
||||
data-testid="load-random-image"
|
||||
>
|
||||
{isLoading ? 'Loading...' : 'Load Random Image'}
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={clearImage}
|
||||
disabled={isLoading}
|
||||
data-testid="clear-image"
|
||||
>
|
||||
Clear Image
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{currentImageUrl && (
|
||||
<div style={{ fontSize: '12px', color: '#666' }}>
|
||||
<span data-testid="current-image-url">Current image: {currentImageUrl}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// List Property Test
|
||||
|
||||
const TodoItemComponent = ({
|
||||
index,
|
||||
todoItem
|
||||
}: {
|
||||
index: number;
|
||||
todoItem: ViewModelInstance | null;
|
||||
}) => {
|
||||
const { value: text, setValue: setText } = useViewModelInstanceString('text', todoItem);
|
||||
const { value: isDone, setValue: setIsDone } = useViewModelInstanceBoolean('isDone', todoItem);
|
||||
|
||||
if (!todoItem) {
|
||||
return <div data-testid={`todo-item-${index}`}>Item not found</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div data-testid={`todo-item-${index}`} style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '10px',
|
||||
padding: '8px',
|
||||
border: '1px solid #ccc',
|
||||
marginBottom: '4px'
|
||||
}}>
|
||||
<input
|
||||
data-testid={`todo-checkbox-${index}`}
|
||||
type="checkbox"
|
||||
checked={isDone ?? false}
|
||||
onChange={(e) => setIsDone(e.target.checked)}
|
||||
/>
|
||||
<input
|
||||
data-testid={`todo-text-${index}`}
|
||||
type="text"
|
||||
value={text || ''}
|
||||
onChange={(e) => setText(e.target.value)}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<div data-testid={`todo-text-value-${index}`} style={{ fontSize: '12px', color: '#666' }}>
|
||||
Text: {text}
|
||||
</div>
|
||||
<div data-testid={`todo-done-value-${index}`} style={{ fontSize: '12px', color: '#666' }}>
|
||||
Done: {isDone ? 'true' : 'false'}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const TodoListTest = ({ src }: { src: string }) => {
|
||||
const { rive, RiveComponent } = useRive({
|
||||
src,
|
||||
autoplay: true,
|
||||
artboard: "Artboard",
|
||||
autoBind: false,
|
||||
stateMachines: "State Machine 1",
|
||||
});
|
||||
|
||||
const viewModel = useViewModel(rive, { name: 'TodoList' });
|
||||
const viewModelInstance = useViewModelInstance(viewModel, { rive });
|
||||
|
||||
const {
|
||||
length,
|
||||
addInstance,
|
||||
addInstanceAt,
|
||||
removeInstance,
|
||||
removeInstanceAt,
|
||||
getInstanceAt,
|
||||
swap
|
||||
} = useViewModelInstanceList('items', viewModelInstance);
|
||||
|
||||
const handleAddItem = () => {
|
||||
const todoItemViewModel = rive?.viewModelByName?.('TodoItem');
|
||||
if (todoItemViewModel) {
|
||||
const newTodoItem = todoItemViewModel.instance?.();
|
||||
if (newTodoItem) {
|
||||
addInstance(newTodoItem);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddItemAt = () => {
|
||||
const todoItemViewModel = rive?.viewModelByName?.('TodoItem');
|
||||
if (todoItemViewModel && length > 0) {
|
||||
const newTodoItem = todoItemViewModel.instance?.();
|
||||
if (newTodoItem) {
|
||||
addInstanceAt(newTodoItem, 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveFirstInstance = () => {
|
||||
const firstInstance = getInstanceAt(0);
|
||||
if (firstInstance) {
|
||||
removeInstance(firstInstance);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveFirstByIndex = () => {
|
||||
if (length > 0) {
|
||||
removeInstanceAt(0);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSwapItems = () => {
|
||||
if (length >= 2) {
|
||||
swap(0, 1);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<RiveComponent style={{ width: '400px', height: '400px' }} />
|
||||
{rive === null ? (
|
||||
<div data-testid="loading-text">Loading…</div>
|
||||
) : (
|
||||
<div>
|
||||
<div data-testid="list-length">Items: {length}</div>
|
||||
|
||||
<div style={{ marginBottom: '10px', display: 'flex', gap: '8px', flexWrap: 'wrap' }}>
|
||||
<button
|
||||
data-testid="add-item-button"
|
||||
onClick={handleAddItem}
|
||||
>
|
||||
Add Item (End)
|
||||
</button>
|
||||
|
||||
<button
|
||||
data-testid="add-item-at-button"
|
||||
onClick={handleAddItemAt}
|
||||
disabled={length === 0}
|
||||
>
|
||||
Add Item at Index 1
|
||||
</button>
|
||||
|
||||
<button
|
||||
data-testid="remove-instance-button"
|
||||
onClick={handleRemoveFirstInstance}
|
||||
disabled={length === 0}
|
||||
>
|
||||
Remove First (by Instance)
|
||||
</button>
|
||||
|
||||
<button
|
||||
data-testid="remove-index-button"
|
||||
onClick={handleRemoveFirstByIndex}
|
||||
disabled={length === 0}
|
||||
>
|
||||
Remove First (by Index)
|
||||
</button>
|
||||
|
||||
<button
|
||||
data-testid="swap-button"
|
||||
onClick={handleSwapItems}
|
||||
disabled={length < 2}
|
||||
>
|
||||
Swap First Two
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div data-testid="todo-items">
|
||||
{Array.from({ length }, (_, index) => (
|
||||
<TodoItemComponent
|
||||
key={index}
|
||||
index={index}
|
||||
todoItem={getInstanceAt(index)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const ArtboardPropertyTest = ({ src }: { src: string }) => {
|
||||
const [currentArtboard1, setCurrentArtboard1] = useState<string>('None');
|
||||
const [currentArtboard2, setCurrentArtboard2] = useState<string>('None');
|
||||
|
||||
const { rive, RiveComponent } = useRive({
|
||||
src,
|
||||
autoplay: true,
|
||||
artboard: "Main",
|
||||
autoBind: true,
|
||||
stateMachines: "State Machine 1",
|
||||
});
|
||||
|
||||
const { setValue: setArtboard1 } = useViewModelInstanceArtboard('artboard_1', rive?.viewModelInstance);
|
||||
const { setValue: setArtboard2 } = useViewModelInstanceArtboard('artboard_2', rive?.viewModelInstance);
|
||||
|
||||
const handleSetArtboard1 = (artboardName: string) => {
|
||||
if (rive) {
|
||||
const artboard = rive.getArtboard(artboardName);
|
||||
setArtboard1(artboard);
|
||||
setCurrentArtboard1(artboardName);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSetArtboard2 = (artboardName: string) => {
|
||||
if (rive) {
|
||||
const artboard = rive.getArtboard(artboardName);
|
||||
setArtboard2(artboard);
|
||||
setCurrentArtboard2(artboardName);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<RiveComponent style={{ width: '400px', height: '400px' }} />
|
||||
{(rive === null) ? <div data-testid="loading-text">Loading…</div> : (
|
||||
<div>
|
||||
<div style={{ marginBottom: '20px' }}>
|
||||
<h4>Artboard 1:</h4>
|
||||
<div style={{ display: 'flex', gap: '10px', marginBottom: '10px' }}>
|
||||
<button
|
||||
data-testid="set-artboard1-blue"
|
||||
onClick={() => handleSetArtboard1('ArtboardBlue')}
|
||||
>
|
||||
Set Blue Artboard
|
||||
</button>
|
||||
<button
|
||||
data-testid="set-artboard1-red"
|
||||
onClick={() => handleSetArtboard1('ArtboardRed')}
|
||||
>
|
||||
Set Red Artboard
|
||||
</button>
|
||||
<button
|
||||
data-testid="set-artboard1-green"
|
||||
onClick={() => handleSetArtboard1('ArtboardGreen')}
|
||||
>
|
||||
Set Green Artboard
|
||||
</button>
|
||||
</div>
|
||||
<div data-testid="artboard1-current">Current: {currentArtboard1}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4>Artboard 2:</h4>
|
||||
<div style={{ display: 'flex', gap: '10px', marginBottom: '10px' }}>
|
||||
<button
|
||||
data-testid="set-artboard2-blue"
|
||||
onClick={() => handleSetArtboard2('ArtboardBlue')}
|
||||
>
|
||||
Set Blue Artboard
|
||||
</button>
|
||||
<button
|
||||
data-testid="set-artboard2-red"
|
||||
onClick={() => handleSetArtboard2('ArtboardRed')}
|
||||
>
|
||||
Set Red Artboard
|
||||
</button>
|
||||
<button
|
||||
data-testid="set-artboard2-green"
|
||||
onClick={() => handleSetArtboard2('ArtboardGreen')}
|
||||
>
|
||||
Set Green Artboard
|
||||
</button>
|
||||
</div>
|
||||
<div data-testid="artboard2-current">Current: {currentArtboard2}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@rive-app/react-canvas-lite",
|
||||
"version": "4.21.1",
|
||||
"version": "4.23.0",
|
||||
"description": "React wrapper around the @rive-app/canvas-lite library",
|
||||
"main": "dist/index.js",
|
||||
"typings": "dist/types/index.d.ts",
|
||||
@@ -18,7 +18,7 @@
|
||||
},
|
||||
"homepage": "https://github.com/rive-app/rive-react#readme",
|
||||
"dependencies": {
|
||||
"@rive-app/canvas-lite": "2.29.0"
|
||||
"@rive-app/canvas-lite": "2.31.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@rive-app/react-canvas",
|
||||
"version": "4.21.1",
|
||||
"version": "4.23.0",
|
||||
"description": "React wrapper around the @rive-app/canvas library",
|
||||
"main": "dist/index.js",
|
||||
"typings": "dist/types/index.d.ts",
|
||||
@@ -18,7 +18,7 @@
|
||||
},
|
||||
"homepage": "https://github.com/rive-app/rive-react#readme",
|
||||
"dependencies": {
|
||||
"@rive-app/canvas": "2.29.0"
|
||||
"@rive-app/canvas": "2.31.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@rive-app/react-webgl",
|
||||
"version": "4.21.1",
|
||||
"version": "4.23.0",
|
||||
"description": "React wrapper around the @rive-app/webgl library",
|
||||
"main": "dist/index.js",
|
||||
"typings": "dist/types/index.d.ts",
|
||||
@@ -18,7 +18,7 @@
|
||||
},
|
||||
"homepage": "https://github.com/rive-app/rive-react#readme",
|
||||
"dependencies": {
|
||||
"@rive-app/webgl": "2.29.0"
|
||||
"@rive-app/webgl": "2.31.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@rive-app/react-webgl2",
|
||||
"version": "4.21.1",
|
||||
"version": "4.23.0",
|
||||
"description": "React wrapper around the @rive-app/webgl2 library",
|
||||
"main": "dist/index.js",
|
||||
"typings": "dist/types/index.d.ts",
|
||||
@@ -18,7 +18,7 @@
|
||||
},
|
||||
"homepage": "https://github.com/rive-app/rive-react#readme",
|
||||
"dependencies": {
|
||||
"@rive-app/webgl2": "2.29.0"
|
||||
"@rive-app/webgl2": "2.31.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0"
|
||||
|
||||
11
package.json
11
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "rive-react",
|
||||
"version": "4.21.1",
|
||||
"version": "4.23.0",
|
||||
"description": "React wrapper around the rive-js library",
|
||||
"main": "dist/index.js",
|
||||
"typings": "dist/types/index.d.ts",
|
||||
@@ -35,10 +35,10 @@
|
||||
},
|
||||
"homepage": "https://github.com/rive-app/rive-react#readme",
|
||||
"dependencies": {
|
||||
"@rive-app/canvas": "2.29.0",
|
||||
"@rive-app/canvas-lite": "2.29.0",
|
||||
"@rive-app/webgl": "2.29.0",
|
||||
"@rive-app/webgl2": "2.29.0"
|
||||
"@rive-app/canvas": "2.31.0",
|
||||
"@rive-app/canvas-lite": "2.31.0",
|
||||
"@rive-app/webgl": "2.31.0",
|
||||
"@rive-app/webgl2": "2.31.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0"
|
||||
@@ -48,6 +48,7 @@
|
||||
"@testing-library/jest-dom": "^5.13.0",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"@types/jest": "^27.0.3",
|
||||
"@types/node": "^18.17.0",
|
||||
"@types/offscreencanvas": "^2019.6.4",
|
||||
"@types/react": "^18.0.0",
|
||||
"@types/testing-library__jest-dom": "^5.9.5",
|
||||
|
||||
@@ -69,6 +69,7 @@ export default function useRive(
|
||||
): RiveState {
|
||||
const [canvasElem, setCanvasElem] = useState<HTMLCanvasElement | null>(null);
|
||||
const containerRef = useRef<HTMLElement | null>(null);
|
||||
const riveRef = useRef<Rive | null>(null);
|
||||
|
||||
const [rive, setRive] = useState<Rive | null>(null);
|
||||
|
||||
@@ -130,13 +131,23 @@ export default function useRive(
|
||||
let r: Rive | null;
|
||||
if (rive == null) {
|
||||
const { useOffscreenRenderer } = options;
|
||||
const { onRiveReady, ...restRiveParams } = riveParams;
|
||||
r = new Rive({
|
||||
useOffscreenRenderer,
|
||||
...riveParams,
|
||||
...restRiveParams,
|
||||
canvas: canvasElem,
|
||||
});
|
||||
if (riveRef.current != null) {
|
||||
riveRef.current!.cleanup();
|
||||
}
|
||||
riveRef.current = r;
|
||||
r.on(EventType.Load, () => {
|
||||
isLoaded = true;
|
||||
|
||||
if (onRiveReady) {
|
||||
onRiveReady(r!);
|
||||
}
|
||||
|
||||
// Check if the component/canvas is mounted before setting state to avoid setState
|
||||
// on an unmounted component in some rare cases
|
||||
if (canvasElem) {
|
||||
@@ -237,6 +248,14 @@ export default function useRive(
|
||||
};
|
||||
}, [rive, canvasElem]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (riveRef.current != null) {
|
||||
riveRef.current!.cleanup();
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Listen for changes in the animations params
|
||||
*/
|
||||
|
||||
35
src/hooks/useViewModelInstanceArtboard.ts
Normal file
35
src/hooks/useViewModelInstanceArtboard.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { useCallback } from 'react';
|
||||
import { ViewModelInstance, ViewModelInstanceArtboard } from '@rive-app/canvas';
|
||||
import { UseViewModelInstanceArtboardResult } from '../types';
|
||||
import { useViewModelInstanceProperty } from './useViewModelInstanceProperty';
|
||||
|
||||
/**
|
||||
* Hook for interacting with artboard properties of a ViewModelInstance.
|
||||
*
|
||||
* @param path - Path to the artboard property (e.g. "targetArtboard" or "group/artboard")
|
||||
* @param viewModelInstance - The ViewModelInstance containing the artboard property
|
||||
* @returns An object with a setter function
|
||||
*/
|
||||
export default function useViewModelInstanceArtboard(
|
||||
path: string,
|
||||
viewModelInstance?: ViewModelInstance | null
|
||||
): UseViewModelInstanceArtboardResult {
|
||||
const result = useViewModelInstanceProperty<ViewModelInstanceArtboard, undefined, UseViewModelInstanceArtboardResult>(
|
||||
path,
|
||||
viewModelInstance,
|
||||
{
|
||||
getProperty: useCallback((vm, p) => vm.artboard(p), []),
|
||||
getValue: useCallback(() => undefined, []), // Artboards properties don't currently have a readable value
|
||||
defaultValue: null,
|
||||
buildPropertyOperations: useCallback((safePropertyAccess) => ({
|
||||
setValue: (newValue) => {
|
||||
safePropertyAccess(prop => { prop.value = newValue; });
|
||||
}
|
||||
}), [])
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
setValue: result.setValue
|
||||
};
|
||||
}
|
||||
35
src/hooks/useViewModelInstanceImage.ts
Normal file
35
src/hooks/useViewModelInstanceImage.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { useCallback } from 'react';
|
||||
import { ViewModelInstance, ViewModelInstanceAssetImage } from '@rive-app/canvas';
|
||||
import { UseViewModelInstanceImageResult, RiveRenderImage } from '../types';
|
||||
import { useViewModelInstanceProperty } from './useViewModelInstanceProperty';
|
||||
|
||||
/**
|
||||
* Hook for interacting with image properties of a ViewModelInstance.
|
||||
*
|
||||
* @param path - Path to the image property (e.g. "profileImage" or "group/avatar")
|
||||
* @param viewModelInstance - The ViewModelInstance containing the image property
|
||||
* @returns An object with a setter function
|
||||
*/
|
||||
export default function useViewModelInstanceImage(
|
||||
path: string,
|
||||
viewModelInstance?: ViewModelInstance | null
|
||||
): UseViewModelInstanceImageResult {
|
||||
const result = useViewModelInstanceProperty<ViewModelInstanceAssetImage, undefined, UseViewModelInstanceImageResult>(
|
||||
path,
|
||||
viewModelInstance,
|
||||
{
|
||||
getProperty: useCallback((vm, p) => vm.image(p), []),
|
||||
getValue: useCallback(() => undefined, []), // Images don't have a readable value
|
||||
defaultValue: null,
|
||||
buildPropertyOperations: useCallback((safePropertyAccess) => ({
|
||||
setValue: (newValue: RiveRenderImage | null) => {
|
||||
safePropertyAccess(prop => { prop.value = newValue; });
|
||||
}
|
||||
}), [])
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
setValue: result.setValue
|
||||
};
|
||||
}
|
||||
75
src/hooks/useViewModelInstanceList.ts
Normal file
75
src/hooks/useViewModelInstanceList.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { ViewModelInstance, ViewModelInstanceList } from '@rive-app/canvas';
|
||||
import { UseViewModelInstanceListResult } from '../types';
|
||||
import { useViewModelInstanceProperty } from './useViewModelInstanceProperty';
|
||||
|
||||
/**
|
||||
* Hook for interacting with list properties of a ViewModelInstance.
|
||||
*
|
||||
* @param path - Path to the property (e.g. "items" or "nested/items")
|
||||
* @param viewModelInstance - The ViewModelInstance containing the list property
|
||||
* @returns An object with the list length and manipulation functions
|
||||
*/
|
||||
export default function useViewModelInstanceList(
|
||||
path: string,
|
||||
viewModelInstance?: ViewModelInstance | null
|
||||
): UseViewModelInstanceListResult {
|
||||
|
||||
// We track revision to trigger re-renders on list manipulation (e.g. addInstance, removeInstance, etc).
|
||||
// This is mostly important for things like the swap function which wouldn't trigger a re-render otherwise because it doesn't change the length of the list.
|
||||
// For example, if the user swaps two items in the list and we don't trigger a re-render, the user will see the old items if they were using the getInstanceAt function.
|
||||
// It also accounts for changes that happen within the Rive file itself rather than through the hook.
|
||||
const [, setRevision] = useState(0);
|
||||
|
||||
const result = useViewModelInstanceProperty<ViewModelInstanceList, number, Omit<UseViewModelInstanceListResult, 'length'>>(
|
||||
path,
|
||||
viewModelInstance,
|
||||
{
|
||||
getProperty: useCallback((vm, p) => vm.list(p), []),
|
||||
getValue: useCallback((prop) => prop.length, []),
|
||||
defaultValue: null,
|
||||
onPropertyEvent: () => {
|
||||
// This fires when the list changes in Rive
|
||||
setRevision(prev => prev + 1);
|
||||
},
|
||||
buildPropertyOperations: useCallback((safePropertyAccess) => ({
|
||||
addInstance: (instance: ViewModelInstance) => {
|
||||
safePropertyAccess(prop => prop.addInstance(instance));
|
||||
},
|
||||
addInstanceAt: (instance: ViewModelInstance, index: number): boolean => {
|
||||
let result = false;
|
||||
safePropertyAccess(prop => {
|
||||
result = prop.addInstanceAt(instance, index);
|
||||
});
|
||||
return result;
|
||||
},
|
||||
removeInstance: (instance: ViewModelInstance) => {
|
||||
safePropertyAccess(prop => prop.removeInstance(instance));
|
||||
},
|
||||
removeInstanceAt: (index: number) => {
|
||||
safePropertyAccess(prop => prop.removeInstanceAt(index));
|
||||
},
|
||||
getInstanceAt: (index: number): ViewModelInstance | null => {
|
||||
let result: ViewModelInstance | null = null;
|
||||
safePropertyAccess(prop => {
|
||||
result = prop.instanceAt(index);
|
||||
});
|
||||
return result;
|
||||
},
|
||||
swap: (a: number, b: number) => {
|
||||
safePropertyAccess(prop => prop.swap(a, b));
|
||||
}
|
||||
}), [])
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
length: result.value ?? 0,
|
||||
addInstance: result.addInstance,
|
||||
addInstanceAt: result.addInstanceAt,
|
||||
removeInstance: result.removeInstance,
|
||||
removeInstanceAt: result.removeInstanceAt,
|
||||
getInstanceAt: result.getInstanceAt,
|
||||
swap: result.swap
|
||||
};
|
||||
}
|
||||
@@ -9,8 +9,11 @@ import useViewModelInstanceBoolean from './hooks/useViewModelInstanceBoolean';
|
||||
import useViewModelInstanceColor from './hooks/useViewModelInstanceColor';
|
||||
import useViewModelInstanceEnum from './hooks/useViewModelInstanceEnum';
|
||||
import useViewModelInstanceTrigger from './hooks/useViewModelInstanceTrigger';
|
||||
import useViewModelInstanceImage from './hooks/useViewModelInstanceImage';
|
||||
import useViewModelInstanceList from './hooks/useViewModelInstanceList';
|
||||
import useResizeCanvas from './hooks/useResizeCanvas';
|
||||
import useRiveFile from './hooks/useRiveFile';
|
||||
import useViewModelInstanceArtboard from './hooks/useViewModelInstanceArtboard';
|
||||
|
||||
export default Rive;
|
||||
export {
|
||||
@@ -26,6 +29,9 @@ export {
|
||||
useViewModelInstanceColor,
|
||||
useViewModelInstanceEnum,
|
||||
useViewModelInstanceTrigger,
|
||||
useViewModelInstanceImage,
|
||||
useViewModelInstanceList,
|
||||
useViewModelInstanceArtboard,
|
||||
RiveProps,
|
||||
};
|
||||
export {
|
||||
|
||||
66
src/types.ts
66
src/types.ts
@@ -1,12 +1,17 @@
|
||||
import {
|
||||
type decodeImage,
|
||||
Rive,
|
||||
RiveFile,
|
||||
RiveFileParameters,
|
||||
RiveParameters,
|
||||
ViewModelInstance,
|
||||
ViewModelInstanceArtboard,
|
||||
} from '@rive-app/canvas';
|
||||
import { ComponentProps, RefCallback } from 'react';
|
||||
|
||||
export type UseRiveParameters = Partial<Omit<RiveParameters, 'canvas'>> | null;
|
||||
export type UseRiveParameters = Partial<Omit<RiveParameters, 'canvas'>> & {
|
||||
onRiveReady?: (rive: Rive) => void;
|
||||
} | null;
|
||||
|
||||
export type UseRiveOptions = {
|
||||
useDevicePixelRatio: boolean;
|
||||
@@ -183,4 +188,63 @@ export type UseViewModelInstanceTriggerResult = {
|
||||
* Fires the property trigger.
|
||||
*/
|
||||
trigger: () => void;
|
||||
};
|
||||
|
||||
export type RiveRenderImage = Awaited<ReturnType<typeof decodeImage>>;
|
||||
|
||||
export type UseViewModelInstanceImageResult = {
|
||||
/**
|
||||
* Set the value of the image.
|
||||
* @param value - The image to set.
|
||||
*/
|
||||
setValue: (value: RiveRenderImage | null) => void;
|
||||
};
|
||||
|
||||
export type UseViewModelInstanceListResult = {
|
||||
/**
|
||||
* The current length of the list.
|
||||
*/
|
||||
length: number;
|
||||
/**
|
||||
* Add an instance to the end of the list.
|
||||
* @param instance - The ViewModelInstance to add.
|
||||
*/
|
||||
addInstance: (instance: ViewModelInstance) => void;
|
||||
/**
|
||||
* Add an instance at a specific index in the list.
|
||||
* @param instance - The ViewModelInstance to add.
|
||||
* @param index - The index to add the instance at.
|
||||
* @returns True if the instance was successfully added, false otherwise.
|
||||
*/
|
||||
addInstanceAt: (instance: ViewModelInstance, index: number) => boolean;
|
||||
/**
|
||||
* Remove an instance from the list.
|
||||
* @param instance - The ViewModelInstance to remove.
|
||||
*/
|
||||
removeInstance: (instance: ViewModelInstance) => void;
|
||||
/**
|
||||
* Remove an instance at a specific index from the list.
|
||||
* @param index - The index to remove the instance from.
|
||||
*/
|
||||
removeInstanceAt: (index: number) => void;
|
||||
/**
|
||||
* Get an instance at a specific index from the list.
|
||||
* @param index - The index to get the instance from.
|
||||
* @returns The ViewModelInstance at the index, or null if not found.
|
||||
*/
|
||||
getInstanceAt: (index: number) => ViewModelInstance | null;
|
||||
/**
|
||||
* Swap two instances in the list.
|
||||
* @param a - The first index.
|
||||
* @param b - The second index.
|
||||
*/
|
||||
swap: (a: number, b: number) => void;
|
||||
};
|
||||
|
||||
export type UseViewModelInstanceArtboardResult = {
|
||||
/**
|
||||
* Set the value of the artboard.
|
||||
* @param value - The artboard to set.
|
||||
*/
|
||||
setValue: (value: ViewModelInstanceArtboard extends { value: infer T } ? T : never) => void;
|
||||
};
|
||||
@@ -1,4 +1,3 @@
|
||||
import { mocked } from 'jest-mock';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
|
||||
import useStateMachineInput from '../src/hooks/useStateMachineInput';
|
||||
@@ -35,6 +34,7 @@ function getRiveMock({
|
||||
const riveMock = new Rive({
|
||||
canvas: undefined as unknown as HTMLCanvasElement,
|
||||
});
|
||||
|
||||
if (smiInputs) {
|
||||
riveMock.stateMachineInputs = jest.fn().mockReturnValue(smiInputs);
|
||||
}
|
||||
@@ -51,8 +51,6 @@ describe('useStateMachineInput', () => {
|
||||
it('returns null if there is no state machine name', () => {
|
||||
const riveMock = getRiveMock();
|
||||
|
||||
mocked(Rive).mockImplementation(() => riveMock);
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useStateMachineInput(riveMock, '', 'testInput')
|
||||
);
|
||||
@@ -71,10 +69,8 @@ describe('useStateMachineInput', () => {
|
||||
it('returns null if there are no inputs for the state machine', () => {
|
||||
const riveMock = getRiveMock({ smiInputs: [] });
|
||||
|
||||
mocked(Rive).mockImplementation(() => riveMock);
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useStateMachineInput(riveMock as Rive, 'smName', '')
|
||||
useStateMachineInput(riveMock, 'smName', '')
|
||||
);
|
||||
expect(result.current).toBeNull();
|
||||
});
|
||||
@@ -85,8 +81,6 @@ describe('useStateMachineInput', () => {
|
||||
} as StateMachineInput;
|
||||
const riveMock = getRiveMock({ smiInputs: [smInput] });
|
||||
|
||||
mocked(Rive).mockImplementation(() => riveMock);
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useStateMachineInput(riveMock, 'smName', 'numInput')
|
||||
);
|
||||
@@ -99,8 +93,6 @@ describe('useStateMachineInput', () => {
|
||||
} as StateMachineInput;
|
||||
const riveMock = getRiveMock({ smiInputs: [smInput] });
|
||||
|
||||
mocked(Rive).mockImplementation(() => riveMock);
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useStateMachineInput(riveMock, 'smName', 'boolInput')
|
||||
);
|
||||
@@ -113,7 +105,6 @@ describe('useStateMachineInput', () => {
|
||||
value: false,
|
||||
} as StateMachineInput;
|
||||
const riveMock = getRiveMock({ smiInputs: [smInput] });
|
||||
mocked(Rive).mockImplementation(() => riveMock);
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useStateMachineInput(riveMock, 'smName', 'boolInput', true)
|
||||
|
||||
Reference in New Issue
Block a user