From 78700c8daf64cc7b4e4633fdbd021c835ede9d8b Mon Sep 17 00:00:00 2001 From: Zachary Plata Date: Sat, 17 Oct 2026 15:20:01 -0500 Subject: [PATCH 1/2] feat: Add enableInkbleed param from JS runtime --- examples/public/tiger.riv | Bin 0 -> 73729 bytes examples/src/components/Inkbleed.stories.ts | 17 +++ examples/src/components/Inkbleed.tsx | 141 ++++++++++++++++++ package-lock.json | 24 +-- package.json | 6 +- src/components/Rive.tsx | 19 ++- src/hooks/useRive.tsx | 21 ++- src/types.ts | 5 +- test/useRive.test.tsx | 157 ++++++++++++++++++++ test/useRiveGpuCanvas.test.tsx | 121 --------------- 10 files changed, 372 insertions(+), 139 deletions(-) create mode 100644 examples/public/tiger.riv create mode 100644 examples/src/components/Inkbleed.stories.ts create mode 100644 examples/src/components/Inkbleed.tsx delete mode 100644 test/useRiveGpuCanvas.test.tsx diff --git a/examples/public/tiger.riv b/examples/public/tiger.riv new file mode 100644 index 0000000000000000000000000000000000000000..5751a86a9e4e284bc382a9303160500da35a2527 GIT binary patch literal 73729 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 + +export default meta; +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/examples/src/components/Inkbleed.tsx b/examples/src/components/Inkbleed.tsx new file mode 100644 index 00000000..a4c28fe4 --- /dev/null +++ b/examples/src/components/Inkbleed.tsx @@ -0,0 +1,141 @@ +import React, { useState } from 'react'; +import { useRive, Fit, Alignment, Layout } from '@rive-app/react-webgl2'; +import type { UseRiveParameters, UseRiveOptions } from '@rive-app/react-webgl2'; + +/** + * Inkbleed — experimental 1x analytic antialiasing (webgl2 only) + */ + +const CARD_SIZE = 380; +const LAYOUT = new Layout({ fit: Fit.Contain, alignment: Alignment.Center }); + +interface CardProps { + title: string; + expectInkbleed: boolean; + params: UseRiveParameters; + opts?: Partial; +} + +const Card = ({ title, expectInkbleed, params, opts }: CardProps) => { + const { rive, RiveComponent } = useRive(params, opts); + // rive.inkbleed isn't React state, so re-render after toggling it. + const [, rerender] = useState(0); + + let label = 'loading…'; + let color: string = COLORS.textMuted; + + if (rive) { + const on = rive.inkbleed; + const ok = on === expectInkbleed; + label = `inkbleed ${on ? 'on' : 'off'} ${ + ok ? '✓' : `✗ expected ${expectInkbleed ? 'on' : 'off'}` + }`; + color = ok ? COLORS.pass : COLORS.fail; + } + + return ( +
+

{title}

+
+ +
+
+ {label} + {params?.enableInkbleed && ( + + )} +
+
+ ); +}; + +const params = (extra?: Partial): UseRiveParameters => ({ + src: 'tiger.riv', + autoplay: true, + layout: LAYOUT, + ...extra, +}); + +const Inkbleed = () => ( +
+
+

Inkbleed

+

+ Inkbleed replaces MSAA with 1x analytic antialiasing. Everything bleeds + out half a pixel, which is most visible on thin strokes like the + whiskers. Fills that don't use the clockwise fill rule are converted to + clockwise on a best-effort basis. +

+ +
+
+ +
+
+
+
+); + +const COLORS = { + bg: '#1a1a1a', + panel: '#252525', + border: '#3a3a3a', + text: '#e0e0e0', + textMuted: '#999', + pass: '#4caf50', + fail: '#ff4d40', +}; + +const styles: Record = { + page: { + background: COLORS.bg, + color: COLORS.text, + fontFamily: 'system-ui, sans-serif', + minHeight: '100vh', + boxSizing: 'border-box', + padding: 24, + }, + inner: { width: 'min(92%, 1240px)', margin: '0 auto' }, + h1: { margin: '0 0 6px', fontSize: 22, fontWeight: 600 }, + intro: { + color: COLORS.textMuted, + fontSize: 14, + lineHeight: 1.6, + margin: '0 0 20px', + }, + section: { marginBottom: 28 }, + grid: { display: 'flex', flexWrap: 'wrap', gap: 16 }, + card: { + background: COLORS.panel, + border: `1px solid ${COLORS.border}`, + borderRadius: 8, + padding: 12, + }, + cardTitle: { fontSize: 13, fontWeight: 600, margin: '0 0 8px' }, + footer: { + marginTop: 8, + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + }, + status: { fontSize: 13, fontWeight: 600 }, + button: { fontSize: 12, padding: '2px 10px' }, +}; + +export default Inkbleed; diff --git a/package-lock.json b/package-lock.json index 3c71b560..1b9c9bd6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,9 +9,9 @@ "version": "4.36.0", "license": "MIT", "dependencies": { - "@rive-app/canvas": "2.44.0", - "@rive-app/canvas-lite": "2.44.0", - "@rive-app/webgl2": "2.44.0" + "@rive-app/canvas": "2.45.0", + "@rive-app/canvas-lite": "2.45.0", + "@rive-app/webgl2": "2.45.0" }, "devDependencies": { "@babel/core": "^7.18.0", @@ -1357,21 +1357,21 @@ } }, "node_modules/@rive-app/canvas": { - "version": "2.44.0", - "resolved": "https://registry.npmjs.org/@rive-app/canvas/-/canvas-2.44.0.tgz", - "integrity": "sha512-ak0GGwCbuWubi6rP9qB5YPy5CxYuTY3AiuTV4i1Vi7GXhuK2XB+M+4WJMuGcYGfGLzojaVMKaxtunD5OjjGzOQ==", + "version": "2.45.0", + "resolved": "https://registry.npmjs.org/@rive-app/canvas/-/canvas-2.45.0.tgz", + "integrity": "sha512-GnfJI75Z990maZC7NtXUzuZG2C66j/qIQvFmFAUDS+fPy8k4genTPTrCSgpVpGQDuedFhhmVcPtRZks5lAPf3w==", "license": "MIT" }, "node_modules/@rive-app/canvas-lite": { - "version": "2.44.0", - "resolved": "https://registry.npmjs.org/@rive-app/canvas-lite/-/canvas-lite-2.44.0.tgz", - "integrity": "sha512-ywsdoYLsC+8RIZ+aMCvHQQgGRxnBHojReaOxDqI5ZL2Y/nRKdySAbLFnSrJIAbGygdkuQdJVUNF3AF01mxGhEA==", + "version": "2.45.0", + "resolved": "https://registry.npmjs.org/@rive-app/canvas-lite/-/canvas-lite-2.45.0.tgz", + "integrity": "sha512-zgJ2kGpEV32cMONDoOo3rUZmgxG8+Yo8k+4ro3lhtppsJFmYVSyyl8SIg21DWTY43cvS04oFtyboX3SX/+MnKg==", "license": "MIT" }, "node_modules/@rive-app/webgl2": { - "version": "2.44.0", - "resolved": "https://registry.npmjs.org/@rive-app/webgl2/-/webgl2-2.44.0.tgz", - "integrity": "sha512-BIlutEsgyPRwMLEat2dDVBannAqIH75TeCCyrYA4KjUMMM+2bFJ2qfwqJACUAsUE+EYdcQaONycfTFRQ4JGLqg==", + "version": "2.45.0", + "resolved": "https://registry.npmjs.org/@rive-app/webgl2/-/webgl2-2.45.0.tgz", + "integrity": "sha512-uoPFjIeKMISabc9lPE5rXMEWCsnh3IZ4NErwmD0ag8wvM+8zDklcgr7P5C6u7ULnfzH5MkCw/hi00Kg1HhJe9g==", "license": "MIT" }, "node_modules/@rollup/plugin-commonjs": { diff --git a/package.json b/package.json index fb4d3aed..3ce8d545 100644 --- a/package.json +++ b/package.json @@ -35,9 +35,9 @@ }, "homepage": "https://github.com/rive-app/rive-react#readme", "dependencies": { - "@rive-app/canvas": "2.44.0", - "@rive-app/canvas-lite": "2.44.0", - "@rive-app/webgl2": "2.44.0" + "@rive-app/canvas": "2.45.0", + "@rive-app/canvas-lite": "2.45.0", + "@rive-app/webgl2": "2.45.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0" diff --git a/src/components/Rive.tsx b/src/components/Rive.tsx index 6848ab9d..3cf0ed08 100644 --- a/src/components/Rive.tsx +++ b/src/components/Rive.tsx @@ -46,9 +46,12 @@ export interface RiveProps { * - `true` normally, so multiple Rive graphics on a page share one context. * - `false` when `enableGPUCanvas` is on, because GPU Canvas needs a context * of its own for each ``. + * - `false` when `enableInkbleed` is on, because inkbleed needs a context of + * its own for each ``. * * Setting it explicitly always wins. Setting it to `true` alongside - * `enableGPUCanvas` means GPU Canvas content will not draw. + * `enableGPUCanvas` means GPU Canvas content will not draw, and alongside + * `enableInkbleed` means inkbleed stays off. */ useOffscreenRenderer?: boolean; /** @@ -66,6 +69,18 @@ export interface RiveProps { * have (the limit varies by browser), so enabling it on too many at once can throw. */ enableGPUCanvas?: boolean; + /** + * @experimental (WebGL2 only) Renders with inkbleed antialiasing instead + * of MSAA. Fills that don't use the clockwise fill rule are converted to clockwise on a + * best-effort basis. False by default. + * + * IMPORTANT: Turning this on makes `useOffscreenRenderer` default to `false` for this + * component if it was not specified, since inkbleed needs a WebGL context of its own. + * + * Each of these takes its own WebGL context, and browsers cap how many a page may + * have (the limit varies by browser), so enabling it on too many at once can throw. + */ + enableInkbleed?: boolean; /** * Specify whether to disable Rive listeners on the canvas, thus preventing any event listeners to be attached to the canvas element */ @@ -104,6 +119,7 @@ const Rive = ({ // undefined, it falls through to defaultOptions — which will still be `true`. useOffscreenRenderer, enableGPUCanvas, + enableInkbleed, shouldDisableRiveListeners = false, shouldResizeCanvasToContainer = true, automaticallyHandleEvents = false, @@ -121,6 +137,7 @@ const Rive = ({ shouldDisableRiveListeners, automaticallyHandleEvents, enableGPUCanvas, + enableInkbleed, }; const options = { diff --git a/src/hooks/useRive.tsx b/src/hooks/useRive.tsx index 78cdd295..db568636 100644 --- a/src/hooks/useRive.tsx +++ b/src/hooks/useRive.tsx @@ -87,6 +87,9 @@ export default function useRive( * is React runtime's (see `defaultOptions`). So opting into GPU Canvas only has * to unwind React's default — an explicit value from the caller still wins, and * still loses GPU Canvas, which the effect below warns about. + * + * Inkbleed is the same: it needs the canvas's own context, created with + * depth/stencil and without MSAA, which the shared offscreen context isn't. */ const explicitOffscreenRenderer = riveParams?.useOffscreenRenderer ?? opts.useOffscreenRenderer; @@ -99,9 +102,13 @@ export default function useRive( Boolean(riveParams?.enableGPUCanvas) || Boolean(riveParams?.riveFile?.deferredRequested); + const wantsInkbleed = Boolean(riveParams?.enableInkbleed); + const useOffscreenRenderer = explicitOffscreenRenderer ?? - (wantsGPUCanvas ? false : defaultOptions.useOffscreenRenderer); + (wantsGPUCanvas || wantsInkbleed + ? false + : defaultOptions.useOffscreenRenderer); // The runtime warns about this too, but from where it sits it cannot say that // a hook option is what turned the offscreen renderer on. @@ -117,6 +124,18 @@ export default function useRive( } }, [wantsGPUCanvas, useOffscreenRenderer]); + useEffect(() => { + if (wantsInkbleed && useOffscreenRenderer) { + console.warn( + '[Rive] `enableInkbleed` and `useOffscreenRenderer` cannot both be on. ' + + 'Inkbleed needs a WebGL context of its own, while the offscreen renderer ' + + 'shares one context across every on the page. This instance ' + + 'renders without inkbleed — drop the explicit `useOffscreenRenderer: true` ' + + 'to use it.' + ); + } + }, [wantsInkbleed, useOffscreenRenderer]); + const devicePixelRatio = useDevicePixelRatio(); /** diff --git a/src/types.ts b/src/types.ts index 6628741a..0a18f097 100644 --- a/src/types.ts +++ b/src/types.ts @@ -28,9 +28,12 @@ export type UseRiveOptions = { * - `true` normally, so multiple Rive graphics on a page share one context. * - `false` when `enableGPUCanvas` is on, because GPU Canvas needs a context * of its own for each ``. + * - `false` when `enableInkbleed` is on, because inkbleed needs a context of + * its own for each ``. * * Setting it explicitly always wins. Setting it to `true` alongside - * `enableGPUCanvas` means GPU Canvas content will not draw. + * `enableGPUCanvas` means GPU Canvas content will not draw, and alongside + * `enableInkbleed` means inkbleed stays off. */ useOffscreenRenderer: boolean; shouldResizeCanvasToContainer: boolean; diff --git a/test/useRive.test.tsx b/test/useRive.test.tsx index 9c140adb..d66f1540 100644 --- a/test/useRive.test.tsx +++ b/test/useRive.test.tsx @@ -4,6 +4,7 @@ import { renderHook, act, render, waitFor } from '@testing-library/react'; import useRive from '../src/hooks/useRive'; import * as rive from '@rive-app/canvas'; +import { UseRiveOptions, UseRiveParameters } from '../src/types'; jest.mock('@rive-app/canvas', () => ({ Rive: jest.fn().mockImplementation(() => ({ @@ -656,3 +657,159 @@ describe('useRive', () => { expect(canvasSpy.height).toBe(200); }); }); + +/** + * GPU Canvas and inkbleed both need the canvas's own WebGL context, while the + * offscreen renderer shares one GL context across every canvas on the page. + */ +describe('useRive — offscreen renderer resolution', () => { + let warnSpy: jest.SpyInstance; + + beforeEach(() => { + mocked(rive.Rive).mockClear(); + // @ts-ignore — a bare stub is enough; nothing here drives playback. + mocked(rive.Rive).mockImplementation(() => ({ + on: jest.fn(), + stop: jest.fn(), + cleanup: jest.fn(), + })); + warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); + }); + + afterEach(() => { + warnSpy.mockRestore(); + }); + + /** Mounts the hook, attaches a canvas, and returns the Rive constructor args. */ + async function constructorParams( + params: UseRiveParameters, + opts?: Partial + ) { + const canvasSpy = document.createElement('canvas'); + const { result } = renderHook(() => useRive(params, opts)); + + await act(async () => { + result.current.setCanvasRef(canvasSpy); + }); + await waitFor(() => { + expect(mocked(rive.Rive)).toHaveBeenCalled(); + }); + + return mocked(rive.Rive).mock.calls[0][0]; + } + + it('keeps the offscreen renderer on by default', async () => { + const args = await constructorParams({ src: 'file-src' }); + expect(args.useOffscreenRenderer).toBe(true); + }); + + it('turns the offscreen renderer off when enableGPUCanvas is set', async () => { + const args = await constructorParams({ + src: 'file-src', + enableGPUCanvas: true, + }); + expect(args.useOffscreenRenderer).toBe(false); + expect(warnSpy).not.toHaveBeenCalled(); + }); + + it('turns it off for a riveFile imported with GPU Canvas, even when the instance did not ask', async () => { + // The file's mode is fixed at import and wins over the instance's flag, so + // the renderer has to follow the file. + const riveFile = { deferredRequested: true } as unknown as rive.RiveFile; + + const args = await constructorParams({ riveFile }); + expect(args.useOffscreenRenderer).toBe(false); + }); + + it('leaves it on for a riveFile imported without GPU Canvas', async () => { + const riveFile = { deferredRequested: false } as unknown as rive.RiveFile; + + const args = await constructorParams({ riveFile }); + expect(args.useOffscreenRenderer).toBe(true); + }); + + it('lets an explicit option win over GPU Canvas, and warns', async () => { + const args = await constructorParams( + { src: 'file-src', enableGPUCanvas: true }, + { useOffscreenRenderer: true } + ); + + expect(args.useOffscreenRenderer).toBe(true); + expect(warnSpy).toHaveBeenCalled(); + }); + + it('lets an explicit riveParams value win over GPU Canvas', async () => { + const args = await constructorParams({ + src: 'file-src', + enableGPUCanvas: true, + useOffscreenRenderer: true, + }); + + expect(args.useOffscreenRenderer).toBe(true); + expect(warnSpy).toHaveBeenCalled(); + }); + + it('honors an explicit false with no GPU Canvas in play', async () => { + const args = await constructorParams( + { src: 'file-src' }, + { useOffscreenRenderer: false } + ); + + expect(args.useOffscreenRenderer).toBe(false); + expect(warnSpy).not.toHaveBeenCalled(); + }); + + it('forwards enableGPUCanvas through to the runtime', async () => { + const args = await constructorParams({ + src: 'file-src', + enableGPUCanvas: true, + }); + expect(args).toMatchObject({ enableGPUCanvas: true }); + }); + + it('turns the offscreen renderer off when enableInkbleed is set', async () => { + const args = await constructorParams({ + src: 'file-src', + enableInkbleed: true, + }); + expect(args.useOffscreenRenderer).toBe(false); + expect(warnSpy).not.toHaveBeenCalled(); + }); + + it('keeps the offscreen renderer on when enableInkbleed is false', async () => { + const args = await constructorParams({ + src: 'file-src', + enableInkbleed: false, + }); + expect(args.useOffscreenRenderer).toBe(true); + }); + + it('lets an explicit option win over inkbleed, and warns', async () => { + const args = await constructorParams( + { src: 'file-src', enableInkbleed: true }, + { useOffscreenRenderer: true } + ); + + expect(args.useOffscreenRenderer).toBe(true); + expect(warnSpy).toHaveBeenCalled(); + }); + + it('lets an explicit riveParams value win over inkbleed', async () => { + const args = await constructorParams({ + src: 'file-src', + enableInkbleed: true, + useOffscreenRenderer: true, + }); + + expect(args.useOffscreenRenderer).toBe(true); + expect(warnSpy).toHaveBeenCalled(); + }); + + it('forwards enableInkbleed through to the runtime', async () => { + const args = await constructorParams({ + src: 'file-src', + enableInkbleed: true, + }); + expect(args).toMatchObject({ enableInkbleed: true }); + }); +}); diff --git a/test/useRiveGpuCanvas.test.tsx b/test/useRiveGpuCanvas.test.tsx deleted file mode 100644 index f5973245..00000000 --- a/test/useRiveGpuCanvas.test.tsx +++ /dev/null @@ -1,121 +0,0 @@ -import { mocked } from 'jest-mock'; -import { renderHook, act, waitFor } from '@testing-library/react'; - -import useRive from '../src/hooks/useRive'; -import * as rive from '@rive-app/canvas'; -import { UseRiveOptions, UseRiveParameters } from '../src/types'; - -/** - * GPU Canvas and the offscreen renderer are mutually exclusive: a GPU Canvas - * session records for a single canvas, while the offscreen renderer shares one - * GL context across every canvas on the page (so it has no `attachSession`). - * - * The JS runtime defaults `useOffscreenRenderer` to false; rive-react defaults it - * to true. These tests pin the resolution rule that reconciles the two, since - * getting it wrong means GPU Canvas silently never draws for React users. - */ -describe('useRive — GPU Canvas / offscreen renderer resolution', () => { - let warnSpy: jest.SpyInstance; - - beforeEach(() => { - mocked(rive.Rive).mockClear(); - // @ts-ignore — a bare stub is enough; nothing here drives playback. - mocked(rive.Rive).mockImplementation(() => ({ - on: jest.fn(), - stop: jest.fn(), - cleanup: jest.fn(), - })); - warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); - }); - - afterEach(() => { - warnSpy.mockRestore(); - }); - - /** Mounts the hook, attaches a canvas, and returns the Rive constructor args. */ - async function constructorParams( - params: UseRiveParameters, - opts?: Partial - ) { - const canvasSpy = document.createElement('canvas'); - const { result } = renderHook(() => useRive(params, opts)); - - await act(async () => { - result.current.setCanvasRef(canvasSpy); - }); - await waitFor(() => { - expect(mocked(rive.Rive)).toHaveBeenCalled(); - }); - - return mocked(rive.Rive).mock.calls[0][0]; - } - - it('keeps the offscreen renderer on by default', async () => { - const args = await constructorParams({ src: 'file-src' }); - expect(args.useOffscreenRenderer).toBe(true); - }); - - it('turns the offscreen renderer off when enableGPUCanvas is set', async () => { - const args = await constructorParams({ - src: 'file-src', - enableGPUCanvas: true, - }); - expect(args.useOffscreenRenderer).toBe(false); - expect(warnSpy).not.toHaveBeenCalled(); - }); - - it('turns it off for a riveFile imported with GPU Canvas, even when the instance did not ask', async () => { - // The file's mode is fixed at import and wins over the instance's flag, so - // the renderer has to follow the file. - const riveFile = { deferredRequested: true } as unknown as rive.RiveFile; - - const args = await constructorParams({ riveFile }); - expect(args.useOffscreenRenderer).toBe(false); - }); - - it('leaves it on for a riveFile imported without GPU Canvas', async () => { - const riveFile = { deferredRequested: false } as unknown as rive.RiveFile; - - const args = await constructorParams({ riveFile }); - expect(args.useOffscreenRenderer).toBe(true); - }); - - it('lets an explicit option win over GPU Canvas, and warns', async () => { - const args = await constructorParams( - { src: 'file-src', enableGPUCanvas: true }, - { useOffscreenRenderer: true } - ); - - expect(args.useOffscreenRenderer).toBe(true); - expect(warnSpy).toHaveBeenCalled(); - }); - - it('lets an explicit riveParams value win over GPU Canvas', async () => { - const args = await constructorParams({ - src: 'file-src', - enableGPUCanvas: true, - useOffscreenRenderer: true, - }); - - expect(args.useOffscreenRenderer).toBe(true); - expect(warnSpy).toHaveBeenCalled(); - }); - - it('honors an explicit false with no GPU Canvas in play', async () => { - const args = await constructorParams( - { src: 'file-src' }, - { useOffscreenRenderer: false } - ); - - expect(args.useOffscreenRenderer).toBe(false); - expect(warnSpy).not.toHaveBeenCalled(); - }); - - it('forwards enableGPUCanvas through to the runtime', async () => { - const args = await constructorParams({ - src: 'file-src', - enableGPUCanvas: true, - }); - expect(args).toMatchObject({ enableGPUCanvas: true }); - }); -}); From a851964b416f2a512b9141576e85083566bd2d91 Mon Sep 17 00:00:00 2001 From: Zachary Plata Date: Sat, 17 Oct 2026 15:30:06 -0500 Subject: [PATCH 2/2] fix inkbleed example --- examples/src/components/Inkbleed.tsx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/examples/src/components/Inkbleed.tsx b/examples/src/components/Inkbleed.tsx index a4c28fe4..94150ff5 100644 --- a/examples/src/components/Inkbleed.tsx +++ b/examples/src/components/Inkbleed.tsx @@ -18,17 +18,17 @@ interface CardProps { const Card = ({ title, expectInkbleed, params, opts }: CardProps) => { const { rive, RiveComponent } = useRive(params, opts); - // rive.inkbleed isn't React state, so re-render after toggling it. - const [, rerender] = useState(0); + // What this card currently asks for; the Toggle button flips it. + const [expected, setExpected] = useState(expectInkbleed); let label = 'loading…'; let color: string = COLORS.textMuted; if (rive) { const on = rive.inkbleed; - const ok = on === expectInkbleed; + const ok = on === expected; label = `inkbleed ${on ? 'on' : 'off'} ${ - ok ? '✓' : `✗ expected ${expectInkbleed ? 'on' : 'off'}` + ok ? '✓' : `✗ expected ${expected ? 'on' : 'off'}` }`; color = ok ? COLORS.pass : COLORS.fail; } @@ -49,8 +49,9 @@ const Card = ({ title, expectInkbleed, params, opts }: CardProps) => { disabled={!rive} onClick={() => { if (!rive) return; - rive.inkbleed = !rive.inkbleed; - rerender((n) => n + 1); + const next = !expected; + rive.inkbleed = next; + setExpected(next); }} > Toggle