diff --git a/packages/react-native/Libraries/Animated/__tests__/AnimatedComposition-itest.js b/packages/react-native/Libraries/Animated/__tests__/AnimatedComposition-itest.js index ba97eacec7f9..f5af08be88e4 100644 --- a/packages/react-native/Libraries/Animated/__tests__/AnimatedComposition-itest.js +++ b/packages/react-native/Libraries/Animated/__tests__/AnimatedComposition-itest.js @@ -379,3 +379,115 @@ describe('composition nodes: native driver, interpolation and detach', () => { }); } }); + +// Regression test for https://github.com/facebook/react-native/issues/49719. +// Listeners attached to a node *derived* from an `Animated.Value` (an operator +// or an interpolation) must keep firing, on both drivers. On the native driver +// the value is computed natively, so the derived node has to subscribe to +// native updates for its own tag rather than relying on the JS graph. +describe('addListener on derived value nodes', () => { + const derivedNodes = [ + { + name: 'Animated.add', + make: (base: Animated.Value) => Animated.add(base, 10), + expected: 60, + }, + { + name: 'Animated.subtract', + make: (base: Animated.Value) => Animated.subtract(base, 10), + expected: 40, + }, + { + name: 'Animated.multiply', + make: (base: Animated.Value) => Animated.multiply(base, 2), + expected: 100, + }, + { + name: 'Animated.divide', + make: (base: Animated.Value) => Animated.divide(base, 2), + expected: 25, + }, + { + name: 'Animated.modulo', + make: (base: Animated.Value) => Animated.modulo(base, 7), + expected: 50 % 7, + }, + { + name: 'interpolate', + make: (base: Animated.Value) => + base.interpolate({inputRange: [0, 50], outputRange: [0, 500]}), + expected: 500, + }, + ]; + + for (const useNativeDriver of [false, true]) { + const driverName = useNativeDriver ? 'native driver' : 'JS driver'; + + for (const {name, make, expected} of derivedNodes) { + it(`${name} notifies its listeners on the ${driverName}`, () => { + let base: ?Animated.Value; + let node: ?Animated.Node; + + function MyApp() { + const value = useAnimatedValue(0); + base = value; + node = make(value); + return ( + + ); + } + + const root = Fantom.createRoot(); + Fantom.runTask(() => { + root.render(); + }); + + // The listener is attached before the node is made native, so this also + // covers the subscription being created from `__makeNative`. + const values: Array = []; + const listenerId = nullthrows(node).addListener(state => { + values.push(state.value); + }); + + let animation: ?Animated.CompositeAnimation; + Fantom.runTask(() => { + animation = Animated.timing(nullthrows(base), { + toValue: 50, + duration: 100, + useNativeDriver, + }); + animation.start(); + }); + Fantom.unstable_produceFramesForDuration(200); + Fantom.runWorkLoop(); + + expect(values.length).toBeGreaterThan(0); + expect(values[values.length - 1]).toBeCloseTo(expected, 0); + + // Removing the listener stops the updates. + nullthrows(node).removeListener(listenerId); + const countAfterRemoval = values.length; + Fantom.runTask(() => { + nullthrows(base).setValue(0); + }); + Fantom.unstable_produceFramesForDuration(32); + Fantom.runWorkLoop(); + expect(values.length).toBe(countAfterRemoval); + + Fantom.runTask(() => { + nullthrows(animation).stop(); + }); + Fantom.runTask(() => { + root.render(); + }); + Fantom.unstable_produceFramesForDuration(16); + Fantom.runWorkLoop(); + }); + } + } +}); diff --git a/packages/react-native/Libraries/Animated/nodes/AnimatedAddition.js b/packages/react-native/Libraries/Animated/nodes/AnimatedAddition.js index 91d686f5af3d..8bbc8a4aca95 100644 --- a/packages/react-native/Libraries/Animated/nodes/AnimatedAddition.js +++ b/packages/react-native/Libraries/Animated/nodes/AnimatedAddition.js @@ -20,6 +20,8 @@ import AnimatedValue from './AnimatedValue'; import AnimatedWithChildren from './AnimatedWithChildren'; export default class AnimatedAddition extends AnimatedWithChildren { + __isNativeValueNode: boolean = true; + _a: AnimatedNode; _b: AnimatedNode; diff --git a/packages/react-native/Libraries/Animated/nodes/AnimatedDiffClamp.js b/packages/react-native/Libraries/Animated/nodes/AnimatedDiffClamp.js index 94fef85aa0e1..fd36fb223c3a 100644 --- a/packages/react-native/Libraries/Animated/nodes/AnimatedDiffClamp.js +++ b/packages/react-native/Libraries/Animated/nodes/AnimatedDiffClamp.js @@ -19,6 +19,8 @@ import AnimatedInterpolation from './AnimatedInterpolation'; import AnimatedWithChildren from './AnimatedWithChildren'; export default class AnimatedDiffClamp extends AnimatedWithChildren { + __isNativeValueNode: boolean = true; + _a: AnimatedNode; _min: number; _max: number; diff --git a/packages/react-native/Libraries/Animated/nodes/AnimatedDivision.js b/packages/react-native/Libraries/Animated/nodes/AnimatedDivision.js index 6b73ff7d9931..3bef22a6c92a 100644 --- a/packages/react-native/Libraries/Animated/nodes/AnimatedDivision.js +++ b/packages/react-native/Libraries/Animated/nodes/AnimatedDivision.js @@ -20,6 +20,8 @@ import AnimatedValue from './AnimatedValue'; import AnimatedWithChildren from './AnimatedWithChildren'; export default class AnimatedDivision extends AnimatedWithChildren { + __isNativeValueNode: boolean = true; + _a: AnimatedNode; _b: AnimatedNode; _warnedAboutDivideByZero: boolean = false; diff --git a/packages/react-native/Libraries/Animated/nodes/AnimatedInterpolation.js b/packages/react-native/Libraries/Animated/nodes/AnimatedInterpolation.js index b6a0d4d3b5f5..1ce99ecd6237 100644 --- a/packages/react-native/Libraries/Animated/nodes/AnimatedInterpolation.js +++ b/packages/react-native/Libraries/Animated/nodes/AnimatedInterpolation.js @@ -444,6 +444,8 @@ function sampleEasingStops( export default class AnimatedInterpolation< OutputT extends InterpolationConfigSupportedOutputType, > extends AnimatedWithChildren { + __isNativeValueNode: boolean = true; + _parent: AnimatedNode; _config: InterpolationConfigType; _interpolation: ?(input: number) => OutputT; diff --git a/packages/react-native/Libraries/Animated/nodes/AnimatedModulo.js b/packages/react-native/Libraries/Animated/nodes/AnimatedModulo.js index 32a698afbdac..7c8e1823cbe2 100644 --- a/packages/react-native/Libraries/Animated/nodes/AnimatedModulo.js +++ b/packages/react-native/Libraries/Animated/nodes/AnimatedModulo.js @@ -19,6 +19,8 @@ import AnimatedInterpolation from './AnimatedInterpolation'; import AnimatedWithChildren from './AnimatedWithChildren'; export default class AnimatedModulo extends AnimatedWithChildren { + __isNativeValueNode: boolean = true; + _a: AnimatedNode; _modulus: number; diff --git a/packages/react-native/Libraries/Animated/nodes/AnimatedMultiplication.js b/packages/react-native/Libraries/Animated/nodes/AnimatedMultiplication.js index 93764f86c751..29e7a7019477 100644 --- a/packages/react-native/Libraries/Animated/nodes/AnimatedMultiplication.js +++ b/packages/react-native/Libraries/Animated/nodes/AnimatedMultiplication.js @@ -20,6 +20,8 @@ import AnimatedValue from './AnimatedValue'; import AnimatedWithChildren from './AnimatedWithChildren'; export default class AnimatedMultiplication extends AnimatedWithChildren { + __isNativeValueNode: boolean = true; + _a: AnimatedNode; _b: AnimatedNode; diff --git a/packages/react-native/Libraries/Animated/nodes/AnimatedNode.js b/packages/react-native/Libraries/Animated/nodes/AnimatedNode.js index a7414bd48218..34ea5ea09ace 100644 --- a/packages/react-native/Libraries/Animated/nodes/AnimatedNode.js +++ b/packages/react-native/Libraries/Animated/nodes/AnimatedNode.js @@ -8,6 +8,7 @@ * @format */ +import type {EventSubscription} from '../../vendor/emitter/EventEmitter'; import type {PlatformConfig} from '../AnimatedPlatformConfig'; import NativeAnimatedHelper from '../../../src/private/animated/NativeAnimatedHelper'; @@ -54,7 +55,12 @@ export default class AnimatedNode { this.removeAllListeners(); } if (this.__isNative && this.__nativeTag != null) { - NativeAnimatedHelper.API.dropAnimatedNode(this.__nativeTag); + const nativeTag = this.__nativeTag; + // The subscription must not outlive the native tag it observes. Any + // listeners kept around are re-subscribed by `__makeNative` if this node + // is attached again. + this.__updateSubscription?.remove(); + NativeAnimatedHelper.API.dropAnimatedNode(nativeTag); this.__nativeTag = undefined; } } @@ -73,6 +79,19 @@ export default class AnimatedNode { __nativeTag: ?number = undefined; __disableBatchingForNativeCreate: ?boolean = undefined; + /** + * Whether the native node backing this one holds a number, and therefore + * supports `startListeningToAnimatedNodeValue`. That native module method + * only accepts tags of "value" nodes (`ValueAnimatedNode` on Android and in + * C++, `RCTValueAnimatedNode` on iOS); passing any other tag throws on + * Android and is a no-op elsewhere. + * + * Subclasses backed by a non-value native node — props, style, transform, + * object, tracking and color — must leave this `false`. + */ + __isNativeValueNode: boolean = false; + __updateSubscription: ?EventSubscription = null; + __makeNative(platformConfig: ?PlatformConfig): void { // Subclasses are expected to set `__isNative` to true before this. invariant( @@ -81,6 +100,9 @@ export default class AnimatedNode { ); this._platformConfig = platformConfig; + if (this._listeners.size > 0) { + this.__ensureUpdateSubscriptionExists(); + } } /** @@ -93,6 +115,9 @@ export default class AnimatedNode { addListener(callback: (value: any) => unknown): string { const id = String(_uniqueId++); this._listeners.set(id, callback); + if (this.__isNative) { + this.__ensureUpdateSubscriptionExists(); + } return id; } @@ -104,6 +129,9 @@ export default class AnimatedNode { */ removeListener(id: string): void { this._listeners.delete(id); + if (this.__isNative && this._listeners.size === 0) { + this.__updateSubscription?.remove(); + } } /** @@ -113,14 +141,53 @@ export default class AnimatedNode { */ removeAllListeners(): void { this._listeners.clear(); + if (this.__isNative) { + this.__updateSubscription?.remove(); + } } hasListeners(): boolean { return this._listeners.size > 0; } - __onAnimatedValueUpdateReceived(value: number, offset: number): void { - this.__callListeners(value + offset); + /** + * Subscribes to native updates of this node's value, so that listeners keep + * firing for natively driven animations. No-op for nodes that are not backed + * by a native "value" node. + */ + __ensureUpdateSubscriptionExists(): void { + if (!this.__isNativeValueNode || this.__updateSubscription != null) { + return; + } + const nativeTag = this.__getNativeTag(); + NativeAnimatedHelper.API.startListeningToAnimatedNodeValue(nativeTag); + const subscription: EventSubscription = + NativeAnimatedHelper.nativeEventEmitter.addListener( + 'onAnimatedValueUpdate', + data => { + if (data.tag === nativeTag) { + this.__onAnimatedValueUpdateReceived(data.value, data.offset); + } + }, + ); + + this.__updateSubscription = { + remove: () => { + // Only this function assigns to `this.__updateSubscription`. + if (this.__updateSubscription == null) { + return; + } + this.__updateSubscription = null; + subscription.remove(); + NativeAnimatedHelper.API.stopListeningToAnimatedNodeValue(nativeTag); + }, + }; + } + + // NOTE: `offset` is omitted by backends that do not track one separately + // (e.g. the C++ backend), in which case it is already folded into `value`. + __onAnimatedValueUpdateReceived(value: number, offset?: ?number): void { + this.__callListeners(value + (offset ?? 0)); } __callListeners(value: number): void { diff --git a/packages/react-native/Libraries/Animated/nodes/AnimatedSubtraction.js b/packages/react-native/Libraries/Animated/nodes/AnimatedSubtraction.js index 04181bda7b20..27366cbe60b3 100644 --- a/packages/react-native/Libraries/Animated/nodes/AnimatedSubtraction.js +++ b/packages/react-native/Libraries/Animated/nodes/AnimatedSubtraction.js @@ -20,6 +20,8 @@ import AnimatedValue from './AnimatedValue'; import AnimatedWithChildren from './AnimatedWithChildren'; export default class AnimatedSubtraction extends AnimatedWithChildren { + __isNativeValueNode: boolean = true; + _a: AnimatedNode; _b: AnimatedNode; diff --git a/packages/react-native/Libraries/Animated/nodes/AnimatedValue.js b/packages/react-native/Libraries/Animated/nodes/AnimatedValue.js index 74ce65ae3649..157511d5727b 100644 --- a/packages/react-native/Libraries/Animated/nodes/AnimatedValue.js +++ b/packages/react-native/Libraries/Animated/nodes/AnimatedValue.js @@ -8,8 +8,6 @@ * @format */ -import type {EventSubscription} from '../../vendor/emitter/EventEmitter'; -import type {PlatformConfig} from '../AnimatedPlatformConfig'; import type Animation from '../animations/Animation'; import type {EndCallback} from '../animations/Animation'; import type { @@ -88,8 +86,7 @@ function _executeAsAnimatedBatch(id: string, operation: () => void) { * See https://reactnative.dev/docs/animatedvalue */ export default class AnimatedValue extends AnimatedWithChildren { - _listenerCount: number; - _updateSubscription: ?EventSubscription; + __isNativeValueNode: boolean = true; _value: number; _startingValue: number; @@ -104,9 +101,6 @@ export default class AnimatedValue extends AnimatedWithChildren { throw new Error('AnimatedValue: Attempting to set value to undefined'); } - this._listenerCount = 0; - this._updateSubscription = null; - this._startingValue = this._value = value; this._offset = 0; this.__deferAnimationStart = @@ -124,9 +118,6 @@ export default class AnimatedValue extends AnimatedWithChildren { }); } this.stopAnimation(); - if (ReactNativeFeatureFlags.animatedKeepListenersOnDetach()) { - this._updateSubscription?.remove(); - } super.__detach(); } @@ -134,65 +125,12 @@ export default class AnimatedValue extends AnimatedWithChildren { return this._value + this._offset; } - __makeNative(platformConfig: ?PlatformConfig): void { - super.__makeNative(platformConfig); - if (this._listenerCount > 0) { - this.__ensureUpdateSubscriptionExists(); - } - } - + /** + * Narrows `AnimatedNode.addListener`: the value of an `Animated.Value` is + * always a number, so listeners always receive `{value: number}`. + */ addListener(callback: ValueListenerCallback): string { - const id = super.addListener(callback); - this._listenerCount++; - if (this.__isNative) { - this.__ensureUpdateSubscriptionExists(); - } - return id; - } - - removeListener(id: string): void { - super.removeListener(id); - this._listenerCount--; - if (this.__isNative && this._listenerCount === 0) { - this._updateSubscription?.remove(); - } - } - - removeAllListeners(): void { - super.removeAllListeners(); - this._listenerCount = 0; - if (this.__isNative) { - this._updateSubscription?.remove(); - } - } - - __ensureUpdateSubscriptionExists(): void { - if (this._updateSubscription != null) { - return; - } - const nativeTag = this.__getNativeTag(); - NativeAnimatedAPI.startListeningToAnimatedNodeValue(nativeTag); - const subscription: EventSubscription = - NativeAnimatedHelper.nativeEventEmitter.addListener( - 'onAnimatedValueUpdate', - data => { - if (data.tag === nativeTag) { - this.__onAnimatedValueUpdateReceived(data.value, data.offset); - } - }, - ); - - this._updateSubscription = { - remove: () => { - // Only this function assigns to `this.#updateSubscription`. - if (this._updateSubscription == null) { - return; - } - this._updateSubscription = null; - subscription.remove(); - NativeAnimatedAPI.stopListeningToAnimatedNodeValue(nativeTag); - }, - }; + return super.addListener(callback); } /** @@ -297,7 +235,7 @@ export default class AnimatedValue extends AnimatedWithChildren { } } - __onAnimatedValueUpdateReceived(value: number, offset?: number): void { + __onAnimatedValueUpdateReceived(value: number, offset?: ?number): void { this._updateValue(value, false /*flush*/); if (offset != null) { this._offset = offset; diff --git a/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.cpp b/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.cpp index 6b7befe12f30..b21524aed354 100644 --- a/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.cpp +++ b/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.cpp @@ -51,6 +51,33 @@ struct NodesQueueItem { bool connectedToFinishedAnimation; }; +// Whether `type` is backed by a `ValueAnimatedNode` subclass, i.e. whether the +// node holds a number that can be observed. Kept exhaustive (no `default`) so +// that adding a node type is a compile error until it is classified here. +bool isValueNodeType(AnimatedNodeType type) noexcept { + switch (type) { + case AnimatedNodeType::Value: + case AnimatedNodeType::Interpolation: + case AnimatedNodeType::Addition: + case AnimatedNodeType::Subtraction: + case AnimatedNodeType::Division: + case AnimatedNodeType::Multiplication: + case AnimatedNodeType::Modulus: + case AnimatedNodeType::Diffclamp: + case AnimatedNodeType::Round: + return true; + case AnimatedNodeType::Style: + case AnimatedNodeType::Props: + case AnimatedNodeType::Transform: + case AnimatedNodeType::Tracking: + case AnimatedNodeType::Color: + case AnimatedNodeType::Object: + return false; + } + // Unreachable: the switch above is exhaustive. + return false; +} + void mergeObjects(folly::dynamic& out, const folly::dynamic& objectToMerge) { react_native_assert(objectToMerge.isObject()); if (out.isObject() && !out.empty()) { @@ -921,8 +948,8 @@ void NativeAnimatedNodesManager::resolvePlatformColor( void NativeAnimatedNodesManager::startListeningToAnimatedNodeValue( Tag tag, ValueListenerCallback&& callback) noexcept { - if (auto iter = animatedNodes_.find(tag); iter != animatedNodes_.end() && - iter->second->type() == AnimatedNodeType::Value) { + if (auto iter = animatedNodes_.find(tag); + iter != animatedNodes_.end() && isValueNodeType(iter->second->type())) { static_cast(iter->second.get()) ->setValueListener(std::move(callback)); } else { @@ -933,8 +960,8 @@ void NativeAnimatedNodesManager::startListeningToAnimatedNodeValue( void NativeAnimatedNodesManager::stopListeningToAnimatedNodeValue( Tag tag) noexcept { - if (auto iter = animatedNodes_.find(tag); iter != animatedNodes_.end() && - iter->second->type() == AnimatedNodeType::Value) { + if (auto iter = animatedNodes_.find(tag); + iter != animatedNodes_.end() && isValueNodeType(iter->second->type())) { static_cast(iter->second.get()) ->setValueListener(nullptr); } else { diff --git a/packages/react-native/ReactCommon/react/renderer/animated/tests/AnimatedNodeTests.cpp b/packages/react-native/ReactCommon/react/renderer/animated/tests/AnimatedNodeTests.cpp index 075f7a598e03..1929fa738f41 100644 --- a/packages/react-native/ReactCommon/react/renderer/animated/tests/AnimatedNodeTests.cpp +++ b/packages/react-native/ReactCommon/react/renderer/animated/tests/AnimatedNodeTests.cpp @@ -12,6 +12,7 @@ #include #include #include +#include namespace facebook::react { @@ -254,6 +255,85 @@ TEST_F(AnimatedNodeTests, RoundAnimatedNodeUsesNearestConfigKey) { EXPECT_DOUBLE_EQ(nodesManager_->getValue(roundTag).value(), 15.0); } +TEST_F(AnimatedNodeTests, StartListeningToDerivedValueNode) { + // Every node that derives from ValueAnimatedNode holds an observable number, + // so `startListeningToAnimatedNodeValue` must accept it — not only nodes of + // type "value". See https://github.com/facebook/react-native/issues/49719. + initNodesManager(); + + auto rootTag = getNextRootViewTag(); + + auto valueTag = ++rootTag; + auto addendTag = ++rootTag; + auto additionTag = ++rootTag; + + nodesManager_->createAnimatedNode( + valueTag, + folly::dynamic::object("type", "value")("value", 0)("offset", 0)); + nodesManager_->createAnimatedNode( + addendTag, + folly::dynamic::object("type", "value")("value", 10)("offset", 0)); + nodesManager_->createAnimatedNode( + additionTag, + folly::dynamic::object("type", "addition")( + "input", folly::dynamic::array(valueTag, addendTag))); + nodesManager_->connectAnimatedNodes(valueTag, additionTag); + nodesManager_->connectAnimatedNodes(addendTag, additionTag); + + std::vector observedValues; + nodesManager_->startListeningToAnimatedNodeValue( + additionTag, + [&observedValues](double value) { observedValues.push_back(value); }); + + runAnimationFrame(0); + + nodesManager_->setAnimatedNodeValue(valueTag, 32); + runAnimationFrame(0); + + ASSERT_FALSE(observedValues.empty()); + EXPECT_DOUBLE_EQ(observedValues.back(), 42); + + nodesManager_->stopListeningToAnimatedNodeValue(additionTag); + + const auto countAfterStop = observedValues.size(); + nodesManager_->setAnimatedNodeValue(valueTag, 0); + runAnimationFrame(0); + + EXPECT_EQ(observedValues.size(), countAfterStop); +} + +TEST_F(AnimatedNodeTests, StartListeningToNonValueNodeIsIgnored) { + // Nodes that do not hold a number cannot be observed. Registering a listener + // on one must be a no-op rather than an unchecked cast. + initNodesManager(); + + auto rootTag = getNextRootViewTag(); + + auto valueTag = ++rootTag; + auto transformTag = ++rootTag; + + nodesManager_->createAnimatedNode( + valueTag, + folly::dynamic::object("type", "value")("value", 1)("offset", 0)); + nodesManager_->createAnimatedNode( + transformTag, + folly::dynamic::object("type", "transform")( + "transforms", + folly::dynamic::array( + folly::dynamic::object("type", "animated")( + "property", "translateX")("nodeTag", valueTag)))); + nodesManager_->connectAnimatedNodes(valueTag, transformTag); + + bool called = false; + nodesManager_->startListeningToAnimatedNodeValue( + transformTag, [&called](double /*value*/) { called = true; }); + + nodesManager_->setAnimatedNodeValue(valueTag, 5); + runAnimationFrame(0); + + EXPECT_FALSE(called); +} + TEST_F(AnimatedNodeTests, SetOffsetReturnsFalseWhenUnchanged) { // This test verifies that setAnimatedNodeOffset doesn't trigger unnecessary // updates when the offset value hasn't changed. diff --git a/packages/react-native/ReactNativeApi.d.ts b/packages/react-native/ReactNativeApi.d.ts index 1cda27553fbe..3b73123cc6df 100644 --- a/packages/react-native/ReactNativeApi.d.ts +++ b/packages/react-native/ReactNativeApi.d.ts @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<0cdd169a97730ccd6c6fdb359f0eb30d>> + * @generated SignedSource<<5391dae1ee6f722cdac0c6590bd19a44>> * * This file was generated by scripts/js-api/build-types/index.js. */ @@ -1412,8 +1412,6 @@ declare class AnimatedValue_default extends AnimatedWithChildren_default { interpolate( config: InterpolationConfigType, ): AnimatedInterpolation_default - removeAllListeners(): void - removeListener(id: string): void resetAnimation(callback?: ((value: number) => void) | null | undefined): void setOffset(offset: number): void setValue(value: number): void @@ -5758,7 +5756,7 @@ export { AlertOptions, // 8a116d2a AlertType, // 5ab91217 AndroidKeyboardEvent, // e03becc8 - Animated, // e99db73c + Animated, // b73ca27a AppConfig, // 35c0ca70 AppRegistry, // 5bc2bced AppState, // 12012be5 @@ -5772,8 +5770,8 @@ export { BackPressEventName, // 4620fb76 BlurEvent, // 4ba4f941 BoxShadowValue, // b679703f - Button, // 78a75446 - ButtonInstance, // aecdca9d + Button, // 1618c5dc + ButtonInstance, // b62f6c43 ButtonProps, // 21c5780c Clipboard, // 41addb89 CodegenTypes, // ab4986cc @@ -5810,9 +5808,9 @@ export { EventSubscription, // b8d084aa ExtendedExceptionData, // 5a6ccf5a FilterFunction, // bf24c0e3 - FlatList, // ff6fd4b9 - FlatListInstance, // 930e6e0d - FlatListProps, // b77a3695 + FlatList, // 31e4dbb8 + FlatListInstance, // e256df59 + FlatListProps, // d134a60c FocusEvent, // 850f1517 FontVariant, // 7c7558bb GestureResponderEvent, // 14d3e77a @@ -5953,18 +5951,18 @@ export { ScrollEvent, // d7abdd0a ScrollResponderType, // 4fb54e25 ScrollToLocationParamsType, // d7ecdad1 - ScrollView, // 5b9149bf + ScrollView, // 74f481ff ScrollViewImperativeMethods, // 308dd401 ScrollViewInstance, // b86e49e8 - ScrollViewProps, // 40e3563d + ScrollViewProps, // 3bcd863e ScrollViewPropsAndroid, // 02f3df2e ScrollViewPropsIOS, // 807cb4f6 ScrollViewScrollToOptions, // 3313411e SectionBase, // 9f13db00 - SectionList, // 5ad78704 + SectionList, // f7750d02 SectionListData, // 1a4de01a - SectionListInstance, // ce15f61c - SectionListProps, // 3b9f4d91 + SectionListInstance, // 6f7c0fa0 + SectionListProps, // d1569771 SectionListRenderItem, // 715b2086 SectionListRenderItemInfo, // 4a48a922 Separators, // 6a45f7e3 @@ -5981,7 +5979,7 @@ export { StatusBarProps, // c2a44d88 StatusBarStyle, // 78f53eea StyleProp, // fa0e9b4a - StyleSheet, // f7fe407a + StyleSheet, // e2400c9e SubmitBehavior, // c4ddf490 Switch, // cf0d6ce5 SwitchChangeEvent, // 899635b1 @@ -6017,9 +6015,9 @@ export { TouchableNativeFeedback, // e2791ad5 TouchableNativeFeedbackInstance, // ce1ad7e9 TouchableNativeFeedbackProps, // ffc9c1d4 - TouchableOpacity, // fac1cc91 + TouchableOpacity, // 46c89652 TouchableOpacityInstance, // b186055b - TouchableOpacityProps, // 9e3eeec9 + TouchableOpacityProps, // d7c93220 TouchableWithoutFeedback, // da544b16 TouchableWithoutFeedbackProps, // a14626e7 TransformsStyle, // 65e70f18 @@ -6037,10 +6035,10 @@ export { VirtualViewMode, // 6be59722 VirtualizedList, // 68c7345e VirtualizedListInstance, // 423ee7c0 - VirtualizedListProps, // 5dded4a1 + VirtualizedListProps, // 89659fe0 VirtualizedSectionList, // 9fd9cd61 VirtualizedSectionListInstance, // 12b706d5 - VirtualizedSectionListProps, // ef311473 + VirtualizedSectionListProps, // 52c34787 WrapperComponentProvider, // 9ef54e61 codegenNativeCommands, // 628a7c0a codegenNativeComponent, // 520daa94 @@ -6048,9 +6046,9 @@ export { processColor, // 6e877698 registerCallableModule, // 839c8cfe requireNativeComponent, // aa36a6dd - useAnimatedColor, // 31a919f9 - useAnimatedValue, // 3eb9d3c0 - useAnimatedValueXY, // b434ca0f + useAnimatedColor, // 821ed7dc + useAnimatedValue, // d625d6a9 + useAnimatedValueXY, // a4c13498 useColorScheme, // d585efdb usePressability, // 782138ed useWindowDimensions, // bb4b683f