@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.
|
@@ -2,7 +2,7 @@ import * as i0 from '@angular/core';
|
|
|
2
2
|
import { inject, PLATFORM_ID, Injectable, InjectionToken, viewChild, DestroyRef, NgZone, input, output, signal, effect, untracked, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
3
|
import { isPlatformBrowser } from '@angular/common';
|
|
4
4
|
import { Fit, Alignment, Layout, Rive, RiveFile, EventType } from '@rive-app/canvas';
|
|
5
|
-
export { Alignment, EventType, Fit, Layout, Rive, RiveFile, StateMachineInput } from '@rive-app/canvas';
|
|
5
|
+
export { Alignment, EventType, Fit, Layout, Rive, RiveFile, StateMachineInput, ViewModelInstance } from '@rive-app/canvas';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Re-export Rive SDK types for consumer convenience
|
|
@@ -61,6 +61,7 @@ class RiveValidationError extends Error {
|
|
|
61
61
|
* - RIVE_1xx: Load errors (file not found, network, bad format)
|
|
62
62
|
* - RIVE_2xx: Validation errors (artboard, animation, state machine mismatch)
|
|
63
63
|
* - RIVE_3xx: Configuration/Usage errors (missing source, bad canvas)
|
|
64
|
+
* - RIVE_4xx: Data Binding errors (ViewModel, property not found, type mismatch)
|
|
64
65
|
*/
|
|
65
66
|
var RiveErrorCode;
|
|
66
67
|
(function (RiveErrorCode) {
|
|
@@ -73,9 +74,14 @@ var RiveErrorCode;
|
|
|
73
74
|
RiveErrorCode["AnimationNotFound"] = "RIVE_202";
|
|
74
75
|
RiveErrorCode["StateMachineNotFound"] = "RIVE_203";
|
|
75
76
|
RiveErrorCode["InputNotFound"] = "RIVE_204";
|
|
77
|
+
RiveErrorCode["TextRunNotFound"] = "RIVE_205";
|
|
76
78
|
// Configuration Errors
|
|
77
79
|
RiveErrorCode["NoSource"] = "RIVE_301";
|
|
78
80
|
RiveErrorCode["InvalidCanvas"] = "RIVE_302";
|
|
81
|
+
// Data Binding Errors
|
|
82
|
+
RiveErrorCode["ViewModelNotFound"] = "RIVE_401";
|
|
83
|
+
RiveErrorCode["DataBindingPropertyNotFound"] = "RIVE_402";
|
|
84
|
+
RiveErrorCode["DataBindingTypeMismatch"] = "RIVE_403";
|
|
79
85
|
})(RiveErrorCode || (RiveErrorCode = {}));
|
|
80
86
|
/**
|
|
81
87
|
* Template messages for each error code.
|
|
@@ -89,8 +95,12 @@ const ERROR_MESSAGES = {
|
|
|
89
95
|
[RiveErrorCode.AnimationNotFound]: 'Animation "{name}" not found',
|
|
90
96
|
[RiveErrorCode.StateMachineNotFound]: 'State machine "{name}" not found',
|
|
91
97
|
[RiveErrorCode.InputNotFound]: 'Input "{name}" not found in "{stateMachine}"',
|
|
98
|
+
[RiveErrorCode.TextRunNotFound]: 'Text run "{name}" not found',
|
|
92
99
|
[RiveErrorCode.NoSource]: 'No animation source provided',
|
|
93
100
|
[RiveErrorCode.InvalidCanvas]: 'Invalid canvas element',
|
|
101
|
+
[RiveErrorCode.ViewModelNotFound]: 'ViewModel "{name}" not found',
|
|
102
|
+
[RiveErrorCode.DataBindingPropertyNotFound]: 'Data binding property "{path}" not found in ViewModel',
|
|
103
|
+
[RiveErrorCode.DataBindingTypeMismatch]: 'Data binding type mismatch for "{path}": expected {expected}, got {actual}',
|
|
94
104
|
};
|
|
95
105
|
/**
|
|
96
106
|
* Formats an error message by replacing placeholders with actual values.
|
|
@@ -399,6 +409,123 @@ function validateConfiguration(rive, config, logger) {
|
|
|
399
409
|
return errors;
|
|
400
410
|
}
|
|
401
411
|
|
|
412
|
+
/**
|
|
413
|
+
* Parses various color input formats into a normalized RiveColor object.
|
|
414
|
+
*
|
|
415
|
+
* Supported formats:
|
|
416
|
+
* - Hex string: '#RRGGBB' or '#RRGGBBAA'
|
|
417
|
+
* - ARGB integer: 0xAARRGGBB (32-bit integer)
|
|
418
|
+
* - RiveColor object: { r, g, b, a? }
|
|
419
|
+
*
|
|
420
|
+
* @param input - Color in any supported format
|
|
421
|
+
* @returns Normalized RiveColor object with all components in 0-255 range
|
|
422
|
+
* @throws Error if the input format is invalid
|
|
423
|
+
*
|
|
424
|
+
* @example
|
|
425
|
+
* parseRiveColor('#FF5733') // { r: 255, g: 87, b: 51, a: 255 }
|
|
426
|
+
* parseRiveColor('#FF573380') // { r: 255, g: 87, b: 51, a: 128 }
|
|
427
|
+
* parseRiveColor(0x80FF5733) // { r: 255, g: 87, b: 51, a: 128 }
|
|
428
|
+
* parseRiveColor({ r: 255, g: 0, b: 0 }) // { r: 255, g: 0, b: 0, a: 255 }
|
|
429
|
+
*/
|
|
430
|
+
function parseRiveColor(input) {
|
|
431
|
+
// If already a RiveColor object, normalize it
|
|
432
|
+
if (typeof input === 'object' && input !== null) {
|
|
433
|
+
return {
|
|
434
|
+
r: clamp(Math.round(input.r), 0, 255),
|
|
435
|
+
g: clamp(Math.round(input.g), 0, 255),
|
|
436
|
+
b: clamp(Math.round(input.b), 0, 255),
|
|
437
|
+
a: clamp(Math.round(input.a ?? 255), 0, 255),
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
// If hex string
|
|
441
|
+
if (typeof input === 'string') {
|
|
442
|
+
return parseHexColor(input);
|
|
443
|
+
}
|
|
444
|
+
// If ARGB integer
|
|
445
|
+
if (typeof input === 'number') {
|
|
446
|
+
return parseArgbInteger(input);
|
|
447
|
+
}
|
|
448
|
+
throw new Error(`Invalid color format: ${input}. Expected hex string, ARGB integer, or RiveColor object.`);
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Converts a RiveColor object to an ARGB 32-bit integer.
|
|
452
|
+
*
|
|
453
|
+
* @param color - RiveColor object
|
|
454
|
+
* @returns ARGB integer in format 0xAARRGGBB
|
|
455
|
+
*
|
|
456
|
+
* @example
|
|
457
|
+
* riveColorToArgb({ r: 255, g: 0, b: 0, a: 255 }) // 0xFFFF0000
|
|
458
|
+
* riveColorToArgb({ r: 0, g: 128, b: 255, a: 128 }) // 0x800080FF
|
|
459
|
+
*/
|
|
460
|
+
function riveColorToArgb(color) {
|
|
461
|
+
const a = clamp(Math.round(color.a), 0, 255);
|
|
462
|
+
const r = clamp(Math.round(color.r), 0, 255);
|
|
463
|
+
const g = clamp(Math.round(color.g), 0, 255);
|
|
464
|
+
const b = clamp(Math.round(color.b), 0, 255);
|
|
465
|
+
return ((a << 24) | (r << 16) | (g << 8) | b) >>> 0;
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Converts a RiveColor object to a hex string.
|
|
469
|
+
*
|
|
470
|
+
* @param color - RiveColor object
|
|
471
|
+
* @returns Hex string in format '#RRGGBBAA'
|
|
472
|
+
*
|
|
473
|
+
* @example
|
|
474
|
+
* riveColorToHex({ r: 255, g: 0, b: 0, a: 255 }) // '#FF0000FF'
|
|
475
|
+
* riveColorToHex({ r: 0, g: 128, b: 255, a: 128 }) // '#0080FF80'
|
|
476
|
+
*/
|
|
477
|
+
function riveColorToHex(color) {
|
|
478
|
+
const r = clamp(Math.round(color.r), 0, 255)
|
|
479
|
+
.toString(16)
|
|
480
|
+
.padStart(2, '0');
|
|
481
|
+
const g = clamp(Math.round(color.g), 0, 255)
|
|
482
|
+
.toString(16)
|
|
483
|
+
.padStart(2, '0');
|
|
484
|
+
const b = clamp(Math.round(color.b), 0, 255)
|
|
485
|
+
.toString(16)
|
|
486
|
+
.padStart(2, '0');
|
|
487
|
+
const a = clamp(Math.round(color.a), 0, 255)
|
|
488
|
+
.toString(16)
|
|
489
|
+
.padStart(2, '0');
|
|
490
|
+
return `#${r}${g}${b}${a}`.toUpperCase();
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Parses a hex color string into a RiveColor object.
|
|
494
|
+
* Supports both '#RRGGBB' and '#RRGGBBAA' formats.
|
|
495
|
+
*/
|
|
496
|
+
function parseHexColor(hex) {
|
|
497
|
+
// Remove '#' if present
|
|
498
|
+
const cleanHex = hex.startsWith('#') ? hex.slice(1) : hex;
|
|
499
|
+
// Validate hex string
|
|
500
|
+
if (!/^[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?$/.test(cleanHex)) {
|
|
501
|
+
throw new Error(`Invalid hex color format: ${hex}. Expected '#RRGGBB' or '#RRGGBBAA'.`);
|
|
502
|
+
}
|
|
503
|
+
const r = parseInt(cleanHex.slice(0, 2), 16);
|
|
504
|
+
const g = parseInt(cleanHex.slice(2, 4), 16);
|
|
505
|
+
const b = parseInt(cleanHex.slice(4, 6), 16);
|
|
506
|
+
const a = cleanHex.length === 8 ? parseInt(cleanHex.slice(6, 8), 16) : 255;
|
|
507
|
+
return { r, g, b, a };
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Parses an ARGB 32-bit integer into a RiveColor object.
|
|
511
|
+
* Format: 0xAARRGGBB
|
|
512
|
+
*/
|
|
513
|
+
function parseArgbInteger(argb) {
|
|
514
|
+
// Ensure it's a valid 32-bit unsigned integer
|
|
515
|
+
const value = argb >>> 0;
|
|
516
|
+
const a = (value >> 24) & 0xff;
|
|
517
|
+
const r = (value >> 16) & 0xff;
|
|
518
|
+
const g = (value >> 8) & 0xff;
|
|
519
|
+
const b = value & 0xff;
|
|
520
|
+
return { r, g, b, a };
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* Clamps a value between min and max.
|
|
524
|
+
*/
|
|
525
|
+
function clamp(value, min, max) {
|
|
526
|
+
return Math.max(min, Math.min(max, value));
|
|
527
|
+
}
|
|
528
|
+
|
|
402
529
|
/**
|
|
403
530
|
* Standalone Angular component for Rive animations
|
|
404
531
|
*
|
|
@@ -466,6 +593,37 @@ class RiveCanvasComponent {
|
|
|
466
593
|
* - false/undefined: use global level
|
|
467
594
|
*/
|
|
468
595
|
debugMode = input(...(ngDevMode ? [undefined, { debugName: "debugMode" }] : []));
|
|
596
|
+
/**
|
|
597
|
+
* Record of text run names to values for declarative text setting.
|
|
598
|
+
* Keys present in this input are CONTROLLED — the input is the source of truth.
|
|
599
|
+
* Keys absent from this input are UNCONTROLLED — managed imperatively.
|
|
600
|
+
* Values are applied reactively when input changes.
|
|
601
|
+
*/
|
|
602
|
+
textRuns = input(...(ngDevMode ? [undefined, { debugName: "textRuns" }] : []));
|
|
603
|
+
/**
|
|
604
|
+
* Name of the ViewModel to use for data binding.
|
|
605
|
+
* If not provided, uses the default ViewModel for the artboard.
|
|
606
|
+
* Only relevant if the .riv file contains ViewModels.
|
|
607
|
+
*/
|
|
608
|
+
viewModelName = input(...(ngDevMode ? [undefined, { debugName: "viewModelName" }] : []));
|
|
609
|
+
/**
|
|
610
|
+
* Record of ViewModel property paths to values for declarative data binding.
|
|
611
|
+
* Keys present in this input are CONTROLLED — the input is the source of truth.
|
|
612
|
+
* Keys absent from this input are UNCONTROLLED — managed imperatively.
|
|
613
|
+
* Values are applied reactively when input changes.
|
|
614
|
+
*
|
|
615
|
+
* Supports multiple data types: number, string, boolean, RiveColor.
|
|
616
|
+
* The component auto-detects the property type from the ViewModel.
|
|
617
|
+
*
|
|
618
|
+
* @example
|
|
619
|
+
* [dataBindings]="{
|
|
620
|
+
* backgroundColor: '#FF5733',
|
|
621
|
+
* score: 42,
|
|
622
|
+
* playerName: 'Alice',
|
|
623
|
+
* isActive: true
|
|
624
|
+
* }"
|
|
625
|
+
*/
|
|
626
|
+
dataBindings = input(...(ngDevMode ? [undefined, { debugName: "dataBindings" }] : []));
|
|
469
627
|
// Outputs (Events)
|
|
470
628
|
loaded = output();
|
|
471
629
|
loadError = output();
|
|
@@ -485,11 +643,18 @@ class RiveCanvasComponent {
|
|
|
485
643
|
* Note: This fires AFTER the animation is loaded, not just instantiated.
|
|
486
644
|
*/
|
|
487
645
|
riveReady = output();
|
|
646
|
+
/**
|
|
647
|
+
* Emitted when a ViewModel property changes from within the animation.
|
|
648
|
+
* Enables two-way data binding between the animation and Angular application.
|
|
649
|
+
* Only fires if the .riv file uses ViewModels with callbacks.
|
|
650
|
+
*/
|
|
651
|
+
dataBindingChange = output();
|
|
488
652
|
// Private writable signals
|
|
489
653
|
#isPlaying = signal(false, ...(ngDevMode ? [{ debugName: "#isPlaying" }] : []));
|
|
490
654
|
#isPaused = signal(false, ...(ngDevMode ? [{ debugName: "#isPaused" }] : []));
|
|
491
655
|
#isLoaded = signal(false, ...(ngDevMode ? [{ debugName: "#isLoaded" }] : []));
|
|
492
656
|
#riveInstance = signal(null, ...(ngDevMode ? [{ debugName: "#riveInstance" }] : []));
|
|
657
|
+
#viewModelInstance = signal(null, ...(ngDevMode ? [{ debugName: "#viewModelInstance" }] : []));
|
|
493
658
|
// Public readonly signals
|
|
494
659
|
isPlaying = this.#isPlaying.asReadonly();
|
|
495
660
|
isPaused = this.#isPaused.asReadonly();
|
|
@@ -499,12 +664,20 @@ class RiveCanvasComponent {
|
|
|
499
664
|
* Use this to access advanced Rive SDK features.
|
|
500
665
|
*/
|
|
501
666
|
riveInstance = this.#riveInstance.asReadonly();
|
|
667
|
+
/**
|
|
668
|
+
* Public signal providing access to the ViewModel instance.
|
|
669
|
+
* Use this to access advanced ViewModel features for data binding.
|
|
670
|
+
* Returns null if the .riv file doesn't use ViewModels.
|
|
671
|
+
*/
|
|
672
|
+
viewModelInstance = this.#viewModelInstance.asReadonly();
|
|
502
673
|
// Private state
|
|
503
674
|
#rive = null;
|
|
504
675
|
logger;
|
|
505
676
|
resizeObserver = null;
|
|
506
677
|
isInitialized = false;
|
|
507
678
|
isPausedByIntersectionObserver = false;
|
|
679
|
+
#viewModelSubscriptionDisposers = new Set();
|
|
680
|
+
#localMutationSuppressions = new Map();
|
|
508
681
|
retestIntersectionTimeoutId = null;
|
|
509
682
|
resizeRafId = null;
|
|
510
683
|
lastWidth = 0;
|
|
@@ -542,6 +715,37 @@ class RiveCanvasComponent {
|
|
|
542
715
|
}
|
|
543
716
|
});
|
|
544
717
|
});
|
|
718
|
+
// Effect to apply text runs when input changes or animation loads
|
|
719
|
+
effect(() => {
|
|
720
|
+
const runs = this.textRuns();
|
|
721
|
+
const isLoaded = this.#isLoaded();
|
|
722
|
+
untracked(() => {
|
|
723
|
+
if (runs && isLoaded && this.#rive) {
|
|
724
|
+
this.applyTextRuns(runs);
|
|
725
|
+
}
|
|
726
|
+
});
|
|
727
|
+
});
|
|
728
|
+
// Effect to apply data bindings when input changes or animation loads
|
|
729
|
+
effect(() => {
|
|
730
|
+
const bindings = this.dataBindings();
|
|
731
|
+
const isLoaded = this.#isLoaded();
|
|
732
|
+
const vmi = this.#viewModelInstance();
|
|
733
|
+
untracked(() => {
|
|
734
|
+
if (bindings && isLoaded && vmi) {
|
|
735
|
+
this.applyDataBindings(bindings);
|
|
736
|
+
}
|
|
737
|
+
});
|
|
738
|
+
});
|
|
739
|
+
// Effect to reinitialize ViewModel when viewModelName changes after load
|
|
740
|
+
effect(() => {
|
|
741
|
+
const viewModelName = this.viewModelName();
|
|
742
|
+
const isLoaded = this.#isLoaded();
|
|
743
|
+
untracked(() => {
|
|
744
|
+
if (isLoaded && this.#rive) {
|
|
745
|
+
this.initializeViewModel();
|
|
746
|
+
}
|
|
747
|
+
});
|
|
748
|
+
});
|
|
545
749
|
// Auto cleanup on destroy
|
|
546
750
|
this.#destroyRef.onDestroy(() => {
|
|
547
751
|
this.cleanupRive();
|
|
@@ -777,6 +981,8 @@ class RiveCanvasComponent {
|
|
|
777
981
|
animations: riveWithMetadata.animationNames,
|
|
778
982
|
stateMachines: riveWithMetadata.stateMachineNames,
|
|
779
983
|
});
|
|
984
|
+
// Initialize ViewModel if available
|
|
985
|
+
this.initializeViewModel();
|
|
780
986
|
}
|
|
781
987
|
this.#ngZone.run(() => {
|
|
782
988
|
this.#isLoaded.set(true);
|
|
@@ -933,10 +1139,746 @@ class RiveCanvasComponent {
|
|
|
933
1139
|
}
|
|
934
1140
|
});
|
|
935
1141
|
}
|
|
1142
|
+
/**
|
|
1143
|
+
* Get the current value of a text run.
|
|
1144
|
+
* Returns undefined if the text run doesn't exist or Rive instance is not loaded.
|
|
1145
|
+
*/
|
|
1146
|
+
getTextRunValue(textRunName) {
|
|
1147
|
+
if (!this.#rive)
|
|
1148
|
+
return undefined;
|
|
1149
|
+
try {
|
|
1150
|
+
return this.#ngZone.runOutsideAngular(() => {
|
|
1151
|
+
return this.#rive.getTextRunValue(textRunName);
|
|
1152
|
+
});
|
|
1153
|
+
}
|
|
1154
|
+
catch (error) {
|
|
1155
|
+
this.logger.warn(`Failed to get text run "${textRunName}":`, error);
|
|
1156
|
+
return undefined;
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
/**
|
|
1160
|
+
* Set a text run value.
|
|
1161
|
+
* Warning: If the text run is controlled by textRuns input, this change will be overwritten
|
|
1162
|
+
* on the next input update.
|
|
1163
|
+
*/
|
|
1164
|
+
setTextRunValue(textRunName, textRunValue) {
|
|
1165
|
+
if (!this.#rive)
|
|
1166
|
+
return;
|
|
1167
|
+
// Check if this key is controlled by textRuns input
|
|
1168
|
+
const controlledRuns = this.textRuns();
|
|
1169
|
+
if (controlledRuns && textRunName in controlledRuns) {
|
|
1170
|
+
this.logger.warn(`Text run "${textRunName}" is controlled by textRuns input. This change will be overwritten on next input update.`);
|
|
1171
|
+
}
|
|
1172
|
+
this.#ngZone.runOutsideAngular(() => {
|
|
1173
|
+
try {
|
|
1174
|
+
this.#rive.setTextRunValue(textRunName, textRunValue);
|
|
1175
|
+
this.logger.debug(`Text run "${textRunName}" set to "${textRunValue}"`);
|
|
1176
|
+
}
|
|
1177
|
+
catch (error) {
|
|
1178
|
+
this.logger.warn(`Failed to set text run "${textRunName}":`, error);
|
|
1179
|
+
this.#ngZone.run(() => this.loadError.emit(new RiveValidationError(formatErrorMessage(RiveErrorCode.TextRunNotFound, {
|
|
1180
|
+
name: textRunName,
|
|
1181
|
+
}), RiveErrorCode.TextRunNotFound)));
|
|
1182
|
+
}
|
|
1183
|
+
});
|
|
1184
|
+
}
|
|
1185
|
+
/**
|
|
1186
|
+
* Get the current value of a text run at a specific path (for nested artboards/components).
|
|
1187
|
+
* Returns undefined if the text run doesn't exist or Rive instance is not loaded.
|
|
1188
|
+
*/
|
|
1189
|
+
getTextRunValueAtPath(textRunName, path) {
|
|
1190
|
+
if (!this.#rive)
|
|
1191
|
+
return undefined;
|
|
1192
|
+
try {
|
|
1193
|
+
return this.#ngZone.runOutsideAngular(() => {
|
|
1194
|
+
return this.#rive.getTextRunValueAtPath(textRunName, path);
|
|
1195
|
+
});
|
|
1196
|
+
}
|
|
1197
|
+
catch (error) {
|
|
1198
|
+
this.logger.warn(`Failed to get text run "${textRunName}" at path "${path}":`, error);
|
|
1199
|
+
return undefined;
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
/**
|
|
1203
|
+
* Set a text run value at a specific path (for nested artboards/components).
|
|
1204
|
+
* Note: AtPath text runs are always uncontrolled (not managed by textRuns input).
|
|
1205
|
+
*/
|
|
1206
|
+
setTextRunValueAtPath(textRunName, textRunValue, path) {
|
|
1207
|
+
if (!this.#rive)
|
|
1208
|
+
return;
|
|
1209
|
+
this.#ngZone.runOutsideAngular(() => {
|
|
1210
|
+
try {
|
|
1211
|
+
this.#rive.setTextRunValueAtPath(textRunName, textRunValue, path);
|
|
1212
|
+
this.logger.debug(`Text run "${textRunName}" at path "${path}" set to "${textRunValue}"`);
|
|
1213
|
+
}
|
|
1214
|
+
catch (error) {
|
|
1215
|
+
this.logger.warn(`Failed to set text run "${textRunName}" at path "${path}":`, error);
|
|
1216
|
+
this.#ngZone.run(() => this.loadError.emit(new RiveValidationError(formatErrorMessage(RiveErrorCode.TextRunNotFound, {
|
|
1217
|
+
name: textRunName,
|
|
1218
|
+
}), RiveErrorCode.TextRunNotFound)));
|
|
1219
|
+
}
|
|
1220
|
+
});
|
|
1221
|
+
}
|
|
1222
|
+
// ========================================================================
|
|
1223
|
+
// Data Binding (ViewModel) Methods
|
|
1224
|
+
// ========================================================================
|
|
1225
|
+
/**
|
|
1226
|
+
* Set a data binding value in the ViewModel.
|
|
1227
|
+
* Auto-detects the property type and applies the value accordingly.
|
|
1228
|
+
* Warning: If the property is controlled by dataBindings input, this change
|
|
1229
|
+
* will be overwritten on the next input update.
|
|
1230
|
+
*/
|
|
1231
|
+
setDataBinding(path, value) {
|
|
1232
|
+
const vmi = this.#viewModelInstance();
|
|
1233
|
+
if (!vmi) {
|
|
1234
|
+
this.logger.warn('No ViewModel instance available');
|
|
1235
|
+
return;
|
|
1236
|
+
}
|
|
1237
|
+
// Check if this key is controlled by dataBindings input
|
|
1238
|
+
const controlledBindings = this.dataBindings();
|
|
1239
|
+
if (controlledBindings && path in controlledBindings) {
|
|
1240
|
+
this.logger.warn(`Data binding "${path}" is controlled by dataBindings input. This change will be overwritten on next input update.`);
|
|
1241
|
+
}
|
|
1242
|
+
this.#ngZone.runOutsideAngular(() => {
|
|
1243
|
+
this.withLocalMutation(path, () => {
|
|
1244
|
+
const resolved = this.resolveViewModelProperty(vmi, path);
|
|
1245
|
+
if (!resolved) {
|
|
1246
|
+
this.logger.warn(`Data binding property "${path}" not found in ViewModel`);
|
|
1247
|
+
this.#ngZone.run(() => this.loadError.emit(new RiveValidationError(formatErrorMessage(RiveErrorCode.DataBindingPropertyNotFound, {
|
|
1248
|
+
path,
|
|
1249
|
+
}), RiveErrorCode.DataBindingPropertyNotFound)));
|
|
1250
|
+
return;
|
|
1251
|
+
}
|
|
1252
|
+
this.tryApplyBinding(path, value, resolved);
|
|
1253
|
+
});
|
|
1254
|
+
});
|
|
1255
|
+
}
|
|
1256
|
+
/**
|
|
1257
|
+
* Get a data binding value from the ViewModel.
|
|
1258
|
+
* Auto-detects the property type and returns the value accordingly.
|
|
1259
|
+
* Returns undefined if the property doesn't exist or ViewModel is not loaded.
|
|
1260
|
+
*/
|
|
1261
|
+
getDataBinding(path) {
|
|
1262
|
+
const vmi = this.#viewModelInstance();
|
|
1263
|
+
if (!vmi)
|
|
1264
|
+
return undefined;
|
|
1265
|
+
return this.#ngZone.runOutsideAngular(() => {
|
|
1266
|
+
// Try each property type
|
|
1267
|
+
const colorProp = vmi.color(path);
|
|
1268
|
+
if (colorProp) {
|
|
1269
|
+
const argb = colorProp.value;
|
|
1270
|
+
const a = (argb >> 24) & 0xff;
|
|
1271
|
+
const r = (argb >> 16) & 0xff;
|
|
1272
|
+
const g = (argb >> 8) & 0xff;
|
|
1273
|
+
const b = argb & 0xff;
|
|
1274
|
+
return { r, g, b, a };
|
|
1275
|
+
}
|
|
1276
|
+
const numberProp = vmi.number(path);
|
|
1277
|
+
if (numberProp)
|
|
1278
|
+
return numberProp.value;
|
|
1279
|
+
const stringProp = vmi.string(path);
|
|
1280
|
+
if (stringProp)
|
|
1281
|
+
return stringProp.value;
|
|
1282
|
+
const boolProp = vmi.boolean(path);
|
|
1283
|
+
if (boolProp)
|
|
1284
|
+
return boolProp.value;
|
|
1285
|
+
const enumProp = vmi.enum(path);
|
|
1286
|
+
if (enumProp)
|
|
1287
|
+
return enumProp.value;
|
|
1288
|
+
return undefined;
|
|
1289
|
+
});
|
|
1290
|
+
}
|
|
1291
|
+
/**
|
|
1292
|
+
* Fire a trigger property in the ViewModel.
|
|
1293
|
+
* Use this for ViewModel-based triggers (data binding).
|
|
1294
|
+
* For state machine triggers, use fireTrigger(stateMachineName, triggerName).
|
|
1295
|
+
*/
|
|
1296
|
+
fireViewModelTrigger(path) {
|
|
1297
|
+
const vmi = this.#viewModelInstance();
|
|
1298
|
+
if (!vmi) {
|
|
1299
|
+
this.logger.warn('No ViewModel instance available');
|
|
1300
|
+
return;
|
|
1301
|
+
}
|
|
1302
|
+
this.#ngZone.runOutsideAngular(() => {
|
|
1303
|
+
const triggerProp = vmi.trigger(path);
|
|
1304
|
+
if (triggerProp) {
|
|
1305
|
+
triggerProp.trigger();
|
|
1306
|
+
this.logger.debug(`ViewModel trigger "${path}" fired`);
|
|
1307
|
+
}
|
|
1308
|
+
else {
|
|
1309
|
+
this.logger.warn(`ViewModel trigger "${path}" not found`);
|
|
1310
|
+
this.#ngZone.run(() => this.loadError.emit(new RiveValidationError(formatErrorMessage(RiveErrorCode.DataBindingPropertyNotFound, {
|
|
1311
|
+
path,
|
|
1312
|
+
}), RiveErrorCode.DataBindingPropertyNotFound)));
|
|
1313
|
+
}
|
|
1314
|
+
});
|
|
1315
|
+
}
|
|
1316
|
+
/**
|
|
1317
|
+
* Set a color value in the ViewModel.
|
|
1318
|
+
* Accepts hex string ('#RRGGBB' or '#RRGGBBAA'), ARGB integer, or RiveColor object.
|
|
1319
|
+
* Warning: If the property is controlled by dataBindings input, this change
|
|
1320
|
+
* will be overwritten on the next input update.
|
|
1321
|
+
*/
|
|
1322
|
+
setColor(path, color) {
|
|
1323
|
+
const vmi = this.#viewModelInstance();
|
|
1324
|
+
if (!vmi) {
|
|
1325
|
+
this.logger.warn('No ViewModel instance available');
|
|
1326
|
+
return;
|
|
1327
|
+
}
|
|
1328
|
+
// Check if this key is controlled by dataBindings input
|
|
1329
|
+
const controlledBindings = this.dataBindings();
|
|
1330
|
+
if (controlledBindings && path in controlledBindings) {
|
|
1331
|
+
this.logger.warn(`Color "${path}" is controlled by dataBindings input. This change will be overwritten on next input update.`);
|
|
1332
|
+
}
|
|
1333
|
+
this.#ngZone.runOutsideAngular(() => {
|
|
1334
|
+
this.withLocalMutation(path, () => {
|
|
1335
|
+
const colorProp = vmi.color(path);
|
|
1336
|
+
if (!colorProp) {
|
|
1337
|
+
this.logger.warn(`Color property "${path}" not found in ViewModel`);
|
|
1338
|
+
this.#ngZone.run(() => this.loadError.emit(new RiveValidationError(formatErrorMessage(RiveErrorCode.DataBindingPropertyNotFound, {
|
|
1339
|
+
path,
|
|
1340
|
+
}), RiveErrorCode.DataBindingPropertyNotFound)));
|
|
1341
|
+
return;
|
|
1342
|
+
}
|
|
1343
|
+
try {
|
|
1344
|
+
const parsedColor = parseRiveColor(color);
|
|
1345
|
+
colorProp.rgba(parsedColor.r, parsedColor.g, parsedColor.b, parsedColor.a);
|
|
1346
|
+
this.logger.debug(`Color "${path}" set to:`, parsedColor);
|
|
1347
|
+
}
|
|
1348
|
+
catch (error) {
|
|
1349
|
+
this.logger.warn(`Failed to set color "${path}":`, error);
|
|
1350
|
+
this.#ngZone.run(() => this.loadError.emit(new RiveValidationError(`Failed to parse color value for "${path}": ${error instanceof Error ? error.message : String(error)}`, RiveErrorCode.DataBindingTypeMismatch)));
|
|
1351
|
+
}
|
|
1352
|
+
});
|
|
1353
|
+
});
|
|
1354
|
+
}
|
|
1355
|
+
/**
|
|
1356
|
+
* Get a color value from the ViewModel.
|
|
1357
|
+
* Returns undefined if the property doesn't exist or ViewModel is not loaded.
|
|
1358
|
+
*/
|
|
1359
|
+
getColor(path) {
|
|
1360
|
+
const vmi = this.#viewModelInstance();
|
|
1361
|
+
if (!vmi)
|
|
1362
|
+
return undefined;
|
|
1363
|
+
return this.#ngZone.runOutsideAngular(() => {
|
|
1364
|
+
const colorProp = vmi.color(path);
|
|
1365
|
+
if (!colorProp)
|
|
1366
|
+
return undefined;
|
|
1367
|
+
const argb = colorProp.value;
|
|
1368
|
+
const a = (argb >> 24) & 0xff;
|
|
1369
|
+
const r = (argb >> 16) & 0xff;
|
|
1370
|
+
const g = (argb >> 8) & 0xff;
|
|
1371
|
+
const b = argb & 0xff;
|
|
1372
|
+
return { r, g, b, a };
|
|
1373
|
+
});
|
|
1374
|
+
}
|
|
1375
|
+
/**
|
|
1376
|
+
* Set a color value using RGBA components (0-255).
|
|
1377
|
+
* Warning: If the property is controlled by dataBindings input, this change
|
|
1378
|
+
* will be overwritten on the next input update.
|
|
1379
|
+
*/
|
|
1380
|
+
setColorRgba(path, r, g, b, a = 255) {
|
|
1381
|
+
this.setColor(path, { r, g, b, a });
|
|
1382
|
+
}
|
|
1383
|
+
/**
|
|
1384
|
+
* Set the opacity of a color (0.0-1.0) while preserving RGB values.
|
|
1385
|
+
* Warning: If the property is controlled by dataBindings input, this change
|
|
1386
|
+
* will be overwritten on the next input update.
|
|
1387
|
+
*/
|
|
1388
|
+
setColorOpacity(path, opacity) {
|
|
1389
|
+
const vmi = this.#viewModelInstance();
|
|
1390
|
+
if (!vmi) {
|
|
1391
|
+
this.logger.warn('No ViewModel instance available');
|
|
1392
|
+
return;
|
|
1393
|
+
}
|
|
1394
|
+
// Validate opacity range
|
|
1395
|
+
if (opacity < 0 || opacity > 1) {
|
|
1396
|
+
this.logger.warn(`Invalid opacity value ${opacity}: must be between 0.0 and 1.0`);
|
|
1397
|
+
this.#ngZone.run(() => this.loadError.emit(new RiveValidationError(`Invalid opacity value for "${path}": ${opacity}. Expected value between 0.0 and 1.0.`, RiveErrorCode.DataBindingTypeMismatch)));
|
|
1398
|
+
return;
|
|
1399
|
+
}
|
|
1400
|
+
// Check if this key is controlled by dataBindings input
|
|
1401
|
+
const controlledBindings = this.dataBindings();
|
|
1402
|
+
if (controlledBindings && path in controlledBindings) {
|
|
1403
|
+
this.logger.warn(`Color "${path}" is controlled by dataBindings input. This change will be overwritten on next input update.`);
|
|
1404
|
+
}
|
|
1405
|
+
this.#ngZone.runOutsideAngular(() => {
|
|
1406
|
+
this.withLocalMutation(path, () => {
|
|
1407
|
+
const colorProp = vmi.color(path);
|
|
1408
|
+
if (!colorProp) {
|
|
1409
|
+
this.logger.warn(`Color property "${path}" not found in ViewModel`);
|
|
1410
|
+
this.#ngZone.run(() => this.loadError.emit(new RiveValidationError(formatErrorMessage(RiveErrorCode.DataBindingPropertyNotFound, {
|
|
1411
|
+
path,
|
|
1412
|
+
}), RiveErrorCode.DataBindingPropertyNotFound)));
|
|
1413
|
+
return;
|
|
1414
|
+
}
|
|
1415
|
+
colorProp.opacity(opacity);
|
|
1416
|
+
this.logger.debug(`Color "${path}" opacity set to ${opacity}`);
|
|
1417
|
+
});
|
|
1418
|
+
});
|
|
1419
|
+
}
|
|
1420
|
+
/**
|
|
1421
|
+
* Apply all text runs from input (controlled keys).
|
|
1422
|
+
* Called on every input change or load.
|
|
1423
|
+
*/
|
|
1424
|
+
applyTextRuns(runs) {
|
|
1425
|
+
this.#ngZone.runOutsideAngular(() => {
|
|
1426
|
+
for (const [name, value] of Object.entries(runs)) {
|
|
1427
|
+
try {
|
|
1428
|
+
this.#rive.setTextRunValue(name, value);
|
|
1429
|
+
this.logger.debug(`Text run "${name}" set to "${value}"`);
|
|
1430
|
+
}
|
|
1431
|
+
catch (error) {
|
|
1432
|
+
this.logger.warn(`Failed to set text run "${name}":`, error);
|
|
1433
|
+
this.#ngZone.run(() => this.loadError.emit(new RiveValidationError(formatErrorMessage(RiveErrorCode.TextRunNotFound, { name }), RiveErrorCode.TextRunNotFound)));
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
});
|
|
1437
|
+
}
|
|
1438
|
+
/**
|
|
1439
|
+
* Initialize ViewModel instance if available in the loaded file.
|
|
1440
|
+
* Called once after animation loads successfully.
|
|
1441
|
+
*/
|
|
1442
|
+
initializeViewModel() {
|
|
1443
|
+
if (!this.#rive)
|
|
1444
|
+
return;
|
|
1445
|
+
this.#ngZone.runOutsideAngular(() => {
|
|
1446
|
+
try {
|
|
1447
|
+
const viewModelName = this.viewModelName();
|
|
1448
|
+
let viewModel;
|
|
1449
|
+
// Get ViewModel by name or use default
|
|
1450
|
+
if (viewModelName) {
|
|
1451
|
+
viewModel = this.#rive.viewModelByName(viewModelName);
|
|
1452
|
+
if (!viewModel) {
|
|
1453
|
+
this.logger.warn(`ViewModel "${viewModelName}" not found. Available ViewModels:`, this.getAvailableViewModelNames());
|
|
1454
|
+
this.#ngZone.run(() => this.loadError.emit(new RiveValidationError(formatErrorMessage(RiveErrorCode.ViewModelNotFound, {
|
|
1455
|
+
name: viewModelName,
|
|
1456
|
+
}), RiveErrorCode.ViewModelNotFound, this.getAvailableViewModelNames())));
|
|
1457
|
+
return;
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
else {
|
|
1461
|
+
viewModel = this.#rive.defaultViewModel();
|
|
1462
|
+
}
|
|
1463
|
+
// If no ViewModel found (file doesn't use ViewModels), that's OK
|
|
1464
|
+
if (!viewModel) {
|
|
1465
|
+
this.logger.debug('No ViewModel found in file (file may not use ViewModels)');
|
|
1466
|
+
return;
|
|
1467
|
+
}
|
|
1468
|
+
// Get ViewModel instance
|
|
1469
|
+
const viewModelInstance = viewModel.instance();
|
|
1470
|
+
if (!viewModelInstance) {
|
|
1471
|
+
this.logger.warn('Failed to create ViewModel instance');
|
|
1472
|
+
return;
|
|
1473
|
+
}
|
|
1474
|
+
// Bind to artboard
|
|
1475
|
+
this.#rive.bindViewModelInstance(viewModelInstance);
|
|
1476
|
+
// Update signal
|
|
1477
|
+
this.#ngZone.run(() => {
|
|
1478
|
+
this.#viewModelInstance.set(viewModelInstance);
|
|
1479
|
+
});
|
|
1480
|
+
// Log ViewModel info in debug mode
|
|
1481
|
+
this.logger.debug('ViewModel initialized:', {
|
|
1482
|
+
name: viewModel.name,
|
|
1483
|
+
properties: this.getViewModelPropertyInfo(viewModelInstance),
|
|
1484
|
+
});
|
|
1485
|
+
// Subscribe to ViewModel property changes for two-way binding
|
|
1486
|
+
this.subscribeToViewModelChanges(viewModelInstance);
|
|
1487
|
+
}
|
|
1488
|
+
catch (error) {
|
|
1489
|
+
this.logger.error('Error initializing ViewModel:', error);
|
|
1490
|
+
}
|
|
1491
|
+
});
|
|
1492
|
+
}
|
|
1493
|
+
/**
|
|
1494
|
+
* Get list of available ViewModel names for error messages.
|
|
1495
|
+
*/
|
|
1496
|
+
getAvailableViewModelNames() {
|
|
1497
|
+
if (!this.#rive)
|
|
1498
|
+
return [];
|
|
1499
|
+
const names = [];
|
|
1500
|
+
const count = this.#rive.viewModelCount;
|
|
1501
|
+
for (let i = 0; i < count; i++) {
|
|
1502
|
+
const vm = this.#rive.viewModelByIndex(i);
|
|
1503
|
+
if (vm)
|
|
1504
|
+
names.push(vm.name);
|
|
1505
|
+
}
|
|
1506
|
+
return names;
|
|
1507
|
+
}
|
|
1508
|
+
/**
|
|
1509
|
+
* Get ViewModel property information for debug logging.
|
|
1510
|
+
*/
|
|
1511
|
+
getViewModelPropertyInfo(vmi) {
|
|
1512
|
+
const info = {};
|
|
1513
|
+
try {
|
|
1514
|
+
const properties = vmi.properties;
|
|
1515
|
+
for (const prop of properties) {
|
|
1516
|
+
// Property has name and type
|
|
1517
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1518
|
+
const propAny = prop;
|
|
1519
|
+
const propertyType = this.normalizeViewModelPropertyType(propAny?.type);
|
|
1520
|
+
info[propAny.name || 'unknown'] = propertyType ?? (propAny.type || 'unknown');
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
catch (error) {
|
|
1524
|
+
this.logger.warn('Failed to get ViewModel property info:', error);
|
|
1525
|
+
}
|
|
1526
|
+
return info;
|
|
1527
|
+
}
|
|
1528
|
+
/**
|
|
1529
|
+
* Subscribe to ViewModel property changes for two-way data binding.
|
|
1530
|
+
* Emits dataBindingChange output when properties change from within the animation.
|
|
1531
|
+
*/
|
|
1532
|
+
subscribeToViewModelChanges(vmi) {
|
|
1533
|
+
this.cleanupViewModelSubscriptions();
|
|
1534
|
+
const properties = vmi.properties ?? [];
|
|
1535
|
+
if (!Array.isArray(properties)) {
|
|
1536
|
+
return;
|
|
1537
|
+
}
|
|
1538
|
+
for (const property of properties) {
|
|
1539
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1540
|
+
const propertyAny = property;
|
|
1541
|
+
const path = propertyAny?.name;
|
|
1542
|
+
if (typeof path !== 'string') {
|
|
1543
|
+
continue;
|
|
1544
|
+
}
|
|
1545
|
+
const resolved = this.resolveViewModelProperty(vmi, path);
|
|
1546
|
+
if (!resolved) {
|
|
1547
|
+
continue;
|
|
1548
|
+
}
|
|
1549
|
+
const subscription = this.subscribeToPropertyChanges(path, resolved.type, resolved.accessor);
|
|
1550
|
+
if (subscription) {
|
|
1551
|
+
this.#viewModelSubscriptionDisposers.add(subscription);
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
/**
|
|
1556
|
+
* Subscribe to changes for a specific ViewModel property.
|
|
1557
|
+
* Uses multiple event APIs to maximize compatibility with @rive-app/canvas runtime versions.
|
|
1558
|
+
*/
|
|
1559
|
+
subscribeToPropertyChanges(path, propertyType, property) {
|
|
1560
|
+
const propertyAny = property;
|
|
1561
|
+
if (!propertyAny) {
|
|
1562
|
+
return undefined;
|
|
1563
|
+
}
|
|
1564
|
+
const callback = () => {
|
|
1565
|
+
if (this.shouldSuppressLocalMutation(path)) {
|
|
1566
|
+
return;
|
|
1567
|
+
}
|
|
1568
|
+
const value = this.readPropertyValue(propertyType, propertyAny);
|
|
1569
|
+
if (value === undefined)
|
|
1570
|
+
return;
|
|
1571
|
+
this.#ngZone.run(() => {
|
|
1572
|
+
this.dataBindingChange.emit({
|
|
1573
|
+
path,
|
|
1574
|
+
value,
|
|
1575
|
+
propertyType,
|
|
1576
|
+
});
|
|
1577
|
+
});
|
|
1578
|
+
};
|
|
1579
|
+
let unsubscribe;
|
|
1580
|
+
try {
|
|
1581
|
+
if (typeof propertyAny.on === 'function') {
|
|
1582
|
+
try {
|
|
1583
|
+
const handler = propertyAny.on(callback);
|
|
1584
|
+
unsubscribe = this.buildUnsubscribeFromHandler(propertyAny, callback, handler);
|
|
1585
|
+
}
|
|
1586
|
+
catch {
|
|
1587
|
+
const handler = propertyAny.on('change', callback);
|
|
1588
|
+
unsubscribe = this.buildUnsubscribeFromHandler(propertyAny, callback, handler, true);
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
else if (typeof propertyAny.subscribe === 'function') {
|
|
1592
|
+
const handler = propertyAny.subscribe(callback);
|
|
1593
|
+
unsubscribe = this.buildUnsubscribeFromHandler(propertyAny, callback, handler);
|
|
1594
|
+
}
|
|
1595
|
+
else if (typeof propertyAny.addEventListener === 'function') {
|
|
1596
|
+
propertyAny.addEventListener('change', callback);
|
|
1597
|
+
unsubscribe = () => propertyAny.removeEventListener?.('change', callback);
|
|
1598
|
+
}
|
|
1599
|
+
else if (typeof propertyAny.addListener === 'function') {
|
|
1600
|
+
propertyAny.addListener('change', callback);
|
|
1601
|
+
unsubscribe = () => propertyAny.removeListener?.('change', callback);
|
|
1602
|
+
}
|
|
1603
|
+
else if (typeof propertyAny.onChange === 'function') {
|
|
1604
|
+
const handler = propertyAny.onChange(callback);
|
|
1605
|
+
unsubscribe = this.buildUnsubscribeFromHandler(propertyAny, callback, handler);
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
catch (error) {
|
|
1609
|
+
this.logger.warn(`Failed to subscribe to ViewModel property "${path}":`, error);
|
|
1610
|
+
}
|
|
1611
|
+
if (!unsubscribe) {
|
|
1612
|
+
this.logger.warn(`No supported subscription API found for ViewModel property "${path}"`);
|
|
1613
|
+
}
|
|
1614
|
+
return unsubscribe;
|
|
1615
|
+
}
|
|
1616
|
+
buildUnsubscribeFromHandler(property, callback, handler, useLegacyEventApi) {
|
|
1617
|
+
if (typeof handler === 'function') {
|
|
1618
|
+
return handler;
|
|
1619
|
+
}
|
|
1620
|
+
if (handler && typeof handler === 'object' && 'unsubscribe' in handler && typeof handler.unsubscribe === 'function') {
|
|
1621
|
+
return () => handler.unsubscribe();
|
|
1622
|
+
}
|
|
1623
|
+
if (handler && typeof handler === 'object' && 'dispose' in handler && typeof handler.dispose === 'function') {
|
|
1624
|
+
return () => handler.dispose();
|
|
1625
|
+
}
|
|
1626
|
+
if (typeof property.off === 'function') {
|
|
1627
|
+
return useLegacyEventApi
|
|
1628
|
+
? () => property.off('change', callback)
|
|
1629
|
+
: () => property.off(callback);
|
|
1630
|
+
}
|
|
1631
|
+
if (typeof property.remove === 'function') {
|
|
1632
|
+
return useLegacyEventApi
|
|
1633
|
+
? () => property.remove('change', callback)
|
|
1634
|
+
: () => property.remove(callback);
|
|
1635
|
+
}
|
|
1636
|
+
if (typeof property.removeListener === 'function') {
|
|
1637
|
+
return useLegacyEventApi
|
|
1638
|
+
? () => property.removeListener('change', callback)
|
|
1639
|
+
: () => property.removeListener(callback);
|
|
1640
|
+
}
|
|
1641
|
+
if (typeof property.unsubscribe === 'function') {
|
|
1642
|
+
return () => property.unsubscribe();
|
|
1643
|
+
}
|
|
1644
|
+
return undefined;
|
|
1645
|
+
}
|
|
1646
|
+
withLocalMutation(path, fn) {
|
|
1647
|
+
const previous = this.#localMutationSuppressions.get(path) ?? 0;
|
|
1648
|
+
this.#localMutationSuppressions.set(path, previous + 1);
|
|
1649
|
+
try {
|
|
1650
|
+
fn();
|
|
1651
|
+
}
|
|
1652
|
+
finally {
|
|
1653
|
+
setTimeout(() => {
|
|
1654
|
+
const current = this.#localMutationSuppressions.get(path) ?? 0;
|
|
1655
|
+
if (current <= 1) {
|
|
1656
|
+
this.#localMutationSuppressions.delete(path);
|
|
1657
|
+
}
|
|
1658
|
+
else {
|
|
1659
|
+
this.#localMutationSuppressions.set(path, current - 1);
|
|
1660
|
+
}
|
|
1661
|
+
});
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
shouldSuppressLocalMutation(path) {
|
|
1665
|
+
const current = this.#localMutationSuppressions.get(path);
|
|
1666
|
+
if (current === undefined || current <= 0) {
|
|
1667
|
+
return false;
|
|
1668
|
+
}
|
|
1669
|
+
if (current <= 1) {
|
|
1670
|
+
this.#localMutationSuppressions.delete(path);
|
|
1671
|
+
}
|
|
1672
|
+
else {
|
|
1673
|
+
this.#localMutationSuppressions.set(path, current - 1);
|
|
1674
|
+
}
|
|
1675
|
+
return true;
|
|
1676
|
+
}
|
|
1677
|
+
readPropertyValue(propertyType, property) {
|
|
1678
|
+
if (propertyType === 'color') {
|
|
1679
|
+
if (!property || typeof property.value !== 'number')
|
|
1680
|
+
return undefined;
|
|
1681
|
+
const argb = property.value;
|
|
1682
|
+
const a = (argb >> 24) & 0xff;
|
|
1683
|
+
const r = (argb >> 16) & 0xff;
|
|
1684
|
+
const g = (argb >> 8) & 0xff;
|
|
1685
|
+
const b = argb & 0xff;
|
|
1686
|
+
return { r, g, b, a };
|
|
1687
|
+
}
|
|
1688
|
+
if (propertyType === 'number' && typeof property.value === 'number') {
|
|
1689
|
+
return property.value;
|
|
1690
|
+
}
|
|
1691
|
+
if (propertyType === 'string' && typeof property.value === 'string') {
|
|
1692
|
+
return property.value;
|
|
1693
|
+
}
|
|
1694
|
+
if (propertyType === 'boolean' && typeof property.value === 'boolean') {
|
|
1695
|
+
return property.value;
|
|
1696
|
+
}
|
|
1697
|
+
if (propertyType === 'enum' && typeof property.value === 'string') {
|
|
1698
|
+
return property.value;
|
|
1699
|
+
}
|
|
1700
|
+
if (propertyType === 'trigger') {
|
|
1701
|
+
// Triggers don't have a meaningful value, but we return true to indicate the trigger fired
|
|
1702
|
+
return true;
|
|
1703
|
+
}
|
|
1704
|
+
return undefined;
|
|
1705
|
+
}
|
|
1706
|
+
normalizeViewModelPropertyType(type) {
|
|
1707
|
+
if (typeof type !== 'string') {
|
|
1708
|
+
return null;
|
|
1709
|
+
}
|
|
1710
|
+
const normalized = type.toLowerCase();
|
|
1711
|
+
if (normalized.includes('color'))
|
|
1712
|
+
return 'color';
|
|
1713
|
+
if (normalized.includes('number'))
|
|
1714
|
+
return 'number';
|
|
1715
|
+
if (normalized.includes('string'))
|
|
1716
|
+
return 'string';
|
|
1717
|
+
if (normalized.includes('boolean'))
|
|
1718
|
+
return 'boolean';
|
|
1719
|
+
if (normalized.includes('enum'))
|
|
1720
|
+
return 'enum';
|
|
1721
|
+
if (normalized.includes('trigger'))
|
|
1722
|
+
return 'trigger';
|
|
1723
|
+
return null;
|
|
1724
|
+
}
|
|
1725
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1726
|
+
resolveViewModelProperty(vmi, path) {
|
|
1727
|
+
const color = vmi.color(path);
|
|
1728
|
+
if (color)
|
|
1729
|
+
return { accessor: color, type: 'color' };
|
|
1730
|
+
const number = vmi.number(path);
|
|
1731
|
+
if (number)
|
|
1732
|
+
return { accessor: number, type: 'number' };
|
|
1733
|
+
const string = vmi.string(path);
|
|
1734
|
+
if (string)
|
|
1735
|
+
return { accessor: string, type: 'string' };
|
|
1736
|
+
const bool = vmi.boolean(path);
|
|
1737
|
+
if (bool)
|
|
1738
|
+
return { accessor: bool, type: 'boolean' };
|
|
1739
|
+
const enumProp = vmi.enum(path);
|
|
1740
|
+
if (enumProp)
|
|
1741
|
+
return { accessor: enumProp, type: 'enum' };
|
|
1742
|
+
const trigger = vmi.trigger(path);
|
|
1743
|
+
if (trigger)
|
|
1744
|
+
return { accessor: trigger, type: 'trigger' };
|
|
1745
|
+
return null;
|
|
1746
|
+
}
|
|
1747
|
+
emitDataBindingTypeMismatch(path, expectedType, actualType) {
|
|
1748
|
+
this.#ngZone.run(() => this.loadError.emit(new RiveValidationError(formatErrorMessage(RiveErrorCode.DataBindingTypeMismatch, {
|
|
1749
|
+
path,
|
|
1750
|
+
expected: expectedType,
|
|
1751
|
+
actual: actualType,
|
|
1752
|
+
}), RiveErrorCode.DataBindingTypeMismatch)));
|
|
1753
|
+
}
|
|
1754
|
+
cleanupViewModelSubscriptions() {
|
|
1755
|
+
this.#viewModelSubscriptionDisposers.forEach((disposer) => {
|
|
1756
|
+
try {
|
|
1757
|
+
disposer();
|
|
1758
|
+
}
|
|
1759
|
+
catch (error) {
|
|
1760
|
+
this.logger.warn('Error during ViewModel subscription cleanup:', error);
|
|
1761
|
+
}
|
|
1762
|
+
});
|
|
1763
|
+
this.#viewModelSubscriptionDisposers.clear();
|
|
1764
|
+
}
|
|
1765
|
+
/**
|
|
1766
|
+
* Apply all data bindings from input (controlled keys).
|
|
1767
|
+
* Called on every input change or load.
|
|
1768
|
+
* Auto-detects property type from ViewModel and applies the value accordingly.
|
|
1769
|
+
*/
|
|
1770
|
+
applyDataBindings(bindings) {
|
|
1771
|
+
const vmi = this.#viewModelInstance();
|
|
1772
|
+
if (!vmi)
|
|
1773
|
+
return;
|
|
1774
|
+
this.#ngZone.runOutsideAngular(() => {
|
|
1775
|
+
for (const [path, value] of Object.entries(bindings)) {
|
|
1776
|
+
try {
|
|
1777
|
+
const resolved = this.resolveViewModelProperty(vmi, path);
|
|
1778
|
+
if (!resolved) {
|
|
1779
|
+
this.logger.warn(`Data binding property "${path}" not found in ViewModel`);
|
|
1780
|
+
this.#ngZone.run(() => this.loadError.emit(new RiveValidationError(formatErrorMessage(RiveErrorCode.DataBindingPropertyNotFound, {
|
|
1781
|
+
path,
|
|
1782
|
+
}), RiveErrorCode.DataBindingPropertyNotFound)));
|
|
1783
|
+
continue;
|
|
1784
|
+
}
|
|
1785
|
+
let applied = false;
|
|
1786
|
+
this.withLocalMutation(path, () => {
|
|
1787
|
+
applied = this.tryApplyBinding(path, value, resolved);
|
|
1788
|
+
});
|
|
1789
|
+
if (applied) {
|
|
1790
|
+
this.logger.debug(`Data binding "${path}" set to:`, value);
|
|
1791
|
+
}
|
|
1792
|
+
else {
|
|
1793
|
+
this.logger.warn(`Data binding property "${path}" has a type mismatch for value type ${typeof value}`);
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
catch (error) {
|
|
1797
|
+
this.logger.warn(`Failed to set data binding "${path}":`, error);
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
});
|
|
1801
|
+
}
|
|
1802
|
+
/**
|
|
1803
|
+
* Try to apply a binding value to a resolved ViewModel property.
|
|
1804
|
+
* Returns true if successful, false on type mismatch.
|
|
1805
|
+
*/
|
|
1806
|
+
tryApplyBinding(path, value, resolved) {
|
|
1807
|
+
const { accessor, type } = resolved;
|
|
1808
|
+
if (type === 'color') {
|
|
1809
|
+
if (typeof value === 'object' && value !== null && 'r' in value) {
|
|
1810
|
+
// RiveColor object
|
|
1811
|
+
const color = value;
|
|
1812
|
+
accessor.rgba(color.r, color.g, color.b, color.a);
|
|
1813
|
+
return true;
|
|
1814
|
+
}
|
|
1815
|
+
if (typeof value === 'string' || typeof value === 'number') {
|
|
1816
|
+
// Hex string or ARGB integer
|
|
1817
|
+
const color = parseRiveColor(value);
|
|
1818
|
+
accessor.rgba(color.r, color.g, color.b, color.a);
|
|
1819
|
+
return true;
|
|
1820
|
+
}
|
|
1821
|
+
this.logger.warn(`Invalid color value for "${path}": expected string, number, or RiveColor`);
|
|
1822
|
+
this.emitDataBindingTypeMismatch(path, type, typeof value);
|
|
1823
|
+
return false;
|
|
1824
|
+
}
|
|
1825
|
+
if (type === 'number') {
|
|
1826
|
+
if (typeof value === 'number') {
|
|
1827
|
+
accessor.value = value;
|
|
1828
|
+
return true;
|
|
1829
|
+
}
|
|
1830
|
+
this.logger.warn(`Invalid number value for "${path}": expected number, got ${typeof value}`);
|
|
1831
|
+
this.emitDataBindingTypeMismatch(path, type, typeof value);
|
|
1832
|
+
return false;
|
|
1833
|
+
}
|
|
1834
|
+
if (type === 'string') {
|
|
1835
|
+
if (typeof value === 'string') {
|
|
1836
|
+
accessor.value = value;
|
|
1837
|
+
return true;
|
|
1838
|
+
}
|
|
1839
|
+
this.logger.warn(`Invalid string value for "${path}": expected string, got ${typeof value}`);
|
|
1840
|
+
this.emitDataBindingTypeMismatch(path, type, typeof value);
|
|
1841
|
+
return false;
|
|
1842
|
+
}
|
|
1843
|
+
if (type === 'boolean') {
|
|
1844
|
+
if (typeof value === 'boolean') {
|
|
1845
|
+
accessor.value = value;
|
|
1846
|
+
return true;
|
|
1847
|
+
}
|
|
1848
|
+
this.logger.warn(`Invalid boolean value for "${path}": expected boolean, got ${typeof value}`);
|
|
1849
|
+
this.emitDataBindingTypeMismatch(path, type, typeof value);
|
|
1850
|
+
return false;
|
|
1851
|
+
}
|
|
1852
|
+
if (type === 'enum') {
|
|
1853
|
+
if (typeof value === 'string') {
|
|
1854
|
+
accessor.value = value;
|
|
1855
|
+
return true;
|
|
1856
|
+
}
|
|
1857
|
+
this.logger.warn(`Invalid enum value for "${path}": expected string, got ${typeof value}`);
|
|
1858
|
+
this.emitDataBindingTypeMismatch(path, type, typeof value);
|
|
1859
|
+
return false;
|
|
1860
|
+
}
|
|
1861
|
+
if (type === 'trigger') {
|
|
1862
|
+
this.logger.warn(`Cannot set trigger property "${path}" via setDataBinding`);
|
|
1863
|
+
return false;
|
|
1864
|
+
}
|
|
1865
|
+
return false;
|
|
1866
|
+
}
|
|
936
1867
|
/**
|
|
937
1868
|
* Clean up Rive instance only
|
|
938
1869
|
*/
|
|
939
1870
|
cleanupRive() {
|
|
1871
|
+
this.cleanupViewModelSubscriptions();
|
|
1872
|
+
this.#localMutationSuppressions.clear();
|
|
1873
|
+
const vmi = this.#viewModelInstance();
|
|
1874
|
+
if (vmi) {
|
|
1875
|
+
try {
|
|
1876
|
+
vmi.cleanup();
|
|
1877
|
+
}
|
|
1878
|
+
catch (error) {
|
|
1879
|
+
this.logger.warn('Error during ViewModel cleanup:', error);
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
940
1882
|
if (this.#rive) {
|
|
941
1883
|
try {
|
|
942
1884
|
this.#rive.cleanup();
|
|
@@ -948,12 +1890,13 @@ class RiveCanvasComponent {
|
|
|
948
1890
|
}
|
|
949
1891
|
// Reset signals
|
|
950
1892
|
this.#riveInstance.set(null);
|
|
1893
|
+
this.#viewModelInstance.set(null);
|
|
951
1894
|
this.#isLoaded.set(false);
|
|
952
1895
|
this.#isPlaying.set(false);
|
|
953
1896
|
this.#isPaused.set(false);
|
|
954
1897
|
}
|
|
955
1898
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.4", ngImport: i0, type: RiveCanvasComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
956
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.1.4", type: RiveCanvasComponent, isStandalone: true, selector: "rive-canvas", inputs: { src: { classPropertyName: "src", publicName: "src", isSignal: true, isRequired: false, transformFunction: null }, buffer: { classPropertyName: "buffer", publicName: "buffer", isSignal: true, isRequired: false, transformFunction: null }, riveFile: { classPropertyName: "riveFile", publicName: "riveFile", isSignal: true, isRequired: false, transformFunction: null }, artboard: { classPropertyName: "artboard", publicName: "artboard", isSignal: true, isRequired: false, transformFunction: null }, animations: { classPropertyName: "animations", publicName: "animations", isSignal: true, isRequired: false, transformFunction: null }, stateMachines: { classPropertyName: "stateMachines", publicName: "stateMachines", isSignal: true, isRequired: false, transformFunction: null }, autoplay: { classPropertyName: "autoplay", publicName: "autoplay", isSignal: true, isRequired: false, transformFunction: null }, fit: { classPropertyName: "fit", publicName: "fit", isSignal: true, isRequired: false, transformFunction: null }, alignment: { classPropertyName: "alignment", publicName: "alignment", isSignal: true, isRequired: false, transformFunction: null }, useOffscreenRenderer: { classPropertyName: "useOffscreenRenderer", publicName: "useOffscreenRenderer", isSignal: true, isRequired: false, transformFunction: null }, shouldUseIntersectionObserver: { classPropertyName: "shouldUseIntersectionObserver", publicName: "shouldUseIntersectionObserver", isSignal: true, isRequired: false, transformFunction: null }, shouldDisableRiveListeners: { classPropertyName: "shouldDisableRiveListeners", publicName: "shouldDisableRiveListeners", isSignal: true, isRequired: false, transformFunction: null }, automaticallyHandleEvents: { classPropertyName: "automaticallyHandleEvents", publicName: "automaticallyHandleEvents", isSignal: true, isRequired: false, transformFunction: null }, debugMode: { classPropertyName: "debugMode", publicName: "debugMode", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { loaded: "loaded", loadError: "loadError", stateChange: "stateChange", riveEvent: "riveEvent", riveReady: "riveReady" }, viewQueries: [{ propertyName: "canvas", first: true, predicate: ["canvas"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
1899
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.1.4", type: RiveCanvasComponent, isStandalone: true, selector: "rive-canvas", inputs: { src: { classPropertyName: "src", publicName: "src", isSignal: true, isRequired: false, transformFunction: null }, buffer: { classPropertyName: "buffer", publicName: "buffer", isSignal: true, isRequired: false, transformFunction: null }, riveFile: { classPropertyName: "riveFile", publicName: "riveFile", isSignal: true, isRequired: false, transformFunction: null }, artboard: { classPropertyName: "artboard", publicName: "artboard", isSignal: true, isRequired: false, transformFunction: null }, animations: { classPropertyName: "animations", publicName: "animations", isSignal: true, isRequired: false, transformFunction: null }, stateMachines: { classPropertyName: "stateMachines", publicName: "stateMachines", isSignal: true, isRequired: false, transformFunction: null }, autoplay: { classPropertyName: "autoplay", publicName: "autoplay", isSignal: true, isRequired: false, transformFunction: null }, fit: { classPropertyName: "fit", publicName: "fit", isSignal: true, isRequired: false, transformFunction: null }, alignment: { classPropertyName: "alignment", publicName: "alignment", isSignal: true, isRequired: false, transformFunction: null }, useOffscreenRenderer: { classPropertyName: "useOffscreenRenderer", publicName: "useOffscreenRenderer", isSignal: true, isRequired: false, transformFunction: null }, shouldUseIntersectionObserver: { classPropertyName: "shouldUseIntersectionObserver", publicName: "shouldUseIntersectionObserver", isSignal: true, isRequired: false, transformFunction: null }, shouldDisableRiveListeners: { classPropertyName: "shouldDisableRiveListeners", publicName: "shouldDisableRiveListeners", isSignal: true, isRequired: false, transformFunction: null }, automaticallyHandleEvents: { classPropertyName: "automaticallyHandleEvents", publicName: "automaticallyHandleEvents", isSignal: true, isRequired: false, transformFunction: null }, debugMode: { classPropertyName: "debugMode", publicName: "debugMode", isSignal: true, isRequired: false, transformFunction: null }, textRuns: { classPropertyName: "textRuns", publicName: "textRuns", isSignal: true, isRequired: false, transformFunction: null }, viewModelName: { classPropertyName: "viewModelName", publicName: "viewModelName", isSignal: true, isRequired: false, transformFunction: null }, dataBindings: { classPropertyName: "dataBindings", publicName: "dataBindings", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { loaded: "loaded", loadError: "loadError", stateChange: "stateChange", riveEvent: "riveEvent", riveReady: "riveReady", dataBindingChange: "dataBindingChange" }, viewQueries: [{ propertyName: "canvas", first: true, predicate: ["canvas"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
957
1900
|
<canvas #canvas [style.width.%]="100" [style.height.%]="100"></canvas>
|
|
958
1901
|
`, isInline: true, styles: [":host{display:block;width:100%;height:100%}canvas{display:block}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
959
1902
|
}
|
|
@@ -962,7 +1905,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.4", ngImpor
|
|
|
962
1905
|
args: [{ selector: 'rive-canvas', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
963
1906
|
<canvas #canvas [style.width.%]="100" [style.height.%]="100"></canvas>
|
|
964
1907
|
`, styles: [":host{display:block;width:100%;height:100%}canvas{display:block}\n"] }]
|
|
965
|
-
}], ctorParameters: () => [], propDecorators: { canvas: [{ type: i0.ViewChild, args: ['canvas', { isSignal: true }] }], src: [{ type: i0.Input, args: [{ isSignal: true, alias: "src", required: false }] }], buffer: [{ type: i0.Input, args: [{ isSignal: true, alias: "buffer", required: false }] }], riveFile: [{ type: i0.Input, args: [{ isSignal: true, alias: "riveFile", required: false }] }], artboard: [{ type: i0.Input, args: [{ isSignal: true, alias: "artboard", required: false }] }], animations: [{ type: i0.Input, args: [{ isSignal: true, alias: "animations", required: false }] }], stateMachines: [{ type: i0.Input, args: [{ isSignal: true, alias: "stateMachines", required: false }] }], autoplay: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoplay", required: false }] }], fit: [{ type: i0.Input, args: [{ isSignal: true, alias: "fit", required: false }] }], alignment: [{ type: i0.Input, args: [{ isSignal: true, alias: "alignment", required: false }] }], useOffscreenRenderer: [{ type: i0.Input, args: [{ isSignal: true, alias: "useOffscreenRenderer", required: false }] }], shouldUseIntersectionObserver: [{ type: i0.Input, args: [{ isSignal: true, alias: "shouldUseIntersectionObserver", required: false }] }], shouldDisableRiveListeners: [{ type: i0.Input, args: [{ isSignal: true, alias: "shouldDisableRiveListeners", required: false }] }], automaticallyHandleEvents: [{ type: i0.Input, args: [{ isSignal: true, alias: "automaticallyHandleEvents", required: false }] }], debugMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "debugMode", required: false }] }], loaded: [{ type: i0.Output, args: ["loaded"] }], loadError: [{ type: i0.Output, args: ["loadError"] }], stateChange: [{ type: i0.Output, args: ["stateChange"] }], riveEvent: [{ type: i0.Output, args: ["riveEvent"] }], riveReady: [{ type: i0.Output, args: ["riveReady"] }] } });
|
|
1908
|
+
}], ctorParameters: () => [], propDecorators: { canvas: [{ type: i0.ViewChild, args: ['canvas', { isSignal: true }] }], src: [{ type: i0.Input, args: [{ isSignal: true, alias: "src", required: false }] }], buffer: [{ type: i0.Input, args: [{ isSignal: true, alias: "buffer", required: false }] }], riveFile: [{ type: i0.Input, args: [{ isSignal: true, alias: "riveFile", required: false }] }], artboard: [{ type: i0.Input, args: [{ isSignal: true, alias: "artboard", required: false }] }], animations: [{ type: i0.Input, args: [{ isSignal: true, alias: "animations", required: false }] }], stateMachines: [{ type: i0.Input, args: [{ isSignal: true, alias: "stateMachines", required: false }] }], autoplay: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoplay", required: false }] }], fit: [{ type: i0.Input, args: [{ isSignal: true, alias: "fit", required: false }] }], alignment: [{ type: i0.Input, args: [{ isSignal: true, alias: "alignment", required: false }] }], useOffscreenRenderer: [{ type: i0.Input, args: [{ isSignal: true, alias: "useOffscreenRenderer", required: false }] }], shouldUseIntersectionObserver: [{ type: i0.Input, args: [{ isSignal: true, alias: "shouldUseIntersectionObserver", required: false }] }], shouldDisableRiveListeners: [{ type: i0.Input, args: [{ isSignal: true, alias: "shouldDisableRiveListeners", required: false }] }], automaticallyHandleEvents: [{ type: i0.Input, args: [{ isSignal: true, alias: "automaticallyHandleEvents", required: false }] }], debugMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "debugMode", required: false }] }], textRuns: [{ type: i0.Input, args: [{ isSignal: true, alias: "textRuns", required: false }] }], viewModelName: [{ type: i0.Input, args: [{ isSignal: true, alias: "viewModelName", required: false }] }], dataBindings: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataBindings", required: false }] }], loaded: [{ type: i0.Output, args: ["loaded"] }], loadError: [{ type: i0.Output, args: ["loadError"] }], stateChange: [{ type: i0.Output, args: ["stateChange"] }], riveEvent: [{ type: i0.Output, args: ["riveEvent"] }], riveReady: [{ type: i0.Output, args: ["riveReady"] }], dataBindingChange: [{ type: i0.Output, args: ["dataBindingChange"] }] } });
|
|
966
1909
|
|
|
967
1910
|
/**
|
|
968
1911
|
* Service for preloading and caching Rive files.
|
|
@@ -1177,5 +2120,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.4", ngImpor
|
|
|
1177
2120
|
* Generated bundle index. Do not edit.
|
|
1178
2121
|
*/
|
|
1179
2122
|
|
|
1180
|
-
export { RiveCanvasComponent, RiveErrorCode, RiveFileService, RiveLoadError, RiveValidationError, provideRiveDebug };
|
|
2123
|
+
export { RiveCanvasComponent, RiveErrorCode, RiveFileService, RiveLoadError, RiveValidationError, parseRiveColor, provideRiveDebug, riveColorToArgb, riveColorToHex };
|
|
1181
2124
|
//# sourceMappingURL=grandgular-rive-angular.mjs.map
|