@grandgular/rive-angular 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -142,6 +142,270 @@ export class InteractiveComponent {
|
|
|
142
142
|
}
|
|
143
143
|
```
|
|
144
144
|
|
|
145
|
+
### Text Runs
|
|
146
|
+
|
|
147
|
+
Rive text runs allow you to update text content at runtime. The library provides two approaches:
|
|
148
|
+
|
|
149
|
+
#### Declarative (Controlled Keys)
|
|
150
|
+
|
|
151
|
+
Use the `textRuns` input for reactive, template-driven text updates:
|
|
152
|
+
|
|
153
|
+
```html
|
|
154
|
+
<rive-canvas
|
|
155
|
+
src="assets/hello.riv"
|
|
156
|
+
[textRuns]="{ greeting: userName(), subtitle: 'Welcome' }"
|
|
157
|
+
/>
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Keys present in `textRuns` are **controlled** — the input is the source of truth and will override any imperative changes.
|
|
161
|
+
|
|
162
|
+
#### Imperative (Uncontrolled Keys)
|
|
163
|
+
|
|
164
|
+
Use methods for reading values or managing keys not in `textRuns`:
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
riveRef = viewChild.required(RiveCanvasComponent);
|
|
168
|
+
|
|
169
|
+
// Read current value
|
|
170
|
+
const greeting = this.riveRef().getTextRunValue('greeting');
|
|
171
|
+
|
|
172
|
+
// Set uncontrolled key
|
|
173
|
+
this.riveRef().setTextRunValue('dynamicText', 'New value');
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
#### Nested Text Runs
|
|
177
|
+
|
|
178
|
+
For text runs inside nested components, use the `AtPath` variants:
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
this.riveRef().setTextRunValueAtPath(
|
|
182
|
+
'button_text',
|
|
183
|
+
'Click Me',
|
|
184
|
+
'NestedArtboard/ButtonComponent'
|
|
185
|
+
);
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
#### Controlled vs Uncontrolled
|
|
189
|
+
|
|
190
|
+
- **Controlled**: Keys in `textRuns` input — managed by Angular, input is source of truth
|
|
191
|
+
- **Uncontrolled**: Keys not in `textRuns` — managed imperatively via methods
|
|
192
|
+
- **Warning**: Calling `setTextRunValue()` on a controlled key logs a warning and the change will be overwritten on next input update
|
|
193
|
+
|
|
194
|
+
### Data Binding (ViewModel)
|
|
195
|
+
|
|
196
|
+
Rive's ViewModel system allows you to bind dynamic data (colors, numbers, strings, booleans, etc.) to your animations. ViewModels are created in the Rive editor and provide a structured way to control animation properties at runtime.
|
|
197
|
+
|
|
198
|
+
#### What is a ViewModel?
|
|
199
|
+
|
|
200
|
+
A ViewModel in Rive is a data structure that:
|
|
201
|
+
- Is created by designers in the Rive editor
|
|
202
|
+
- Contains typed properties (color, number, string, boolean, enum, trigger)
|
|
203
|
+
- Can be bound to animation elements (fills, strokes, transforms, etc.)
|
|
204
|
+
- Supports two-way data binding (changes in code affect animation, changes in animation can trigger events)
|
|
205
|
+
|
|
206
|
+
#### When to use Data Binding vs Text Runs?
|
|
207
|
+
|
|
208
|
+
- **Text Runs**: Simple text updates, no ViewModel setup required in editor
|
|
209
|
+
- **Data Binding**: Dynamic colors, numbers, complex data structures, two-way reactivity
|
|
210
|
+
|
|
211
|
+
#### Declarative (Controlled) Approach
|
|
212
|
+
|
|
213
|
+
Use the `dataBindings` input for reactive, template-driven data binding:
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
import { Component, signal } from '@angular/core';
|
|
217
|
+
import { RiveCanvasComponent } from '@grandgular/rive-angular';
|
|
218
|
+
|
|
219
|
+
@Component({
|
|
220
|
+
selector: 'app-data-binding',
|
|
221
|
+
standalone: true,
|
|
222
|
+
imports: [RiveCanvasComponent],
|
|
223
|
+
template: `
|
|
224
|
+
<rive-canvas
|
|
225
|
+
src="assets/animation.riv"
|
|
226
|
+
[dataBindings]="{
|
|
227
|
+
backgroundColor: themeColor(),
|
|
228
|
+
score: playerScore(),
|
|
229
|
+
playerName: userName(),
|
|
230
|
+
isActive: isGameActive()
|
|
231
|
+
}"
|
|
232
|
+
(dataBindingChange)="onDataChange($event)"
|
|
233
|
+
/>
|
|
234
|
+
|
|
235
|
+
<button (click)="changeTheme()">Change Theme</button>
|
|
236
|
+
<button (click)="incrementScore()">+10 Points</button>
|
|
237
|
+
`
|
|
238
|
+
})
|
|
239
|
+
export class DataBindingComponent {
|
|
240
|
+
themeColor = signal('#FF5733');
|
|
241
|
+
playerScore = signal(0);
|
|
242
|
+
userName = signal('Player 1');
|
|
243
|
+
isGameActive = signal(true);
|
|
244
|
+
|
|
245
|
+
changeTheme() {
|
|
246
|
+
const colors = ['#FF5733', '#33FF57', '#3357FF', '#F333FF'];
|
|
247
|
+
const randomColor = colors[Math.floor(Math.random() * colors.length)];
|
|
248
|
+
this.themeColor.set(randomColor);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
incrementScore() {
|
|
252
|
+
this.playerScore.update(score => score + 10);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
onDataChange(event: DataBindingChangeEvent) {
|
|
256
|
+
console.log('Property changed from animation:', event);
|
|
257
|
+
// event.path: property path
|
|
258
|
+
// event.value: new value (for triggers, value is always true)
|
|
259
|
+
// event.propertyType: 'color' | 'number' | 'string' | 'boolean' | 'enum' | 'trigger'
|
|
260
|
+
|
|
261
|
+
if (event.propertyType === 'trigger') {
|
|
262
|
+
console.log(`Trigger "${event.path}" fired from animation`);
|
|
263
|
+
// Handle trigger event (e.g., show popup, play sound, etc.)
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
#### Imperative (Uncontrolled) Approach
|
|
270
|
+
|
|
271
|
+
Use methods for direct, programmatic control:
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
import { Component, viewChild } from '@angular/core';
|
|
275
|
+
import { RiveCanvasComponent } from '@grandgular/rive-angular';
|
|
276
|
+
|
|
277
|
+
@Component({
|
|
278
|
+
selector: 'app-imperative',
|
|
279
|
+
standalone: true,
|
|
280
|
+
imports: [RiveCanvasComponent],
|
|
281
|
+
template: `
|
|
282
|
+
<rive-canvas src="assets/animation.riv" />
|
|
283
|
+
|
|
284
|
+
<button (click)="updateColor()">Update Color</button>
|
|
285
|
+
<button (click)="updateScore()">Update Score</button>
|
|
286
|
+
<button (click)="triggerAnimation()">Fire Trigger</button>
|
|
287
|
+
`
|
|
288
|
+
})
|
|
289
|
+
export class ImperativeComponent {
|
|
290
|
+
riveRef = viewChild.required(RiveCanvasComponent);
|
|
291
|
+
|
|
292
|
+
updateColor() {
|
|
293
|
+
// Set color using hex string
|
|
294
|
+
this.riveRef().setColor('backgroundColor', '#00FF00');
|
|
295
|
+
|
|
296
|
+
// Or using RGBA components
|
|
297
|
+
this.riveRef().setColorRgba('backgroundColor', 0, 255, 0, 255);
|
|
298
|
+
|
|
299
|
+
// Or change only opacity
|
|
300
|
+
this.riveRef().setColorOpacity('backgroundColor', 0.5);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
updateScore() {
|
|
304
|
+
// Set any data binding value (auto-detects type)
|
|
305
|
+
this.riveRef().setDataBinding('score', 100);
|
|
306
|
+
this.riveRef().setDataBinding('playerName', 'Winner');
|
|
307
|
+
this.riveRef().setDataBinding('isActive', false);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
triggerAnimation() {
|
|
311
|
+
// Fire a trigger property
|
|
312
|
+
this.riveRef().fireViewModelTrigger('onComplete');
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
readValues() {
|
|
316
|
+
// Read current values
|
|
317
|
+
const color = this.riveRef().getColor('backgroundColor');
|
|
318
|
+
// color: { r: 0, g: 255, b: 0, a: 255 }
|
|
319
|
+
|
|
320
|
+
const score = this.riveRef().getDataBinding('score');
|
|
321
|
+
// score: 100 (auto-detected as number)
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
#### Color Utilities
|
|
327
|
+
|
|
328
|
+
The library exports color conversion utilities for advanced use cases:
|
|
329
|
+
|
|
330
|
+
```typescript
|
|
331
|
+
import { parseRiveColor, riveColorToArgb, riveColorToHex } from '@grandgular/rive-angular';
|
|
332
|
+
|
|
333
|
+
// Parse various color formats
|
|
334
|
+
const color1 = parseRiveColor('#FF5733'); // { r: 255, g: 87, b: 51, a: 255 }
|
|
335
|
+
const color2 = parseRiveColor('#FF573380'); // { r: 255, g: 87, b: 51, a: 128 }
|
|
336
|
+
const color3 = parseRiveColor(0x80FF5733); // { r: 255, g: 87, b: 51, a: 128 }
|
|
337
|
+
const color4 = parseRiveColor({ r: 255, g: 0, b: 0, a: 255 });
|
|
338
|
+
|
|
339
|
+
// Convert to ARGB integer
|
|
340
|
+
const argb = riveColorToArgb({ r: 255, g: 0, b: 0, a: 255 }); // 0xFFFF0000
|
|
341
|
+
|
|
342
|
+
// Convert to hex string
|
|
343
|
+
const hex = riveColorToHex({ r: 255, g: 0, b: 0, a: 255 }); // '#FF0000FF'
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
#### Selecting a ViewModel
|
|
347
|
+
|
|
348
|
+
If your `.riv` file contains multiple ViewModels, specify which one to use:
|
|
349
|
+
|
|
350
|
+
```typescript
|
|
351
|
+
<rive-canvas
|
|
352
|
+
src="assets/animation.riv"
|
|
353
|
+
viewModelName="GameViewModel"
|
|
354
|
+
[dataBindings]="{ score: 42 }"
|
|
355
|
+
/>
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
If `viewModelName` is not provided, the default ViewModel for the artboard is used.
|
|
359
|
+
|
|
360
|
+
#### Controlled vs Uncontrolled
|
|
361
|
+
|
|
362
|
+
Same semantics as Text Runs:
|
|
363
|
+
|
|
364
|
+
- **Controlled**: Keys in `dataBindings` input — managed by Angular, input is source of truth
|
|
365
|
+
- **Uncontrolled**: Keys not in `dataBindings` — managed imperatively via methods
|
|
366
|
+
- **Warning**: Calling `setDataBinding()` or `setColor()` on a controlled key logs a warning and the change will be overwritten on next input update
|
|
367
|
+
|
|
368
|
+
#### Validation and Error Handling
|
|
369
|
+
|
|
370
|
+
Imperative methods (`setDataBinding`, `setColor`, `setColorOpacity`, `fireViewModelTrigger`) emit validation errors via the `loadError` output when:
|
|
371
|
+
|
|
372
|
+
- Property path doesn't exist in the ViewModel (`RIVE_402`)
|
|
373
|
+
- Value type doesn't match property type (`RIVE_403`)
|
|
374
|
+
- Color format is invalid (hex string, ARGB integer, or RiveColor object expected)
|
|
375
|
+
- Opacity value is out of range (must be between 0.0 and 1.0)
|
|
376
|
+
|
|
377
|
+
```typescript
|
|
378
|
+
<rive-canvas
|
|
379
|
+
src="assets/animation.riv"
|
|
380
|
+
(loadError)="handleError($event)"
|
|
381
|
+
/>
|
|
382
|
+
|
|
383
|
+
handleError(error: Error) {
|
|
384
|
+
if (error instanceof RiveValidationError) {
|
|
385
|
+
console.error('Validation error:', error.code, error.message);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
#### Advanced: Direct ViewModel Access
|
|
391
|
+
|
|
392
|
+
For advanced scenarios, access the ViewModel instance directly:
|
|
393
|
+
|
|
394
|
+
```typescript
|
|
395
|
+
riveRef = viewChild.required(RiveCanvasComponent);
|
|
396
|
+
|
|
397
|
+
advancedUsage() {
|
|
398
|
+
const vmi = this.riveRef().viewModelInstance();
|
|
399
|
+
if (vmi) {
|
|
400
|
+
// Direct access to ViewModel SDK methods
|
|
401
|
+
const colorProp = vmi.color('backgroundColor');
|
|
402
|
+
if (colorProp) {
|
|
403
|
+
colorProp.rgba(255, 0, 0, 255);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
```
|
|
408
|
+
|
|
145
409
|
### Preloading files with RiveFileService
|
|
146
410
|
|
|
147
411
|
For better performance, you can preload and cache .riv files:
|
|
@@ -252,8 +516,12 @@ In this case, `onError` receives a `RiveValidationError` with code `RIVE_201`, a
|
|
|
252
516
|
| `RIVE_202` | Validation | Animation not found |
|
|
253
517
|
| `RIVE_203` | Validation | State machine not found |
|
|
254
518
|
| `RIVE_204` | Validation | Input/Trigger not found in State Machine |
|
|
519
|
+
| `RIVE_205` | Validation | Text run not found |
|
|
255
520
|
| `RIVE_301` | Config | No animation source provided |
|
|
256
521
|
| `RIVE_302` | Config | Invalid canvas element |
|
|
522
|
+
| `RIVE_401` | Data Binding | ViewModel not found |
|
|
523
|
+
| `RIVE_402` | Data Binding | Property not found in ViewModel |
|
|
524
|
+
| `RIVE_403` | Data Binding | Type mismatch (value doesn't match property type) |
|
|
257
525
|
|
|
258
526
|
## API Reference
|
|
259
527
|
|
|
@@ -277,6 +545,7 @@ In this case, `onError` receives a `RiveValidationError` with code `RIVE_201`, a
|
|
|
277
545
|
| `shouldDisableRiveListeners` | `boolean` | `false` | Disable Rive event listeners |
|
|
278
546
|
| `automaticallyHandleEvents` | `boolean` | `false` | Auto-handle Rive events (e.g., OpenUrlEvent) |
|
|
279
547
|
| `debugMode` | `boolean` | `undefined` | Enable verbose logging for this instance |
|
|
548
|
+
| `textRuns` | `Record<string, string>` | - | Declarative text run values. Keys present are controlled by input. |
|
|
280
549
|
|
|
281
550
|
#### Outputs
|
|
282
551
|
|
|
@@ -311,6 +580,10 @@ All signals are **readonly** and cannot be mutated externally. Use the public me
|
|
|
311
580
|
| `reset()` | Reset animation to beginning |
|
|
312
581
|
| `setInput(stateMachine: string, input: string, value: number \| boolean)` | Set state machine input value |
|
|
313
582
|
| `fireTrigger(stateMachine: string, trigger: string)` | Fire state machine trigger |
|
|
583
|
+
| `getTextRunValue(name: string): string \| undefined` | Get text run value |
|
|
584
|
+
| `setTextRunValue(name: string, value: string)` | Set text run value (warns if key is controlled) |
|
|
585
|
+
| `getTextRunValueAtPath(name: string, path: string): string \| undefined` | Get nested text run value |
|
|
586
|
+
| `setTextRunValueAtPath(name: string, value: string, path: string)` | Set nested text run |
|
|
314
587
|
|
|
315
588
|
### RiveFileService
|
|
316
589
|
|