@office-iss/react-native-win32 0.0.0-canary.260 → 0.0.0-canary.261

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 (30) hide show
  1. package/.flowconfig +4 -3
  2. package/CHANGELOG.json +16 -1
  3. package/CHANGELOG.md +12 -4
  4. package/Libraries/Core/ReactNativeVersion.js +1 -1
  5. package/Libraries/Lists/SectionListModern.js +2 -2
  6. package/Libraries/NativeComponent/NativeComponentRegistry.js +13 -20
  7. package/Libraries/NativeComponent/StaticViewConfigValidator.js +0 -21
  8. package/Libraries/ReactNative/RendererImplementation.js +2 -2
  9. package/Libraries/Renderer/shims/ReactNativeTypes.js +9 -4
  10. package/Libraries/StyleSheet/StyleSheetTypes.d.ts +3 -3
  11. package/Libraries/StyleSheet/StyleSheetTypes.js +1 -1
  12. package/Libraries/StyleSheet/processBackgroundImage.js +136 -38
  13. package/Libraries/StyleSheet/processFilter.js +4 -4
  14. package/Libraries/Utilities/Appearance.js +65 -73
  15. package/Libraries/Utilities/DevLoadingView.js +2 -4
  16. package/Libraries/Utilities/stringifyViewConfig.js +22 -0
  17. package/Libraries/Utilities/useColorScheme.js +3 -3
  18. package/index.js +1 -1
  19. package/index.win32.js +1 -1
  20. package/jest/setup.js +0 -4
  21. package/overrides.json +3 -3
  22. package/package.json +11 -11
  23. package/src/private/featureflags/ReactNativeFeatureFlags.js +12 -6
  24. package/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js +2 -2
  25. package/src/private/specs/modules/NativeAppearance.js +3 -3
  26. package/src/private/webapis/intersectionobserver/IntersectionObserver.js +4 -2
  27. package/src/private/webapis/intersectionobserver/IntersectionObserverManager.js +9 -9
  28. package/src/private/webapis/mutationobserver/MutationObserver.js +4 -2
  29. package/src/private/webapis/mutationobserver/MutationObserverManager.js +19 -10
  30. package/Libraries/Utilities/verifyComponentAttributeEquivalence.js +0 -135
package/.flowconfig CHANGED
@@ -54,8 +54,6 @@
54
54
  .*/node_modules/sample-apps/.*
55
55
  .*/node_modules/playground/.*
56
56
 
57
- ; Ignore templates for 'npx @react-native-community/cli init'
58
- <PROJECT_ROOT>/packages/react-native/template/.*
59
57
  <PROJECT_ROOT>/packages/react-native/sdks/.*
60
58
 
61
59
  ; Ignore the codegen e2e tests
@@ -67,6 +65,9 @@
67
65
  ; Ignore "BUCK" generated dirs
68
66
  <PROJECT_ROOT>/\.buckd/
69
67
 
68
+ ; Ignore cache files
69
+ .*/node_modules/.cache*
70
+
70
71
  ; Ignore the src-win folder - flow files are combined with ones from react-native into the root Libraries folder
71
72
  .*/react-native-win32/src-win/.*
72
73
 
@@ -159,4 +160,4 @@ untyped-import
159
160
  untyped-type-import
160
161
 
161
162
  [version]
162
- ^0.243.0
163
+ ^0.244.0
package/CHANGELOG.json CHANGED
@@ -2,7 +2,22 @@
2
2
  "name": "@office-iss/react-native-win32",
