@office-iss/react-native-win32 0.0.0-canary.264 → 0.0.0-canary.265

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/.flowconfig +1 -1
  2. package/CHANGELOG.json +14981 -14942
  3. package/CHANGELOG.md +31 -19
  4. package/Libraries/Animated/AnimatedImplementation.js +2 -2
  5. package/Libraries/Animated/NativeAnimatedAllowlist.js +20 -9
  6. package/Libraries/Animated/animations/Animation.js +1 -4
  7. package/Libraries/Animated/createAnimatedComponent.js +13 -0
  8. package/Libraries/Animated/nodes/AnimatedNode.js +39 -45
  9. package/Libraries/Animated/nodes/AnimatedObject.js +13 -3
  10. package/Libraries/Animated/nodes/AnimatedProps.js +81 -37
  11. package/Libraries/Animated/nodes/AnimatedStyle.js +104 -39
  12. package/Libraries/Animated/nodes/AnimatedTransform.js +55 -22
  13. package/Libraries/Animated/nodes/AnimatedWithChildren.js +1 -3
  14. package/Libraries/Animated/useAnimatedProps.js +38 -20
  15. package/Libraries/Components/ProgressBarAndroid/ProgressBarAndroid.android.js +3 -1
  16. package/Libraries/Components/ScrollView/ScrollView.js +12 -9
  17. package/Libraries/Components/ScrollView/ScrollViewNativeComponent.js +3 -0
  18. package/Libraries/Components/TextInput/RCTTextInputViewConfig.js +10 -0
  19. package/Libraries/Components/TextInput/TextInput.d.ts +19 -0
  20. package/Libraries/Components/TextInput/TextInput.flow.js +17 -1
  21. package/Libraries/Components/TextInput/TextInput.js +17 -1
  22. package/Libraries/Components/TextInput/TextInput.win32.js +17 -1
  23. package/Libraries/Components/Touchable/TouchableBounce.js +1 -1
  24. package/Libraries/Components/Touchable/TouchableHighlight.js +2 -2
  25. package/Libraries/Components/Touchable/TouchableOpacity.js +1 -1
  26. package/Libraries/Components/View/ReactNativeStyleAttributes.js +6 -2
  27. package/Libraries/Core/ReactNativeVersion.js +2 -2
  28. package/Libraries/Image/AssetSourceResolver.js +12 -1
  29. package/Libraries/Modal/Modal.d.ts +7 -0
  30. package/Libraries/Modal/Modal.js +9 -1
  31. package/Libraries/NativeComponent/BaseViewConfig.android.js +7 -2
  32. package/Libraries/NativeComponent/BaseViewConfig.ios.js +11 -2
  33. package/Libraries/NativeComponent/BaseViewConfig.win32.js +1 -1
  34. package/Libraries/NativeComponent/StaticViewConfigValidator.js +0 -1
  35. package/Libraries/ReactNative/AppRegistry.js +2 -6
  36. package/Libraries/ReactNative/getNativeComponentAttributes.js +4 -0
  37. package/Libraries/Renderer/shims/ReactNativeTypes.js +3 -3
  38. package/Libraries/Renderer/shims/ReactNativeViewConfigRegistry.js +5 -6
  39. package/Libraries/StyleSheet/StyleSheetTypes.d.ts +102 -5
  40. package/Libraries/StyleSheet/StyleSheetTypes.js +9 -5
  41. package/Libraries/StyleSheet/processBoxShadow.js +5 -7
  42. package/Libraries/StyleSheet/processFilter.js +4 -4
  43. package/Libraries/Text/TextNativeComponent.js +0 -1
  44. package/Libraries/Utilities/HMRClient.js +5 -5
  45. package/overrides.json +6 -6
  46. package/package.json +18 -16
  47. package/src/private/animated/NativeAnimatedHelper.js +12 -8
  48. package/src/private/animated/NativeAnimatedHelper.win32.js +12 -8
  49. package/src/private/animated/useAnimatedPropsMemo.js +349 -0
  50. package/src/private/components/HScrollViewNativeComponents.js +9 -8
  51. package/src/private/components/SafeAreaView_INTERNAL_DO_NOT_USE.js +13 -9
  52. package/src/private/components/VScrollViewNativeComponents.js +9 -8
  53. package/src/private/featureflags/ReactNativeFeatureFlags.js +50 -22
  54. package/src/private/featureflags/ReactNativeFeatureFlagsBase.js +8 -2
  55. package/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js +7 -4
  56. package/src/private/webapis/dom/geometry/DOMRect.js +2 -2
  57. package/src/private/webapis/dom/geometry/DOMRectReadOnly.js +2 -2
  58. package/types/experimental.d.ts +0 -105
  59. package/types/modules/Codegen.d.ts +6 -0
