Skip to content

Commit 60a8c15

Browse files
gabrieldonadelOlimpiaZurek
authored andcommitted
fix: Patch AnimatedStyle to avoid discarding the initial style info (facebook#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 facebook#34425. This uses a slightly modified version of a patch used by react-native-web necolas/react-native-web@4c678d2. ## Changelog [General] [Fixed] - Patch AnimatedStyle to avoid discarding the initial style info Pull Request resolved: facebook#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
1 parent d8c2eb7 commit 60a8c15

File tree

2 files changed

+238
-8
lines changed

2 files changed

+238
-8
lines changed
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @format
8+
* @oncall react_native
9+
*/
10+
11+
const StyleSheet = require('../../StyleSheet/StyleSheet');
12+
let Animated = require('../Animated').default;
13+
let AnimatedProps = require('../nodes/AnimatedProps').default;
14+
15+
jest.mock('../../Utilities/Platform', () => {
16+
return {OS: 'web'};
17+
});
18+
19+
describe('Animated tests', () => {
20+
beforeEach(() => {
21+
jest.resetModules();
22+
});
23+
24+
describe('Animated', () => {
25+
it('works end to end', () => {
26+
const anim = new Animated.Value(0);
27+
const translateAnim = anim.interpolate({
28+
inputRange: [0, 1],
29+
outputRange: [100, 200],
30+
});
31+
32+
const callback = jest.fn();
33+
34+
const node = new AnimatedProps(
35+
{
36+
style: {
37+
backgroundColor: 'red',
38+
opacity: anim,
39+
transform: [
40+
{
41+
translate: [translateAnim, translateAnim],
42+
},
43+
{
44+
translateX: translateAnim,
45+
},
46+
{scale: anim},
47+
],
48+
shadowOffset: {
49+
width: anim,
50+
height: anim,
51+
},
52+
},
53+
},
54+
callback,
55+
);
56+
57+
expect(node.__getValue()).toEqual({
58+
style: [
59+
{
60+
backgroundColor: 'red',
61+
opacity: anim,
62+
shadowOffset: {
63+
width: anim,
64+
height: anim,
65+
},
66+
transform: [
67+
{translate: [translateAnim, translateAnim]},
68+
{translateX: translateAnim},
69+
{scale: anim},
70+
],
71+
},
72+
{
73+
opacity: 0,
74+
transform: [{translate: [100, 100]}, {translateX: 100}, {scale: 0}],
75+
shadowOffset: {
76+
width: 0,
77+
height: 0,
78+
},
79+
},
80+
],
81+
});
82+
83+
expect(anim.__getChildren().length).toBe(0);
84+
85+
node.__attach();
86+
87+
expect(anim.__getChildren().length).toBe(3);
88+
89+
anim.setValue(0.5);
90+
91+
expect(callback).toBeCalled();
92+
93+
expect(node.__getValue()).toEqual({
94+
style: [
95+
{
96+
backgroundColor: 'red',
97+
opacity: anim,
98+
shadowOffset: {
99+
width: anim,
100+
height: anim,
101+
},
102+
transform: [
103+
{translate: [translateAnim, translateAnim]},
104+
{translateX: translateAnim},
105+
{scale: anim},
106+
],
107+
},
108+
{
109+
opacity: 0.5,
110+
transform: [
111+
{translate: [150, 150]},
112+
{translateX: 150},
113+
{scale: 0.5},
114+
],
115+
shadowOffset: {
116+
width: 0.5,
117+
height: 0.5,
118+
},
119+
},
120+
],
121+
});
122+
123+
node.__detach();
124+
expect(anim.__getChildren().length).toBe(0);
125+
126+
anim.setValue(1);
127+
expect(callback.mock.calls.length).toBe(1);
128+
});
129+
130+
/**
131+
* The behavior matters when the input style is a mix of values
132+
* from StyleSheet.create and an inline style with an animation
133+
*/
134+
it('does not discard initial style', () => {
135+
const value1 = new Animated.Value(1);
136+
const scale = value1.interpolate({
137+
inputRange: [0, 1],
138+
outputRange: [1, 2],
139+
});
140+
const callback = jest.fn();
141+
const node = new AnimatedProps(
142+
{
143+
style: [
144+
styles.red,
145+
{
146+
transform: [
147+
{
148+
scale,
149+
},
150+
],
151+
},
152+
],
153+
},
154+
callback,
155+
);
156+
157+
expect(node.__getValue()).toEqual({
158+
style: [
159+
[
160+
styles.red,
161+
{
162+
transform: [{scale}],
163+
},
164+
],
165+
{
166+
transform: [{scale: 2}],
167+
},
168+
],
169+
});
170+
171+
node.__attach();
172+
expect(callback.mock.calls.length).toBe(0);
173+
value1.setValue(0.5);
174+
expect(callback.mock.calls.length).toBe(1);
175+
expect(node.__getValue()).toEqual({
176+
style: [
177+
[
178+
styles.red,
179+
{
180+
transform: [{scale}],
181+
},
182+
],
183+
{
184+
transform: [{scale: 1.5}],
185+
},
186+
],
187+
});
188+
189+
node.__detach();
190+
});
191+
});
192+
});
193+
194+
const styles = StyleSheet.create({
195+
red: {
196+
backgroundColor: 'red',
197+
},
198+
});

Libraries/Animated/nodes/AnimatedStyle.js

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,24 +13,52 @@
1313
import type {PlatformConfig} from '../AnimatedPlatformConfig';
1414

1515
import flattenStyle from '../../StyleSheet/flattenStyle';
16+
import Platform from '../../Utilities/Platform';
1617
import NativeAnimatedHelper from '../NativeAnimatedHelper';
1718
import AnimatedNode from './AnimatedNode';
1819
import AnimatedTransform from './AnimatedTransform';
1920
import AnimatedWithChildren from './AnimatedWithChildren';
2021

22+
function createAnimatedStyle(inputStyle: any): Object {
23+
const style = flattenStyle(inputStyle);
24+
const animatedStyles: any = {};
25+
for (const key in style) {
26+
const value = style[key];
27+
if (key === 'transform') {
28+
animatedStyles[key] = new AnimatedTransform(value);
29+
} else if (value instanceof AnimatedNode) {
30+
animatedStyles[key] = value;
31+
} else if (value && !Array.isArray(value) && typeof value === 'object') {
32+
animatedStyles[key] = createAnimatedStyle(value);
33+
}
34+
}
35+
return animatedStyles;
36+
}
37+
38+
function createStyleWithAnimatedTransform(inputStyle: any): Object {
39+
let style = flattenStyle(inputStyle) || ({}: {[string]: any});
40+
41+
if (style.transform) {
42+
style = {
43+
...style,
44+
transform: new AnimatedTransform(style.transform),
45+
};
46+
}
47+
return style;
48+
}
49+
2150
export default class AnimatedStyle extends AnimatedWithChildren {
51+
_inputStyle: any;
2252
_style: Object;
2353

2454
constructor(style: any) {
2555
super();
26-
style = flattenStyle(style) || ({}: {[string]: any});
27-
if (style.transform) {
28-
style = {
29-
...style,
30-
transform: new AnimatedTransform(style.transform),
31-
};
56+
if (Platform.OS === 'web') {
57+
this._inputStyle = style;
58+
this._style = createAnimatedStyle(style);
59+
} else {
60+
this._style = createStyleWithAnimatedTransform(style);
3261
}
33-
this._style = style;
3462
}
3563

3664
// Recursively get values for nested styles (like iOS's shadowOffset)
@@ -50,7 +78,11 @@ export default class AnimatedStyle extends AnimatedWithChildren {
5078
return updatedStyle;
5179
}
5280

53-
__getValue(): Object {
81+
__getValue(): Object | Array<Object> {
82+
if (Platform.OS === 'web') {
83+
return [this._inputStyle, this._walkStyleAndGetValues(this._style)];
84+
}
85+
5486
return this._walkStyleAndGetValues(this._style);
5587
}
5688

0 commit comments

Comments
 (0)