3
3
  "entries": [
4
4
  {
5
- "date": "Wed, 28 Aug 2024 05:14:05 GMT",
5
+ "date": "Thu, 05 Sep 2024 05:13:08 GMT",
6
+ "version": "0.0.0-canary.261",
7
+ "tag": "@office-iss/react-native-win32_v0.0.0-canary.261",
8
+ "comments": {
9
+ "prerelease": [
10
+ {
11
+ "author": "34109996+chiaramooney@users.noreply.github.com",
12
+ "package": "@office-iss/react-native-win32",
13
+ "commit": "277d6f06a3e1bf1650cd9d1e7b4f22e0619968e9",
14
+ "comment": "Integrate 8/20"
15
+ }
16
+ ]
17
+ }
18
+ },
19
+ {
20
+ "date": "Wed, 28 Aug 2024 05:14:44 GMT",
6
21
  "version": "0.0.0-canary.260",
7
22
  "tag": "@office-iss/react-native-win32_v0.0.0-canary.260",
8
23
  "comments": {
package/CHANGELOG.md CHANGED
@@ -1,17 +1,25 @@
1
1
  # Change Log - @office-iss/react-native-win32
2
2
 
3
- This log was last generated on Wed, 28 Aug 2024 05:14:05 GMT and should not be manually modified.
3
+ This log was last generated on Thu, 05 Sep 2024 05:13:08 GMT and should not be manually modified.
4
4
 
5
5
  <!-- Start content -->
6
6
 
7
- ## 0.0.0-canary.260
7
+ ## 0.0.0-canary.261
8
8
 
9
- Wed, 28 Aug 2024 05:14:05 GMT
9
+ Thu, 05 Sep 2024 05:13:08 GMT
10
10
 
11
11
  ### Changes
12
12
 
13
- - integrate 0.76.0-nightly-20240816-17017d2b8 (tatianakapos@microsoft.com)
13
+ - Integrate 8/20 (34109996+chiaramooney@users.noreply.github.com)
14
14
 
15
+ ## 0.0.0-canary.260
16
+
17
+ Wed, 28 Aug 2024 05:14:44 GMT
18
+
19
+ ### Changes
20
+
21
+ - integrate 0.76.0-nightly-20240816-17017d2b8 (tatianakapos@microsoft.com)
22
+
15
23
  ## 0.0.0-canary.259
16
24
 
17
25
  Thu, 22 Aug 2024 05:24:27 GMT
@@ -17,7 +17,7 @@ const version: $ReadOnly<{
17
17
  major: 0,
18
18
  minor: 76,
19
19
  patch: 0,
20
- prerelease: 'nightly-20240816-17017d2b8',
20
+ prerelease: 'nightly-20240824-09e88448c',
21
21
  };
22
22
 
23
23
  module.exports = {version};
@@ -16,7 +16,7 @@ import type {
16
16
  SectionBase as _SectionBase,
17
17
  VirtualizedSectionListProps,
18
18
  } from '@react-native/virtualized-lists';
19
- import type {AbstractComponent, Element, ElementRef} from 'react';
19
+ import type {AbstractComponent, ElementRef} from 'react';
20
20
 
21
21
  import Platform from '../Utilities/Platform';
22
22
  import {VirtualizedSectionList} from '@react-native/virtualized-lists';
@@ -56,7 +56,7 @@ type OptionalProps<SectionT: SectionBase<any>> = {|
56
56
  ...
57
57
  },
58
58
  ...
59
- }) => null | Element<any>,
59
+ }) => null | React.MixedElement,
60
60
  /**
61
61
  * A marker property for telling the list to re-render (since it implements `PureComponent`). If
62
62
  * any of your `renderItem`, Header, Footer, etc. functions depend on anything outside of the
@@ -17,7 +17,6 @@ import type {
17
17
  import getNativeComponentAttributes from '../ReactNative/getNativeComponentAttributes';
18
18
  import UIManager from '../ReactNative/UIManager';
19
19
  import * as ReactNativeViewConfigRegistry from '../Renderer/shims/ReactNativeViewConfigRegistry';
20
- import verifyComponentAttributeEquivalence from '../Utilities/verifyComponentAttributeEquivalence';
21
20
  import * as StaticViewConfigValidator from './StaticViewConfigValidator';
22
21
  import {createViewConfig} from './ViewConfig';
23
22
  import invariant from 'invariant';
@@ -35,7 +34,6 @@ let getRuntimeConfig;
35
34
  export function setRuntimeConfigProvider(
36
35
  runtimeConfigProvider: (name: string) => ?{
37
36
  native: boolean,
38
- strict: boolean,
39
37
  verify: boolean,
40
38
  },
41
39
  ): void {
@@ -55,9 +53,8 @@ export function get<Config>(
55
53
  viewConfigProvider: () => PartialViewConfig,
56
54
  ): HostComponent<Config> {
57
55
  ReactNativeViewConfigRegistry.register(name, () => {
58
- const {native, strict, verify} = getRuntimeConfig?.(name) ?? {
56
+ const {native, verify} = getRuntimeConfig?.(name) ?? {
59
57
  native: !global.RN$Bridgeless,
60
- strict: false,
61
58
  verify: false,
62
59
  };
63
60
 
@@ -92,23 +89,19 @@ export function get<Config>(
92
89
  ? createViewConfig(viewConfigProvider())
93
90
  : viewConfig;
94
91
 
95
- if (strict) {
96
- const validationOutput = StaticViewConfigValidator.validate(
97
- name,
98
- nativeViewConfig,
99
- staticViewConfig,
92
+ const validationOutput = StaticViewConfigValidator.validate(
93
+ name,
94
+ nativeViewConfig,
95
+ staticViewConfig,
96
+ );
97
+
98
+ if (validationOutput.type === 'invalid') {
99
+ console.error(
100
+ StaticViewConfigValidator.stringifyValidationResult(
101
+ name,
102
+ validationOutput,
103
+ ),
100
104
  );
101
-
102
- if (validationOutput.type === 'invalid') {
103
- console.error(
104
- StaticViewConfigValidator.stringifyValidationResult(
105
- name,
106
- validationOutput,
107
- ),
108
- );
109
- }
110
- } else {
111
- verifyComponentAttributeEquivalence(nativeViewConfig, staticViewConfig);
112
105
  }
113
106
  }
114
107
 
@@ -22,11 +22,6 @@ export type Difference =
22
22
  path: Array<string>,
23
23
  nativeValue: mixed,
24
24
  staticValue: mixed,
25
- }
26
- | {
27
- type: 'unexpected',
28
- path: Array<string>,
29
- staticValue: mixed,
30
25
  };
31
26
 
32
27
  export type ValidationResult = ValidResult | InvalidResult;
@@ -90,8 +85,6 @@ export function stringifyValidationResult(
90
85
  return `- '${path.join('.')}' is missing.`;
91
86
  case 'unequal':
92
87
  return `- '${path.join('.')}' is the wrong value.`;
93
- case 'unexpected':
94
- return `- '${path.join('.')}' is present but not expected to be.`;
95
88
  }
96
89
  }),
97
90
  '',
@@ -145,20 +138,6 @@ function accumulateDifferences(
145
138
  });
146
139
  }
147
140
  }
148
-
149
- for (const staticKey in staticObject) {
150
- if (
151
- !nativeObject.hasOwnProperty(staticKey) &&
152
- // $FlowFixMe[invalid-computed-prop]
153
- !isIgnored(staticObject[staticKey])
154
- ) {
155
- differences.push({
156
- path: [...path, staticKey],
157
- type: 'unexpected',
158
- staticValue: staticObject[staticKey],
159
- });
160
- }
161
- }
162
141
  }
163
142
 
164
143
  function ifObject(value: mixed): ?{...} {
@@ -14,7 +14,7 @@ import type {
14
14
  Node,
15
15
  } from '../Renderer/shims/ReactNativeTypes';
16
16
  import type ReactFabricHostComponent from './ReactFabricPublicInstance/ReactFabricHostComponent';
17
- import type {Element, ElementRef, ElementType} from 'react';
17
+ import type {ElementRef, ElementType} from 'react';
18
18
 
19
19
  import {
20
20
  onCaughtError,
@@ -28,7 +28,7 @@ export function renderElement({
28
28
  useFabric,
29
29
  useConcurrentRoot,
30
30
  }: {
31
- element: Element<ElementType>,
31
+ element: React.MixedElement,
32
32
  rootTag: number,
33
33
  useFabric: boolean,
34
34
  useConcurrentRoot: boolean,
@@ -7,10 +7,15 @@
7
7
  * @noformat
8
8
  * @nolint
9
9
  * @flow strict
10
- * @generated SignedSource<<4405023a5d82ddc01db31d8eb46a7aa0>>
10
+ * @generated SignedSource<<89361333bb6b688486e35849a9c669a6>>
11
11
  */
12
12
 
13
- import type {ElementRef, ElementType, Element, AbstractComponent} from 'react';
13
+ import type {
14
+ ElementRef,
15
+ ElementType,
16
+ MixedElement,
17
+ AbstractComponent,
18
+ } from 'react';
14
19
 
