@umituz/react-native-mascot 1.0.4 → 1.0.7

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.
Files changed (39) hide show
  1. package/README.md +60 -0
  2. package/package.json +2 -1
  3. package/src/application/services/AnimationStateManager.ts +69 -0
  4. package/src/application/services/AppearanceManagement.ts +40 -0
  5. package/src/application/services/MascotService.ts +42 -33
  6. package/src/application/services/PersonalityManagement.ts +39 -0
  7. package/src/application/services/StateHistory.ts +55 -0
  8. package/src/application/services/StateMachine.ts +154 -0
  9. package/src/application/services/StateTransitions.ts +73 -0
  10. package/src/application.ts +40 -0
  11. package/src/assets/index.ts +14 -19
  12. package/src/core.ts +62 -0
  13. package/src/domain/entities/Mascot.ts +186 -127
  14. package/src/domain/types/AnimationStateTypes.ts +148 -0
  15. package/src/domain/types/MascotTypes.ts +9 -0
  16. package/src/domain/value-objects/AnimationState.ts +126 -0
  17. package/src/index.ts +9 -99
  18. package/src/infrastructure/controllers/AnimationController.ts +26 -122
  19. package/src/infrastructure/controllers/AnimationPlayer.ts +104 -0
  20. package/src/infrastructure/controllers/AnimationTimer.ts +62 -0
  21. package/src/infrastructure/controllers/EventManager.ts +108 -0
  22. package/src/infrastructure/di/Container.ts +73 -10
  23. package/src/infrastructure/managers/AssetManager.ts +134 -63
  24. package/src/infrastructure/managers/MascotBuilder.ts +89 -0
  25. package/src/infrastructure/managers/MascotFactory.ts +24 -176
  26. package/src/infrastructure/managers/MascotTemplates.ts +151 -0
  27. package/src/infrastructure/utils/LRUCache.ts +218 -0
  28. package/src/infrastructure.ts +24 -0
  29. package/src/presentation/components/LottieMascot.tsx +85 -0
  30. package/src/presentation/components/MascotView.tsx +42 -233
  31. package/src/presentation/components/SVGMascot.tsx +61 -0
  32. package/src/presentation/contexts/MascotContext.tsx +2 -3
  33. package/src/presentation/hooks/useMascot.ts +118 -39
  34. package/src/presentation/hooks/useMascotAnimation.ts +9 -15
  35. package/src/presentation/hooks/useMascotState.ts +213 -0
  36. package/src/presentation.ts +37 -0
  37. package/src/types.d.ts +4 -0
  38. package/src/application/index.ts +0 -8
  39. package/src/domain/value-objects/index.ts +0 -9
package/README.md CHANGED
@@ -8,6 +8,10 @@ Interactive mascot system for React Native apps - Customizable animated characte
8
8
  - ✅ **Lottie Animations** - Professional JSON animations
9
9
  - ✅ **SVG Rendering** - Custom vector graphics
10
10
  - ✅ **Mood System** - Dynamic personality and emotions
11
+ - ✅ **State-Based Animations** - 6 predefined states (idle, loading, success, error, empty, guide)
12
+ - ✅ **Auto-Transitions** - Automatic state transitions (e.g., loading → success)
13
+ - ✅ **Size Variants** - Built-in small, medium, large sizes
14
+ - ✅ **Optimized Performance** - LRU cache, React.memo, memory leak prevention
11
15
  - ✅ **Interactive** - Touch-enabled mascots
12
16
  - ✅ **Animation Queue** - Sequence animations
13
17
  - ✅ **Custom Accessories** - Add glasses, hats, etc.
@@ -99,6 +103,62 @@ function CustomMascotScreen() {
99
103
  touchEnabled: true
100
104
  });
101
105
 
