Skip to content

Commit 8ca3890

Browse files
committed
feat: make platforms work together
ie: @react-spring/web should work in the same window as @react-spring/three
1 parent d909026 commit 8ca3890

26 files changed

+241
-255
lines changed

packages/animated/src/AnimatedProps.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@ export class AnimatedProps extends AnimatedObject implements FluidObserver {
1818
if (!props) return // The constructor passes null.
1919
if (context) {
2020
TreeContext.current = context
21+
if (props.style) {
22+
const { createAnimatedStyle } = context.host
23+
props = { ...props, style: createAnimatedStyle(props.style) }
24+
}
2125
}
22-
super.setValue(
23-
props.style && G.createAnimatedStyle
24-
? { ...props, style: G.createAnimatedStyle(props.style) }
25-
: props
26-
)
26+
super.setValue(props)
2727
TreeContext.current = null
2828
}
2929

packages/animated/src/context.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { FluidValue } from 'shared'
2+
import { HostConfig } from './createHost'
23

34
export type TreeContext = {
45
dependencies: Set<FluidValue>
6+
host: HostConfig
57
}
68

79
export const TreeContext: { current: TreeContext | null } = { current: null }

packages/animated/src/createHost.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { is, each, Lookup } from 'shared'
2+
import { AnimatableComponent, withAnimated } from './withAnimated'
3+
import { Animated } from './Animated'
4+
import { AnimatedObject } from './AnimatedObject'
5+
6+
export interface HostConfig {
7+
/** Provide custom logic for native updates */
8+
applyAnimatedValues: (node: any, props: Lookup) => boolean | void
9+
/** Wrap the `style` prop with an animated node */
10+
createAnimatedStyle: (style: Lookup) => Animated
11+
/** Intercept props before they're passed to an animated component */
12+
getComponentProps: (props: Lookup) => typeof props
13+
}
14+
15+
// A stub type that gets replaced by @react-spring/web and others.
16+
type WithAnimated = {
17+
(Component: AnimatableComponent): any
18+
[key: string]: any
19+
}
20+
21+
// For storing the animated version on the original component
22+
const cacheKey = Symbol.for('AnimatedComponent')
23+
24+
export const createHost = (
25+
components: AnimatableComponent[] | { [key: string]: AnimatableComponent },
26+
{
27+
applyAnimatedValues = () => false,
28+
createAnimatedStyle = style => new AnimatedObject(style),
29+
getComponentProps = props => props,
30+
}: Partial<HostConfig> = {}
31+
) => {
32+
const hostConfig: HostConfig = {
33+
applyAnimatedValues,
34+
createAnimatedStyle,
35+
getComponentProps,
36+
}
37+
38+
const animated: WithAnimated = (Component: any) => {
39+
const displayName = getDisplayName(Component) || 'Anonymous'
40+
41+
if (is.str(Component)) {
42+
Component = withAnimated(Component, hostConfig)
43+
} else {
44+
Component =
45+
Component[cacheKey] ||
46+
(Component[cacheKey] = withAnimated(Component, hostConfig))
47+
}
48+
49+
Component.displayName = `Animated(${displayName})`
50+
return Component
51+
}
52+
53+
each(components, (Component, key) => {
54+
if (!is.str(key)) {
55+
key = getDisplayName(Component)!
56+
}
57+
animated[key] = animated(Component)
58+
})
59+
60+
return {
61+
animated,
62+
}
63+
}
64+
65+
const getDisplayName = (arg: AnimatableComponent) =>
66+
is.str(arg)
67+
? arg
68+
: arg && is.str(arg.displayName)
69+
? arg.displayName
70+
: (is.fun(arg) && arg.name) || null

packages/animated/src/globals.ts

Lines changed: 0 additions & 7 deletions
This file was deleted.

packages/animated/src/index.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ export * from './AnimatedValue'
44
export * from './AnimatedString'
55
export * from './AnimatedArray'
66
export * from './AnimatedObject'
7-
export * from './AnimatedStyle'
87
export * from './AnimatedProps'
9-
export * from './withAnimated'
8+
export * from './createHost'
109
export * from './types'
Lines changed: 6 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,13 @@
11
import React, { forwardRef, useRef, Ref } from 'react'
22
import { useLayoutEffect } from 'react-layout-effect'
33
import { is, each, useForceUpdate, ElementType, FluidConfig } from 'shared'
4-
import * as G from 'shared/globals'
54

65
import { AnimatedProps } from './AnimatedProps'
6+
import { HostConfig } from './createHost'
77

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

