@grandgular/rive-angular 0.3.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
@@ -191,6 +191,221 @@ this.riveRef().setTextRunValueAtPath(
191
191
  - **Uncontrolled**: Keys not in `textRuns` — managed imperatively via methods
192
192
  - **Warning**: Calling `setTextRunValue()` on a controlled key logs a warning and the change will be overwritten on next input update
193
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
+
194
409
  ### Preloading files with RiveFileService
195
410
 
196
411
  For better performance, you can preload and cache .riv files:
@@ -304,6 +519,9 @@ In this case, `onError` receives a `RiveValidationError` with code `RIVE_201`, a
304
519
  | `RIVE_205` | Validation | Text run not found |
305
520
  | `RIVE_301` | Config | No animation source provided |
306
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) |
307
525
 
308
526
  ## API Reference
309
527