Compare commits

..

34 Commits

Author SHA1 Message Date
Adam
dab7a8d6f0 Merge branch 'main' into feature/initial-databinding 2025-05-05 18:21:14 -07:00
Adam
d2ccee1220 feat: add db test 2025-05-05 17:32:36 -07:00
Lance
5bf53fc238 Add onTrigger example 2025-05-05 15:14:01 -06:00
Lance
e49b9ae606 Add stocks data binding example 2025-05-01 23:10:49 -06:00
Lance
fc78d38a0b Merge storybook 2025-05-01 22:27:46 -06:00
lancesnider
04397e658c chore: release 4.19.0 2025-05-01 22:27:46 -06:00
Lance
52519885c6 Fix failing tests 2025-05-01 22:27:46 -06:00
Lance
2ad2df085b Upgrade testing 2025-05-01 22:27:46 -06:00
Lance
c9308db9dc Upgrade React and React DOM 2025-05-01 22:27:46 -06:00
Lance
082b9a6323 Update contributing docs 2025-05-01 22:27:46 -06:00
Lance
6aaee36ff0 Add storybook linter 2025-05-01 22:27:46 -06:00
Lance
b7581e5b25 Call storybook from parent 2025-05-01 22:27:46 -06:00
Lance
4879b95245 General cleanup 2025-05-01 22:27:46 -06:00
Lance
4407dec4fc Remove unnecessary react app stuff 2025-05-01 22:27:46 -06:00
Lance
b73c3f58b2 Storybookk page reloads when the package in the parent changes 2025-05-01 22:27:46 -06:00
Lance
cee7ca5a43 Add examples 2025-05-01 22:27:46 -06:00
Lance
cf1d7c4c1c Remove old storybook and auto docs 2025-05-01 22:27:46 -06:00
philter
c414f143fd chore: release 4.18.9 2025-05-01 22:27:46 -06:00
Phil Chung
1f8afe4635 chore: bump rive web to 2.27.1 2025-05-01 22:27:46 -06:00
bodymovin
469e9073ce chore: release 4.18.8 2025-05-01 22:27:46 -06:00
Hernan Torrisi
43290d1cfd rive canvas 2.27.0 2025-05-01 22:27:46 -06:00
Adam
37763e11c7 fix: use default view model if none provide in useViewModelInstance and useViewModel 2025-05-01 15:58:37 -07:00
Adam
384b62c1b7 fix: unify effects in useViewModelInstance 2025-04-29 17:18:34 -07:00
Adam
d9e61373b3 fix: change useViewModel property hook parameters to match conventions 2025-04-29 17:07:17 -07:00
Adam
66f1ae021c chore: simplify useViewModel 2025-04-29 16:30:09 -07:00
Adam
1fc4fa44e2 fix: avoid rebuilding operations unless necessary 2025-04-28 18:04:19 -07:00
Adam
3866cb9e06 fix: hot reload crash 2025-04-24 15:33:07 -07:00
Adam
bef03fc403 chore: update UseViewModelParameters documentation 2025-04-15 16:46:54 -07:00
Adam
c4da27c7b6 fix: remove viewmodel hooks from initial release 2025-04-15 15:18:38 -07:00
Adam
c2ec32350d chore: update color setter methods to be more explicit 2025-04-15 12:07:27 -07:00
Adam
ead1d8e0e3 fix: lint issue 2025-04-14 14:48:15 -07:00
Adam
26cb0b5dec feat: add instance property hooks 2025-04-13 12:38:27 -07:00
Hernan Torrisi
0e40840e6f hook with generic 2025-04-11 10:19:47 -07:00
Hernan Torrisi
480037c399 inital work for data binding hooks 2025-04-11 10:19:47 -07:00
24 changed files with 580 additions and 1515 deletions

View File

