Skip to content

Commit

Permalink
Merge branch 'main' into feature/bug-hanging-shared-transitions
Browse files Browse the repository at this point in the history
  • Loading branch information
aymather authored Feb 22, 2025
2 parents d29eb52 + fa3aa31 commit 4597399
Show file tree
Hide file tree
Showing 28 changed files with 450 additions and 286 deletions.
29 changes: 29 additions & 0 deletions packages/docs-reanimated/docs/core/useAnimatedStyle.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,35 @@ import AnimatingStylesSrc from '!!raw-loader!@site/src/examples/AnimatingStyles'

## Remarks

- Animated styles take precedence over React Native's static styles. All values specified in animated styles override values from static styles.

<Indent>

```jsx
function App() {
const animatedStyles = useAnimatedStyle(() => ({
width: sv.value,
}));

return (
<Animated.View
style={[
// highlight-start
animatedStyles, // ⚠️ overrides the static style width
{ width: 100 },
// highlight-end
]}
/>
);
}
```

</Indent>

- Animated styles don't follow the order in which they are specified in the style array. The last updated animated style is the one that takes effect.

- Removing the animated style from the view doesn't unset values that were applied in the animated style. To unset these values, you need to manually set them to `undefined` in the animated style.

- Mutating shared values in `useAnimatedStyle`'s callback is an undefined behavior which may lead to infinite loops.

<Indent>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,35 @@ import AnimatingStylesSrc from '!!raw-loader!@site/src/examples/AnimatingStyles'

## Remarks

- Animated styles take precedence over React Native's static styles. All values specified in animated styles override values from static styles.

<Indent>

```jsx
function App() {
const animatedStyles = useAnimatedStyle(() => ({
width: sv.value,
}));

return (
<Animated.View
style={[
// highlight-start
animatedStyles, // ⚠️ overrides the static style width
{ width: 100 },
// highlight-end
]}
/>
);
}
```

</Indent>

- Animated styles don't follow the order in which they are specified in the style array. The last updated animated style is the one that takes effect.

- Removing the animated style from the view doesn't unset values that were applied in the animated style. To unset these values, you need to manually set them to `undefined` in the animated style.

- Mutating shared values in `useAnimatedStyle`'s callback is an undefined behavior which may lead to infinite loops.

<Indent>
Expand Down
2 changes: 0 additions & 2 deletions packages/react-native-reanimated/src/commonTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,8 +206,6 @@ export interface StyleProps extends ViewStyle, TextStyle {
[key: string]: any;
}

export type PlainStyle = ViewStyle & TextStyle & ImageStyle;

