From 65f41eef20edc77058fb601ee2db0a62fd71b3e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20=C5=81opaci=C5=84ski?= Date: Tue, 25 Nov 2025 15:17:27 +0000 Subject: [PATCH 01/13] Implement CSS transition props diffing in JS --- .../src/common/types/helpers.ts | 1 + .../src/css/native/managers/CSSManager.ts | 2 +- .../native/managers/CSSTransitionsManager.ts | 45 ++++++++++------- .../src/css/types/interfaces.ts | 7 ++- .../src/css/utils/props.ts | 48 ++++++++++++++++++- 5 files changed, 82 insertions(+), 21 deletions(-) diff --git a/packages/react-native-reanimated/src/common/types/helpers.ts b/packages/react-native-reanimated/src/common/types/helpers.ts index 6a924c7633d0..2e9e4ba25a37 100644 --- a/packages/react-native-reanimated/src/common/types/helpers.ts +++ b/packages/react-native-reanimated/src/common/types/helpers.ts @@ -13,6 +13,7 @@ export type Maybe = T | null | undefined; export type NonMutable = T extends object ? Readonly : T; export type AnyRecord = Record; +export type UnknownRecord = Record; export type AnyComponent = ComponentType; diff --git a/packages/react-native-reanimated/src/css/native/managers/CSSManager.ts b/packages/react-native-reanimated/src/css/native/managers/CSSManager.ts index 6c1b9f4d8bb2..47854fe45eb1 100644 --- a/packages/react-native-reanimated/src/css/native/managers/CSSManager.ts +++ b/packages/react-native-reanimated/src/css/native/managers/CSSManager.ts @@ -54,7 +54,7 @@ export default class CSSManager implements ICSSManager { setViewStyle(this.viewTag, normalizedStyle); } - this.cssTransitionsManager.update(transitionProperties); + this.cssTransitionsManager.update(transitionProperties, normalizedStyle ?? null); this.cssAnimationsManager.update(animationProperties); // If the current update is not the fist one, we want to update CSS diff --git a/packages/react-native-reanimated/src/css/native/managers/CSSTransitionsManager.ts b/packages/react-native-reanimated/src/css/native/managers/CSSTransitionsManager.ts index 4f530ff4aa13..4388a4bc78a2 100644 --- a/packages/react-native-reanimated/src/css/native/managers/CSSTransitionsManager.ts +++ b/packages/react-native-reanimated/src/css/native/managers/CSSTransitionsManager.ts @@ -1,5 +1,6 @@ 'use strict'; import type { ShadowNodeWrapper } from '../../../commonTypes'; +import type { UnknownRecord } from '../../../common'; import type { CSSTransitionProperties, ICSSTransitionsManager, @@ -14,44 +15,54 @@ import { updateCSSTransition, } from '../proxy'; import type { NormalizedCSSTransitionConfig } from '../types'; +import { getChangedProps } from '../../utils'; export default class CSSTransitionsManager implements ICSSTransitionsManager { private readonly viewTag: number; private readonly shadowNodeWrapper: ShadowNodeWrapper; private transitionConfig: NormalizedCSSTransitionConfig | null = null; + private previousStyle: UnknownRecord | null = null; constructor(shadowNodeWrapper: ShadowNodeWrapper, viewTag: number) { this.viewTag = viewTag; this.shadowNodeWrapper = shadowNodeWrapper; } - update(transitionProperties: CSSTransitionProperties | null): void { - if (!transitionProperties) { - this.detach(); - return; - } + update( + transitionProperties: CSSTransitionProperties | null, + style: UnknownRecord | null + ): void { + const previousStyle = this.previousStyle; + this.previousStyle = style; + + const transitionConfig = transitionProperties + ? normalizeCSSTransitionProperties(transitionProperties) + : null; - const transitionConfig = - normalizeCSSTransitionProperties(transitionProperties); if (!transitionConfig) { this.detach(); return; } - if (this.transitionConfig) { - const configUpdates = getNormalizedCSSTransitionConfigUpdates( - this.transitionConfig, - transitionConfig - ); + getChangedProps(previousStyle, style, transitionConfig.properties); - if (Object.keys(configUpdates).length > 0) { - this.transitionConfig = transitionConfig; - updateCSSTransition(this.viewTag, configUpdates); - } - } else { + if (!this.transitionConfig) { this.attachTransition(transitionConfig); + return; + } + + const configUpdates = getNormalizedCSSTransitionConfigUpdates( + this.transitionConfig, + transitionConfig + ); + + if (Object.keys(configUpdates).length > 0) { + this.transitionConfig = transitionConfig; + updateCSSTransition(this.viewTag, configUpdates); } + + this.previousStyle = style; } unmountCleanup(): void { diff --git a/packages/react-native-reanimated/src/css/types/interfaces.ts b/packages/react-native-reanimated/src/css/types/interfaces.ts index 96417881bd86..432a480eeabb 100644 --- a/packages/react-native-reanimated/src/css/types/interfaces.ts +++ b/packages/react-native-reanimated/src/css/types/interfaces.ts @@ -1,6 +1,6 @@ 'use strict'; import type { ExistingCSSAnimationProperties } from './animation'; -import type { CSSStyle } from './props'; +import type { UnknownRecord } from '../common'; import type { CSSTransitionProperties } from './transition'; export interface ICSSAnimationsManager { @@ -9,7 +9,10 @@ export interface ICSSAnimationsManager { } export interface ICSSTransitionsManager { - update(transitionProperties: CSSTransitionProperties | null): void; + update( + transitionProperties: CSSTransitionProperties | null, + style: UnknownRecord | null + ): void; unmountCleanup(): void; } diff --git a/packages/react-native-reanimated/src/css/utils/props.ts b/packages/react-native-reanimated/src/css/utils/props.ts index 409ee768021f..c04140aa63b1 100644 --- a/packages/react-native-reanimated/src/css/utils/props.ts +++ b/packages/react-native-reanimated/src/css/utils/props.ts @@ -1,5 +1,5 @@ 'use strict'; -import type { AnyRecord, PlainStyle } from '../../common'; +import type { AnyRecord, PlainStyle, UnknownRecord } from '../../common'; import { logger } from '../../common'; import { isSharedValue } from '../../isSharedValue'; import type { @@ -8,6 +8,7 @@ import type { CSSTransitionProperties, ExistingCSSAnimationProperties, } from '../types'; +import { deepEqual } from './equality'; import { isAnimationProp, isCSSKeyframesObject, @@ -95,3 +96,48 @@ function validateCSSTransitionProps(props: Partial) { ); } } + +function hasValue(value: unknown): boolean { + return value !== null && value !== undefined; +} + +export type ChangedProps = Partial>; + +export function getChangedProps( + previousStyle: UnknownRecord | null, + nextStyle: UnknownRecord | null, + allowedProperties: string[] | 'all' +): ChangedProps | null { + if (!previousStyle || !nextStyle) { + return null; + } + + const monitoredProperties = Array.isArray(allowedProperties) + ? allowedProperties + : Array.from( + new Set([ + ...Object.keys(previousStyle), + ...Object.keys(nextStyle), + ]) + ); + + const diff: AnyRecord = {}; + + for (const property of monitoredProperties) { + const nextValue = nextStyle[property]; + const prevValue = previousStyle[property]; + + if (!hasValue(prevValue) || !hasValue(nextValue)) { + if (prevValue !== nextValue) { + diff[property] = { from: prevValue, to: nextValue }; + } + continue; + } + + if (!deepEqual(prevValue, nextValue)) { + diff[property] = { from: prevValue, to: nextValue }; + } + } + + return Object.keys(diff).length > 0 ? (diff as ChangedProps) : null; +} From 2c792ca8f7a26572adc632573d8cfa5922582f50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20=C5=81opaci=C5=84ski?= Date: Wed, 26 Nov 2025 10:24:45 +0000 Subject: [PATCH 02/13] Update JS part to apply transition based on props and settings diff --- .../native/managers/CSSTransitionsManager.ts | 119 ++++++++++++++---- .../src/css/native/proxy.ts | 15 +-- .../src/css/native/types/transition.ts | 5 + .../src/css/utils/props.ts | 12 +- 4 files changed, 108 insertions(+), 43 deletions(-) diff --git a/packages/react-native-reanimated/src/css/native/managers/CSSTransitionsManager.ts b/packages/react-native-reanimated/src/css/native/managers/CSSTransitionsManager.ts index 4388a4bc78a2..696f997efdce 100644 --- a/packages/react-native-reanimated/src/css/native/managers/CSSTransitionsManager.ts +++ b/packages/react-native-reanimated/src/css/native/managers/CSSTransitionsManager.ts @@ -5,16 +5,13 @@ import type { CSSTransitionProperties, ICSSTransitionsManager, } from '../../types'; -import { - getNormalizedCSSTransitionConfigUpdates, - normalizeCSSTransitionProperties, -} from '../normalization'; -import { - registerCSSTransition, - unregisterCSSTransition, - updateCSSTransition, -} from '../proxy'; -import type { NormalizedCSSTransitionConfig } from '../types'; +import { normalizeCSSTransitionProperties } from '../normalization'; +import { applyCSSTransition, unregisterCSSTransition } from '../proxy'; +import type { + CSSTransitionUpdates, + NormalizedCSSTransitionConfig, + NormalizedSingleCSSTransitionSettings, +} from '../types'; import { getChangedProps } from '../../utils'; export default class CSSTransitionsManager implements ICSSTransitionsManager { @@ -45,24 +42,26 @@ export default class CSSTransitionsManager implements ICSSTransitionsManager { return; } - getChangedProps(previousStyle, style, transitionConfig.properties); + const propertyDiff = getChangedProps( + previousStyle, + style, + transitionConfig.properties + ); - if (!this.transitionConfig) { - this.attachTransition(transitionConfig); + if (!propertyDiff) { return; } - const configUpdates = getNormalizedCSSTransitionConfigUpdates( - this.transitionConfig, - transitionConfig + const transitionUpdates = this.getTransitionUpdates( + transitionConfig, + propertyDiff ); - if (Object.keys(configUpdates).length > 0) { - this.transitionConfig = transitionConfig; - updateCSSTransition(this.viewTag, configUpdates); - } + this.transitionConfig = transitionConfig; - this.previousStyle = style; + if (transitionUpdates) { + applyCSSTransition(this.shadowNodeWrapper, transitionUpdates); + } } unmountCleanup(): void { @@ -76,10 +75,80 @@ export default class CSSTransitionsManager implements ICSSTransitionsManager { } } - private attachTransition(transitionConfig: NormalizedCSSTransitionConfig) { - if (!this.transitionConfig) { - registerCSSTransition(this.shadowNodeWrapper, transitionConfig); - this.transitionConfig = transitionConfig; + private getTransitionUpdates( + newConfig: NormalizedCSSTransitionConfig, + propertyDiff: Record + ): CSSTransitionUpdates | null { + const previousConfig = this.transitionConfig; + + if (!previousConfig) { + return { + properties: propertyDiff, + settings: newConfig.settings, + }; } + + const updates: CSSTransitionUpdates = {}; + + if (propertyDiff) { + updates.properties = propertyDiff; + } + + const settingsDiff = this.getSettingsUpdates(newConfig, previousConfig); + + if (settingsDiff) { + updates.settings = settingsDiff; + } + + return Object.keys(updates).length > 0 ? updates : null; + } + + private getSettingsUpdates( + newConfig: NormalizedCSSTransitionConfig, + previousConfig: NormalizedCSSTransitionConfig + ): Record> | null { + const diff: Record< + string, + Partial + > = {}; + + for (const [property, settings] of Object.entries(newConfig.settings)) { + const previousSettings = previousConfig.settings[property]; + + if (!previousSettings) { + diff[property] = settings; + continue; + } + + const propertyDiff = this.getSettingsDiff(previousSettings, settings); + + if (propertyDiff) { + diff[property] = propertyDiff; + } + } + + return Object.keys(diff).length > 0 ? diff : null; + } + + private getSettingsDiff( + previousSettings: NormalizedSingleCSSTransitionSettings, + nextSettings: NormalizedSingleCSSTransitionSettings + ): Partial | null { + const diff: Partial = {}; + + if (previousSettings.duration !== nextSettings.duration) { + diff.duration = nextSettings.duration; + } + if (previousSettings.delay !== nextSettings.delay) { + diff.delay = nextSettings.delay; + } + if (previousSettings.allowDiscrete !== nextSettings.allowDiscrete) { + diff.allowDiscrete = nextSettings.allowDiscrete; + } + if (previousSettings.timingFunction !== nextSettings.timingFunction) { + diff.timingFunction = nextSettings.timingFunction; + } + + return Object.keys(diff).length > 0 ? diff : null; } } diff --git a/packages/react-native-reanimated/src/css/native/proxy.ts b/packages/react-native-reanimated/src/css/native/proxy.ts index 914f40bfa5df..e2f27c96bc25 100644 --- a/packages/react-native-reanimated/src/css/native/proxy.ts +++ b/packages/react-native-reanimated/src/css/native/proxy.ts @@ -3,8 +3,8 @@ import type { ShadowNodeWrapper, StyleProps } from '../../commonTypes'; import { ReanimatedModule } from '../../ReanimatedModule'; import type { CSSAnimationUpdates, + CSSTransitionUpdates, NormalizedCSSAnimationKeyframesConfig, - NormalizedCSSTransitionConfig, } from './types'; // COMMON @@ -59,18 +59,11 @@ export function unregisterCSSAnimations(viewTag: number) { // TRANSITIONS -export function registerCSSTransition( +export function applyCSSTransition( shadowNodeWrapper: ShadowNodeWrapper, - transitionConfig: NormalizedCSSTransitionConfig + transitionUpdates: CSSTransitionUpdates ) { - ReanimatedModule.registerCSSTransition(shadowNodeWrapper, transitionConfig); -} - -export function updateCSSTransition( - viewTag: number, - configUpdates: Partial -) { - ReanimatedModule.updateCSSTransition(viewTag, configUpdates); + ReanimatedModule.applyCSSTransition(shadowNodeWrapper, transitionUpdates); } export function unregisterCSSTransition(viewTag: number) { diff --git a/packages/react-native-reanimated/src/css/native/types/transition.ts b/packages/react-native-reanimated/src/css/native/types/transition.ts index cd85232d9632..ec21f99bf2e5 100644 --- a/packages/react-native-reanimated/src/css/native/types/transition.ts +++ b/packages/react-native-reanimated/src/css/native/types/transition.ts @@ -17,3 +17,8 @@ export type NormalizedCSSTransitionConfig = { export type NormalizedCSSTransitionConfigUpdates = Partial; + +export type CSSTransitionUpdates = { + properties?: Record; + settings?: Record>; +}; diff --git a/packages/react-native-reanimated/src/css/utils/props.ts b/packages/react-native-reanimated/src/css/utils/props.ts index c04140aa63b1..0a5a8cbb962a 100644 --- a/packages/react-native-reanimated/src/css/utils/props.ts +++ b/packages/react-native-reanimated/src/css/utils/props.ts @@ -101,13 +101,11 @@ function hasValue(value: unknown): boolean { return value !== null && value !== undefined; } -export type ChangedProps = Partial>; - export function getChangedProps( previousStyle: UnknownRecord | null, nextStyle: UnknownRecord | null, allowedProperties: string[] | 'all' -): ChangedProps | null { +): Record | null { if (!previousStyle || !nextStyle) { return null; } @@ -121,7 +119,7 @@ export function getChangedProps( ]) ); - const diff: AnyRecord = {}; + const diff: Record = {}; for (const property of monitoredProperties) { const nextValue = nextStyle[property]; @@ -129,15 +127,15 @@ export function getChangedProps( if (!hasValue(prevValue) || !hasValue(nextValue)) { if (prevValue !== nextValue) { - diff[property] = { from: prevValue, to: nextValue }; + diff[property] = [prevValue, nextValue]; } continue; } if (!deepEqual(prevValue, nextValue)) { - diff[property] = { from: prevValue, to: nextValue }; + diff[property] = [prevValue, nextValue]; } } - return Object.keys(diff).length > 0 ? (diff as ChangedProps) : null; + return Object.keys(diff).length > 0 ? diff : null; } From 5fc275a14db35d44fe3e79eaa0ee2cec1f1fdac0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20=C5=81opaci=C5=84ski?= Date: Thu, 27 Nov 2025 12:14:48 +0000 Subject: [PATCH 03/13] Much progress --- .../CSS/configs/CSSTransitionConfig.cpp | 94 +++++++++++++ .../CSS/configs/CSSTransitionConfig.h | 21 +++ .../CSS/registries/CSSTransitionsRegistry.cpp | 37 ++++- .../CSS/registries/CSSTransitionsRegistry.h | 4 +- .../Common/cpp/reanimated/CSS/utils/props.cpp | 132 ------------------ .../Common/cpp/reanimated/CSS/utils/props.h | 16 --- .../NativeModules/ReanimatedModuleProxy.cpp | 19 +-- .../NativeModules/ReanimatedModuleProxy.h | 3 +- .../ReanimatedModuleProxySpec.cpp | 14 +- .../NativeModules/ReanimatedModuleProxySpec.h | 3 +- .../src/ReanimatedModule/NativeReanimated.ts | 27 +++- .../js-reanimated/JSReanimated.ts | 8 +- .../ReanimatedModule/reanimatedModuleProxy.ts | 7 +- .../native/managers/CSSTransitionsManager.ts | 121 ++++++++-------- .../src/css/native/proxy.ts | 15 +- .../src/css/native/types/transition.ts | 11 +- 16 files changed, 263 insertions(+), 269 deletions(-) diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/configs/CSSTransitionConfig.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/configs/CSSTransitionConfig.cpp index f9b52ed3ef3b..36081650a1e5 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/configs/CSSTransitionConfig.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/configs/CSSTransitionConfig.cpp @@ -87,4 +87,98 @@ PartialCSSTransitionConfig parsePartialCSSTransitionConfig(jsi::Runtime &rt, con return result; } +static PartialCSSTransitionPropertySettings +parsePartialPropertySettings(jsi::Runtime &rt, const jsi::Object &settings) { + PartialCSSTransitionPropertySettings result; + + if (settings.hasProperty(rt, "duration")) { + result.duration = getDuration(rt, settings); + } + + if (settings.hasProperty(rt, "delay")) { + result.delay = getDelay(rt, settings); + } + + if (settings.hasProperty(rt, "allowDiscrete")) { + const auto allowDiscreteValue = settings.getProperty(rt, "allowDiscrete"); + if (allowDiscreteValue.isBool()) { + result.allowDiscrete = allowDiscreteValue.getBool(); + } + } + + if (settings.hasProperty(rt, "timingFunction")) { + result.easingFunction = getTimingFunction(rt, settings); + } + + return result; +} +static CSSTransitionPropertySettingsUpdates +parseSettingsUpdatesObject(jsi::Runtime &rt, const jsi::Object &settings) { + CSSTransitionPropertySettingsUpdates result; + const auto propertyNames = settings.getPropertyNames(rt); + const auto count = propertyNames.size(rt); + + for (size_t i = 0; i < count; ++i) { + const auto propertyName = propertyNames.getValueAtIndex(rt, i).asString(rt).utf8(rt); + const auto propertyValue = settings.getProperty(rt, jsi::PropNameID::forUtf8(rt, propertyName)); + + if (!propertyValue.isObject()) { + continue; + } + + const auto propertySettings = propertyValue.asObject(rt); + result.emplace(propertyName, parsePartialPropertySettings(rt, propertySettings)); + } + + return result; +} +static CSSTransitionPropertyDiffs +parsePropertyDiffs(jsi::Runtime &rt, const jsi::Object &diffs) { + CSSTransitionPropertyDiffs result; + const auto propertyNames = diffs.getPropertyNames(rt); + const auto count = propertyNames.size(rt); + + for (size_t i = 0; i < count; ++i) { + const auto propertyName = propertyNames.getValueAtIndex(rt, i).asString(rt).utf8(rt); + const auto diffValue = diffs.getProperty(rt, jsi::PropNameID::forUtf8(rt, propertyName)); + + if (!diffValue.isObject()) { + continue; + } + + const auto diffArray = diffValue.asObject(rt).asArray(rt); + if (diffArray.size(rt) != 2) { + continue; + } + + result.emplace(propertyName, std::make_pair(diffArray.getValueAtIndex(rt, 0), diffArray.getValueAtIndex(rt, 1))); + } + + return result; +} + +CSSTransitionUpdates parseCSSTransitionUpdates(jsi::Runtime &rt, const jsi::Value &updates) { + const auto updatesObj = updates.asObject(rt); + + CSSTransitionUpdates result; + + if (updatesObj.hasProperty(rt, "properties")) { + const auto propertiesValue = updatesObj.getProperty(rt, "properties"); + auto propertyDiffs = parsePropertyDiffs(rt, propertiesValue.asObject(rt)); + if (!propertyDiffs.empty()) { + result.properties = std::move(propertyDiffs); + } + } + + if (updatesObj.hasProperty(rt, "settings")) { + const auto settingsValue = updatesObj.getProperty(rt, "settings"); + auto settingsUpdates = parseSettingsUpdatesObject(rt, settingsValue.asObject(rt)); + if (!settingsUpdates.empty()) { + result.settings = std::move(settingsUpdates); + } + } + + return result; +} + } // namespace reanimated::css diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/configs/CSSTransitionConfig.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/configs/CSSTransitionConfig.h index ca78dd547963..200b9f5e8dcc 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/configs/CSSTransitionConfig.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/configs/CSSTransitionConfig.h @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -28,6 +29,24 @@ struct PartialCSSTransitionConfig { std::optional settings; }; +struct PartialCSSTransitionPropertySettings { + std::optional duration; + std::optional easingFunction; + std::optional delay; + std::optional allowDiscrete; +}; + +using CSSTransitionPropertySettingsUpdates = + std::unordered_map; + +using CSSTransitionPropertyDiffs = + std::unordered_map>; + +struct CSSTransitionUpdates { + std::optional properties; + std::optional settings; +}; + std::optional getTransitionPropertySettings( const CSSTransitionPropertiesSettings &propertiesSettings, const std::string &propName); @@ -40,4 +59,6 @@ CSSTransitionConfig parseCSSTransitionConfig(jsi::Runtime &rt, const jsi::Value PartialCSSTransitionConfig parsePartialCSSTransitionConfig(jsi::Runtime &rt, const jsi::Value &partialConfig); +CSSTransitionUpdates parseCSSTransitionUpdates(jsi::Runtime &rt, const jsi::Value &updates); + } // namespace reanimated::css diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.cpp index cafc6c10f161..1b43a85950cc 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.cpp @@ -37,13 +37,38 @@ void CSSTransitionsRegistry::remove(const Tag viewTag) { registry_.erase(viewTag); } -void CSSTransitionsRegistry::updateSettings(const Tag viewTag, const PartialCSSTransitionConfig &config) { - const auto &transition = registry_[viewTag]; - transition->updateSettings(config); +void CSSTransitionsRegistry::apply(jsi::Runtime &rt, const Tag viewTag, const CSSTransitionUpdates &updates) { + auto transitionIt = registry_.find(viewTag); - // Replace style overrides with the new ones if transition properties were - // updated (we want to keep overrides only for transitioned properties) - if (config.properties.has_value()) { + if (transitionIt == registry_.end()) { + auto shadowNode = staticPropsRegistry_->getShadowNode(viewTag); + if (!shadowNode || !updates.settings.has_value()) { + return; + } + + CSSTransitionConfig config; + if (updates.properties.has_value()) { + PropertyNames propertyNames; + propertyNames.reserve(updates.properties->size()); + for (const auto &entry : *updates.properties) { + propertyNames.push_back(entry.first); + } + config.properties = propertyNames; + } else { + config.properties = std::nullopt; + } + + config.settings = parseCSSTransitionPropertiesSettings(rt, *updates.settings); + + auto transition = std::make_shared(std::move(shadowNode), config, viewStylesRepository_); + add(transition); + transitionIt = registry_.find(viewTag); + } + + const auto &transition = transitionIt->second; + transition->applyUpdates(rt, updates); + + if (updates.properties.has_value()) { updateInUpdatesRegistry(transition, transition->getCurrentInterpolationStyle()); } } diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.h index dc497118f33b..5e96f6b77352 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.h @@ -1,9 +1,9 @@ #pragma once +#include #include #include #include -#include #include #include @@ -25,8 +25,8 @@ class CSSTransitionsRegistry : public UpdatesRegistry, public std::enable_shared bool isEmpty() const override; bool hasUpdates() const; + void apply(jsi::Runtime &rt, Tag viewTag, const CSSTransitionUpdates &updates); void add(const std::shared_ptr &transition); - void updateSettings(Tag viewTag, const PartialCSSTransitionConfig &config); void remove(Tag viewTag) override; void update(double timestamp); diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/utils/props.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/utils/props.cpp index ac6ff0fc78fd..d904712662b7 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/utils/props.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/utils/props.cpp @@ -14,136 +14,4 @@ bool isDiscreteProperty(const std::string &propName, const std::string &componen return it->second->isDiscreteProperty(); } -bool areArraysDifferentRecursive(const folly::dynamic &oldArray, const folly::dynamic &newArray) { - if (oldArray.size() != newArray.size()) { - return true; - } - - for (size_t i = 0; i < oldArray.size(); i++) { - const auto [oldChangedProp, newChangedProp] = getChangedPropsRecursive(oldArray[i], newArray[i]); - - if (!oldChangedProp.isNull() || !newChangedProp.isNull()) { - return true; - } - } - - return false; -} - -std::pair getChangedPropsRecursive( - const folly::dynamic &oldProp, - const folly::dynamic &newProp) { - if (!oldProp.isObject() || !newProp.isObject()) { - // Primitive values comparison - if (oldProp != newProp) { - return {oldProp, newProp}; - } - return {folly::dynamic(), folly::dynamic()}; - } - - if (oldProp.isArray() && newProp.isArray()) { - // Arrays comparison - if (areArraysDifferentRecursive(oldProp, newProp)) { - return {oldProp, newProp}; - } - return {folly::dynamic(), folly::dynamic()}; - } - - folly::dynamic oldResult = folly::dynamic::object; - folly::dynamic newResult = folly::dynamic::object; - bool oldHasChanges = false; - bool newHasChanges = false; - - // Check for removed properties - for (const auto &item : oldProp.items()) { - const auto &propName = item.first.asString(); - if (!newProp.count(propName)) { - const auto &oldValue = item.second; - oldResult[propName] = oldValue; - oldHasChanges = true; - } - } - - // Check for new and changed properties - for (const auto &item : newProp.items()) { - const auto &propName = item.first.asString(); - const auto &newValue = item.second; - - if (oldProp.count(propName)) { - const auto &oldValue = oldProp[propName]; - auto [oldChangedProp, newChangedProp] = getChangedPropsRecursive(oldValue, newValue); - - if (!oldChangedProp.isNull() && !newChangedProp.isNull()) { - oldResult[propName] = std::move(oldChangedProp); - newResult[propName] = std::move(newChangedProp); - oldHasChanges = true; - newHasChanges = true; - } - } else { - newResult[propName] = newValue; - newHasChanges = true; - } - } - - return { - oldHasChanges ? std::move(oldResult) : folly::dynamic(), newHasChanges ? std::move(newResult) : folly::dynamic()}; -} - -std::pair -getChangedValueForProp(const folly::dynamic &oldObject, const folly::dynamic &newObject, const std::string &propName) { - const bool oldHasProperty = oldObject.count(propName); - const bool newHasProperty = newObject.count(propName); - - if (oldHasProperty && newHasProperty) { - const auto &oldVal = oldObject[propName]; - const auto &newVal = newObject[propName]; - - if (oldVal.isObject() && newVal.isObject()) { - return getChangedPropsRecursive(oldVal, newVal); - } else if (oldVal != newVal) { - return {oldVal, newVal}; - } - - return {folly::dynamic(), folly::dynamic()}; - } - - if (oldHasProperty) { - const auto &oldVal = oldObject[propName]; - return {oldVal, folly::dynamic()}; - } else if (newHasProperty) { - const auto &newVal = newObject[propName]; - return {folly::dynamic(), newVal}; - } - - return {folly::dynamic(), folly::dynamic()}; -} - -ChangedProps getChangedProps( - const folly::dynamic &oldProps, - const folly::dynamic &newProps, - const PropertyNames &allowedProperties) { - folly::dynamic oldResult = folly::dynamic::object; - folly::dynamic newResult = folly::dynamic::object; - PropertyNames changedPropertyNames; - - for (const auto &propName : allowedProperties) { - auto [oldChangedProp, newChangedProp] = getChangedValueForProp(oldProps, newProps, propName); - - const auto hasOldChangedProp = !oldChangedProp.isNull(); - const auto hasNewChangedProp = !newChangedProp.isNull(); - - if (hasOldChangedProp) { - oldResult[propName] = std::move(oldChangedProp); - } - if (hasNewChangedProp) { - newResult[propName] = std::move(newChangedProp); - } - if (hasOldChangedProp || hasNewChangedProp) { - changedPropertyNames.push_back(propName); - } - } - - return {std::move(oldResult), std::move(newResult), std::move(changedPropertyNames)}; -} - } // namespace reanimated::css diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/utils/props.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/utils/props.h index fa21ec606eea..5e286c224654 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/utils/props.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/utils/props.h @@ -11,22 +11,6 @@ namespace reanimated::css { -struct ChangedProps { - const folly::dynamic oldProps; - const folly::dynamic newProps; - const PropertyNames changedPropertyNames; -}; - bool isDiscreteProperty(const std::string &propName, const std::string &componentName); -// We need to specify it here because there are 2 methods referencing -// each other in the recursion and areArraysDifferentRecursive must be -// aware that getChangedPropsRecursive exists -std::pair getChangedPropsRecursive( - const folly::dynamic &oldProp, - const folly::dynamic &newProp); - -ChangedProps -getChangedProps(const folly::dynamic &oldProps, const folly::dynamic &newProps, const PropertyNames &allowedProperties); - } // namespace reanimated::css diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp index 454eeb1a4cc0..9801ad42c8d4 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp @@ -416,31 +416,20 @@ void ReanimatedModuleProxy::unregisterCSSAnimations(const jsi::Value &viewTag) { cssAnimationsRegistry_->remove(viewTag.asNumber()); } -void ReanimatedModuleProxy::registerCSSTransition( +void ReanimatedModuleProxy::applyCSSTransition( jsi::Runtime &rt, const jsi::Value &shadowNodeWrapper, - const jsi::Value &transitionConfig) { + const jsi::Value &transitionUpdates) { auto shadowNode = shadowNodeFromValue(rt, shadowNodeWrapper); - - auto transition = std::make_shared( - std::move(shadowNode), parseCSSTransitionConfig(rt, transitionConfig), viewStylesRepository_); + const auto updates = parseCSSTransitionUpdates(rt, transitionUpdates); { auto lock = cssTransitionsRegistry_->lock(); - cssTransitionsRegistry_->add(transition); + cssTransitionsRegistry_->apply(shadowNode->getTag(), updates); } maybeRunCSSLoop(); } -void ReanimatedModuleProxy::updateCSSTransition( - jsi::Runtime &rt, - const jsi::Value &viewTag, - const jsi::Value &configUpdates) { - auto lock = cssTransitionsRegistry_->lock(); - cssTransitionsRegistry_->updateSettings(viewTag.asNumber(), parsePartialCSSTransitionConfig(rt, configUpdates)); - maybeRunCSSLoop(); -} - void ReanimatedModuleProxy::unregisterCSSTransition(jsi::Runtime &rt, const jsi::Value &viewTag) { auto lock = cssTransitionsRegistry_->lock(); cssTransitionsRegistry_->remove(viewTag.asNumber()); diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.h b/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.h index 599a0157f8c6..79775d741eb2 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.h @@ -117,9 +117,8 @@ class ReanimatedModuleProxy : public ReanimatedModuleProxySpec, override; void unregisterCSSAnimations(const jsi::Value &viewTag) override; - void registerCSSTransition(jsi::Runtime &rt, const jsi::Value &shadowNodeWrapper, const jsi::Value &transitionConfig) + void applyCSSTransition(jsi::Runtime &rt, const jsi::Value &shadowNodeWrapper, const jsi::Value &transitionUpdates) override; - void updateCSSTransition(jsi::Runtime &rt, const jsi::Value &viewTag, const jsi::Value &configUpdates) override; void unregisterCSSTransition(jsi::Runtime &rt, const jsi::Value &viewTag) override; void cssLoopCallback(const double /*timestampMs*/); diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxySpec.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxySpec.cpp index 7e89477abdfc..4e13e2ef878a 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxySpec.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxySpec.cpp @@ -120,16 +120,9 @@ static jsi::Value REANIMATED_SPEC_PREFIX( } static jsi::Value REANIMATED_SPEC_PREFIX( - registerCSSTransition)(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value *args, size_t) { + applyCSSTransition)(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value *args, size_t) { static_cast(&turboModule) - ->registerCSSTransition(rt, std::move(args[0]), std::move(args[1])); - return jsi::Value::undefined(); -} - -static jsi::Value REANIMATED_SPEC_PREFIX( - updateCSSTransition)(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value *args, size_t) { - static_cast(&turboModule) - ->updateCSSTransition(rt, std::move(args[0]), std::move(args[1])); + ->applyCSSTransition(rt, std::move(args[0]), std::move(args[1])); return jsi::Value::undefined(); } @@ -168,8 +161,7 @@ ReanimatedModuleProxySpec::ReanimatedModuleProxySpec(const std::shared_ptr + transitionUpdates: CSSTransitionUpdates ) { - this.#reanimatedModuleProxy.updateCSSTransition(viewTag, configUpdates); + this.#reanimatedModuleProxy.updateCSSTransition( + viewTag, + transitionUpdates + ); } unregisterCSSTransition(viewTag: number) { @@ -271,8 +277,15 @@ class DummyReanimatedModuleProxy implements ReanimatedModuleProxy { registerCSSAnimations(): void {} updateCSSAnimations(): void {} unregisterCSSAnimations(): void {} - registerCSSTransition(): void {} - updateCSSTransition(): void {} + registerCSSTransition( + _shadowNodeWrapper: ShadowNodeWrapper, + _transitionConfig: NormalizedCSSTransitionConfig, + _transitionDiff: CSSTransitionUpdates + ): void {} + updateCSSTransition( + _viewTag: number, + _transitionUpdates: CSSTransitionUpdates + ): void {} unregisterCSSTransition(): void {} registerSensor(): number { return -1; diff --git a/packages/react-native-reanimated/src/ReanimatedModule/js-reanimated/JSReanimated.ts b/packages/react-native-reanimated/src/ReanimatedModule/js-reanimated/JSReanimated.ts index 00cd647af6d9..ed5ac419582b 100644 --- a/packages/react-native-reanimated/src/ReanimatedModule/js-reanimated/JSReanimated.ts +++ b/packages/react-native-reanimated/src/ReanimatedModule/js-reanimated/JSReanimated.ts @@ -23,8 +23,9 @@ import type { import { SensorType } from '../../commonTypes'; import type { CSSAnimationUpdates, - NormalizedCSSAnimationKeyframesConfig, + CSSTransitionUpdates, NormalizedCSSTransitionConfig, + NormalizedCSSAnimationKeyframesConfig, } from '../../css/native'; import { assertWorkletsVersion } from '../../platform-specific/workletsVersion'; import type { IReanimatedModule } from '../reanimatedModuleProxy'; @@ -319,7 +320,8 @@ class JSReanimated implements IReanimatedModule { registerCSSTransition( _shadowNodeWrapper: ShadowNodeWrapper, - _transitionConfig: NormalizedCSSTransitionConfig + _transitionConfig: NormalizedCSSTransitionConfig, + _transitionDiff: CSSTransitionUpdates ): void { throw new ReanimatedError( '`registerCSSTransition` is not available in JSReanimated.' @@ -328,7 +330,7 @@ class JSReanimated implements IReanimatedModule { updateCSSTransition( _viewTag: number, - _settingsUpdates: Partial + _transitionUpdates: CSSTransitionUpdates ): void { throw new ReanimatedError( '`updateCSSTransition` is not available in JSReanimated.' diff --git a/packages/react-native-reanimated/src/ReanimatedModule/reanimatedModuleProxy.ts b/packages/react-native-reanimated/src/ReanimatedModule/reanimatedModuleProxy.ts index 7eb2a09904b8..f6d6418ee086 100644 --- a/packages/react-native-reanimated/src/ReanimatedModule/reanimatedModuleProxy.ts +++ b/packages/react-native-reanimated/src/ReanimatedModule/reanimatedModuleProxy.ts @@ -12,8 +12,9 @@ import type { } from '../commonTypes'; import type { CSSAnimationUpdates, + CSSTransitionUpdates, NormalizedCSSAnimationKeyframesConfig, - NormalizedCSSTransitionConfig, + NormalizedCSSTransitionConfigWithDiff, } from '../css/native'; /** Type of `__reanimatedModuleProxy` injected with JSI. */ @@ -81,12 +82,12 @@ export interface ReanimatedModuleProxy { registerCSSTransition( shadowNodeWrapper: ShadowNodeWrapper, - transitionConfig: NormalizedCSSTransitionConfig + transitionConfig: NormalizedCSSTransitionConfigWithDiff ): void; updateCSSTransition( viewTag: number, - settingsUpdates: Partial + transitionUpdates: CSSTransitionUpdates ): void; unregisterCSSTransition(viewTag: number): void; diff --git a/packages/react-native-reanimated/src/css/native/managers/CSSTransitionsManager.ts b/packages/react-native-reanimated/src/css/native/managers/CSSTransitionsManager.ts index 696f997efdce..f92a23a812d8 100644 --- a/packages/react-native-reanimated/src/css/native/managers/CSSTransitionsManager.ts +++ b/packages/react-native-reanimated/src/css/native/managers/CSSTransitionsManager.ts @@ -6,7 +6,11 @@ import type { ICSSTransitionsManager, } from '../../types'; import { normalizeCSSTransitionProperties } from '../normalization'; -import { applyCSSTransition, unregisterCSSTransition } from '../proxy'; +import { + registerCSSTransition, + updateCSSTransition, + unregisterCSSTransition, +} from '../proxy'; import type { CSSTransitionUpdates, NormalizedCSSTransitionConfig, @@ -31,7 +35,7 @@ export default class CSSTransitionsManager implements ICSSTransitionsManager { style: UnknownRecord | null ): void { const previousStyle = this.previousStyle; - this.previousStyle = style; + const previousConfig = this.transitionConfig; const transitionConfig = transitionProperties ? normalizeCSSTransitionProperties(transitionProperties) @@ -42,26 +46,38 @@ export default class CSSTransitionsManager implements ICSSTransitionsManager { return; } - const propertyDiff = getChangedProps( - previousStyle, - style, - transitionConfig.properties - ); + const propertyDiff = + transitionConfig.properties.length > 0 + ? getChangedProps(previousStyle, style, transitionConfig.properties) + : null; - if (!propertyDiff) { - return; - } + this.previousStyle = style; - const transitionUpdates = this.getTransitionUpdates( - transitionConfig, - propertyDiff - ); + if (propertyDiff) { + if (!previousConfig) { + registerCSSTransition(this.shadowNodeWrapper, { + properties: propertyDiff, + settings: transitionConfig.settings, + }); + } else { + const updates: CSSTransitionUpdates = { + properties: propertyDiff, + }; + + const settingsDiff = this.getSettingsUpdates( + transitionConfig, + previousConfig + ); + + if (settingsDiff) { + updates.settings = settingsDiff; + } + + updateCSSTransition(this.viewTag, updates); + } + } this.transitionConfig = transitionConfig; - - if (transitionUpdates) { - applyCSSTransition(this.shadowNodeWrapper, transitionUpdates); - } } unmountCleanup(): void { @@ -71,36 +87,9 @@ export default class CSSTransitionsManager implements ICSSTransitionsManager { private detach() { if (this.transitionConfig) { unregisterCSSTransition(this.viewTag); - this.transitionConfig = null; - } - } - - private getTransitionUpdates( - newConfig: NormalizedCSSTransitionConfig, - propertyDiff: Record - ): CSSTransitionUpdates | null { - const previousConfig = this.transitionConfig; - - if (!previousConfig) { - return { - properties: propertyDiff, - settings: newConfig.settings, - }; - } - - const updates: CSSTransitionUpdates = {}; - - if (propertyDiff) { - updates.properties = propertyDiff; - } - - const settingsDiff = this.getSettingsUpdates(newConfig, previousConfig); - - if (settingsDiff) { - updates.settings = settingsDiff; } - - return Object.keys(updates).length > 0 ? updates : null; + this.transitionConfig = null; + this.previousStyle = null; } private getSettingsUpdates( @@ -112,43 +101,43 @@ export default class CSSTransitionsManager implements ICSSTransitionsManager { Partial > = {}; - for (const [property, settings] of Object.entries(newConfig.settings)) { - const previousSettings = previousConfig.settings[property]; - - if (!previousSettings) { - diff[property] = settings; - continue; - } - - const propertyDiff = this.getSettingsDiff(previousSettings, settings); + for (const [property, nextSettings] of Object.entries(newConfig.settings)) { + const perPropertyDiff = this.getPropertySettingsDiff( + previousConfig.settings[property], + nextSettings + ); - if (propertyDiff) { - diff[property] = propertyDiff; + if (perPropertyDiff) { + diff[property] = perPropertyDiff; } } return Object.keys(diff).length > 0 ? diff : null; } - private getSettingsDiff( - previousSettings: NormalizedSingleCSSTransitionSettings, + private getPropertySettingsDiff( + previousSettings: NormalizedSingleCSSTransitionSettings | undefined, nextSettings: NormalizedSingleCSSTransitionSettings ): Partial | null { - const diff: Partial = {}; + if (!previousSettings) { + return nextSettings; + } + + const settingsDiff: Partial = {}; if (previousSettings.duration !== nextSettings.duration) { - diff.duration = nextSettings.duration; + settingsDiff.duration = nextSettings.duration; } if (previousSettings.delay !== nextSettings.delay) { - diff.delay = nextSettings.delay; + settingsDiff.delay = nextSettings.delay; } if (previousSettings.allowDiscrete !== nextSettings.allowDiscrete) { - diff.allowDiscrete = nextSettings.allowDiscrete; + settingsDiff.allowDiscrete = nextSettings.allowDiscrete; } if (previousSettings.timingFunction !== nextSettings.timingFunction) { - diff.timingFunction = nextSettings.timingFunction; + settingsDiff.timingFunction = nextSettings.timingFunction; } - return Object.keys(diff).length > 0 ? diff : null; + return Object.keys(settingsDiff).length > 0 ? settingsDiff : null; } } diff --git a/packages/react-native-reanimated/src/css/native/proxy.ts b/packages/react-native-reanimated/src/css/native/proxy.ts index e2f27c96bc25..5012cb1bf1ea 100644 --- a/packages/react-native-reanimated/src/css/native/proxy.ts +++ b/packages/react-native-reanimated/src/css/native/proxy.ts @@ -5,6 +5,8 @@ import type { CSSAnimationUpdates, CSSTransitionUpdates, NormalizedCSSAnimationKeyframesConfig, + NormalizedCSSTransitionConfig, + NormalizedCSSTransitionConfigWithDiff, } from './types'; // COMMON @@ -59,11 +61,18 @@ export function unregisterCSSAnimations(viewTag: number) { // TRANSITIONS -export function applyCSSTransition( +export function registerCSSTransition( shadowNodeWrapper: ShadowNodeWrapper, - transitionUpdates: CSSTransitionUpdates + config: NormalizedCSSTransitionConfigWithDiff ) { - ReanimatedModule.applyCSSTransition(shadowNodeWrapper, transitionUpdates); + ReanimatedModule.registerCSSTransition(shadowNodeWrapper, config); +} + +export function updateCSSTransition( + viewTag: number, + updates: CSSTransitionUpdates +) { + ReanimatedModule.updateCSSTransition(viewTag, updates); } export function unregisterCSSTransition(viewTag: number) { diff --git a/packages/react-native-reanimated/src/css/native/types/transition.ts b/packages/react-native-reanimated/src/css/native/types/transition.ts index ec21f99bf2e5..dcc07171b0e6 100644 --- a/packages/react-native-reanimated/src/css/native/types/transition.ts +++ b/packages/react-native-reanimated/src/css/native/types/transition.ts @@ -15,10 +15,19 @@ export type NormalizedCSSTransitionConfig = { settings: Record; }; +export type CSSTransitionPropertiesDiff = Record; + +export type NormalizedCSSTransitionConfigWithDiff = Omit< + NormalizedCSSTransitionConfig, + 'properties' +> & { + properties: CSSTransitionPropertiesDiff; +}; + export type NormalizedCSSTransitionConfigUpdates = Partial; export type CSSTransitionUpdates = { - properties?: Record; + properties?: CSSTransitionPropertiesDiff; settings?: Record>; }; From 5312ed8eaa61d08043a87c1718eb27ea0d7134a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20=C5=81opaci=C5=84ski?= Date: Thu, 27 Nov 2025 15:58:03 +0000 Subject: [PATCH 04/13] More progress --- .../CSS/configs/CSSTransitionConfig.cpp | 187 +++++++----------- .../CSS/configs/CSSTransitionConfig.h | 22 +-- .../cpp/reanimated/CSS/core/CSSTransition.cpp | 65 +++++- .../cpp/reanimated/CSS/core/CSSTransition.h | 9 +- .../CSS/interpolation/PropertyInterpolator.h | 6 +- .../groups/ArrayPropertiesInterpolator.cpp | 43 ++-- .../groups/ArrayPropertiesInterpolator.h | 6 +- .../groups/RecordPropertiesInterpolator.cpp | 56 ++++-- .../groups/RecordPropertiesInterpolator.h | 6 +- .../OperationsStyleInterpolator.cpp | 7 +- .../operations/OperationsStyleInterpolator.h | 6 +- .../values/ValueInterpolator.cpp | 34 ++-- .../interpolation/values/ValueInterpolator.h | 8 +- .../CSS/registries/CSSTransitionsRegistry.cpp | 54 ++--- .../CSS/registries/CSSTransitionsRegistry.h | 4 +- .../NativeModules/ReanimatedModuleProxy.cpp | 22 ++- .../NativeModules/ReanimatedModuleProxy.h | 10 +- .../ReanimatedModuleProxySpec.cpp | 14 +- .../NativeModules/ReanimatedModuleProxySpec.h | 3 +- 19 files changed, 306 insertions(+), 256 deletions(-) diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/configs/CSSTransitionConfig.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/configs/CSSTransitionConfig.cpp index 36081650a1e5..002082e2155c 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/configs/CSSTransitionConfig.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/configs/CSSTransitionConfig.cpp @@ -1,124 +1,67 @@ #include -#include +#include -namespace reanimated::css { +#include -std::optional getTransitionPropertySettings( - const CSSTransitionPropertiesSettings &propertiesSettings, - const std::string &propName) { - // Try to use property specific settings first - const auto &propIt = propertiesSettings.find(propName); - if (propIt != propertiesSettings.end()) { - return propIt->second; - } - // Fallback to "all" settings if no property specific settings are available - const auto &allIt = propertiesSettings.find("all"); - if (allIt != propertiesSettings.end()) { - return allIt->second; - } - // Or return nullopt if no settings are available - return std::nullopt; -} +namespace reanimated::css { -TransitionProperties getProperties(jsi::Runtime &rt, const jsi::Object &config) { - const auto transitionProperty = config.getProperty(rt, "properties"); +CSSTransitionPropertyDiffs parsePropertyDiffs(jsi::Runtime &rt, const jsi::Object &diffs) { + CSSTransitionPropertyDiffs result; + const auto propertyNames = diffs.getPropertyNames(rt); + const auto propertyCount = propertyNames.size(rt); - if (transitionProperty.isObject()) { - PropertyNames properties; + for (size_t i = 0; i < propertyCount; ++i) { + const auto propertyName = propertyNames.getValueAtIndex(rt, i).asString(rt).utf8(rt); + const auto diffValue = diffs.getProperty(rt, jsi::PropNameID::forUtf8(rt, propertyName)); - const auto propertiesArray = transitionProperty.asObject(rt).asArray(rt); - const auto propertiesCount = propertiesArray.size(rt); - for (size_t i = 0; i < propertiesCount; ++i) { - properties.emplace_back(propertiesArray.getValueAtIndex(rt, i).asString(rt).utf8(rt)); + if (!diffValue.isObject()) { + continue; } - return properties; - } - - return std::nullopt; -} - -bool getAllowDiscrete(jsi::Runtime &rt, const jsi::Object &config) { - return config.getProperty(rt, "allowDiscrete").asBool(); -} - -CSSTransitionPropertiesSettings parseCSSTransitionPropertiesSettings(jsi::Runtime &rt, const jsi::Object &settings) { - CSSTransitionPropertiesSettings result; - - const auto propertyNames = settings.getPropertyNames(rt); - const auto propertiesCount = propertyNames.size(rt); - - for (size_t i = 0; i < propertiesCount; ++i) { - const auto propertyName = propertyNames.getValueAtIndex(rt, i).asString(rt).utf8(rt); - const auto propertySettings = settings.getProperty(rt, jsi::PropNameID::forUtf8(rt, propertyName)).asObject(rt); + const auto diffArray = diffValue.asObject(rt).asArray(rt); + if (diffArray.size(rt) != 2) { + continue; + } result.emplace( propertyName, - CSSTransitionPropertySettings{ - getDuration(rt, propertySettings), - getTimingFunction(rt, propertySettings), - getDelay(rt, propertySettings), - getAllowDiscrete(rt, propertySettings)}); + std::make_pair(diffArray.getValueAtIndex(rt, 0), diffArray.getValueAtIndex(rt, 1))); } return result; } -CSSTransitionConfig parseCSSTransitionConfig(jsi::Runtime &rt, const jsi::Value &config) { - const auto configObj = config.asObject(rt); - return CSSTransitionConfig{ - getProperties(rt, configObj), - parseCSSTransitionPropertiesSettings(rt, configObj.getProperty(rt, "settings").asObject(rt))}; -} - -PartialCSSTransitionConfig parsePartialCSSTransitionConfig(jsi::Runtime &rt, const jsi::Value &partialConfig) { - const auto partialObj = partialConfig.asObject(rt); - - PartialCSSTransitionConfig result; - - if (partialObj.hasProperty(rt, "properties")) { - result.properties = getProperties(rt, partialObj); - } - if (partialObj.hasProperty(rt, "settings")) { - result.settings = parseCSSTransitionPropertiesSettings(rt, partialObj.getProperty(rt, "settings").asObject(rt)); - } - - return result; -} - -static PartialCSSTransitionPropertySettings -parsePartialPropertySettings(jsi::Runtime &rt, const jsi::Object &settings) { +PartialCSSTransitionPropertySettings parsePartialPropertySettings( + jsi::Runtime &rt, + const jsi::Object &settings) { PartialCSSTransitionPropertySettings result; if (settings.hasProperty(rt, "duration")) { result.duration = getDuration(rt, settings); } + if (settings.hasProperty(rt, "timingFunction")) { + result.easingFunction = getTimingFunction(rt, settings); + } + if (settings.hasProperty(rt, "delay")) { result.delay = getDelay(rt, settings); } if (settings.hasProperty(rt, "allowDiscrete")) { - const auto allowDiscreteValue = settings.getProperty(rt, "allowDiscrete"); - if (allowDiscreteValue.isBool()) { - result.allowDiscrete = allowDiscreteValue.getBool(); - } - } - - if (settings.hasProperty(rt, "timingFunction")) { - result.easingFunction = getTimingFunction(rt, settings); + result.allowDiscrete = settings.getProperty(rt, "allowDiscrete").getBool(); } return result; } -static CSSTransitionPropertySettingsUpdates -parseSettingsUpdatesObject(jsi::Runtime &rt, const jsi::Object &settings) { + +CSSTransitionPropertySettingsUpdates parseSettingsUpdates(jsi::Runtime &rt, const jsi::Object &settings) { CSSTransitionPropertySettingsUpdates result; const auto propertyNames = settings.getPropertyNames(rt); - const auto count = propertyNames.size(rt); + const auto propertyCount = propertyNames.size(rt); - for (size_t i = 0; i < count; ++i) { + for (size_t i = 0; i < propertyCount; ++i) { const auto propertyName = propertyNames.getValueAtIndex(rt, i).asString(rt).utf8(rt); const auto propertyValue = settings.getProperty(rt, jsi::PropNameID::forUtf8(rt, propertyName)); @@ -127,52 +70,70 @@ parseSettingsUpdatesObject(jsi::Runtime &rt, const jsi::Object &settings) { } const auto propertySettings = propertyValue.asObject(rt); - result.emplace(propertyName, parsePartialPropertySettings(rt, propertySettings)); + auto parsedSettings = parsePartialPropertySettings(rt, propertySettings); + result.emplace(propertyName, std::move(parsedSettings)); } return result; } -static CSSTransitionPropertyDiffs -parsePropertyDiffs(jsi::Runtime &rt, const jsi::Object &diffs) { - CSSTransitionPropertyDiffs result; - const auto propertyNames = diffs.getPropertyNames(rt); - const auto count = propertyNames.size(rt); - for (size_t i = 0; i < count; ++i) { +CSSTransitionPropertiesSettings parseSettings(jsi::Runtime &rt, const jsi::Object &settings) { + CSSTransitionPropertiesSettings result; + const auto propertyNames = settings.getPropertyNames(rt); + const auto propertyCount = propertyNames.size(rt); + + for (size_t i = 0; i < propertyCount; ++i) { const auto propertyName = propertyNames.getValueAtIndex(rt, i).asString(rt).utf8(rt); - const auto diffValue = diffs.getProperty(rt, jsi::PropNameID::forUtf8(rt, propertyName)); + const auto propertySettings = settings.getProperty(rt, jsi::PropNameID::forUtf8(rt, propertyName)).asObject(rt); - if (!diffValue.isObject()) { - continue; - } + result.emplace( + propertyName, + CSSTransitionPropertySettings{ + getDuration(rt, propertySettings), + getTimingFunction(rt, propertySettings), + getDelay(rt, propertySettings), + propertySettings.getProperty(rt, "allowDiscrete").asBool()}); + } - const auto diffArray = diffValue.asObject(rt).asArray(rt); - if (diffArray.size(rt) != 2) { - continue; - } + return result; +} - result.emplace(propertyName, std::make_pair(diffArray.getValueAtIndex(rt, 0), diffArray.getValueAtIndex(rt, 1))); +std::optional getTransitionPropertySettings( + const CSSTransitionPropertiesSettings &propertiesSettings, + const std::string &propName) { + const auto &propIt = propertiesSettings.find(propName); + if (propIt != propertiesSettings.end()) { + return propIt->second; } + const auto &allIt = propertiesSettings.find("all"); + if (allIt != propertiesSettings.end()) { + return allIt->second; + } + + return std::nullopt; +} + +CSSTransitionConfig parseCSSTransitionConfig(jsi::Runtime &rt, const jsi::Value &config) { + const auto configObj = config.asObject(rt); + + CSSTransitionConfig result{ + .properties = parsePropertyDiffs(rt, configObj.getProperty(rt, "properties").asObject(rt)), + .settings = parseSettings(rt, configObj.getProperty(rt, "settings").asObject(rt)), + }; + return result; } CSSTransitionUpdates parseCSSTransitionUpdates(jsi::Runtime &rt, const jsi::Value &updates) { const auto updatesObj = updates.asObject(rt); - - CSSTransitionUpdates result; - - if (updatesObj.hasProperty(rt, "properties")) { - const auto propertiesValue = updatesObj.getProperty(rt, "properties"); - auto propertyDiffs = parsePropertyDiffs(rt, propertiesValue.asObject(rt)); - if (!propertyDiffs.empty()) { - result.properties = std::move(propertyDiffs); - } - } + CSSTransitionUpdates result{ + .properties = parsePropertyDiffs(rt, updatesObj.getProperty(rt, "properties").asObject(rt)), + }; if (updatesObj.hasProperty(rt, "settings")) { const auto settingsValue = updatesObj.getProperty(rt, "settings"); - auto settingsUpdates = parseSettingsUpdatesObject(rt, settingsValue.asObject(rt)); + auto settingsUpdates = parseSettingsUpdates(rt, settingsValue.asObject(rt)); if (!settingsUpdates.empty()) { result.settings = std::move(settingsUpdates); } diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/configs/CSSTransitionConfig.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/configs/CSSTransitionConfig.h index 200b9f5e8dcc..fa851f014aaf 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/configs/CSSTransitionConfig.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/configs/CSSTransitionConfig.h @@ -19,16 +19,14 @@ struct CSSTransitionPropertySettings { using CSSTransitionPropertiesSettings = std::unordered_map; +using CSSTransitionPropertyDiffs = + std::unordered_map>; + struct CSSTransitionConfig { - TransitionProperties properties; + CSSTransitionPropertyDiffs properties; CSSTransitionPropertiesSettings settings; }; -struct PartialCSSTransitionConfig { - std::optional properties; - std::optional settings; -}; - struct PartialCSSTransitionPropertySettings { std::optional duration; std::optional easingFunction; @@ -39,11 +37,8 @@ struct PartialCSSTransitionPropertySettings { using CSSTransitionPropertySettingsUpdates = std::unordered_map; -using CSSTransitionPropertyDiffs = - std::unordered_map>; - struct CSSTransitionUpdates { - std::optional properties; + CSSTransitionPropertyDiffs properties; std::optional settings; }; @@ -51,14 +46,7 @@ std::optional getTransitionPropertySettings( const CSSTransitionPropertiesSettings &propertiesSettings, const std::string &propName); -TransitionProperties getProperties(jsi::Runtime &rt, const jsi::Object &config); - -CSSTransitionPropertiesSettings parseCSSTransitionPropertiesSettings(jsi::Runtime &rt, const jsi::Object &settings); - CSSTransitionConfig parseCSSTransitionConfig(jsi::Runtime &rt, const jsi::Value &config); - -PartialCSSTransitionConfig parsePartialCSSTransitionConfig(jsi::Runtime &rt, const jsi::Value &partialConfig); - CSSTransitionUpdates parseCSSTransitionUpdates(jsi::Runtime &rt, const jsi::Value &updates); } // namespace reanimated::css diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/core/CSSTransition.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/core/CSSTransition.cpp index 315b3b652c20..98361895b4d4 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/core/CSSTransition.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/core/CSSTransition.cpp @@ -78,18 +78,49 @@ PropertyNames CSSTransition::getAllowedProperties(const folly::dynamic &oldProps return {allAllowedProps.begin(), allAllowedProps.end()}; } -void CSSTransition::updateSettings(const PartialCSSTransitionConfig &config) { - if (config.properties.has_value()) { - updateTransitionProperties(config.properties.value()); - } - if (config.settings.has_value()) { - settings_ = config.settings.value(); +folly::dynamic CSSTransition::run( + jsi::Runtime &rt, + const ChangedProps &changedProps, + const CSSTransitionUpdates &updates, + const folly::dynamic &lastUpdateValue, + const double timestamp) { + if (updates.settings.has_value()) { + for (const auto &[property, partialSettings] : *updates.settings) { + auto &existingSettings = settings_[property]; + + if (partialSettings.duration.has_value()) { + existingSettings.duration = partialSettings.duration.value(); + } + if (partialSettings.easingFunction.has_value()) { + existingSettings.easingFunction = partialSettings.easingFunction.value(); + } + if (partialSettings.delay.has_value()) { + existingSettings.delay = partialSettings.delay.value(); + } + if (partialSettings.allowDiscrete.has_value()) { + existingSettings.allowDiscrete = partialSettings.allowDiscrete.value(); + } + } + updateAllowedDiscreteProperties(); } -} -folly::dynamic -CSSTransition::run(const ChangedProps &changedProps, const folly::dynamic &lastUpdateValue, const double timestamp) { + if (!updates.properties.empty()) { + ChangedProps transitionChangedProps; + transitionChangedProps.changedPropertyNames.reserve(updates.properties.size()); + transitionChangedProps.oldProps = folly::dynamic::object; + transitionChangedProps.newProps = folly::dynamic::object; + + for (const auto &[property, diffPair] : updates.properties) { + transitionChangedProps.changedPropertyNames.push_back(property); + transitionChangedProps.oldProps[property] = jsiValueToDynamic(rt, diffPair.first); + transitionChangedProps.newProps[property] = jsiValueToDynamic(rt, diffPair.second); + } + + updateTransitionProperties(properties_); + styleInterpolator_.updateInterpolatedProperties(transitionChangedProps, {}); + } + progressProvider_.runProgressProviders( timestamp, settings_, @@ -134,6 +165,22 @@ void CSSTransition::updateAllowedDiscreteProperties() { } } +void CSSTransition::applyPropertyDiffs(jsi::Runtime &rt, const CSSTransitionPropertyDiffs &diffs) { + ChangedProps changedProps; + changedProps.changedPropertyNames.reserve(diffs.size()); + changedProps.oldProps = folly::dynamic::object; + changedProps.newProps = folly::dynamic::object; + + for (const auto &[property, diffPair] : diffs) { + changedProps.changedPropertyNames.push_back(property); + changedProps.oldProps[property] = jsiValueToDynamic(rt, diffPair.first); + changedProps.newProps[property] = jsiValueToDynamic(rt, diffPair.second); + } + + const folly::dynamic lastUpdateValue; // empty since updates come from JS + styleInterpolator_.updateInterpolatedProperties(changedProps, lastUpdateValue); +} + bool CSSTransition::isAllowedProperty(const std::string &propertyName) const { if (!isDiscreteProperty(propertyName, shadowNode_->getComponentName())) { return true; diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/core/CSSTransition.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/core/CSSTransition.h index 1ba76f6a648a..e4b07c147c3e 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/core/CSSTransition.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/core/CSSTransition.h @@ -25,9 +25,12 @@ class CSSTransition { folly::dynamic getCurrentInterpolationStyle() const; TransitionProperties getProperties() const; PropertyNames getAllowedProperties(const folly::dynamic &oldProps, const folly::dynamic &newProps); - - void updateSettings(const PartialCSSTransitionConfig &config); - folly::dynamic run(const ChangedProps &changedProps, const folly::dynamic &lastUpdateValue, double timestamp); + folly::dynamic run( + jsi::Runtime &rt, + const ChangedProps &changedProps, + const CSSTransitionUpdates &updates, + const folly::dynamic &lastUpdateValue, + double timestamp); folly::dynamic update(double timestamp); private: diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/PropertyInterpolator.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/PropertyInterpolator.h index 04647f4c52f0..0fe2e2863436 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/PropertyInterpolator.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/PropertyInterpolator.h @@ -27,9 +27,9 @@ class PropertyInterpolator { virtual void updateKeyframes(jsi::Runtime &rt, const jsi::Value &keyframes) = 0; virtual void updateKeyframesFromStyleChange( - const folly::dynamic &oldStyleValue, - const folly::dynamic &newStyleValue, - const folly::dynamic &lastUpdateValue) = 0; + jsi::Runtime &rt, + const jsi::Value &oldStyleValue, + const jsi::Value &newStyleValue) = 0; virtual folly::dynamic interpolate( const std::shared_ptr &shadowNode, diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/ArrayPropertiesInterpolator.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/ArrayPropertiesInterpolator.cpp index 4e75120ee27d..661b29e5e343 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/ArrayPropertiesInterpolator.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/ArrayPropertiesInterpolator.cpp @@ -42,30 +42,35 @@ void ArrayPropertiesInterpolator::updateKeyframes(jsi::Runtime &rt, const jsi::V } void ArrayPropertiesInterpolator::updateKeyframesFromStyleChange( - const folly::dynamic &oldStyleValue, - const folly::dynamic &newStyleValue, - const folly::dynamic &lastUpdateValue) { - const auto emptyArray = folly::dynamic::array(); - const auto null = folly::dynamic(); - - const auto &oldStyleArray = oldStyleValue.empty() ? emptyArray : oldStyleValue; - const auto &newStyleArray = newStyleValue.empty() ? emptyArray : newStyleValue; - const auto &lastUpdateArray = lastUpdateValue.empty() ? emptyArray : lastUpdateValue; - - const size_t oldSize = oldStyleArray.size(); - const size_t newSize = newStyleArray.size(); - const size_t lastSize = lastUpdateArray.size(); + jsi::Runtime &rt, + const jsi::Value &oldStyleValue, + const jsi::Value &newStyleValue) { + const auto getArray = [&rt](const jsi::Value &value) -> std::optional { + if (!value.isObject()) { + return std::nullopt; + } + + const auto object = value.asObject(rt); + if (!object.isArray(rt)) { + return std::nullopt; + } + + return object.asArray(rt); + }; + + const auto oldArray = getArray(oldStyleValue); + const auto newArray = getArray(newStyleValue); + + const size_t oldSize = oldArray.has_value() ? oldArray->size(rt) : 0; + const size_t newSize = newArray.has_value() ? newArray->size(rt) : 0; const size_t valuesCount = std::max(oldSize, newSize); resizeInterpolators(valuesCount); for (size_t i = 0; i < valuesCount; ++i) { - // These index checks ensure that interpolation works between 2 arrays - // with different lengths - interpolators_[i]->updateKeyframesFromStyleChange( - i < oldSize ? oldStyleArray[i] : null, - i < newSize ? newStyleArray[i] : null, - i < lastSize ? lastUpdateArray[i] : null); + const auto oldValue = (oldArray.has_value() && i < oldSize) ? oldArray->getValueAtIndex(rt, i) : jsi::Value::undefined(); + const auto newValue = (newArray.has_value() && i < newSize) ? newArray->getValueAtIndex(rt, i) : jsi::Value::undefined(); + interpolators_[i]->updateKeyframesFromStyleChange(rt, oldValue, newValue); } } diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/ArrayPropertiesInterpolator.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/ArrayPropertiesInterpolator.h index 0284399e1e9c..82fa1c9b37e2 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/ArrayPropertiesInterpolator.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/ArrayPropertiesInterpolator.h @@ -19,9 +19,9 @@ class ArrayPropertiesInterpolator : public GroupPropertiesInterpolator { void updateKeyframes(jsi::Runtime &rt, const jsi::Value &keyframes) override; void updateKeyframesFromStyleChange( - const folly::dynamic &oldStyleValue, - const folly::dynamic &newStyleValue, - const folly::dynamic &lastUpdateValue) override; + jsi::Runtime &rt, + const jsi::Value &oldStyleValue, + const jsi::Value &newStyleValue) override; protected: folly::dynamic mapInterpolators(const std::function &callback) const override; diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/RecordPropertiesInterpolator.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/RecordPropertiesInterpolator.cpp index 10a519054a32..16ae0444f0f3 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/RecordPropertiesInterpolator.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/RecordPropertiesInterpolator.cpp @@ -38,32 +38,52 @@ void RecordPropertiesInterpolator::updateKeyframes(jsi::Runtime &rt, const jsi:: } void RecordPropertiesInterpolator::updateKeyframesFromStyleChange( - const folly::dynamic &oldStyleValue, - const folly::dynamic &newStyleValue, - const folly::dynamic &lastUpdateValue) { - // TODO - maybe add a possibility to remove interpolators that are no longer - // used (for now, for simplicity, we only add new ones) - const folly::dynamic emptyObject = folly::dynamic::object(); - const auto null = folly::dynamic(); + jsi::Runtime &rt, + const jsi::Value &oldStyleValue, + const jsi::Value &newStyleValue, + const jsi::Value &lastUpdateValue) { + const auto getObject = [&rt](const jsi::Value &value) -> std::optional { + if (!value.isObject()) { + return std::nullopt; + } + + const auto object = value.asObject(rt); + if (!object.isObject()) { + return std::nullopt; + } - const auto &oldStyleObject = oldStyleValue.empty() ? emptyObject : oldStyleValue; - const auto &newStyleObject = newStyleValue.empty() ? emptyObject : newStyleValue; - const auto &lastUpdateObject = lastUpdateValue.empty() ? emptyObject : lastUpdateValue; + return object; + }; + + const auto oldObject = getObject(oldStyleValue); + const auto newObject = getObject(newStyleValue); + const auto lastObject = getObject(lastUpdateValue); std::unordered_set propertyNamesSet; - for (const auto &key : oldStyleObject.keys()) { - propertyNamesSet.insert(key.asString()); + if (oldObject.has_value()) { + jsi::Array names = oldObject->getPropertyNames(rt); + const size_t count = names.size(rt); + for (size_t i = 0; i < count; ++i) { + propertyNamesSet.insert(names.getValueAtIndex(rt, i).asString(rt).utf8(rt)); + } } - for (const auto &key : newStyleObject.keys()) { - propertyNamesSet.insert(key.asString()); + if (newObject.has_value()) { + jsi::Array names = newObject->getPropertyNames(rt); + const size_t count = names.size(rt); + for (size_t i = 0; i < count; ++i) { + propertyNamesSet.insert(names.getValueAtIndex(rt, i).asString(rt).utf8(rt)); + } } for (const auto &propertyName : propertyNamesSet) { maybeCreateInterpolator(propertyName); - interpolators_[propertyName]->updateKeyframesFromStyleChange( - oldStyleObject.getDefault(propertyName, null), - newStyleObject.getDefault(propertyName, null), - lastUpdateObject.getDefault(propertyName, null)); + + const auto propId = jsi::PropNameID::forUtf8(rt, propertyName); + const auto oldValue = oldObject.has_value() ? oldObject->getProperty(rt, propId) : jsi::Value::undefined(); + const auto newValue = newObject.has_value() ? newObject->getProperty(rt, propId) : jsi::Value::undefined(); + const auto lastValue = lastObject.has_value() ? lastObject->getProperty(rt, propId) : jsi::Value::undefined(); + + interpolators_[propertyName]->updateKeyframesFromStyleChange(rt, oldValue, newValue, lastValue); } } diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/RecordPropertiesInterpolator.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/RecordPropertiesInterpolator.h index b743e9fa1436..8154c6f9f7e6 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/RecordPropertiesInterpolator.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/RecordPropertiesInterpolator.h @@ -20,9 +20,9 @@ class RecordPropertiesInterpolator : public GroupPropertiesInterpolator { void updateKeyframes(jsi::Runtime &rt, const jsi::Value &keyframes) override; void updateKeyframesFromStyleChange( - const folly::dynamic &oldStyleValue, - const folly::dynamic &newStyleValue, - const folly::dynamic &lastUpdateValue) override; + jsi::Runtime &rt, + const jsi::Value &oldStyleValue, + const jsi::Value &newStyleValue) override; protected: folly::dynamic mapInterpolators(const std::function &callback) const override; diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/operations/OperationsStyleInterpolator.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/operations/OperationsStyleInterpolator.cpp index 012fddbc81b1..646f5886dabb 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/operations/OperationsStyleInterpolator.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/operations/OperationsStyleInterpolator.cpp @@ -127,9 +127,10 @@ void OperationsStyleInterpolator::updateKeyframes(jsi::Runtime &rt, const jsi::V } void OperationsStyleInterpolator::updateKeyframesFromStyleChange( - const folly::dynamic &oldStyleValue, - const folly::dynamic &newStyleValue, - const folly::dynamic &lastUpdateValue) { + jsi::Runtime &rt, + const jsi::Value &oldStyleValue, + const jsi::Value &newStyleValue, + const jsi::Value &lastUpdateValue) { if (oldStyleValue.isNull()) { reversingAdjustedStartValue_ = std::nullopt; } else { diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/operations/OperationsStyleInterpolator.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/operations/OperationsStyleInterpolator.h index 9c1835c4a667..6fecacb74401 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/operations/OperationsStyleInterpolator.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/operations/OperationsStyleInterpolator.h @@ -46,9 +46,9 @@ class OperationsStyleInterpolator : public PropertyInterpolator { void updateKeyframes(jsi::Runtime &rt, const jsi::Value &keyframes) override; void updateKeyframesFromStyleChange( - const folly::dynamic &oldStyleValue, - const folly::dynamic &newStyleValue, - const folly::dynamic &lastUpdateValue) override; + jsi::Runtime &rt, + const jsi::Value &oldStyleValue, + const jsi::Value &newStyleValue) override; protected: const std::shared_ptr interpolators_; diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/values/ValueInterpolator.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/values/ValueInterpolator.cpp index 2628915de724..391bb4d2a3ea 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/values/ValueInterpolator.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/values/ValueInterpolator.cpp @@ -11,7 +11,8 @@ ValueInterpolator::ValueInterpolator( const std::shared_ptr &viewStylesRepository) : PropertyInterpolator(propertyPath, viewStylesRepository), defaultStyleValue_(defaultValue), - defaultStyleValueDynamic_(defaultValue->toDynamic()) {} + defaultStyleValueDynamic_(defaultValue->toDynamic()), + lastUpdateValue_(nullptr) {} folly::dynamic ValueInterpolator::getStyleValue(const std::shared_ptr &shadowNode) const { return viewStylesRepository_->getStyleProp(shadowNode->getTag(), propertyPath_); @@ -58,27 +59,38 @@ void ValueInterpolator::updateKeyframes(jsi::Runtime &rt, const jsi::Value &keyf } void ValueInterpolator::updateKeyframesFromStyleChange( - const folly::dynamic &oldStyleValue, - const folly::dynamic &newStyleValue, - const folly::dynamic &lastUpdateValue) { + jsi::Runtime &rt, + const jsi::Value &oldStyleValue, + const jsi::Value &newStyleValue) { + const bool hasLastUpdateValue = lastUpdateValue_ != nullptr; ValueKeyframe firstKeyframe, lastKeyframe; - if (!lastUpdateValue.isNull()) { - firstKeyframe = ValueKeyframe{0, createValue(lastUpdateValue)}; - } else if (!oldStyleValue.isNull()) { - firstKeyframe = ValueKeyframe{0, createValue(oldStyleValue)}; + const bool hasOldValue = !oldStyleValue.isUndefined(); + const bool hasNewValue = !newStyleValue.isUndefined(); + + if (hasLastUpdateValue) { + firstKeyframe = ValueKeyframe{0, std::nullopt}; + } else if (hasOldValue) { + firstKeyframe = ValueKeyframe{0, createValue(rt, oldStyleValue)}; } else { firstKeyframe = ValueKeyframe{0, defaultStyleValue_}; } - if (newStyleValue.isNull()) { + if (!hasNewValue) { lastKeyframe = ValueKeyframe{1, defaultStyleValue_}; } else { - lastKeyframe = ValueKeyframe{1, createValue(newStyleValue)}; + lastKeyframe = ValueKeyframe{1, createValue(rt, newStyleValue)}; } keyframes_ = {std::move(firstKeyframe), std::move(lastKeyframe)}; - reversingAdjustedStartValue_ = oldStyleValue; + if (hasLastUpdateValue) { + reversingAdjustedStartValue_ = lastUpdateValue_->toDynamic(); + } else if (hasOldValue) { + reversingAdjustedStartValue_ = createValue(rt, oldStyleValue)->toDynamic(); + } else { + reversingAdjustedStartValue_ = folly::dynamic(); + } + lastUpdateValue_ = hasNewValue ? createValue(rt, newStyleValue) : nullptr; } folly::dynamic ValueInterpolator::interpolate( diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/values/ValueInterpolator.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/values/ValueInterpolator.h index d638191faf20..74251499a6ee 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/values/ValueInterpolator.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/values/ValueInterpolator.h @@ -36,9 +36,9 @@ class ValueInterpolator : public PropertyInterpolator { void updateKeyframes(jsi::Runtime &rt, const jsi::Value &keyframes) override; void updateKeyframesFromStyleChange( - const folly::dynamic &oldStyleValue, - const folly::dynamic &newStyleValue, - const folly::dynamic &lastUpdateValue) override; + jsi::Runtime &rt, + const jsi::Value &oldStyleValue, + const jsi::Value &newStyleValue) override; folly::dynamic interpolate( const std::shared_ptr &shadowNode, @@ -50,9 +50,9 @@ class ValueInterpolator : public PropertyInterpolator { std::shared_ptr defaultStyleValue_; folly::dynamic defaultStyleValueDynamic_; folly::dynamic reversingAdjustedStartValue_; + std::shared_ptr lastUpdateValue_; virtual std::shared_ptr createValue(jsi::Runtime &rt, const jsi::Value &value) const = 0; - virtual std::shared_ptr createValue(const folly::dynamic &value) const = 0; virtual folly::dynamic interpolateValue( double progress, const std::shared_ptr &fromValue, diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.cpp index 1b43a85950cc..0e38730cee5b 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.cpp @@ -20,15 +20,6 @@ bool CSSTransitionsRegistry::hasUpdates() const { return !runningTransitionTags_.empty() || !delayedTransitionsManager_.empty(); } -void CSSTransitionsRegistry::add(const std::shared_ptr &transition) { - const auto &shadowNode = transition->getShadowNode(); - const auto viewTag = shadowNode->getTag(); - - registry_.insert({viewTag, transition}); - PropsObserver observer = createPropsObserver(viewTag); - staticPropsRegistry_->setObserver(viewTag, std::move(observer)); -} - void CSSTransitionsRegistry::remove(const Tag viewTag) { removeFromUpdatesRegistry(viewTag); staticPropsRegistry_->removeObserver(viewTag); @@ -37,40 +28,31 @@ void CSSTransitionsRegistry::remove(const Tag viewTag) { registry_.erase(viewTag); } -void CSSTransitionsRegistry::apply(jsi::Runtime &rt, const Tag viewTag, const CSSTransitionUpdates &updates) { - auto transitionIt = registry_.find(viewTag); +void CSSTransitionsRegistry::add( + jsi::Runtime &rt, + std::shared_ptr shadowNode, + const CSSTransitionConfig &config) { + const auto viewTag = shadowNode->getTag(); + auto transition = std::make_shared(std::move(shadowNode), config, viewStylesRepository_); - if (transitionIt == registry_.end()) { - auto shadowNode = staticPropsRegistry_->getShadowNode(viewTag); - if (!shadowNode || !updates.settings.has_value()) { - return; - } + registry_.insert({viewTag, transition}); + PropsObserver observer = createPropsObserver(viewTag); + staticPropsRegistry_->setObserver(viewTag, std::move(observer)); - CSSTransitionConfig config; - if (updates.properties.has_value()) { - PropertyNames propertyNames; - propertyNames.reserve(updates.properties->size()); - for (const auto &entry : *updates.properties) { - propertyNames.push_back(entry.first); - } - config.properties = propertyNames; - } else { - config.properties = std::nullopt; - } + // Newly added transitions should immediately store their initial interpolation style + updateInUpdatesRegistry(transition, transition->getCurrentInterpolationStyle()); +} - config.settings = parseCSSTransitionPropertiesSettings(rt, *updates.settings); +void CSSTransitionsRegistry::update(jsi::Runtime &rt, const Tag viewTag, const CSSTransitionUpdates &updates) { + auto transitionIt = registry_.find(viewTag); - auto transition = std::make_shared(std::move(shadowNode), config, viewStylesRepository_); - add(transition); - transitionIt = registry_.find(viewTag); + if (transitionIt == registry_.end()) { + return; } const auto &transition = transitionIt->second; - transition->applyUpdates(rt, updates); - - if (updates.properties.has_value()) { - updateInUpdatesRegistry(transition, transition->getCurrentInterpolationStyle()); - } + transition->update(rt, updates); + updateInUpdatesRegistry(transition, transition->getCurrentInterpolationStyle()); } void CSSTransitionsRegistry::update(const double timestamp) { diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.h index 5e96f6b77352..9aee66da7314 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.h @@ -25,8 +25,8 @@ class CSSTransitionsRegistry : public UpdatesRegistry, public std::enable_shared bool isEmpty() const override; bool hasUpdates() const; - void apply(jsi::Runtime &rt, Tag viewTag, const CSSTransitionUpdates &updates); - void add(const std::shared_ptr &transition); + void add(jsi::Runtime &rt, std::shared_ptr shadowNode, const CSSTransitionConfig &config); + void update(jsi::Runtime &rt, Tag viewTag, const CSSTransitionUpdates &updates); void remove(Tag viewTag) override; void update(double timestamp); diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp index 9801ad42c8d4..94f2028fd756 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp @@ -416,17 +416,33 @@ void ReanimatedModuleProxy::unregisterCSSAnimations(const jsi::Value &viewTag) { cssAnimationsRegistry_->remove(viewTag.asNumber()); } -void ReanimatedModuleProxy::applyCSSTransition( +void ReanimatedModuleProxy::registerCSSTransition( jsi::Runtime &rt, const jsi::Value &shadowNodeWrapper, - const jsi::Value &transitionUpdates) { + const jsi::Value &transitionConfig) { auto shadowNode = shadowNodeFromValue(rt, shadowNodeWrapper); + const auto config = parseCSSTransitionConfig(rt, transitionConfig); + + { + auto lock = cssTransitionsRegistry_->lock(); + cssTransitionsRegistry_->add(rt, shadowNode, config); + } + + maybeRunCSSLoop(); +} + +void ReanimatedModuleProxy::updateCSSTransition( + jsi::Runtime &rt, + const jsi::Value &viewTag, + const jsi::Value &transitionUpdates) { + const auto tag = viewTag.asNumber(); const auto updates = parseCSSTransitionUpdates(rt, transitionUpdates); { auto lock = cssTransitionsRegistry_->lock(); - cssTransitionsRegistry_->apply(shadowNode->getTag(), updates); + cssTransitionsRegistry_->update(rt, tag, updates); } + maybeRunCSSLoop(); } diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.h b/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.h index 79775d741eb2..5ca3f4c2b845 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.h @@ -117,8 +117,14 @@ class ReanimatedModuleProxy : public ReanimatedModuleProxySpec, override; void unregisterCSSAnimations(const jsi::Value &viewTag) override; - void applyCSSTransition(jsi::Runtime &rt, const jsi::Value &shadowNodeWrapper, const jsi::Value &transitionUpdates) - override; + void registerCSSTransition( + jsi::Runtime &rt, + const jsi::Value &shadowNodeWrapper, + const jsi::Value &transitionConfig) override; + void updateCSSTransition( + jsi::Runtime &rt, + const jsi::Value &viewTag, + const jsi::Value &transitionUpdates) override; void unregisterCSSTransition(jsi::Runtime &rt, const jsi::Value &viewTag) override; void cssLoopCallback(const double /*timestampMs*/); diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxySpec.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxySpec.cpp index 4e13e2ef878a..7e89477abdfc 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxySpec.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxySpec.cpp @@ -120,9 +120,16 @@ static jsi::Value REANIMATED_SPEC_PREFIX( } static jsi::Value REANIMATED_SPEC_PREFIX( - applyCSSTransition)(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value *args, size_t) { + registerCSSTransition)(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value *args, size_t) { static_cast(&turboModule) - ->applyCSSTransition(rt, std::move(args[0]), std::move(args[1])); + ->registerCSSTransition(rt, std::move(args[0]), std::move(args[1])); + return jsi::Value::undefined(); +} + +static jsi::Value REANIMATED_SPEC_PREFIX( + updateCSSTransition)(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value *args, size_t) { + static_cast(&turboModule) + ->updateCSSTransition(rt, std::move(args[0]), std::move(args[1])); return jsi::Value::undefined(); } @@ -161,7 +168,8 @@ ReanimatedModuleProxySpec::ReanimatedModuleProxySpec(const std::shared_ptr Date: Thu, 27 Nov 2025 20:20:41 +0000 Subject: [PATCH 05/13] Cleanup no longer used methods in the registry and transition classes --- .../cpp/reanimated/CSS/core/CSSTransition.cpp | 42 ------------ .../cpp/reanimated/CSS/core/CSSTransition.h | 3 - .../CSS/registries/CSSTransitionsRegistry.cpp | 67 +------------------ .../CSS/registries/CSSTransitionsRegistry.h | 5 +- 4 files changed, 2 insertions(+), 115 deletions(-) diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/core/CSSTransition.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/core/CSSTransition.cpp index 98361895b4d4..acdf98df19e9 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/core/CSSTransition.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/core/CSSTransition.cpp @@ -36,48 +36,6 @@ TransitionProgressState CSSTransition::getState() const { return progressProvider_.getState(); } -folly::dynamic CSSTransition::getCurrentInterpolationStyle() const { - return styleInterpolator_.interpolate(shadowNode_, progressProvider_, allowDiscreteProperties_); -} - -TransitionProperties CSSTransition::getProperties() const { - return properties_; -} - -PropertyNames CSSTransition::getAllowedProperties(const folly::dynamic &oldProps, const folly::dynamic &newProps) { - if (!oldProps.isObject() || !newProps.isObject()) { - return {}; - } - - // If specific properties are set, process only those - if (properties_.has_value()) { - PropertyNames allowedProps; - const auto &properties = properties_.value(); - allowedProps.reserve(properties.size()); - - for (const auto &prop : properties) { - if (isAllowedProperty(prop)) { - allowedProps.push_back(prop); - } - } - - return allowedProps; - } - - // Process all properties from both old and new props - std::unordered_set allAllowedProps; - - for (const auto &props : {oldProps, newProps}) { - for (const auto &propertyName : props.keys()) { - if (isAllowedProperty(propertyName.asString())) { - allAllowedProps.insert(propertyName.asString()); - } - } - } - - return {allAllowedProps.begin(), allAllowedProps.end()}; -} - folly::dynamic CSSTransition::run( jsi::Runtime &rt, const ChangedProps &changedProps, diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/core/CSSTransition.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/core/CSSTransition.h index e4b07c147c3e..ae43593757d7 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/core/CSSTransition.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/core/CSSTransition.h @@ -22,9 +22,6 @@ class CSSTransition { std::shared_ptr getShadowNode() const; double getMinDelay(double timestamp) const; TransitionProgressState getState() const; - folly::dynamic getCurrentInterpolationStyle() const; - TransitionProperties getProperties() const; - PropertyNames getAllowedProperties(const folly::dynamic &oldProps, const folly::dynamic &newProps); folly::dynamic run( jsi::Runtime &rt, const ChangedProps &changedProps, diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.cpp index 0e38730cee5b..577743b22725 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.cpp @@ -8,7 +8,7 @@ namespace reanimated::css { CSSTransitionsRegistry::CSSTransitionsRegistry( const std::shared_ptr &staticPropsRegistry, const GetAnimationTimestampFunction &getCurrentTimestamp) - : getCurrentTimestamp_(getCurrentTimestamp), staticPropsRegistry_(staticPropsRegistry) {} + : getCurrentTimestamp_(getCurrentTimestamp) {} bool CSSTransitionsRegistry::isEmpty() const { // The registry is empty if has no registered animations and no updates @@ -22,7 +22,6 @@ bool CSSTransitionsRegistry::hasUpdates() const { void CSSTransitionsRegistry::remove(const Tag viewTag) { removeFromUpdatesRegistry(viewTag); - staticPropsRegistry_->removeObserver(viewTag); delayedTransitionsManager_.remove(viewTag); runningTransitionTags_.erase(viewTag); registry_.erase(viewTag); @@ -36,11 +35,6 @@ void CSSTransitionsRegistry::add( auto transition = std::make_shared(std::move(shadowNode), config, viewStylesRepository_); registry_.insert({viewTag, transition}); - PropsObserver observer = createPropsObserver(viewTag); - staticPropsRegistry_->setObserver(viewTag, std::move(observer)); - - // Newly added transitions should immediately store their initial interpolation style - updateInUpdatesRegistry(transition, transition->getCurrentInterpolationStyle()); } void CSSTransitionsRegistry::update(jsi::Runtime &rt, const Tag viewTag, const CSSTransitionUpdates &updates) { @@ -52,7 +46,6 @@ void CSSTransitionsRegistry::update(jsi::Runtime &rt, const Tag viewTag, const C const auto &transition = transitionIt->second; transition->update(rt, updates); - updateInUpdatesRegistry(transition, transition->getCurrentInterpolationStyle()); } void CSSTransitionsRegistry::update(const double timestamp) { @@ -111,64 +104,6 @@ void CSSTransitionsRegistry::scheduleOrActivateTransition(const std::shared_ptr< } } -PropsObserver CSSTransitionsRegistry::createPropsObserver(const Tag viewTag) { - return [weakThis = weak_from_this(), viewTag](const folly::dynamic &oldProps, const folly::dynamic &newProps) { - auto strongThis = weakThis.lock(); - if (!strongThis) { - return; - } - - const auto &transition = strongThis->registry_[viewTag]; - const auto allowedProperties = transition->getAllowedProperties(oldProps, newProps); - - const auto changedProps = getChangedProps(oldProps, newProps, allowedProperties); - - if (changedProps.changedPropertyNames.empty()) { - return; - } - - { - std::lock_guard lock{strongThis->mutex_}; - - const auto &shadowNode = transition->getShadowNode(); - const auto &lastUpdates = strongThis->getUpdatesFromRegistry(shadowNode->getTag()); - const auto &transitionStartStyle = transition->run(changedProps, lastUpdates, strongThis->getCurrentTimestamp_()); - strongThis->updateInUpdatesRegistry(transition, transitionStartStyle); - strongThis->scheduleOrActivateTransition(transition); - } - }; -} - -void CSSTransitionsRegistry::updateInUpdatesRegistry( - const std::shared_ptr &transition, - const folly::dynamic &updates) { - const auto &shadowNode = transition->getShadowNode(); - const auto &lastUpdates = getUpdatesFromRegistry(shadowNode->getTag()); - const auto &transitionProperties = transition->getProperties(); - - folly::dynamic filteredUpdates = folly::dynamic::object; - - if (!transitionProperties.has_value()) { - // If transitionProperty is set to 'all' (optional has no value), we have - // to keep the result of the previous transition updated with the new - // transition starting values - if (!lastUpdates.empty()) { - filteredUpdates = lastUpdates; - } - } else if (!lastUpdates.empty()) { - // Otherwise, we keep only allowed properties from the last updates - // and update the object with the new transition starting values - for (const auto &prop : transitionProperties.value()) { - if (lastUpdates.count(prop)) { - filteredUpdates[prop] = lastUpdates[prop]; - } - } - } - - // updated object contains only allowed properties so we don't need - // to do additional filtering here - filteredUpdates.update(updates); - setInUpdatesRegistry(shadowNode, filteredUpdates); } } // namespace reanimated::css diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.h index 9aee66da7314..d178b5a828b7 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.h @@ -16,7 +16,7 @@ namespace reanimated::css { -class CSSTransitionsRegistry : public UpdatesRegistry, public std::enable_shared_from_this { +class CSSTransitionsRegistry : public UpdatesRegistry { public: CSSTransitionsRegistry( const std::shared_ptr &staticPropsRegistry, @@ -35,7 +35,6 @@ class CSSTransitionsRegistry : public UpdatesRegistry, public std::enable_shared using Registry = std::unordered_map>; const GetAnimationTimestampFunction &getCurrentTimestamp_; - const std::shared_ptr staticPropsRegistry_; Registry registry_; @@ -44,8 +43,6 @@ class CSSTransitionsRegistry : public UpdatesRegistry, public std::enable_shared void activateDelayedTransitions(double timestamp); void scheduleOrActivateTransition(const std::shared_ptr &transition); - PropsObserver createPropsObserver(Tag viewTag); - void updateInUpdatesRegistry(const std::shared_ptr &transition, const folly::dynamic &updates); }; } // namespace reanimated::css From b5f6f00eaa658b82148b783e5c1ac6200255a3b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20=C5=81opaci=C5=84ski?= Date: Thu, 27 Nov 2025 21:27:12 +0000 Subject: [PATCH 06/13] Add null type for removed props, clean up no longer needed methods --- .../CSS/configs/CSSTransitionConfig.cpp | 16 ++-- .../CSS/configs/CSSTransitionConfig.h | 8 +- .../cpp/reanimated/CSS/core/CSSTransition.cpp | 85 +------------------ .../cpp/reanimated/CSS/core/CSSTransition.h | 9 -- .../groups/RecordPropertiesInterpolator.cpp | 8 +- .../OperationsStyleInterpolator.cpp | 31 ++----- .../operations/OperationsStyleInterpolator.h | 3 +- .../styles/TransitionStyleInterpolator.cpp | 27 ++---- .../styles/TransitionStyleInterpolator.h | 4 +- .../CSS/registries/CSSTransitionsRegistry.cpp | 2 +- .../ReanimatedModule/reanimatedModuleProxy.ts | 4 +- .../src/css/native/proxy.ts | 4 +- .../src/css/native/types/transition.ts | 12 ++- 13 files changed, 49 insertions(+), 164 deletions(-) diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/configs/CSSTransitionConfig.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/configs/CSSTransitionConfig.cpp index 002082e2155c..9e78cd291526 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/configs/CSSTransitionConfig.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/configs/CSSTransitionConfig.cpp @@ -6,8 +6,8 @@ namespace reanimated::css { -CSSTransitionPropertyDiffs parsePropertyDiffs(jsi::Runtime &rt, const jsi::Object &diffs) { - CSSTransitionPropertyDiffs result; +CSSTransitionPropertyUpdates parsePropertyUpdates(jsi::Runtime &rt, const jsi::Object &diffs) { + CSSTransitionPropertyUpdates result; const auto propertyNames = diffs.getPropertyNames(rt); const auto propertyCount = propertyNames.size(rt); @@ -15,6 +15,11 @@ CSSTransitionPropertyDiffs parsePropertyDiffs(jsi::Runtime &rt, const jsi::Objec const auto propertyName = propertyNames.getValueAtIndex(rt, i).asString(rt).utf8(rt); const auto diffValue = diffs.getProperty(rt, jsi::PropNameID::forUtf8(rt, propertyName)); + if (diffValue.isNull()) { + result.emplace(propertyName, std::nullopt); + continue; + } + if (!diffValue.isObject()) { continue; } @@ -26,7 +31,8 @@ CSSTransitionPropertyDiffs parsePropertyDiffs(jsi::Runtime &rt, const jsi::Objec result.emplace( propertyName, - std::make_pair(diffArray.getValueAtIndex(rt, 0), diffArray.getValueAtIndex(rt, 1))); + std::make_optional(std::make_pair( + diffArray.getValueAtIndex(rt, 0), diffArray.getValueAtIndex(rt, 1)))); } return result; @@ -118,7 +124,7 @@ CSSTransitionConfig parseCSSTransitionConfig(jsi::Runtime &rt, const jsi::Value const auto configObj = config.asObject(rt); CSSTransitionConfig result{ - .properties = parsePropertyDiffs(rt, configObj.getProperty(rt, "properties").asObject(rt)), + .properties = parsePropertyUpdates(rt, configObj.getProperty(rt, "properties").asObject(rt)), .settings = parseSettings(rt, configObj.getProperty(rt, "settings").asObject(rt)), }; @@ -128,7 +134,7 @@ CSSTransitionConfig parseCSSTransitionConfig(jsi::Runtime &rt, const jsi::Value CSSTransitionUpdates parseCSSTransitionUpdates(jsi::Runtime &rt, const jsi::Value &updates) { const auto updatesObj = updates.asObject(rt); CSSTransitionUpdates result{ - .properties = parsePropertyDiffs(rt, updatesObj.getProperty(rt, "properties").asObject(rt)), + .properties = parsePropertyUpdates(rt, updatesObj.getProperty(rt, "properties").asObject(rt)), }; if (updatesObj.hasProperty(rt, "settings")) { diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/configs/CSSTransitionConfig.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/configs/CSSTransitionConfig.h index fa851f014aaf..15af36e580ac 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/configs/CSSTransitionConfig.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/configs/CSSTransitionConfig.h @@ -19,11 +19,11 @@ struct CSSTransitionPropertySettings { using CSSTransitionPropertiesSettings = std::unordered_map; -using CSSTransitionPropertyDiffs = - std::unordered_map>; +using CSSTransitionPropertyUpdates = + std::unordered_map>>; struct CSSTransitionConfig { - CSSTransitionPropertyDiffs properties; + CSSTransitionPropertyUpdates properties; CSSTransitionPropertiesSettings settings; }; @@ -38,7 +38,7 @@ using CSSTransitionPropertySettingsUpdates = std::unordered_map; struct CSSTransitionUpdates { - CSSTransitionPropertyDiffs properties; + CSSTransitionPropertyUpdates properties; std::optional settings; }; diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/core/CSSTransition.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/core/CSSTransition.cpp index acdf98df19e9..c9cddcce651a 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/core/CSSTransition.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/core/CSSTransition.cpp @@ -13,15 +13,9 @@ CSSTransition::CSSTransition( const std::shared_ptr &viewStylesRepository) : shadowNode_(std::move(shadowNode)), viewStylesRepository_(viewStylesRepository), - properties_(config.properties), settings_(config.settings), styleInterpolator_(TransitionStyleInterpolator(shadowNode_->getComponentName(), viewStylesRepository)), progressProvider_(TransitionProgressProvider()) { - updateAllowedDiscreteProperties(); -} - -Tag CSSTransition::getViewTag() const { - return shadowNode_->getTag(); } std::shared_ptr CSSTransition::getShadowNode() const { @@ -36,12 +30,7 @@ TransitionProgressState CSSTransition::getState() const { return progressProvider_.getState(); } -folly::dynamic CSSTransition::run( - jsi::Runtime &rt, - const ChangedProps &changedProps, - const CSSTransitionUpdates &updates, - const folly::dynamic &lastUpdateValue, - const double timestamp) { +folly::dynamic CSSTransition::run(jsi::Runtime &rt, const CSSTransitionUpdates &updates, const double timestamp) { if (updates.settings.has_value()) { for (const auto &[property, partialSettings] : *updates.settings) { auto &existingSettings = settings_[property]; @@ -59,33 +48,14 @@ folly::dynamic CSSTransition::run( existingSettings.allowDiscrete = partialSettings.allowDiscrete.value(); } } - - updateAllowedDiscreteProperties(); } if (!updates.properties.empty()) { - ChangedProps transitionChangedProps; - transitionChangedProps.changedPropertyNames.reserve(updates.properties.size()); - transitionChangedProps.oldProps = folly::dynamic::object; - transitionChangedProps.newProps = folly::dynamic::object; - - for (const auto &[property, diffPair] : updates.properties) { - transitionChangedProps.changedPropertyNames.push_back(property); - transitionChangedProps.oldProps[property] = jsiValueToDynamic(rt, diffPair.first); - transitionChangedProps.newProps[property] = jsiValueToDynamic(rt, diffPair.second); - } - - updateTransitionProperties(properties_); - styleInterpolator_.updateInterpolatedProperties(transitionChangedProps, {}); + styleInterpolator_.updateInterpolatedProperties(rt, updates.properties); } - progressProvider_.runProgressProviders( - timestamp, - settings_, - changedProps.changedPropertyNames, - styleInterpolator_.getReversedPropertyNames(changedProps.newProps)); - styleInterpolator_.updateInterpolatedProperties(changedProps, lastUpdateValue); - return update(timestamp); + progressProvider_.runProgressProviders(timestamp, settings_, updates.properties); + return update(rt, timestamp); } folly::dynamic CSSTransition::update(const double timestamp) { @@ -100,51 +70,4 @@ folly::dynamic CSSTransition::update(const double timestamp) { return result; } -void CSSTransition::updateTransitionProperties(const TransitionProperties &properties) { - properties_ = properties; - - const auto isAllPropertiesTransition = !properties_.has_value(); - if (isAllPropertiesTransition) { - return; - } - - const std::unordered_set transitionPropertyNames(properties_->begin(), properties_->end()); - - styleInterpolator_.discardIrrelevantInterpolators(transitionPropertyNames); - progressProvider_.discardIrrelevantProgressProviders(transitionPropertyNames); -} - -void CSSTransition::updateAllowedDiscreteProperties() { - allowDiscreteProperties_.clear(); - for (const auto &[propertyName, propertySettings] : settings_) { - if (propertySettings.allowDiscrete) { - allowDiscreteProperties_.insert(propertyName); - } - } -} - -void CSSTransition::applyPropertyDiffs(jsi::Runtime &rt, const CSSTransitionPropertyDiffs &diffs) { - ChangedProps changedProps; - changedProps.changedPropertyNames.reserve(diffs.size()); - changedProps.oldProps = folly::dynamic::object; - changedProps.newProps = folly::dynamic::object; - - for (const auto &[property, diffPair] : diffs) { - changedProps.changedPropertyNames.push_back(property); - changedProps.oldProps[property] = jsiValueToDynamic(rt, diffPair.first); - changedProps.newProps[property] = jsiValueToDynamic(rt, diffPair.second); - } - - const folly::dynamic lastUpdateValue; // empty since updates come from JS - styleInterpolator_.updateInterpolatedProperties(changedProps, lastUpdateValue); -} - -bool CSSTransition::isAllowedProperty(const std::string &propertyName) const { - if (!isDiscreteProperty(propertyName, shadowNode_->getComponentName())) { - return true; - } - - return allowDiscreteProperties_.contains(propertyName) || allowDiscreteProperties_.contains("all"); -} - } // namespace reanimated::css diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/core/CSSTransition.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/core/CSSTransition.h index ae43593757d7..ff2ce6b9f498 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/core/CSSTransition.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/core/CSSTransition.h @@ -18,30 +18,21 @@ class CSSTransition { const CSSTransitionConfig &config, const std::shared_ptr &viewStylesRepository); - Tag getViewTag() const; std::shared_ptr getShadowNode() const; double getMinDelay(double timestamp) const; TransitionProgressState getState() const; folly::dynamic run( jsi::Runtime &rt, - const ChangedProps &changedProps, const CSSTransitionUpdates &updates, - const folly::dynamic &lastUpdateValue, double timestamp); folly::dynamic update(double timestamp); private: const std::shared_ptr shadowNode_; const std::shared_ptr viewStylesRepository_; - std::unordered_set allowDiscreteProperties_; - TransitionProperties properties_; CSSTransitionPropertiesSettings settings_; TransitionStyleInterpolator styleInterpolator_; TransitionProgressProvider progressProvider_; - - void updateTransitionProperties(const TransitionProperties &properties); - void updateAllowedDiscreteProperties(); - bool isAllowedProperty(const std::string &propertyName) const; }; } // namespace reanimated::css diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/RecordPropertiesInterpolator.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/RecordPropertiesInterpolator.cpp index 16ae0444f0f3..2b440013dbbb 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/RecordPropertiesInterpolator.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/RecordPropertiesInterpolator.cpp @@ -40,8 +40,7 @@ void RecordPropertiesInterpolator::updateKeyframes(jsi::Runtime &rt, const jsi:: void RecordPropertiesInterpolator::updateKeyframesFromStyleChange( jsi::Runtime &rt, const jsi::Value &oldStyleValue, - const jsi::Value &newStyleValue, - const jsi::Value &lastUpdateValue) { + const jsi::Value &newStyleValue) { const auto getObject = [&rt](const jsi::Value &value) -> std::optional { if (!value.isObject()) { return std::nullopt; @@ -57,7 +56,6 @@ void RecordPropertiesInterpolator::updateKeyframesFromStyleChange( const auto oldObject = getObject(oldStyleValue); const auto newObject = getObject(newStyleValue); - const auto lastObject = getObject(lastUpdateValue); std::unordered_set propertyNamesSet; if (oldObject.has_value()) { @@ -81,9 +79,7 @@ void RecordPropertiesInterpolator::updateKeyframesFromStyleChange( const auto propId = jsi::PropNameID::forUtf8(rt, propertyName); const auto oldValue = oldObject.has_value() ? oldObject->getProperty(rt, propId) : jsi::Value::undefined(); const auto newValue = newObject.has_value() ? newObject->getProperty(rt, propId) : jsi::Value::undefined(); - const auto lastValue = lastObject.has_value() ? lastObject->getProperty(rt, propId) : jsi::Value::undefined(); - - interpolators_[propertyName]->updateKeyframesFromStyleChange(rt, oldValue, newValue, lastValue); + interpolators_[propertyName]->updateKeyframesFromStyleChange(rt, oldValue, newValue); } } diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/operations/OperationsStyleInterpolator.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/operations/OperationsStyleInterpolator.cpp index 646f5886dabb..2631bfbf3854 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/operations/OperationsStyleInterpolator.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/operations/OperationsStyleInterpolator.cpp @@ -129,23 +129,20 @@ void OperationsStyleInterpolator::updateKeyframes(jsi::Runtime &rt, const jsi::V void OperationsStyleInterpolator::updateKeyframesFromStyleChange( jsi::Runtime &rt, const jsi::Value &oldStyleValue, - const jsi::Value &newStyleValue, - const jsi::Value &lastUpdateValue) { - if (oldStyleValue.isNull()) { + const jsi::Value &newStyleValue) { + if (oldStyleValue.isUndefined()) { reversingAdjustedStartValue_ = std::nullopt; } else { - reversingAdjustedStartValue_ = parseStyleOperations(oldStyleValue); + reversingAdjustedStartValue_ = parseStyleOperations(rt, oldStyleValue); } - const auto &prevStyleValue = lastUpdateValue.isNull() ? oldStyleValue : lastUpdateValue; - keyframes_.clear(); keyframes_.reserve(1); keyframes_.emplace_back(createStyleOperationsKeyframe( 0, 1, - parseStyleOperations(prevStyleValue).value_or(StyleOperations{}), - parseStyleOperations(newStyleValue).value_or(StyleOperations{}))); + parseStyleOperations(rt, oldStyleValue).value_or(StyleOperations{}), + parseStyleOperations(rt, newStyleValue).value_or(StyleOperations{}))); } std::optional OperationsStyleInterpolator::parseStyleOperations( @@ -168,24 +165,6 @@ std::optional OperationsStyleInterpolator::parseStyleOperations return styleOperations; } -std::optional OperationsStyleInterpolator::parseStyleOperations(const folly::dynamic &values) const { - if (values.empty()) { - return std::nullopt; - } - - const auto &operationsArray = values; - const auto operationsCount = operationsArray.size(); - - StyleOperations styleOperations; - styleOperations.reserve(operationsCount); - - for (size_t i = 0; i < operationsCount; ++i) { - styleOperations.emplace_back(createStyleOperation(operationsArray[i])); - } - - return styleOperations; -} - std::shared_ptr OperationsStyleInterpolator::createStyleOperationsKeyframe( const double fromOffset, const double toOffset, diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/operations/OperationsStyleInterpolator.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/operations/OperationsStyleInterpolator.h index 6fecacb74401..b9f72a9a0778 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/operations/OperationsStyleInterpolator.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/operations/OperationsStyleInterpolator.h @@ -42,7 +42,7 @@ class OperationsStyleInterpolator : public PropertyInterpolator { folly::dynamic interpolate( const std::shared_ptr &shadowNode, const std::shared_ptr &progressProvider, - const double fallbackInterpolateThreshold) const override; + double fallbackInterpolateThreshold) const override; void updateKeyframes(jsi::Runtime &rt, const jsi::Value &keyframes) override; void updateKeyframesFromStyleChange( @@ -66,7 +66,6 @@ class OperationsStyleInterpolator : public PropertyInterpolator { std::optional reversingAdjustedStartValue_; std::optional parseStyleOperations(jsi::Runtime &rt, const jsi::Value &values) const; - std::optional parseStyleOperations(const folly::dynamic &values) const; std::shared_ptr createStyleOperationsKeyframe( double fromOffset, double toOffset, diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/styles/TransitionStyleInterpolator.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/styles/TransitionStyleInterpolator.cpp index 902df9500c3d..e9a2761cc436 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/styles/TransitionStyleInterpolator.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/styles/TransitionStyleInterpolator.cpp @@ -72,31 +72,22 @@ void TransitionStyleInterpolator::discardIrrelevantInterpolators( } void TransitionStyleInterpolator::updateInterpolatedProperties( - const ChangedProps &changedProps, - const folly::dynamic &lastUpdateValue) { - const auto &oldPropsObj = changedProps.oldProps; - const auto &newPropsObj = changedProps.newProps; - - const auto empty = folly::dynamic(); + jsi::Runtime &rt, + const CSSTransitionPropertyUpdates &propertyUpdates) { + for (const auto &[propertyName, diffPair] : propertyUpdates) { + if (!diffPair.has_value()) { + interpolators_.erase(propertyName); + continue; + } - for (const auto &propertyName : changedProps.changedPropertyNames) { auto it = interpolators_.find(propertyName); - const auto shouldCreateInterpolator = it == interpolators_.end(); - - if (shouldCreateInterpolator) { + if (it == interpolators_.end()) { const auto newInterpolator = createPropertyInterpolator( propertyName, {}, getComponentInterpolators(componentName_), viewStylesRepository_); it = interpolators_.emplace(propertyName, newInterpolator).first; } - const auto &oldValue = oldPropsObj.getDefault(propertyName, empty); - const auto &newValue = newPropsObj.getDefault(propertyName, empty); - // Pass lastValue only if the interpolator is updated (no new interpolator - // was created), otherwise pass an empty value - const auto &lastValue = - (shouldCreateInterpolator || lastUpdateValue.empty()) ? empty : lastUpdateValue.getDefault(propertyName, empty); - - it->second->updateKeyframesFromStyleChange(oldValue, newValue, lastValue); + it->second->updateKeyframesFromStyleChange(rt, diffPair->first, diffPair->second); } } diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/styles/TransitionStyleInterpolator.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/styles/TransitionStyleInterpolator.h index 02e121d20e3f..e009c7ef0996 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/styles/TransitionStyleInterpolator.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/styles/TransitionStyleInterpolator.h @@ -27,7 +27,9 @@ class TransitionStyleInterpolator { void discardFinishedInterpolators(const TransitionProgressProvider &transitionProgressProvider); void discardIrrelevantInterpolators(const std::unordered_set &transitionPropertyNames); - void updateInterpolatedProperties(const ChangedProps &changedProps, const folly::dynamic &lastUpdateValue); + void updateInterpolatedProperties( + jsi::Runtime &rt, + const CSSTransitionPropertyUpdates &propertyUpdates); private: using MapInterpolatorsCallback = std::function< diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.cpp index 577743b22725..7061232355fe 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.cpp @@ -89,7 +89,7 @@ void CSSTransitionsRegistry::activateDelayedTransitions(const double timestamp) } void CSSTransitionsRegistry::scheduleOrActivateTransition(const std::shared_ptr &transition) { - const auto viewTag = transition->getViewTag(); + const auto viewTag = transition->getShadowNode()->getViewTag(); const auto currentTimestamp = getCurrentTimestamp_(); const auto minDelay = transition->getMinDelay(currentTimestamp); diff --git a/packages/react-native-reanimated/src/ReanimatedModule/reanimatedModuleProxy.ts b/packages/react-native-reanimated/src/ReanimatedModule/reanimatedModuleProxy.ts index f6d6418ee086..a2bb3ec30e0c 100644 --- a/packages/react-native-reanimated/src/ReanimatedModule/reanimatedModuleProxy.ts +++ b/packages/react-native-reanimated/src/ReanimatedModule/reanimatedModuleProxy.ts @@ -14,7 +14,7 @@ import type { CSSAnimationUpdates, CSSTransitionUpdates, NormalizedCSSAnimationKeyframesConfig, - NormalizedCSSTransitionConfigWithDiff, + NormalizedNewCSSTransitionConfig, } from '../css/native'; /** Type of `__reanimatedModuleProxy` injected with JSI. */ @@ -82,7 +82,7 @@ export interface ReanimatedModuleProxy { registerCSSTransition( shadowNodeWrapper: ShadowNodeWrapper, - transitionConfig: NormalizedCSSTransitionConfigWithDiff + transitionConfig: NormalizedNewCSSTransitionConfig ): void; updateCSSTransition( diff --git a/packages/react-native-reanimated/src/css/native/proxy.ts b/packages/react-native-reanimated/src/css/native/proxy.ts index 5012cb1bf1ea..942d955a74d7 100644 --- a/packages/react-native-reanimated/src/css/native/proxy.ts +++ b/packages/react-native-reanimated/src/css/native/proxy.ts @@ -6,7 +6,7 @@ import type { CSSTransitionUpdates, NormalizedCSSAnimationKeyframesConfig, NormalizedCSSTransitionConfig, - NormalizedCSSTransitionConfigWithDiff, + NormalizedNewCSSTransitionConfig, } from './types'; // COMMON @@ -63,7 +63,7 @@ export function unregisterCSSAnimations(viewTag: number) { export function registerCSSTransition( shadowNodeWrapper: ShadowNodeWrapper, - config: NormalizedCSSTransitionConfigWithDiff + config: NormalizedNewCSSTransitionConfig ) { ReanimatedModule.registerCSSTransition(shadowNodeWrapper, config); } diff --git a/packages/react-native-reanimated/src/css/native/types/transition.ts b/packages/react-native-reanimated/src/css/native/types/transition.ts index dcc07171b0e6..acc0c48c151e 100644 --- a/packages/react-native-reanimated/src/css/native/types/transition.ts +++ b/packages/react-native-reanimated/src/css/native/types/transition.ts @@ -15,19 +15,17 @@ export type NormalizedCSSTransitionConfig = { settings: Record; }; -export type CSSTransitionPropertiesDiff = Record; +export type CSSTransitionPropertyUpdates = Record; -export type NormalizedCSSTransitionConfigWithDiff = Omit< - NormalizedCSSTransitionConfig, - 'properties' -> & { - properties: CSSTransitionPropertiesDiff; +export type NormalizedNewCSSTransitionConfig = { + properties: CSSTransitionPropertyUpdates; + settings: Record; }; export type NormalizedCSSTransitionConfigUpdates = Partial; export type CSSTransitionUpdates = { - properties?: CSSTransitionPropertiesDiff; + properties?: CSSTransitionPropertyUpdates; settings?: Record>; }; From fe7414fc034bca62314d7e70a1cf71431cbe9fdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20=C5=81opaci=C5=84ski?= Date: Fri, 28 Nov 2025 01:02:04 +0000 Subject: [PATCH 07/13] Restore registry updates logic, handle allowDiscrete --- .../cpp/reanimated/CSS/core/CSSTransition.cpp | 46 ++++++------ .../cpp/reanimated/CSS/core/CSSTransition.h | 6 +- .../styles/TransitionStyleInterpolator.cpp | 16 ++-- .../styles/TransitionStyleInterpolator.h | 3 +- .../progress/TransitionProgressProvider.cpp | 30 +++++--- .../CSS/progress/TransitionProgressProvider.h | 8 +- .../CSS/registries/CSSTransitionsRegistry.cpp | 74 +++++++++++++++---- .../CSS/registries/CSSTransitionsRegistry.h | 21 ++++-- .../Common/cpp/reanimated/CSS/utils/props.cpp | 17 ----- .../Common/cpp/reanimated/CSS/utils/props.h | 16 ---- .../NativeModules/ReanimatedModuleProxy.cpp | 2 +- .../native/managers/CSSTransitionsManager.ts | 11 +-- 12 files changed, 140 insertions(+), 110 deletions(-) delete mode 100644 packages/react-native-reanimated/Common/cpp/reanimated/CSS/utils/props.cpp delete mode 100644 packages/react-native-reanimated/Common/cpp/reanimated/CSS/utils/props.h diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/core/CSSTransition.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/core/CSSTransition.cpp index c9cddcce651a..f2032a9679dd 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/core/CSSTransition.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/core/CSSTransition.cpp @@ -2,8 +2,7 @@ #include #include -#include -#include +#include namespace reanimated::css { @@ -30,37 +29,40 @@ TransitionProgressState CSSTransition::getState() const { return progressProvider_.getState(); } -folly::dynamic CSSTransition::run(jsi::Runtime &rt, const CSSTransitionUpdates &updates, const double timestamp) { - if (updates.settings.has_value()) { - for (const auto &[property, partialSettings] : *updates.settings) { - auto &existingSettings = settings_[property]; +void CSSTransition::updateSettings(const CSSTransitionPropertySettingsUpdates &settingsUpdates) { + for (const auto &[property, partialSettings] : settingsUpdates) { + auto &existingSettings = settings_[property]; - if (partialSettings.duration.has_value()) { - existingSettings.duration = partialSettings.duration.value(); - } - if (partialSettings.easingFunction.has_value()) { - existingSettings.easingFunction = partialSettings.easingFunction.value(); - } - if (partialSettings.delay.has_value()) { - existingSettings.delay = partialSettings.delay.value(); - } - if (partialSettings.allowDiscrete.has_value()) { - existingSettings.allowDiscrete = partialSettings.allowDiscrete.value(); - } + if (partialSettings.duration.has_value()) { + existingSettings.duration = partialSettings.duration.value(); + } + if (partialSettings.easingFunction.has_value()) { + existingSettings.easingFunction = partialSettings.easingFunction.value(); + } + if (partialSettings.delay.has_value()) { + existingSettings.delay = partialSettings.delay.value(); + } + if (partialSettings.allowDiscrete.has_value()) { + existingSettings.allowDiscrete = partialSettings.allowDiscrete.value(); } } +} - if (!updates.properties.empty()) { - styleInterpolator_.updateInterpolatedProperties(rt, updates.properties); +folly::dynamic CSSTransition::run( + jsi::Runtime &rt, + const CSSTransitionPropertyUpdates &propertyUpdates, + const double timestamp) { + if (!propertyUpdates.empty()) { + styleInterpolator_.updateInterpolatedProperties(rt, propertyUpdates); } - progressProvider_.runProgressProviders(timestamp, settings_, updates.properties); + progressProvider_.runProgressProviders(timestamp, settings_, propertyUpdates); return update(rt, timestamp); } folly::dynamic CSSTransition::update(const double timestamp) { progressProvider_.update(timestamp); - auto result = styleInterpolator_.interpolate(shadowNode_, progressProvider_, allowDiscreteProperties_); + auto result = styleInterpolator_.interpolate(shadowNode_, progressProvider_); // Remove interpolators for which interpolation has finished // (we won't need them anymore in the current transition) styleInterpolator_.discardFinishedInterpolators(progressProvider_); diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/core/CSSTransition.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/core/CSSTransition.h index ff2ce6b9f498..d8ac0bf3cb10 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/core/CSSTransition.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/core/CSSTransition.h @@ -21,10 +21,8 @@ class CSSTransition { std::shared_ptr getShadowNode() const; double getMinDelay(double timestamp) const; TransitionProgressState getState() const; - folly::dynamic run( - jsi::Runtime &rt, - const CSSTransitionUpdates &updates, - double timestamp); + void updateSettings(const CSSTransitionPropertySettingsUpdates &settingsUpdates); + folly::dynamic run(jsi::Runtime &rt, const CSSTransitionPropertyUpdates &propertyUpdates, double timestamp); folly::dynamic update(double timestamp); private: diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/styles/TransitionStyleInterpolator.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/styles/TransitionStyleInterpolator.cpp index e9a2761cc436..99a82ab5cf8c 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/styles/TransitionStyleInterpolator.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/styles/TransitionStyleInterpolator.cpp @@ -35,16 +35,18 @@ std::unordered_set TransitionStyleInterpolator::getReversedProperty folly::dynamic TransitionStyleInterpolator::interpolate( const std::shared_ptr &shadowNode, - const TransitionProgressProvider &transitionProgressProvider, - const std::unordered_set &allowDiscreteProperties) const { + const TransitionProgressProvider &transitionProgressProvider) const { folly::dynamic result = folly::dynamic::object; - const auto allFallbackInterpolateThreshold = allowDiscreteProperties.contains("all") ? 0.5 : 0; - for (const auto &[propertyName, progressProvider] : transitionProgressProvider.getPropertyProgressProviders()) { - const auto &interpolator = interpolators_.at(propertyName); - const auto fallbackInterpolateThreshold = - (allowDiscreteProperties.contains(propertyName)) ? 0.5 : allFallbackInterpolateThreshold; + const auto interpolatorIt = interpolators_.find(propertyName); + if (interpolatorIt == interpolators_.end()) { + continue; + } + + const auto &interpolator = interpolatorIt->second; + const double fallbackInterpolateThreshold = + progressProvider->getFallbackInterpolateThreshold(interpolator->isDiscreteProperty()); result[propertyName] = interpolator->interpolate(shadowNode, progressProvider, fallbackInterpolateThreshold); } diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/styles/TransitionStyleInterpolator.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/styles/TransitionStyleInterpolator.h index e009c7ef0996..b2531ea2b207 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/styles/TransitionStyleInterpolator.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/styles/TransitionStyleInterpolator.h @@ -22,8 +22,7 @@ class TransitionStyleInterpolator { folly::dynamic interpolate( const std::shared_ptr &shadowNode, - const TransitionProgressProvider &transitionProgressProvider, - const std::unordered_set &allowDiscreteProperties) const; + const TransitionProgressProvider &transitionProgressProvider) const; void discardFinishedInterpolators(const TransitionProgressProvider &transitionProgressProvider); void discardIrrelevantInterpolators(const std::unordered_set &transitionPropertyNames); diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/progress/TransitionProgressProvider.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/progress/TransitionProgressProvider.cpp index b100b248549e..1e502fc77bf5 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/progress/TransitionProgressProvider.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/progress/TransitionProgressProvider.cpp @@ -8,23 +8,24 @@ namespace reanimated::css { // TransitionPropertyProgressProvider - TransitionPropertyProgressProvider::TransitionPropertyProgressProvider( const double timestamp, const double duration, const double delay, - const EasingFunction &easingFunction) - : RawProgressProvider(timestamp, duration, delay), easingFunction_(easingFunction) {} - + const EasingFunction &easingFunction, + const bool allowDiscrete) + : RawProgressProvider(timestamp, duration, delay), easingFunction_(easingFunction), allowDiscrete_(allowDiscrete) {} TransitionPropertyProgressProvider::TransitionPropertyProgressProvider( const double timestamp, const double duration, const double delay, const EasingFunction &easingFunction, - const double reversingShorteningFactor) + const double reversingShorteningFactor, + const bool allowDiscrete) : RawProgressProvider(timestamp, duration, delay), easingFunction_(easingFunction), - reversingShorteningFactor_(reversingShorteningFactor) {} + reversingShorteningFactor_(reversingShorteningFactor), + allowDiscrete_(allowDiscrete) {} double TransitionPropertyProgressProvider::getGlobalProgress() const { return rawProgress_.value_or(0); @@ -56,6 +57,10 @@ TransitionProgressState TransitionPropertyProgressProvider::getState() const { return TransitionProgressState::Running; } +double TransitionPropertyProgressProvider::getFallbackInterpolateThreshold(const bool isDiscreteProperty) const { + return !isDiscreteProperty || allowDiscrete_ ? 0.5 : 0.0; +} + std::optional TransitionPropertyProgressProvider::calculateRawProgress(const double timestamp) { if (duration_ == 0) { return 1; @@ -94,10 +99,6 @@ double TransitionProgressProvider::getMinDelay(const double timestamp) const { return minDelay; } -TransitionPropertyProgressProviders TransitionProgressProvider::getPropertyProgressProviders() const { - return propertyProgressProviders_; -} - std::unordered_set TransitionProgressProvider::getRemovedProperties() const { return removedProperties_; } @@ -158,7 +159,11 @@ void TransitionProgressProvider::runProgressProviders( propertyProgressProviders_.insert_or_assign( propertyName, std::make_shared( - timestamp, propertySettings.duration, propertySettings.delay, propertySettings.easingFunction)); + timestamp, + propertySettings.duration, + propertySettings.delay, + propertySettings.easingFunction, + propertySettings.allowDiscrete)); } } @@ -187,7 +192,8 @@ TransitionProgressProvider::createReversingShorteningProgressProvider( propertySettings.duration * newReversingShorteningFactor, propertySettings.delay < 0 ? newReversingShorteningFactor * propertySettings.delay : propertySettings.delay, propertySettings.easingFunction, - newReversingShorteningFactor); + newReversingShorteningFactor, + propertySettings.allowDiscrete); } } // namespace reanimated::css diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/progress/TransitionProgressProvider.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/progress/TransitionProgressProvider.h index 53ce6e626647..9a5966460c19 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/progress/TransitionProgressProvider.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/progress/TransitionProgressProvider.h @@ -20,19 +20,22 @@ class TransitionPropertyProgressProvider final : public KeyframeProgressProvider double timestamp, double duration, double delay, - const EasingFunction &easingFunction); + const EasingFunction &easingFunction, + bool allowDiscrete); TransitionPropertyProgressProvider( double timestamp, double duration, double delay, const EasingFunction &easingFunction, - double reversingShorteningFactor); + double reversingShorteningFactor, + bool allowDiscrete); double getGlobalProgress() const override; double getKeyframeProgress(double fromOffset, double toOffset) const override; double getRemainingDelay(double timestamp) const; double getReversingShorteningFactor() const; TransitionProgressState getState() const; + double getFallbackInterpolateThreshold(bool isDiscreteProperty) const; protected: std::optional calculateRawProgress(double timestamp) override; @@ -40,6 +43,7 @@ class TransitionPropertyProgressProvider final : public KeyframeProgressProvider private: EasingFunction easingFunction_; double reversingShorteningFactor_ = 1; + bool allowDiscrete_ = false; double getElapsedTime(double timestamp) const; }; diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.cpp index 7061232355fe..a4c69040b90e 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.cpp @@ -5,9 +5,7 @@ namespace reanimated::css { -CSSTransitionsRegistry::CSSTransitionsRegistry( - const std::shared_ptr &staticPropsRegistry, - const GetAnimationTimestampFunction &getCurrentTimestamp) +CSSTransitionsRegistry::CSSTransitionsRegistry(const GetAnimationTimestampFunction &getCurrentTimestamp) : getCurrentTimestamp_(getCurrentTimestamp) {} bool CSSTransitionsRegistry::isEmpty() const { @@ -20,32 +18,40 @@ bool CSSTransitionsRegistry::hasUpdates() const { return !runningTransitionTags_.empty() || !delayedTransitionsManager_.empty(); } -void CSSTransitionsRegistry::remove(const Tag viewTag) { - removeFromUpdatesRegistry(viewTag); - delayedTransitionsManager_.remove(viewTag); - runningTransitionTags_.erase(viewTag); - registry_.erase(viewTag); -} - void CSSTransitionsRegistry::add( jsi::Runtime &rt, std::shared_ptr shadowNode, const CSSTransitionConfig &config) { const auto viewTag = shadowNode->getTag(); - auto transition = std::make_shared(std::move(shadowNode), config, viewStylesRepository_); + auto transition = std::make_shared(std::move(shadowNode), config, viewStylesRepository_); registry_.insert({viewTag, transition}); + runTransition(rt, transition, config.properties); } -void CSSTransitionsRegistry::update(jsi::Runtime &rt, const Tag viewTag, const CSSTransitionUpdates &updates) { - auto transitionIt = registry_.find(viewTag); +void CSSTransitionsRegistry::remove(const Tag viewTag) { + removeFromUpdatesRegistry(viewTag); + delayedTransitionsManager_.remove(viewTag); + runningTransitionTags_.erase(viewTag); + registry_.erase(viewTag); +} +void CSSTransitionsRegistry::update( + jsi::Runtime &rt, + const Tag viewTag, + const CSSTransitionUpdates &updates) { + const auto transitionIt = registry_.find(viewTag); if (transitionIt == registry_.end()) { return; } const auto &transition = transitionIt->second; - transition->update(rt, updates); + + if (updates.settings.has_value()) { + transition->updateSettings(*updates.settings); + } + + runTransition(rt, transition, updates.properties); } void CSSTransitionsRegistry::update(const double timestamp) { @@ -89,7 +95,7 @@ void CSSTransitionsRegistry::activateDelayedTransitions(const double timestamp) } void CSSTransitionsRegistry::scheduleOrActivateTransition(const std::shared_ptr &transition) { - const auto viewTag = transition->getShadowNode()->getViewTag(); + const auto viewTag = transition->getShadowNode()->getTag(); const auto currentTimestamp = getCurrentTimestamp_(); const auto minDelay = transition->getMinDelay(currentTimestamp); @@ -104,6 +110,44 @@ void CSSTransitionsRegistry::scheduleOrActivateTransition(const std::shared_ptr< } } +void CSSTransitionsRegistry::updateInUpdatesRegistry( + const std::shared_ptr &transition, + const folly::dynamic &updates) { + const auto &shadowNode = transition->getShadowNode(); + const auto &lastUpdates = getUpdatesFromRegistry(shadowNode->getTag()); + const auto &transitionProperties = transition->getProperties(); + + folly::dynamic filteredUpdates = folly::dynamic::object; + + if (!transitionProperties.has_value()) { + // If transitionProperty is set to 'all' (optional has no value), we have + // to keep the result of the previous transition updated with the new + // transition starting values + if (!lastUpdates.empty()) { + filteredUpdates = lastUpdates; + } + } else if (!lastUpdates.empty()) { + // Otherwise, we keep only allowed properties from the last updates + // and update the object with the new transition starting values + for (const auto &prop : transitionProperties.value()) { + if (lastUpdates.count(prop)) { + filteredUpdates[prop] = lastUpdates[prop]; + } + } + } + + // updated object contains only allowed properties so we don't need + // to do additional filtering here + filteredUpdates.update(updates); + setInUpdatesRegistry(shadowNode, filteredUpdates); } } // namespace reanimated::css +void CSSTransitionsRegistry::runTransition( + jsi::Runtime &rt, + const std::shared_ptr &transition, + const CSSTransitionPropertyUpdates &propertyUpdates) { + const auto startStyle = transition->run(rt, propertyUpdates, getCurrentTimestamp_()); + updateInUpdatesRegistry(transition, startStyle); + scheduleOrActivateTransition(transition); +} diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.h index d178b5a828b7..c0e86280f0de 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.h @@ -1,6 +1,5 @@ #pragma once -#include #include #include #include @@ -18,15 +17,19 @@ namespace reanimated::css { class CSSTransitionsRegistry : public UpdatesRegistry { public: - CSSTransitionsRegistry( - const std::shared_ptr &staticPropsRegistry, - const GetAnimationTimestampFunction &getCurrentTimestamp); + CSSTransitionsRegistry(const GetAnimationTimestampFunction &getCurrentTimestamp); bool isEmpty() const override; bool hasUpdates() const; - void add(jsi::Runtime &rt, std::shared_ptr shadowNode, const CSSTransitionConfig &config); - void update(jsi::Runtime &rt, Tag viewTag, const CSSTransitionUpdates &updates); + void add( + jsi::Runtime &rt, + std::shared_ptr shadowNode, + const CSSTransitionConfig &config); + void update( + jsi::Runtime &rt, + Tag viewTag, + const CSSTransitionUpdates &updates); void remove(Tag viewTag) override; void update(double timestamp); @@ -35,7 +38,6 @@ class CSSTransitionsRegistry : public UpdatesRegistry { using Registry = std::unordered_map>; const GetAnimationTimestampFunction &getCurrentTimestamp_; - Registry registry_; std::unordered_set runningTransitionTags_; @@ -43,6 +45,11 @@ class CSSTransitionsRegistry : public UpdatesRegistry { void activateDelayedTransitions(double timestamp); void scheduleOrActivateTransition(const std::shared_ptr &transition); + void runTransition( + jsi::Runtime &rt, + const std::shared_ptr &transition, + const CSSTransitionPropertyUpdates &propertyUpdates); + void updateInUpdatesRegistry(const std::shared_ptr &transition, const folly::dynamic &updates); }; } // namespace reanimated::css diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/utils/props.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/utils/props.cpp deleted file mode 100644 index d904712662b7..000000000000 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/utils/props.cpp +++ /dev/null @@ -1,17 +0,0 @@ -#include - -#include -#include - -namespace reanimated::css { - -bool isDiscreteProperty(const std::string &propName, const std::string &componentName) { - const auto &interpolators = getComponentInterpolators(componentName); - const auto it = interpolators.find(propName); - if (it == interpolators.end()) { - return false; - } - return it->second->isDiscreteProperty(); -} - -} // namespace reanimated::css diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/utils/props.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/utils/props.h deleted file mode 100644 index 5e286c224654..000000000000 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/utils/props.h +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#include -#include - -#include -#include -#include -#include -#include - -namespace reanimated::css { - -bool isDiscreteProperty(const std::string &propName, const std::string &componentName); - -} // namespace reanimated::css diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp index 94f2028fd756..6d7dcfbfcf81 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp @@ -57,7 +57,7 @@ ReanimatedModuleProxy::ReanimatedModuleProxy( viewStylesRepository_(std::make_shared(staticPropsRegistry_, animatedPropsRegistry_)), cssAnimationKeyframesRegistry_(std::make_shared(viewStylesRepository_)), cssAnimationsRegistry_(std::make_shared()), - cssTransitionsRegistry_(std::make_shared(staticPropsRegistry_, getAnimationTimestamp_)), + cssTransitionsRegistry_(std::make_shared(getAnimationTimestamp_)), synchronouslyUpdateUIPropsFunction_(platformDepMethodsHolder.synchronouslyUpdateUIPropsFunction), #ifdef ANDROID filterUnmountedTagsFunction_(platformDepMethodsHolder.filterUnmountedTagsFunction), diff --git a/packages/react-native-reanimated/src/css/native/managers/CSSTransitionsManager.ts b/packages/react-native-reanimated/src/css/native/managers/CSSTransitionsManager.ts index f92a23a812d8..f1810bd37a13 100644 --- a/packages/react-native-reanimated/src/css/native/managers/CSSTransitionsManager.ts +++ b/packages/react-native-reanimated/src/css/native/managers/CSSTransitionsManager.ts @@ -54,21 +54,22 @@ export default class CSSTransitionsManager implements ICSSTransitionsManager { this.previousStyle = style; if (propertyDiff) { + const settingsDiff = this.getSettingsUpdates( + transitionConfig, + previousConfig + ); + if (!previousConfig) { registerCSSTransition(this.shadowNodeWrapper, { properties: propertyDiff, settings: transitionConfig.settings, + settingsUpdates: settingsDiff ?? undefined, }); } else { const updates: CSSTransitionUpdates = { properties: propertyDiff, }; - const settingsDiff = this.getSettingsUpdates( - transitionConfig, - previousConfig - ); - if (settingsDiff) { updates.settings = settingsDiff; } From 9d5142090baae08d64ec6008498daa0aeba53c10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20=C5=81opaci=C5=84ski?= Date: Fri, 28 Nov 2025 08:05:29 +0000 Subject: [PATCH 08/13] Remove unnecessary setViewStyle calls --- .../src/ReanimatedModule/NativeReanimated.ts | 2 +- .../js-reanimated/JSReanimated.ts | 2 +- .../ReanimatedModule/reanimatedModuleProxy.ts | 2 +- .../src/css/native/managers/CSSManager.ts | 27 ++++------ .../native/managers/CSSTransitionsManager.ts | 52 ++++++++++--------- .../src/css/native/proxy.ts | 3 +- 6 files changed, 42 insertions(+), 46 deletions(-) diff --git a/packages/react-native-reanimated/src/ReanimatedModule/NativeReanimated.ts b/packages/react-native-reanimated/src/ReanimatedModule/NativeReanimated.ts index dba0777f1330..c5a3c066b208 100644 --- a/packages/react-native-reanimated/src/ReanimatedModule/NativeReanimated.ts +++ b/packages/react-native-reanimated/src/ReanimatedModule/NativeReanimated.ts @@ -187,7 +187,7 @@ See https://docs.swmansion.com/react-native-reanimated/docs/guides/troubleshooti this.#reanimatedModuleProxy.unsubscribeFromKeyboardEvents(listenerId); } - setViewStyle(viewTag: number, style: StyleProps) { + setViewStyle(viewTag: number, style: StyleProps | null) { this.#reanimatedModuleProxy.setViewStyle(viewTag, style); } diff --git a/packages/react-native-reanimated/src/ReanimatedModule/js-reanimated/JSReanimated.ts b/packages/react-native-reanimated/src/ReanimatedModule/js-reanimated/JSReanimated.ts index ed5ac419582b..cbeacd2fcc29 100644 --- a/packages/react-native-reanimated/src/ReanimatedModule/js-reanimated/JSReanimated.ts +++ b/packages/react-native-reanimated/src/ReanimatedModule/js-reanimated/JSReanimated.ts @@ -271,7 +271,7 @@ class JSReanimated implements IReanimatedModule { // noop } - setViewStyle(_viewTag: number, _style: StyleProps): void { + setViewStyle(_viewTag: number, _style: StyleProps | null): void { throw new ReanimatedError('setViewStyle is not available in JSReanimated.'); } diff --git a/packages/react-native-reanimated/src/ReanimatedModule/reanimatedModuleProxy.ts b/packages/react-native-reanimated/src/ReanimatedModule/reanimatedModuleProxy.ts index a2bb3ec30e0c..a97370b18535 100644 --- a/packages/react-native-reanimated/src/ReanimatedModule/reanimatedModuleProxy.ts +++ b/packages/react-native-reanimated/src/ReanimatedModule/reanimatedModuleProxy.ts @@ -60,7 +60,7 @@ export interface ReanimatedModuleProxy { setShouldAnimateExitingForTag(viewTag: number, shouldAnimate: boolean): void; - setViewStyle(viewTag: number, style: StyleProps): void; + setViewStyle(viewTag: number, style: StyleProps | null): void; markNodeAsRemovable(shadowNodeWrapper: ShadowNodeWrapper): void; unmarkNodeAsRemovable(viewTag: number): void; diff --git a/packages/react-native-reanimated/src/css/native/managers/CSSManager.ts b/packages/react-native-reanimated/src/css/native/managers/CSSManager.ts index 47854fe45eb1..a3aa00023b21 100644 --- a/packages/react-native-reanimated/src/css/native/managers/CSSManager.ts +++ b/packages/react-native-reanimated/src/css/native/managers/CSSManager.ts @@ -18,7 +18,7 @@ export default class CSSManager implements ICSSManager { private readonly viewTag: number; private readonly viewName: string; private readonly styleBuilder: StyleBuilder | null = null; - private isFirstUpdate: boolean = true; + private isStyleSet = false; constructor({ shadowNodeWrapper, viewTag, viewName = 'RCTView' }: ViewInfo) { const tag = (this.viewTag = viewTag as number); @@ -46,25 +46,20 @@ export default class CSSManager implements ICSSManager { ); } - const normalizedStyle = this.styleBuilder?.buildFrom(filteredStyle); + const normalizedStyle = this.styleBuilder?.buildFrom(filteredStyle) ?? null; - // If the update is called during the first css style update, we won't - // trigger CSS transitions and set styles before attaching CSS transitions - if (this.isFirstUpdate && normalizedStyle) { - setViewStyle(this.viewTag, normalizedStyle); - } - - this.cssTransitionsManager.update(transitionProperties, normalizedStyle ?? null); + this.cssTransitionsManager.update(transitionProperties, normalizedStyle); this.cssAnimationsManager.update(animationProperties); - // If the current update is not the fist one, we want to update CSS - // animations and transitions first and update the style then to make - // sure that the new transition is fired with new settings (like duration) - if (!this.isFirstUpdate && normalizedStyle) { - setViewStyle(this.viewTag, normalizedStyle); + if (normalizedStyle) { + if (animationProperties) { + setViewStyle(this.viewTag, normalizedStyle); + this.isStyleSet = true; + } + } else if (this.isStyleSet) { + setViewStyle(this.viewTag, null); + this.isStyleSet = false; } - - this.isFirstUpdate = false; } unmountCleanup(): void { diff --git a/packages/react-native-reanimated/src/css/native/managers/CSSTransitionsManager.ts b/packages/react-native-reanimated/src/css/native/managers/CSSTransitionsManager.ts index f1810bd37a13..8a2741cd7687 100644 --- a/packages/react-native-reanimated/src/css/native/managers/CSSTransitionsManager.ts +++ b/packages/react-native-reanimated/src/css/native/managers/CSSTransitionsManager.ts @@ -46,36 +46,38 @@ export default class CSSTransitionsManager implements ICSSTransitionsManager { return; } - const propertyDiff = - transitionConfig.properties.length > 0 - ? getChangedProps(previousStyle, style, transitionConfig.properties) - : null; + const propertyDiff = getChangedProps( + previousStyle, + style, + transitionConfig.properties + ); this.previousStyle = style; - if (propertyDiff) { - const settingsDiff = this.getSettingsUpdates( - transitionConfig, - previousConfig - ); + if (!propertyDiff) { + this.transitionConfig = transitionConfig; + return; + } - if (!previousConfig) { - registerCSSTransition(this.shadowNodeWrapper, { - properties: propertyDiff, - settings: transitionConfig.settings, - settingsUpdates: settingsDiff ?? undefined, - }); - } else { - const updates: CSSTransitionUpdates = { - properties: propertyDiff, - }; - - if (settingsDiff) { - updates.settings = settingsDiff; - } - - updateCSSTransition(this.viewTag, updates); + const settingsDiff = previousConfig + ? this.getSettingsUpdates(transitionConfig, previousConfig) + : null; + + if (!previousConfig) { + registerCSSTransition(this.shadowNodeWrapper, { + properties: propertyDiff, + settings: transitionConfig.settings, + }); + } else { + const updates: CSSTransitionUpdates = { + properties: propertyDiff, + }; + + if (settingsDiff) { + updates.settings = settingsDiff; } + + updateCSSTransition(this.viewTag, updates); } this.transitionConfig = transitionConfig; diff --git a/packages/react-native-reanimated/src/css/native/proxy.ts b/packages/react-native-reanimated/src/css/native/proxy.ts index 942d955a74d7..8b0db9935af0 100644 --- a/packages/react-native-reanimated/src/css/native/proxy.ts +++ b/packages/react-native-reanimated/src/css/native/proxy.ts @@ -5,13 +5,12 @@ import type { CSSAnimationUpdates, CSSTransitionUpdates, NormalizedCSSAnimationKeyframesConfig, - NormalizedCSSTransitionConfig, NormalizedNewCSSTransitionConfig, } from './types'; // COMMON -export function setViewStyle(viewTag: number, style: StyleProps) { +export function setViewStyle(viewTag: number, style: StyleProps | null) { ReanimatedModule.setViewStyle(viewTag, style); } From fa1e915bbbbf1bfbfde049de4da000288508b851 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20=C5=81opaci=C5=84ski?= Date: Fri, 28 Nov 2025 17:52:49 +0100 Subject: [PATCH 09/13] Some cleanup and improvements --- .../cpp/reanimated/CSS/common/definitions.h | 8 --- .../CSS/configs/CSSTransitionConfig.cpp | 9 +-- .../CSS/configs/CSSTransitionConfig.h | 7 +- .../cpp/reanimated/CSS/core/CSSTransition.cpp | 24 ++++--- .../CSS/interpolation/PropertyInterpolator.h | 3 +- .../groups/ArrayPropertiesInterpolator.cpp | 55 +++++----------- .../groups/ArrayPropertiesInterpolator.h | 4 +- .../groups/RecordPropertiesInterpolator.cpp | 64 +++++++------------ .../groups/RecordPropertiesInterpolator.h | 4 +- .../OperationsStyleInterpolator.cpp | 59 ++++++++--------- .../operations/OperationsStyleInterpolator.h | 4 +- .../styles/TransitionStyleInterpolator.cpp | 32 ++-------- .../styles/TransitionStyleInterpolator.h | 2 +- .../values/SimpleValueInterpolator.h | 1 - .../values/ValueInterpolator.cpp | 45 +++---------- .../interpolation/values/ValueInterpolator.h | 10 +-- .../progress/TransitionProgressProvider.cpp | 8 +-- .../CSS/progress/TransitionProgressProvider.h | 5 +- .../CSS/registries/CSSAnimationsRegistry.h | 1 - .../CSS/registries/CSSTransitionsRegistry.cpp | 5 +- .../CSS/registries/CSSTransitionsRegistry.h | 16 +---- .../NativeModules/ReanimatedModuleProxy.h | 11 +--- .../NativeModules/ReanimatedModuleProxySpec.h | 3 +- .../src/ReanimatedModule/NativeReanimated.ts | 24 ++----- .../js-reanimated/JSReanimated.ts | 5 +- .../src/css/native/managers/CSSManager.ts | 5 +- .../native/managers/CSSTransitionsManager.ts | 64 +++++++++---------- .../src/css/native/types/transition.ts | 5 +- .../src/css/types/interfaces.ts | 4 +- .../src/css/utils/props.ts | 17 ++--- 30 files changed, 181 insertions(+), 323 deletions(-) diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/common/definitions.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/common/definitions.h index 1b9f6292641a..fe4af931cd45 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/common/definitions.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/common/definitions.h @@ -9,15 +9,7 @@ namespace reanimated::css { using namespace facebook; -using PropertyNames = std::vector; using PropertyPath = std::vector; -/** - * If nullopt - all style properties can trigger transition - * If empty vector - no style property can trigger transition - * Otherwise - only specified style properties can trigger transition - */ -using TransitionProperties = std::optional; - using EasingFunction = std::function; using ColorChannels = std::array; diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/configs/CSSTransitionConfig.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/configs/CSSTransitionConfig.cpp index 9e78cd291526..26504c7ed642 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/configs/CSSTransitionConfig.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/configs/CSSTransitionConfig.cpp @@ -2,8 +2,6 @@ #include -#include - namespace reanimated::css { CSSTransitionPropertyUpdates parsePropertyUpdates(jsi::Runtime &rt, const jsi::Object &diffs) { @@ -31,16 +29,13 @@ CSSTransitionPropertyUpdates parsePropertyUpdates(jsi::Runtime &rt, const jsi::O result.emplace( propertyName, - std::make_optional(std::make_pair( - diffArray.getValueAtIndex(rt, 0), diffArray.getValueAtIndex(rt, 1)))); + std::make_optional(std::make_pair(diffArray.getValueAtIndex(rt, 0), diffArray.getValueAtIndex(rt, 1)))); } return result; } -PartialCSSTransitionPropertySettings parsePartialPropertySettings( - jsi::Runtime &rt, - const jsi::Object &settings) { +PartialCSSTransitionPropertySettings parsePartialPropertySettings(jsi::Runtime &rt, const jsi::Object &settings) { PartialCSSTransitionPropertySettings result; if (settings.hasProperty(rt, "duration")) { diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/configs/CSSTransitionConfig.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/configs/CSSTransitionConfig.h index 15af36e580ac..417911fb3e0d 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/configs/CSSTransitionConfig.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/configs/CSSTransitionConfig.h @@ -7,6 +7,7 @@ #include #include #include +#include namespace reanimated::css { @@ -19,8 +20,7 @@ struct CSSTransitionPropertySettings { using CSSTransitionPropertiesSettings = std::unordered_map; -using CSSTransitionPropertyUpdates = - std::unordered_map>>; +using CSSTransitionPropertyUpdates = std::unordered_map>>; struct CSSTransitionConfig { CSSTransitionPropertyUpdates properties; @@ -34,8 +34,7 @@ struct PartialCSSTransitionPropertySettings { std::optional allowDiscrete; }; -using CSSTransitionPropertySettingsUpdates = - std::unordered_map; +using CSSTransitionPropertySettingsUpdates = std::unordered_map; struct CSSTransitionUpdates { CSSTransitionPropertyUpdates properties; diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/core/CSSTransition.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/core/CSSTransition.cpp index f2032a9679dd..4dbd837d00c0 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/core/CSSTransition.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/core/CSSTransition.cpp @@ -3,6 +3,8 @@ #include #include #include +#include +#include namespace reanimated::css { @@ -14,8 +16,7 @@ CSSTransition::CSSTransition( viewStylesRepository_(viewStylesRepository), settings_(config.settings), styleInterpolator_(TransitionStyleInterpolator(shadowNode_->getComponentName(), viewStylesRepository)), - progressProvider_(TransitionProgressProvider()) { -} + progressProvider_(TransitionProgressProvider()) {} std::shared_ptr CSSTransition::getShadowNode() const { return shadowNode_; @@ -48,16 +49,19 @@ void CSSTransition::updateSettings(const CSSTransitionPropertySettingsUpdates &s } } -folly::dynamic CSSTransition::run( - jsi::Runtime &rt, - const CSSTransitionPropertyUpdates &propertyUpdates, - const double timestamp) { - if (!propertyUpdates.empty()) { - styleInterpolator_.updateInterpolatedProperties(rt, propertyUpdates); +folly::dynamic +CSSTransition::run(jsi::Runtime &rt, const CSSTransitionPropertyUpdates &propertyUpdates, const double timestamp) { + const auto reversedPropertyNames = styleInterpolator_.updateInterpolatedProperties(rt, propertyUpdates); + + std::vector propertyNames; + propertyNames.reserve(propertyUpdates.size()); + for (const auto &[key, value] : propertyUpdates) { + propertyNames.emplace_back(key); } - progressProvider_.runProgressProviders(timestamp, settings_, propertyUpdates); - return update(rt, timestamp); + progressProvider_.runProgressProviders(timestamp, settings_, propertyNames, reversedPropertyNames); + + return update(timestamp); } folly::dynamic CSSTransition::update(const double timestamp) { diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/PropertyInterpolator.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/PropertyInterpolator.h index 0fe2e2863436..63436d8f0103 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/PropertyInterpolator.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/PropertyInterpolator.h @@ -23,10 +23,9 @@ class PropertyInterpolator { virtual folly::dynamic getResetStyle(const std::shared_ptr &shadowNode) const = 0; virtual folly::dynamic getFirstKeyframeValue() const = 0; virtual folly::dynamic getLastKeyframeValue() const = 0; - virtual bool equalsReversingAdjustedStartValue(const folly::dynamic &propertyValue) const = 0; virtual void updateKeyframes(jsi::Runtime &rt, const jsi::Value &keyframes) = 0; - virtual void updateKeyframesFromStyleChange( + virtual bool updateKeyframesFromStyleChange( jsi::Runtime &rt, const jsi::Value &oldStyleValue, const jsi::Value &newStyleValue) = 0; diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/ArrayPropertiesInterpolator.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/ArrayPropertiesInterpolator.cpp index 661b29e5e343..cd9b635e2d49 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/ArrayPropertiesInterpolator.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/ArrayPropertiesInterpolator.cpp @@ -11,25 +11,6 @@ ArrayPropertiesInterpolator::ArrayPropertiesInterpolator( const std::shared_ptr &viewStylesRepository) : GroupPropertiesInterpolator(propertyPath, viewStylesRepository), factories_(factories) {} -bool ArrayPropertiesInterpolator::equalsReversingAdjustedStartValue(const folly::dynamic &propertyValue) const { - if (!propertyValue.isArray()) { - return false; - } - - const auto valuesCount = propertyValue.size(); - if (valuesCount != interpolators_.size()) { - return false; - } - - for (size_t i = 0; i < valuesCount; ++i) { - if (!interpolators_[i]->equalsReversingAdjustedStartValue(propertyValue[i])) { - return false; - } - } - - return true; -} - void ArrayPropertiesInterpolator::updateKeyframes(jsi::Runtime &rt, const jsi::Value &keyframes) { const jsi::Array keyframesArray = keyframes.asObject(rt).asArray(rt); const size_t valuesCount = keyframesArray.size(rt); @@ -41,37 +22,31 @@ void ArrayPropertiesInterpolator::updateKeyframes(jsi::Runtime &rt, const jsi::V } } -void ArrayPropertiesInterpolator::updateKeyframesFromStyleChange( +bool ArrayPropertiesInterpolator::updateKeyframesFromStyleChange( jsi::Runtime &rt, const jsi::Value &oldStyleValue, const jsi::Value &newStyleValue) { - const auto getArray = [&rt](const jsi::Value &value) -> std::optional { - if (!value.isObject()) { - return std::nullopt; - } - - const auto object = value.asObject(rt); - if (!object.isArray(rt)) { - return std::nullopt; - } - - return object.asArray(rt); - }; + const auto oldStyleArray = oldStyleValue.isUndefined() ? jsi::Array(rt, 0) : oldStyleValue.asObject(rt).asArray(rt); + const auto newStyleArray = newStyleValue.isUndefined() ? jsi::Array(rt, 0) : newStyleValue.asObject(rt).asArray(rt); - const auto oldArray = getArray(oldStyleValue); - const auto newArray = getArray(newStyleValue); - - const size_t oldSize = oldArray.has_value() ? oldArray->size(rt) : 0; - const size_t newSize = newArray.has_value() ? newArray->size(rt) : 0; + const size_t oldSize = oldStyleArray.size(rt); + const size_t newSize = newStyleArray.size(rt); const size_t valuesCount = std::max(oldSize, newSize); resizeInterpolators(valuesCount); + bool areAllPropsReversed = true; + for (size_t i = 0; i < valuesCount; ++i) { - const auto oldValue = (oldArray.has_value() && i < oldSize) ? oldArray->getValueAtIndex(rt, i) : jsi::Value::undefined(); - const auto newValue = (newArray.has_value() && i < newSize) ? newArray->getValueAtIndex(rt, i) : jsi::Value::undefined(); - interpolators_[i]->updateKeyframesFromStyleChange(rt, oldValue, newValue); + // These index checks ensure that interpolation works between 2 arrays + // with different lengths + areAllPropsReversed &= interpolators_[i]->updateKeyframesFromStyleChange( + rt, + i < oldSize ? oldStyleArray.getValueAtIndex(rt, i) : jsi::Value::undefined(), + i < newSize ? newStyleArray.getValueAtIndex(rt, i) : jsi::Value::undefined()); } + + return areAllPropsReversed; } folly::dynamic ArrayPropertiesInterpolator::mapInterpolators( diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/ArrayPropertiesInterpolator.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/ArrayPropertiesInterpolator.h index 82fa1c9b37e2..baba29a8e9d3 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/ArrayPropertiesInterpolator.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/ArrayPropertiesInterpolator.h @@ -15,10 +15,8 @@ class ArrayPropertiesInterpolator : public GroupPropertiesInterpolator { const std::shared_ptr &viewStylesRepository); virtual ~ArrayPropertiesInterpolator() = default; - bool equalsReversingAdjustedStartValue(const folly::dynamic &propertyValue) const override; - void updateKeyframes(jsi::Runtime &rt, const jsi::Value &keyframes) override; - void updateKeyframesFromStyleChange( + bool updateKeyframesFromStyleChange( jsi::Runtime &rt, const jsi::Value &oldStyleValue, const jsi::Value &newStyleValue) override; diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/RecordPropertiesInterpolator.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/RecordPropertiesInterpolator.cpp index 2b440013dbbb..a8368551cfc1 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/RecordPropertiesInterpolator.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/RecordPropertiesInterpolator.cpp @@ -12,14 +12,6 @@ RecordPropertiesInterpolator::RecordPropertiesInterpolator( const std::shared_ptr &viewStylesRepository) : GroupPropertiesInterpolator(propertyPath, viewStylesRepository), factories_(factories) {} -bool RecordPropertiesInterpolator::equalsReversingAdjustedStartValue(const folly::dynamic &propertyValue) const { - return std::ranges::all_of(propertyValue.items(), [this](const auto &item) { - const auto &[propName, propValue] = item; - const auto it = interpolators_.find(propName.getString()); - return it != interpolators_.end() && it->second->equalsReversingAdjustedStartValue(propValue); - }); -} - void RecordPropertiesInterpolator::updateKeyframes(jsi::Runtime &rt, const jsi::Value &keyframes) { // TODO - maybe add a possibility to remove interpolators that are no longer // used (for now, for simplicity, we only add new ones) @@ -37,50 +29,42 @@ void RecordPropertiesInterpolator::updateKeyframes(jsi::Runtime &rt, const jsi:: } } -void RecordPropertiesInterpolator::updateKeyframesFromStyleChange( +bool RecordPropertiesInterpolator::updateKeyframesFromStyleChange( jsi::Runtime &rt, const jsi::Value &oldStyleValue, const jsi::Value &newStyleValue) { - const auto getObject = [&rt](const jsi::Value &value) -> std::optional { - if (!value.isObject()) { - return std::nullopt; - } - - const auto object = value.asObject(rt); - if (!object.isObject()) { - return std::nullopt; - } - - return object; - }; + // TODO - maybe add a possibility to remove interpolators that are no longer + // used (for now, for simplicity, we only add new ones) + const auto oldStyleObject = oldStyleValue.isUndefined() ? jsi::Object(rt) : oldStyleValue.asObject(rt); + const auto newStyleObject = newStyleValue.isUndefined() ? jsi::Object(rt) : newStyleValue.asObject(rt); - const auto oldObject = getObject(oldStyleValue); - const auto newObject = getObject(newStyleValue); + const auto oldPropertyNames = oldStyleObject.getPropertyNames(rt); + const auto newPropertyNames = newStyleObject.getPropertyNames(rt); + const auto oldSize = oldPropertyNames.size(rt); + const auto newSize = newPropertyNames.size(rt); std::unordered_set propertyNamesSet; - if (oldObject.has_value()) { - jsi::Array names = oldObject->getPropertyNames(rt); - const size_t count = names.size(rt); - for (size_t i = 0; i < count; ++i) { - propertyNamesSet.insert(names.getValueAtIndex(rt, i).asString(rt).utf8(rt)); - } + for (size_t i = 0; i < oldSize; ++i) { + propertyNamesSet.insert(oldPropertyNames.getValueAtIndex(rt, i).asString(rt).utf8(rt)); } - if (newObject.has_value()) { - jsi::Array names = newObject->getPropertyNames(rt); - const size_t count = names.size(rt); - for (size_t i = 0; i < count; ++i) { - propertyNamesSet.insert(names.getValueAtIndex(rt, i).asString(rt).utf8(rt)); - } + for (size_t i = 0; i < newSize; ++i) { + propertyNamesSet.insert(newPropertyNames.getValueAtIndex(rt, i).asString(rt).utf8(rt)); } + bool areAllPropsReversed = true; + for (const auto &propertyName : propertyNamesSet) { maybeCreateInterpolator(propertyName); - - const auto propId = jsi::PropNameID::forUtf8(rt, propertyName); - const auto oldValue = oldObject.has_value() ? oldObject->getProperty(rt, propId) : jsi::Value::undefined(); - const auto newValue = newObject.has_value() ? newObject->getProperty(rt, propId) : jsi::Value::undefined(); - interpolators_[propertyName]->updateKeyframesFromStyleChange(rt, oldValue, newValue); + const auto propNameID = jsi::PropNameID::forUtf8(rt, propertyName); + areAllPropsReversed &= interpolators_[propertyName]->updateKeyframesFromStyleChange( + rt, + oldStyleObject.hasProperty(rt, propNameID) ? oldStyleObject.getProperty(rt, propNameID) + : jsi::Value::undefined(), + newStyleObject.hasProperty(rt, propNameID) ? newStyleObject.getProperty(rt, propNameID) + : jsi::Value::undefined()); } + + return areAllPropsReversed; } folly::dynamic RecordPropertiesInterpolator::mapInterpolators( diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/RecordPropertiesInterpolator.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/RecordPropertiesInterpolator.h index 8154c6f9f7e6..964234a53dd4 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/RecordPropertiesInterpolator.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/RecordPropertiesInterpolator.h @@ -16,10 +16,8 @@ class RecordPropertiesInterpolator : public GroupPropertiesInterpolator { const std::shared_ptr &viewStylesRepository); virtual ~RecordPropertiesInterpolator() = default; - bool equalsReversingAdjustedStartValue(const folly::dynamic &propertyValue) const override; - void updateKeyframes(jsi::Runtime &rt, const jsi::Value &keyframes) override; - void updateKeyframesFromStyleChange( + bool updateKeyframesFromStyleChange( jsi::Runtime &rt, const jsi::Value &oldStyleValue, const jsi::Value &newStyleValue) override; diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/operations/OperationsStyleInterpolator.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/operations/OperationsStyleInterpolator.cpp index 2631bfbf3854..427833d7e70b 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/operations/OperationsStyleInterpolator.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/operations/OperationsStyleInterpolator.cpp @@ -40,31 +40,6 @@ folly::dynamic OperationsStyleInterpolator::getLastKeyframeValue() const { return toOperations.has_value() ? convertOperationsToDynamic(toOperations.value()) : defaultStyleValueDynamic_; } -bool OperationsStyleInterpolator::equalsReversingAdjustedStartValue(const folly::dynamic &propertyValue) const { - const auto propertyOperations = parseStyleOperations(propertyValue); - - if (!reversingAdjustedStartValue_.has_value()) { - return !propertyOperations.has_value(); - } else if (!propertyOperations.has_value()) { - return false; - } - - const auto &reversingAdjustedOperationsValue = reversingAdjustedStartValue_.value(); - const auto &propertyOperationsValue = propertyOperations.value(); - - if (reversingAdjustedOperationsValue.size() != propertyOperationsValue.size()) { - return false; - } - - for (size_t i = 0; i < reversingAdjustedOperationsValue.size(); ++i) { - if (*reversingAdjustedOperationsValue[i] != *propertyOperationsValue[i]) { - return false; - } - } - - return true; -} - folly::dynamic OperationsStyleInterpolator::interpolate( const std::shared_ptr &shadowNode, const std::shared_ptr &progressProvider, @@ -126,16 +101,10 @@ void OperationsStyleInterpolator::updateKeyframes(jsi::Runtime &rt, const jsi::V } } -void OperationsStyleInterpolator::updateKeyframesFromStyleChange( +bool OperationsStyleInterpolator::updateKeyframesFromStyleChange( jsi::Runtime &rt, const jsi::Value &oldStyleValue, const jsi::Value &newStyleValue) { - if (oldStyleValue.isUndefined()) { - reversingAdjustedStartValue_ = std::nullopt; - } else { - reversingAdjustedStartValue_ = parseStyleOperations(rt, oldStyleValue); - } - keyframes_.clear(); keyframes_.reserve(1); keyframes_.emplace_back(createStyleOperationsKeyframe( @@ -143,6 +112,14 @@ void OperationsStyleInterpolator::updateKeyframesFromStyleChange( 1, parseStyleOperations(rt, oldStyleValue).value_or(StyleOperations{}), parseStyleOperations(rt, newStyleValue).value_or(StyleOperations{}))); + + const auto &toOperations = keyframes_.back()->toOperations; + bool equalsReversingAdjustedStartValue = toOperations.has_value() == reversingAdjustedStartValue_.has_value() && + (!toOperations.has_value() || toOperations.value() == reversingAdjustedStartValue_.value()); + + reversingAdjustedStartValue_ = keyframes_.back()->fromOperations; + + return equalsReversingAdjustedStartValue; } std::optional OperationsStyleInterpolator::parseStyleOperations( @@ -165,6 +142,24 @@ std::optional OperationsStyleInterpolator::parseStyleOperations return styleOperations; } +std::optional OperationsStyleInterpolator::parseStyleOperations(const folly::dynamic &values) const { + if (values.empty()) { + return std::nullopt; + } + + const auto &operationsArray = values; + const auto operationsCount = operationsArray.size(); + + StyleOperations styleOperations; + styleOperations.reserve(operationsCount); + + for (size_t i = 0; i < operationsCount; ++i) { + styleOperations.emplace_back(createStyleOperation(operationsArray[i])); + } + + return styleOperations; +} + std::shared_ptr OperationsStyleInterpolator::createStyleOperationsKeyframe( const double fromOffset, const double toOffset, diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/operations/OperationsStyleInterpolator.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/operations/OperationsStyleInterpolator.h index b9f72a9a0778..716526958c32 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/operations/OperationsStyleInterpolator.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/operations/OperationsStyleInterpolator.h @@ -37,7 +37,6 @@ class OperationsStyleInterpolator : public PropertyInterpolator { folly::dynamic getResetStyle(const std::shared_ptr &shadowNode) const override; folly::dynamic getFirstKeyframeValue() const override; folly::dynamic getLastKeyframeValue() const override; - bool equalsReversingAdjustedStartValue(const folly::dynamic &propertyValue) const override; folly::dynamic interpolate( const std::shared_ptr &shadowNode, @@ -45,7 +44,7 @@ class OperationsStyleInterpolator : public PropertyInterpolator { double fallbackInterpolateThreshold) const override; void updateKeyframes(jsi::Runtime &rt, const jsi::Value &keyframes) override; - void updateKeyframesFromStyleChange( + bool updateKeyframesFromStyleChange( jsi::Runtime &rt, const jsi::Value &oldStyleValue, const jsi::Value &newStyleValue) override; @@ -66,6 +65,7 @@ class OperationsStyleInterpolator : public PropertyInterpolator { std::optional reversingAdjustedStartValue_; std::optional parseStyleOperations(jsi::Runtime &rt, const jsi::Value &values) const; + std::optional parseStyleOperations(const folly::dynamic &values) const; std::shared_ptr createStyleOperationsKeyframe( double fromOffset, double toOffset, diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/styles/TransitionStyleInterpolator.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/styles/TransitionStyleInterpolator.cpp index 99a82ab5cf8c..decdb04945f6 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/styles/TransitionStyleInterpolator.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/styles/TransitionStyleInterpolator.cpp @@ -11,28 +11,6 @@ TransitionStyleInterpolator::TransitionStyleInterpolator( const std::shared_ptr &viewStylesRepository) : componentName_(componentName), viewStylesRepository_(viewStylesRepository) {} -std::unordered_set TransitionStyleInterpolator::getReversedPropertyNames( - const folly::dynamic &newPropertyValues) const { - std::unordered_set reversedProperties; - - if (!newPropertyValues.isObject()) { - return reversedProperties; - } - - for (const auto &[propertyName, propertyValue] : newPropertyValues.items()) { - const auto propertyNameStr = propertyName.getString(); - const auto it = interpolators_.find(propertyNameStr); - if (it != interpolators_.end() && - // First keyframe value of the previous transition is the reversing - // adjusted start value - it->second->equalsReversingAdjustedStartValue(propertyValue)) { - reversedProperties.insert(propertyNameStr); - } - } - - return reversedProperties; -} - folly::dynamic TransitionStyleInterpolator::interpolate( const std::shared_ptr &shadowNode, const TransitionProgressProvider &transitionProgressProvider) const { @@ -46,7 +24,7 @@ folly::dynamic TransitionStyleInterpolator::interpolate( const auto &interpolator = interpolatorIt->second; const double fallbackInterpolateThreshold = - progressProvider->getFallbackInterpolateThreshold(interpolator->isDiscreteProperty()); + progressProvider->getFallbackInterpolateThreshold(interpolator->isDiscrete()); result[propertyName] = interpolator->interpolate(shadowNode, progressProvider, fallbackInterpolateThreshold); } @@ -73,9 +51,11 @@ void TransitionStyleInterpolator::discardIrrelevantInterpolators( } } -void TransitionStyleInterpolator::updateInterpolatedProperties( +std::unordered_set TransitionStyleInterpolator::updateInterpolatedProperties( jsi::Runtime &rt, const CSSTransitionPropertyUpdates &propertyUpdates) { + std::unordered_set reversedPropertyNames; + for (const auto &[propertyName, diffPair] : propertyUpdates) { if (!diffPair.has_value()) { interpolators_.erase(propertyName); @@ -89,8 +69,10 @@ void TransitionStyleInterpolator::updateInterpolatedProperties( it = interpolators_.emplace(propertyName, newInterpolator).first; } - it->second->updateKeyframesFromStyleChange(rt, diffPair->first, diffPair->second); + reversedPropertyNames.insert(it->second->updateKeyframesFromStyleChange(rt, diffPair->first, diffPair->second)); } + + return reversedPropertyNames; } } // namespace reanimated::css diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/styles/TransitionStyleInterpolator.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/styles/TransitionStyleInterpolator.h index b2531ea2b207..cff3fa6fe1a0 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/styles/TransitionStyleInterpolator.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/styles/TransitionStyleInterpolator.h @@ -26,7 +26,7 @@ class TransitionStyleInterpolator { void discardFinishedInterpolators(const TransitionProgressProvider &transitionProgressProvider); void discardIrrelevantInterpolators(const std::unordered_set &transitionPropertyNames); - void updateInterpolatedProperties( + std::unordered_set updateInterpolatedProperties( jsi::Runtime &rt, const CSSTransitionPropertyUpdates &propertyUpdates); diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/values/SimpleValueInterpolator.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/values/SimpleValueInterpolator.h index 17c168bf00ed..27de1b1d2b7d 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/values/SimpleValueInterpolator.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/values/SimpleValueInterpolator.h @@ -27,7 +27,6 @@ class SimpleValueInterpolator : public ValueInterpolator { protected: std::shared_ptr createValue(jsi::Runtime &rt, const jsi::Value &value) const override; - std::shared_ptr createValue(const folly::dynamic &value) const override; folly::dynamic interpolateValue( diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/values/ValueInterpolator.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/values/ValueInterpolator.cpp index 391bb4d2a3ea..ee55c519ed30 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/values/ValueInterpolator.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/values/ValueInterpolator.cpp @@ -11,8 +11,7 @@ ValueInterpolator::ValueInterpolator( const std::shared_ptr &viewStylesRepository) : PropertyInterpolator(propertyPath, viewStylesRepository), defaultStyleValue_(defaultValue), - defaultStyleValueDynamic_(defaultValue->toDynamic()), - lastUpdateValue_(nullptr) {} + defaultStyleValueDynamic_(defaultValue->toDynamic()) {} folly::dynamic ValueInterpolator::getStyleValue(const std::shared_ptr &shadowNode) const { return viewStylesRepository_->getStyleProp(shadowNode->getTag(), propertyPath_); @@ -36,13 +35,6 @@ folly::dynamic ValueInterpolator::getLastKeyframeValue() const { return convertOptionalToDynamic(keyframes_.back().value); } -bool ValueInterpolator::equalsReversingAdjustedStartValue(const folly::dynamic &propertyValue) const { - if (reversingAdjustedStartValue_.isNull()) { - return propertyValue.isNull(); - } - return reversingAdjustedStartValue_ == propertyValue; -} - void ValueInterpolator::updateKeyframes(jsi::Runtime &rt, const jsi::Value &keyframes) { const auto parsedKeyframes = parseJSIKeyframes(rt, keyframes); @@ -58,39 +50,18 @@ void ValueInterpolator::updateKeyframes(jsi::Runtime &rt, const jsi::Value &keyf } } -void ValueInterpolator::updateKeyframesFromStyleChange( +bool ValueInterpolator::updateKeyframesFromStyleChange( jsi::Runtime &rt, const jsi::Value &oldStyleValue, const jsi::Value &newStyleValue) { - const bool hasLastUpdateValue = lastUpdateValue_ != nullptr; - ValueKeyframe firstKeyframe, lastKeyframe; - - const bool hasOldValue = !oldStyleValue.isUndefined(); - const bool hasNewValue = !newStyleValue.isUndefined(); - - if (hasLastUpdateValue) { - firstKeyframe = ValueKeyframe{0, std::nullopt}; - } else if (hasOldValue) { - firstKeyframe = ValueKeyframe{0, createValue(rt, oldStyleValue)}; - } else { - firstKeyframe = ValueKeyframe{0, defaultStyleValue_}; - } + keyframes_ = { + ValueKeyframe{0, oldStyleValue.isUndefined() ? defaultStyleValue_ : createValue(rt, oldStyleValue)}, + ValueKeyframe{1, newStyleValue.isUndefined() ? defaultStyleValue_ : createValue(rt, newStyleValue)}}; - if (!hasNewValue) { - lastKeyframe = ValueKeyframe{1, defaultStyleValue_}; - } else { - lastKeyframe = ValueKeyframe{1, createValue(rt, newStyleValue)}; - } + auto equalsReversingAdjustedStartValue = keyframes_[1].value.value() == reversingAdjustedStartValue_; + reversingAdjustedStartValue_ = keyframes_[0].value.value(); - keyframes_ = {std::move(firstKeyframe), std::move(lastKeyframe)}; - if (hasLastUpdateValue) { - reversingAdjustedStartValue_ = lastUpdateValue_->toDynamic(); - } else if (hasOldValue) { - reversingAdjustedStartValue_ = createValue(rt, oldStyleValue)->toDynamic(); - } else { - reversingAdjustedStartValue_ = folly::dynamic(); - } - lastUpdateValue_ = hasNewValue ? createValue(rt, newStyleValue) : nullptr; + return equalsReversingAdjustedStartValue; } folly::dynamic ValueInterpolator::interpolate( diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/values/ValueInterpolator.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/values/ValueInterpolator.h index 74251499a6ee..f5e3c7ee8ecf 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/values/ValueInterpolator.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/values/ValueInterpolator.h @@ -32,10 +32,9 @@ class ValueInterpolator : public PropertyInterpolator { folly::dynamic getResetStyle(const std::shared_ptr &shadowNode) const override; folly::dynamic getFirstKeyframeValue() const override; folly::dynamic getLastKeyframeValue() const override; - bool equalsReversingAdjustedStartValue(const folly::dynamic &propertyValue) const override; void updateKeyframes(jsi::Runtime &rt, const jsi::Value &keyframes) override; - void updateKeyframesFromStyleChange( + bool updateKeyframesFromStyleChange( jsi::Runtime &rt, const jsi::Value &oldStyleValue, const jsi::Value &newStyleValue) override; @@ -47,12 +46,10 @@ class ValueInterpolator : public PropertyInterpolator { protected: std::vector keyframes_; - std::shared_ptr defaultStyleValue_; folly::dynamic defaultStyleValueDynamic_; - folly::dynamic reversingAdjustedStartValue_; - std::shared_ptr lastUpdateValue_; virtual std::shared_ptr createValue(jsi::Runtime &rt, const jsi::Value &value) const = 0; + virtual std::shared_ptr createValue(const folly::dynamic &value) const = 0; virtual folly::dynamic interpolateValue( double progress, const std::shared_ptr &fromValue, @@ -60,6 +57,9 @@ class ValueInterpolator : public PropertyInterpolator { const ValueInterpolationContext &context) const = 0; private: + std::shared_ptr defaultStyleValue_; + std::shared_ptr reversingAdjustedStartValue_; + folly::dynamic convertOptionalToDynamic(const std::optional> &value) const; std::shared_ptr getFallbackValue(const std::shared_ptr &shadowNode) const; size_t getToKeyframeIndex(const std::shared_ptr &progressProvider) const; diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/progress/TransitionProgressProvider.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/progress/TransitionProgressProvider.cpp index 1e502fc77bf5..e836f3a92d32 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/progress/TransitionProgressProvider.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/progress/TransitionProgressProvider.cpp @@ -57,8 +57,8 @@ TransitionProgressState TransitionPropertyProgressProvider::getState() const { return TransitionProgressState::Running; } -double TransitionPropertyProgressProvider::getFallbackInterpolateThreshold(const bool isDiscreteProperty) const { - return !isDiscreteProperty || allowDiscrete_ ? 0.5 : 0.0; +double TransitionPropertyProgressProvider::getFallbackInterpolateThreshold(const bool isDiscrete) const { + return !isDiscrete || allowDiscrete_ ? 0.5 : 0.0; } std::optional TransitionPropertyProgressProvider::calculateRawProgress(const double timestamp) { @@ -129,7 +129,7 @@ void TransitionProgressProvider::discardIrrelevantProgressProviders( void TransitionProgressProvider::runProgressProviders( const double timestamp, const CSSTransitionPropertiesSettings &propertiesSettings, - const PropertyNames &changedPropertyNames, + const std::vector &changedPropertyNames, const std::unordered_set &reversedPropertyNames) { for (const auto &propertyName : changedPropertyNames) { const auto propertySettingsOptional = getTransitionPropertySettings(propertiesSettings, propertyName); @@ -145,7 +145,7 @@ void TransitionProgressProvider::runProgressProviders( const auto &progressProvider = it->second; progressProvider->update(timestamp); - if (reversedPropertyNames.find(propertyName) != reversedPropertyNames.end() && + if (reversedPropertyNames.contains(propertyName) && progressProvider->getState() != TransitionProgressState::Finished) { // Create reversing shortening progress provider for interrupted // reversing transition diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/progress/TransitionProgressProvider.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/progress/TransitionProgressProvider.h index 9a5966460c19..27c40e753590 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/progress/TransitionProgressProvider.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/progress/TransitionProgressProvider.h @@ -3,7 +3,6 @@ #include #include #include -#include #include #include @@ -35,7 +34,7 @@ class TransitionPropertyProgressProvider final : public KeyframeProgressProvider double getRemainingDelay(double timestamp) const; double getReversingShorteningFactor() const; TransitionProgressState getState() const; - double getFallbackInterpolateThreshold(bool isDiscreteProperty) const; + double getFallbackInterpolateThreshold(bool isDiscrete) const; protected: std::optional calculateRawProgress(double timestamp) override; @@ -63,7 +62,7 @@ class TransitionProgressProvider final { void runProgressProviders( double timestamp, const CSSTransitionPropertiesSettings &propertiesSettings, - const PropertyNames &changedPropertyNames, + const std::vector &changedPropertyNames, const std::unordered_set &reversedPropertyNames); void update(double timestamp); diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSAnimationsRegistry.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSAnimationsRegistry.h index a0b161511222..5a20c08360ec 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSAnimationsRegistry.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSAnimationsRegistry.h @@ -3,7 +3,6 @@ #include #include #include -#include #include #include diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.cpp index a4c69040b90e..55a27c15a17a 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.cpp @@ -36,10 +36,7 @@ void CSSTransitionsRegistry::remove(const Tag viewTag) { registry_.erase(viewTag); } -void CSSTransitionsRegistry::update( - jsi::Runtime &rt, - const Tag viewTag, - const CSSTransitionUpdates &updates) { +void CSSTransitionsRegistry::update(jsi::Runtime &rt, const Tag viewTag, const CSSTransitionUpdates &updates) { const auto transitionIt = registry_.find(viewTag); if (transitionIt == registry_.end()) { return; diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.h index c0e86280f0de..86acf31d20ac 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.h @@ -1,35 +1,25 @@ #pragma once #include -#include #include #include #include #include -#include #include #include -#include -#include namespace reanimated::css { class CSSTransitionsRegistry : public UpdatesRegistry { public: - CSSTransitionsRegistry(const GetAnimationTimestampFunction &getCurrentTimestamp); + explicit CSSTransitionsRegistry(const GetAnimationTimestampFunction &getCurrentTimestamp); bool isEmpty() const override; bool hasUpdates() const; - void add( - jsi::Runtime &rt, - std::shared_ptr shadowNode, - const CSSTransitionConfig &config); - void update( - jsi::Runtime &rt, - Tag viewTag, - const CSSTransitionUpdates &updates); + void add(jsi::Runtime &rt, std::shared_ptr shadowNode, const CSSTransitionConfig &config); + void update(jsi::Runtime &rt, Tag viewTag, const CSSTransitionUpdates &updates); void remove(Tag viewTag) override; void update(double timestamp); diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.h b/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.h index 5ca3f4c2b845..e25b60b758cc 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.h @@ -117,14 +117,9 @@ class ReanimatedModuleProxy : public ReanimatedModuleProxySpec, override; void unregisterCSSAnimations(const jsi::Value &viewTag) override; - void registerCSSTransition( - jsi::Runtime &rt, - const jsi::Value &shadowNodeWrapper, - const jsi::Value &transitionConfig) override; - void updateCSSTransition( - jsi::Runtime &rt, - const jsi::Value &viewTag, - const jsi::Value &transitionUpdates) override; + void registerCSSTransition(jsi::Runtime &rt, const jsi::Value &shadowNodeWrapper, const jsi::Value &transitionConfig) + override; + void updateCSSTransition(jsi::Runtime &rt, const jsi::Value &viewTag, const jsi::Value &transitionUpdates) override; void unregisterCSSTransition(jsi::Runtime &rt, const jsi::Value &viewTag) override; void cssLoopCallback(const double /*timestampMs*/); diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxySpec.h b/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxySpec.h index 009c19957556..fae7c8350fd5 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxySpec.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxySpec.h @@ -84,7 +84,8 @@ class JSI_EXPORT ReanimatedModuleProxySpec : public TurboModule { // CSS transitions virtual void registerCSSTransition(jsi::Runtime &rt, const jsi::Value &shadowNodeWrapper, const jsi::Value &transitionConfig) = 0; - virtual void updateCSSTransition(jsi::Runtime &rt, const jsi::Value &viewTag, const jsi::Value &transitionUpdates) = 0; + virtual void + updateCSSTransition(jsi::Runtime &rt, const jsi::Value &viewTag, const jsi::Value &transitionUpdates) = 0; virtual void unregisterCSSTransition(jsi::Runtime &rt, const jsi::Value &viewTag) = 0; }; diff --git a/packages/react-native-reanimated/src/ReanimatedModule/NativeReanimated.ts b/packages/react-native-reanimated/src/ReanimatedModule/NativeReanimated.ts index c5a3c066b208..269aa71d23bb 100644 --- a/packages/react-native-reanimated/src/ReanimatedModule/NativeReanimated.ts +++ b/packages/react-native-reanimated/src/ReanimatedModule/NativeReanimated.ts @@ -24,8 +24,8 @@ import type { import type { CSSAnimationUpdates, CSSTransitionUpdates, - NormalizedCSSTransitionConfig, NormalizedCSSAnimationKeyframesConfig, + NormalizedNewCSSTransitionConfig, } from '../css/native'; import { getShadowNodeWrapperFromRef } from '../fabricUtils'; import { checkCppVersion } from '../platform-specific/checkCppVersion'; @@ -231,13 +231,11 @@ See https://docs.swmansion.com/react-native-reanimated/docs/guides/troubleshooti registerCSSTransition( shadowNodeWrapper: ShadowNodeWrapper, - transitionConfig: NormalizedCSSTransitionConfig, - transitionDiff: CSSTransitionUpdates + transitionConfig: NormalizedNewCSSTransitionConfig ) { this.#reanimatedModuleProxy.registerCSSTransition( shadowNodeWrapper, - transitionConfig, - transitionDiff + transitionConfig ); } @@ -245,10 +243,7 @@ See https://docs.swmansion.com/react-native-reanimated/docs/guides/troubleshooti viewTag: number, transitionUpdates: CSSTransitionUpdates ) { - this.#reanimatedModuleProxy.updateCSSTransition( - viewTag, - transitionUpdates - ); + this.#reanimatedModuleProxy.updateCSSTransition(viewTag, transitionUpdates); } unregisterCSSTransition(viewTag: number) { @@ -277,15 +272,8 @@ class DummyReanimatedModuleProxy implements ReanimatedModuleProxy { registerCSSAnimations(): void {} updateCSSAnimations(): void {} unregisterCSSAnimations(): void {} - registerCSSTransition( - _shadowNodeWrapper: ShadowNodeWrapper, - _transitionConfig: NormalizedCSSTransitionConfig, - _transitionDiff: CSSTransitionUpdates - ): void {} - updateCSSTransition( - _viewTag: number, - _transitionUpdates: CSSTransitionUpdates - ): void {} + registerCSSTransition(): void {} + updateCSSTransition(): void {} unregisterCSSTransition(): void {} registerSensor(): number { return -1; diff --git a/packages/react-native-reanimated/src/ReanimatedModule/js-reanimated/JSReanimated.ts b/packages/react-native-reanimated/src/ReanimatedModule/js-reanimated/JSReanimated.ts index cbeacd2fcc29..68798b93dfd6 100644 --- a/packages/react-native-reanimated/src/ReanimatedModule/js-reanimated/JSReanimated.ts +++ b/packages/react-native-reanimated/src/ReanimatedModule/js-reanimated/JSReanimated.ts @@ -24,8 +24,8 @@ import { SensorType } from '../../commonTypes'; import type { CSSAnimationUpdates, CSSTransitionUpdates, - NormalizedCSSTransitionConfig, NormalizedCSSAnimationKeyframesConfig, + NormalizedNewCSSTransitionConfig, } from '../../css/native'; import { assertWorkletsVersion } from '../../platform-specific/workletsVersion'; import type { IReanimatedModule } from '../reanimatedModuleProxy'; @@ -320,8 +320,7 @@ class JSReanimated implements IReanimatedModule { registerCSSTransition( _shadowNodeWrapper: ShadowNodeWrapper, - _transitionConfig: NormalizedCSSTransitionConfig, - _transitionDiff: CSSTransitionUpdates + _transitionConfig: NormalizedNewCSSTransitionConfig ): void { throw new ReanimatedError( '`registerCSSTransition` is not available in JSReanimated.' diff --git a/packages/react-native-reanimated/src/css/native/managers/CSSManager.ts b/packages/react-native-reanimated/src/css/native/managers/CSSManager.ts index a3aa00023b21..4ada19c44dc1 100644 --- a/packages/react-native-reanimated/src/css/native/managers/CSSManager.ts +++ b/packages/react-native-reanimated/src/css/native/managers/CSSManager.ts @@ -41,8 +41,9 @@ export default class CSSManager implements ICSSManager { filterCSSAndStyleProperties(style); if (!this.styleBuilder && (animationProperties || transitionProperties)) { + const kind = animationProperties ? 'CSS animations' : 'a cSS transition'; throw new ReanimatedError( - `Tried to apply CSS animations to ${this.viewName} which is not supported` + `Tried to apply ${kind} to ${this.viewName} which is not supported` ); } @@ -58,7 +59,7 @@ export default class CSSManager implements ICSSManager { } } else if (this.isStyleSet) { setViewStyle(this.viewTag, null); - this.isStyleSet = false; + this.isStyleSet = false; } } diff --git a/packages/react-native-reanimated/src/css/native/managers/CSSTransitionsManager.ts b/packages/react-native-reanimated/src/css/native/managers/CSSTransitionsManager.ts index 8a2741cd7687..1f8b0558be3e 100644 --- a/packages/react-native-reanimated/src/css/native/managers/CSSTransitionsManager.ts +++ b/packages/react-native-reanimated/src/css/native/managers/CSSTransitionsManager.ts @@ -1,22 +1,22 @@ 'use strict'; -import type { ShadowNodeWrapper } from '../../../commonTypes'; import type { UnknownRecord } from '../../../common'; +import type { ShadowNodeWrapper } from '../../../commonTypes'; import type { CSSTransitionProperties, ICSSTransitionsManager, } from '../../types'; +import { getChangedProps } from '../../utils'; import { normalizeCSSTransitionProperties } from '../normalization'; import { registerCSSTransition, - updateCSSTransition, unregisterCSSTransition, + updateCSSTransition, } from '../proxy'; import type { CSSTransitionUpdates, NormalizedCSSTransitionConfig, NormalizedSingleCSSTransitionSettings, } from '../types'; -import { getChangedProps } from '../../utils'; export default class CSSTransitionsManager implements ICSSTransitionsManager { private readonly viewTag: number; @@ -37,11 +37,12 @@ export default class CSSTransitionsManager implements ICSSTransitionsManager { const previousStyle = this.previousStyle; const previousConfig = this.transitionConfig; - const transitionConfig = transitionProperties + this.previousStyle = style; + this.transitionConfig = transitionProperties ? normalizeCSSTransitionProperties(transitionProperties) : null; - if (!transitionConfig) { + if (!this.transitionConfig) { this.detach(); return; } @@ -49,24 +50,23 @@ export default class CSSTransitionsManager implements ICSSTransitionsManager { const propertyDiff = getChangedProps( previousStyle, style, - transitionConfig.properties + this.transitionConfig.properties === 'all' + ? undefined + : this.transitionConfig.properties ); - this.previousStyle = style; - if (!propertyDiff) { - this.transitionConfig = transitionConfig; return; } const settingsDiff = previousConfig - ? this.getSettingsUpdates(transitionConfig, previousConfig) + ? this.getSettingsUpdates(this.transitionConfig, previousConfig) : null; if (!previousConfig) { registerCSSTransition(this.shadowNodeWrapper, { properties: propertyDiff, - settings: transitionConfig.settings, + settings: this.transitionConfig.settings, }); } else { const updates: CSSTransitionUpdates = { @@ -79,8 +79,6 @@ export default class CSSTransitionsManager implements ICSSTransitionsManager { updateCSSTransition(this.viewTag, updates); } - - this.transitionConfig = transitionConfig; } unmountCleanup(): void { @@ -96,22 +94,22 @@ export default class CSSTransitionsManager implements ICSSTransitionsManager { } private getSettingsUpdates( - newConfig: NormalizedCSSTransitionConfig, - previousConfig: NormalizedCSSTransitionConfig + oldConfig: NormalizedCSSTransitionConfig, + newConfig: NormalizedCSSTransitionConfig ): Record> | null { const diff: Record< string, Partial > = {}; - for (const [property, nextSettings] of Object.entries(newConfig.settings)) { - const perPropertyDiff = this.getPropertySettingsDiff( - previousConfig.settings[property], - nextSettings + for (const [property, newSettings] of Object.entries(newConfig.settings)) { + const settingsDiff = this.getPropertySettingsDiff( + oldConfig.settings[property], + newSettings ); - if (perPropertyDiff) { - diff[property] = perPropertyDiff; + if (settingsDiff) { + diff[property] = settingsDiff; } } @@ -119,26 +117,26 @@ export default class CSSTransitionsManager implements ICSSTransitionsManager { } private getPropertySettingsDiff( - previousSettings: NormalizedSingleCSSTransitionSettings | undefined, - nextSettings: NormalizedSingleCSSTransitionSettings + oldSettings: NormalizedSingleCSSTransitionSettings | undefined, + newSettings: NormalizedSingleCSSTransitionSettings ): Partial | null { - if (!previousSettings) { - return nextSettings; + if (!oldSettings) { + return newSettings; } const settingsDiff: Partial = {}; - if (previousSettings.duration !== nextSettings.duration) { - settingsDiff.duration = nextSettings.duration; + if (oldSettings.duration !== newSettings.duration) { + settingsDiff.duration = newSettings.duration; } - if (previousSettings.delay !== nextSettings.delay) { - settingsDiff.delay = nextSettings.delay; + if (oldSettings.delay !== newSettings.delay) { + settingsDiff.delay = newSettings.delay; } - if (previousSettings.allowDiscrete !== nextSettings.allowDiscrete) { - settingsDiff.allowDiscrete = nextSettings.allowDiscrete; + if (oldSettings.allowDiscrete !== newSettings.allowDiscrete) { + settingsDiff.allowDiscrete = newSettings.allowDiscrete; } - if (previousSettings.timingFunction !== nextSettings.timingFunction) { - settingsDiff.timingFunction = nextSettings.timingFunction; + if (oldSettings.timingFunction !== newSettings.timingFunction) { + settingsDiff.timingFunction = newSettings.timingFunction; } return Object.keys(settingsDiff).length > 0 ? settingsDiff : null; diff --git a/packages/react-native-reanimated/src/css/native/types/transition.ts b/packages/react-native-reanimated/src/css/native/types/transition.ts index acc0c48c151e..e0f3796589dc 100644 --- a/packages/react-native-reanimated/src/css/native/types/transition.ts +++ b/packages/react-native-reanimated/src/css/native/types/transition.ts @@ -15,7 +15,10 @@ export type NormalizedCSSTransitionConfig = { settings: Record; }; -export type CSSTransitionPropertyUpdates = Record; +export type CSSTransitionPropertyUpdates = Record< + string, + [unknown, unknown] | null +>; export type NormalizedNewCSSTransitionConfig = { properties: CSSTransitionPropertyUpdates; diff --git a/packages/react-native-reanimated/src/css/types/interfaces.ts b/packages/react-native-reanimated/src/css/types/interfaces.ts index 432a480eeabb..6fbd00f583d6 100644 --- a/packages/react-native-reanimated/src/css/types/interfaces.ts +++ b/packages/react-native-reanimated/src/css/types/interfaces.ts @@ -1,6 +1,6 @@ 'use strict'; +import type { UnknownRecord } from '../../common'; import type { ExistingCSSAnimationProperties } from './animation'; -import type { UnknownRecord } from '../common'; import type { CSSTransitionProperties } from './transition'; export interface ICSSAnimationsManager { @@ -17,6 +17,6 @@ export interface ICSSTransitionsManager { } export interface ICSSManager { - update(style: CSSStyle | null): void; + update(style: UnknownRecord | null): void; unmountCleanup(): void; } diff --git a/packages/react-native-reanimated/src/css/utils/props.ts b/packages/react-native-reanimated/src/css/utils/props.ts index 0a5a8cbb962a..1cdc1156e7a8 100644 --- a/packages/react-native-reanimated/src/css/utils/props.ts +++ b/packages/react-native-reanimated/src/css/utils/props.ts @@ -104,24 +104,21 @@ function hasValue(value: unknown): boolean { export function getChangedProps( previousStyle: UnknownRecord | null, nextStyle: UnknownRecord | null, - allowedProperties: string[] | 'all' + allowedProperties?: string[] ): Record | null { if (!previousStyle || !nextStyle) { return null; } - const monitoredProperties = Array.isArray(allowedProperties) - ? allowedProperties - : Array.from( - new Set([ - ...Object.keys(previousStyle), - ...Object.keys(nextStyle), - ]) - ); + const allowedPropertiesArray = + allowedProperties ?? + Array.from( + new Set([...Object.keys(previousStyle), ...Object.keys(nextStyle)]) + ); const diff: Record = {}; - for (const property of monitoredProperties) { + for (const property of allowedPropertiesArray) { const nextValue = nextStyle[property]; const prevValue = previousStyle[property]; From c6e93eee88636fc6a97453d1d8cfcfce864d1bac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20=C5=81opaci=C5=84ski?= Date: Sat, 29 Nov 2025 01:41:24 +0100 Subject: [PATCH 10/13] More progress --- .../CSS/interpolation/InterpolatorFactory.h | 6 ----- .../interpolation/PropertyInterpolator.cpp | 2 +- .../CSS/interpolation/PropertyInterpolator.h | 2 +- .../groups/ArrayPropertiesInterpolator.cpp | 9 +++++++ .../groups/ArrayPropertiesInterpolator.h | 1 + .../groups/GroupPropertiesInterpolator.h | 1 + .../groups/RecordPropertiesInterpolator.cpp | 9 +++++++ .../groups/RecordPropertiesInterpolator.h | 1 + .../styles/TransitionStyleInterpolator.cpp | 7 +++++- .../values/ResolvableValueInterpolator.h | 2 ++ .../values/SimpleValueInterpolator.cpp | 5 ++++ .../values/SimpleValueInterpolator.h | 2 ++ .../progress/TransitionProgressProvider.cpp | 24 +++++-------------- .../CSS/progress/TransitionProgressProvider.h | 8 ++----- .../CSS/registries/CSSTransitionsRegistry.cpp | 3 ++- 15 files changed, 48 insertions(+), 34 deletions(-) diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/InterpolatorFactory.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/InterpolatorFactory.h index f19515ff2e00..8d8fe063f5bf 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/InterpolatorFactory.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/InterpolatorFactory.h @@ -28,12 +28,6 @@ class SimpleValueInterpolatorFactory : public PropertyInterpolatorFactory { explicit SimpleValueInterpolatorFactory(const TValue &defaultValue) : PropertyInterpolatorFactory(), defaultValue_(defaultValue) {} - bool isDiscreteProperty() const override { - // The property is considered discrete if all of the allowed types are - // discrete - return (Discrete && ...); - } - const CSSValue &getDefaultValue() const override { return defaultValue_; } diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/PropertyInterpolator.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/PropertyInterpolator.cpp index 34fd6922f490..f4eefe4904ef 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/PropertyInterpolator.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/PropertyInterpolator.cpp @@ -12,7 +12,7 @@ PropertyInterpolator::PropertyInterpolator( const std::shared_ptr &viewStylesRepository) : propertyPath_(std::move(propertyPath)), viewStylesRepository_(viewStylesRepository) {} -bool PropertyInterpolatorFactory::isDiscreteProperty() const { +bool PropertyInterpolator::isDiscrete() const { return false; } diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/PropertyInterpolator.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/PropertyInterpolator.h index 63436d8f0103..a1933e3a8b4c 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/PropertyInterpolator.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/PropertyInterpolator.h @@ -19,6 +19,7 @@ class PropertyInterpolator { PropertyPath propertyPath, const std::shared_ptr &viewStylesRepository); + virtual bool isDiscrete() const = 0; virtual folly::dynamic getStyleValue(const std::shared_ptr &shadowNode) const = 0; virtual folly::dynamic getResetStyle(const std::shared_ptr &shadowNode) const = 0; virtual folly::dynamic getFirstKeyframeValue() const = 0; @@ -48,7 +49,6 @@ class PropertyInterpolatorFactory { PropertyInterpolatorFactory() = default; virtual ~PropertyInterpolatorFactory() = default; - virtual bool isDiscreteProperty() const; virtual const CSSValue &getDefaultValue() const = 0; virtual std::shared_ptr create( diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/ArrayPropertiesInterpolator.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/ArrayPropertiesInterpolator.cpp index cd9b635e2d49..48c616ea7d5d 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/ArrayPropertiesInterpolator.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/ArrayPropertiesInterpolator.cpp @@ -11,6 +11,15 @@ ArrayPropertiesInterpolator::ArrayPropertiesInterpolator( const std::shared_ptr &viewStylesRepository) : GroupPropertiesInterpolator(propertyPath, viewStylesRepository), factories_(factories) {} +bool ArrayPropertiesInterpolator::isDiscrete() const { + for (const auto &interpolator : interpolators_) { + if (!interpolator->isDiscrete()) { + return false; + } + } + return true; +} + void ArrayPropertiesInterpolator::updateKeyframes(jsi::Runtime &rt, const jsi::Value &keyframes) { const jsi::Array keyframesArray = keyframes.asObject(rt).asArray(rt); const size_t valuesCount = keyframesArray.size(rt); diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/ArrayPropertiesInterpolator.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/ArrayPropertiesInterpolator.h index baba29a8e9d3..ec147196d131 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/ArrayPropertiesInterpolator.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/ArrayPropertiesInterpolator.h @@ -15,6 +15,7 @@ class ArrayPropertiesInterpolator : public GroupPropertiesInterpolator { const std::shared_ptr &viewStylesRepository); virtual ~ArrayPropertiesInterpolator() = default; + bool isDiscrete() const override; void updateKeyframes(jsi::Runtime &rt, const jsi::Value &keyframes) override; bool updateKeyframesFromStyleChange( jsi::Runtime &rt, diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/GroupPropertiesInterpolator.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/GroupPropertiesInterpolator.h index 1969da673f58..8d13cf18c002 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/GroupPropertiesInterpolator.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/GroupPropertiesInterpolator.h @@ -14,6 +14,7 @@ class GroupPropertiesInterpolator : public PropertyInterpolator { const PropertyPath &propertyPath, const std::shared_ptr &viewStylesRepository); + bool isDiscrete() const override; folly::dynamic getStyleValue(const std::shared_ptr &shadowNode) const override; folly::dynamic getResetStyle(const std::shared_ptr &shadowNode) const override; folly::dynamic getFirstKeyframeValue() const override; diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/RecordPropertiesInterpolator.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/RecordPropertiesInterpolator.cpp index a8368551cfc1..c7a5d1b5102e 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/RecordPropertiesInterpolator.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/RecordPropertiesInterpolator.cpp @@ -12,6 +12,15 @@ RecordPropertiesInterpolator::RecordPropertiesInterpolator( const std::shared_ptr &viewStylesRepository) : GroupPropertiesInterpolator(propertyPath, viewStylesRepository), factories_(factories) {} +bool RecordPropertiesInterpolator::isDiscrete() const { + for (const auto &[propertyName, interpolator] : interpolators_) { + if (!interpolator->isDiscrete()) { + return false; + } + } + return true; +} + void RecordPropertiesInterpolator::updateKeyframes(jsi::Runtime &rt, const jsi::Value &keyframes) { // TODO - maybe add a possibility to remove interpolators that are no longer // used (for now, for simplicity, we only add new ones) diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/RecordPropertiesInterpolator.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/RecordPropertiesInterpolator.h index 964234a53dd4..c5750a1c4808 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/RecordPropertiesInterpolator.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/RecordPropertiesInterpolator.h @@ -16,6 +16,7 @@ class RecordPropertiesInterpolator : public GroupPropertiesInterpolator { const std::shared_ptr &viewStylesRepository); virtual ~RecordPropertiesInterpolator() = default; + bool isDiscrete() const override; void updateKeyframes(jsi::Runtime &rt, const jsi::Value &keyframes) override; bool updateKeyframesFromStyleChange( jsi::Runtime &rt, diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/styles/TransitionStyleInterpolator.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/styles/TransitionStyleInterpolator.cpp index decdb04945f6..930eadc0b540 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/styles/TransitionStyleInterpolator.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/styles/TransitionStyleInterpolator.cpp @@ -57,6 +57,8 @@ std::unordered_set TransitionStyleInterpolator::updateInterpolatedP std::unordered_set reversedPropertyNames; for (const auto &[propertyName, diffPair] : propertyUpdates) { + // If the diffPair is not present, it means that the property was removed and should no + // longer be transitioned. In such a case, we can remove the interpolator for this property. if (!diffPair.has_value()) { interpolators_.erase(propertyName); continue; @@ -69,7 +71,10 @@ std::unordered_set TransitionStyleInterpolator::updateInterpolatedP it = interpolators_.emplace(propertyName, newInterpolator).first; } - reversedPropertyNames.insert(it->second->updateKeyframesFromStyleChange(rt, diffPair->first, diffPair->second)); + auto isPropertyReversed = it->second->updateKeyframesFromStyleChange(rt, diffPair->first, diffPair->second); + if (isPropertyReversed) { + reversedPropertyNames.insert(propertyName); + } } return reversedPropertyNames; diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/values/ResolvableValueInterpolator.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/values/ResolvableValueInterpolator.h index 8bca92e4a396..d252bc1ebc9f 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/values/ResolvableValueInterpolator.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/values/ResolvableValueInterpolator.h @@ -29,6 +29,8 @@ class ResolvableValueInterpolator final : public SimpleValueInterpolator &viewStylesRepository, const ResolvableValueInterpolatorConfig &config); + bool isDiscrete() const override; + protected: folly::dynamic interpolateValue( double progress, diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/values/SimpleValueInterpolator.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/values/SimpleValueInterpolator.cpp index 2e868ccc2784..dc476784dfb6 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/values/SimpleValueInterpolator.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/values/SimpleValueInterpolator.cpp @@ -26,6 +26,11 @@ SimpleValueInterpolator::SimpleValueInterpolator( const std::shared_ptr &viewStylesRepository) : ValueInterpolator(propertyPath, std::make_shared(defaultStyleValue), viewStylesRepository) {} +template +bool SimpleValueInterpolator::isDiscrete() const { + return (Discrete && ...); +} + template std::shared_ptr SimpleValueInterpolator::createValue( jsi::Runtime &rt, diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/values/SimpleValueInterpolator.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/values/SimpleValueInterpolator.h index 27de1b1d2b7d..7ca7c85dbd21 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/values/SimpleValueInterpolator.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/values/SimpleValueInterpolator.h @@ -25,6 +25,8 @@ class SimpleValueInterpolator : public ValueInterpolator { const ValueType &defaultStyleValue, const std::shared_ptr &viewStylesRepository); + bool isDiscrete() const override; + protected: std::shared_ptr createValue(jsi::Runtime &rt, const jsi::Value &value) const override; std::shared_ptr createValue(const folly::dynamic &value) const override; diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/progress/TransitionProgressProvider.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/progress/TransitionProgressProvider.cpp index e836f3a92d32..1bf4db620b14 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/progress/TransitionProgressProvider.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/progress/TransitionProgressProvider.cpp @@ -12,20 +12,17 @@ TransitionPropertyProgressProvider::TransitionPropertyProgressProvider( const double timestamp, const double duration, const double delay, - const EasingFunction &easingFunction, - const bool allowDiscrete) - : RawProgressProvider(timestamp, duration, delay), easingFunction_(easingFunction), allowDiscrete_(allowDiscrete) {} + const EasingFunction &easingFunction) + : RawProgressProvider(timestamp, duration, delay), easingFunction_(easingFunction) {} TransitionPropertyProgressProvider::TransitionPropertyProgressProvider( const double timestamp, const double duration, const double delay, const EasingFunction &easingFunction, - const double reversingShorteningFactor, - const bool allowDiscrete) + const double reversingShorteningFactor) : RawProgressProvider(timestamp, duration, delay), easingFunction_(easingFunction), - reversingShorteningFactor_(reversingShorteningFactor), - allowDiscrete_(allowDiscrete) {} + reversingShorteningFactor_(reversingShorteningFactor) {} double TransitionPropertyProgressProvider::getGlobalProgress() const { return rawProgress_.value_or(0); @@ -57,10 +54,6 @@ TransitionProgressState TransitionPropertyProgressProvider::getState() const { return TransitionProgressState::Running; } -double TransitionPropertyProgressProvider::getFallbackInterpolateThreshold(const bool isDiscrete) const { - return !isDiscrete || allowDiscrete_ ? 0.5 : 0.0; -} - std::optional TransitionPropertyProgressProvider::calculateRawProgress(const double timestamp) { if (duration_ == 0) { return 1; @@ -159,11 +152,7 @@ void TransitionProgressProvider::runProgressProviders( propertyProgressProviders_.insert_or_assign( propertyName, std::make_shared( - timestamp, - propertySettings.duration, - propertySettings.delay, - propertySettings.easingFunction, - propertySettings.allowDiscrete)); + timestamp, propertySettings.duration, propertySettings.delay, propertySettings.easingFunction)); } } @@ -192,8 +181,7 @@ TransitionProgressProvider::createReversingShorteningProgressProvider( propertySettings.duration * newReversingShorteningFactor, propertySettings.delay < 0 ? newReversingShorteningFactor * propertySettings.delay : propertySettings.delay, propertySettings.easingFunction, - newReversingShorteningFactor, - propertySettings.allowDiscrete); + newReversingShorteningFactor); } } // namespace reanimated::css diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/progress/TransitionProgressProvider.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/progress/TransitionProgressProvider.h index 27c40e753590..da77e729392b 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/progress/TransitionProgressProvider.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/progress/TransitionProgressProvider.h @@ -19,22 +19,19 @@ class TransitionPropertyProgressProvider final : public KeyframeProgressProvider double timestamp, double duration, double delay, - const EasingFunction &easingFunction, - bool allowDiscrete); + const EasingFunction &easingFunction); TransitionPropertyProgressProvider( double timestamp, double duration, double delay, const EasingFunction &easingFunction, - double reversingShorteningFactor, - bool allowDiscrete); + double reversingShorteningFactor); double getGlobalProgress() const override; double getKeyframeProgress(double fromOffset, double toOffset) const override; double getRemainingDelay(double timestamp) const; double getReversingShorteningFactor() const; TransitionProgressState getState() const; - double getFallbackInterpolateThreshold(bool isDiscrete) const; protected: std::optional calculateRawProgress(double timestamp) override; @@ -42,7 +39,6 @@ class TransitionPropertyProgressProvider final : public KeyframeProgressProvider private: EasingFunction easingFunction_; double reversingShorteningFactor_ = 1; - bool allowDiscrete_ = false; double getElapsedTime(double timestamp) const; }; diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.cpp index 55a27c15a17a..e5b321c030ed 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.cpp @@ -139,7 +139,6 @@ void CSSTransitionsRegistry::updateInUpdatesRegistry( setInUpdatesRegistry(shadowNode, filteredUpdates); } -} // namespace reanimated::css void CSSTransitionsRegistry::runTransition( jsi::Runtime &rt, const std::shared_ptr &transition, @@ -148,3 +147,5 @@ void CSSTransitionsRegistry::runTransition( updateInUpdatesRegistry(transition, startStyle); scheduleOrActivateTransition(transition); } + +} // namespace reanimated::css From bf174cefecd3c26bd1e478b92e3dfeb8f1b56c2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20=C5=81opaci=C5=84ski?= Date: Wed, 3 Dec 2025 23:18:18 +0100 Subject: [PATCH 11/13] Make it work --- .../CSS/configs/CSSTransitionConfig.h | 11 +++ .../cpp/reanimated/CSS/core/CSSTransition.cpp | 30 +++++--- .../cpp/reanimated/CSS/core/CSSTransition.h | 7 +- .../CSS/interpolation/InterpolatorFactory.cpp | 18 +++++ .../CSS/interpolation/InterpolatorFactory.h | 8 +++ .../interpolation/PropertyInterpolator.cpp | 4 -- .../CSS/interpolation/PropertyInterpolator.h | 2 +- .../groups/ArrayPropertiesInterpolator.cpp | 9 --- .../groups/ArrayPropertiesInterpolator.h | 1 - .../groups/GroupPropertiesInterpolator.h | 1 - .../groups/RecordPropertiesInterpolator.cpp | 9 --- .../groups/RecordPropertiesInterpolator.h | 1 - .../styles/TransitionStyleInterpolator.cpp | 69 ++++++++++++++----- .../styles/TransitionStyleInterpolator.h | 13 +++- .../values/ResolvableValueInterpolator.h | 2 - .../values/SimpleValueInterpolator.cpp | 5 -- .../values/SimpleValueInterpolator.h | 2 - .../progress/TransitionProgressProvider.cpp | 50 ++++++++++---- .../CSS/progress/TransitionProgressProvider.h | 12 ++-- .../CSS/registries/CSSTransitionsRegistry.cpp | 14 ++-- .../CSS/registries/CSSTransitionsRegistry.h | 3 +- .../NativeModules/ReanimatedModuleProxy.cpp | 4 +- .../native/managers/CSSTransitionsManager.ts | 13 ++-- 23 files changed, 189 insertions(+), 99 deletions(-) diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/configs/CSSTransitionConfig.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/configs/CSSTransitionConfig.h index 417911fb3e0d..d75f3882e22b 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/configs/CSSTransitionConfig.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/configs/CSSTransitionConfig.h @@ -11,6 +11,17 @@ namespace reanimated::css { +enum class TransitionPropertyStatus : uint8_t { + Updated, + Removed, + Reversed, +}; + +struct TransitionPropertyUpdate { + std::string name; + TransitionPropertyStatus status; +}; + struct CSSTransitionPropertySettings { double duration; EasingFunction easingFunction; diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/core/CSSTransition.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/core/CSSTransition.cpp index 4dbd837d00c0..44c6892d1b92 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/core/CSSTransition.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/core/CSSTransition.cpp @@ -1,8 +1,9 @@ #include #include +#include #include -#include +#include #include #include @@ -49,18 +50,27 @@ void CSSTransition::updateSettings(const CSSTransitionPropertySettingsUpdates &s } } -folly::dynamic -CSSTransition::run(jsi::Runtime &rt, const CSSTransitionPropertyUpdates &propertyUpdates, const double timestamp) { - const auto reversedPropertyNames = styleInterpolator_.updateInterpolatedProperties(rt, propertyUpdates); - - std::vector propertyNames; - propertyNames.reserve(propertyUpdates.size()); - for (const auto &[key, value] : propertyUpdates) { - propertyNames.emplace_back(key); +std::optional> CSSTransition::getProperties() const { + const auto allIt = settings_.find("all"); + if (allIt != settings_.end()) { + return std::nullopt; // "all" means no specific properties } - progressProvider_.runProgressProviders(timestamp, settings_, propertyNames, reversedPropertyNames); + std::unordered_set properties; + for (const auto &[property, _] : settings_) { + properties.insert(property); + } + return properties; +} +folly::dynamic CSSTransition::run( + jsi::Runtime &rt, + const CSSTransitionPropertyUpdates &propertyUpdates, + const jsi::Value &lastUpdates, + const double timestamp) { + const auto updatedProperties = + styleInterpolator_.updateInterpolatedProperties(rt, propertyUpdates, lastUpdates, settings_); + progressProvider_.runProgressProviders(timestamp, updatedProperties, settings_); return update(timestamp); } diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/core/CSSTransition.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/core/CSSTransition.h index d8ac0bf3cb10..05be4a49b9ad 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/core/CSSTransition.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/core/CSSTransition.h @@ -22,7 +22,12 @@ class CSSTransition { double getMinDelay(double timestamp) const; TransitionProgressState getState() const; void updateSettings(const CSSTransitionPropertySettingsUpdates &settingsUpdates); - folly::dynamic run(jsi::Runtime &rt, const CSSTransitionPropertyUpdates &propertyUpdates, double timestamp); + std::optional> getProperties() const; + folly::dynamic run( + jsi::Runtime &rt, + const CSSTransitionPropertyUpdates &propertyUpdates, + const jsi::Value &lastUpdates, + double timestamp); folly::dynamic update(double timestamp); private: diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/InterpolatorFactory.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/InterpolatorFactory.cpp index 1e86264b81d1..f64999b76e91 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/InterpolatorFactory.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/InterpolatorFactory.cpp @@ -1,6 +1,7 @@ #include #include +#include #include #include #include @@ -12,6 +13,11 @@ class RecordInterpolatorFactory : public PropertyInterpolatorFactory { explicit RecordInterpolatorFactory(const InterpolatorFactoriesRecord &factories) : PropertyInterpolatorFactory(), factories_(factories) {} + bool isDiscrete() const override { + return std::all_of( + factories_.begin(), factories_.end(), [](const auto &pair) { return pair.second->isDiscrete(); }); + } + const CSSValue &getDefaultValue() const override { static EmptyObjectValue emptyObjectValue; return emptyObjectValue; @@ -63,6 +69,10 @@ class ArrayInterpolatorFactory : public ArrayLikeInterpolatorFactory { explicit ArrayInterpolatorFactory(const InterpolatorFactoriesArray &factories) : ArrayLikeInterpolatorFactory(), factories_(factories) {} + bool isDiscrete() const override { + return std::all_of(factories_.begin(), factories_.end(), [](const auto &factory) { return factory->isDiscrete(); }); + } + std::shared_ptr create( const PropertyPath &propertyPath, const std::shared_ptr &viewStylesRepository) const override { @@ -78,6 +88,10 @@ class FiltersInterpolatorFactory : public ArrayLikeInterpolatorFactory { explicit FiltersInterpolatorFactory(const std::shared_ptr &interpolators) : ArrayLikeInterpolatorFactory(), interpolators_(interpolators) {} + bool isDiscrete() const override { + return false; + } + std::shared_ptr create( const PropertyPath &propertyPath, const std::shared_ptr &viewStylesRepository) const override { @@ -93,6 +107,10 @@ class TransformsInterpolatorFactory : public PropertyInterpolatorFactory { explicit TransformsInterpolatorFactory(const std::shared_ptr &interpolators) : PropertyInterpolatorFactory(), interpolators_(interpolators) {} + bool isDiscrete() const override { + return false; + } + const CSSValue &getDefaultValue() const override { static EmptyTransformsValue emptyTransformsValue; return emptyTransformsValue; diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/InterpolatorFactory.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/InterpolatorFactory.h index 8d8fe063f5bf..085ef228a537 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/InterpolatorFactory.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/InterpolatorFactory.h @@ -28,6 +28,10 @@ class SimpleValueInterpolatorFactory : public PropertyInterpolatorFactory { explicit SimpleValueInterpolatorFactory(const TValue &defaultValue) : PropertyInterpolatorFactory(), defaultValue_(defaultValue) {} + bool isDiscrete() const override { + return (Discrete && ...); + } + const CSSValue &getDefaultValue() const override { return defaultValue_; } @@ -50,6 +54,10 @@ class ResolvableValueInterpolatorFactory : public PropertyInterpolatorFactory { explicit ResolvableValueInterpolatorFactory(const TValue &defaultValue, ResolvableValueInterpolatorConfig config) : PropertyInterpolatorFactory(), defaultValue_(defaultValue), config_(std::move(config)) {} + bool isDiscrete() const override { + return (Discrete && ...); + } + const CSSValue &getDefaultValue() const override { return defaultValue_; } diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/PropertyInterpolator.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/PropertyInterpolator.cpp index f4eefe4904ef..f07632ed5d1e 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/PropertyInterpolator.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/PropertyInterpolator.cpp @@ -12,10 +12,6 @@ PropertyInterpolator::PropertyInterpolator( const std::shared_ptr &viewStylesRepository) : propertyPath_(std::move(propertyPath)), viewStylesRepository_(viewStylesRepository) {} -bool PropertyInterpolator::isDiscrete() const { - return false; -} - std::string PropertyInterpolator::getPropertyPathString() const { if (propertyPath_.empty()) { return ""; diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/PropertyInterpolator.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/PropertyInterpolator.h index a1933e3a8b4c..ab712c1badec 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/PropertyInterpolator.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/PropertyInterpolator.h @@ -19,7 +19,6 @@ class PropertyInterpolator { PropertyPath propertyPath, const std::shared_ptr &viewStylesRepository); - virtual bool isDiscrete() const = 0; virtual folly::dynamic getStyleValue(const std::shared_ptr &shadowNode) const = 0; virtual folly::dynamic getResetStyle(const std::shared_ptr &shadowNode) const = 0; virtual folly::dynamic getFirstKeyframeValue() const = 0; @@ -49,6 +48,7 @@ class PropertyInterpolatorFactory { PropertyInterpolatorFactory() = default; virtual ~PropertyInterpolatorFactory() = default; + virtual bool isDiscrete() const = 0; virtual const CSSValue &getDefaultValue() const = 0; virtual std::shared_ptr create( diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/ArrayPropertiesInterpolator.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/ArrayPropertiesInterpolator.cpp index 48c616ea7d5d..cd9b635e2d49 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/ArrayPropertiesInterpolator.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/ArrayPropertiesInterpolator.cpp @@ -11,15 +11,6 @@ ArrayPropertiesInterpolator::ArrayPropertiesInterpolator( const std::shared_ptr &viewStylesRepository) : GroupPropertiesInterpolator(propertyPath, viewStylesRepository), factories_(factories) {} -bool ArrayPropertiesInterpolator::isDiscrete() const { - for (const auto &interpolator : interpolators_) { - if (!interpolator->isDiscrete()) { - return false; - } - } - return true; -} - void ArrayPropertiesInterpolator::updateKeyframes(jsi::Runtime &rt, const jsi::Value &keyframes) { const jsi::Array keyframesArray = keyframes.asObject(rt).asArray(rt); const size_t valuesCount = keyframesArray.size(rt); diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/ArrayPropertiesInterpolator.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/ArrayPropertiesInterpolator.h index ec147196d131..baba29a8e9d3 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/ArrayPropertiesInterpolator.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/ArrayPropertiesInterpolator.h @@ -15,7 +15,6 @@ class ArrayPropertiesInterpolator : public GroupPropertiesInterpolator { const std::shared_ptr &viewStylesRepository); virtual ~ArrayPropertiesInterpolator() = default; - bool isDiscrete() const override; void updateKeyframes(jsi::Runtime &rt, const jsi::Value &keyframes) override; bool updateKeyframesFromStyleChange( jsi::Runtime &rt, diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/GroupPropertiesInterpolator.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/GroupPropertiesInterpolator.h index 8d13cf18c002..1969da673f58 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/GroupPropertiesInterpolator.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/GroupPropertiesInterpolator.h @@ -14,7 +14,6 @@ class GroupPropertiesInterpolator : public PropertyInterpolator { const PropertyPath &propertyPath, const std::shared_ptr &viewStylesRepository); - bool isDiscrete() const override; folly::dynamic getStyleValue(const std::shared_ptr &shadowNode) const override; folly::dynamic getResetStyle(const std::shared_ptr &shadowNode) const override; folly::dynamic getFirstKeyframeValue() const override; diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/RecordPropertiesInterpolator.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/RecordPropertiesInterpolator.cpp index c7a5d1b5102e..a8368551cfc1 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/RecordPropertiesInterpolator.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/RecordPropertiesInterpolator.cpp @@ -12,15 +12,6 @@ RecordPropertiesInterpolator::RecordPropertiesInterpolator( const std::shared_ptr &viewStylesRepository) : GroupPropertiesInterpolator(propertyPath, viewStylesRepository), factories_(factories) {} -bool RecordPropertiesInterpolator::isDiscrete() const { - for (const auto &[propertyName, interpolator] : interpolators_) { - if (!interpolator->isDiscrete()) { - return false; - } - } - return true; -} - void RecordPropertiesInterpolator::updateKeyframes(jsi::Runtime &rt, const jsi::Value &keyframes) { // TODO - maybe add a possibility to remove interpolators that are no longer // used (for now, for simplicity, we only add new ones) diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/RecordPropertiesInterpolator.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/RecordPropertiesInterpolator.h index c5750a1c4808..964234a53dd4 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/RecordPropertiesInterpolator.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/groups/RecordPropertiesInterpolator.h @@ -16,7 +16,6 @@ class RecordPropertiesInterpolator : public GroupPropertiesInterpolator { const std::shared_ptr &viewStylesRepository); virtual ~RecordPropertiesInterpolator() = default; - bool isDiscrete() const override; void updateKeyframes(jsi::Runtime &rt, const jsi::Value &keyframes) override; bool updateKeyframesFromStyleChange( jsi::Runtime &rt, diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/styles/TransitionStyleInterpolator.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/styles/TransitionStyleInterpolator.cpp index 930eadc0b540..cefc31b3c322 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/styles/TransitionStyleInterpolator.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/styles/TransitionStyleInterpolator.cpp @@ -1,6 +1,7 @@ #include #include +#include #include #include @@ -9,7 +10,7 @@ namespace reanimated::css { TransitionStyleInterpolator::TransitionStyleInterpolator( const std::string &componentName, const std::shared_ptr &viewStylesRepository) - : componentName_(componentName), viewStylesRepository_(viewStylesRepository) {} + : viewStylesRepository_(viewStylesRepository), interpolatorFactories_(getComponentInterpolators(componentName)) {} folly::dynamic TransitionStyleInterpolator::interpolate( const std::shared_ptr &shadowNode, @@ -22,10 +23,8 @@ folly::dynamic TransitionStyleInterpolator::interpolate( continue; } - const auto &interpolator = interpolatorIt->second; - const double fallbackInterpolateThreshold = - progressProvider->getFallbackInterpolateThreshold(interpolator->isDiscrete()); - result[propertyName] = interpolator->interpolate(shadowNode, progressProvider, fallbackInterpolateThreshold); + result[propertyName] = interpolatorIt->second->interpolate( + shadowNode, progressProvider, progressProvider->getFallbackInterpolateThreshold()); } return result; @@ -51,33 +50,69 @@ void TransitionStyleInterpolator::discardIrrelevantInterpolators( } } -std::unordered_set TransitionStyleInterpolator::updateInterpolatedProperties( +std::vector TransitionStyleInterpolator::updateInterpolatedProperties( jsi::Runtime &rt, - const CSSTransitionPropertyUpdates &propertyUpdates) { - std::unordered_set reversedPropertyNames; + const CSSTransitionPropertyUpdates &propertyUpdates, + const jsi::Value &lastUpdates, + const CSSTransitionPropertiesSettings &settings) { + std::vector result; + + // Check if lastUpdates is null or undefined (converted from empty folly::dynamic()) + const bool hasLastUpdates = !lastUpdates.isNull() && !lastUpdates.isUndefined(); + const jsi::Object lastUpdatesObject = hasLastUpdates ? lastUpdates.asObject(rt) : jsi::Object(rt); for (const auto &[propertyName, diffPair] : propertyUpdates) { - // If the diffPair is not present, it means that the property was removed and should no - // longer be transitioned. In such a case, we can remove the interpolator for this property. - if (!diffPair.has_value()) { + if (!diffPair.has_value() || !isAllowedProperty(propertyName, settings)) { + // If the diffPair is not present (this means that the property was removed and should no + // longer be transitioned) or if the property is not allowed to be transitioned, we should + // remove the interpolator for this property. interpolators_.erase(propertyName); + result.emplace_back(TransitionPropertyUpdate{propertyName, TransitionPropertyStatus::Removed}); continue; } auto it = interpolators_.find(propertyName); if (it == interpolators_.end()) { - const auto newInterpolator = createPropertyInterpolator( - propertyName, {}, getComponentInterpolators(componentName_), viewStylesRepository_); + const auto newInterpolator = + createPropertyInterpolator(propertyName, {}, interpolatorFactories_, viewStylesRepository_); it = interpolators_.emplace(propertyName, newInterpolator).first; } - auto isPropertyReversed = it->second->updateKeyframesFromStyleChange(rt, diffPair->first, diffPair->second); - if (isPropertyReversed) { - reversedPropertyNames.insert(propertyName); + // Try to get the last update value from the lastUpdates object + // If lastUpdates is null/undefined (from empty folly::dynamic()), use diffPair->first + std::optional lastUpdateValueOpt; + if (hasLastUpdates) { + auto lastUpdateValue = lastUpdatesObject.getProperty(rt, propertyName.c_str()); + if (!lastUpdateValue.isUndefined()) { + lastUpdateValueOpt.emplace(std::move(lastUpdateValue)); + } } + + const jsi::Value &oldStyleValue = lastUpdateValueOpt.has_value() ? *lastUpdateValueOpt : diffPair->first; + auto isPropertyReversed = it->second->updateKeyframesFromStyleChange(rt, oldStyleValue, diffPair->second); + const auto status = isPropertyReversed ? TransitionPropertyStatus::Reversed : TransitionPropertyStatus::Updated; + result.emplace_back(TransitionPropertyUpdate{propertyName, status}); + } + + return result; +} + +bool TransitionStyleInterpolator::isAllowedProperty( + const std::string &propertyName, + const CSSTransitionPropertiesSettings &settings) const { + const auto it = interpolatorFactories_.find(propertyName); + if (it == interpolatorFactories_.end()) { + return false; + } + + // Non-discrete properties are always allowed + if (!it->second->isDiscrete()) { + return true; } - return reversedPropertyNames; + // Discrete properties require allowDiscrete to be true + const auto propertySettings = getTransitionPropertySettings(settings, propertyName); + return propertySettings.has_value() && propertySettings->allowDiscrete; } } // namespace reanimated::css diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/styles/TransitionStyleInterpolator.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/styles/TransitionStyleInterpolator.h index cff3fa6fe1a0..32794a927c7e 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/styles/TransitionStyleInterpolator.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/styles/TransitionStyleInterpolator.h @@ -2,13 +2,16 @@ #include #include +#include #include #include +#include #include #include #include #include +#include namespace reanimated::css { @@ -26,17 +29,21 @@ class TransitionStyleInterpolator { void discardFinishedInterpolators(const TransitionProgressProvider &transitionProgressProvider); void discardIrrelevantInterpolators(const std::unordered_set &transitionPropertyNames); - std::unordered_set updateInterpolatedProperties( + std::vector updateInterpolatedProperties( jsi::Runtime &rt, - const CSSTransitionPropertyUpdates &propertyUpdates); + const CSSTransitionPropertyUpdates &propertyUpdates, + const jsi::Value &lastUpdates, + const CSSTransitionPropertiesSettings &settings); private: + bool isAllowedProperty(const std::string &propertyName, const CSSTransitionPropertiesSettings &settings) const; + using MapInterpolatorsCallback = std::function< folly::dynamic(const std::shared_ptr &, const std::shared_ptr &)>; - const std::string componentName_; const std::shared_ptr viewStylesRepository_; + const InterpolatorFactoriesRecord &interpolatorFactories_; PropertyInterpolatorsRecord interpolators_; }; diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/values/ResolvableValueInterpolator.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/values/ResolvableValueInterpolator.h index d252bc1ebc9f..8bca92e4a396 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/values/ResolvableValueInterpolator.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/values/ResolvableValueInterpolator.h @@ -29,8 +29,6 @@ class ResolvableValueInterpolator final : public SimpleValueInterpolator &viewStylesRepository, const ResolvableValueInterpolatorConfig &config); - bool isDiscrete() const override; - protected: folly::dynamic interpolateValue( double progress, diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/values/SimpleValueInterpolator.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/values/SimpleValueInterpolator.cpp index dc476784dfb6..2e868ccc2784 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/values/SimpleValueInterpolator.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/values/SimpleValueInterpolator.cpp @@ -26,11 +26,6 @@ SimpleValueInterpolator::SimpleValueInterpolator( const std::shared_ptr &viewStylesRepository) : ValueInterpolator(propertyPath, std::make_shared(defaultStyleValue), viewStylesRepository) {} -template -bool SimpleValueInterpolator::isDiscrete() const { - return (Discrete && ...); -} - template std::shared_ptr SimpleValueInterpolator::createValue( jsi::Runtime &rt, diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/values/SimpleValueInterpolator.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/values/SimpleValueInterpolator.h index 7ca7c85dbd21..27de1b1d2b7d 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/values/SimpleValueInterpolator.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/values/SimpleValueInterpolator.h @@ -25,8 +25,6 @@ class SimpleValueInterpolator : public ValueInterpolator { const ValueType &defaultStyleValue, const std::shared_ptr &viewStylesRepository); - bool isDiscrete() const override; - protected: std::shared_ptr createValue(jsi::Runtime &rt, const jsi::Value &value) const override; std::shared_ptr createValue(const folly::dynamic &value) const override; diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/progress/TransitionProgressProvider.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/progress/TransitionProgressProvider.cpp index 1bf4db620b14..2cf8d65fde05 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/progress/TransitionProgressProvider.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/progress/TransitionProgressProvider.cpp @@ -12,17 +12,20 @@ TransitionPropertyProgressProvider::TransitionPropertyProgressProvider( const double timestamp, const double duration, const double delay, - const EasingFunction &easingFunction) - : RawProgressProvider(timestamp, duration, delay), easingFunction_(easingFunction) {} + const EasingFunction &easingFunction, + const bool allowDiscrete) + : RawProgressProvider(timestamp, duration, delay), easingFunction_(easingFunction), allowDiscrete_(allowDiscrete) {} TransitionPropertyProgressProvider::TransitionPropertyProgressProvider( const double timestamp, const double duration, const double delay, const EasingFunction &easingFunction, + const bool allowDiscrete, const double reversingShorteningFactor) : RawProgressProvider(timestamp, duration, delay), easingFunction_(easingFunction), - reversingShorteningFactor_(reversingShorteningFactor) {} + reversingShorteningFactor_(reversingShorteningFactor), + allowDiscrete_(allowDiscrete) {} double TransitionPropertyProgressProvider::getGlobalProgress() const { return rawProgress_.value_or(0); @@ -43,6 +46,10 @@ double TransitionPropertyProgressProvider::getReversingShorteningFactor() const return reversingShorteningFactor_; } +double TransitionPropertyProgressProvider::getFallbackInterpolateThreshold() const { + return allowDiscrete_ ? 0.5 : 0; +} + TransitionProgressState TransitionPropertyProgressProvider::getState() const { if (!rawProgress_.has_value()) { return TransitionProgressState::Pending; @@ -92,6 +99,10 @@ double TransitionProgressProvider::getMinDelay(const double timestamp) const { return minDelay; } +TransitionPropertyProgressProviders TransitionProgressProvider::getPropertyProgressProviders() const { + return propertyProgressProviders_; +} + std::unordered_set TransitionProgressProvider::getRemovedProperties() const { return removedProperties_; } @@ -121,14 +132,21 @@ void TransitionProgressProvider::discardIrrelevantProgressProviders( void TransitionProgressProvider::runProgressProviders( const double timestamp, - const CSSTransitionPropertiesSettings &propertiesSettings, - const std::vector &changedPropertyNames, - const std::unordered_set &reversedPropertyNames) { - for (const auto &propertyName : changedPropertyNames) { - const auto propertySettingsOptional = getTransitionPropertySettings(propertiesSettings, propertyName); + const std::vector &propertyUpdates, + const CSSTransitionPropertiesSettings &settings) { + for (const auto &propertyUpdate : propertyUpdates) { + const auto &propertyName = propertyUpdate.name; + const auto status = propertyUpdate.status; + + // Handle removed properties + if (status == TransitionPropertyStatus::Removed) { + propertyProgressProviders_.erase(propertyName); + continue; + } + const auto propertySettingsOptional = getTransitionPropertySettings(settings, propertyName); if (!propertySettingsOptional.has_value()) { - throw std::invalid_argument("[Reanimated] Property '" + propertyName + "' is not a valid transition property"); + throw std::runtime_error("[Reanimated] Settings not found for CSS transition property: '" + propertyName + "'"); } const auto &propertySettings = propertySettingsOptional.value(); @@ -138,21 +156,24 @@ void TransitionProgressProvider::runProgressProviders( const auto &progressProvider = it->second; progressProvider->update(timestamp); - if (reversedPropertyNames.contains(propertyName) && + if (status == TransitionPropertyStatus::Reversed && progressProvider->getState() != TransitionProgressState::Finished) { - // Create reversing shortening progress provider for interrupted - // reversing transition + // Create reversing shortening progress provider for interrupted reversing transition propertyProgressProviders_.insert_or_assign( propertyName, createReversingShorteningProgressProvider(timestamp, propertySettings, *progressProvider)); continue; } } - // Create progress provider with the new settings + // Create a new progress provider with the latest settings propertyProgressProviders_.insert_or_assign( propertyName, std::make_shared( - timestamp, propertySettings.duration, propertySettings.delay, propertySettings.easingFunction)); + timestamp, + propertySettings.duration, + propertySettings.delay, + propertySettings.easingFunction, + propertySettings.allowDiscrete)); } } @@ -181,6 +202,7 @@ TransitionProgressProvider::createReversingShorteningProgressProvider( propertySettings.duration * newReversingShorteningFactor, propertySettings.delay < 0 ? newReversingShorteningFactor * propertySettings.delay : propertySettings.delay, propertySettings.easingFunction, + propertySettings.allowDiscrete, newReversingShorteningFactor); } diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/progress/TransitionProgressProvider.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/progress/TransitionProgressProvider.h index da77e729392b..68bd28556945 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/progress/TransitionProgressProvider.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/progress/TransitionProgressProvider.h @@ -8,6 +8,7 @@ #include #include #include +#include namespace reanimated::css { @@ -19,18 +20,21 @@ class TransitionPropertyProgressProvider final : public KeyframeProgressProvider double timestamp, double duration, double delay, - const EasingFunction &easingFunction); + const EasingFunction &easingFunction, + bool allowDiscrete); TransitionPropertyProgressProvider( double timestamp, double duration, double delay, const EasingFunction &easingFunction, + bool allowDiscrete, double reversingShorteningFactor); double getGlobalProgress() const override; double getKeyframeProgress(double fromOffset, double toOffset) const override; double getRemainingDelay(double timestamp) const; double getReversingShorteningFactor() const; + double getFallbackInterpolateThreshold() const; TransitionProgressState getState() const; protected: @@ -39,6 +43,7 @@ class TransitionPropertyProgressProvider final : public KeyframeProgressProvider private: EasingFunction easingFunction_; double reversingShorteningFactor_ = 1; + bool allowDiscrete_; double getElapsedTime(double timestamp) const; }; @@ -57,9 +62,8 @@ class TransitionProgressProvider final { void discardIrrelevantProgressProviders(const std::unordered_set &transitionPropertyNames); void runProgressProviders( double timestamp, - const CSSTransitionPropertiesSettings &propertiesSettings, - const std::vector &changedPropertyNames, - const std::unordered_set &reversedPropertyNames); + const std::vector &propertyUpdates, + const CSSTransitionPropertiesSettings &settings); void update(double timestamp); private: diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.cpp index e5b321c030ed..9c123e990d37 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.cpp @@ -20,13 +20,11 @@ bool CSSTransitionsRegistry::hasUpdates() const { void CSSTransitionsRegistry::add( jsi::Runtime &rt, - std::shared_ptr shadowNode, - const CSSTransitionConfig &config) { - const auto viewTag = shadowNode->getTag(); - - auto transition = std::make_shared(std::move(shadowNode), config, viewStylesRepository_); + std::shared_ptr transition, + const CSSTransitionPropertyUpdates &propertyUpdates) { + const auto viewTag = transition->getShadowNode()->getTag(); registry_.insert({viewTag, transition}); - runTransition(rt, transition, config.properties); + runTransition(rt, transition, propertyUpdates); } void CSSTransitionsRegistry::remove(const Tag viewTag) { @@ -143,7 +141,9 @@ void CSSTransitionsRegistry::runTransition( jsi::Runtime &rt, const std::shared_ptr &transition, const CSSTransitionPropertyUpdates &propertyUpdates) { - const auto startStyle = transition->run(rt, propertyUpdates, getCurrentTimestamp_()); + const auto &lastUpdates = getUpdatesFromRegistry(transition->getShadowNode()->getTag()); + const auto startStyle = + transition->run(rt, propertyUpdates, valueFromDynamic(rt, lastUpdates), getCurrentTimestamp_()); updateInUpdatesRegistry(transition, startStyle); scheduleOrActivateTransition(transition); } diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.h index 86acf31d20ac..553717428b1a 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.h @@ -18,7 +18,8 @@ class CSSTransitionsRegistry : public UpdatesRegistry { bool isEmpty() const override; bool hasUpdates() const; - void add(jsi::Runtime &rt, std::shared_ptr shadowNode, const CSSTransitionConfig &config); + void + add(jsi::Runtime &rt, std::shared_ptr transition, const CSSTransitionPropertyUpdates &propertyUpdates); void update(jsi::Runtime &rt, Tag viewTag, const CSSTransitionUpdates &updates); void remove(Tag viewTag) override; diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp index 6d7dcfbfcf81..045ed54413a6 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp @@ -423,9 +423,11 @@ void ReanimatedModuleProxy::registerCSSTransition( auto shadowNode = shadowNodeFromValue(rt, shadowNodeWrapper); const auto config = parseCSSTransitionConfig(rt, transitionConfig); + auto transition = std::make_shared(std::move(shadowNode), config, viewStylesRepository_); + { auto lock = cssTransitionsRegistry_->lock(); - cssTransitionsRegistry_->add(rt, shadowNode, config); + cssTransitionsRegistry_->add(rt, transition, config.properties); } maybeRunCSSLoop(); diff --git a/packages/react-native-reanimated/src/css/native/managers/CSSTransitionsManager.ts b/packages/react-native-reanimated/src/css/native/managers/CSSTransitionsManager.ts index 1f8b0558be3e..44e13479878d 100644 --- a/packages/react-native-reanimated/src/css/native/managers/CSSTransitionsManager.ts +++ b/packages/react-native-reanimated/src/css/native/managers/CSSTransitionsManager.ts @@ -38,11 +38,11 @@ export default class CSSTransitionsManager implements ICSSTransitionsManager { const previousConfig = this.transitionConfig; this.previousStyle = style; - this.transitionConfig = transitionProperties + const transitionConfig = transitionProperties ? normalizeCSSTransitionProperties(transitionProperties) : null; - if (!this.transitionConfig) { + if (!transitionConfig) { this.detach(); return; } @@ -50,9 +50,9 @@ export default class CSSTransitionsManager implements ICSSTransitionsManager { const propertyDiff = getChangedProps( previousStyle, style, - this.transitionConfig.properties === 'all' + transitionConfig.properties === 'all' ? undefined - : this.transitionConfig.properties + : transitionConfig.properties ); if (!propertyDiff) { @@ -60,13 +60,13 @@ export default class CSSTransitionsManager implements ICSSTransitionsManager { } const settingsDiff = previousConfig - ? this.getSettingsUpdates(this.transitionConfig, previousConfig) + ? this.getSettingsUpdates(previousConfig, transitionConfig) : null; if (!previousConfig) { registerCSSTransition(this.shadowNodeWrapper, { properties: propertyDiff, - settings: this.transitionConfig.settings, + settings: transitionConfig.settings, }); } else { const updates: CSSTransitionUpdates = { @@ -79,6 +79,7 @@ export default class CSSTransitionsManager implements ICSSTransitionsManager { updateCSSTransition(this.viewTag, updates); } + this.transitionConfig = transitionConfig; } unmountCleanup(): void { From 8bd920778644e3dbcc4c152b02707189fa8803c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20=C5=81opaci=C5=84ski?= Date: Thu, 4 Dec 2025 11:59:48 +0000 Subject: [PATCH 12/13] Add proper props removal when transitionProperty changes --- .../native/managers/CSSTransitionsManager.ts | 70 +++++- .../__tests__/CSSTransitionsManager.test.ts | 234 +++++++++++++++--- 2 files changed, 265 insertions(+), 39 deletions(-) diff --git a/packages/react-native-reanimated/src/css/native/managers/CSSTransitionsManager.ts b/packages/react-native-reanimated/src/css/native/managers/CSSTransitionsManager.ts index 44e13479878d..019f89f9f0da 100644 --- a/packages/react-native-reanimated/src/css/native/managers/CSSTransitionsManager.ts +++ b/packages/react-native-reanimated/src/css/native/managers/CSSTransitionsManager.ts @@ -47,12 +47,11 @@ export default class CSSTransitionsManager implements ICSSTransitionsManager { return; } - const propertyDiff = getChangedProps( + const propertyDiff = this.getPropertyDiff( previousStyle, style, - transitionConfig.properties === 'all' - ? undefined - : transitionConfig.properties + previousConfig, + transitionConfig ); if (!propertyDiff) { @@ -94,6 +93,69 @@ export default class CSSTransitionsManager implements ICSSTransitionsManager { this.previousStyle = null; } + private getPropertyDiff( + previousStyle: UnknownRecord | null, + nextStyle: UnknownRecord | null, + previousConfig: NormalizedCSSTransitionConfig | null, + nextConfig: NormalizedCSSTransitionConfig + ): Record | null { + const trackedProperties = + nextConfig.properties === 'all' ? undefined : nextConfig.properties; + + const changedProps = getChangedProps( + previousStyle, + nextStyle, + trackedProperties + ); + + const removedPropsDiff = this.getRemovedPropertiesDiff( + previousStyle, + previousConfig, + nextConfig + ); + + const combinedDiff = { + ...changedProps, + ...removedPropsDiff, + }; + + return Object.keys(combinedDiff).length > 0 ? combinedDiff : null; + } + + private getRemovedPropertiesDiff( + previousStyle: UnknownRecord | null, + previousConfig: NormalizedCSSTransitionConfig | null, + nextConfig: NormalizedCSSTransitionConfig + ): Record { + if (!previousConfig || !previousStyle) { + return {}; + } + + const nextProperties = nextConfig.properties; + if (nextProperties === 'all') { + return {}; + } + + const previousProperties = + previousConfig.properties === 'all' + ? Object.keys(previousStyle) + : previousConfig.properties; + + const diff: Record = {}; + + for (const property of previousProperties) { + if (previousStyle[property] !== undefined) { + diff[property] = null; + } + } + + for (const property of nextProperties) { + delete diff[property]; + } + + return diff; + } + private getSettingsUpdates( oldConfig: NormalizedCSSTransitionConfig, newConfig: NormalizedCSSTransitionConfig diff --git a/packages/react-native-reanimated/src/css/native/managers/__tests__/CSSTransitionsManager.test.ts b/packages/react-native-reanimated/src/css/native/managers/__tests__/CSSTransitionsManager.test.ts index 6781259c30c9..895fcd58319a 100644 --- a/packages/react-native-reanimated/src/css/native/managers/__tests__/CSSTransitionsManager.test.ts +++ b/packages/react-native-reanimated/src/css/native/managers/__tests__/CSSTransitionsManager.test.ts @@ -26,17 +26,31 @@ describe('CSSTransitionsManager', () => { }); describe('update', () => { + const initialStyle = { opacity: 0 }; + describe('attaching transition', () => { - test('registers a transition if there is no existing transition', () => { + test('registers native transition only after tracked style property changes', () => { const transitionProperties: CSSTransitionProperties = { transitionProperty: 'opacity', + transitionDuration: '200ms', }; + const nextStyle = { opacity: 0.5 }; + + manager.update(transitionProperties, initialStyle); + expect(registerCSSTransition).not.toHaveBeenCalled(); + + manager.update(transitionProperties, nextStyle); - manager.update(transitionProperties); + const normalizedConfig = normalizeCSSTransitionProperties( + transitionProperties + ); expect(registerCSSTransition).toHaveBeenCalledWith( shadowNodeWrapper, - normalizeCSSTransitionProperties(transitionProperties) + { + properties: { opacity: [0, 0.5] }, + settings: normalizedConfig?.settings ?? {}, + } ); expect(unregisterCSSTransition).not.toHaveBeenCalled(); expect(updateCSSTransition).not.toHaveBeenCalled(); @@ -44,74 +58,224 @@ describe('CSSTransitionsManager', () => { }); describe('updating transition', () => { - test("doesn't update transition if method was called with the same config", () => { + test("doesn't send native updates when style and settings remain unchanged", () => { const transitionProperties: CSSTransitionProperties = { transitionProperty: 'opacity', + transitionDuration: '150ms', }; + const secondStyle = { opacity: 0.4 }; - manager.update(transitionProperties); + manager.update(transitionProperties, initialStyle); + manager.update(transitionProperties, secondStyle); expect(registerCSSTransition).toHaveBeenCalledTimes(1); expect(unregisterCSSTransition).not.toHaveBeenCalled(); expect(updateCSSTransition).not.toHaveBeenCalled(); - manager.update(transitionProperties); - expect(registerCSSTransition).toHaveBeenCalledTimes(1); + jest.clearAllMocks(); + + manager.update(transitionProperties, secondStyle); + expect(registerCSSTransition).not.toHaveBeenCalled(); expect(unregisterCSSTransition).not.toHaveBeenCalled(); expect(updateCSSTransition).not.toHaveBeenCalled(); }); - test('updates transition if method was called with different config', () => { - const transitionProperties: CSSTransitionProperties = { + test('does not invoke native updates when only settings or unrelated props change', () => { + const baseTransition: CSSTransitionProperties = { transitionProperty: 'opacity', + transitionDuration: '180ms', }; - const newTransitionConfig: CSSTransitionProperties = { - transitionProperty: 'transform', - transitionDuration: '1.5s', + const changedStyle = { opacity: 0.6 }; + + manager.update(baseTransition, initialStyle); + manager.update(baseTransition, changedStyle); + + jest.clearAllMocks(); + + const settingsOnlyUpdate: CSSTransitionProperties = { + transitionProperty: 'opacity', + transitionDuration: '220ms', }; - manager.update(transitionProperties); - expect(registerCSSTransition).toHaveBeenCalledTimes(1); + manager.update(settingsOnlyUpdate, changedStyle); + expect(registerCSSTransition).not.toHaveBeenCalled(); expect(unregisterCSSTransition).not.toHaveBeenCalled(); expect(updateCSSTransition).not.toHaveBeenCalled(); - manager.update(newTransitionConfig); - expect(registerCSSTransition).toHaveBeenCalledTimes(1); + const unrelatedStyleChange = { + opacity: 0.6, + transform: 'scale(1.1)', + }; + + manager.update(baseTransition, unrelatedStyleChange); + expect(registerCSSTransition).not.toHaveBeenCalled(); + expect(unregisterCSSTransition).not.toHaveBeenCalled(); + expect(updateCSSTransition).not.toHaveBeenCalled(); + }); + + test('updates native transition when tracked style property changes with new settings', () => { + const transitionProperties: CSSTransitionProperties = { + transitionProperty: 'opacity', + transitionDuration: '100ms', + }; + const updatedTransitionConfig: CSSTransitionProperties = { + transitionProperty: 'opacity', + transitionDuration: '200ms', + }; + const secondStyle = { opacity: 0.25 }; + const thirdStyle = { opacity: 0.75 }; + + manager.update(transitionProperties, initialStyle); + manager.update(transitionProperties, secondStyle); + + jest.clearAllMocks(); + + manager.update(updatedTransitionConfig, thirdStyle); + expect(registerCSSTransition).not.toHaveBeenCalled(); expect(unregisterCSSTransition).not.toHaveBeenCalled(); - expect(updateCSSTransition).toHaveBeenCalledTimes(1); expect(updateCSSTransition).toHaveBeenCalledWith(viewTag, { - properties: ['transform'], - settings: { - transform: { - duration: 1500, - delay: 0, - timingFunction: 'ease', - allowDiscrete: false, - }, - }, + properties: { opacity: [0.25, 0.75] }, + settings: { opacity: { duration: 200 } }, }); }); + + test('removes transitioned properties immediately when transitionProperty no longer includes them', () => { + const transitionProperties: CSSTransitionProperties = { + transitionProperty: ['opacity', 'transform'], + transitionDuration: '150ms', + }; + const initialTransitionStyle = { + opacity: 0, + transform: 'scale(1)', + }; + const updatedStyle = { + opacity: 1, + transform: 'scale(1.1)', + }; + + manager.update(transitionProperties, initialTransitionStyle); + manager.update(transitionProperties, updatedStyle); + + expect(registerCSSTransition).toHaveBeenCalledTimes(1); + + jest.clearAllMocks(); + + const narrowedTransition: CSSTransitionProperties = { + transitionProperty: 'transform', + transitionDuration: '150ms', + }; + + manager.update(narrowedTransition, updatedStyle); + + expect(registerCSSTransition).not.toHaveBeenCalled(); + expect(unregisterCSSTransition).not.toHaveBeenCalled(); + expect(updateCSSTransition).toHaveBeenCalledWith( + viewTag, + expect.objectContaining({ + properties: { opacity: null }, + }) + ); + }); + + test('removes properties when previous config tracked all properties and new config is narrower', () => { + const transitionProperties: CSSTransitionProperties = { + transitionProperty: 'all', + transitionDuration: '120ms', + }; + const initialTransitionStyle = { + opacity: 0, + transform: 'scale(1)', + }; + const updatedStyle = { + opacity: 0.4, + transform: 'scale(1.1)', + }; + + manager.update(transitionProperties, initialTransitionStyle); + manager.update(transitionProperties, updatedStyle); + + jest.clearAllMocks(); + + const narrowedConfig: CSSTransitionProperties = { + transitionProperty: 'transform', + transitionDuration: '120ms', + }; + + manager.update(narrowedConfig, updatedStyle); + + expect(registerCSSTransition).not.toHaveBeenCalled(); + expect(unregisterCSSTransition).not.toHaveBeenCalled(); + expect(updateCSSTransition).toHaveBeenCalledWith( + viewTag, + expect.objectContaining({ + properties: { opacity: null }, + }) + ); + }); + + test('sends combined diff when style changes and property is removed in the same update', () => { + const transitionProperties: CSSTransitionProperties = { + transitionProperty: ['opacity', 'transform'], + transitionDuration: '160ms', + }; + const firstStyle = { + opacity: 0.2, + transform: 'scale(1)', + }; + const secondStyle = { + opacity: 0.5, + transform: 'scale(1.2)', + }; + + manager.update(transitionProperties, firstStyle); + manager.update(transitionProperties, secondStyle); + + jest.clearAllMocks(); + + const narrowedConfig: CSSTransitionProperties = { + transitionProperty: 'transform', + transitionDuration: '160ms', + }; + const thirdStyle = { + opacity: 0.7, + transform: 'scale(1.4)', + }; + + manager.update(narrowedConfig, thirdStyle); + + expect(updateCSSTransition).toHaveBeenCalledWith( + viewTag, + expect.objectContaining({ + properties: { + opacity: null, + transform: ['scale(1.2)', 'scale(1.4)'], + }, + }) + ); + }); }); describe('detaching transition', () => { - test('detaches transition if method was called with null config and there is existing transition', () => { + test('unregisters native transition when config is cleared even if there are no style changes', () => { const transitionProperties: CSSTransitionProperties = { transitionProperty: 'opacity', + transitionDuration: '80ms', }; + const secondStyle: UnknownRecord = { opacity: 0.6 }; - manager.update(transitionProperties); - expect(registerCSSTransition).toHaveBeenCalledTimes(1); - expect(unregisterCSSTransition).not.toHaveBeenCalled(); - expect(updateCSSTransition).not.toHaveBeenCalled(); + manager.update(transitionProperties, initialStyle); + manager.update(transitionProperties, secondStyle); - manager.update(null); - expect(registerCSSTransition).toHaveBeenCalledTimes(1); - expect(unregisterCSSTransition).toHaveBeenCalledTimes(1); + jest.clearAllMocks(); + + manager.update(null, secondStyle); + expect(registerCSSTransition).not.toHaveBeenCalled(); + expect(unregisterCSSTransition).toHaveBeenCalledWith(viewTag); expect(updateCSSTransition).not.toHaveBeenCalled(); }); }); test("doesn't call detach if there is no existing transition", () => { - manager.update(null); + manager.update(null, null); expect(registerCSSTransition).not.toHaveBeenCalled(); expect(unregisterCSSTransition).not.toHaveBeenCalled(); expect(updateCSSTransition).not.toHaveBeenCalled(); From 7368a3399c04efa0c234db96892d455a745d2509 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20=C5=81opaci=C5=84ski?= Date: Thu, 4 Dec 2025 14:04:41 +0100 Subject: [PATCH 13/13] Fix formattin issues, remove unused code --- .../__tests__/CSSTransitionsManager.test.ts | 18 +- .../transition/__tests__/config.test.ts | 239 +----------------- .../native/normalization/transition/config.ts | 40 --- .../native/normalization/transition/index.ts | 5 +- .../src/css/native/types/transition.ts | 12 +- .../src/css/utils/equality.ts | 13 - 6 files changed, 11 insertions(+), 316 deletions(-) diff --git a/packages/react-native-reanimated/src/css/native/managers/__tests__/CSSTransitionsManager.test.ts b/packages/react-native-reanimated/src/css/native/managers/__tests__/CSSTransitionsManager.test.ts index 895fcd58319a..fabf23b62c59 100644 --- a/packages/react-native-reanimated/src/css/native/managers/__tests__/CSSTransitionsManager.test.ts +++ b/packages/react-native-reanimated/src/css/native/managers/__tests__/CSSTransitionsManager.test.ts @@ -41,17 +41,13 @@ describe('CSSTransitionsManager', () => { manager.update(transitionProperties, nextStyle); - const normalizedConfig = normalizeCSSTransitionProperties( - transitionProperties - ); + const normalizedConfig = + normalizeCSSTransitionProperties(transitionProperties); - expect(registerCSSTransition).toHaveBeenCalledWith( - shadowNodeWrapper, - { - properties: { opacity: [0, 0.5] }, - settings: normalizedConfig?.settings ?? {}, - } - ); + expect(registerCSSTransition).toHaveBeenCalledWith(shadowNodeWrapper, { + properties: { opacity: [0, 0.5] }, + settings: normalizedConfig?.settings ?? {}, + }); expect(unregisterCSSTransition).not.toHaveBeenCalled(); expect(updateCSSTransition).not.toHaveBeenCalled(); }); @@ -260,7 +256,7 @@ describe('CSSTransitionsManager', () => { transitionProperty: 'opacity', transitionDuration: '80ms', }; - const secondStyle: UnknownRecord = { opacity: 0.6 }; + const secondStyle = { opacity: 0.6 }; manager.update(transitionProperties, initialStyle); manager.update(transitionProperties, secondStyle); diff --git a/packages/react-native-reanimated/src/css/native/normalization/transition/__tests__/config.test.ts b/packages/react-native-reanimated/src/css/native/normalization/transition/__tests__/config.test.ts index e91398133265..ae40a6becd97 100644 --- a/packages/react-native-reanimated/src/css/native/normalization/transition/__tests__/config.test.ts +++ b/packages/react-native-reanimated/src/css/native/normalization/transition/__tests__/config.test.ts @@ -4,17 +4,8 @@ import { cubicBezier } from '../../../../easing'; import type { CSSTransitionProperties, CSSTransitionProperty, - Repeat, } from '../../../../types'; -import type { - NormalizedCSSTransitionConfig, - NormalizedCSSTransitionPropertyNames, -} from '../../../types'; -import { - ERROR_MESSAGES, - getNormalizedCSSTransitionConfigUpdates, - normalizeCSSTransitionProperties, -} from '../config'; +import { ERROR_MESSAGES, normalizeCSSTransitionProperties } from '../config'; describe(normalizeCSSTransitionProperties, () => { describe('when there is a single transition property', () => { @@ -331,231 +322,3 @@ describe(normalizeCSSTransitionProperties, () => { }); }); }); - -describe(getNormalizedCSSTransitionConfigUpdates, () => { - test('returns empty object if nothing changed', () => { - const oldConfig: NormalizedCSSTransitionConfig = { - properties: 'all', - settings: { - all: { - duration: 1500, - timingFunction: cubicBezier(0.4, 0, 0.2, 1).normalize(), - delay: 300, - allowDiscrete: false, - }, - }, - }; - const newConfig: NormalizedCSSTransitionConfig = { - properties: 'all', - settings: { - all: { - duration: 1500, - timingFunction: cubicBezier(0.4, 0, 0.2, 1).normalize(), - delay: 300, - allowDiscrete: false, - }, - }, - }; - - expect( - getNormalizedCSSTransitionConfigUpdates(oldConfig, newConfig) - ).toEqual({}); - }); - - describe('property changes', () => { - test.each([ - ['all', ['opacity'], ['opacity']], - [['opacity'], 'all', 'all'], - [['opacity'], ['transform'], ['transform']], - [['opacity', 'transform'], 'all', 'all'], - ['all', ['opacity', 'transform'], ['opacity', 'transform']], - [['opacity', 'transform'], ['opacity'], ['opacity']], - ] satisfies Repeat[])( - 'returns property update if properties changed from %p to %p', - (oldProperties, newProperties, expected) => { - const oldConfig: NormalizedCSSTransitionConfig = { - properties: oldProperties, - settings: {}, - }; - const newConfig: NormalizedCSSTransitionConfig = { - properties: newProperties, - settings: {}, - }; - - expect( - getNormalizedCSSTransitionConfigUpdates(oldConfig, newConfig) - ).toEqual({ properties: expected }); - } - ); - - test.each([ - 'all', - ['opacity'], - ['opacity', 'transform'], - ] satisfies NormalizedCSSTransitionPropertyNames[])( - 'does not return property update if properties did not change from %p', - (properties) => { - const oldConfig: NormalizedCSSTransitionConfig = { - properties, - settings: {}, - }; - const newConfig: NormalizedCSSTransitionConfig = { - properties, - settings: {}, - }; - - expect( - getNormalizedCSSTransitionConfigUpdates(oldConfig, newConfig) - ).toEqual({}); - } - ); - }); - - describe('settings changes', () => { - describe('single transition settings', () => { - test('returns all new settings if at least one setting changed', () => { - const oldConfig: NormalizedCSSTransitionConfig = { - properties: 'all', - settings: { - all: { - duration: 1500, - timingFunction: cubicBezier(0.4, 0, 0.2, 1).normalize(), - delay: 300, - allowDiscrete: false, - }, - }, - }; - const newConfig: NormalizedCSSTransitionConfig = { - properties: 'all', - settings: { - all: { - duration: 1500, - timingFunction: 'ease-in', // changed - delay: 300, - allowDiscrete: false, - }, - }, - }; - - expect( - getNormalizedCSSTransitionConfigUpdates(oldConfig, newConfig) - ).toEqual({ - settings: { - all: { - duration: 1500, - timingFunction: 'ease-in', - delay: 300, - allowDiscrete: false, - }, - }, - }); - }); - - test('returns empty object if nothing changed', () => { - const oldConfig: NormalizedCSSTransitionConfig = { - properties: 'all', - settings: { - all: { - duration: 1500, - timingFunction: cubicBezier(0.4, 0, 0.2, 1).normalize(), - delay: 300, - allowDiscrete: false, - }, - }, - }; - const newConfig: NormalizedCSSTransitionConfig = { - properties: 'all', - settings: { - all: { - duration: 1500, - timingFunction: cubicBezier(0.4, 0, 0.2, 1).normalize(), - delay: 300, - allowDiscrete: false, - }, - }, - }; - - expect( - getNormalizedCSSTransitionConfigUpdates(oldConfig, newConfig) - ).toEqual({}); - }); - }); - - describe('multiple transition settings', () => { - test('returns all new settings if at least one setting changed', () => { - const oldConfig: NormalizedCSSTransitionConfig = { - properties: ['opacity', 'transform', 'width'], - settings: { - opacity: { - duration: 1500, - timingFunction: cubicBezier(0.4, 0, 0.2, 1).normalize(), - delay: 300, - allowDiscrete: false, - }, - transform: { - duration: 2000, - timingFunction: 'ease-in', - delay: 500, - allowDiscrete: false, - }, - width: { - duration: 1000, - timingFunction: 'ease-out', - delay: 200, - allowDiscrete: false, - }, - }, - }; - const newConfig: NormalizedCSSTransitionConfig = { - properties: ['transform', 'width', 'opacity'], - settings: { - opacity: { - duration: 1500, - timingFunction: cubicBezier(0.4, 0, 0.2, 1).normalize(), - delay: 500, - allowDiscrete: false, - }, - transform: { - duration: 2000, - timingFunction: 'ease-in', - delay: 500, - allowDiscrete: true, - }, - width: { - duration: 500, - timingFunction: 'ease', - delay: 200, - allowDiscrete: false, - }, - }, - }; - - expect( - getNormalizedCSSTransitionConfigUpdates(oldConfig, newConfig) - ).toEqual({ - properties: ['transform', 'width', 'opacity'], - settings: { - opacity: { - duration: 1500, - timingFunction: cubicBezier(0.4, 0, 0.2, 1).normalize(), - delay: 500, - allowDiscrete: false, - }, - transform: { - duration: 2000, - timingFunction: 'ease-in', - delay: 500, - allowDiscrete: true, - }, - width: { - duration: 500, - timingFunction: 'ease', - delay: 200, - allowDiscrete: false, - }, - }, - }); - }); - }); - }); -}); diff --git a/packages/react-native-reanimated/src/css/native/normalization/transition/config.ts b/packages/react-native-reanimated/src/css/native/normalization/transition/config.ts index 7a014a128eb4..6820b7b760f7 100644 --- a/packages/react-native-reanimated/src/css/native/normalization/transition/config.ts +++ b/packages/react-native-reanimated/src/css/native/normalization/transition/config.ts @@ -5,10 +5,8 @@ import type { CSSTransitionProperties, CSSTransitionProperty, } from '../../../types'; -import { areArraysEqual, deepEqual } from '../../../utils'; import type { NormalizedCSSTransitionConfig, - NormalizedCSSTransitionConfigUpdates, NormalizedSingleCSSTransitionSettings, } from '../../types'; import { @@ -134,41 +132,3 @@ export function normalizeCSSTransitionProperties( settings, }; } - -export function getNormalizedCSSTransitionConfigUpdates( - oldConfig: NormalizedCSSTransitionConfig, - newConfig: NormalizedCSSTransitionConfig -): NormalizedCSSTransitionConfigUpdates { - const configUpdates: NormalizedCSSTransitionConfigUpdates = {}; - - if ( - oldConfig.properties !== newConfig.properties && - (!Array.isArray(oldConfig.properties) || - !Array.isArray(newConfig.properties) || - !areArraysEqual(oldConfig.properties, newConfig.properties)) - ) { - configUpdates.properties = newConfig.properties; - } - - const newSettingsKeys = Object.keys(newConfig.settings); - const oldSettingsKeys = Object.keys(oldConfig.settings); - - if (newSettingsKeys.length !== oldSettingsKeys.length) { - configUpdates.settings = newConfig.settings; - } else { - for (const key of newSettingsKeys) { - if ( - !oldConfig.settings[key] || - // TODO - think of a better way to compare settings (necessary for - // timing functions comparison). Maybe add some custom way instead - // of deepEqual - !deepEqual(oldConfig.settings[key], newConfig.settings[key]) - ) { - configUpdates.settings = newConfig.settings; - break; - } - } - } - - return configUpdates; -} diff --git a/packages/react-native-reanimated/src/css/native/normalization/transition/index.ts b/packages/react-native-reanimated/src/css/native/normalization/transition/index.ts index 514cf90c04cf..8d1a0a296016 100644 --- a/packages/react-native-reanimated/src/css/native/normalization/transition/index.ts +++ b/packages/react-native-reanimated/src/css/native/normalization/transition/index.ts @@ -1,5 +1,2 @@ 'use strict'; -export { - getNormalizedCSSTransitionConfigUpdates, - normalizeCSSTransitionProperties, -} from './config'; +export { normalizeCSSTransitionProperties } from './config'; diff --git a/packages/react-native-reanimated/src/css/native/types/transition.ts b/packages/react-native-reanimated/src/css/native/types/transition.ts index e0f3796589dc..a2542b8a211a 100644 --- a/packages/react-native-reanimated/src/css/native/types/transition.ts +++ b/packages/react-native-reanimated/src/css/native/types/transition.ts @@ -8,26 +8,18 @@ export type NormalizedSingleCSSTransitionSettings = { allowDiscrete: boolean; }; -export type NormalizedCSSTransitionPropertyNames = 'all' | string[]; - export type NormalizedCSSTransitionConfig = { - properties: NormalizedCSSTransitionPropertyNames; + properties: 'all' | string[]; settings: Record; }; -export type CSSTransitionPropertyUpdates = Record< - string, - [unknown, unknown] | null ->; +type CSSTransitionPropertyUpdates = Record; export type NormalizedNewCSSTransitionConfig = { properties: CSSTransitionPropertyUpdates; settings: Record; }; -export type NormalizedCSSTransitionConfigUpdates = - Partial; - export type CSSTransitionUpdates = { properties?: CSSTransitionPropertyUpdates; settings?: Record>; diff --git a/packages/react-native-reanimated/src/css/utils/equality.ts b/packages/react-native-reanimated/src/css/utils/equality.ts index 2a1a187252ac..b502df00760a 100644 --- a/packages/react-native-reanimated/src/css/utils/equality.ts +++ b/packages/react-native-reanimated/src/css/utils/equality.ts @@ -1,17 +1,4 @@ 'use strict'; -export function areArraysEqual(array1: T[], array2: T[]): boolean { - if (array1.length !== array2.length) { - return false; - } - - for (let i = 0; i < array1.length; i++) { - if (array1[i] !== array2[i]) { - return false; - } - } - - return true; -} export function deepEqual(obj1: T, obj2: T): boolean { if (obj1 === obj2) {