Skip to content

feat: make platforms work together #979

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
May 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 1 addition & 8 deletions packages/animated/src/Animated.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FluidValue, defineHidden } from 'shared'
import { defineHidden } from 'shared'
import { AnimatedValue } from './AnimatedValue'

const $node: any = Symbol.for('Animated:node')
Expand Down Expand Up @@ -40,13 +40,6 @@ export abstract class Animated<T = any> {
getPayload(): Payload {
return this.payload || []
}

/** The `AnimatedProps` class sets this before initializing */
static context: TreeContext | null = null
}

export type Payload = readonly AnimatedValue[]

export type TreeContext = {
dependencies: Set<FluidValue>
}
5 changes: 3 additions & 2 deletions packages/animated/src/AnimatedObject.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Lookup, each, getFluidConfig } from 'shared'
import { Animated, isAnimated, getPayload } from './Animated'
import { AnimatedValue } from './AnimatedValue'
import { TreeContext } from './context'

type Source = Lookup | null

Expand Down Expand Up @@ -54,8 +55,8 @@ export class AnimatedObject extends Animated {
/** Add to a payload set. */
protected _addToPayload(this: Set<AnimatedValue>, source: any) {
const config = getFluidConfig(source)
if (config && Animated.context) {
Animated.context.dependencies.add(source)
if (config && TreeContext.current) {
TreeContext.current.dependencies.add(source)
}
const payload = getPayload(source)
if (payload) {
Expand Down
16 changes: 8 additions & 8 deletions packages/animated/src/AnimatedProps.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { FluidObserver, FluidEvent } from 'shared'
import * as G from 'shared/globals'

import { Animated, TreeContext } from './Animated'
import { AnimatedObject } from './AnimatedObject'
import { TreeContext } from './context'

type Props = object & { style?: any }

Expand All @@ -17,14 +17,14 @@ export class AnimatedProps extends AnimatedObject implements FluidObserver {
setValue(props: Props | null, context?: TreeContext) {
if (!props) return // The constructor passes null.
if (context) {
Animated.context = context
TreeContext.current = context
if (props.style) {
const { createAnimatedStyle } = context.host
props = { ...props, style: createAnimatedStyle(props.style) }
}
}
super.setValue(
props.style && G.createAnimatedStyle
? { ...props, style: G.createAnimatedStyle(props.style) }
: props
)
Animated.context = null
super.setValue(props)
TreeContext.current = null
}

/** @internal */
Expand Down
9 changes: 9 additions & 0 deletions packages/animated/src/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { FluidValue } from 'shared'
import { HostConfig } from './createHost'

export type TreeContext = {
dependencies: Set<FluidValue>
host: HostConfig
}

export const TreeContext: { current: TreeContext | null } = { current: null }
70 changes: 70 additions & 0 deletions packages/animated/src/createHost.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { is, each, Lookup } from 'shared'
import { AnimatableComponent, withAnimated } from './withAnimated'
import { Animated } from './Animated'
import { AnimatedObject } from './AnimatedObject'

export interface HostConfig {
/** Provide custom logic for native updates */
applyAnimatedValues: (node: any, props: Lookup) => boolean | void
/** Wrap the `style` prop with an animated node */
createAnimatedStyle: (style: Lookup) => Animated
/** Intercept props before they're passed to an animated component */
getComponentProps: (props: Lookup) => typeof props
}

// A stub type that gets replaced by @react-spring/web and others.
type WithAnimated = {
(Component: AnimatableComponent): any
[key: string]: any
}

// For storing the animated version on the original component
const cacheKey = Symbol.for('AnimatedComponent')

export const createHost = (
components: AnimatableComponent[] | { [key: string]: AnimatableComponent },
{
applyAnimatedValues = () => false,
createAnimatedStyle = style => new AnimatedObject(style),
getComponentProps = props => props,
}: Partial<HostConfig> = {}
) => {
const hostConfig: HostConfig = {
applyAnimatedValues,
createAnimatedStyle,
getComponentProps,
}

const animated: WithAnimated = (Component: any) => {
const displayName = getDisplayName(Component) || 'Anonymous'

if (is.str(Component)) {
Component = withAnimated(Component, hostConfig)
} else {
Component =
Component[cacheKey] ||
(Component[cacheKey] = withAnimated(Component, hostConfig))
}

Component.displayName = `Animated(${displayName})`
return Component
}

each(components, (Component, key) => {
if (!is.str(key)) {
key = getDisplayName(Component)!
}
animated[key] = animated(Component)
})

return {
animated,
}
}

const getDisplayName = (arg: AnimatableComponent) =>
is.str(arg)
? arg
: arg && is.str(arg.displayName)
? arg.displayName
: (is.fun(arg) && arg.name) || null
7 changes: 0 additions & 7 deletions packages/animated/src/globals.ts

This file was deleted.

4 changes: 1 addition & 3 deletions packages/animated/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import './globals'
export * from './Animated'
export * from './AnimatedValue'
export * from './AnimatedString'
export * from './AnimatedArray'
export * from './AnimatedObject'
export * from './AnimatedStyle'
export * from './AnimatedProps'
export * from './withAnimated'
export * from './createHost'
export * from './types'
64 changes: 6 additions & 58 deletions packages/animated/src/withAnimated.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,13 @@
import React, { forwardRef, useRef, Ref } from 'react'
import { useLayoutEffect } from 'react-layout-effect'
import { is, each, useForceUpdate, ElementType, FluidConfig } from 'shared'
import * as G from 'shared/globals'

import { AnimatedProps } from './AnimatedProps'
import { HostConfig } from './createHost'

// For storing the animated version on the original component
const cacheKey = Symbol.for('AnimatedComponent')
export type AnimatableComponent = string | Exclude<ElementType, string>

type AnimatableComponent = string | Exclude<ElementType, string>

// A stub type that gets replaced by @react-spring/web and others.
type WithAnimated = {
(Component: AnimatableComponent): any
[key: string]: any
}

export const withAnimated: WithAnimated = (Component: any) => {
const animated = is.str(Component)
? createAnimatedComponent(Component)
: Component[cacheKey] ||
(Component[cacheKey] = createAnimatedComponent(Component))

animated.displayName = `Animated(${
is.str(Component)
? Component
: Component.displayName || Component.name || 'Anonymous'
})`

return animated
}

const createAnimatedComponent = (Component: any) =>
export const withAnimated = (Component: any, host: HostConfig) =>
forwardRef((rawProps: any, ref: Ref<any>) => {
const instanceRef = useRef<any>(null)
const hasInstance: boolean =
Expand All @@ -48,7 +24,7 @@ const createAnimatedComponent = (Component: any) =>
}

const didUpdate = instance
? G.applyAnimatedValues(instance, props.getValue(true))
? host.applyAnimatedValues(instance, props.getValue(true))
: false

// Re-render the component when native updates fail.
Expand All @@ -58,7 +34,7 @@ const createAnimatedComponent = (Component: any) =>
})

const dependencies = new Set<FluidConfig>()
props.setValue(rawProps, { dependencies })
props.setValue(rawProps, { dependencies, host })

useLayoutEffect(() => {
each(dependencies, dep => dep.addChild(props))
Expand All @@ -67,7 +43,7 @@ const createAnimatedComponent = (Component: any) =>

return (
<Component
{...G.getComponentProps(props.getValue())}
{...host.getComponentProps(props.getValue())}
ref={
hasInstance &&
((value: any) => {
Expand All @@ -85,31 +61,3 @@ function updateRef<T>(ref: Ref<T>, value: T) {
}
return value
}

/**
* Pass the given components to `withAnimated` and add the newly animated
* components to `withAnimated` as properties.
*/
export const extendAnimated = (
withAnimated: WithAnimated,
components: AnimatableComponent[] | { [key: string]: AnimatableComponent },
lowercase?: boolean
): any => {
each(components, (Component, key) => {
if (!is.str(key)) {
key = getDisplayName(Component)!
}
if (lowercase) {
key = key[0].toLowerCase() + key.slice(1)
}
withAnimated[key] = withAnimated(Component)
})
return withAnimated
}

const getDisplayName = (arg: AnimatableComponent) =>
is.str(arg)
? arg
: arg && is.str(arg.displayName)
? arg.displayName
: (is.fun(arg) && arg.name) || null
1 change: 0 additions & 1 deletion packages/core/test/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ beforeEach(() => {
Globals.assign({
now: mockRaf.now,
requestAnimationFrame: mockRaf.raf,
cancelAnimationFrame: mockRaf.cancel,
batchedUpdates: fn => {
// This lets our useTransition hook force its component
// to update from within an "onRest" handler.
Expand Down
38 changes: 0 additions & 38 deletions packages/shared/src/globals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@ import { noop } from './helpers'
// Required
//

export let defaultElement: string | ElementType

export let applyAnimatedValues: (node: any, props: any) => boolean | void

export let createStringInterpolator: (
config: InterpolatorConfig<string>
) => (input: number) => string
Expand All @@ -36,23 +32,13 @@ export let colorNames = null as { [key: string]: number } | null

export let skipAnimation = false as boolean

export let getComponentProps = (props: any) => props

export let createAnimatedStyle = null as ((style: any) => any) | null

export let createAnimatedTransform = null as ((transform: any) => any) | null

declare const window: {
requestAnimationFrame: (cb: (time: number) => void) => number
cancelAnimationFrame: (id: number) => void
}

export let requestAnimationFrame: (cb: (time: number) => void) => number =
typeof window !== 'undefined' ? window.requestAnimationFrame : () => -1

export let cancelAnimationFrame: (id: number) => void =
typeof window !== 'undefined' ? window.cancelAnimationFrame : noop

export let batchedUpdates = (callback: () => void) => callback()

export let willAdvance: (animations: OpaqueAnimation[]) => void = noop
Expand All @@ -72,22 +58,10 @@ export interface AnimatedGlobals {
colorNames?: typeof colorNames
/** Make all animations instant and skip the frameloop entirely */
skipAnimation?: typeof skipAnimation
/** The `div` element equivalent for the current platform */
defaultElement?: typeof defaultElement
/** Intercept props before they're passed to an animated component */
getComponentProps?: typeof getComponentProps
/** Provide custom logic for native updates */
applyAnimatedValues?: typeof applyAnimatedValues
/** Provide custom logic for string interpolation */
createStringInterpolator?: typeof createStringInterpolator
/** Wrap the `transform` prop with an animated node */
createAnimatedTransform?: typeof createAnimatedTransform
/** Wrap the `style` prop with an animated node */
createAnimatedStyle?: typeof createAnimatedStyle
/** Schedule a function to run on the next frame */
requestAnimationFrame?: typeof requestAnimationFrame
/** Prevent a scheduled function from running on the next frame */
cancelAnimationFrame?: typeof cancelAnimationFrame
/** Event props are called with `batchedUpdates` to reduce extraneous renders */
batchedUpdates?: typeof batchedUpdates
/** @internal Exposed for testing purposes */
Expand All @@ -101,14 +75,8 @@ export const assign = (globals: AnimatedGlobals): AnimatedGlobals =>
frameLoop,
colorNames,
skipAnimation,
defaultElement,
getComponentProps,
applyAnimatedValues,
createStringInterpolator,
createAnimatedTransform,
createAnimatedStyle,
requestAnimationFrame,
cancelAnimationFrame,
batchedUpdates,
willAdvance,
} = Object.assign(
Expand All @@ -118,14 +86,8 @@ export const assign = (globals: AnimatedGlobals): AnimatedGlobals =>
frameLoop,
colorNames,
skipAnimation,
defaultElement,
getComponentProps,
applyAnimatedValues,
createStringInterpolator,
createAnimatedTransform,
createAnimatedStyle,
requestAnimationFrame,
cancelAnimationFrame,
batchedUpdates,
willAdvance,
},
Expand Down
Loading