Skip to content

Commit 588861f

Browse files
committed
copy files from react-spring
1 parent c6e2ec9 commit 588861f

File tree

5 files changed

+517
-0
lines changed

5 files changed

+517
-0
lines changed

spring/src/AnimatedArray.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { AnimatedValue } from "./animated"
2+
import { AnimatedObject } from "./AnimatedObject"
3+
import { AnimatedString } from "./AnimatedString"
4+
import { isAnimatedString } from "./utils"
5+
6+
type Value = number | string
7+
type Source = AnimatedValue<Value>[]
8+
9+
/** An array of animated nodes */
10+
export class AnimatedArray<
11+
T extends ReadonlyArray<Value> = Value[]
12+
> extends AnimatedObject {
13+
protected declare source: Source
14+
constructor(source: T) {
15+
super(source)
16+
}
17+
18+
/** @internal */
19+
static create<T extends ReadonlyArray<Value>>(source: T) {
20+
return new AnimatedArray(source)
21+
}
22+
23+
getValue(): T {
24+
return this.source.map(node => node.getValue()) as any
25+
}
26+
27+
setValue(source: T) {
28+
const payload = this.getPayload()
29+
// Reuse the payload when lengths are equal.
30+
if (source.length == payload.length) {
31+
return payload.map((node, i) => node.setValue(source[i])).some(Boolean)
32+
}
33+
// Remake the payload when length changes.
34+
super.setValue(source.map(makeAnimated))
35+
return true
36+
}
37+
}
38+
39+
function makeAnimated(value: any) {
40+
const nodeType = isAnimatedString(value) ? AnimatedString : AnimatedValue
41+
return nodeType.create(value)
42+
}

spring/src/AnimatedObject.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { Animated, AnimatedValue, getPayload, isAnimated } from "./animated"
2+
import { TreeContext } from "./context"
3+
import { getFluidValue, hasFluidValue } from "./fluids"
4+
import { Lookup, eachProp, each } from "./utils"
5+
6+
7+
/** An object containing `Animated` nodes */
8+
export class AnimatedObject extends Animated {
9+
constructor(protected source: Lookup) {
10+
super()
11+
this.setValue(source)
12+
}
13+
14+
getValue(animated?: boolean) {
15+
const values: Lookup = {}
16+
eachProp(this.source, (source, key) => {
17+
if (isAnimated(source)) {
18+
values[key] = source.getValue(animated)
19+
} else if (hasFluidValue(source)) {
20+
values[key] = getFluidValue(source)
21+
} else if (!animated) {
22+
values[key] = source
23+
}
24+
})
25+
return values
26+
}
27+
28+
/** Replace the raw object data */
29+
setValue(source: Lookup) {
30+
this.source = source
31+
this.payload = this._makePayload(source)
32+
}
33+
34+
reset() {
35+
if (this.payload) {
36+
each(this.payload, node => node.reset())
37+
}
38+
}
39+
40+
/** Create a payload set. */
41+
protected _makePayload(source: Lookup) {
42+
if (source) {
43+
const payload = new Set<AnimatedValue>()
44+
eachProp(source, this._addToPayload, payload)
45+
return Array.from(payload)
46+
}
47+
}
48+
49+
/** Add to a payload set. */
50+
protected _addToPayload(this: Set<AnimatedValue>, source: any) {
51+
if (TreeContext.dependencies && hasFluidValue(source)) {
52+
TreeContext.dependencies.add(source)
53+
}
54+
const payload = getPayload(source)
55+
if (payload) {
56+
each(payload, node => this.add(node))
57+
}
58+
}
59+
}

spring/src/AnimatedString.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { AnimatedValue } from "./animated"
2+
import { createInterpolator } from "./createInterpolator"
3+
import { is } from "./utils"
4+
5+
type Value = string | number
6+
7+
export class AnimatedString extends AnimatedValue<Value> {
8+
protected declare _value: number
9+
protected _string: string | null = null
10+
protected _toString: (input: number) => string
11+
12+
constructor(value: string) {
13+
super(0)
14+
this._toString = createInterpolator({
15+
output: [value, value],
16+
})
17+
}
18+
19+
/** @internal */
20+
static create(value: string) {
21+
return new AnimatedString(value)
22+
}
23+
24+
getValue() {
25+
let value = this._string
26+
return value == null ? (this._string = this._toString(this._value)) : value
27+
}
28+
29+
setValue(value: Value) {
30+
if (is.str(value)) {
31+
if (value == this._string) {
32+
return false
33+
}
34+
this._string = value
35+
this._value = 1
36+
} else if (super.setValue(value)) {
37+
this._string = null
38+
} else {
39+
return false
40+
}
41+
return true
42+
}
43+
44+
reset(goal?: string) {
45+
if (goal) {
46+
this._toString = createInterpolator({
47+
output: [this.getValue(), goal],
48+
})
49+
}
50+
this._value = 0
51+
super.reset()
52+
}
53+
}