15
20
  export type MeasureOnSuccessCallback = (
16
21
  x: number,
@@ -222,7 +227,7 @@ export type ReactNativeType = {
222
227
  eventType: string,
223
228
  ): void,
224
229
  render(
225
- element: Element<ElementType>,
230
+ element: MixedElement,
226
231
  containerTag: number,
227
232
  callback: ?() => void,
228
233
  options: ?RenderRootOptions,
@@ -257,7 +262,7 @@ export type ReactFabricType = {
257
262
  eventType: string,
258
263
  ): void,
259
264
  render(
260
- element: Element<ElementType>,
265
+ element: MixedElement,
261
266
  containerTag: number,
262
267
  callback: ?() => void,
263
268
  concurrentRoot: ?boolean,
@@ -276,10 +276,10 @@ export type BlendMode =
276
276
  export type GradientValue = {
277
277
  type: 'linearGradient';
278
278
  // Angle or direction enums
279
- direction: string | undefined;
280
- colorStops: Array<{
279
+ direction?: string | undefined;
280
+ colorStops: ReadonlyArray<{
281
281
  color: ColorValue;
282
- position: number | undefined;
282
+ positions?: ReadonlyArray<string[]> | undefined;
283
283
  }>;
284
284
  };
285
285
 
@@ -715,7 +715,7 @@ export type GradientValue = {
715
715
  direction?: string,
716
716
  colorStops: $ReadOnlyArray<{
717
717
  color: ____ColorValue_Internal,
718
- position?: string,
718
+ positions?: $ReadOnlyArray<string>,
719
719
  }>,
720
720
  };
721
721
 
@@ -45,33 +45,34 @@ export default function processBackgroundImage(
45
45
  result = parseCSSLinearGradient(backgroundImage);
46
46
  } else if (Array.isArray(backgroundImage)) {
47
47
  for (const bgImage of backgroundImage) {
48
- const processedColorStops = [];
48
+ const processedColorStops: Array<{
49
+ color: ProcessedColorValue,
50
+ position: number | null,
51
+ }> = [];
49
52
  for (let index = 0; index < bgImage.colorStops.length; index++) {
50
- const stop = bgImage.colorStops[index];
51
- const processedColor = processColor(stop.color);
52
- let processedPosition: number | null = null;
53
-
54
- // Currently we only support percentage and undefined value for color stop position.
55
- if (typeof stop.position === 'undefined') {
56
- processedPosition =
57
- bgImage.colorStops.length === 1
58
- ? 1
59
- : index / (bgImage.colorStops.length - 1);
60
- } else if (stop.position.endsWith('%')) {
61
- processedPosition = parseFloat(stop.position) / 100;
62
- } else {
63
- // If a color stop position is invalid, return an empty array and do not apply gradient. Same as web.
53
+ const colorStop = bgImage.colorStops[index];
54
+ const processedColor = processColor(colorStop.color);
55
+ if (processedColor == null) {
56
+ // If a color is invalid, return an empty array and do not apply gradient. Same as web.
64
57
  return [];
65
58
  }
66
-
67
- if (processedColor != null) {
59
+ if (colorStop.positions != null && colorStop.positions.length > 0) {
60
+ for (const position of colorStop.positions) {
61
+ if (position.endsWith('%')) {
62
+ processedColorStops.push({
63
+ color: processedColor,
64
+ position: parseFloat(position) / 100,
65
+ });
66
+ } else {
67
+ // If a position is invalid, return an empty array and do not apply gradient. Same as web.
68
+ return [];
69
+ }
70
+ }
71
+ } else {
68
72
  processedColorStops.push({
69
73
  color: processedColor,
70
- position: processedPosition,
74
+ position: null,
71
75
  });
72
- } else {
73
- // If a color is invalid, return an empty array and do not apply gradient. Same as web.
74
- return [];
75
76
  }
76
77
  }
77
78
 
@@ -96,12 +97,14 @@ export default function processBackgroundImage(
96
97
  }
97
98
  }
98
99
 
100
+ const fixedColorStops = getFixedColorStops(processedColorStops);
101
+
99
102
  if (points != null) {
100
103
  result = result.concat({
101
104
  type: 'linearGradient',
102
105
  start: points.start,
103
106
  end: points.end,
104
- colorStops: processedColorStops,
107
+ colorStops: fixedColorStops,
105
108
  });
106
109
  }
107
110
  }
@@ -123,7 +126,7 @@ function parseCSSLinearGradient(
123
126
  let points = TO_BOTTOM_START_END_POINTS;
124
127
  const trimmedDirection = parts[0].trim().toLowerCase();
125
128
  const colorStopRegex =
126
- /\s*((?:(?:rgba?|hsla?)\s*\([^)]+\))|#[0-9a-fA-F]+|[a-zA-Z]+)(?:\s+([0-9.]+%?))?\s*/gi;
129
+ /\s*((?:(?:rgba?|hsla?)\s*\([^)]+\))|#[0-9a-fA-F]+|[a-zA-Z]+)(?:\s+(-?[0-9.]+%?)(?:\s+(-?[0-9.]+%?))?)?\s*/gi;
127
130
 
128
131
  if (ANGLE_UNIT_REGEX.test(trimmedDirection)) {
129
132
  const angle = parseAngle(trimmedDirection);
@@ -154,32 +157,50 @@ function parseCSSLinearGradient(
154
157
  const fullColorStopsStr = parts.join(',');
155
158
  let colorStopMatch;
156
159
  while ((colorStopMatch = colorStopRegex.exec(fullColorStopsStr))) {
157
- const [, color, position] = colorStopMatch;
160
+ const [, color, position1, position2] = colorStopMatch;
158
161
  const processedColor = processColor(color.trim().toLowerCase());
159
- if (
160
- processedColor != null &&
161
- (typeof position === 'undefined' || position.endsWith('%'))
162
- ) {
162
+ if (processedColor == null) {
163
+ // If a color is invalid, return an empty array and do not apply any gradient. Same as web.
164
+ return [];
165
+ }
166
+
167
+ if (typeof position1 !== 'undefined') {
168
+ if (position1.endsWith('%')) {
169
+ colorStops.push({
170
+ color: processedColor,
171
+ position: parseFloat(position1) / 100,
172
+ });
173
+ } else {
174
+ // If a position is invalid, return an empty array and do not apply any gradient. Same as web.
175
+ return [];
176
+ }
177
+ } else {
163
178
  colorStops.push({
164
179
  color: processedColor,
165
- position: position ? parseFloat(position) / 100 : null,
180
+ position: null,
166
181
  });
167
- } else {
168
- // If a color or position is invalid, return an empty array and do not apply any gradient. Same as web.
169
- return [];
182
+ }
183
+
184
+ if (typeof position2 !== 'undefined') {
185
+ if (position2.endsWith('%')) {
186
+ colorStops.push({
187
+ color: processedColor,
188
+ position: parseFloat(position2) / 100,
189
+ });
190
+ } else {
191
+ // If a position is invalid, return an empty array and do not apply any gradient. Same as web.
192
+ return [];
193
+ }
170
194
  }
171
195
  }
172
196
 
197
+ const fixedColorStops = getFixedColorStops(colorStops);
198
+
173
199
  gradients.push({
174
200
  type: 'linearGradient',
175
201
  start: points.start,
176
202
  end: points.end,
177
- colorStops: colorStops.map((stop, index, array) => ({
178
- color: stop.color,
179
- position:
180
- stop.position ??
181
- (array.length === 1 ? 1 : index / (array.length - 1)),
182
- })),
203
+ colorStops: fixedColorStops,
183
204
  });
184
205
  }
185
206
 
@@ -284,3 +305,80 @@ function parseAngle(angle: string): ?number {
284
305
  return null;
285
306
  }
286
307
  }
308
+
309
+ // https://drafts.csswg.org/css-images-4/#color-stop-fixup
310
+ function getFixedColorStops(
311
+ colorStops: $ReadOnlyArray<{
312
+ color: ProcessedColorValue,
313
+ position: number | null,
314
+ }>,
315
+ ): Array<{
316
+ color: ProcessedColorValue,
317
+ position: number,
318
+ }> {
319
+ let fixedColorStops: Array<{
320
+ color: ProcessedColorValue,
321
+ position: number,
322
+ }> = [];
323
+ let hasNullPositions = false;
324
+ let maxPositionSoFar = colorStops[0].position ?? 0;
325
+ for (let i = 0; i < colorStops.length; i++) {
326
+ const colorStop = colorStops[i];
327
+ let newPosition = colorStop.position;
328
+ if (newPosition === null) {
329
+ // Step 1:
330
+ // If the first color stop does not have a position,
331
+ // set its position to 0%. If the last color stop does not have a position,
332
+ // set its position to 100%.
333
+ if (i === 0) {
334
+ newPosition = 0;
335
+ } else if (i === colorStops.length - 1) {
336
+ newPosition = 1;
337
+ }
338
+ }
339
+ // Step 2:
340
+ // If a color stop or transition hint has a position
341
+ // that is less than the specified position of any color stop or transition hint
342
+ // before it in the list, set its position to be equal to the
343
+ // largest specified position of any color stop or transition hint before it.
344
+ if (newPosition !== null) {
345
+ newPosition = Math.max(newPosition, maxPositionSoFar);
346
+ fixedColorStops[i] = {
347
+ color: colorStop.color,
348
+ position: newPosition,
349
+ };
350
+ maxPositionSoFar = newPosition;
351
+ } else {
352
+ hasNullPositions = true;
353
+ }
354
+ }
355
+
356
+ // Step 3:
357
+ // If any color stop still does not have a position,
358
+ // then, for each run of adjacent color stops without positions,
359
+ // set their positions so that they are evenly spaced between the preceding and
360
+ // following color stops with positions.
361
+ if (hasNullPositions) {
362
+ let lastDefinedIndex = 0;
363
+ for (let i = 1; i < fixedColorStops.length; i++) {
364
+ if (fixedColorStops[i] !== undefined) {
365
+ const unpositionedStops = i - lastDefinedIndex - 1;
366
+ if (unpositionedStops > 0) {
367
+ const startPosition = fixedColorStops[lastDefinedIndex].position;
368
+ const endPosition = fixedColorStops[i].position;
369
+ const increment =
370
+ (endPosition - startPosition) / (unpositionedStops + 1);
371
+ for (let j = 1; j <= unpositionedStops; j++) {
372
+ fixedColorStops[lastDefinedIndex + j] = {
373
+ color: colorStops[lastDefinedIndex + j].color,
374
+ position: startPosition + increment * j,
375
+ };
376
+ }
377
+ }
378
+ lastDefinedIndex = i;
379
+ }
380
+ }
381
+ }
382
+
383
+ return fixedColorStops;
384
+ }
@@ -44,8 +44,8 @@ export default function processFilter(
44
44
  }
45
45
 
46
46
  if (typeof filter === 'string') {
47
- // matches on functions with args like "drop-shadow(1.5)"
48
- const regex = /([\w-]+)\(([^)]+)\)/g;
47
+ // matches on functions with args and nested functions like "drop-shadow(10 10 10 rgba(0, 0, 0, 1))"
48
+ const regex = /([\w-]+)\(([^()]*|\([^()]*\)|[^()]*\([^()]*\)[^()]*)\)/g;
49
49
  let matches;
50
50
 
51
51
  while ((matches = regex.exec(filter))) {
@@ -254,8 +254,8 @@ function parseDropShadowString(rawDropShadow: string): ?DropShadowPrimitive {
254
254
  let lengthCount = 0;
255
255
  let keywordDetectedAfterLength = false;
256
256
 
257
- // split on all whitespaces
258
- for (const arg of rawDropShadow.split(/\s+/)) {
257
+ // split args by all whitespaces that are not in parenthesis
258
+ for (const arg of rawDropShadow.split(/\s+(?![^(]*\))/)) {
259
259
  const processedColor = processColor(arg);
260
260
  if (processedColor != null) {
261
261
  if (dropShadow.color != null) {
@@ -9,7 +9,6 @@
9
9
  */
10
10
 
11
11
  import NativeEventEmitter from '../EventEmitter/NativeEventEmitter';
12
- import Platform from '../Utilities/Platform';
13
12
  import EventEmitter, {
14
13
  type EventSubscription,
15
14
  } from '../vendor/emitter/EventEmitter';
@@ -20,88 +19,81 @@ import NativeAppearance, {
20
19
  } from './NativeAppearance';
21
20
  import invariant from 'invariant';
22
21
 
23
- type AppearanceListener = (preferences: AppearancePreferences) => void;
24
22
  const eventEmitter = new EventEmitter<{
25
- change: [AppearancePreferences],
23
+ change: [{colorScheme: ?ColorSchemeName}],
26
24
  }>();
27
25
 
28
26
  type NativeAppearanceEventDefinitions = {
29
27
  appearanceChanged: [AppearancePreferences],
30
28
  };
31
29
 
32
- if (NativeAppearance) {
33
- const nativeEventEmitter =
34
- new NativeEventEmitter<NativeAppearanceEventDefinitions>(
35
- // T88715063: NativeEventEmitter only used this parameter on iOS. Now it uses it on all platforms, so this code was modified automatically to preserve its behavior
36
- // If you want to use the native module on other platforms, please remove this condition and test its behavior
37
- Platform.OS !== 'ios' ? null : NativeAppearance,
38
- );
39
- nativeEventEmitter.addListener(
40
- 'appearanceChanged',
41
- (newAppearance: AppearancePreferences) => {
42
- const {colorScheme} = newAppearance;
43
- invariant(
44
- colorScheme === 'dark' ||
45
- colorScheme === 'light' ||
46
- colorScheme == null,
47
- "Unrecognized color scheme. Did you mean 'dark' or 'light'?",
48
- );
49
- eventEmitter.emit('change', {colorScheme});
50
- },
51
- );
30
+ // Cache the color scheme to reduce the cost of reading it between changes.
31
+ // NOTE: If `NativeAppearance` is null, this will always be null.
32
+ let appearance: ?{colorScheme: ?ColorSchemeName} = null;
33
+
34
+ if (NativeAppearance != null) {
35
+ new NativeEventEmitter<NativeAppearanceEventDefinitions>(
36
+ NativeAppearance,
37
+ ).addListener('appearanceChanged', (newAppearance: AppearancePreferences) => {
38
+ const colorScheme = toColorScheme(newAppearance.colorScheme);
39
+ appearance = {colorScheme};
40
+ eventEmitter.emit('change', appearance);
41
+ });
52
42
  }
53
43
 
54
- module.exports = {
55
- /**
56
- * Note: Although color scheme is available immediately, it may change at any
57
- * time. Any rendering logic or styles that depend on this should try to call
58
- * this function on every render, rather than caching the value (for example,
59
- * using inline styles rather than setting a value in a `StyleSheet`).
60
- *
61
- * Example: `const colorScheme = Appearance.getColorScheme();`
62
- *
63
- * @returns {?ColorSchemeName} Value for the color scheme preference.
64
- */
65
- getColorScheme(): ?ColorSchemeName {
66
- if (__DEV__) {
67
- if (isAsyncDebugging) {
68
- // Hard code light theme when using the async debugger as
69
- // sync calls aren't supported
70
- return 'light';
71
- }
44
+ /**
45
+ * Returns the current color scheme preference. This value may change, so the
46
+ * value should not be cached without either listening to changes or using
47
+ * the `useColorScheme` hook.
48
+ */
49
+ export function getColorScheme(): ?ColorSchemeName {
50
+ if (__DEV__) {
51
+ if (isAsyncDebugging) {
52
+ // Hard code light theme when using the async debugger as
53
+ // sync calls aren't supported
54
+ return 'light';
72
55
  }
56
+ }
57
+ let colorScheme = null;
58
+ if (NativeAppearance != null) {
59
+ if (appearance == null) {
60
+ // Lazily initialize `appearance`. This should only happen once because
61
+ // we never reassign a null value to `appearance`.
62
+ appearance = {
63
+ colorScheme: toColorScheme(NativeAppearance.getColorScheme()),
64
+ };
65
+ }
66
+ colorScheme = appearance.colorScheme;
67
+ }
68
+ return colorScheme;
69
+ }
73
70
 
74
- // TODO: (hramos) T52919652 Use ?ColorSchemeName once codegen supports union
75
- const nativeColorScheme: ?string =
76
- NativeAppearance == null
77
- ? null
78
- : NativeAppearance.getColorScheme() || null;
79
- invariant(
80
- nativeColorScheme === 'dark' ||
81
- nativeColorScheme === 'light' ||
82
- nativeColorScheme == null,
83
- "Unrecognized color scheme. Did you mean 'dark' or 'light'?",
84
- );
85
- return nativeColorScheme;
86
- },
87
-
88
- setColorScheme(colorScheme: ?ColorSchemeName): void {
89
- const nativeColorScheme = colorScheme == null ? 'unspecified' : colorScheme;
90
-
91
- invariant(
92
- colorScheme === 'dark' || colorScheme === 'light' || colorScheme == null,
93
- "Unrecognized color scheme. Did you mean 'dark', 'light' or null?",
94
- );
71
+ /**
72
+ * Updates the current color scheme to the supplied value.
73
+ */
74
+ export function setColorScheme(colorScheme: ?ColorSchemeName): void {
75
+ if (NativeAppearance != null) {
76
+ NativeAppearance.setColorScheme(colorScheme ?? 'unspecified');
77
+ appearance = {colorScheme};
78
+ }
79
+ }
95
80
 
96
- if (NativeAppearance != null && NativeAppearance.setColorScheme != null) {
97
- NativeAppearance.setColorScheme(nativeColorScheme);
98
- }
99
- },
81
+ /**
82
+ * Add an event handler that is fired when appearance preferences change.
83
+ */
84
+ export function addChangeListener(
85
+ listener: ({colorScheme: ?ColorSchemeName}) => void,
86
+ ): EventSubscription {
87
+ return eventEmitter.addListener('change', listener);
88
+ }
100
89
 
101
- /**
102
- * Add an event handler that is fired when appearance preferences change.
103
- */
104
- addChangeListener(listener: AppearanceListener): EventSubscription {
105
- return eventEmitter.addListener('change', listener);
106
- },
107
- };
90
+ /**
91
+ * TODO: (hramos) T52919652 Use ?ColorSchemeName once codegen supports union
92
+ */
93
+ function toColorScheme(colorScheme: ?string): ?ColorSchemeName {
94
+ invariant(
95
+ colorScheme === 'dark' || colorScheme === 'light' || colorScheme == null,
96
+ "Unrecognized color scheme. Did you mean 'dark', 'light' or null?",
97
+ );
98
+ return colorScheme;
99
+ }
@@ -9,7 +9,7 @@
9
9
  */
10
10
 
11
11
  import processColor from '../StyleSheet/processColor';
12
- import Appearance from './Appearance';
12
+ import {getColorScheme} from './Appearance';
13
13
  import NativeDevLoadingView from './NativeDevLoadingView';
14
14
 
15
15
  const COLOR_SCHEME = {
@@ -39,9 +39,7 @@ module.exports = {
39
39
  showMessage(message: string, type: 'load' | 'refresh') {
40
40
  if (NativeDevLoadingView) {
41
41
  const colorScheme =
42
- Appearance.getColorScheme() === 'dark'
43
- ? COLOR_SCHEME.dark
44
- : COLOR_SCHEME.default;
42
+ getColorScheme() === 'dark' ? COLOR_SCHEME.dark : COLOR_SCHEME.default;
45
43
 
46
44
  const colorSet = colorScheme[type];
47
45
 
@@ -0,0 +1,22 @@
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
+ * @format
8
+ * @flow
9
+ */
10
+
11
+ export default function stringifyViewConfig(viewConfig: any): string {
12
+ return JSON.stringify(
13
+ viewConfig,
14
+ (key, val) => {
15
+ if (typeof val === 'function') {
16
+ return `ƒ ${val.name}`;
17
+ }
18
+ return val;
19
+ },
20
+ 2,
21
+ );
22
+ }
@@ -12,14 +12,14 @@
12
12
 
13
13
  import type {ColorSchemeName} from './NativeAppearance';
14
14
 
15
- import Appearance from './Appearance';
15
+ import {addChangeListener, getColorScheme} from './Appearance';
16
16
  import {useSyncExternalStore} from 'react';
17
17
 
18
18
  const subscribe = (onStoreChange: () => void) => {
19
- const appearanceSubscription = Appearance.addChangeListener(onStoreChange);
19
+ const appearanceSubscription = addChangeListener(onStoreChange);
20
20
  return () => appearanceSubscription.remove();
21
21
  };
22
22
 
23
23
  export default function useColorScheme(): ?ColorSchemeName {
24
- return useSyncExternalStore(subscribe, Appearance.getColorScheme);
24
+ return useSyncExternalStore(subscribe, getColorScheme);
25
25
  }
package/index.js CHANGED
@@ -82,7 +82,7 @@ import typeof StyleSheet from './Libraries/StyleSheet/StyleSheet';
82
82
  import typeof Text from './Libraries/Text/Text';
83
83
  import typeof * as TurboModuleRegistry from './Libraries/TurboModule/TurboModuleRegistry';
84
84
  import typeof UTFSequence from './Libraries/UTFSequence';
85
- import typeof Appearance from './Libraries/Utilities/Appearance';
85
+ import typeof * as Appearance from './Libraries/Utilities/Appearance';
86
86
  import typeof BackHandler from './Libraries/Utilities/BackHandler';
87
87
  import typeof DeviceInfo from './Libraries/Utilities/DeviceInfo';
88
88
  import typeof DevSettings from './Libraries/Utilities/DevSettings';
package/index.win32.js CHANGED
@@ -82,7 +82,7 @@ import typeof StyleSheet from './Libraries/StyleSheet/StyleSheet';
82
82
  import typeof Text from './Libraries/Text/Text';
83
83
  import typeof * as TurboModuleRegistry from './Libraries/TurboModule/TurboModuleRegistry';
84
84
  import typeof UTFSequence from './Libraries/UTFSequence';
85
- import typeof Appearance from './Libraries/Utilities/Appearance';
85
+ import typeof * as Appearance from './Libraries/Utilities/Appearance';
86
86
  import typeof BackHandler from './Libraries/Utilities/BackHandler';
87
87
  import typeof DeviceInfo from './Libraries/Utilities/DeviceInfo';
88
88
  import typeof DevSettings from './Libraries/Utilities/DevSettings';
package/jest/setup.js CHANGED
@@ -392,10 +392,6 @@ jest
392
392
  .mock('../Libraries/ReactNative/requireNativeComponent', () => {
393
393
  return jest.requireActual('./mockNativeComponent');
394
394
  })
395
- .mock(
396
- '../Libraries/Utilities/verifyComponentAttributeEquivalence',
397
- () => function () {},
398
- )
399
395
  .mock('../Libraries/Vibration/Vibration', () => ({
400
396
  vibrate: jest.fn(),
401
397
  cancel: jest.fn(),
package/overrides.json CHANGED
@@ -7,19 +7,19 @@
7
7
  "**/__snapshots__/**",
8
8
  "src-win/rntypes/**"
9
9
  ],
10
- "baseVersion": "0.76.0-nightly-20240816-17017d2b8",
10
+ "baseVersion": "0.76.0-nightly-20240824-09e88448c",
11
11
  "overrides": [
12
12
  {
13
13
  "type": "derived",
14
14
  "file": ".flowconfig",
15
15
  "baseFile": ".flowconfig",
16
- "baseHash": "3d94d14740956b3455abfbdff5e043212d04974b"
16
+ "baseHash": "27c9677306d4f1bd627a346969c592aee606811a"
17
17
  },
18
18
  {
19
19
  "type": "derived",
20
20
  "file": "src-win/index.win32.js",
21
21
  "baseFile": "packages/react-native/index.js",
22
- "baseHash": "dc1e45b643d33d868da60fa27da2cbda0231b7f3"
22
+ "baseHash": "da5d9e79c8c14c56ba00f73c8514c06e6fffe8ed"
23
23
  },
24
24
  {
25
25
  "type": "platform",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@office-iss/react-native-win32",
3
- "version": "0.0.0-canary.260",
3
+ "version": "0.0.0-canary.261",
4
4
  "description": "Implementation of react native on top of Office's Win32 platform.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -30,13 +30,13 @@
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-20240816-17017d2b8",
34
- "@react-native/codegen": "0.76.0-nightly-20240816-17017d2b8",
35
- "@react-native/community-cli-plugin": "0.76.0-nightly-20240816-17017d2b8",
36
- "@react-native/gradle-plugin": "0.76.0-nightly-20240816-17017d2b8",
37
- "@react-native/js-polyfills": "0.76.0-nightly-20240816-17017d2b8",
38
- "@react-native/normalize-colors": "0.76.0-nightly-20240816-17017d2b8",
39
- "@react-native/virtualized-lists": "0.76.0-nightly-20240816-17017d2b8",
33
+ "@react-native/assets-registry": "0.76.0-nightly-20240824-09e88448c",
34
+ "@react-native/codegen": "0.76.0-nightly-20240824-09e88448c",
35
+ "@react-native/community-cli-plugin": "0.76.0-nightly-20240824-09e88448c",
36
+ "@react-native/gradle-plugin": "0.76.0-nightly-20240824-09e88448c",
37
+ "@react-native/js-polyfills": "0.76.0-nightly-20240824-09e88448c",
38
+ "@react-native/normalize-colors": "0.76.0-nightly-20240824-09e88448c",
39
+ "@react-native/virtualized-lists": "0.76.0-nightly-20240824-09e88448c",
40
40
  "abort-controller": "^3.0.0",
41
41
  "anser": "^1.4.9",
42
42
  "ansi-regex": "^5.0.0",
@@ -82,19 +82,19 @@
82
82
  "@types/prop-types": "15.7.1",
83
83
  "@types/react": "^18.2.6",
84
84
  "eslint": "^8.19.0",
85
- "flow-bin": "^0.241.0",
85
+ "flow-bin": "^0.244.0",
86
86
  "jscodeshift": "^0.14.0",
87
87
  "just-scripts": "^1.3.3",
88
88
  "prettier": "2.8.8",
89
89
  "react": "19.0.0-rc-fb9a90fa48-20240614",
90
- "react-native": "0.76.0-nightly-20240816-17017d2b8",
90
+ "react-native": "0.76.0-nightly-20240824-09e88448c",
91
91
  "react-native-platform-override": "^1.9.45",
92
92
  "typescript": "5.0.4"
93
93
  },
94
94
  "peerDependencies": {
95
95
  "@types/react": "^18.2.6",
96
96
  "react": "^19.0.0-rc-fb9a90fa48-20240614",
97
- "react-native": "0.76.0-nightly-20240816-17017d2b8"
97
+ "react-native": "0.76.0-nightly-20240824-09e88448c"
98
98
  },
99
99
  "beachball": {
100
100
  "defaultNpmTag": "canary",
@@ -4,7 +4,7 @@
4
4
  * This source code is licensed under the MIT license found in the
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  *
7
- * @generated SignedSource<<fb7a3dcf7b3e5001e45f808fb4410376>>
7
+ * @generated SignedSource<<3c32ce3847859db45d2e8bafb3cc31a1>>
8
8
  * @flow strict-local
9
9
  */
10
10
 
@@ -30,6 +30,7 @@ export type ReactNativeFeatureFlagsJsOnly = {
30
30
  animatedShouldDebounceQueueFlush: Getter<boolean>,
31
31
  animatedShouldUseSingleOp: Getter<boolean>,
32
32
  enableAccessToHostTreeInFabric: Getter<boolean>,
33
+ enableOptimisedVirtualizedCells: Getter<boolean>,
33
34
  isLayoutAnimationEnabled: Getter<boolean>,
34
35
  shouldSkipStateUpdatesForLoopingAnimations: Getter<boolean>,
35
36
  shouldUseAnimatedObjectForTransform: Getter<boolean>,
@@ -48,7 +49,6 @@ export type ReactNativeFeatureFlags = {
48
49
  commonTestFlag: Getter<boolean>,
49
50
  allowRecursiveCommitsWithSynchronousMountOnAndroid: Getter<boolean>,
50
51
  batchRenderingUpdatesInEventLoop: Getter<boolean>,
51
- changeOrderOfMountingInstructionsOnAndroid: Getter<boolean>,
52
52
  completeReactInstanceCreationOnBgThreadOnAndroid: Getter<boolean>,
53
53
  destroyFabricSurfacesInReactInstanceManager: Getter<boolean>,
54
54
  enableAlignItemsBaselineOnFabricIOS: Getter<boolean>,
@@ -60,6 +60,7 @@ export type ReactNativeFeatureFlags = {
60
60
  enableFabricLogs: Getter<boolean>,
61
61
  enableFabricRendererExclusively: Getter<boolean>,
62
62
  enableGranularShadowTreeStateReconciliation: Getter<boolean>,
63
+ enableLayoutAnimationsOnIOS: Getter<boolean>,
63
64
  enableLongTaskAPI: Getter<boolean>,
64
65
  enableMicrotasks: Getter<boolean>,
65
66
  enablePropsUpdateReconciliationAndroid: Getter<boolean>,
@@ -112,6 +113,11 @@ export const animatedShouldUseSingleOp: Getter<boolean> = createJavaScriptFlagGe
112
113
  */
113
114
  export const enableAccessToHostTreeInFabric: Getter<boolean> = createJavaScriptFlagGetter('enableAccessToHostTreeInFabric', false);
114
115
 
116
+ /**
117
+ * Removing unnecessary rerenders Virtualized cells after any rerenders of Virualized list. Works with strict=true option
118
+ */
119
+ export const enableOptimisedVirtualizedCells: Getter<boolean> = createJavaScriptFlagGetter('enableOptimisedVirtualizedCells', false);
120
+
115
121
  /**
116
122
  * Function used to enable / disabled Layout Animations in React Native.
117
123
  */
@@ -169,10 +175,6 @@ export const allowRecursiveCommitsWithSynchronousMountOnAndroid: Getter<boolean>
169
175
  * When enabled, the RuntimeScheduler processing the event loop will batch all rendering updates and dispatch them together at the end of each iteration of the loop.
170
176
  */
171
177
  export const batchRenderingUpdatesInEventLoop: Getter<boolean> = createNativeFlagGetter('batchRenderingUpdatesInEventLoop', false);
172
- /**
173
- * When enabled, insert of views on Android will be moved from the beginning of the IntBufferBatchMountItem to be after layout updates.
174
- */
175
- export const changeOrderOfMountingInstructionsOnAndroid: Getter<boolean> = createNativeFlagGetter('changeOrderOfMountingInstructionsOnAndroid', false);
176
178
  /**
177
179
  * Do not wait for a main-thread dispatch to complete init to start executing work on the JS thread on Android
178
180
  */
@@ -217,6 +219,10 @@ export const enableFabricRendererExclusively: Getter<boolean> = createNativeFlag
217
219
  * When enabled, the renderer would only fail commits when they propagate state and the last commit that updated state changed before committing.
218
220
  */
219
221
  export const enableGranularShadowTreeStateReconciliation: Getter<boolean> = createNativeFlagGetter('enableGranularShadowTreeStateReconciliation', false);
222
+ /**
223
+ * When enabled, LayoutAnimations API will animate state changes on iOS.
224
+ */
225
+ export const enableLayoutAnimationsOnIOS: Getter<boolean> = createNativeFlagGetter('enableLayoutAnimationsOnIOS', true);
220
226
  /**
221
227
  * Enables the reporting of long tasks through `PerformanceObserver`. Only works if the event loop is enabled.
222
228
  */
@@ -4,7 +4,7 @@
4
4
  * This source code is licensed under the MIT license found in the
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  *
7
- * @generated SignedSource<<9728234e0662758c72ba79b3cffbd4e5>>
7
+ * @generated SignedSource<<1f1710a12f6980b23bb6c0ece0060b59>>
8
8
  * @flow strict-local
9
9
  */
10
10
 
@@ -26,7 +26,6 @@ export interface Spec extends TurboModule {
26
26
  +commonTestFlag?: () => boolean;
27
27
  +allowRecursiveCommitsWithSynchronousMountOnAndroid?: () => boolean;
28
28
  +batchRenderingUpdatesInEventLoop?: () => boolean;
29
- +changeOrderOfMountingInstructionsOnAndroid?: () => boolean;
30
29
  +completeReactInstanceCreationOnBgThreadOnAndroid?: () => boolean;
31
30
  +destroyFabricSurfacesInReactInstanceManager?: () => boolean;
32
31
  +enableAlignItemsBaselineOnFabricIOS?: () => boolean;
@@ -38,6 +37,7 @@ export interface Spec extends TurboModule {
38
37
  +enableFabricLogs?: () => boolean;
39
38
  +enableFabricRendererExclusively?: () => boolean;
40
39
  +enableGranularShadowTreeStateReconciliation?: () => boolean;
40
+ +enableLayoutAnimationsOnIOS?: () => boolean;
41
41
  +enableLongTaskAPI?: () => boolean;
42
42
  +enableMicrotasks?: () => boolean;
43
43
  +enablePropsUpdateReconciliationAndroid?: () => boolean;
@@ -14,19 +14,19 @@ import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboMod
14
14
 
15
15
  export type ColorSchemeName = 'light' | 'dark';
16
16
 
17
- export type AppearancePreferences = {|
17
+ export type AppearancePreferences = {
18
18
  // TODO: (hramos) T52919652 Use ?ColorSchemeName once codegen supports union
19
19
  // types.
20
20
  /* 'light' | 'dark' */
21
21
  colorScheme?: ?string,
22
- |};
22
+ };
23
23
 
24
24
  export interface Spec extends TurboModule {
25
25
  // TODO: (hramos) T52919652 Use ?ColorSchemeName once codegen supports union
26
26
  // types.
27
27
  /* 'light' | 'dark' */
28
28
  +getColorScheme: () => ?string;
29
- +setColorScheme?: (colorScheme: string) => void;
29
+ +setColorScheme: (colorScheme: string) => void;
30
30
 
31
31
  // RCTEventEmitter
32
32
  +addListener: (eventName: string) => void;
@@ -141,12 +141,14 @@ export default class IntersectionObserver {
141
141
  return;
142
142
  }
143
143
 
144
- IntersectionObserverManager.observe({
144
+ const didStartObserving = IntersectionObserverManager.observe({
145
145
  intersectionObserverId: this._getOrCreateIntersectionObserverId(),
146
146
  target,
147
147
  });
148
148
 
149
- this._observationTargets.add(target);
149
+ if (didStartObserving) {
150
+ this._observationTargets.add(target);
151
+ }
150
152
  }
151
153
 
152
154
  /**
@@ -116,10 +116,10 @@ export function observe({
116
116
  }: {
117
117
  intersectionObserverId: IntersectionObserverId,
118
118
  target: ReactNativeElement,
119
- }): void {
119
+ }): boolean {
120
120
  if (NativeIntersectionObserver == null) {
121
121
  warnNoNativeIntersectionObserver();
122
- return;
122
+ return false;
123
123
  }
124
124
 
125
125
  const registeredObserver = registeredIntersectionObservers.get(
@@ -129,15 +129,13 @@ export function observe({
129
129
  console.error(
130
130
  `IntersectionObserverManager: could not start observing target because IntersectionObserver with ID ${intersectionObserverId} was not registered.`,
131
131
  );
132
- return;
132
+ return false;
133
133
  }
134
134
 
135
135
  const targetShadowNode = getShadowNode(target);
136
136
  if (targetShadowNode == null) {
137
- console.error(
138
- 'IntersectionObserverManager: could not find reference to host node from target',
139
- );
140
- return;
137
+ // The target is disconnected. We can't observe it anymore.
138
+ return false;
141
139
  }
142
140
 
143
141
  const instanceHandle = getInstanceHandle(target);
@@ -145,7 +143,7 @@ export function observe({
145
143
  console.error(
146
144
  'IntersectionObserverManager: could not find reference to instance handle from target',
147
145
  );
148
- return;
146
+ return false;
149
147
  }
150
148
 
151
149
  // Store the mapping between the instance handle and the target so we can
@@ -160,11 +158,13 @@ export function observe({
160
158
  isConnected = true;
161
159
  }
162
160
 
163
- return NativeIntersectionObserver.observe({
161
+ NativeIntersectionObserver.observe({
164
162
  intersectionObserverId,
165
163
  targetShadowNode,
166
164
  thresholds: registeredObserver.observer.thresholds,
167
165
  });
166
+
167
+ return true;
168
168
  }
169
169
 
170
170
  export function unobserve(
@@ -121,13 +121,15 @@ export default class MutationObserver {
121
121
  MutationObserverManager.unobserve(mutationObserverId, target);
122
122
  }
123
123
 
124
- MutationObserverManager.observe({
124
+ const didStartObserving = MutationObserverManager.observe({
125
125
  mutationObserverId,
126
126
  target,
127
127
  subtree: Boolean(options?.subtree),
128
128
  });
129
129
 
130
- this._observationTargets.add(target);
130
+ if (didStartObserving) {
131
+ this._observationTargets.add(target);
132
+ }
131
133
  }
132
134
 
133
135
  _unobserve(target: ReactNativeElement): void {
@@ -43,6 +43,13 @@ const registeredMutationObservers: Map<
43
43
  $ReadOnly<{observer: MutationObserver, callback: MutationObserverCallback}>,
44
44
  > = new Map();
45
45
 
46
+ // The mapping between ReactNativeElement and their corresponding shadow node
47
+ // needs to be kept here because React removes the link when unmounting.
48
+ const targetToShadowNodeMap: WeakMap<
49
+ ReactNativeElement,
50
+ ReturnType<typeof getShadowNode>,
51
+ > = new WeakMap();
52
+
46
53
  /**
47
54
  * Registers the given mutation observer and returns a unique ID for it,
48
55
  * which is required to start observing targets.
@@ -85,10 +92,10 @@ export function observe({
85
92
  mutationObserverId: MutationObserverId,
86
93
  target: ReactNativeElement,
87
94
  subtree: boolean,
88
- }): void {
95
+ }): boolean {
89
96
  if (NativeMutationObserver == null) {
90
97
  warnNoNativeMutationObserver();
91
- return;
98
+ return false;
92
99
  }
93
100
 
94
101
  const registeredObserver =
@@ -97,17 +104,17 @@ export function observe({
97
104
  console.error(
98
105
  `MutationObserverManager: could not start observing target because MutationObserver with ID ${mutationObserverId} was not registered.`,
99
106
  );
100
- return;
107
+ return false;
101
108
  }
102
109
 
103
110
  const targetShadowNode = getShadowNode(target);
104
111
  if (targetShadowNode == null) {
105
- console.error(
106
- 'MutationObserverManager: could not find reference to host node from target',
107
- );
108
- return;
112
+ // The target is disconnected. We can't observe it anymore.
113
+ return false;
109
114
  }
110
115
 
116
+ targetToShadowNodeMap.set(target, targetShadowNode);
117
+
111
118
  if (!isConnected) {
112
119
  NativeMutationObserver.connect(
113
120
  notifyMutationObservers,
@@ -121,11 +128,13 @@ export function observe({
121
128
  isConnected = true;
122
129
  }
123
130
 
124
- return NativeMutationObserver.observe({
131
+ NativeMutationObserver.observe({
125
132
  mutationObserverId,
126
133
  targetShadowNode,
127
134
  subtree,
128
135
  });
136
+
137
+ return true;
129
138
  }
130
139
 
131
140
  export function unobserve(
@@ -146,10 +155,10 @@ export function unobserve(
146
155
  return;
147
156
  }
148
157
 
149
- const targetShadowNode = getShadowNode(target);
158
+ const targetShadowNode = targetToShadowNodeMap.get(target);
150
159
  if (targetShadowNode == null) {
151
160
  console.error(
152
- 'MutationObserverManager: could not find reference to host node from target',
161
+ 'MutationObserverManager: could not find registration data for target',
153
162
  );
154
163
  return;
155
164
  }
@@ -1,135 +0,0 @@
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
- * @format
8
- * @flow
9
- */
10
-
11
- import PlatformBaseViewConfig from '../NativeComponent/PlatformBaseViewConfig';
12
- import {type ViewConfig} from '../Renderer/shims/ReactNativeTypes';
13
-
14
- const IGNORED_KEYS = ['transform', 'hitSlop'];
15
-
16
- /**
17
- * The purpose of this function is to validate that the view config that
18
- * native exposes for a given view manager is the same as the view config
19
- * that is specified for that view manager in JS.
20
- *
21
- * In order to improve perf, we want to avoid calling into native to get
22
- * the view config when each view manager is used. To do this, we are moving
23
- * the configs to JS. In the future we will use these JS based view configs
24
- * to codegen the view manager on native to ensure they stay in sync without
25
- * this runtime check.
26
- *
27
- * If this function fails, that likely means a change was made to the native
28
- * view manager without updating the JS config as well. Ideally you can make
29
- * that direct change to the JS config. If you don't know what the differences
30
- * are, the best approach I've found is to create a view that prints
31
- * the return value of getNativeComponentAttributes, and then copying that
32
- * text and pasting it back into JS:
33
- * <Text selectable={true}>{JSON.stringify(getNativeComponentAttributes('RCTView'))}</Text>
34
- *
35
- * This is meant to be a stopgap until the time comes when we only have a
36
- * single source of truth. I wonder if this message will still be here two
37
- * years from now...
38
- */
39
- export default function verifyComponentAttributeEquivalence(
40
- nativeViewConfig: ViewConfig,
41
- staticViewConfig: ViewConfig,
42
- ) {
43
- for (const prop of [
44
- 'validAttributes',
45
- 'bubblingEventTypes',
46
- 'directEventTypes',
47
- ]) {
48
- const diff = Object.keys(
49
- lefthandObjectDiff(nativeViewConfig[prop], staticViewConfig[prop]),
50
- );
51
-
52
- if (diff.length > 0) {
53
- const name =
54
- staticViewConfig.uiViewClassName ?? nativeViewConfig.uiViewClassName;
55
- console.error(
56
- `'${name}' has a view config that does not match native. ` +
57
- `'${prop}' is missing: ${diff.join(', ')}`,
58
- );
59
- }
60
- }
61
- }
62
-
63
- // Return the different key-value pairs of the right object, by iterating through the keys in the left object
64
- // Note it won't return a difference where a key is missing in the left but exists the right.
65
- function lefthandObjectDiff(leftObj: Object, rightObj: Object): Object {
66
- const differentKeys: {[string]: any | {...}} = {};
67
-
68
- function compare(leftItem: any, rightItem: any, key: string) {
69
- if (typeof leftItem !== typeof rightItem && leftItem != null) {
70
- differentKeys[key] = rightItem;
71
- return;
72
- }
73
-
74
- if (typeof leftItem === 'object') {
75
- const objDiff = lefthandObjectDiff(leftItem, rightItem);
76
- if (Object.keys(objDiff).length > 1) {
77
- differentKeys[key] = objDiff;
78
- }
79
- return;
80
- }
81
-
82
- if (leftItem !== rightItem) {
83
- differentKeys[key] = rightItem;
84
- return;
85
- }
86
- }
87
-
88
- for (const key in leftObj) {
89
- if (IGNORED_KEYS.includes(key)) {
90
- continue;
91
- }
92
-
93
- if (!rightObj) {
94
- differentKeys[key] = {};
95
- } else if (leftObj.hasOwnProperty(key)) {
96
- compare(leftObj[key], rightObj[key], key);
97
- }
98
- }
99
-
100
- return differentKeys;
101
- }
102
-
103
- export function getConfigWithoutViewProps(
104
- viewConfig: ViewConfig,
105
- propName: string,
106
- ): {...} {
107
- // $FlowFixMe[invalid-computed-prop]
108
- if (!viewConfig[propName]) {
109
- return {};
110
- }
111
-
112
- return (
113
- Object.keys(viewConfig[propName])
114
- // $FlowFixMe[invalid-computed-prop]
115
- .filter(prop => !PlatformBaseViewConfig[propName][prop])
116
- .reduce<{[string]: any}>((obj, prop) => {
117
- // $FlowFixMe[invalid-computed-prop]
118
- obj[prop] = viewConfig[propName][prop];
119
- return obj;
120
- }, {})
121
- );
122
- }
123
-
124
- export function stringifyViewConfig(viewConfig: any): string {
125
- return JSON.stringify(
126
- viewConfig,
127
- (key, val) => {
128
- if (typeof val === 'function') {
129
- return `ƒ ${val.name}`;
130
- }
131
- return val;
132
- },
133
- 2,
134
- );
135
- }