@@ -13,65 +13,38 @@ import { colorNames, interpolation as interp, now } from './Globals'
13
13
import { SpringProps } from '../../types/renderprops'
14
14
15
15
type Animation = any
16
+ type AnimationMap = { [ name : string ] : Animation }
17
+ type InterpolationMap = { [ name : string ] : AnimatedValue | AnimatedValueArray }
16
18
17
- type ControllerProps < DS extends object > = DS &
19
+ type UpdateProps < DS extends object > = DS &
18
20
SpringProps < DS > & {
19
21
attach ?: ( ctrl : Controller ) => Animation
20
22
}
21
23
22
- type AnimationsFor < P > = { [ Key in keyof P ] : any }
23
-
24
- type ValuesFor < P > = { [ Key in keyof P ] : any }
25
-
26
- type InterpolationsFor < DS > = {
27
- [ K in keyof DS ] : DS [ K ] extends ArrayLike < any >
28
- ? AnimatedValueArray
29
- : AnimatedValue
30
- }
31
-
32
- type EndCallback = ( finished ?: boolean ) => void
24
+ type OnEnd = ( finished ?: boolean ) => void
33
25
34
26
let nextId = 1
35
27
class Controller < DS extends object = any > {
36
28
id = nextId ++
37
29
idle = true
38
30
guid = 0
39
- props : ControllerProps < DS > = { } as any
40
- merged : any = { }
41
- animations = { } as AnimationsFor < DS >
42
- interpolations = { } as InterpolationsFor < DS >
43
- values = { } as ValuesFor < DS >
44
- configs : any = [ ]
31
+ props : UpdateProps < DS > = { } as any
32
+ merged : DS = { } as any
33
+ values : DS = { } as any
34
+ interpolations : InterpolationMap = { }
35
+ animations : AnimationMap = { }
36
+ configs : any [ ] = [ ]
45
37
queue : any [ ] = [ ]
46
38
prevQueue : any [ ] = [ ]
47
- onEndQueue : EndCallback [ ] = [ ]
39
+ onEndQueue : OnEnd [ ] = [ ]
48
40
pendingCount = 0
49
41
50
- // Add this controller to the frameloop
51
- private _start ( onEnd ?: EndCallback ) {
52
- if ( onEnd ) this . onEndQueue . push ( onEnd )
53
- if ( this . idle ) {
54
- this . idle = false
55
- start ( this )
56
- }
57
- }
58
-
59
- private _stop ( finished ?: boolean ) {
60
- this . idle = true
61
- stop ( this )
62
- if ( finished && this . props . onRest ) {
63
- this . props . onRest ( this . merged )
64
- }
65
- if ( this . onEndQueue . length ) {
66
- this . onEndQueue . forEach ( onEnd => onEnd ( finished ) )
67
- this . onEndQueue = [ ]
68
- }
69
- }
42
+ getValues = ( ) => this . interpolations
70
43
71
44
/** update(props)
72
45
* This function filters input props and creates an array of tasks which are executed in .start()
73
46
* Each task is allowed to carry a delay, which means it can execute asnychroneously */
74
- update ( args : ControllerProps < DS > ) {
47
+ update ( args : UpdateProps < DS > ) {
75
48
// Extract delay and the to-prop from props
76
49
const { delay = 0 , to, ...props } = interpolateTo ( args ) as any
77
50
@@ -99,15 +72,15 @@ class Controller<DS extends object = any> {
99
72
this . queue . sort ( ( a , b ) => a . delay - b . delay )
100
73
101
74
// Diff the reduced props immediately (they'll contain the from-prop and some config)
102
- if ( hasKeys ( props ) ) this . diff ( props )
75
+ if ( hasKeys ( props ) ) this . _diff ( props )
103
76
104
77
return this
105
78
}
106
79
107
80
/** start()
108
81
* This function either executes a queue, if present, or starts the frameloop, which animates.
109
82
* The `useSpring` hooks never have > 1 update per call, because they call this every render. */
110
- start ( onEnd ?: EndCallback ) {
83
+ start ( onEnd ?: OnEnd ) {
111
84
// If a queue is present we must execute it
112
85
if ( this . queue . length ) {
113
86
const { prevQueue } = this
@@ -127,25 +100,33 @@ class Controller<DS extends object = any> {
127
100
const queue = ( this . prevQueue = this . queue )
128
101
this . queue = prevQueue
129
102
130
- // Reset delay/async tracking
103
+ // Reset any queue-related state
131
104
this . pendingCount = 0
105
+ this . onEndQueue . length = 0
106
+
107
+ // Never assume that the last update always finishes last, since that's
108
+ // not true when 2+ async animations have indeterminate durations.
109
+ let remaining = queue . length
110
+ const didEnd =
111
+ onEnd &&
112
+ ( ( finished ?: boolean ) => {
113
+ if ( -- remaining < 0 || guid !== this . guid ) return
114
+ onEnd ( finished )
115
+ } )
132
116
133
117
// Go through each entry and execute it
134
- queue . forEach ( ( { delay, onStart , ...props } ) => {
118
+ queue . forEach ( ( { delay, ...props } ) => {
135
119
// Entries can be delayed, async, or immediate
136
- const async = is . arr ( props . to ) || is . fun ( props . to )
137
120
if ( delay ) {
138
121
this . pendingCount ++
139
122
setTimeout ( ( ) => {
140
123
if ( guid === this . guid ) {
141
124
this . pendingCount --
142
- if ( async ) this . runAsync ( guid , props , onEnd )
143
- else this . diff ( props ) . _start ( onEnd )
125
+ this . _run ( guid , props , didEnd )
144
126
}
145
127
} , delay )
146
128
} else {
147
- if ( async ) this . runAsync ( guid , props , onEnd )
148
- else this . diff ( props ) . _start ( onEnd )
129
+ this . _run ( guid , props , didEnd )
149
130
}
150
131
} )
151
132
}
@@ -154,6 +135,16 @@ class Controller<DS extends object = any> {
154
135
return this
155
136
}
156
137
138
+ // Called by the frameloop
139
+ onFrame ( isActive : boolean ) {
140
+ if ( this . props . onFrame ) {
141
+ this . props . onFrame ( this . values )
142
+ }
143
+ if ( ! isActive ) {
144
+ this . _stop ( true )
145
+ }
146
+ }
147
+
157
148
stop ( finished ?: boolean ) {
158
149
if ( ! this . idle || this . pendingCount ) {
159
150
this . guid ++
@@ -163,21 +154,52 @@ class Controller<DS extends object = any> {
163
154
return this
164
155
}
165
156
166
- // Called by the frameloop
167
- onFrame ( isActive : boolean ) {
168
- if ( this . props . onFrame ) {
169
- this . props . onFrame ( this . values )
157
+ destroy ( ) {
158
+ this . stop ( )
159
+ this . props = { } as any
160
+ this . merged = { } as any
161
+ this . values = { } as any
162
+ this . interpolations = { }
163
+ this . animations = { }
164
+ this . configs = [ ]
165
+ }
166
+
167
+ // Add this controller to the frameloop
168
+ private _start ( onEnd ?: OnEnd ) {
169
+ if ( onEnd ) this . onEndQueue . push ( onEnd )
170
+ if ( this . idle ) {
171
+ this . idle = false
172
+ start ( this )
170
173
}
171
- if ( ! isActive ) {
172
- this . _stop ( true )
174
+ }
175
+
176
+ private _stop ( finished ?: boolean ) {
177
+ this . idle = true
178
+ stop ( this )
179
+ if ( finished && this . props . onRest ) {
180
+ this . props . onRest ( this . merged )
181
+ }
182
+ if ( this . onEndQueue . length ) {
183
+ this . onEndQueue . forEach ( onEnd => onEnd ( finished ) )
184
+ this . onEndQueue = [ ]
185
+ }
186
+ }
187
+
188
+ private _run ( guid : number , props : UpdateProps < DS > , onEnd ?: OnEnd ) {
189
+ // Never call `onStart` for immediate animations
190
+ if ( ! props . immediate ) {
191
+ const { onStart } = props
192
+ // Allow `useCallback` to prevent multiple calls
193
+ if ( onStart && onStart !== this . props . onStart ) onStart ( )
194
+ }
195
+ if ( is . arr ( props . to ) || is . fun ( props . to ) ) {
196
+ this . _runAsync ( guid , props , onEnd )
197
+ } else {
198
+ this . _diff ( props ) . _start ( onEnd )
173
199
}
174
200
}
175
201
176
- runAsync (
177
- guid : number ,
178
- props : SpringProps < DS > ,
179
- onEnd ?: ( finished : boolean ) => void
180
- ) {
202
+ private _runAsync ( guid : number , props : UpdateProps < DS > , onEnd ?: OnEnd ) {
181
203
// If "to" is either a function or an array it will be processed async, therefor "to" should be empty right now
182
204
// If the view relies on certain values "from" has to be present
183
205
let queue = Promise . resolve ( )
@@ -189,7 +211,7 @@ class Controller<DS extends object = any> {
189
211
if ( guid !== this . guid ) return
190
212
const fresh = { ...props , ...interpolateTo ( p ) }
191
213
if ( is . arr ( fresh . config ) ) fresh . config = fresh . config [ i ]
192
- return new Promise < any > ( r => this . diff ( fresh ) . start ( r ) )
214
+ return new Promise < any > ( r => this . _diff ( fresh ) . _start ( r ) )
193
215
} )
194
216
} )
195
217
} else if ( is . fun ( to ) ) {
@@ -202,7 +224,7 @@ class Controller<DS extends object = any> {
202
224
if ( guid !== this . guid ) return
203
225
const fresh = { ...props , ...interpolateTo ( p ) }
204
226
if ( is . arr ( fresh . config ) ) fresh . config = fresh . config [ i ++ ]
205
- return ( last = new Promise ( r => this . diff ( fresh ) . start ( r ) ) )
227
+ return ( last = new Promise ( r => this . _diff ( fresh ) . _start ( r ) ) )
206
228
} ,
207
229
// cancel()
208
230
finished => this . stop ( finished )
@@ -219,11 +241,11 @@ class Controller<DS extends object = any> {
219
241
} )
220
242
}
221
243
222
- diff ( props : any ) {
244
+ private _diff ( props : UpdateProps < DS > ) {
223
245
this . props = { ...this . props , ...props }
224
246
let {
225
- from = { } ,
226
- to = { } ,
247
+ from = { } as any ,
248
+ to = { } as any ,
227
249
config = { } ,
228
250
reverse,
229
251
attach,
@@ -381,27 +403,17 @@ class Controller<DS extends object = any> {
381
403
if ( changed ) {
382
404
// Make animations available to frameloop
383
405
this . configs = Object . values ( this . animations )
384
- this . values = { } as ValuesFor < DS >
385
- this . interpolations = { } as InterpolationsFor < DS >
386
- for ( let key in this . animations ) {
387
- this . interpolations [ key ] = this . animations [ key ] . interpolation
388
- this . values [ key ] = this . animations [ key ] . interpolation . getValue ( )
406
+
407
+ this . interpolations = { }
408
+ const values = ( this . values = { } as any )
409
+ for ( const key in this . animations ) {
410
+ const { interpolation } = this . animations [ key ]
411
+ this . interpolations [ key ] = interpolation
412
+ values [ key ] = interpolation . getValue ( )
389
413
}
390
414
}
391
415
return this
392
416
}
393
-
394
- destroy ( ) {
395
- this . stop ( )
396
- this . props = { } as DS
397
- this . merged = { }
398
- this . animations = { } as AnimationsFor < DS >
399
- this . interpolations = { } as InterpolationsFor < DS >
400
- this . values = { } as ValuesFor < DS >
401
- this . configs = [ ]
402
- }
403
-
404
- getValues = ( ) => this . interpolations
405
417
}
406
418
407
419
export default Controller
0 commit comments