Skip to content

Commit

Permalink
feat: make "To" and "SpringTransform" inherit from "AnimationValue"
Browse files Browse the repository at this point in the history
These classes don't use much from "SpringValue", so I think we're better off moving the shared functionality into a shared superclass, like "AnimationValue".
  • Loading branch information
aleclarson committed Sep 27, 2019
1 parent 084c8ac commit c74d4c0
Show file tree
Hide file tree
Showing 6 changed files with 221 additions and 167 deletions.
118 changes: 108 additions & 10 deletions packages/animated/src/AnimationValue.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,117 @@
import { FluidValue, FluidObserver, FluidType, defineHidden } from 'shared'
import { Animated } from './Animated'
import {
FluidValue,
FluidObserver,
FluidType,
defineHidden,
is,
each,
} from 'shared'
import { AnimatedValue } from './AnimatedValue'
import { AnimatedArray } from './AnimatedArray'

export const isAnimationValue = (value: any): value is AnimationValue =>
(value && value[FluidType]) == 2

/** Called whenever an `AnimationValue` is changed */
export type OnChange<T = unknown> = (
value: T,
source: AnimationValue<T>
) => void

/** An object or function that observes an `AnimationValue` */
export type AnimationObserver<T = unknown> = FluidObserver<T> | OnChange<T>

let nextId = 1