11-
type AnimatableComponent = string | Exclude<ElementType, string>
12-
13-
// A stub type that gets replaced by @react-spring/web and others.
14-
type WithAnimated = {
15-
(Component: AnimatableComponent): any
16-
[key: string]: any
17-
}
18-
19-
export const withAnimated: WithAnimated = (Component: any) => {
20-
const animated = is.str(Component)
21-
? createAnimatedComponent(Component)
22-
: Component[cacheKey] ||
23-
(Component[cacheKey] = createAnimatedComponent(Component))
24-
25-
animated.displayName = `Animated(${
26-
is.str(Component)
27-
? Component
28-
: Component.displayName || Component.name || 'Anonymous'
29-
})`
30-
31-
return animated
32-
}
33-
34-
const createAnimatedComponent = (Component: any) =>
10+
export const withAnimated = (Component: any, host: HostConfig) =>
3511
forwardRef((rawProps: any, ref: Ref<any>) => {
3612
const instanceRef = useRef<any>(null)
3713
const hasInstance: boolean =
@@ -48,7 +24,7 @@ const createAnimatedComponent = (Component: any) =>
4824
}
4925

5026
const didUpdate = instance
51-
? G.applyAnimatedValues(instance, props.getValue(true))
27+
? host.applyAnimatedValues(instance, props.getValue(true))
5228
: false
5329

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

6036
const dependencies = new Set<FluidConfig>()
61-
props.setValue(rawProps, { dependencies })
37+
props.setValue(rawProps, { dependencies, host })
6238

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

6844
return (
6945
<Component
70-
{...G.getComponentProps(props.getValue())}
46+
{...host.getComponentProps(props.getValue())}
7147
ref={
7248
hasInstance &&
7349
((value: any) => {
@@ -85,31 +61,3 @@ function updateRef<T>(ref: Ref<T>, value: T) {
8561
}
8662
return value
8763
}
88-
89-
/**
90-
* Pass the given components to `withAnimated` and add the newly animated
91-
* components to `withAnimated` as properties.
92-
*/
93-
export const extendAnimated = (
94-
withAnimated: WithAnimated,
95-
components: AnimatableComponent[] | { [key: string]: AnimatableComponent },
96-
lowercase?: boolean
97-
): any => {
98-
each(components, (Component, key) => {
99-
if (!is.str(key)) {
100-
key = getDisplayName(Component)!
101-
}
102-
if (lowercase) {
103-
key = key[0].toLowerCase() + key.slice(1)
104-
}
105-
withAnimated[key] = withAnimated(Component)
106-
})
107-
return withAnimated
108-
}
109-
110-
const getDisplayName = (arg: AnimatableComponent) =>
111-
is.str(arg)
112-
? arg
113-
: arg && is.str(arg.displayName)
114-
? arg.displayName
115-
: (is.fun(arg) && arg.name) || null

packages/shared/src/globals.ts

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ import { noop } from './helpers'
99
// Required
1010
//
1111

12-
export let applyAnimatedValues: (node: any, props: any) => boolean | void
13-
1412
export let createStringInterpolator: (
1513
config: InterpolatorConfig<string>
1614
) => (input: number) => string
@@ -34,12 +32,6 @@ export let colorNames = null as { [key: string]: number } | null
3432

3533
export let skipAnimation = false as boolean
3634

37-
export let getComponentProps = (props: any) => props
38-
39-
export let createAnimatedStyle = null as ((style: any) => any) | null
40-
41-
export let createAnimatedTransform = null as ((transform: any) => any) | null
42-
4335
declare const window: {
4436
requestAnimationFrame: (cb: (time: number) => void) => number
4537
}
@@ -66,16 +58,8 @@ export interface AnimatedGlobals {
6658
colorNames?: typeof colorNames
6759
/** Make all animations instant and skip the frameloop entirely */
6860
skipAnimation?: typeof skipAnimation
69-
/** Intercept props before they're passed to an animated component */
70-
getComponentProps?: typeof getComponentProps
71-
/** Provide custom logic for native updates */
72-
applyAnimatedValues?: typeof applyAnimatedValues
7361
/** Provide custom logic for string interpolation */
7462
createStringInterpolator?: typeof createStringInterpolator
75-
/** Wrap the `transform` prop with an animated node */
76-
createAnimatedTransform?: typeof createAnimatedTransform
77-
/** Wrap the `style` prop with an animated node */
78-
createAnimatedStyle?: typeof createAnimatedStyle
7963
/** Schedule a function to run on the next frame */
8064
requestAnimationFrame?: typeof requestAnimationFrame
8165
/** Event props are called with `batchedUpdates` to reduce extraneous renders */
@@ -91,11 +75,7 @@ export const assign = (globals: AnimatedGlobals): AnimatedGlobals =>
9175
frameLoop,
9276
colorNames,
9377
skipAnimation,
94-
getComponentProps,
95-
applyAnimatedValues,
9678
createStringInterpolator,
97-
createAnimatedTransform,
98-
createAnimatedStyle,
9979
requestAnimationFrame,
10080
batchedUpdates,
10181
willAdvance,
@@ -106,11 +86,7 @@ export const assign = (globals: AnimatedGlobals): AnimatedGlobals =>
10686
frameLoop,
10787
colorNames,
10888
skipAnimation,
109-
getComponentProps,
110-
applyAnimatedValues,
11189
createStringInterpolator,
112-
createAnimatedTransform,
113-
createAnimatedStyle,
11490
requestAnimationFrame,
11591
batchedUpdates,
11692
willAdvance,

targets/konva/src/animated.ts

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,20 @@
11
import { CSSProperties, ForwardRefExoticComponent } from 'react'
2-
import { withAnimated, extendAnimated } from 'animated'
32
import {
43
AssignableKeys,
54
ElementType,
65
ComponentPropsWithRef,
76
FluidValue,
87
} from 'shared'
9-
import { KonvaExports, KonvaElements, elements } from './elements'
8+
import { KonvaExports, Primitives } from './primitives'
109

11-
type CreateAnimated = <T extends ElementType>(
12-
wrappedComponent: T
13-
) => AnimatedComponent<T>
14-
15-
type KonvaComponents = {
16-
[Tag in KonvaElements]: AnimatedComponent<KonvaExports[Tag]>
10+
type AnimatedPrimitives = {
11+
[P in Primitives]: AnimatedComponent<KonvaExports[P]>
1712
}
1813

19-
// Extend animated with all the available Konva elements
20-
export const animated: CreateAnimated & KonvaComponents = extendAnimated(
21-
withAnimated,
22-
elements
23-
)
24-
25-
export { animated as a }
14+
/** The type of the `animated()` function */
15+
export type WithAnimated = {
16+
<T extends ElementType>(wrappedComponent: T): AnimatedComponent<T>
17+
} & AnimatedPrimitives
2618

2719
/** The type of an `animated()` component */
2820
export type AnimatedComponent<

targets/konva/src/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,24 @@
11
import { Globals } from 'shared'
22
import { createStringInterpolator } from 'shared/stringInterpolation'
33
import colorNames from 'shared/colors'
4+
import { createHost } from 'animated'
5+
import { primitives } from './primitives'
6+
import { WithAnimated } from './animated'
47

58
Globals.assign({
69
createStringInterpolator,
710
colorNames,
11+
})
12+
13+
const host = createHost(primitives, {
814
applyAnimatedValues(instance, props) {
915
if (!instance.nodeType) return false
1016
instance._applyProps(instance, props)
1117
},
1218
})
1319

20+
export const animated = host.animated as WithAnimated
21+
export { animated as a }
22+
1423
export * from './animated'
1524
export * from 'core'

targets/konva/src/elements.ts renamed to targets/konva/src/primitives.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ import * as konva from 'react-konva'
33

44
export type KonvaExports = typeof konva
55

6-
export type KonvaElements = {
6+
export type Primitives = {
77
[P in keyof KonvaExports]: KonvaExports[P] extends ElementType ? P : never
88
}[keyof KonvaExports]
99

10-
export const elements: KonvaElements[] = [
10+
export const primitives: Primitives[] = [
1111
'Arc',
1212
'Arrow',
1313
'Circle',

packages/animated/src/AnimatedStyle.ts renamed to targets/native/src/AnimatedStyle.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { AnimatedObject } from './AnimatedObject'
2-
import * as G from 'shared/globals'
1+
import { AnimatedObject } from 'animated'
2+
import { AnimatedTransform } from './AnimatedTransform'
33

44
type Style = object & { transform?: any }
55

@@ -10,8 +10,8 @@ export class AnimatedStyle extends AnimatedObject {
1010

1111
setValue(style: Style | null) {
1212
super.setValue(
13-
style && style.transform && G.createAnimatedTransform
14-
? { ...style, transform: G.createAnimatedTransform(style.transform) }
13+
style && style.transform
14+
? { ...style, transform: new AnimatedTransform(style.transform) }
1515
: style
1616
)
1717
}

targets/native/src/__tests__/index.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { assert, _, test, describe } from 'spec.ts';
2-
import { AnimatedProps } from '..';
3-
import { AnimatedTransform } from '../animated';
2+
import { AnimatedProps, AnimatedTransform } from '../animated';
43
import { FluidProps, FluidValue } from 'shared';
54
import { ViewStyle } from 'react-native';
65

0 commit comments

Comments
 (0)