package/overrides.json CHANGED
@@ -7,13 +7,13 @@
7
7
  "**/__snapshots__/**",
8
8
  "src-win/rntypes/**"
9
9
  ],
10
- "baseVersion": "0.76.0-nightly-20240909-143f1ad29",
10
+ "baseVersion": "0.77.0-nightly-20240921-1747f57c6",
11
11
  "overrides": [
12
12
  {
13
13
  "type": "derived",
14
14
  "file": ".flowconfig",
15
15
  "baseFile": ".flowconfig",
16
- "baseHash": "cc375839a102263f435205df68b79d3d74c4f20a"
16
+ "baseHash": "3fbaf9dcd4027fa382894d06f330d0c68ceff9fb"
17
17
  },
18
18
  {
19
19
  "type": "derived",
@@ -109,7 +109,7 @@
109
109
  "type": "derived",
110
110
  "file": "src-win/Libraries/Components/TextInput/TextInput.win32.js",
111
111
  "baseFile": "packages/react-native/Libraries/Components/TextInput/TextInput.js",
112
- "baseHash": "494c1159a38de1b57d3889a4dabdc3708204ad4d"
112
+ "baseHash": "19224f9b870df6e0c82d8da36f1f7b22bdd6b596"
113
113
  },
114
114
  {
115
115
  "type": "patch",
@@ -337,7 +337,7 @@
337
337
  "type": "derived",
338
338
  "file": "src-win/Libraries/NativeComponent/BaseViewConfig.win32.js",
339
339
  "baseFile": "packages/react-native/Libraries/NativeComponent/BaseViewConfig.ios.js",
340
- "baseHash": "91e7e2aacf6c1559247dccad0c581fde64202bf3"
340
+ "baseHash": "0b4642542c2865c5cd3d542a82b295a1aca21c1a"
341
341
  },
342
342
  {
343
343
  "type": "copy",
@@ -438,7 +438,7 @@
438
438
  "type": "derived",
439
439
  "file": "src-win/Libraries/Text/TextNativeComponent.win32.js",
440
440
  "baseFile": "packages/react-native/Libraries/Text/TextNativeComponent.js",
441
- "baseHash": "642b6fc1c5b5802e3612f39c2ae0135b149a65f8",
441
+ "baseHash": "1b2e6301edc13f3a91e47b9befe8a47a12e6ad39",
442
442
  "issue": 7074
443
443
  },
444
444
  {
@@ -509,7 +509,7 @@
509
509
  "type": "patch",
510
510
  "file": "src-win/src/private/animated/NativeAnimatedHelper.win32.js",
511
511
  "baseFile": "packages/react-native/src/private/animated/NativeAnimatedHelper.js",
512
- "baseHash": "c91921c31ad267817d7a38859005149473ca6da5",
512
+ "baseHash": "bc63f57b28f1e98755101f650a2c15b6aa263abf",
513
513
  "issue": 11041
514
514
  },
515
515
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@office-iss/react-native-win32",
3
- "version": "0.0.0-canary.264",
3
+ "version": "0.0.0-canary.265",
4
4
  "description": "Implementation of react native on top of Office's Win32 platform.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -30,17 +30,19 @@
30
30
  "@react-native-community/cli-platform-android": "14.0.0",
31
31
  "@react-native-community/cli-platform-ios": "14.0.0",
32
32
  "@react-native/assets": "1.0.0",
33
- "@react-native/assets-registry": "0.76.0-nightly-20240909-143f1ad29",
34
- "@react-native/codegen": "0.76.0-nightly-20240909-143f1ad29",
35
- "@react-native/community-cli-plugin": "0.76.0-nightly-20240909-143f1ad29",
36
- "@react-native/gradle-plugin": "0.76.0-nightly-20240909-143f1ad29",
37
- "@react-native/js-polyfills": "0.76.0-nightly-20240909-143f1ad29",
38
- "@react-native/normalize-colors": "0.76.0-nightly-20240909-143f1ad29",
39
- "@react-native/virtualized-lists": "0.76.0-nightly-20240909-143f1ad29",
33
+ "@react-native/assets-registry": "0.77.0-nightly-20240921-1747f57c6",
34
+ "@react-native/codegen": "0.77.0-nightly-20240921-1747f57c6",
35
+ "@react-native/community-cli-plugin": "0.77.0-nightly-20240921-1747f57c6",
36
+ "@react-native/gradle-plugin": "0.77.0-nightly-20240921-1747f57c6",
37
+ "@react-native/js-polyfills": "0.77.0-nightly-20240921-1747f57c6",
38
+ "@react-native/normalize-colors": "0.77.0-nightly-20240921-1747f57c6",
39
+ "@react-native/virtualized-lists": "0.77.0-nightly-20240921-1747f57c6",
40
40
  "abort-controller": "^3.0.0",
41
41
  "anser": "^1.4.9",
42
42
  "ansi-regex": "^5.0.0",
43
43
  "art": "^0.10.0",
44
+ "babel-jest": "^29.7.0",
45
+ "babel-plugin-syntax-hermes-parser": "^0.23.1",
44
46
  "base64-js": "^1.5.1",
45
47
  "chalk": "^4.0.0",
46
48
  "commander": "^12.0.0",
@@ -51,8 +53,8 @@
51
53
  "jest-environment-node": "^29.6.3",
52
54
  "jsc-android": "^250231.0.0",
53
55
  "memoize-one": "^5.0.0",
54
- "metro-runtime": "^0.80.10",
55
- "metro-source-map": "^0.80.10",
56
+ "metro-runtime": "^0.81.0-alpha.0",
57
+ "metro-source-map": "^0.81.0-alpha.0",
56
58
  "mkdirp": "^0.5.1",
57
59
  "nullthrows": "^1.1.1",
58
60
  "pretty-format": "^29.7.0",
@@ -74,9 +76,9 @@
74
76
  "@babel/eslint-parser": "^7.25.1",
75
77
  "@react-native/metro-config": "0.76.0-nightly-20240701-9f6cb21ed",
76
78
  "@rnw-scripts/babel-react-native-config": "0.0.0",
77
- "@rnw-scripts/eslint-config": "1.2.28",
78
- "@rnw-scripts/jest-out-of-tree-snapshot-resolver": "^1.1.32",
79
- "@rnw-scripts/just-task": "2.3.45",
79
+ "@rnw-scripts/eslint-config": "1.2.29",
80
+ "@rnw-scripts/jest-out-of-tree-snapshot-resolver": "^1.1.33",
81
+ "@rnw-scripts/just-task": "2.3.46",
80
82
  "@rnw-scripts/metro-dev-config": "0.0.0",
81
83
  "@rnx-kit/jest-preset": "^0.1.17",
82
84
  "@types/node": "^18.0.0",
@@ -88,14 +90,14 @@
88
90
  "just-scripts": "^1.3.3",
89
91
  "prettier": "2.8.8",
90
92
  "react": "19.0.0-rc-fb9a90fa48-20240614",
91
- "react-native": "0.76.0-nightly-20240909-143f1ad29",
92
- "react-native-platform-override": "^1.9.47",
93
+ "react-native": "0.77.0-nightly-20240921-1747f57c6",
94
+ "react-native-platform-override": "^1.9.48",
93
95
  "typescript": "5.0.4"
94
96
  },