@@ -10,29 +10,23 @@ on:
description: 'Minor'
type: boolean
default: false
permissions:
id-token: write # Required for OIDC
contents: read
jobs:
publish_job:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v2
with:
fetch-depth: 0
token: ${{ secrets.PAT_GITHUB }}
- name: Setup Git config
run: |
git config --local user.email 'hello@rive.app'
git config --local user.name ${{ github.actor }}
- uses: actions/setup-node@v4
- name: Authenticate with registry
run: npm config set //registry.npmjs.org/:_authToken ${{ secrets.NPM_TOKEN }}
- uses: actions/setup-node@v2
with:
node-version: '24'
node-version: '16.x'
registry-url: 'https://registry.npmjs.org'
- name: Upgrade npm for OIDC support
run: npm install -g npm@latest
- name: Install Modules
run: npm install
- name: Run type check
@@ -44,9 +38,21 @@ jobs:
- if: ${{ inputs.major == true }}
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 }}

30
.github/workflows/storybook.yml vendored Normal file
View File

@@ -0,0 +1,30 @@
name: Deploy Storybook
on:
# Testing to see if this job is causing the race condition
workflow_dispatch:
# pull_request:
# types: [closed]
# branches:
# - main
# paths: ['src', 'examples/stories/**'] # Trigger the action only when files change in the folders defined here
jobs:
build-and-deploy:
runs-on: ubuntu-latest
if: github.event.pull_request.merged == true
steps:
- name: Checkout 🛎️
uses: actions/checkout@v2
with:
persist-credentials: false
- name: Install and Build 🔧
run: | # Install npm packages and build the Storybook files
npm install
npm run build-storybook
- name: Deploy 🚀
uses: JamesIves/github-pages-deploy-action@3.6.2
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BRANCH: main # The branch the action should deploy to.
FOLDER: docs-build # The folder that the build-storybook script generates files.
CLEAN: true # Automatically remove deleted files from the deploy branch
TARGET_FOLDER: docs # The folder that we serve our Storybook files from

View File

