1
- import React , { useEffect , useRef , useImperativeHandle , ReactNode } from 'react'
2
- import { is , toArray , useForceUpdate , useOnce , each } from 'shared'
1
+ import React , {
2
+ useEffect ,
3
+ useRef ,
4
+ useImperativeHandle ,
5
+ ReactNode ,
6
+ useMemo ,
7
+ } from 'react'
8
+ import { is , toArray , useForceUpdate , useOnce , each , OneOrMore } from 'shared'
9
+ import { now } from 'shared/globals'
10
+
3
11
import { callProp , interpolateTo } from './helpers'
12
+ import { SpringHandle } from './types/spring'
4
13
import { Controller } from './Controller'
5
- import { now } from 'shared/globals'
6
14
7
15
// TODO: convert to "const enum" once Babel supports it
8
16
type Phase = number
@@ -24,19 +32,22 @@ export type ItemKeys<T> =
24
32
| number
25
33
| null
26
34
27
- export function useTransition < T > (
28
- data : T | readonly T [ ] ,
29
- props : any ,
30
- deps ?: any
31
- ) {
35
+ export function useTransition < T > ( data : OneOrMore < T > , props : any , deps ?: any ) {
32
36
const { key, ref, reset, sort, trail = 0 , expires = Infinity } = props
33
37
34
38
// Every item has its own transition.
35
- const items = toArray ( data )
39
+ const items = toArray < unknown > ( data )
36
40
const transitions : Transition [ ] = [ ]
37
41
38
42
// Keys help with reusing transitions between renders.
39
- const keys = is . und ( key ) ? items : is . fun ( key ) ? items . map ( key ) : toArray ( key )
43
+ // The `key` prop can be undefined (which means the items themselves are used
44
+ // as keys), or a function (which maps each item to its key), or an array of
45
+ // keys (which are assigned to each item by index).
46
+ const keys : any [ ] = is . und ( key )
47
+ ? items
48
+ : is . fun ( key )
49
+ ? items . map ( key )
50
+ : toArray ( key )
40
51
41
52
// The "onRest" callbacks need a ref to the latest transitions.
42
53
const usedTransitions = useRef < Transition [ ] | null > ( null )
@@ -49,7 +60,7 @@ export function useTransition<T>(
49
60
useOnce ( ( ) => ( ) =>
50
61
each ( usedTransitions . current ! , t => {
51
62
if ( t . expiresBy ) clearTimeout ( t . expirationId )
52
- t . spring . destroy ( )
63
+ t . ctrl . dispose ( )
53
64
} )
54
65
)
55
66
@@ -73,7 +84,7 @@ export function useTransition<T>(
73
84
key : keys [ i ] ,
74
85
item,
75
86
phase : MOUNT ,
76
- spring : new Controller ( ) ,
87
+ ctrl : new Controller ( ) ,
77
88
} )
78
89
} )
79
90
@@ -132,6 +143,7 @@ export function useTransition<T>(
132
143
} else return
133
144
}
134
145
146
+ // The payload is used to update the spring props once the current render is committed.
135
147
const payload : any = {
136
148
// When "to" is a function, it can return (1) an array of "useSpring" props,
137
149
// (2) an async function, or (3) an object with any "useSpring" props.
@@ -154,7 +166,7 @@ export function useTransition<T>(
154
166
} else {
155
167
// Postpone dismounts while other controllers are active.
156
168
const transitions = usedTransitions . current !
157
- if ( transitions . every ( t => t . spring . idle ) ) {
169
+ if ( transitions . every ( t => t . ctrl . idle ) ) {
158
170
forceUpdate ( )
159
171
} else if ( expires < Infinity ) {
160
172
t . expirationId = setTimeout ( forceUpdate , expires )
@@ -171,46 +183,56 @@ export function useTransition<T>(
171
183
if ( t . phase > MOUNT ) {
172
184
change . payload = payload
173
185
} else {
174
- t . spring . update ( payload )
186
+ t . ctrl . update ( payload )
175
187
}
176
188
} )
177
189
178
- useImperativeHandle (
179
- ref ,
180
- ( ) => ( {
190
+ const api = useMemo (
191
+ ( ) : SpringHandle => ( {
181
192
get controllers ( ) {
182
- return usedTransitions . current ! . map ( t => t . spring )
193
+ return usedTransitions . current ! . map ( t => t . ctrl )
183
194
} ,
184
- start : ( ) =>
185
- Promise . all (
186
- usedTransitions . current ! . map (
187
- t => new Promise ( done => t . spring . start ( done ) )
195
+ update ( props ) {
196
+ each ( usedTransitions . current ! , ( t , i ) =>
197
+ t . ctrl . update (
198
+ is . fun ( props ) ? props ( i , t . ctrl ) : is . arr ( props ) ? props [ i ] : props
188
199
)
189
- ) ,
190
- stop : ( finished ?: boolean ) =>
191
- each ( usedTransitions . current ! , t => t . spring . stop ( finished ) ) ,
200
+ )
201
+ return api
202
+ } ,
203
+ async start ( ) {
204
+ const transitions = usedTransitions . current !
205
+ const results = await Promise . all ( transitions . map ( t => t . ctrl . start ( ) ) )
206
+ return {
207
+ value : results . map ( result => result . value ) ,
208
+ finished : results . every ( result => result . finished ) ,
209
+ }
210
+ } ,
211
+ stop : keys => each ( usedTransitions . current ! , t => t . ctrl . stop ( keys ) ) ,
192
212
} ) ,
193
213
[ ]
194
214
)
195
215
216
+ useImperativeHandle ( ref , ( ) => api )
217
+
196
218
useEffect (
197
219
( ) => {
198
220
each ( changes , ( { phase, payload } , t ) => {
199
221
t . phase = phase
200
- if ( payload ) t . spring . update ( payload )
201
- if ( ! ref ) t . spring . start ( )
222
+ if ( payload ) t . ctrl . update ( payload )
223
+ if ( ! ref ) t . ctrl . start ( )
202
224
} )
203
225
} ,
204
226
reset ? void 0 : deps
205
227
)
206
228
207
229
return ( render : ( props : any , item : T ) => ReactNode ) =>
208
230
transitions . map ( t => {
209
- const elem : any = render ( { ...t . spring . animated } , t . item )
231
+ const elem : any = render ( { ...t . ctrl . springs } , t . item )
210
232
return elem && elem . type ? (
211
233
< elem . type
212
234
{ ...elem . props }
213
- key = { is . str ( t . key ) || is . num ( t . key ) ? t . key : t . spring . id }
235
+ key = { is . str ( t . key ) || is . num ( t . key ) ? t . key : t . ctrl . id }
214
236
ref = { elem . ref }
215
237
/>
216
238
) : (
@@ -227,8 +249,8 @@ interface Change {
227
249
interface Transition < T = any > {
228
250
key : any
229
251
item : T
252
+ ctrl : Controller
230
253
phase : Phase
231
- spring : Controller
232
254
/** Destroy no later than this date */
233
255
expiresBy ?: number
234
256
expirationId ?: number
0 commit comments