forked from pmndrs/react-spring
-
Notifications
You must be signed in to change notification settings - Fork 0
/
SpringRef.ts
169 lines (152 loc) · 5.1 KB
/
SpringRef.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
import { each, is, deprecateDirectCall } from '@react-spring/shared'
import { Lookup, Falsy, OneOrMore } from '@react-spring/types'
import { AsyncResult, ControllerUpdate } from './types'
import { Controller } from './Controller'
interface ControllerUpdateFn<State extends Lookup = Lookup> {
(i: number, ctrl: Controller<State>): ControllerUpdate<State> | Falsy
}
function noUnsafeEval() {
try {
new Function('')
return false
} catch (e) {
return true
}
}
/**
* Extending from function allows SpringRef instances to be callable.
* https://hackernoon.com/creating-callable-objects-in-javascript-d21l3te1
*
* ```js
* const [springs, api] = useSpring(() => ({x: 0}))
* api.start({x: 3}) // this works
* api({x: 3}) // this also works (non breaking from 9rc3)
* ```
*
* Since extending Function is prohibited in CSP-enabled environment without `unsafe-eval`,
* simple check is employed here to disable the callable behavior.
*
* Note that it also can be disabled explicitly
* using a global variable `window.REACT_SPRING_DISABLE_CALLABLE_REF`.
*
* ```js
* window.REACT_SPRING_DISABLE_CALLABLE_REF = true
* ```
*/
const ConditionalCallable = (() => {
if ((window as any).REACT_SPRING_DISABLE_CALLABLE_REF || noUnsafeEval()) {
return class {}
} else {
return class extends Function {
constructor() {
super(
'return arguments.callee._call.apply(arguments.callee, arguments)'
)
}
}
}
})()
export class SpringRef<
State extends Lookup = Lookup
> extends ConditionalCallable {
readonly current: Controller<State>[] = []
/** @deprecated use the property 'start' instead */
_call(props?: ControllerUpdate<State> | ControllerUpdateFn<State>) {
deprecateDirectCall()
this.start(props)
}
/** Update the state of each controller without animating. */
set(values: Partial<State>) {
each(this.current, ctrl => ctrl.set(values))
}
/** Start the queued animations of each controller. */
start(): AsyncResult<Controller<State>>[]
/** Update every controller with the same props. */
start(props: ControllerUpdate<State>): AsyncResult<Controller<State>>[]
/** Update controllers based on their state. */
start(props: ControllerUpdateFn<State>): AsyncResult<Controller<State>>[]
/** Start animating each controller. */
start(
props?: ControllerUpdate<State> | ControllerUpdateFn<State>
): AsyncResult<Controller<State>>[]
/** @internal */
start(props?: object | ControllerUpdateFn<State>) {
const results: AsyncResult[] = []
each(this.current, (ctrl, i) => {
if (is.und(props)) {
results.push(ctrl.start())
} else {
const update = this._getProps(props, ctrl, i)
if (update) {
results.push(ctrl.start(update))
}
}
})
return results
}
/** Add the same props to each controller's update queue. */
update(props: ControllerUpdate<State>): this
/** Generate separate props for each controller's update queue. */
update(props: ControllerUpdateFn<State>): this
/** Add props to each controller's update queue. */
update(props: ControllerUpdate<State> | ControllerUpdateFn<State>): this
/** @internal */
update(props: object | ControllerUpdateFn<State>) {
each(this.current, (ctrl, i) => ctrl.update(this._getProps(props, ctrl, i)))
return this
}
/** Add a controller to this ref */
add(ctrl: Controller<State>) {
if (!this.current.includes(ctrl)) {
this.current.push(ctrl)
}
}
/** Remove a controller from this ref */
delete(ctrl: Controller<State>) {
const i = this.current.indexOf(ctrl)
if (~i) this.current.splice(i, 1)
}
/** Overridden by `useTrail` to manipulate props */
protected _getProps(
arg: ControllerUpdate<State> | ControllerUpdateFn<State>,
ctrl: Controller<State>,
index: number
): ControllerUpdate<State> | Falsy {
return is.fun(arg) ? arg(index, ctrl) : arg
}
}
export interface SpringRef<State extends Lookup> {
(props?: ControllerUpdate<State> | ControllerUpdateFn<State>): AsyncResult<
Controller<State>
>[]
/** Stop all animations. */
stop(): this
/** Stop animations for the given keys. */
stop(keys: OneOrMore<string>): this
/** Cancel all animations. */
stop(cancel: boolean): this
/** Cancel animations for the given keys. */
stop(cancel: boolean, keys: OneOrMore<string>): this
/** Stop some or all animations. */
stop(keys?: OneOrMore<string>): this
/** Cancel some or all animations. */
stop(cancel: boolean, keys?: OneOrMore<string>): this
/** Pause all animations. */
pause(): this
/** Pause animations for the given keys. */
pause(keys: OneOrMore<string>): this
/** Pause some or all animations. */
pause(keys?: OneOrMore<string>): this
/** Resume all animations. */
resume(): this
/** Resume animations for the given keys. */
resume(keys: OneOrMore<string>): this
/** Resume some or all animations. */
resume(keys?: OneOrMore<string>): this
}
each(['stop', 'pause', 'resume'] as const, key => {
SpringRef.prototype[key] = function (this: SpringRef) {
each(this.current, ctrl => ctrl[key](...arguments))
return this
} as any
})