@@ -7,7 +7,7 @@
"changelog": "npx auto-changelog --stdout --commit-limit false --unreleased --template https://raw.githubusercontent.com/release-it/release-it/master/templates/changelog-compact.hbs"
},
"npm": {
"publish": false
"publish": true
},
"github": {
"release": true,

View File

@@ -4,211 +4,8 @@ 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.27.1](https://github.com/rive-app/rive-react/compare/v4.27.0...v4.27.1)
- chore: bump js runtime to 2.35.2 [`8819832`](https://github.com/rive-app/rive-react/commit/881983229f699de7da516a05cda415158c1517d2)
- Update supported React versions to include 19.0.0 [`1abd420`](https://github.com/rive-app/rive-react/commit/1abd420408b4c06ae172d1f060c2461e7226a3d3)
#### [v4.27.0](https://github.com/rive-app/rive-react/compare/v4.26.2...v4.27.0)
> 13 February 2026
- chore: release 4.27.0 [`e7217e5`](https://github.com/rive-app/rive-react/commit/e7217e5943afa7ab5405d0301465df20ab135c7a)
- rive canvas 2.35.0 [`e4738c0`](https://github.com/rive-app/rive-react/commit/e4738c014de2fa707c3950d2b3b7011bde4624e0)
#### [v4.26.2](https://github.com/rive-app/rive-react/compare/v4.26.1...v4.26.2)
> 27 January 2026
- chore: release 4.26.2 [`e1055c2`](https://github.com/rive-app/rive-react/commit/e1055c2907466a5857ef8880916db30d896f4275)
- bump rive to 2.34.2 [`3c1459b`](https://github.com/rive-app/rive-react/commit/3c1459beb31f68d8a77d0ead405f0c4726cbeb43)
#### [v4.26.1](https://github.com/rive-app/rive-react/compare/v4.26.0...v4.26.1)
> 15 January 2026
- chore: release 4.26.1 [`3c59c7a`](https://github.com/rive-app/rive-react/commit/3c59c7a66736bf1955f38a98e351687df7bd1494)
- update rive_canvas [`2dceda5`](https://github.com/rive-app/rive-react/commit/2dceda58bfc57830fb181796df321789727973cc)
#### [v4.26.0](https://github.com/rive-app/rive-react/compare/v4.25.3...v4.26.0)
> 13 January 2026
- chore: release 4.26.0 [`1af00a3`](https://github.com/rive-app/rive-react/commit/1af00a38c7973141bccca08845e396c59a88c0ba)
- chore: bump to 2.34.0 [`6ab0496`](https://github.com/rive-app/rive-react/commit/6ab0496025739f2d6c78ed84ada0488d6dd58ab2)
#### [v4.25.3](https://github.com/rive-app/rive-react/compare/v4.25.2...v4.25.3)
> 8 January 2026
- chore: release 4.25.3 [`37ced47`](https://github.com/rive-app/rive-react/commit/37ced4732343d6c6076bd018b6e179615df32003)
- update rive to 2.33.3 [`79f1a06`](https://github.com/rive-app/rive-react/commit/79f1a06df09ec95f9bacdc260e7fcabcfb13975e)
#### [v4.25.2](https://github.com/rive-app/rive-react/compare/v4.25.1...v4.25.2)
> 6 January 2026
- chore: release 4.25.2 [`f0d4c3e`](https://github.com/rive-app/rive-react/commit/f0d4c3e6d250527b4cbbda2d0fdbe05058cd1772)
- Bump rive wasm to 2.33.2 [`0d3300e`](https://github.com/rive-app/rive-react/commit/0d3300eca435ca3862cd63ce795dd5f5b561a1a8)
#### [v4.25.1](https://github.com/rive-app/rive-react/compare/v4.25.0...v4.25.1)
> 20 December 2025
- chore: release 4.25.1 [`7f6ab5a`](https://github.com/rive-app/rive-react/commit/7f6ab5ad10a996503e590f362c17cd96ee69ac5d)
- Bump rive wasm to 2.33.1 [`5df9332`](https://github.com/rive-app/rive-react/commit/5df93323eda2e08b64d375e39e57a22f473aab1e)
#### [v4.25.0](https://github.com/rive-app/rive-react/compare/v4.24.0...v4.25.0)
> 17 December 2025
- chore: release 4.25.0 [`e6bfec7`](https://github.com/rive-app/rive-react/commit/e6bfec7c517c5fe001c40476de1bd0d48dd77fd7)
- chore: update npm publish workflow with OIDC support [`925de2f`](https://github.com/rive-app/rive-react/commit/925de2fc94172a306f194f45b628f711fff14d50)
- chore: bump rive web 2.33.0 [`f74cfbc`](https://github.com/rive-app/rive-react/commit/f74cfbc3d04383aeb9707a378d4056be400567c3)
#### [v4.24.0](https://github.com/rive-app/rive-react/compare/v4.23.4...v4.24.0)
> 10 November 2025
- chore: release 4.24.0 [`19f2026`](https://github.com/rive-app/rive-react/commit/19f20268ae1c39d873f9d64cf10a499a4f792970)
- chore: bump rive web 2.32.0 [`e6e4356`](https://github.com/rive-app/rive-react/commit/e6e43564b1af8da608c146a2a76795e29063daf8)
#### [v4.23.4](https://github.com/rive-app/rive-react/compare/v4.23.3...v4.23.4)
> 23 September 2025
- chore: release 4.23.4 [`efeee47`](https://github.com/rive-app/rive-react/commit/efeee472e60946d293f6287e11350fdaf4afe03b)
- bump rive to 2.31.6 [`42d502f`](https://github.com/rive-app/rive-react/commit/42d502f452b6a76cafc332c2cd84bd97e315be77)
#### [v4.23.3](https://github.com/rive-app/rive-react/compare/v4.23.2...v4.23.3)
> 4 September 2025
- chore: release 4.23.3 [`bd483e0`](https://github.com/rive-app/rive-react/commit/bd483e0ab72a99a9c55c7e9fb80bd95827ca54cb)
- chore: bump rive wasm to 2.31.5 [`7c361e4`](https://github.com/rive-app/rive-react/commit/7c361e4c538cf813fdc94f572fac1e4a82258ae8)
#### [v4.23.2](https://github.com/rive-app/rive-react/compare/v4.23.1...v4.23.2)
> 4 September 2025
- chore: release 4.23.2 [`d80df17`](https://github.com/rive-app/rive-react/commit/d80df170aa0d1c575101c8cd1bb76968173c439f)
- docs: update README [`f732a3b`](https://github.com/rive-app/rive-react/commit/f732a3b044e2a56ed0ea178a43d68612423c0548)
- chore: bump rive wasm to 2.31.4 [`a3118d5`](https://github.com/rive-app/rive-react/commit/a3118d59841c45467b46170e3eed7ba3359d4fea)
#### [v4.23.1](https://github.com/rive-app/rive-react/compare/v4.23.0...v4.23.1)
> 13 August 2025
- chore: release 4.23.1 [`57ebc37`](https://github.com/rive-app/rive-react/commit/57ebc37e3f99eb7fd9673e34441f395c990e312b)
- bump rive to 2.31.2 [`69a3568`](https://github.com/rive-app/rive-react/commit/69a356894d3acf44f4d24b708e9f8d8dda5b3046)
- bump rive canvas to 2.31.1 [`788b7ef`](https://github.com/rive-app/rive-react/commit/788b7ef68e9001460175b596f74d7f54616a69d4)
#### [v4.23.0](https://github.com/rive-app/rive-react/compare/v4.22.1...v4.23.0)
> 8 August 2025
- chore: release 4.23.0 [`69658c2`](https://github.com/rive-app/rive-react/commit/69658c204ad1f70a408bab098136c2b23083fd16)
- 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)
> 23 May 2025
- chore: release 4.21.0 [`b26280a`](https://github.com/rive-app/rive-react/commit/b26280ae125173b52dd9a6147833f45631c2252f)
- chore: bump rive wasm 2.28.0 [`a62e89d`](https://github.com/rive-app/rive-react/commit/a62e89de9436360e439896a1aa11623e3574897e)
#### [v4.20.2](https://github.com/rive-app/rive-react/compare/v4.20.1...v4.20.2)
> 23 May 2025
- chore: release 4.20.2 [`31255f9`](https://github.com/rive-app/rive-react/commit/31255f974635278aea211dcf827e3a0cd0cc138e)
- chore: bump rive wasm 2.27.5 [`3e76853`](https://github.com/rive-app/rive-react/commit/3e768533df747da69acd392332495303077fa8c6)
#### [v4.20.1](https://github.com/rive-app/rive-react/compare/v4.20.0...v4.20.1)
> 14 May 2025
- chore: release 4.20.1 [`c790e66`](https://github.com/rive-app/rive-react/commit/c790e6672389ea68ee222140a49bcb7e4a7d3ca3)
- rive canvas 2.27.3 [`ab89793`](https://github.com/rive-app/rive-react/commit/ab89793032bcadf58f680610cea2e15fcd76d0b2)
#### [v4.20.0](https://github.com/rive-app/rive-react/compare/v4.19.1...v4.20.0)
> 12 May 2025
- feat: add db test [`46e1987`](https://github.com/rive-app/rive-react/commit/46e19874a2ec5893b5d3365f61db871400327087)
- fix: hot reload crash [`6d76e9f`](https://github.com/rive-app/rive-react/commit/6d76e9f85d949ec1e0e4d29458676efbe1c24d1d)
- inital work for data binding hooks [`452eb89`](https://github.com/rive-app/rive-react/commit/452eb89e72ffb73f837917fd969a51ed238a6d05)
#### [v4.19.1](https://github.com/rive-app/rive-react/compare/v4.19.0...v4.19.1)
> 8 May 2025
- chore: release 4.19.1 [`d303e8c`](https://github.com/rive-app/rive-react/commit/d303e8c96f70fa8886d96aba35afd911f0fcef50)
- chore: rive-wasm -> 2.27.2 [`479d534`](https://github.com/rive-app/rive-react/commit/479d5340e87f5335a2525b547e690be60ebafc00)
#### [v4.19.0](https://github.com/rive-app/rive-react/compare/v4.18.9...v4.19.0)
> 29 April 2025
- Add examples [`5354d1f`](https://github.com/rive-app/rive-react/commit/5354d1f69bfe91dc67c39cee80a6ea00c4c70cb1)
- Storybookk page reloads when the package in the parent changes [`7277ed2`](https://github.com/rive-app/rive-react/commit/7277ed2f0d877150637a69f2ff9122db1b151686)
- Upgrade React and React DOM [`a9a98fe`](https://github.com/rive-app/rive-react/commit/a9a98fece2caf727e941ce645be2f031efaf8a89)

View File

@@ -1,4 +1,5 @@
![Build Status](https://github.com/rive-app/rive-react/actions/workflows/tests.yml/badge.svg)
[![Storybook](https://cdn.jsdelivr.net/gh/storybookjs/brand@main/badge/badge-storybook.svg)](https://rive-app.github.io/rive-react)
![Discord badge](https://img.shields.io/discord/532365473602600965)
![Twitter handle](https://img.shields.io/twitter/follow/rive_app.svg?style=social&label=Follow)
@@ -6,16 +7,9 @@
![Rive hero image](https://cdn.rive.app/rive_logo_dark_bg.png)
[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.
A React runtime library for [Rive](https://rive.app).
This library is a wrapper around the [JS/Wasm runtime](https://github.com/rive-app/rive-wasm), giving full control over the JS/Wasm 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/)
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.
## Table of contents
@@ -55,12 +49,14 @@ For more information, see the Runtime sections of the Rive help documentation:
## Supported versions
This library supports React versions `^16.8.0` through `^19.0.0`.
This library supports React versions `^16.8.0` through `^18.0.0`.
## Examples
Check out our Storybook instance that shows how to use the library in small examples, along with code snippets! This includes examples using the basic component, as well as the convenient hooks exported to take advantage of state machines.
- [Example page](https://rive-app.github.io/rive-react)
- [Login screen w/ input tracking](https://rive-app.github.io/rive-use-cases/?path=/story/example-loginformcomponent--primary)
- [Mouse tracking](https://codesandbox.io/s/rive-mouse-track-test-t0y965?file=/src/App.js)
- [Accessibility concerns](https://rive.app/blog/accesible-web-animations-aria-live-regions)

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

@@ -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, ImagePropertyTest, TodoListTest, ArtboardPropertyTest } from './DataBindingTests';
import { StringPropertyTest, NumberPropertyTest, BooleanPropertyTest, ColorPropertyTest, EnumPropertyTest, NestedViewModelTest, TriggerPropertyTest, PersonForm, PersonInstances } from './DataBindingTests';
const meta: Meta = {
title: 'Tests/DataBinding',
@@ -345,183 +345,4 @@ 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');
});
}
};

View File

@@ -9,12 +9,7 @@ import Rive, {
useViewModelInstanceNumber,
useViewModelInstanceEnum,
useViewModelInstanceColor,
useViewModelInstanceTrigger,
useViewModelInstanceImage,
decodeImage,
ViewModelInstance,
useViewModelInstanceList,
useViewModelInstanceArtboard
useViewModelInstanceTrigger
} from '@rive-app/react-webgl2';
@@ -527,355 +522,3 @@ 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>
);
};

View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "@rive-app/react-canvas-lite",
"version": "4.27.1",
"version": "4.19.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.35.2"
"@rive-app/canvas-lite": "2.27.1"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0"

View File

@@ -1,6 +1,6 @@
{
"name": "@rive-app/react-canvas",
"version": "4.27.1",
"version": "4.19.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.35.2"
"@rive-app/canvas": "2.27.1"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0"

View File

@@ -1,6 +1,6 @@
{
"name": "@rive-app/react-webgl",
"version": "4.27.1",
"version": "4.19.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.35.2"
"@rive-app/webgl": "2.27.1"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0"

View File

@@ -1,6 +1,6 @@
{
"name": "@rive-app/react-webgl2",
"version": "4.27.1",
"version": "4.19.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.35.2"
"@rive-app/webgl2": "2.27.1"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0"

View File

@@ -1,6 +1,6 @@
{
"name": "rive-react",
"version": "4.27.1",
"version": "4.19.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.35.2",
"@rive-app/canvas-lite": "2.35.2",
"@rive-app/webgl": "2.35.2",
"@rive-app/webgl2": "2.35.2"
"@rive-app/canvas": "2.27.1",
"@rive-app/canvas-lite": "2.27.1",
"@rive-app/webgl": "2.27.1",
"@rive-app/webgl2": "2.27.1"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0"
@@ -48,7 +48,6 @@
"@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",

View File

@@ -69,7 +69,6 @@ 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);
@@ -131,23 +130,13 @@ export default function useRive(
let r: Rive | null;
if (rive == null) {
const { useOffscreenRenderer } = options;
const { onRiveReady, ...restRiveParams } = riveParams;
r = new Rive({
useOffscreenRenderer,
...restRiveParams,
...riveParams,
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) {
@@ -248,14 +237,6 @@ export default function useRive(
};
}, [rive, canvasElem]);
useEffect(() => {
return () => {
if (riveRef.current != null) {
riveRef.current!.cleanup();
}
};
}, []);
/**
* Listen for changes in the animations params
*/

View File

@@ -1,7 +1,26 @@
import { useState, useEffect } from 'react';
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.
*
@@ -16,37 +35,57 @@ export default function useViewModel(
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() {
if (!rive) {
const currentRive = riveRef.current;
const currentParams = paramsRef.current;
if (!currentRive) {
setViewModel(null);
return;
}
let model: ViewModel | null = null;
if (name != null) {
model = rive.viewModelByName?.(name) || null;
} else if (useDefault) {
model = rive.defaultViewModel() || null;
if (currentParams?.name != null) {
model = currentRive.viewModelByName?.(currentParams.name) || null;
} else if (currentParams?.useDefault) {
model = currentRive.defaultViewModel() || null;
} else {
model = rive.defaultViewModel() || null;
model = currentRive.defaultViewModel() || null;
}
setViewModel(model);
shouldUpdate.current = false;
}
fetchViewModel();
if (rive) {
rive.on(EventType.Load, fetchViewModel);
const currentRive = riveRef.current;
if (currentRive) {
currentRive.on(EventType.Load, fetchViewModel);
}
return () => {
if (rive) {
rive.off(EventType.Load, fetchViewModel);
if (currentRive) {
currentRive.off(EventType.Load, fetchViewModel);
}
};
}, [rive, name, useDefault]);

View File

@@ -1,7 +1,30 @@
import { useState, useEffect } from 'react';
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.
*
@@ -20,26 +43,50 @@ export default function useViewModelInstance(
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(() => {
if (!viewModel) {
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 (name != null) {
result = viewModel.instanceByName(name) || null;
} else if (useDefault) {
result = viewModel.defaultInstance?.() || null;
} else if (useNew) {
result = viewModel.instance?.() || 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 = viewModel.defaultInstance?.() || null;
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);
}

View File

@@ -1,35 +0,0 @@
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
};
}

View File

@@ -1,35 +0,0 @@
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
};
}

View File

@@ -1,75 +0,0 @@
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
};
}

View File

@@ -9,11 +9,8 @@ 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 {
@@ -29,9 +26,6 @@ export {
useViewModelInstanceColor,
useViewModelInstanceEnum,
useViewModelInstanceTrigger,
useViewModelInstanceImage,
useViewModelInstanceList,
useViewModelInstanceArtboard,
RiveProps,
};
export {

View File

@@ -1,17 +1,12 @@
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'>> & {
onRiveReady?: (rive: Rive) => void;
} | null;
export type UseRiveParameters = Partial<Omit<RiveParameters, 'canvas'>> | null;
export type UseRiveOptions = {
useDevicePixelRatio: boolean;
@@ -188,63 +183,4 @@ 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;
};