fix: Patch AnimatedStyle to avoid discarding the initial style info (#35198)

Summary:
This PR patches `AnimatedStyle` to avoid discarding the initial style information which destroys the output of web-style compilers and breaks rendering, as requested on https://github.com/facebook/react-native/issues/34425. This uses a slightly modified version of a patch used by react-native-web  https://github.com/necolas/react-native-web/commit/4c678d2912bddf30ad3d1f2c9a71f453d29427f0.

## Changelog

[General] [Fixed] - Patch AnimatedStyle to avoid discarding the initial style info

Pull Request resolved: https://github.com/facebook/react-native/pull/35198

Test Plan:
Run `yarn jest Animated` and ensure CI is green

![image](https://user-images.githubusercontent.com/11707729/199869612-4f2302da-5791-492f-83a7-683305757c23.png)

Reviewed By: necolas

Differential Revision: D41379826

Pulled By: javache

fbshipit-source-id: 7e16726828b98def14847ec3499ff93777a9cbfb
This commit is contained in:
Gabriel Donadel Dall'Agnol
2022-11-18 07:22:01 -08:00
committed by Facebook GitHub Bot
parent 0ba1127c15
commit c06323f463
2 changed files with 238 additions and 8 deletions
@@ -0,0 +1,198 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @oncall react_native
*/
const StyleSheet = require('../../StyleSheet/StyleSheet');
let Animated = require('../Animated').default;
let AnimatedProps = require('../nodes/AnimatedProps').default;
jest.mock('../../Utilities/Platform', () => {
return {OS: 'web'};
});
describe('Animated tests', () => {
beforeEach(() => {
jest.resetModules();
});
describe('Animated', () => {
it('works end to end', () => {
const anim = new Animated.Value(0);
const translateAnim = anim.interpolate({
inputRange: [0, 1],
outputRange: [100, 200],
});
const callback = jest.fn();
const node = new AnimatedProps(
{
style: {
backgroundColor: 'red',
opacity: anim,
transform: [
{
translate: [translateAnim, translateAnim],
},
{
translateX: translateAnim,
},
{scale: anim},
],
shadowOffset: {
width: anim,
height: anim,
},
},
},
callback,
);
expect(node.__getValue()).toEqual({
style: [
{
backgroundColor: 'red',
opacity: anim,
shadowOffset: {
width: anim,
height: anim,
},
transform: [
{translate: [translateAnim, translateAnim]},
{translateX: translateAnim},
{scale: anim},
],
},
{
opacity: 0,
transform: [{translate: [100, 100]}, {translateX: 100}, {scale: 0}],
shadowOffset: {
width: 0,
height: 0,
},
},
],
});
expect(anim.__getChildren().length).toBe(0);
node.__attach();
expect(anim.__getChildren().length).toBe(3);
anim.setValue(0.5);
expect(callback).toBeCalled();
expect(node.__getValue()).toEqual({
style: [
{
backgroundColor: 'red',
opacity: anim,
shadowOffset: {
width: anim,
height: anim,
},
transform: [
{translate: [translateAnim, translateAnim]},
{translateX: translateAnim},
{scale: anim},
],
},
{
opacity: 0.5,
transform: [
{translate: [150, 150]},
{translateX: 150},
{scale: 0.5},
],
shadowOffset: {
width: 0.5,
height: 0.5,
},
},
],
});
node.__detach();
expect(anim.__getChildren().length).toBe(0);
anim.setValue(1);
expect(callback.mock.calls.length).toBe(1);
});
/**
* The behavior matters when the input style is a mix of values
* from StyleSheet.create and an inline style with an animation
*/
it('does not discard initial style', () => {
const value1 = new Animated.Value(1);
const scale = value1.interpolate({
inputRange: [0, 1],
outputRange: [1, 2],
});
const callback = jest.fn();
const node = new AnimatedProps(
{
style: [
styles.red,
{
transform: [
{
scale,
},
],
},
],
},
callback,
);
expect(node.__getValue()).toEqual({
style: [
[
styles.red,
{
transform: [{scale}],
},
],
{
transform: [{scale: 2}],
},
],
});
node.__attach();
expect(callback.mock.calls.length).toBe(0);
value1.setValue(0.5);
expect(callback.mock.calls.length).toBe(1);
expect(node.__getValue()).toEqual({
style: [
[
styles.red,
{
transform: [{scale}],
},
],
{
transform: [{scale: 1.5}],
},
],
});
node.__detach();
});
});
});
const styles = StyleSheet.create({
red: {
backgroundColor: 'red',
},
});
+40 -8
View File
@@ -13,24 +13,52 @@
import type {PlatformConfig} from '../AnimatedPlatformConfig';
import flattenStyle from '../../StyleSheet/flattenStyle';
import Platform from '../../Utilities/Platform';
import NativeAnimatedHelper from '../NativeAnimatedHelper';
import AnimatedNode from './AnimatedNode';
import AnimatedTransform from './AnimatedTransform';
import AnimatedWithChildren from './AnimatedWithChildren';
function createAnimatedStyle(inputStyle: any): Object {
const style = flattenStyle(inputStyle);
const animatedStyles: any = {};
for (const key in style) {
const value = style[key];
if (key === 'transform') {
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);
}
}
return animatedStyles;
}
function createStyleWithAnimatedTransform(inputStyle: any): Object {
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();
style = flattenStyle(style) || ({}: {[string]: any});
if (style.transform) {
style = {
...style,
transform: new AnimatedTransform(style.transform),
};
if (Platform.OS === 'web') {
this._inputStyle = style;
this._style = createAnimatedStyle(style);
} else {
this._style = createStyleWithAnimatedTransform(style);
}
this._style = style;
}
// Recursively get values for nested styles (like iOS's shadowOffset)
@@ -50,7 +78,11 @@ export default class AnimatedStyle extends AnimatedWithChildren {
return updatedStyle;
}
__getValue(): Object {
__getValue(): Object | Array<Object> {
if (Platform.OS === 'web') {
return [this._inputStyle, this._walkStyleAndGetValues(this._style)];
}
return this._walkStyleAndGetValues(this._style);
}