/** @internal A kind of `FluidValue` that provides access to an `Animated` object, a generated identifier, and the current value */
export abstract class AnimationValue<T = any> implements FluidValue<T> {
constructor() {
/**
* A kind of `FluidValue` that manages an `AnimatedValue` node.
*
* Its underlying value can be accessed and even observed.
*/
export abstract class AnimationValue<T = any>
implements FluidValue<T>, FluidObserver {
readonly id = nextId++

abstract idle: boolean
abstract node:
| AnimatedValue<T>
| (T extends ReadonlyArray<any> ? AnimatedArray<T> : never)

protected _priority = 0
protected _children = new Set<AnimationObserver<T>>()

constructor(readonly key?: keyof any) {
defineHidden(this, FluidType, 2)
}
readonly id = nextId++
abstract node: Animated
abstract get(): T
abstract addChild(child: FluidObserver): void
abstract removeChild(child: FluidObserver): void

/** @internal Controls the order in which animations are updated */
get priority() {
return this._priority
}
set priority(priority: number) {
if (this.priority != priority) {
this.priority = priority
this._onPriorityChange(priority)
}
}

/** Get the current value */
get() {
return this.node.getValue()
}

/** @internal */
addChild(child: AnimationObserver<T>): void {
if (!this._children.size) this._attach()
this._children.add(child)
}

/** @internal */
removeChild(child: AnimationObserver<T>): void {
this._children.delete(child)
if (!this._children.size) this._detach()
}

/** @internal */
abstract onParentChange(value: T, idle: boolean, parent: FluidValue): void

/** @internal */
onParentPriorityChange(priority: number, _parent: FluidValue) {
// Assume we only have one parent.
this.priority = priority + 1
}

protected _attach() {}
protected _detach() {}

/** Notify observers of a change to our value */
protected _onChange(value: T, idle = false) {
// Clone "_children" so it can be safely mutated by the loop.
for (const observer of Array.from(this._children)) {
if (is.fun(observer)) {
observer(value, this)
} else {
observer.onParentChange(value, idle, this)
}
}
}

/** Notify observers of a change to our priority */
protected _onPriorityChange(priority: number) {
each(this._children, observer => {
if (!is.fun(observer)) {
observer.onParentPriorityChange(priority, this)
}
})
}

/** Reset our node and the nodes of every descendant */
protected _reset(goal?: T) {
this.node.reset(!this.idle, goal)
each(this._children, observer => {
if (isAnimationValue(observer)) {
observer._reset(goal)
}
})
}
}
102 changes: 18 additions & 84 deletions packages/core/src/SpringValue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
EasingFunction,
toArray,
InterpolatorArgs,
FluidObserver,
isFluidValue,
FluidValue,
} from 'shared'
Expand All @@ -16,6 +15,7 @@ import {
AnimatedValue,
AnimatedString,
AnimatedArray,
OnChange,
} from '@react-spring/animated'
import invariant from 'tiny-invariant'
import * as G from 'shared/globals'
Expand All @@ -35,7 +35,7 @@ import {
} from './runAsync'
import { SpringConfig, Animatable, RangeProps } from './types/spring'
import { Indexable, Merge } from './types/common'
import { callProp, DEFAULT_PROPS, DefaultProps, matchProp } from './helpers'
import { callProp, DEFAULT_PROPS, DefaultProps, matchProp, isEqual } from './helpers'
import { config } from './constants'
import { To } from './To'

Expand All @@ -48,9 +48,6 @@ export type OnAnimate<T = unknown> = (
/** Called before the animation is added to the frameloop */
export type OnStart<T = unknown> = (spring: SpringValue<T>) => void

/** Called whenever the animated value is changed */
export type OnChange<T = unknown> = (value: T, spring: SpringValue<T>) => void

/** Called once the animation comes to a halt */
export type OnRest<T = unknown> = (result: AnimationResult<T>) => void

Expand Down Expand Up @@ -134,22 +131,15 @@ const BASE_CONFIG: SpringConfig = {
clamp: false,
}

/** An observer of a `SpringValue` */
export type SpringObserver<T = any> = OnChange<T> | FluidObserver<T>

/** An opaque animatable value */
export class SpringValue<T = any, P extends string = string>
extends AnimationValue<T>
implements FluidObserver<T> {
export class SpringValue<T = any> extends AnimationValue<T> {
static phases = { DISPOSED, CREATED, IDLE, PAUSED, ACTIVE }
/** The animation state */
animation?: Animation<T>
/** The queue of pending props */
queue?: PendingProps<T>[]
/** @internal The animated node. Do not touch! */
node!: AnimatedNode<T>
/** @internal Determines order of animations on each frame */
priority = 0
node!: AnimationValue<T>['node']
/** The lifecycle phase of this spring */
protected _phase = CREATED
/** The state for `runAsync` calls */
Expand All @@ -160,11 +150,9 @@ export class SpringValue<T = any, P extends string = string>
protected _defaultProps: PendingProps<T> = {}
/** Cancel any update from before this timestamp */
protected _lastAsyncId = 0
/** Objects that want to know when this spring changes */
protected _children = new Set<SpringObserver<T>>()

constructor(readonly key: P) {
super()
constructor(key: keyof any) {
super(key)
this._state = { key }
}

Expand All @@ -177,13 +165,6 @@ export class SpringValue<T = any, P extends string = string>
return this._phase == phase
}

/** Get the current value */
get(): T
get<P extends keyof Animation>(prop: P): Animation<T>[P] | undefined
get(prop?: keyof Animation) {
return prop ? this.animation && this.animation[prop] : this.node.getValue()
}

/** Set the current value, while stopping the current animation */
set(value: T, notify = true) {
this.node.setValue(value)
Expand Down Expand Up @@ -380,10 +361,10 @@ export class SpringValue<T = any, P extends string = string>
}

/** @internal */
onParentChange(value: any, finished: boolean) {
onParentChange(value: any, idle: boolean) {
// The "FrameLoop" handles everything other than immediate animation.
if (this.animation!.immediate) {
if (finished) {
if (idle) {
this.finish(value)
} else {
this.set(value)
Expand All @@ -396,11 +377,6 @@ export class SpringValue<T = any, P extends string = string>
}
}

/** @internal */
onParentPriorityChange(priority: number) {
this._setPriority(priority + 1)
}

protected _checkDisposed(name: string) {
invariant(
!this.is(DISPOSED),
Expand Down Expand Up @@ -626,27 +602,13 @@ export class SpringValue<T = any, P extends string = string>
anim.to = value
if (isFluidValue(value)) {
value.addChild(this)
this._setPriority((value.priority || 0) + 1)
this.priority = (value.priority || 0) + 1
} else {
this._setPriority(0)
}
}

protected _setPriority(priority: number) {
if (this.priority == priority) return
this.priority = priority
if (!this.idle) {
// Re-enter the frameloop so our new priority is used.
G.frameLoop.stop(this).start(this)
}
for (const observer of Array.from(this._children)) {
if ('onParentPriorityChange' in observer) {
observer.onParentPriorityChange(priority, this)
}
this.priority = 0
}
}

/** @internal */
/** @internal Called by the frameloop */
public _onChange(value: T, finished = false) {
const anim = this.animation
if (anim) {
Expand All @@ -662,25 +624,19 @@ export class SpringValue<T = any, P extends string = string>
anim.onChange(value, this)
}
}
super._onChange(value, finished)
}

// Clone "_children" so it can be safely mutated by the loop.
for (const observer of Array.from(this._children)) {
if ('onParentChange' in observer) {
observer.onParentChange(value, finished, this)
} else if (!finished) {
observer(value, this)
}
protected _onPriorityChange(priority: number) {
// Re-enter the frameloop so our new priority is used.
G.frameLoop.stop(this).start(this)
}
super._onPriorityChange(priority)
}

/** Reset our node, and the nodes of every descendant spring */
protected _reset(goal = computeGoal(this.animation!.to)) {
this.node.reset(!this.idle, goal)
each(this._children, child => {
if (child instanceof SpringValue) {
child._reset(goal)
}
})
super._reset(goal)
}

/** Enter the frameloop */
Expand Down Expand Up @@ -755,16 +711,6 @@ export class SpringValue<T = any, P extends string = string>
return this._getNodeType(value).create(computeGoal(value))
}
}

/** @internal */
addChild(child: SpringObserver<T>): void {
this._children.add(child)
}

/** @internal */
removeChild(child: SpringObserver<T>): void {
this._children.delete(child)
}
}

// Merge configs when the existence of "decay" or "duration" has not changed.
Expand Down Expand Up @@ -792,15 +738,3 @@ function computeGoal<T>(value: T | FluidValue<T>): T {
})(1)
: value
}

// Compare animatable values
function isEqual(a: any, b: any) {
if (is.arr(a)) {
if (!is.arr(b) || a.length !== b.length) return false
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) return false
}
return true
}
return a === b
}
Loading

0 comments on commit c74d4c0

Please sign in to comment.