Skip to content

Commit

Permalink
fix: Patch AnimatedStyle to avoid discarding the initial style info (#…
Browse files Browse the repository at this point in the history
…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 #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: #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
  • Loading branch information
gabrieldonadel authored and facebook-github-bot committed Nov 18, 2022
1 parent 0ba1127 commit c06323f
Show file tree
Hide file tree
Showing 2 changed files with 238 additions and 8 deletions.
198 changes: 198 additions & 0 deletions Libraries/Animated/__tests__/Animated-web-test.js
Original file line number Diff line number Diff line change
@@ -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',
},
});
48 changes: 40 additions & 8 deletions Libraries/Animated/nodes/AnimatedStyle.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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);
}

Expand Down

0 comments on commit c06323f

Please sign in to comment.