react-native-windows 0.0.0-canary.821 → 0.0.0-canary.823

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 (46) hide show
  1. package/.flowconfig +1 -2
  2. package/Folly/TEMP_UntilFollyUpdate/ConstexprMath.h +4 -2
  3. package/Libraries/Components/ScrollView/AndroidHorizontalScrollViewNativeComponent.js +1 -0
  4. package/Libraries/Components/ScrollView/ScrollViewNativeComponent.js +2 -1
  5. package/Libraries/Components/ScrollView/ScrollViewNativeComponent.windows.js +2 -1
  6. package/Libraries/Components/StatusBar/StatusBar.js +1 -21
  7. package/Libraries/Components/TextInput/TextInput.js +6 -3
  8. package/Libraries/Components/TextInput/TextInput.windows.js +6 -3
  9. package/Libraries/Components/View/ReactNativeStyleAttributes.js +6 -0
  10. package/Libraries/Core/Devtools/loadBundleFromServer.windows.js +153 -0
  11. package/Libraries/Core/ReactNativeVersion.js +1 -1
  12. package/Libraries/Core/setUpPerformance.js +5 -3
  13. package/Libraries/IntersectionObserver/IntersectionObserverManager.js +6 -26
  14. package/Libraries/LogBox/Data/LogBoxData.js +0 -25
  15. package/Libraries/NativeComponent/BaseViewConfig.android.js +3 -0
  16. package/Libraries/NativeComponent/BaseViewConfig.ios.js +3 -0
  17. package/Libraries/NativeComponent/BaseViewConfig.windows.js +3 -0
  18. package/Libraries/ReactNative/getNativeComponentAttributes.js +3 -0
  19. package/Libraries/StyleSheet/StyleSheetTypes.js +6 -0
  20. package/Libraries/StyleSheet/processFilter.js +132 -0
  21. package/Libraries/StyleSheet/processTransform.js +18 -3
  22. package/Microsoft.ReactNative/Fabric/FabricUIManagerModule.cpp +0 -2
  23. package/Microsoft.ReactNative/Fabric/FabricUIManagerModule.h +0 -1
  24. package/Microsoft.ReactNative/packages.lock.json +42 -70
  25. package/Microsoft.ReactNative.Managed/packages.lock.json +3 -70
  26. package/PropertySheets/Generated/PackageVersion.g.props +2 -2
  27. package/ReactCommon/TEMP_UntilReactCommonUpdate/react/performance/timeline/BoundedConsumableBuffer.h +10 -10
  28. package/codegen/NativeAppStateSpec.g.h +8 -8
  29. package/codegen/NativeMutationObserverSpec.g.h +14 -14
  30. package/codegen/NativePushNotificationManagerIOSSpec.g.h +15 -15
  31. package/codegen/NativeReactNativeFeatureFlagsSpec.g.h +15 -9
  32. package/codegen/react/components/rnwcore/States.h +0 -26
  33. package/codegen/rnwcoreJSI-generated.cpp +9 -22
  34. package/codegen/rnwcoreJSI.h +483 -529
  35. package/fmt/TEMP_UntilFmtUpdate/core.h +2925 -0
  36. package/fmt/fmt.vcxproj +1 -1
  37. package/jest/mockComponent.js +7 -0
  38. package/package.json +19 -19
  39. package/rn-get-polyfills.js +1 -0
  40. package/src/private/featureflags/ReactNativeFeatureFlags.js +9 -4
  41. package/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js +3 -2
  42. package/Libraries/NativeModules/specs/NativeAnimationsDebugModule.js +0 -13
  43. package/ReactCommon/TEMP_UntilReactCommonUpdate/react/renderer/observers/events/EventPerformanceLogger.cpp +0 -200
  44. package/codegen/NativeAnimationsDebugModuleSpec.g.h +0 -40
  45. package/jest/ReactNativeInternalFeatureFlagsMock.js +0 -13
  46. package/src/private/specs/modules/NativeAnimationsDebugModule.js +0 -20
