From 9134d0b679d710da5e6a570e605a7003b8369fd2 Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Tue, 8 Dec 2020 12:30:14 -0500 Subject: [PATCH] chore: upgrade to fluids@0.2 --- packages/animated/src/AnimatedObject.ts | 21 +++++------ packages/animated/src/withAnimated.tsx | 12 ++++--- packages/core/src/Controller.ts | 13 +++---- packages/core/src/FrameValue.ts | 39 +++++++------------- packages/core/src/Interpolation.test.ts | 3 +- packages/core/src/Interpolation.ts | 16 +++++---- packages/core/src/SpringValue.test.ts | 20 ++++++----- packages/core/src/SpringValue.ts | 48 ++++++++++++------------- packages/core/src/helpers.ts | 8 ++--- packages/core/test/setup.ts | 32 ++++++++++------- packages/shared/package.json | 2 +- targets/web/src/AnimatedStyle.ts | 47 +++++++++++------------- yarn.lock | 15 +++++--- 13 files changed, 138 insertions(+), 138 deletions(-) diff --git a/packages/animated/src/AnimatedObject.ts b/packages/animated/src/AnimatedObject.ts index 451c476a6a..c7c069c1a7 100644 --- a/packages/animated/src/AnimatedObject.ts +++ b/packages/animated/src/AnimatedObject.ts @@ -1,5 +1,10 @@ import { Lookup } from '@react-spring/types' -import { each, eachProp, getFluidConfig } from '@react-spring/shared' +import { + each, + eachProp, + getFluidValue, + hasFluidValue, +} from '@react-spring/shared' import { Animated, isAnimated, getPayload } from './Animated' import { AnimatedValue } from './AnimatedValue' import { TreeContext } from './context' @@ -16,13 +21,10 @@ export class AnimatedObject extends Animated { eachProp(this.source, (source, key) => { if (isAnimated(source)) { values[key] = source.getValue(animated) - } else { - const config = getFluidConfig(source) - if (config) { - values[key] = config.get() - } else if (!animated) { - values[key] = source - } + } else if (hasFluidValue(source)) { + values[key] = getFluidValue(source) + } else if (!animated) { + values[key] = source } }) return values @@ -51,8 +53,7 @@ export class AnimatedObject extends Animated { /** Add to a payload set. */ protected _addToPayload(this: Set, source: any) { - const config = getFluidConfig(source) - if (config && TreeContext.dependencies) { + if (TreeContext.dependencies && hasFluidValue(source)) { TreeContext.dependencies.add(source) } const payload = getPayload(source) diff --git a/packages/animated/src/withAnimated.tsx b/packages/animated/src/withAnimated.tsx index 33fa28735f..6375c94364 100644 --- a/packages/animated/src/withAnimated.tsx +++ b/packages/animated/src/withAnimated.tsx @@ -10,6 +10,8 @@ import { FluidEvent, FluidObserver, FluidValue, + addFluidObserver, + removeFluidObserver, } from '@react-spring/shared' import { ElementType } from '@react-spring/types' @@ -67,11 +69,11 @@ export const withAnimated = (Component: any, host: HostConfig) => { observerRef.current = observer // Observe the latest dependencies. - each(deps, dep => dep.addChild(observer)) + each(deps, dep => addFluidObserver(dep, observer)) // Stop observing previous dependencies. if (lastObserver) { - each(lastObserver.deps, dep => dep.removeChild(lastObserver)) + each(lastObserver.deps, dep => removeFluidObserver(dep, lastObserver)) raf.cancel(lastObserver.update) } }) @@ -79,7 +81,7 @@ export const withAnimated = (Component: any, host: HostConfig) => { // Stop observing on unmount. useOnce(() => () => { const observer = observerRef.current! - each(observer.deps, dep => dep.removeChild(observer)) + each(observer.deps, dep => removeFluidObserver(dep, observer)) }) const usedProps = host.getComponentProps(props.getValue()) @@ -87,9 +89,9 @@ export const withAnimated = (Component: any, host: HostConfig) => { }) } -class PropsObserver implements FluidObserver { +class PropsObserver { constructor(readonly update: () => void, readonly deps: Set) {} - onParentChange(event: FluidEvent) { + eventObserved(event: FluidEvent) { if (event.type == 'change') { raf.write(this.update) } diff --git a/packages/core/src/Controller.ts b/packages/core/src/Controller.ts index 1c4538b7f3..3a6f94910e 100644 --- a/packages/core/src/Controller.ts +++ b/packages/core/src/Controller.ts @@ -8,6 +8,8 @@ import { toArray, eachProp, flushCalls, + addFluidObserver, + FluidObserver, } from '@react-spring/shared' import { getDefaultProp } from './helpers' @@ -40,8 +42,7 @@ export interface ControllerQueue } > {} -export class Controller - implements FrameValue.Observer { +export class Controller { readonly id = nextId++ /** The animated values */ @@ -246,7 +247,7 @@ export class Controller } /** @internal */ - onParentChange(event: FrameValue.Event) { + eventObserved(event: FrameValue.Event) { if (event.type == 'change') { this._changed.add(event.parent) if (!event.idle) { @@ -449,16 +450,16 @@ export function setSprings( eachProp(springs, (spring, key) => { if (!ctrl.springs[key]) { ctrl.springs[key] = spring - spring.addChild(ctrl) + addFluidObserver(spring, ctrl) } }) } -function createSpring(key: string, observer?: FrameValue.Observer) { +function createSpring(key: string, observer?: FluidObserver) { const spring = new SpringValue() spring.key = key if (observer) { - spring.addChild(observer) + addFluidObserver(spring, observer) } return spring } diff --git a/packages/core/src/FrameValue.ts b/packages/core/src/FrameValue.ts index 955bb793db..5a38e560db 100644 --- a/packages/core/src/FrameValue.ts +++ b/packages/core/src/FrameValue.ts @@ -1,10 +1,9 @@ import { deprecateInterpolate, - each, frameLoop, FluidValue, - FluidObserver, Globals as G, + callFluidObservers, } from '@react-spring/shared' import { InterpolatorArgs } from '@react-spring/types' import { getAnimated } from '@react-spring/animated' @@ -21,16 +20,16 @@ let nextId = 1 * * Its underlying value can be accessed and even observed. */ -export abstract class FrameValue - extends FluidValue> - implements FluidObserver { +export abstract class FrameValue extends FluidValue< + T, + FrameValue.Event +> { readonly id = nextId++ abstract key?: string abstract get idle(): boolean protected _priority = 0 - protected _children = new Set>() get priority() { return this._priority @@ -63,23 +62,19 @@ export abstract class FrameValue return this.get() } - /** @internal */ - addChild(child: FrameValue.Observer): void { - if (!this._children.size) this._attach() - this._children.add(child) + protected observerAdded(count: number) { + if (count == 1) this._attach() } - /** @internal */ - removeChild(child: FrameValue.Observer): void { - this._children.delete(child) - if (!this._children.size) this._detach() + protected observerRemoved(count: number) { + if (count == 0) this._detach() } /** @internal */ abstract advance(dt: number): void /** @internal */ - abstract onParentChange(_event: FrameValue.Event): void + abstract eventObserved(_event: FrameValue.Event): void /** Called when the first child is added. */ protected _attach() {} @@ -89,7 +84,7 @@ export abstract class FrameValue /** Tell our children about our new value */ protected _onChange(value: T, idle = false) { - this._emit({ + callFluidObservers(this, { type: 'change', parent: this, value, @@ -102,19 +97,12 @@ export abstract class FrameValue if (!this.idle) { frameLoop.sort(this) } - this._emit({ + callFluidObservers(this, { type: 'priority', parent: this, priority, }) } - - protected _emit(event: FrameValue.Event) { - // Clone "_children" so it can be safely mutated inside the loop. - each(Array.from(this._children), child => { - child.onParentChange(event) - }) - } } export declare namespace FrameValue { @@ -141,7 +129,4 @@ export declare namespace FrameValue { /** Events sent to children of `FrameValue` objects */ export type Event = ChangeEvent | PriorityEvent | IdleEvent - - /** An object that handles `FrameValue` events */ - export type Observer = FluidObserver> } diff --git a/packages/core/src/Interpolation.test.ts b/packages/core/src/Interpolation.test.ts index 97d18ce8c2..379746bc19 100644 --- a/packages/core/src/Interpolation.test.ts +++ b/packages/core/src/Interpolation.test.ts @@ -1,5 +1,6 @@ import { SpringValue } from './SpringValue' import { to } from './interpolate' +import { addFluidObserver } from '@react-spring/shared' describe('Interpolation', () => { it.todo('can use a SpringValue') @@ -25,7 +26,7 @@ describe('Interpolation', () => { // For interpolation to be active, it must be observed. const observer = jest.fn() - c.addChild({ onParentChange: observer }) + addFluidObserver(c, observer) // Pause the first input. a.pause() diff --git a/packages/core/src/Interpolation.ts b/packages/core/src/Interpolation.ts index 7fa524120e..01f47d2b8c 100644 --- a/packages/core/src/Interpolation.ts +++ b/packages/core/src/Interpolation.ts @@ -12,8 +12,12 @@ import { toArray, frameLoop, FluidValue, + getFluidValue, createInterpolator, Globals as G, + callFluidObservers, + addFluidObserver, + removeFluidObserver, } from '@react-spring/shared' import { FrameValue, isFrameValue } from './FrameValue' @@ -75,8 +79,8 @@ export class Interpolation extends FrameValue { protected _get() { const inputs: Arrify = is.arr(this.source) - ? this.source.map(node => node.get()) - : (toArray(this.source.get()) as any) + ? this.source.map(getFluidValue) + : (toArray(getFluidValue(this.source)) as any) return this.calc(...inputs) } @@ -102,7 +106,7 @@ export class Interpolation extends FrameValue { protected _attach() { let priority = 1 each(toArray(this.source), source => { - source.addChild(this) + addFluidObserver(source, this) if (isFrameValue(source)) { if (!source.idle) { this._active.add(source) @@ -117,14 +121,14 @@ export class Interpolation extends FrameValue { // Stop observing our sources once we have no observers. protected _detach() { each(toArray(this.source), source => { - source.removeChild(this) + removeFluidObserver(source, this) }) this._active.clear() becomeIdle(this) } /** @internal */ - onParentChange(event: FrameValue.Event) { + eventObserved(event: FrameValue.Event) { // Update our value when an idle parent is changed, // and enter the frameloop when a parent is resumed. if (event.type == 'change') { @@ -172,7 +176,7 @@ function becomeIdle(self: Interpolation) { node.done = true }) - self['_emit']({ + callFluidObservers(self, { type: 'idle', parent: self, }) diff --git a/packages/core/src/SpringValue.test.ts b/packages/core/src/SpringValue.test.ts index 09cb6922ef..4bd0dc3a09 100644 --- a/packages/core/src/SpringValue.test.ts +++ b/packages/core/src/SpringValue.test.ts @@ -1,7 +1,13 @@ import { SpringValue } from './SpringValue' import { FrameValue } from './FrameValue' import { flushMicroTasks } from 'flush-microtasks' -import { Globals } from '@react-spring/shared' +import { + addFluidObserver, + FluidObserver, + getFluidObservers, + Globals, + removeFluidObserver, +} from '@react-spring/shared' const frameLength = 1000 / 60 @@ -64,7 +70,7 @@ describe('SpringValue', () => { }) // The target is not attached until the spring is observed. - spring.addChild({ onParentChange() {} }) + addFluidObserver(spring, () => {}) mockRaf.step() target.set('red') @@ -801,14 +807,12 @@ function describeTarget(name: string, create: (from: number) => OpaqueTarget) { describe('when our target is ' + name, () => { let target: OpaqueTarget let spring: SpringValue - let observer: FrameValue.Observer & { - onParentChange: jest.MockedFunction - } + let observer: FluidObserver beforeEach(() => { target = create(1) spring = new SpringValue(0) // The target is not attached until the spring is observed. - spring.addChild((observer = { onParentChange: jest.fn() })) + addFluidObserver(spring, (observer = () => {})) }) it('animates toward the current value', async () => { @@ -906,14 +910,14 @@ function describeTarget(name: string, create: (from: number) => OpaqueTarget) { // in a memory leak since the Interpolation stays in the frameloop. describe('when our last child is detached', () => { it('detaches from the target', () => { - const children = target.node['_children'] spring.start({ to: target.node }) // Expect the target node to be attached. + const children = getFluidObservers(target.node)! expect(children.has(spring)).toBeTruthy() // Remove the observer. - spring.removeChild(observer) + removeFluidObserver(spring, observer) // Expect the target node to be detached. expect(children.has(spring)).toBeFalsy() diff --git a/packages/core/src/SpringValue.ts b/packages/core/src/SpringValue.ts index cf1516fa6f..4e69c44c18 100644 --- a/packages/core/src/SpringValue.ts +++ b/packages/core/src/SpringValue.ts @@ -8,10 +8,14 @@ import { frameLoop, flushCalls, getFluidValue, - getFluidConfig, isAnimatedString, FluidValue, Globals as G, + callFluidObservers, + hasFluidValue, + addFluidObserver, + removeFluidObserver, + getFluidObservers, } from '@react-spring/shared' import { Animated, @@ -160,11 +164,8 @@ export class SpringValue extends FrameValue { let { config, toValues } = anim const payload = getPayload(anim.to) - if (!payload) { - const toConfig = getFluidConfig(anim.to) - if (toConfig) { - toValues = toArray(toConfig.get()) - } + if (!payload && hasFluidValue(anim.to)) { + toValues = toArray(getFluidValue(anim.to)) as any } anim.values.forEach((node, i) => { @@ -423,7 +424,7 @@ export class SpringValue extends FrameValue { } /** @internal */ - onParentChange(event: FrameValue.Event) { + eventObserved(event: FrameValue.Event) { if (event.type == 'change') { this._start() } else if (event.type == 'priority') { @@ -593,6 +594,9 @@ export class SpringValue extends FrameValue { anim.from = from } + // Coerce "from" into a static value. + from = getFluidValue(from) + /** The "to" value is changing. */ const hasToChanged = !isEqual(to, prevTo) @@ -600,14 +604,6 @@ export class SpringValue extends FrameValue { this._focus(to) } - // Both "from" and "to" can use a fluid config (thanks to http://npmjs.org/fluids). - const toConfig = getFluidConfig(to) - const fromConfig = getFluidConfig(from) - - if (fromConfig) { - from = fromConfig.get() - } - /** The "to" prop is async. */ const hasAsyncTo = isAsyncTo(props.to) @@ -676,7 +672,7 @@ export class SpringValue extends FrameValue { // When the goal value is fluid, we don't know if its value // will change before the next animation frame, so it always // starts the animation to be safe. - let started = !!toConfig + let started = hasFluidValue(to) let finished = false if (!started) { @@ -715,9 +711,9 @@ export class SpringValue extends FrameValue { if (!hasAsyncTo) { // Make sure our "toValues" are updated even if our previous // "to" prop is a fluid value whose current value is also ours. - if (started || getFluidConfig(prevTo)) { + if (started || hasFluidValue(prevTo)) { anim.values = node.getPayload() - anim.toValues = toConfig + anim.toValues = hasFluidValue(to) ? null : goalType == AnimatedString ? [1] @@ -795,11 +791,11 @@ export class SpringValue extends FrameValue { protected _focus(value: T | FluidValue) { const anim = this.animation if (value !== anim.to) { - if (this._children.size) { + if (getFluidObservers(this)) { this._detach() } anim.to = value - if (this._children.size) { + if (getFluidObservers(this)) { this._attach() } } @@ -809,9 +805,8 @@ export class SpringValue extends FrameValue { let priority = 0 const { to } = this.animation - const config = getFluidConfig(to) - if (config) { - config.addChild(this) + if (hasFluidValue(to)) { + addFluidObserver(to, this) if (isFrameValue(to)) { priority = to.priority + 1 } @@ -821,7 +816,10 @@ export class SpringValue extends FrameValue { } protected _detach() { - getFluidConfig(this.animation.to)?.removeChild(this) + const { to } = this.animation + if (hasFluidValue(to)) { + removeFluidObserver(to, this) + } } /** @@ -920,7 +918,7 @@ export class SpringValue extends FrameValue { anim.onChange = anim.onPause = anim.onResume = undefined } - this._emit({ + callFluidObservers(this, { type: 'idle', parent: this, }) diff --git a/packages/core/src/helpers.ts b/packages/core/src/helpers.ts index 4a02542061..a588ca5540 100644 --- a/packages/core/src/helpers.ts +++ b/packages/core/src/helpers.ts @@ -2,7 +2,7 @@ import { is, toArray, eachProp, - getFluidConfig, + getFluidValue, isAnimatedString, FluidValue, Globals as G, @@ -183,10 +183,8 @@ export function inferTo(props: T): InferTo { // Compute the goal value, converting "red" to "rgba(255, 0, 0, 1)" in the process export function computeGoal(value: T | FluidValue): T { - const config = getFluidConfig(value) - return config - ? computeGoal(config.get()) - : is.arr(value) + value = getFluidValue(value) + return is.arr(value) ? value.map(computeGoal) : isAnimatedString(value) ? (G.createStringInterpolator({ diff --git a/packages/core/test/setup.ts b/packages/core/test/setup.ts index f96636b6f4..2ea7c13e27 100644 --- a/packages/core/test/setup.ts +++ b/packages/core/test/setup.ts @@ -1,7 +1,15 @@ import createMockRaf from 'mock-raf' import { flushMicroTasks } from 'flush-microtasks' import { act } from '@testing-library/react' -import { isEqual, is, colors, frameLoop } from '@react-spring/shared' +import { + isEqual, + is, + colors, + frameLoop, + addFluidObserver, + removeFluidObserver, + getFluidObservers, +} from '@react-spring/shared' import { __raf as raf } from 'rafz' import { Globals, Controller, FrameValue } from '..' @@ -38,15 +46,13 @@ afterEach(() => { // This observes every SpringValue animation when "advanceUntil" is used. // Any changes between frames are not recorded. -const frameObserver = { - onParentChange(event: FrameValue.Event) { - const spring = event.parent - if (event.type == 'change') { - let frames = frameCache.get(spring) - if (!frames) frameCache.set(spring, (frames = [])) - frames.push(event.value) - } - }, +const frameObserver = (event: FrameValue.Event) => { + const spring = event.parent + if (event.type == 'change') { + let frames = frameCache.get(spring) + if (!frames) frameCache.set(spring, (frames = [])) + frames.push(event.value) + } } global.getFrames = (target, preserve) => { @@ -91,8 +97,8 @@ global.advanceUntil = async test => { const values: FrameValue[] = [] const observe = (value: unknown) => { if (value instanceof FrameValue && !value.idle) { - value['_children'].forEach(observe) - value.addChild(frameObserver) + getFluidObservers(value)?.forEach(observe) + addFluidObserver(value, frameObserver) values.push(value) } } @@ -106,7 +112,7 @@ global.advanceUntil = async test => { // Stop observing after the frame is processed. for (const value of values) { - value.removeChild(frameObserver) + removeFluidObserver(value, frameObserver) } // Ensure pending effects are flushed. diff --git a/packages/shared/package.json b/packages/shared/package.json index bfb90d788b..b59b09dca3 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -11,7 +11,7 @@ }, "dependencies": { "@react-spring/types": "link:../types", - "fluids": "^0.1.6", + "fluids": "^0.2.2", "rafz": "^0.1.13" } } diff --git a/targets/web/src/AnimatedStyle.ts b/targets/web/src/AnimatedStyle.ts index bd241b5887..cff57357f0 100644 --- a/targets/web/src/AnimatedStyle.ts +++ b/targets/web/src/AnimatedStyle.ts @@ -7,9 +7,11 @@ import { eachProp, FluidValue, FluidEvent, - FluidObserver, - getFluidConfig, getFluidValue, + callFluidObservers, + hasFluidValue, + addFluidObserver, + removeFluidObserver, } from '@react-spring/shared' /** The transform-functions @@ -116,9 +118,8 @@ export class AnimatedStyle extends AnimatedObject { } /** @internal */ -class FluidTransform extends FluidValue implements FluidObserver { +class FluidTransform extends FluidValue { protected _value: string | null = null - protected _children = new Set() constructor(readonly inputs: Inputs, readonly transforms: Transforms) { super() @@ -142,38 +143,32 @@ class FluidTransform extends FluidValue implements FluidObserver { return identity ? 'none' : transform } - addChild(child: FluidObserver) { - if (!this._children.size) { - // Start observing our inputs once we have an observer. + // Start observing our inputs once we have an observer. + protected observerAdded(count: number) { + if (count == 1) each(this.inputs, input => - each(input, value => { - const config = getFluidConfig(value) - if (config) config.addChild(this) - }) + each( + input, + value => hasFluidValue(value) && addFluidObserver(value, this) + ) ) - } - this._children.add(child) } - removeChild(child: FluidObserver) { - this._children.delete(child) - if (!this._children.size) { - // Stop observing our inputs once we have no observers. + // Stop observing our inputs once we have no observers. + protected observerRemoved(count: number) { + if (count == 0) each(this.inputs, input => - each(input, value => { - const config = getFluidConfig(value) - if (config) config.removeChild(this) - }) + each( + input, + value => hasFluidValue(value) && removeFluidObserver(value, this) + ) ) - } } - onParentChange(event: FluidEvent) { + eventObserved(event: FluidEvent) { if (event.type == 'change') { this._value = null } - each(this._children, child => { - child.onParentChange(event) - }) + callFluidObservers(this, event) } } diff --git a/yarn.lock b/yarn.lock index 9b51b6594e..38035252de 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2304,7 +2304,7 @@ version "9.0.0-rc.3" dependencies: "@react-spring/types" "link:packages/types" - fluids "^0.1.6" + fluids "^0.2.0" rafz "^0.1.13" "@react-spring/three@link:targets/three": @@ -4990,10 +4990,15 @@ findup-sync@^4.0.0: micromatch "^4.0.2" resolve-dir "^1.0.1" -fluids@^0.1.6: - version "0.1.10" - resolved "https://registry.npmjs.org/fluids/-/fluids-0.1.10.tgz#0517e7a53dbce1db011dddec301b75178518ba0e" - integrity sha512-66FLmUJOrkvEHIsRVeM+88MG0bjd2TOBuR0BkM0hzyCb68W9drzqeX/AHDNp3ouZALQN7JvBvmKdVhHI+PZsdg== +fluids@^0.2.0: + version "0.2.0" + resolved "https://registry.npmjs.org/fluids/-/fluids-0.2.0.tgz#b3b5869c8b1ef4a18eeee1bf4344286b9dcc91df" + integrity sha512-Ben70C291Vcu4MomxULtm/9D6QUE22mpkeF/LuKYLACyhUcrHxCWyDpCZmEYecAXX4WgVaTFPq1MSPvao80Agw== + +fluids@^0.2.2: + version "0.2.2" + resolved "https://registry.npmjs.org/fluids/-/fluids-0.2.2.tgz#7e2daf4ba345d6601f922be14d7c89da05309d8a" + integrity sha512-vVrf225DaIUxtRgm1vGifGRaBqel2LA59ZvBsOaIZmNxGOBHXY0CGh03W07fLLvjwhwzaN2ABdt2x+nLOPwSBw== flush-microtasks@^1.0.1: version "1.0.1"