spring/src/AnimatedStyle.ts

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
import { AnimatedObject } from "./AnimatedObject"
2+
import { addFluidObserver, callFluidObservers, FluidEvent, FluidValue, getFluidValue, hasFluidValue, removeFluidObserver } from "./fluids"
3+
import { is,each, Lookup, OneOrMore, eachProp, toArray } from "./utils"
4+
5+
/** The transform-functions
6+
* (https://developer.mozilla.org/fr/docs/Web/CSS/transform-function)
7+
* that you can pass as keys to your animated component style and that will be
8+
* animated. Perspective has been left out as it would conflict with the
9+
* non-transform perspective style.
10+
*/
11+
const domTransforms = /^(matrix|translate|scale|rotate|skew)/
12+
13+
// These keys have "px" units by default
14+
const pxTransforms = /^(translate)/
15+
16+
// These keys have "deg" units by default
17+
const degTransforms = /^(rotate|skew)/
18+
19+
type Value = number | string
20+
21+
/** Add a unit to the value when the value is unit-less (eg: a number) */
22+
const addUnit = (value: Value, unit: string): string | 0 =>
23+
is.num(value) && value !== 0 ? value + unit : value
24+
25+
/**
26+
* Checks if the input value matches the identity value.
27+
*
28+
* isValueIdentity(0, 0) // => true
29+
* isValueIdentity('0px', 0) // => true
30+
* isValueIdentity([0, '0px', 0], 0) // => true
31+
*/
32+
const isValueIdentity = (value: OneOrMore<Value>, id: number): boolean =>
33+
is.arr(value)
34+
? value.every(v => isValueIdentity(v, id))
35+
: is.num(value)
36+
? value === id
37+
: parseFloat(value) === id
38+
39+
type Inputs = ReadonlyArray<Value | FluidValue<Value>>[]
40+
type Transforms = ((value: any) => [string, boolean])[]
41+
42+
/**
43+
* This AnimatedStyle will simplify animated components transforms by
44+
* interpolating all transform function passed as keys in the style object
45+
* including shortcuts such as x, y and z for translateX/Y/Z
46+
*/
47+
export class AnimatedStyle extends AnimatedObject {
48+
constructor({ x, y, z, ...style }: Lookup) {
49+
/**
50+
* An array of arrays that contains the values (static or fluid)
51+
* used by each transform function.
52+
*/
53+
const inputs: Inputs = []
54+
/**
55+
* An array of functions that take a list of values (static or fluid)
56+
* and returns (1) a CSS transform string and (2) a boolean that's true
57+
* when the transform has no effect (eg: an identity transform).
58+
*/
59+
const transforms: Transforms = []
60+
61+
// Combine x/y/z into translate3d
62+
if (x || y || z) {
63+
inputs.push([x || 0, y || 0, z || 0])
64+
transforms.push((xyz: Value[]) => [
65+
`translate3d(${xyz.map(v => addUnit(v, 'px')).join(',')})`, // prettier-ignore
66+
isValueIdentity(xyz, 0),
67+
])
68+
}
69+
70+
// Pluck any other transform-related props
71+
eachProp(style, (value, key) => {
72+
if (key === 'transform') {
73+
inputs.push([value || ''])
74+
transforms.push((transform: string) => [transform, transform === ''])
75+
} else if (domTransforms.test(key)) {
76+
delete style[key]
77+
if (is.und(value)) return
78+
79+
const unit = pxTransforms.test(key)
80+
? 'px'
81+
: degTransforms.test(key)
82+
? 'deg'
83+
: ''
84+
85+
inputs.push(toArray(value))
86+
transforms.push(
87+
key === 'rotate3d'
88+
? ([x, y, z, deg]: [number, number, number, Value]) => [
89+
`rotate3d(${x},${y},${z},${addUnit(deg, unit)})`,
90+
isValueIdentity(deg, 0),
91+
]
92+
: (input: Value[]) => [
93+
`${key}(${input.map(v => addUnit(v, unit)).join(',')})`,
94+
isValueIdentity(input, key.startsWith('scale') ? 1 : 0),
95+
]
96+
)
97+
}
98+
})
99+
100+
if (inputs.length) {
101+
style.transform = new FluidTransform(inputs, transforms)
102+
}
103+
104+
super(style)
105+
}
106+
}
107+
108+
/** @internal */
109+
class FluidTransform extends FluidValue<string> {
110+
protected _value: string | null = null
111+
112+
constructor(readonly inputs: Inputs, readonly transforms: Transforms) {
113+
super()
114+
}
115+
116+
get() {
117+
return this._value || (this._value = this._get())
118+
}
119+
120+
protected _get() {
121+
let transform = ''
122+
let identity = true
123+
each(this.inputs, (input, i) => {
124+
const arg1 = getFluidValue(input[0])
125+
const [t, id] = this.transforms[i](
126+
is.arr(arg1) ? arg1 : input.map(getFluidValue)
127+
)
128+
transform += ' ' + t
129+
identity = identity && id
130+
})
131+
return identity ? 'none' : transform
132+
}
133+
134+
// Start observing our inputs once we have an observer.
135+
protected observerAdded(count: number) {
136+
if (count == 1)
137+
each(this.inputs, input =>
138+
each(
139+
input,
140+
value => hasFluidValue(value) && addFluidObserver(value, this)
141+
)
142+
)
143+
}
144+
145+
// Stop observing our inputs once we have no observers.
146+
protected observerRemoved(count: number) {
147+
if (count == 0)
148+
each(this.inputs, input =>
149+
each(
150+
input,
151+
value => hasFluidValue(value) && removeFluidObserver(value, this)
152+
)
153+
)
154+
}
155+
156+
eventObserved(event: FluidEvent) {
157+
if (event.type == 'change') {
158+
this._value = null
159+
}
160+
callFluidObservers(this, event)
161+
}
162+
}

0 commit comments

Comments
 (0)