package/.flowconfig CHANGED
@@ -90,7 +90,6 @@ flow/
90
90
  ../node_modules/.flow/flow-typed/
91
91
 
92
92
  [options]
93
- experimental.global_find_ref=true
94
93
  enums=true
95
94
  casting_syntax=both
96
95
 
@@ -147,4 +146,4 @@ untyped-import
147
146
  untyped-type-import
148
147
 
149
148
  [version]
150
- ^0.235.1
149
+ ^0.236.0
@@ -257,7 +257,8 @@ constexpr auto& constexpr_iterated_squares_desc_2_v =
257
257
  template <typename T, typename... Ts>
258
258
  constexpr T constexpr_max(T a, Ts... ts) {
259
259
  T list[] = {ts..., a}; // 0-length arrays are illegal
260
- for (auto i = 0u; i < sizeof...(Ts); ++i) {
260
+ // [Windows #12703 - Fix folly CodeQL issues]
261
+ for (size_t i = 0; i < sizeof...(Ts); ++i) {
261
262
  a = list[i] < a ? a : list[i];
262
263
  }
263
264
  return a;
@@ -268,7 +269,8 @@ constexpr T constexpr_max(T a, Ts... ts) {
268
269
  template <typename T, typename... Ts>
269
270
  constexpr T constexpr_min(T a, Ts... ts) {
270
271
  T list[] = {ts..., a}; // 0-length arrays are illegal
271
- for (auto i = 0u; i < sizeof...(Ts); ++i) {
272
+ // [Windows #12703 - Fix folly CodeQL issues]
273
+ for (size_t i = 0; i < sizeof...(Ts); ++i) {
272
274
  a = list[i] < a ? list[i] : a;
273
275
  }
274
276
  return a;
@@ -31,6 +31,7 @@ export const __INTERNAL_VIEW_CONFIG: PartialViewConfig = {
31
31
  pagingEnabled: true,
32
32
  persistentScrollbar: true,
33
33
  horizontal: true,
34
+ enableSyncOnScroll: true,
34
35
  scrollEnabled: true,
35
36
  scrollEventThrottle: true,
36
37
  scrollPerfTag: true,
@@ -45,6 +45,7 @@ export const __INTERNAL_VIEW_CONFIG: PartialViewConfig =
45
45
  diff: require('../../Utilities/differ/pointsDiffer'),
46
46
  },
47
47
  decelerationRate: true,
48
+ enableSyncOnScroll: true, // Fabric only.
48
49
  disableIntervalMomentum: true,
49
50
  maintainVisibleContentPosition: true,
50
51
  pagingEnabled: true,
@@ -134,7 +135,7 @@ export const __INTERNAL_VIEW_CONFIG: PartialViewConfig =
134
135
  contentInsetAdjustmentBehavior: true,
135
136
  decelerationRate: true,
136
137
  endDraggingSensitivityMultiplier: true,
137
- enableSyncOnScroll: true, // iOS-Fabric only.
138
+ enableSyncOnScroll: true, // Fabric only.
138
139
  directionalLockEnabled: true,
139
140
  disableIntervalMomentum: true,
140
141
  indicatorStyle: true,
@@ -45,6 +45,7 @@ export const __INTERNAL_VIEW_CONFIG: PartialViewConfig =
45
45
  diff: require('../../Utilities/differ/pointsDiffer'),
46
46
  },
47
47
  decelerationRate: true,
48
+ enableSyncOnScroll: true, // Fabric only.
48
49
  disableIntervalMomentum: true,
49
50
  maintainVisibleContentPosition: true,
50
51
  pagingEnabled: true,
@@ -134,7 +135,7 @@ export const __INTERNAL_VIEW_CONFIG: PartialViewConfig =
134
135
  contentInsetAdjustmentBehavior: true,
135
136
  decelerationRate: true,
136
137
  endDraggingSensitivityMultiplier: true,
137
- enableSyncOnScroll: true, // iOS-Fabric only.
138
+ enableSyncOnScroll: true, // Fabric only.
138
139
  directionalLockEnabled: true,
139
140
  disableIntervalMomentum: true,
140
141
  indicatorStyle: true,
@@ -163,29 +163,9 @@ function createStackEntry(props: any): any {
163
163
  /**
164
164
  * Component to control the app status bar.
165
165
  *
166
- * ### Usage with Navigator
167
- *
168
166
  * It is possible to have multiple `StatusBar` components mounted at the same
169
167
  * time. The props will be merged in the order the `StatusBar` components were
170
- * mounted. One use case is to specify status bar styles per route using `Navigator`.
171
- *
172
- * ```
173
- * <View>
174
- * <StatusBar
175
- * backgroundColor="blue"
176
- * barStyle="light-content"
177
- * />
178
- * <Navigator
179
- * initialRoute={{statusBarHidden: true}}
180
- * renderScene={(route, navigator) =>
181
- * <View>
182
- * <StatusBar hidden={route.statusBarHidden} />
183
- * ...
184
- * </View>
185
- * }
186
- * />
187
- * </View>
188
- * ```
168
+ * mounted.
189
169
  *
190
170
  * ### Imperative API
191
171
  *
@@ -1099,12 +1099,14 @@ function InternalTextInput(props: Props): React.Node {
1099
1099
  };
1100
1100
 
1101
1101
  const [mostRecentEventCount, setMostRecentEventCount] = useState<number>(0);
1102
-
1103
1102
  const [lastNativeText, setLastNativeText] = useState<?Stringish>(props.value);
1104
1103
  const [lastNativeSelectionState, setLastNativeSelection] = useState<{|
1105
- selection: ?Selection,
1104
+ selection: Selection,
1106
1105
  mostRecentEventCount: number,
1107
- |}>({selection, mostRecentEventCount});
1106
+ |}>({
1107
+ selection: {start: -1, end: -1},
1108
+ mostRecentEventCount: mostRecentEventCount,
1109
+ });
1108
1110
 
1109
1111
  const lastNativeSelection = lastNativeSelectionState.selection;
1110
1112
 
@@ -1442,6 +1444,7 @@ function InternalTextInput(props: Props): React.Node {
1442
1444
  onSelectionChange={_onSelectionChange}
1443
1445
  onSelectionChangeShouldSetResponder={emptyFunctionThatReturnsTrue}
1444
1446
  selection={selection}
1447
+ selectionColor={selectionColor}
1445
1448
  style={StyleSheet.compose(
1446
1449
  useMultilineDefaultStyle ? styles.multilineDefault : null,
1447
1450
  style,
@@ -1176,12 +1176,14 @@ function InternalTextInput(props: Props): React.Node {
1176
1176
  };
1177
1177
 
1178
1178
  const [mostRecentEventCount, setMostRecentEventCount] = useState<number>(0);
1179
-
1180
1179
  const [lastNativeText, setLastNativeText] = useState<?Stringish>(props.value);
1181
1180
  const [lastNativeSelectionState, setLastNativeSelection] = useState<{|
1182
- selection: ?Selection,
1181
+ selection: Selection,
1183
1182
  mostRecentEventCount: number,
1184
- |}>({selection, mostRecentEventCount});
1183
+ |}>({
1184
+ selection: {start: -1, end: -1},
1185
+ mostRecentEventCount: mostRecentEventCount,
1186
+ });
1185
1187
 
1186
1188
  const lastNativeSelection = lastNativeSelectionState.selection;
1187
1189
 
@@ -1589,6 +1591,7 @@ function InternalTextInput(props: Props): React.Node {
1589
1591
  onSelectionChange={_onSelectionChange}
1590
1592
  onSelectionChangeShouldSetResponder={emptyFunctionThatReturnsTrue}
1591
1593
  selection={selection}
1594
+ selectionColor={selectionColor}
1592
1595
  style={StyleSheet.compose(
1593
1596
  useMultilineDefaultStyle ? styles.multilineDefault : null,
1594
1597
  style,
@@ -12,6 +12,7 @@ import type {AnyAttributeType} from '../../Renderer/shims/ReactNativeTypes';
12
12
 
13
13
  import processAspectRatio from '../../StyleSheet/processAspectRatio';
14
14
  import processColor from '../../StyleSheet/processColor';
15
+ import processFilter from '../../StyleSheet/processFilter';
15
16
  import processFontVariant from '../../StyleSheet/processFontVariant';
16
17
  import processTransform from '../../StyleSheet/processTransform';
17
18
  import processTransformOrigin from '../../StyleSheet/processTransformOrigin';
@@ -114,6 +115,11 @@ const ReactNativeStyleAttributes: {[string]: AnyAttributeType, ...} = {
114
115
  transform: {process: processTransform},
115
116
  transformOrigin: {process: processTransformOrigin},
116
117
 
118
+ /**
119
+ * Filter
120
+ */
121
+ experimental_filter: {process: processFilter},
122
+
117
123
  /**
118
124
  * View
119
125
  */
@@ -0,0 +1,153 @@
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 Networking from '../../Network/RCTNetworking';
13
+ import DevLoadingView from '../../Utilities/DevLoadingView';
14
+ import HMRClient from '../../Utilities/HMRClient';
15
+ import getDevServer from './getDevServer';
16
+
17
+ declare var global: {globalEvalWithSourceUrl?: (string, string) => mixed, ...};
18
+
19
+ let pendingRequests = 0;
20
+
21
+ const cachedPromisesByUrl = new Map<string, Promise<void>>();
22
+
23
+ function asyncRequest(
24
+ url: string,
25
+ ): Promise<{body: string, headers: {[string]: string}}> {
26
+ let id = null;
27
+ let responseText = null;
28
+ let headers = null;
29
+ let dataListener;
30
+ let completeListener;
31
+ let responseListener;
32
+ let incrementalDataListener;
33
+ return new Promise<{body: string, headers: {[string]: string}}>(
34
+ (resolve, reject) => {
35
+ dataListener = Networking.addListener(
36
+ 'didReceiveNetworkData',
37
+ ([requestId, response]) => {
38
+ if (requestId === id) {
39
+ responseText = response;
40
+ }
41
+ },
42
+ );
43
+ incrementalDataListener = Networking.addListener(
44
+ 'didReceiveNetworkIncrementalData',
45
+ ([requestId, data]) => {
46
+ if (requestId === id) {
47
+ if (responseText != null) {
48
+ responseText += data;
49
+ } else {
50
+ responseText = data;
51
+ }
52
+ }
53
+ },
54
+ );
55
+ responseListener = Networking.addListener(
56
+ 'didReceiveNetworkResponse',
57
+ ([requestId, status, responseHeaders]) => {
58
+ if (requestId === id) {
59
+ headers = responseHeaders;
60
+ }
61
+ },
62
+ );
63
+ completeListener = Networking.addListener(
64
+ 'didCompleteNetworkResponse',
65
+ ([requestId, error]) => {
66
+ if (requestId === id) {
67
+ if (error) {
68
+ reject(error);
69
+ } else {
70
+ //$FlowFixMe[incompatible-call]
71
+ resolve({body: responseText, headers});
72
+ }
73
+ }
74
+ },
75
+ );
76
+ Networking.sendRequest(
77
+ 'GET',
78
+ 'asyncRequest',
79
+ url,
80
+ {},
81
+ '',
82
+ 'text',
83
+ true,
84
+ 0,
85
+ requestId => {
86
+ id = requestId;
87
+ },
88
+ true,
89
+ );
90
+ },
91
+ ).finally(() => {
92
+ dataListener?.remove();
93
+ completeListener?.remove();
94
+ responseListener?.remove();
95
+ incrementalDataListener?.remove();
96
+ });
97
+ }
98
+
99
+ function buildUrlForBundle(bundlePathAndQuery: string) {
100
+ const {url: serverUrl} = getDevServer();
101
+ return (
102
+ serverUrl.replace(/\/+$/, '') + '/' + bundlePathAndQuery.replace(/^\/+/, '')
103
+ );
104
+ }
105
+
106
+ module.exports = function (bundlePathAndQuery: string): Promise<void> {
107
+ const requestUrl = buildUrlForBundle(bundlePathAndQuery);
108
+ let loadPromise = cachedPromisesByUrl.get(requestUrl);
109
+
110
+ if (loadPromise) {
111
+ return loadPromise;
112
+ }
113
+ DevLoadingView.showMessage('Downloading...', 'load');
114
+ ++pendingRequests;
115
+
116
+ loadPromise = asyncRequest(requestUrl)
117
+ .then<void>(({body, headers}) => {
118
+ if (
119
+ headers['Content-Type'] != null &&
120
+ headers['Content-Type'].indexOf('application/json') >= 0
121
+ ) {
122
+ // Errors are returned as JSON.
123
+ throw new Error(
124
+ JSON.parse(body).message ||
125
+ `Unknown error fetching '${bundlePathAndQuery}'`,
126
+ );
127
+ }
128
+
129
+ HMRClient.registerBundle(requestUrl);
130
+
131
+ // Some engines do not support `sourceURL` as a comment. We expose a
132
+ // `globalEvalWithSourceUrl` function to handle updates in that case.
133
+ if (global.globalEvalWithSourceUrl) {
134
+ global.globalEvalWithSourceUrl(body, requestUrl);
135
+ } else {
136
+ // [Windows #12704 - CodeQL patch]
137
+ // eslint-disable-next-line no-eval
138
+ eval(body); // CodeQL [js/eval-usage] Debug only. Developer inner loop.
139
+ }
140
+ })
141
+ .catch<void>(e => {
142
+ cachedPromisesByUrl.delete(requestUrl);
143
+ throw e;
144
+ })
145
+ .finally(() => {
146
+ if (!--pendingRequests) {
147
+ DevLoadingView.hide();
148
+ }
149
+ });
150
+
151
+ cachedPromisesByUrl.set(requestUrl, loadPromise);
152
+ return loadPromise;
153
+ };
@@ -17,7 +17,7 @@ const version: $ReadOnly<{
17
17
  major: 0,
18
18
  minor: 75,
19
19
  patch: 0,
20
- prerelease: 'nightly-20240502-88de74b2d',
20
+ prerelease: 'nightly-20240518-93c079b92',
21
21
  };
22
22
 
23
23
  module.exports = {version};
@@ -19,11 +19,13 @@ if (NativePerformance) {
19
19
  } else {
20
20
  if (!global.performance) {
21
21
  // $FlowExpectedError[cannot-write]
22
- global.performance = ({
23
- now: function () {
22
+ global.performance = {
23
+ mark: () => {},
24
+ measure: () => {},
25
+ now: () => {
24
26
  const performanceNow = global.nativePerformanceNow || Date.now;
25
27
  return performanceNow();
26
28
  },
27
- }: {now?: () => number});
29
+ };
28
30
  }
29
31
  }
@@ -67,19 +67,11 @@ function setTargetForInstanceHandle(
67
67
  instanceHandleToTargetMap.set(key, target);
68
68
  }
69
69
 
70
- function unsetTargetForInstanceHandle(instanceHandle: mixed): void {
71
- // $FlowExpectedError[incompatible-type] instanceHandle is typed as mixed but we know it's an object and we need it to be to use it as a key in a WeakMap.
72
- const key: interface {} = instanceHandle;
73
- instanceHandleToTargetMap.delete(key);
74
- }
75
-
76
70
  // The mapping between ReactNativeElement and their corresponding shadow node
77
71
  // also needs to be kept here because React removes the link when unmounting.
78
- // We also keep the instance handle so we don't have to retrieve it again
79
- // from the target to unobserve.
80
- const targetToShadowNodeAndInstanceHandleMap: WeakMap<
72
+ const targetToShadowNodeMap: WeakMap<
81
73
  ReactNativeElement,
82
- [ReturnType<typeof getShadowNode>, mixed],
74
+ ReturnType<typeof getShadowNode>,
83
75
  > = new WeakMap();
84
76
 
85
77
  /**
@@ -163,12 +155,8 @@ export function observe({
163
155
  // access it even after the instance handle has been unmounted.
164
156
  setTargetForInstanceHandle(instanceHandle, target);
165
157
 
166
- // Same for the mapping between the target and its shadow node
167
- // and instance handle.
168
- targetToShadowNodeAndInstanceHandleMap.set(target, [
169
- targetShadowNode,
170
- instanceHandle,
171
- ]);
158
+ // Same for the mapping between the target and its shadow node.
159
+ targetToShadowNodeMap.set(target, targetShadowNode);
172
160
 
173
161
  if (!isConnected) {
174
162
  NativeIntersectionObserver.connect(notifyIntersectionObservers);
@@ -201,26 +189,18 @@ export function unobserve(
201
189
  return;
202
190
  }
203
191
 
204
- const targetShadowNodeAndInstanceHandle =
205
- targetToShadowNodeAndInstanceHandleMap.get(target);
206
- if (targetShadowNodeAndInstanceHandle == null) {
192
+ const targetShadowNode = targetToShadowNodeMap.get(target);
193
+ if (targetShadowNode == null) {
207
194
  console.error(
208
195
  'IntersectionObserverManager: could not find registration data for target',
209
196
  );
210
197
  return;
211
198
  }
212
199
 
213
- const [targetShadowNode, instanceHandle] = targetShadowNodeAndInstanceHandle;
214
-
215
200
  NativeIntersectionObserver.unobserve(
216
201
  intersectionObserverId,
217
202
  targetShadowNode,
218
203
  );
219
-
220
- // We can guarantee we won't receive any more entries for this target,
221
- // so we don't need to keep the mappings anymore.
222
- unsetTargetForInstanceHandle(instanceHandle);
223
- targetToShadowNodeAndInstanceHandleMap.delete(target);
224
204
  }
225
205
 
226
206
  /**
@@ -449,31 +449,6 @@ export function withSubscription(
449
449
  this._subscription.unsubscribe();
450
450
  }
451
451
  }
452
-
453
- _handleDismiss = (): void => {
454
- // Here we handle the cases when the log is dismissed and it
455
- // was either the last log, or when the current index
456
- // is now outside the bounds of the log array.
457
- const {selectedLogIndex, logs: stateLogs} = this.state;
458
- const logsArray = Array.from(stateLogs);
459
- if (selectedLogIndex != null) {
460
- if (logsArray.length - 1 <= 0) {
461
- setSelectedLog(-1);
462
- } else if (selectedLogIndex >= logsArray.length - 1) {
463
- setSelectedLog(selectedLogIndex - 1);
464
- }
465
-
466
- dismiss(logsArray[selectedLogIndex]);
467
- }
468
- };
469
-
470
- _handleMinimize = (): void => {
471
- setSelectedLog(-1);
472
- };
473
-
474
- _handleSetSelectedLog = (index: number): void => {
475
- setSelectedLog(index);
476
- };
477
452
  }
478
453
 
479
454
  return LogBoxStateSubscription;
@@ -166,6 +166,9 @@ const validAttributesForNonEventProps = {
166
166
  backgroundColor: {process: require('../StyleSheet/processColor').default},
167
167
  transform: true,
168
168
  transformOrigin: true,
169
+ experimental_filter: {
170
+ process: require('../StyleSheet/processFilter').default,
171
+ },
169
172
  opacity: true,
170
173
  elevation: true,
171
174
  shadowColor: {process: require('../StyleSheet/processColor').default},
@@ -220,6 +220,9 @@ const validAttributesForNonEventProps = {
220
220
  hitSlop: {diff: require('../Utilities/differ/insetsDiffer')},
221
221
  collapsable: true,
222
222
  collapsableChildren: true,
223
+ experimental_filter: {
224
+ process: require('../StyleSheet/processFilter').default,
225
+ },
223
226
 
224
227
  borderTopWidth: true,
225
228
  borderTopColor: {process: require('../StyleSheet/processColor').default},
@@ -240,6 +240,9 @@ const validAttributesForNonEventProps = {
240
240
  hitSlop: {diff: require('../Utilities/differ/insetsDiffer')},
241
241
  collapsable: true,
242
242
  collapsableChildren: true,
243
+ experimental_filter: {
244
+ process: require('../StyleSheet/processFilter').default,
245
+ },
243
246
 
244
247
  borderTopWidth: true,
245
248
  borderTopColor: {process: require('../StyleSheet/processColor').default},
@@ -14,6 +14,7 @@ const ReactNativeStyleAttributes = require('../Components/View/ReactNativeStyleA
14
14
  const resolveAssetSource = require('../Image/resolveAssetSource');
15
15
  const processColor = require('../StyleSheet/processColor').default;
16
16
  const processColorArray = require('../StyleSheet/processColorArray');
17
+ const processFilter = require('../StyleSheet/processFilter').default;
17
18
  const insetsDiffer = require('../Utilities/differ/insetsDiffer');
18
19
  const matricesDiffer = require('../Utilities/differ/matricesDiffer');
19
20
  const pointsDiffer = require('../Utilities/differ/pointsDiffer');
@@ -188,6 +189,8 @@ function getProcessorForType(typeName: string): ?(nextProp: any) => any {
188
189
  return processColor;
189
190
  case 'ColorArray':
190
191
  return processColorArray;
192
+ case 'Filter':
193
+ return processFilter;
191
194
  case 'ImageSource':
192
195
  return resolveAssetSource;
193
196
  }
@@ -11,6 +11,7 @@
11
11
  'use strict';
12
12
 
13
13
  import type AnimatedNode from '../Animated/nodes/AnimatedNode';
14
+ import type {FilterPrimitive} from '../StyleSheet/processFilter';
14
15
  import type {
15
16
  ____DangerouslyImpreciseStyle_InternalOverrides,
16
17
  ____ImageStyle_InternalOverrides,
@@ -690,10 +691,15 @@ export type ____ShadowStyle_Internal = $ReadOnly<{
690
691
  ...____ShadowStyle_InternalOverrides,
691
692
  }>;
692
693
 
694
+ type ____FilterStyle_Internal = $ReadOnly<{
695
+ experimental_filter?: $ReadOnlyArray<FilterPrimitive>,
696
+ }>;
697
+
693
698
  export type ____ViewStyle_InternalCore = $ReadOnly<{
694
699
  ...$Exact<____LayoutStyle_Internal>,
695
700
  ...$Exact<____ShadowStyle_Internal>,
696
701
  ...$Exact<____TransformStyle_Internal>,
702
+ ...____FilterStyle_Internal,
697
703
  backfaceVisibility?: 'visible' | 'hidden',
698
704
  backgroundColor?: ____ColorValue_Internal,
699
705
  borderColor?: ____ColorValue_Internal,
@@ -0,0 +1,132 @@
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
+ 'use strict';
12
+
13
+ export type FilterPrimitive =
14
+ | {brightness: number | string}
15
+ | {blur: number | string}
16
+ | {contrast: number | string}
17
+ | {grayscale: number | string}
18
+ | {hueRotate: number | string}
19
+ | {invert: number | string}
20
+ | {opacity: number | string}
21
+ | {saturate: number | string}
22
+ | {sepia: number | string};
23
+
24
+ export default function processFilter(
25
+ filter: $ReadOnlyArray<FilterPrimitive> | string,
26
+ ): $ReadOnlyArray<FilterPrimitive> {
27
+ let result: Array<FilterPrimitive> = [];
28
+ if (typeof filter === 'string') {
29
+ // matches on functions with args like "brightness(1.5)"
30
+ const regex = new RegExp(/(\w+)\(([^)]+)\)/g);
31
+ let matches;
32
+
33
+ while ((matches = regex.exec(filter))) {
34
+ const amount = _getFilterAmount(matches[1], matches[2]);
35
+
36
+ if (amount != null) {
37
+ const filterPrimitive = {};
38
+ // $FlowFixMe The key will be the correct one but flow can't see that.
39
+ filterPrimitive[matches[1]] = amount;
40
+ // $FlowFixMe The key will be the correct one but flow can't see that.
41
+ result.push(filterPrimitive);
42
+ } else {
43
+ // If any primitive is invalid then apply none of the filters. This is how
44
+ // web works and makes it clear that something is wrong becuase no
45
+ // graphical effects are happening.
46
+ return [];
47
+ }
48
+ }
49
+ } else {
50
+ for (const filterPrimitive of filter) {
51
+ const [filterName, filterValue] = Object.entries(filterPrimitive)[0];
52
+ const amount = _getFilterAmount(filterName, filterValue);
53
+
54
+ if (amount != null) {
55
+ const resultObject = {};
56
+ // $FlowFixMe
57
+ resultObject[filterName] = amount;
58
+ // $FlowFixMe
59
+ result.push(resultObject);
60
+ } else {
61
+ // If any primitive is invalid then apply none of the filters. This is how
62
+ // web works and makes it clear that something is wrong becuase no
63
+ // graphical effects are happening.
64
+ return [];
65
+ }
66
+ }
67
+ }
68
+
69
+ return result;
70
+ }
71
+
72
+ function _getFilterAmount(filterName: string, filterArgs: mixed): ?number {
73
+ let filterArgAsNumber: number;
74
+ let unit: string;
75
+ if (typeof filterArgs === 'string') {
76
+ // matches on args with units like "1.5 5% -80deg"
77
+ const argsWithUnitsRegex = new RegExp(/([+-]?\d*(\.\d+)?)([a-zA-Z%]+)?/g);
78
+ const match = argsWithUnitsRegex.exec(filterArgs);
79
+
80
+ if (!match || isNaN(Number(match[1]))) {
81
+ return undefined;
82
+ }
83
+
84
+ filterArgAsNumber = Number(match[1]);
85
+ unit = match[3];
86
+ } else if (typeof filterArgs === 'number') {
87
+ filterArgAsNumber = filterArgs;
88
+ } else {
89
+ return undefined;
90
+ }
91
+
92
+ switch (filterName) {
93
+ // Hue rotate takes some angle that can have a unit and can be
94
+ // negative. Additionally, 0 with no unit is allowed.
95
+ case 'hueRotate':
96
+ if (filterArgAsNumber === 0) {
97
+ return 0;
98
+ }
99
+ if (unit !== 'deg' && unit !== 'rad') {
100
+ return undefined;
101
+ }
102
+ return unit === 'rad'
103
+ ? (180 * filterArgAsNumber) / Math.PI
104
+ : filterArgAsNumber;
105
+ // blur takes any positive CSS length that is not a percent. In RN
106
+ // we currently only have DIPs, so we are not parsing units here.
107
+ case 'blur':
108
+ if ((unit && unit !== 'px') || filterArgAsNumber < 0) {
109
+ return undefined;
110
+ }
111
+ return filterArgAsNumber;
112
+ // All other filters except take a non negative number or percentage. There
113
+ // are no units associated with this value and percentage numbers map 1-to-1
114
+ // to a non-percentage number (e.g. 50% == 0.5).
115
+ case 'brightness':
116
+ case 'contrast':
117
+ case 'grayscale':
118
+ case 'invert':
119
+ case 'opacity':
120
+ case 'saturate':
121
+ case 'sepia':
122
+ if ((unit && unit !== '%' && unit !== 'px') || filterArgAsNumber < 0) {
123
+ return undefined;
124
+ }
125
+ if (unit === '%') {
126
+ filterArgAsNumber /= 100;
127
+ }
128
+ return filterArgAsNumber;
129
+ default:
130
+ return undefined;
131
+ }
132
+ }