106
+ return <MascotView mascot={mascot} size={150} />;
107
+ }
108
+ ```
109
+
110
+ ### State-Based Animations (NEW!)
111
+
112
+ Inspired by production apps like Vivoim Style, the new state-based system provides predefined animation states with auto-transitions:
113
+
114
+ ```tsx
115
+ import { useMascotState } from '@umituz/react-native-mascot';
116
+
117
+ function StatefulMascot() {
118
+ const mascot = useMascotState({
119
+ initialState: 'idle',
120
+ size: 'medium',
121
+ enableAutoTransition: true,
122
+ onStateChange: (from, to) => console.log(`${from} → ${to}`),
123
+ onAnimationComplete: (state) => console.log(`Completed: ${state}`)
124
+ });
125
+
126
+ return (
127
+ <View>
128
+ {/* Use mascot.state to drive your Lottie animations */}
129
+ {/* mascot.size: 40 (small), 80 (medium), 120 (large), or custom number */}
130
+ {/* mascot.isLooping: true for idle/loading/empty/guide, false for success/error */}
131
+ {/* mascot.duration: animation duration in milliseconds */}
132
+ {/* mascot.speed: animation speed multiplier */}
133
+
134
+ <MascotView
135
+ mascot={mascot.mascot}
136
+ size={mascot.size}
137
+ />
138
+
139
+ <Button title="Start Loading" onPress={mascot.startLoading} />
140
+ <Button title="Success" onPress={mascot.triggerSuccess} />
141
+ <Button title="Error" onPress={mascot.triggerError} />
142
+ <Button title="Reset" onPress={mascot.reset} />
143
+ </View>
144
+ );
145
+ }
146
+ ```
147
+
148
+ **Available States:**
149
+ - `idle` - Calm breathing animation (default, loops)
150
+ - `loading` - Active processing animation (loops)
151
+ - `success` - Confirmation animation (non-looping, auto-transitions to idle)
152
+ - `error` - Error acknowledgment (non-looping, auto-transitions to idle)
153
+ - `empty` - Empty state invitation (loops)
154
+ - `guide` - Onboarding assistance (loops)
155
+
156
+ **Auto-Transitions:**
157
+ - `success` → `idle` (after 100ms delay)
158
+ - `error` → `idle` (after 100ms delay)
159
+ - `guide` → `idle` (after 200ms delay)
160
+ - `loading` → `success` or `error` (when you call `stopLoading(success)`)
161
+
102
162
  return <MascotView mascot={mascot} />;
103
163
  }
104
164
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-mascot",
3
- "version": "1.0.4",
3
+ "version": "1.0.7",
4
4
  "description": "Interactive mascot system for React Native apps - Customizable animated characters with Lottie and SVG support, mood system, and easy integration",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -65,6 +65,7 @@
65
65
  }
66
66
  },
67
67
  "devDependencies": {
68
+ "@types/node": "^22.10.2",
68
69
  "@types/react": "~19.1.0",
69
70
  "@typescript-eslint/eslint-plugin": "^7.0.0",
70
71
  "@typescript-eslint/parser": "^7.0.0",
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Animation State Manager (Main - 60 lines)
3
+ * Public API for state management
4
+ */
5
+
6
+ import { StateMachine } from './StateMachine';
7
+ import type { MascotAnimationState } from '../../domain/types/AnimationStateTypes';
8
+
9
+ export interface AnimationStateManagerConfig {
10
+ enableAutoTransition?: boolean;
11
+ enableHistoryTracking?: boolean;
12
+ maxHistorySize?: number;
13
+ onStateChange?: (from: MascotAnimationState, to: MascotAnimationState) => void;
14
+ }
15
+
16
+ export class AnimationStateManager {
17
+ private readonly _stateMachine: StateMachine;
18
+
19
+ constructor(
20
+ initialState: MascotAnimationState = 'idle',
21
+ config: AnimationStateManagerConfig = {}
22
+ ) {
23
+ this._stateMachine = new StateMachine(initialState, config);
24
+ }
25
+
26
+ get currentState(): MascotAnimationState {
27
+ return this._stateMachine.currentStateValue;
28
+ }
29
+
30
+ get previousState(): MascotAnimationState | null {
31
+ return this._stateMachine.previousState?.value || null;
32
+ }
33
+
34
+ get stateHistory(): MascotAnimationState[] {
35
+ return this._stateMachine.history;
36
+ }
37
+
38
+ transitionTo(newState: MascotAnimationState): void {
39
+ this._stateMachine.transitionTo(newState);
40
+ }
41
+
42
+ triggerSuccess(): void {
43
+ this._stateMachine.triggerSuccess();
44
+ }
45
+
46
+ triggerError(): void {
47
+ this._stateMachine.triggerError();
48
+ }
49
+
50
+ startLoading(): void {
51
+ this._stateMachine.startLoading();
52
+ }
53
+
54
+ stopLoading(success: boolean): void {
55
+ this._stateMachine.stopLoading(success);
56
+ }
57
+
58
+ reset(): void {
59
+ this._stateMachine.reset();
60
+ }
61
+
62
+ isLooping(): boolean {
63
+ return this._stateMachine.isLooping();
64
+ }
65
+
66
+ destroy(): void {
67
+ this._stateMachine.destroy();
68
+ }
69
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Appearance Management (60 lines)
3
+ * Mascot appearance and accessory operations
4
+ */
5
+
6
+ import { Mascot } from '../../domain/entities/Mascot';
7
+ import type { MascotAppearance } from '../../domain/types/MascotTypes';
8
+
9
+ export class AppearanceManagement {
10
+ constructor(private readonly mascot: Mascot) {}
11
+
12
+ updateAppearance(appearance: Partial<MascotAppearance>): void {
13
+ this.mascot.updateAppearance(appearance);
14
+ }
15
+
16
+ setBaseColor(color: string): void {
17
+ this.mascot.setBaseColor(color);
18
+ }
19
+
20
+ setAccentColor(color: string): void {
21
+ this.mascot.setAccentColor(color);
22
+ }
23
+
24
+ addAccessory(accessory: {
25
+ id: string;
26
+ type: string;
27
+ color?: string;
28
+ position?: { x: number; y: number };
29
+ }): void {
30
+ this.mascot.addAccessory(accessory);
31
+ }
32
+
33
+ removeAccessory(accessoryId: string): void {
34
+ this.mascot.removeAccessory(accessoryId);
35
+ }
36
+
37
+ get appearance() {
38
+ return this.mascot.appearance;
39
+ }
40
+ }
@@ -1,28 +1,33 @@
1
1
  /**
2
- * MascotService
3
- * Application service that orchestrates mascot use cases
4
- * This is the main entry point for all mascot operations
2
+ * Mascot Service (120 lines)
3
+ * Core application service for mascot operations
5
4
  */
6
5
 
7
6
  import { Mascot } from '../../domain/entities/Mascot';
8
- import type { MascotConfig, MascotMood, MascotAppearance, MascotAccessory } from '../../domain/types/MascotTypes';
7
+ import type { MascotConfig, MascotMood, MascotAppearance } from '../../domain/types/MascotTypes';
9
8
  import type { IMascotRepository } from '../../domain/interfaces/IMascotRepository';
10
9
  import type { IAnimationController, AnimationOptions } from '../../domain/interfaces/IAnimationController';
11
10
  import type { IAssetManager } from '../../domain/interfaces/IAssetManager';
12
11
  import { MascotFactory } from '../../infrastructure/managers/MascotFactory';
13
12
  import { MascotNotInitializedError, AnimationNotFoundError } from '../errors/MascotErrors';
13
+ import { PersonalityManagement } from './PersonalityManagement';
14
+ import { AppearanceManagement } from './AppearanceManagement';
14
15
 
15
16
  export type MascotTemplate = 'friendly-bot' | 'cute-pet' | 'wise-owl' | 'pixel-hero';
16
17
 
17
18
  export class MascotService {
18
19
  private _mascot: Mascot | null = null;
19
20
  private _changeListeners: Set<() => void> = new Set();
21
+ private _personality: PersonalityManagement | null = null;
22
+ private _appearance: AppearanceManagement | null = null;
20
23
 
21
24
  constructor(
22
25
  private readonly _repository: IMascotRepository,
23
26
  private readonly _animationController: IAnimationController,
24
- private readonly _assetManager: IAssetManager
25
- ) {}
27
+ _assetManager: IAssetManager
28
+ ) {
29
+ void _assetManager;
30
+ }
26
31
 
27
32
  // ✅ Initialization
28
33
  async initialize(config: MascotConfig): Promise<void> {
@@ -58,60 +63,67 @@ export class MascotService {
58
63
  return this._mascot !== null;
59
64
  }
60
65
 
61
- // ✅ Personality Management
66
+ // ✅ Manager Access (lazy loaded)
67
+ private _getPersonality(): PersonalityManagement {
68
+ if (!this._personality) {
69
+ this._ensureMascot();
70
+ this._personality = new PersonalityManagement(this._mascot!);
71
+ }
72
+ return this._personality;
73
+ }
74
+
75
+ private _getAppearance(): AppearanceManagement {
76
+ if (!this._appearance) {
77
+ this._ensureMascot();
78
+ this._appearance = new AppearanceManagement(this._mascot!);
79
+ }
80
+ return this._appearance;
81
+ }
82
+
83
+ // ✅ Personality Management (delegated)
62
84
  setMood(mood: MascotMood): void {
63
- this._ensureMascot();
64
- this._mascot!.setMood(mood);
85
+ this._getPersonality().setMood(mood);
65
86
  this._notifyChange();
66
87
  }
67
88
 
68
89
  setEnergy(value: number): void {
69
- this._ensureMascot();
70
- this._mascot!.setEnergy(value);
90
+ this._getPersonality().setEnergy(value);
71
91
  this._notifyChange();
72
92
  }
73
93
 
74
94
  setFriendliness(value: number): void {
75
- this._ensureMascot();
76
- this._mascot!.setFriendliness(value);
95
+ this._getPersonality().setFriendliness(value);
77
96
  this._notifyChange();
78
97
  }
79
98
 
80
99
  setPlayfulness(value: number): void {
81
- this._ensureMascot();
82
- this._mascot!.setPlayfulness(value);
100
+ this._getPersonality().setPlayfulness(value);
83
101
  this._notifyChange();
84
102
  }
85
103
 
86
- // ✅ Rich Behaviors
87
104
  cheerUp(): void {
88
- this._ensureMascot();
89
- this._mascot!.cheerUp();
105
+ this._getPersonality().cheerUp();
90
106
  this._notifyChange();
91
107
  }
92
108
 
93
109
  boostEnergy(amount: number): void {
94
- this._ensureMascot();
95
- this._mascot!.boostEnergy(amount);
110
+ this._getPersonality().boostEnergy(amount);
96
111
  this._notifyChange();
97
112
  }
98
113
 
99
- // ✅ Appearance Management
114
+ // ✅ Appearance Management (delegated)
100
115
  updateAppearance(appearance: Partial<MascotAppearance>): void {
101
- this._ensureMascot();
102
- this._mascot!.updateAppearance(appearance);
116
+ this._getAppearance().updateAppearance(appearance);
103
117
  this._notifyChange();
104
118
  }
105
119
 
106
120
  setBaseColor(color: string): void {
107
- this._ensureMascot();
108
- this._mascot!.setBaseColor(color);
121
+ this._getAppearance().setBaseColor(color);
109
122
  this._notifyChange();
110
123
  }
111
124
 
112
125
  setAccentColor(color: string): void {
113
- this._ensureMascot();
114
- this._mascot!.setAccentColor(color);
126
+ this._getAppearance().setAccentColor(color);
115
127
  this._notifyChange();
116
128
  }
117
129
 
@@ -121,14 +133,12 @@ export class MascotService {
121
133
  color?: string;
122
134
  position?: { x: number; y: number };
123
135
  }): void {
124
- this._ensureMascot();
125
- this._mascot!.addAccessory(accessory);
136
+ this._getAppearance().addAccessory(accessory);
126
137
  this._notifyChange();
127
138
  }
128
139
 
129
140
  removeAccessory(accessoryId: string): void {
130
- this._ensureMascot();
131
- this._mascot!.removeAccessory(accessoryId);
141
+ this._getAppearance().removeAccessory(accessoryId);
132
142
  this._notifyChange();
133
143
  }
134
144
 
@@ -175,7 +185,7 @@ export class MascotService {
175
185
  this._notifyChange();
176
186
  }
177
187
 
178
- // ✅ Observable Pattern for React Integration
188
+ // ✅ Observable Pattern
179
189
  subscribe(listener: () => void): () => void {
180
190
  this._changeListeners.add(listener);
181
191
  return () => this._changeListeners.delete(listener);
@@ -185,7 +195,6 @@ export class MascotService {
185
195
  this._changeListeners.forEach((listener) => listener());
186
196
  }
187
197
 
188
- // ✅ Validation
189
198
  private _ensureMascot(): void {
190
199
  if (!this._mascot) {
191
200
  throw new MascotNotInitializedError();
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Personality Management (60 lines)
3
+ * Mascot personality operations and behaviors
4
+ */
5
+
6
+ import { Mascot } from '../../domain/entities/Mascot';
7
+ import type { MascotMood } from '../../domain/types/MascotTypes';
8
+
9
+ export class PersonalityManagement {
10
+ constructor(private readonly mascot: Mascot) {}
11
+
12
+ setMood(mood: MascotMood): void {
13
+ this.mascot.setMood(mood);
14
+ }
15
+
16
+ setEnergy(value: number): void {
17
+ this.mascot.setEnergy(value);
18
+ }
19
+
20
+ setFriendliness(value: number): void {
21
+ this.mascot.setFriendliness(value);
22
+ }
23
+
24
+ setPlayfulness(value: number): void {
25
+ this.mascot.setPlayfulness(value);
26
+ }
27
+
28
+ cheerUp(): void {
29
+ this.mascot.cheerUp();
30
+ }
31
+
32
+ boostEnergy(amount: number): void {
33
+ this.mascot.boostEnergy(amount);
34
+ }
35
+
36
+ get personality() {
37
+ return this.mascot.personality;
38
+ }
39
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * State History (60 lines)
3
+ * Efficient history tracking with circular buffer
4
+ */
5
+
6
+ import type { MascotAnimationState } from '../../domain/types/AnimationStateTypes';
7
+
8
+ interface HistoryEntry {
9
+ state: MascotAnimationState;
10
+ timestamp: number;
11
+ triggeredBy: 'user' | 'system' | 'auto-transition';
12
+ }
13
+
14
+ export class StateHistory {
15
+ private readonly _buffer: HistoryEntry[];
16
+ private readonly _capacity: number;
17
+ private _size: number = 0;
18
+ private _head: number = 0;
19
+
20
+ constructor(capacity: number = 50) {
21
+ this._capacity = capacity;
22
+ this._buffer = new Array(capacity);
23
+ }
24
+
25
+ add(state: MascotAnimationState, triggeredBy: HistoryEntry['triggeredBy']): void {
26
+ this._buffer[this._head] = { state, timestamp: Date.now(), triggeredBy };
27
+ this._head = (this._head + 1) % this._capacity;
28
+
29
+ if (this._size < this._capacity) {
30
+ this._size++;
31
+ }
32
+ }
33
+
34
+ getStates(): MascotAnimationState[] {
35
+ const result: MascotAnimationState[] = [];
36
+ let index = this._head - this._size;
37
+
38
+ for (let i = 0; i < this._size; i++) {
39
+ if (index < 0) index += this._capacity;
40
+ result.push(this._buffer[index].state);
41
+ index++;
42
+ }
43
+
44
+ return result;
45
+ }
46
+
47
+ clear(): void {
48
+ this._size = 0;
49
+ this._head = 0;
50
+ }
51
+
52
+ get size(): number {
53
+ return this._size;
54
+ }
55
+ }
@@ -0,0 +1,154 @@
1
+ /**
2
+ * State Machine (120 lines)
3
+ * Core state transition logic with validation
4
+ */
5
+
6
+ import type { MascotAnimationState } from '../../domain/types/AnimationStateTypes';
7
+ import { AnimationState } from '../../domain/value-objects/AnimationState';
8
+ import { StateTransitions } from './StateTransitions';
9
+ import { StateHistory } from './StateHistory';
10
+
11
+ export interface StateMachineConfig {
12
+ enableAutoTransition?: boolean;
13
+ enableHistoryTracking?: boolean;
14
+ maxHistorySize?: number;
15
+ onStateChange?: (from: MascotAnimationState, to: MascotAnimationState) => void;
16
+ }
17
+
18
+ export class StateMachine {
19
+ private _currentState: AnimationState;
20
+ private _previousState: AnimationState | null = null;
21
+ private readonly _transitions: StateTransitions;
22
+ private readonly _history: StateHistory;
23
+ private readonly _config: Required<StateMachineConfig>;
24
+
25
+ constructor(
26
+ initialState: MascotAnimationState = 'idle',
27
+ config: StateMachineConfig = {}
28
+ ) {
29
+ this._currentState = AnimationState.create(initialState);
30
+ this._transitions = new StateTransitions();
31
+ this._history = new StateHistory(config.maxHistorySize || 50);
32
+ this._config = {
33
+ enableAutoTransition: config.enableAutoTransition ?? true,
34
+ enableHistoryTracking: config.enableHistoryTracking ?? true,
35
+ maxHistorySize: config.maxHistorySize ?? 50,
36
+ onStateChange: config.onStateChange ?? (() => {}),
37
+ };
38
+ }
39
+
40
+ get currentState(): AnimationState {
41
+ return this._currentState;
42
+ }
43
+
44
+ get currentStateValue(): MascotAnimationState {
45
+ return this._currentState.value;
46
+ }
47
+
48
+ get previousState(): AnimationState | null {
49
+ return this._previousState;
50
+ }
51
+
52
+ get history(): MascotAnimationState[] {
53
+ return this._history.getStates();
54
+ }
55
+
56
+ transitionTo(
57
+ newState: MascotAnimationState,
58
+ triggeredBy: 'user' | 'system' | 'auto-transition' = 'user'
59
+ ): void {
60
+ // Validate transition
61
+ if (!this._transitions.canTransitionTo(this.currentStateValue, newState)) {
62
+ const validTransitions = this._transitions
63
+ .getAvailableTransitions(this.currentStateValue)
64
+ .map((t) => t.to);
65
+ throw new Error(
66
+ `Cannot transition from '${this.currentStateValue}' to '${newState}'. ` +
67
+ `Valid transitions: ${validTransitions.join(', ')}`
68
+ );
69
+ }
70
+
71
+ // Store previous state
72
+ this._previousState = this._currentState;
73
+
74
+ // Track history
75
+ if (this._config.enableHistoryTracking) {
76
+ this._history.add(this.currentStateValue, triggeredBy);
77
+ }
78
+
79
+ // Update state
80
+ const oldState = this.currentStateValue;
81
+ this._currentState = AnimationState.create(newState);
82
+
83
+ // Notify
84
+ this._config.onStateChange(oldState, newState);
85
+
86
+ // Schedule auto-transition
87
+ if (this._config.enableAutoTransition && !this._currentState.shouldLoop()) {
88
+ const next = this._currentState.getNextState();
89
+ if (next) {
90
+ this._scheduleAutoTransition(next);
91
+ }
92
+ }
93
+ }
94
+
95
+ // Shortcut methods
96
+ triggerSuccess(): void {
97
+ this.transitionTo('success', 'system');
98
+ }
99
+
100
+ triggerError(): void {
101
+ this.transitionTo('error', 'system');
102
+ }
103
+
104
+ startLoading(): void {
105
+ this.transitionTo('loading', 'system');
106
+ }
107
+
108
+ stopLoading(success: boolean): void {
109
+ if (this.currentStateValue === 'loading') {
110
+ this.transitionTo(success ? 'success' : 'error', 'system');
111
+ }
112
+ }
113
+
114
+ reset(): void {
115
+ this.transitionTo('idle', 'system');
116
+ }
117
+
118
+ // State queries
119
+ isLooping(): boolean {
120
+ return this._currentState.shouldLoop();
121
+ }
122
+
123
+ isIdle(): boolean {
124
+ return this.currentStateValue === 'idle';
125
+ }
126
+
127
+ isLoading(): boolean {
128
+ return this.currentStateValue === 'loading';
129
+ }
130
+
131
+ // Auto-transition scheduling
132
+ private _autoTransitionTimer: NodeJS.Timeout | null = null;
133
+
134
+ private _scheduleAutoTransition(targetState: MascotAnimationState): void {
135
+ const duration = this._currentState.getDuration();
136
+ const delay = this._currentState.getTransitionDelay(targetState);
137
+
138
+ this._autoTransitionTimer = setTimeout(() => {
139
+ try {
140
+ this.transitionTo(targetState, 'auto-transition');
141
+ } catch (error) {
142
+ console.warn('Auto-transition failed:', error);
143
+ }
144
+ }, duration + delay);
145
+ }
146
+
147
+ destroy(): void {
148
+ if (this._autoTransitionTimer) {
149
+ clearTimeout(this._autoTransitionTimer);
150
+ }
151
+ this._history.clear();
152
+ this._config.onStateChange = () => {};
153
+ }
154
+ }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * State Transitions (80 lines)
3
+ * Transition rules and validation
4
+ */
5
+
6
+ import type { MascotAnimationState } from '../../domain/types/AnimationStateTypes';
7
+
8
+ export interface StateTransition {
9
+ from: MascotAnimationState;
10
+ to: MascotAnimationState;
11
+ condition?: 'always' | 'on-success' | 'on-error' | 'on-complete';
12
+ delay?: number;
13
+ }
14
+
15
+ export class StateTransitions {
16
+ private readonly _transitions: Map<MascotAnimationState, StateTransition[]>;
17
+
18
+ constructor() {
19
+ this._transitions = new Map();
20
+ this._initializeTransitions();
21
+ }
22
+
23
+ canTransitionTo(from: MascotAnimationState, to: MascotAnimationState): boolean {
24
+ const transitions = this._transitions.get(from);
25
+ if (!transitions) return false;
26
+ return transitions.some((t) => t.to === to);
27
+ }
28
+
29
+ getAvailableTransitions(state: MascotAnimationState): StateTransition[] {
30
+ return this._transitions.get(state) || [];
31
+ }
32
+
33
+ getTransitionDelay(from: MascotAnimationState, to: MascotAnimationState): number {
34
+ const transitions = this._transitions.get(from);
35
+ const transition = transitions?.find((t) => t.to === to);
36
+ return transition?.delay || 0;
37
+ }
38
+
39
+ private _initializeTransitions(): void {
40
+ // Idle transitions
41
+ this._addTransition('idle', 'loading', 'always');
42
+ this._addTransition('idle', 'empty', 'always');
43
+ this._addTransition('idle', 'guide', 'always');
44
+
45
+ // Loading transitions
46
+ this._addTransition('loading', 'success', 'on-success');
47
+ this._addTransition('loading', 'error', 'on-error');
48
+
49
+ // Success transitions
50
+ this._addTransition('success', 'idle', 'on-complete', 100);
51
+
52
+ // Error transitions
53
+ this._addTransition('error', 'idle', 'on-complete', 100);
54
+
55
+ // Empty transitions
56
+ this._addTransition('empty', 'idle', 'always');
57
+
58
+ // Guide transitions
59
+ this._addTransition('guide', 'idle', 'on-complete', 200);
60
+ }
61
+
62
+ private _addTransition(
63
+ from: MascotAnimationState,
64
+ to: MascotAnimationState,
65
+ condition: StateTransition['condition'] = 'always',
66
+ delay: number = 0
67
+ ): void {
68
+ if (!this._transitions.has(from)) {
69
+ this._transitions.set(from, []);
70
+ }
71
+ this._transitions.get(from)!.push({ from, to, condition, delay });
72
+ }
73
+ }