/**
* A value that can be used both on the [JavaScript
* thread](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/glossary#javascript-thread)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
'use strict';
import '../layoutReanimation/animationsManager';

import type { Component } from 'react';
import React from 'react';
import { Platform } from 'react-native';
import type React from 'react';

import { removeFromPropsRegistry } from '../AnimatedPropsRegistry';
import { getReduceMotionFromConfig } from '../animation/util';
Expand Down Expand Up @@ -345,7 +343,7 @@ export default class AnimatedComponent
const filtered = filterStyles(flattenArray(props.style ?? []));
this._prevAnimatedStyles = this._animatedStyles;
this._animatedStyles = filtered.animatedStyles;
this._planStyle = filtered.plainStyle;
this._cssStyle = filtered.cssStyle;
}

_configureLayoutTransition() {
Expand Down Expand Up @@ -468,11 +466,6 @@ export default class AnimatedComponent
};
}

const platformProps = Platform.select({
web: {},
default: { collapsable: false },
});

const skipEntering = this.context?.current;
const nativeID =
skipEntering || !isFabric() ? undefined : `${this.reanimatedID}`;
Expand All @@ -484,18 +477,10 @@ export default class AnimatedComponent
}
: {};

const ChildComponent = this.ChildComponent;

return (
<ChildComponent
nativeID={nativeID}
{...filteredProps}
{...jestProps}
// Casting is used here, because ref can be null - in that case it cannot be assigned to HTMLElement.
// After spending some time trying to figure out what to do with this problem, we decided to leave it this way
ref={this._setComponentRef as (ref: Component) => void}
{...platformProps}
/>
);
return super.render({
nativeID,
...filteredProps,
...jestProps,
});
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict';
import type { PlainStyle, StyleProps } from '../commonTypes';
import type { StyleProps } from '../commonTypes';
import type { CSSStyle } from '../css';
import type { NestedArray } from './commonTypes';

export function flattenArray<T>(array: NestedArray<T>): T[] {
Expand Down Expand Up @@ -36,25 +37,25 @@ export const has = <K extends string>(
};

export function filterStyles(styles: StyleProps[] | undefined): {
plainStyle: PlainStyle;
cssStyle: CSSStyle;
animatedStyles: StyleProps[];
} {
if (!styles) {
return { animatedStyles: [], plainStyle: {} };
return { animatedStyles: [], cssStyle: {} };
}

return styles.reduce<{
plainStyle: PlainStyle;
cssStyle: CSSStyle;
animatedStyles: StyleProps[];
}>(
({ animatedStyles, plainStyle }, style) => {
({ animatedStyles, cssStyle }, style) => {
if (style?.viewDescriptors) {
animatedStyles.push(style);
} else {
plainStyle = { ...plainStyle, ...style } as PlainStyle;
cssStyle = { ...cssStyle, ...style } as CSSStyle;
}
return { animatedStyles, plainStyle };
return { animatedStyles, cssStyle };
},
{ animatedStyles: [], plainStyle: {} }
{ animatedStyles: [], cssStyle: {} }
);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
'use strict';
import type { MutableRefObject, Ref } from 'react';
import type { ComponentProps, MutableRefObject, Ref } from 'react';
import React, { Component } from 'react';
import type { StyleProp } from 'react-native';
import { Platform, StyleSheet } from 'react-native';
Expand All @@ -17,6 +17,7 @@ import { isFabric, isWeb, shouldBeUseWeb } from '../../PlatformChecker';
import { ReanimatedError } from '../errors';
import { CSSManager } from '../managers';
import type { AnyComponent, AnyRecord, CSSStyle, PlainStyle } from '../types';
import { filterNonCSSStyleProps } from './utils';

const SHOULD_BE_USE_WEB = shouldBeUseWeb();
const IS_WEB = isWeb();
Expand All @@ -37,7 +38,7 @@ export default class AnimatedComponent<
_CSSManager?: CSSManager;

_viewInfo?: ViewInfo;
_planStyle: CSSStyle = {};
_cssStyle: CSSStyle = {}; // RN style object with Reanimated CSS properties
_componentRef: AnimatedComponentRef | HTMLElement | null = null;
_hasAnimatedRef = false;
// Used only on web
Expand Down Expand Up @@ -148,15 +149,15 @@ export default class AnimatedComponent<
};

_updateStyles(props: P) {
this._planStyle = StyleSheet.flatten(props.style) ?? {};
this._cssStyle = StyleSheet.flatten(props.style) ?? {};
}

componentDidMount() {
this._updateStyles(this.props);

if (isFabric() || IS_WEB) {
this._CSSManager = new CSSManager(this._getViewInfo());
this._CSSManager?.attach(this._planStyle);
this._CSSManager?.attach(this._cssStyle);
}
}

Expand All @@ -168,14 +169,14 @@ export default class AnimatedComponent<
this._updateStyles(nextProps);

if (this._CSSManager) {
this._CSSManager.update(this._planStyle);
this._CSSManager.update(this._cssStyle);
}

// TODO - maybe check if the render is necessary instead of always returning true
return true;
}

render() {
render(props?: ComponentProps<AnyComponent>) {
const { ChildComponent } = this;

const platformProps = Platform.select({
Expand All @@ -186,7 +187,9 @@ export default class AnimatedComponent<
return (
<ChildComponent
{...this.props}
{...props}
{...platformProps}
style={filterNonCSSStyleProps(props?.style ?? this.props.style)}
// Casting is used here, because ref can be null - in that case it cannot be assigned to HTMLElement.
// After spending some time trying to figure out what to do with this problem, we decided to leave it this way
ref={this._setComponentRef as (ref: Component) => void}
Expand Down
42 changes: 42 additions & 0 deletions packages/react-native-reanimated/src/css/component/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
'use strict';
import type {
Falsy,
RecursiveArray,
RegisteredStyle,
StyleProp,
} from 'react-native';

import type { AnyRecord, CSSStyle } from '../types';
import { isCSSStyleProp } from '../utils/guards';

type BaseStyle = CSSStyle | Falsy | RegisteredStyle<CSSStyle>;
type StyleProps = BaseStyle | RecursiveArray<BaseStyle> | readonly BaseStyle[];

function filterNonCSSStylePropsRecursive(
props: StyleProps
): StyleProp<CSSStyle> {
if (Array.isArray(props)) {
return props.map(filterNonCSSStylePropsRecursive);
}

if (!props) {
return props;
}

if (typeof props === 'object') {
return Object.entries(props).reduce<AnyRecord>((acc, [key, value]) => {
if (!isCSSStyleProp(key)) {
acc[key] = value;
}
return acc;
}, {});
}

return props;
}

export function filterNonCSSStyleProps(
props: StyleProp<CSSStyle>
): StyleProp<CSSStyle> {
return filterNonCSSStylePropsRecursive(props);
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,9 @@ export const VALID_PREDEFINED_TIMING_FUNCTIONS: PredefinedTimingFunction[] = [
'step-start',
'step-end',
];

export const VALID_PARAMETRIZED_TIMING_FUNCTIONS: string[] = [
'cubic-bezier',
'steps',
'linear',
];
1 change: 1 addition & 0 deletions packages/react-native-reanimated/src/css/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,6 @@ export type {
CSSTransitionProperties,
CSSTransitionProperty,
CSSTransitionSettings,
CSSTransitionShorthand,
CSSTransitionTimingFunction,
} from './types';
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,6 @@ export default class CSSAnimationsManager {
}

attach(animationProperties: ExistingCSSAnimationProperties | null) {
if (!animationProperties) {
return;
}

this.update(animationProperties);
}

Expand Down Expand Up @@ -154,24 +150,17 @@ export default class CSSAnimationsManager {
) {
this.element.style.animationName = animationNames.join(',');

const maybeDuration = maybeAddSuffixes(
this.element.style.animationDuration = maybeAddSuffixes(
animationSettings,
'animationDuration',
'ms'
);
).join(',');

if (maybeDuration) {
this.element.style.animationDuration = maybeDuration.join(',');
}

const maybeDelay = maybeAddSuffixes(
this.element.style.animationDelay = maybeAddSuffixes(
animationSettings,
'animationDelay',
'ms'
);
if (maybeDelay) {
this.element.style.animationDelay = maybeDelay.join(',');
}
).join(',');

if (animationSettings.animationIterationCount) {
this.element.style.animationIterationCount =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,7 @@ export default class CSSManager {
}

attach(style: CSSStyle): void {
const [animationConfig, transitionConfig] =
filterCSSAndStyleProperties(style);

if (animationConfig) {
this.animationsManager.attach(animationConfig);
}

if (transitionConfig) {
this.transitionsManager.attach(transitionConfig);
}
this.update(style);
}

update(style: CSSStyle): void {
Expand Down
Loading

0 comments on commit 4597399

Please sign in to comment.