95
97
  "peerDependencies": {
96
98
  "@types/react": "^18.2.6",
97
99
  "react": "^19.0.0-rc-fb9a90fa48-20240614",
98
- "react-native": "0.76.0-nightly-20240909-143f1ad29"
100
+ "react-native": "0.77.0-nightly-20240921-1747f57c6"
99
101
  },
100
102
  "beachball": {
101
103
  "defaultNpmTag": "canary",
@@ -46,7 +46,7 @@ const isSingleOpBatching =
46
46
  Platform.OS === 'android' &&
47
47
  NativeAnimatedModule?.queueAndExecuteBatchedOperations != null &&
48
48
  ReactNativeFeatureFlags.animatedShouldUseSingleOp();
49
- let flushQueueTimeout = null;
49
+ let flushQueueImmediate = null;
50
50
 
51
51
  const eventListenerGetValueCallbacks: {
52
52
  [number]: (value: number) => void,
@@ -142,9 +142,13 @@ const API = {
142
142
  queueOperations = true;
143
143
  if (
144
144
  ReactNativeFeatureFlags.animatedShouldDebounceQueueFlush() &&
145
- flushQueueTimeout
145
+ flushQueueImmediate
146
146
  ) {
147
- clearTimeout(flushQueueTimeout);
147
+ if (ReactNativeFeatureFlags.enableAnimatedClearImmediateFix()) {
148
+ clearImmediate(flushQueueImmediate);
149
+ } else {
150
+ clearTimeout(flushQueueImmediate);
151
+ }
148
152
  }
149
153
  },
150
154
 
@@ -161,9 +165,9 @@ const API = {
161
165
  invariant(NativeAnimatedModule, 'Native animated module is not available');
162
166
 
163
167
  if (ReactNativeFeatureFlags.animatedShouldDebounceQueueFlush()) {
164
- const prevTimeout = flushQueueTimeout;
165
- clearImmediate(prevTimeout);
166
- flushQueueTimeout = setImmediate(API.flushQueue);
168
+ const prevImmediate = flushQueueImmediate;
169
+ clearImmediate(prevImmediate);
170
+ flushQueueImmediate = setImmediate(API.flushQueue);
167
171
  } else {
168
172
  API.flushQueue();
169
173
  }
@@ -176,7 +180,7 @@ const API = {
176
180
  NativeAnimatedModule || process.env.NODE_ENV === 'test',
177
181
  'Native animated module is not available',
178
182
  );
179
- flushQueueTimeout = null;
183
+ flushQueueImmediate = null;
180
184
 
181
185
  if (singleOpQueue.length === 0) {
182
186
  return;
@@ -198,7 +202,7 @@ const API = {
198
202
  NativeAnimatedModule || process.env.NODE_ENV === 'test',
199
203
  'Native animated module is not available',
200
204
  );
201
- flushQueueTimeout = null;
205
+ flushQueueImmediate = null;
202
206
 
203
207
  if (queue.length === 0) {
204
208
  return;
@@ -46,7 +46,7 @@ const isSingleOpBatching =
46
46
  Platform.OS === 'android' &&
47
47
  NativeAnimatedModule?.queueAndExecuteBatchedOperations != null &&
48
48
  ReactNativeFeatureFlags.animatedShouldUseSingleOp();
49
- let flushQueueTimeout = null;
49
+ let flushQueueImmediate = null;
50
50
 
51
51
  const eventListenerGetValueCallbacks: {
52
52
  [number]: (value: number) => void,
@@ -142,9 +142,13 @@ const API = {
142
142
  queueOperations = true;
143
143
  if (
144
144
  ReactNativeFeatureFlags.animatedShouldDebounceQueueFlush() &&
145
- flushQueueTimeout
145
+ flushQueueImmediate
146
146
  ) {
147
- clearTimeout(flushQueueTimeout);
147
+ if (ReactNativeFeatureFlags.enableAnimatedClearImmediateFix()) {
148
+ clearImmediate(flushQueueImmediate);
149
+ } else {
150
+ clearTimeout(flushQueueImmediate);
151
+ }
148
152
  }
149
153
  },
150
154
 
@@ -161,9 +165,9 @@ const API = {
161
165
  invariant(NativeAnimatedModule, 'Native animated module is not available');
162
166
 
163
167
  if (ReactNativeFeatureFlags.animatedShouldDebounceQueueFlush()) {
164
- const prevTimeout = flushQueueTimeout;
165
- clearImmediate(prevTimeout);
166
- flushQueueTimeout = setImmediate(API.flushQueue);
168
+ const prevImmediate = flushQueueImmediate;
169
+ clearImmediate(prevImmediate);
170
+ flushQueueImmediate = setImmediate(API.flushQueue);
167
171
  } else {
168
172
  API.flushQueue();
169
173
  }
@@ -178,7 +182,7 @@ const API = {
178
182
  'Native animated module is not available',
179
183
  );
180
184
  Windows] */
181
- flushQueueTimeout = null;
185
+ flushQueueImmediate = null;
182
186
 
183
187
  if (singleOpQueue.length === 0) {
184
188
  return;
@@ -200,7 +204,7 @@ const API = {
200
204
  NativeAnimatedModule || process.env.NODE_ENV === 'test',
201
205
  'Native animated module is not available',
202
206
  );
203
- flushQueueTimeout = null;
207
+ flushQueueImmediate = null;
204
208
 
205
209
  if (queue.length === 0) {
206
210
  return;
@@ -0,0 +1,349 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow strict-local
8
+ * @format
9
+ * @oncall react_native
10
+ */
11
+
12
+ import type AnimatedProps from '../../../Libraries/Animated/nodes/AnimatedProps';
13
+ import type {AnimatedPropsAllowlist} from '../../../Libraries/Animated/nodes/AnimatedProps';
14
+ import type {AnimatedStyleAllowlist} from '../../../Libraries/Animated/nodes/AnimatedStyle';
15
+
16
+ import {AnimatedEvent} from '../../../Libraries/Animated/AnimatedEvent';
17
+ import AnimatedNode from '../../../Libraries/Animated/nodes/AnimatedNode';
18
+ import {isPlainObject} from '../../../Libraries/Animated/nodes/AnimatedObject';
19
+ import flattenStyle from '../../../Libraries/StyleSheet/flattenStyle';
20
+
21
+ import nullthrows from 'nullthrows';
22
+ import {useMemo, useState} from 'react';
23
+
24
+ type CompositeKey = {
25
+ style?: {[string]: CompositeKeyComponent},
26
+ [string]:
27
+ | CompositeKeyComponent
28
+ | AnimatedEvent
29
+ | $ReadOnlyArray<mixed>
30
+ | $ReadOnly<{[string]: mixed}>,
31
+ };
32
+
33
+ type CompositeKeyComponent =
34
+ | AnimatedNode
35
+ | Array<CompositeKeyComponent | null>
36
+ | {[string]: CompositeKeyComponent};
37
+
38
+ type $ReadOnlyCompositeKey = $ReadOnly<{
39
+ style?: $ReadOnly<{[string]: CompositeKeyComponent}>,
40
+ [string]:
41
+ | $ReadOnlyCompositeKeyComponent
42
+ | AnimatedEvent
43
+ | $ReadOnlyArray<mixed>
44
+ | $ReadOnly<{[string]: mixed}>,
45
+ }>;
46
+
47
+ type $ReadOnlyCompositeKeyComponent =
48
+ | AnimatedNode
49
+ | $ReadOnlyArray<$ReadOnlyCompositeKeyComponent | null>
50
+ | $ReadOnly<{[string]: $ReadOnlyCompositeKeyComponent}>;
51
+
52
+ /**
53
+ * A hook that returns an `AnimatedProps` object that is memoized based on the
54
+ * subset of `props` that are instances of `AnimatedNode` or `AnimatedEvent`.
55
+ */
56
+ export function useAnimatedPropsMemo(
57
+ create: () => AnimatedProps,
58
+ // TODO: Make this two separate arguments after the experiment is over. This
59
+ // is only an array-like structure to make it easier to experiment with this
60
+ // and `useMemo`.
61
+ [allowlist, props]: [?AnimatedPropsAllowlist, {[string]: mixed}],
62
+ ): AnimatedProps {
63
+ const compositeKey = useMemo(
64
+ () => createCompositeKeyForProps(props, allowlist),
65
+ [allowlist, props],
66
+ );
67
+
68
+ const [state, setState] = useState<{
69
+ allowlist: ?AnimatedPropsAllowlist,
70
+ compositeKey: $ReadOnlyCompositeKey | null,
71
+ value: AnimatedProps,
72
+ }>(() => ({
73
+ allowlist,
74
+ compositeKey,
75
+ value: create(),
76
+ }));
77
+
78
+ if (
79
+ state.allowlist !== allowlist ||
80
+ !areCompositeKeysEqual(state.compositeKey, compositeKey)
81
+ ) {
82
+ setState({
83
+ allowlist,
84
+ compositeKey,
85
+ value: create(),
86
+ });
87
+ }
88
+ return state.value;
89
+ }
90
+
91
+ /**
92
+ * Creates a new composite key for a `props` object that can be used to detect
93
+ * whether a new `AnimatedProps` instance must be created.
94
+ *
95
+ * - With an allowlist, those props are searched for `AnimatedNode` instances.
96
+ * - Without an allowlist, `style` is searched for `AnimatedNode` instances,
97
+ * but all other objects and arrays are included (not searched). We do not
98
+ * search objects and arrays without an allowlist in case they are very large
99
+ * data structures. We safely traverse `style` becuase it is bounded.
100
+ *
101
+ * Any `AnimatedEvent` instances at the first depth are always included.
102
+ *
103
+ * If `props` contains no `AnimatedNode` or `AnimatedEvent` instances, this
104
+ * returns null.
105
+ */
106
+ export function createCompositeKeyForProps(
107
+ props: $ReadOnly<{[string]: mixed}>,
108
+ allowlist: ?AnimatedPropsAllowlist,
109
+ ): $ReadOnlyCompositeKey | null {
110
+ let compositeKey: CompositeKey | null = null;
111
+
112
+ const keys = Object.keys(props);
113
+ for (let ii = 0, length = keys.length; ii < length; ii++) {
114
+ const key = keys[ii];
115
+ const value = props[key];
116
+
117
+ if (allowlist == null || Object.hasOwn(allowlist, key)) {
118
+ let compositeKeyComponent;
119
+ if (key === 'style') {
120
+ // $FlowFixMe[incompatible-call] - `style` is a valid argument.
121
+ // $FlowFixMe[incompatible-type] - `flattenStyle` returns an object.
122
+ const flatStyle: ?{[string]: mixed} = flattenStyle(value);
123
+ if (flatStyle != null) {
124
+ compositeKeyComponent = createCompositeKeyForObject(
125
+ flatStyle,
126
+ allowlist?.style,
127
+ );
128
+ }
129
+ } else if (
130
+ value instanceof AnimatedNode ||
131
+ value instanceof AnimatedEvent
132
+ ) {
133
+ compositeKeyComponent = value;
134
+ } else if (Array.isArray(value)) {
135
+ compositeKeyComponent =
136
+ allowlist == null ? value : createCompositeKeyForArray(value);
137
+ } else if (isPlainObject(value)) {
138
+ compositeKeyComponent =
139
+ allowlist == null ? value : createCompositeKeyForObject(value);
140
+ }
141
+ if (compositeKeyComponent != null) {
142
+ if (compositeKey == null) {
143
+ compositeKey = {} as CompositeKey;
144
+ }
145
+ compositeKey[key] = compositeKeyComponent;
146
+ }
147
+ }
148
+ }
149
+
150
+ return compositeKey;
151
+ }
152
+
153
+ /**
154
+ * Creates a new composite key for an array that retains all values that are or
155
+ * contain `AnimatedNode` instances, and `null` for the rest.
156
+ *
157
+ * If `array` contains no `AnimatedNode` instances, this returns null.
158
+ */
159
+ function createCompositeKeyForArray(
160
+ array: $ReadOnlyArray<mixed>,
161
+ ): $ReadOnlyArray<$ReadOnlyCompositeKeyComponent | null> | null {
162
+ let compositeKey: Array<$ReadOnlyCompositeKeyComponent | null> | null = null;
163
+
164
+ for (let ii = 0, length = array.length; ii < length; ii++) {
165
+ const value = array[ii];
166
+
167
+ let compositeKeyComponent;
168
+ if (value instanceof AnimatedNode) {
169
+ compositeKeyComponent = value;
170
+ } else if (Array.isArray(value)) {
171
+ compositeKeyComponent = createCompositeKeyForArray(value);
172
+ } else if (isPlainObject(value)) {
173
+ compositeKeyComponent = createCompositeKeyForObject(value);
174
+ }
175
+ if (compositeKeyComponent != null) {
176
+ if (compositeKey == null) {
177
+ compositeKey = new Array<$ReadOnlyCompositeKeyComponent | null>(
178
+ array.length,
179
+ ).fill(null);
180
+ }
181
+ compositeKey[ii] = compositeKeyComponent;
182
+ }
183
+ }
184
+
185
+ return compositeKey;
186
+ }
187
+
188
+ /**
189
+ * Creates a new composite key for an object that retains only properties that
190
+ * are or contain `AnimatedNode` instances.
191
+ *
192
+ * When used to create composite keys for `style` props:
193
+ *
194
+ * - With an allowlist, those properties are searched.
195
+ * - Without an allowlist, every property is searched.
196
+ *
197
+ * If `object` contains no `AnimatedNode` instances, this returns null.
198
+ */
199
+ function createCompositeKeyForObject(
200
+ object: $ReadOnly<{[string]: mixed}>,
201
+ allowlist?: ?AnimatedStyleAllowlist,
202
+ ): $ReadOnly<{[string]: $ReadOnlyCompositeKeyComponent}> | null {
203
+ let compositeKey: {[string]: $ReadOnlyCompositeKeyComponent} | null = null;
204
+
205
+ const keys = Object.keys(object);
206
+ for (let ii = 0, length = keys.length; ii < length; ii++) {
207
+ const key = keys[ii];
208
+
209
+ if (allowlist == null || Object.hasOwn(allowlist, key)) {
210
+ const value = object[key];
211
+
212
+ let compositeKeyComponent;
213
+ if (value instanceof AnimatedNode) {
214
+ compositeKeyComponent = value;
215
+ } else if (Array.isArray(value)) {
216
+ compositeKeyComponent = createCompositeKeyForArray(value);
217
+ } else if (isPlainObject(value)) {
218
+ compositeKeyComponent = createCompositeKeyForObject(value);
219
+ }
220
+ if (compositeKeyComponent != null) {
221
+ if (compositeKey == null) {
222
+ compositeKey = {} as {[string]: $ReadOnlyCompositeKeyComponent};
223
+ }
224
+ compositeKey[key] = compositeKeyComponent;
225
+ }
226
+ }
227
+ }
228
+
229
+ return compositeKey;
230
+ }
231
+
232
+ export function areCompositeKeysEqual(
233
+ maybePrev: $ReadOnlyCompositeKey | null,
234
+ maybeNext: $ReadOnlyCompositeKey | null,
235
+ allowlist: ?AnimatedPropsAllowlist,
236
+ ): boolean {
237
+ if (maybePrev === maybeNext) {
238
+ return true;
239
+ }
240
+ if (maybePrev === null || maybeNext === null) {
241
+ return false;
242
+ }
243
+ // Help Flow retain the type refinements of these.
244
+ const prev = maybePrev;
245
+ const next = maybeNext;
246
+
247
+ const keys = Object.keys(prev);
248
+ const length = keys.length;
249
+ if (length !== Object.keys(next).length) {
250
+ return false;
251
+ }
252
+ for (let ii = 0; ii < length; ii++) {
253
+ const key = keys[ii];
254
+ if (!Object.hasOwn(next, key)) {
255
+ return false;
256
+ }
257
+ const prevComponent = prev[key];
258
+ const nextComponent = next[key];
259
+
260
+ if (key === 'style') {
261
+ // We know style components are objects with non-mixed values.
262
+ if (
263
+ !areCompositeKeyComponentsEqual(
264
+ // $FlowIgnore[incompatible-cast]
265
+ prevComponent as $ReadOnlyCompositeKeyComponent,
266
+ // $FlowIgnore[incompatible-cast]
267
+ nextComponent as $ReadOnlyCompositeKeyComponent,
268
+ )
269
+ ) {
270
+ return false;
271
+ }
272
+ } else if (
273
+ prevComponent instanceof AnimatedNode ||
274
+ prevComponent instanceof AnimatedEvent
275
+ ) {
276
+ if (prevComponent !== nextComponent) {
277
+ return false;
278
+ }
279
+ } else {
280
+ // When `allowlist` is null, the components must be the same. Otherwise,
281
+ // we created the components using deep traversal, so deep compare them.
282
+ if (allowlist == null) {
283
+ if (prevComponent !== nextComponent) {
284
+ return false;
285
+ }
286
+ } else {
287
+ if (
288
+ !areCompositeKeyComponentsEqual(
289
+ // $FlowIgnore[incompatible-cast]
290
+ prevComponent as $ReadOnlyCompositeKeyComponent,
291
+ // $FlowIgnore[incompatible-cast]
292
+ nextComponent as $ReadOnlyCompositeKeyComponent,
293
+ )
294
+ ) {
295
+ return false;
296
+ }
297
+ }
298
+ }
299
+ }
300
+ return true;
301
+ }
302
+
303
+ function areCompositeKeyComponentsEqual(
304
+ prev: $ReadOnlyCompositeKeyComponent | null,
305
+ next: $ReadOnlyCompositeKeyComponent | null,
306
+ ): boolean {
307
+ if (prev === next) {
308
+ return true;
309
+ }
310
+ if (prev instanceof AnimatedNode) {
311
+ return prev === next;
312
+ }
313
+ if (Array.isArray(prev)) {
314
+ if (!Array.isArray(next)) {
315
+ return false;
316
+ }
317
+ const length = prev.length;
318
+ if (length !== next.length) {
319
+ return false;
320
+ }
321
+ for (let ii = 0; ii < length; ii++) {
322
+ if (!areCompositeKeyComponentsEqual(prev[ii], next[ii])) {
323
+ return false;
324
+ }
325
+ }
326
+ return true;
327
+ }
328
+ if (isPlainObject(prev)) {
329
+ if (!isPlainObject(next)) {
330
+ return false;
331
+ }
332
+ const keys = Object.keys(prev);
333
+ const length = keys.length;
334
+ if (length !== Object.keys(next).length) {
335
+ return false;
336
+ }
337
+ for (let ii = 0; ii < length; ii++) {
338
+ const key = keys[ii];
339
+ if (
340
+ !Object.hasOwn(nullthrows(next), key) ||
341
+ !areCompositeKeyComponentsEqual(prev[key], next[key])
342
+ ) {
343
+ return false;
344
+ }
345
+ }
346
+ return true;
347
+ }
348
+ return false;
349
+ }
@@ -21,33 +21,34 @@ import Platform from '../../../Libraries/Utilities/Platform';
21
21
  import AndroidHorizontalScrollContentViewNativeComponent from '../specs/components/AndroidHorizontalScrollContentViewNativeComponent';
22
22
  import useSyncOnScroll from './useSyncOnScroll';
23
23
  import * as React from 'react';
24
+ import {forwardRef} from 'react';
24
25
 
25
26
  const HScrollViewNativeComponentForPlatform =
26
27
  Platform.OS === 'android'
27
28
  ? AndroidHorizontalScrollViewNativeComponent
28
29
  : ScrollViewNativeComponent;
29
30
 
31
+ // TODO: After upgrading to React 19, remove `forwardRef` from this component.
30
32
  export const HScrollViewNativeComponent: React.AbstractComponent<
31
33
  ScrollViewNativeProps,
32
34
  TScrollViewNativeImperativeHandle,
33
35
  // $FlowExpectedError[incompatible-type] - Flow cannot model imperative handles, yet.
34
- > = function HScrollViewNativeComponent(props: {
35
- ...ScrollViewNativeProps,
36
- ref?: React.RefSetter<TScrollViewNativeImperativeHandle | null>,
37
- ...
38
- }): React.Node {
39
- const [ref, enableSyncOnScroll] = useSyncOnScroll(props.ref);
36
+ > = forwardRef(function HScrollViewNativeComponent(
37
+ props: ScrollViewNativeProps,
38
+ ref: ?React.RefSetter<TScrollViewNativeImperativeHandle | null>,
39
+ ): React.Node {
40
+ const [componentRef, enableSyncOnScroll] = useSyncOnScroll(ref);
40
41
  // NOTE: When `useSyncOnScroll` triggers an update, `props` will not have
41
42
  // changed. Notably, `props.children` will be the same, allowing React to
42
43
  // bail out during reconciliation.
43
44
  return (
44
45
  <HScrollViewNativeComponentForPlatform
45
46
  {...props}
46
- ref={ref}
47
+ ref={componentRef}
47
48
  enableSyncOnScroll={enableSyncOnScroll}
48
49
  />
49
50
  );
50
- };
51
+ });
51
52
 
52
53
  export const HScrollContentViewNativeComponent: HostComponent<ViewProps> =
53
54
  Platform.OS === 'android'
@@ -12,16 +12,20 @@
12
12
  import type {ViewProps} from '../../../Libraries/Components/View/ViewPropTypes';
13
13
  import Platform from '../../../Libraries/Utilities/Platform';
14
14
  import View from '../../../Libraries/Components/View/View';
15
+ import UIManager from '../../../Libraries/ReactNative/UIManager';
15
16
  import * as React from 'react';
16
- export * from '../../../src/private/specs/components/RCTSafeAreaViewNativeComponent';
17
- import RCTSafeAreaViewNativeComponent from '../../../src/private/specs/components/RCTSafeAreaViewNativeComponent';
18
17
 
19
- let exported: React.AbstractComponent<ViewProps, React.ElementRef<typeof View>>;
20
-
21
- if (Platform.OS === 'android' || Platform.OS === 'ios') {
22
- exported = RCTSafeAreaViewNativeComponent;
23
- } else {
24
- exported = View;
25
- }
18
+ const exported: React.AbstractComponent<
19
+ ViewProps,
20
+ React.ElementRef<typeof View>,
21
+ > = Platform.select({
22
+ ios: require('../../../src/private/specs/components/RCTSafeAreaViewNativeComponent')
23
+ .default,
24
+ android: UIManager.hasViewManagerConfig('RCTSafeAreaView')
25
+ ? require('../../../src/private/specs/components/RCTSafeAreaViewNativeComponent')
26
+ .default
27
+ : View,
28
+ default: View,
29
+ });
26
30
 
27
31
  export default exported;