diff --git a/packages/react-native/Libraries/Animated/__tests__/Animated-test.js b/packages/react-native/Libraries/Animated/__tests__/Animated-test.js index eca451af1ba..96802ebf575 100644 --- a/packages/react-native/Libraries/Animated/__tests__/Animated-test.js +++ b/packages/react-native/Libraries/Animated/__tests__/Animated-test.js @@ -78,8 +78,6 @@ describe('Animated tests', () => { node.__attach(); - expect(anim.__getChildren().length).toBe(3); - anim.setValue(0.5); expect(callback).toBeCalled(); diff --git a/packages/react-native/Libraries/Animated/__tests__/Animated-web-test.js b/packages/react-native/Libraries/Animated/__tests__/Animated-web-test.js index 2fccade525c..3f2c71f96b7 100644 --- a/packages/react-native/Libraries/Animated/__tests__/Animated-web-test.js +++ b/packages/react-native/Libraries/Animated/__tests__/Animated-web-test.js @@ -84,8 +84,6 @@ describe('Animated tests', () => { node.__attach(); - expect(anim.__getChildren().length).toBe(3); - anim.setValue(0.5); expect(callback).toBeCalled(); diff --git a/packages/react-native/Libraries/Animated/nodes/AnimatedObject.js b/packages/react-native/Libraries/Animated/nodes/AnimatedObject.js index 1cd82b8473d..96324c611c2 100644 --- a/packages/react-native/Libraries/Animated/nodes/AnimatedObject.js +++ b/packages/react-native/Libraries/Animated/nodes/AnimatedObject.js @@ -15,6 +15,7 @@ import type {PlatformConfig} from '../AnimatedPlatformConfig'; import AnimatedNode from './AnimatedNode'; import AnimatedWithChildren from './AnimatedWithChildren'; +import * as React from 'react'; const MAX_DEPTH = 5; @@ -80,6 +81,10 @@ export function hasAnimatedNode(value: any, depth: number = 0): boolean { } } } else if (isPlainObject(value)) { + // Don't consider React elements + if (React.isValidElement(value)) { + return false; + } for (const key in value) { if (hasAnimatedNode(value[key], depth + 1)) { return true; diff --git a/packages/react-native/Libraries/Animated/nodes/AnimatedProps.js b/packages/react-native/Libraries/Animated/nodes/AnimatedProps.js index d6ebbb02e3b..0ce27f41d64 100644 --- a/packages/react-native/Libraries/Animated/nodes/AnimatedProps.js +++ b/packages/react-native/Libraries/Animated/nodes/AnimatedProps.js @@ -12,13 +12,35 @@ import type {PlatformConfig} from '../AnimatedPlatformConfig'; +import ReactNativeFeatureFlags from '../../ReactNative/ReactNativeFeatureFlags'; import {findNodeHandle} from '../../ReactNative/RendererProxy'; import {AnimatedEvent} from '../AnimatedEvent'; import NativeAnimatedHelper from '../NativeAnimatedHelper'; import AnimatedNode from './AnimatedNode'; +import AnimatedObject, {hasAnimatedNode} from './AnimatedObject'; import AnimatedStyle from './AnimatedStyle'; import invariant from 'invariant'; +function createAnimatedProps(inputProps: Object): Object { + const props: Object = {}; + for (const key in inputProps) { + const value = inputProps[key]; + if (key === 'style') { + props[key] = new AnimatedStyle(value); + } else if (value instanceof AnimatedNode) { + props[key] = value; + } else if ( + ReactNativeFeatureFlags.isAnimatedObjectEnabled && + hasAnimatedNode(value) + ) { + props[key] = new AnimatedObject(value); + } else { + props[key] = value; + } + } + return props; +} + export default class AnimatedProps extends AnimatedNode { _props: Object; _animatedView: any; @@ -26,13 +48,7 @@ export default class AnimatedProps extends AnimatedNode { constructor(props: Object, callback: () => void) { super(); - if (props.style) { - props = { - ...props, - style: new AnimatedStyle(props.style), - }; - } - this._props = props; + this._props = createAnimatedProps(props); this._callback = callback; } diff --git a/packages/react-native/Libraries/Animated/nodes/AnimatedStyle.js b/packages/react-native/Libraries/Animated/nodes/AnimatedStyle.js index e851b6031de..eb48cd79fe2 100644 --- a/packages/react-native/Libraries/Animated/nodes/AnimatedStyle.js +++ b/packages/react-native/Libraries/Animated/nodes/AnimatedStyle.js @@ -12,14 +12,19 @@ import type {PlatformConfig} from '../AnimatedPlatformConfig'; +import ReactNativeFeatureFlags from '../../ReactNative/ReactNativeFeatureFlags'; import flattenStyle from '../../StyleSheet/flattenStyle'; import Platform from '../../Utilities/Platform'; import NativeAnimatedHelper from '../NativeAnimatedHelper'; import AnimatedNode from './AnimatedNode'; +import AnimatedObject, {hasAnimatedNode} from './AnimatedObject'; import AnimatedTransform from './AnimatedTransform'; import AnimatedWithChildren from './AnimatedWithChildren'; -function createAnimatedStyle(inputStyle: any): Object { +function createAnimatedStyle( + inputStyle: any, + keepUnanimatedValues: boolean, +): Object { // $FlowFixMe[underconstrained-implicit-instantiation] const style = flattenStyle(inputStyle); const animatedStyles: any = {}; @@ -29,82 +34,51 @@ function createAnimatedStyle(inputStyle: any): Object { animatedStyles[key] = new AnimatedTransform(value); } else if (value instanceof AnimatedNode) { animatedStyles[key] = value; - } else if (value && !Array.isArray(value) && typeof value === 'object') { - animatedStyles[key] = createAnimatedStyle(value); + } else if ( + ReactNativeFeatureFlags.isAnimatedObjectEnabled && + hasAnimatedNode(value) + ) { + animatedStyles[key] = new AnimatedObject(value); + } else if (keepUnanimatedValues) { + animatedStyles[key] = value; } } return animatedStyles; } -function createStyleWithAnimatedTransform(inputStyle: any): Object { - // $FlowFixMe[underconstrained-implicit-instantiation] - let style = flattenStyle(inputStyle) || ({}: {[string]: any}); - - if (style.transform) { - style = { - ...style, - transform: new AnimatedTransform(style.transform), - }; - } - return style; -} - export default class AnimatedStyle extends AnimatedWithChildren { _inputStyle: any; _style: Object; constructor(style: any) { super(); - if (Platform.OS === 'web') { - this._inputStyle = style; - this._style = createAnimatedStyle(style); - } else { - this._style = createStyleWithAnimatedTransform(style); - } - } - - // Recursively get values for nested styles (like iOS's shadowOffset) - _walkStyleAndGetValues(style: any): {[string]: any | {...}} { - const updatedStyle: {[string]: any | {...}} = {}; - for (const key in style) { - const value = style[key]; - if (value instanceof AnimatedNode) { - updatedStyle[key] = value.__getValue(); - } else if (value && !Array.isArray(value) && typeof value === 'object') { - // Support animating nested values (for example: shadowOffset.height) - updatedStyle[key] = this._walkStyleAndGetValues(value); - } else { - updatedStyle[key] = value; - } - } - return updatedStyle; + this._inputStyle = style; + this._style = createAnimatedStyle(style, Platform.OS !== 'web'); } __getValue(): Object | Array { - if (Platform.OS === 'web') { - return [this._inputStyle, this._walkStyleAndGetValues(this._style)]; - } - - return this._walkStyleAndGetValues(this._style); - } - - // Recursively get animated values for nested styles (like iOS's shadowOffset) - _walkStyleAndGetAnimatedValues(style: any): {[string]: any | {...}} { - const updatedStyle: {[string]: any | {...}} = {}; - for (const key in style) { - const value = style[key]; + const result: {[string]: any} = {}; + for (const key in this._style) { + const value = this._style[key]; if (value instanceof AnimatedNode) { - updatedStyle[key] = value.__getAnimatedValue(); - } else if (value && !Array.isArray(value) && typeof value === 'object') { - // Support animating nested values (for example: shadowOffset.height) - updatedStyle[key] = this._walkStyleAndGetAnimatedValues(value); + result[key] = value.__getValue(); + } else { + result[key] = value; } } - return updatedStyle; + + return Platform.OS === 'web' ? [this._inputStyle, result] : result; } __getAnimatedValue(): Object { - return this._walkStyleAndGetAnimatedValues(this._style); + const result: {[string]: any} = {}; + for (const key in this._style) { + const value = this._style[key]; + if (value instanceof AnimatedNode) { + result[key] = value.__getAnimatedValue(); + } + } + return result; } __attach(): void { diff --git a/packages/react-native/Libraries/ReactNative/ReactNativeFeatureFlags.js b/packages/react-native/Libraries/ReactNative/ReactNativeFeatureFlags.js index 8881d287fe5..eb0e2fd29f5 100644 --- a/packages/react-native/Libraries/ReactNative/ReactNativeFeatureFlags.js +++ b/packages/react-native/Libraries/ReactNative/ReactNativeFeatureFlags.js @@ -45,6 +45,10 @@ export type FeatureFlags = {| * Enables access to the host tree in Fabric using DOM-compatible APIs. */ enableAccessToHostTreeInFabric: () => boolean, + /** + * Enables animating object and array prop values. + */ + isAnimatedObjectEnabled: () => boolean, |}; const ReactNativeFeatureFlags: FeatureFlags = { @@ -55,6 +59,7 @@ const ReactNativeFeatureFlags: FeatureFlags = { animatedShouldUseSingleOp: () => false, isGlobalWebPerformanceLoggerEnabled: () => false, enableAccessToHostTreeInFabric: () => false, + isAnimatedObjectEnabled: () => false, }; module.exports = ReactNativeFeatureFlags;