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

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 (142) hide show
  1. package/.flowconfig +2 -4
  2. package/CHANGELOG.json +31 -1
  3. package/CHANGELOG.md +24 -8
  4. package/Libraries/Alert/Alert.js +3 -0
  5. package/Libraries/Animated/AnimatedImplementation.js +7 -7
  6. package/Libraries/Animated/animations/Animation.js +10 -0
  7. package/Libraries/Animated/animations/TimingAnimation.js +1 -0
  8. package/Libraries/Animated/components/AnimatedScrollView.js +2 -2
  9. package/Libraries/Animated/createAnimatedComponent.js +1 -1
  10. package/Libraries/Animated/nodes/AnimatedValue.js +1 -0
  11. package/Libraries/Animated/useAnimatedProps.js +138 -6
  12. package/Libraries/BatchedBridge/NativeModules.js +2 -0
  13. package/Libraries/Blob/FileReader.js +1 -1
  14. package/Libraries/Blob/URL.js +2 -62
  15. package/Libraries/Blob/URLSearchParams.js +71 -0
  16. package/Libraries/Components/DrawerAndroid/DrawerLayoutAndroid.android.js +1 -1
  17. package/Libraries/Components/RefreshControl/__mocks__/RefreshControlMock.js +1 -1
  18. package/Libraries/Components/ScrollView/AndroidHorizontalScrollViewNativeComponent.js +3 -0
  19. package/Libraries/Components/ScrollView/ScrollView.js +5 -5
  20. package/Libraries/Components/ScrollView/ScrollViewNativeComponent.js +3 -0
  21. package/Libraries/Components/ScrollView/ScrollViewStickyHeader.js +1 -1
  22. package/Libraries/Components/StatusBar/StatusBar.js +3 -1
  23. package/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js +3 -0
  24. package/Libraries/Components/TextInput/TextInput.d.ts +32 -2
  25. package/Libraries/Components/TextInput/TextInput.js +220 -80
  26. package/Libraries/Components/TextInput/TextInput.win32.js +220 -86
  27. package/Libraries/Components/View/ReactNativeStyleAttributes.js +22 -0
  28. package/Libraries/Components/View/ReactNativeViewAttributes.js +2 -0
  29. package/Libraries/Components/View/ReactNativeViewAttributes.win32.js +2 -0
  30. package/Libraries/Components/View/ViewAccessibility.d.ts +15 -0
  31. package/Libraries/Components/View/ViewNativeComponent.js +6 -0
  32. package/Libraries/Components/View/ViewPropTypes.js +14 -0
  33. package/Libraries/Components/View/ViewPropTypes.win32.js +14 -0
  34. package/Libraries/Core/ExceptionsManager.js +2 -0
  35. package/Libraries/Core/InitializeCore.js +1 -1
  36. package/Libraries/Core/ReactFiberErrorDialog.js +3 -0
  37. package/Libraries/Core/ReactNativeVersion.js +1 -1
  38. package/Libraries/Core/setUpErrorHandling.js +7 -1
  39. package/Libraries/Core/setUpReactRefresh.js +0 -4
  40. package/Libraries/Image/AssetSourceResolver.js +28 -1
  41. package/Libraries/Image/Image.android.js +9 -14
  42. package/Libraries/Image/Image.ios.js +11 -22
  43. package/Libraries/Image/Image.win32.js +10 -21
  44. package/Libraries/Image/ImageBackground.js +1 -8
  45. package/Libraries/Image/ImageUtils.js +9 -9
  46. package/Libraries/Image/ImageViewNativeComponent.js +4 -0
  47. package/Libraries/Inspector/NetworkOverlay.js +1 -1
  48. package/Libraries/Interaction/TaskQueue.js +1 -0
  49. package/Libraries/Lists/FlatList.js +1 -1
  50. package/Libraries/Lists/SectionList.js +2 -2
  51. package/Libraries/Lists/SectionListModern.js +1 -1
  52. package/Libraries/LogBox/Data/LogBoxData.js +31 -4
  53. package/Libraries/NativeComponent/BaseViewConfig.android.js +2 -0
  54. package/Libraries/NativeComponent/BaseViewConfig.ios.js +7 -0
  55. package/Libraries/NativeComponent/BaseViewConfig.win32.js +7 -0
  56. package/Libraries/NativeComponent/NativeComponentRegistry.js +9 -2
  57. package/Libraries/Network/XMLHttpRequest.js +4 -2
  58. package/Libraries/ReactNative/BridgelessUIManager.js +1 -0
  59. package/Libraries/ReactNative/ReactFabricPublicInstance/ReactFabricHostComponent.js +1 -1
  60. package/Libraries/ReactNative/ReactFabricPublicInstance/ReactFabricPublicInstance.js +5 -5
  61. package/Libraries/ReactNative/RendererImplementation.js +24 -2
  62. package/Libraries/ReactNative/getNativeComponentAttributes.js +8 -0
  63. package/Libraries/Renderer/shims/ReactNativeTypes.js +3 -1
  64. package/Libraries/StyleSheet/StyleSheet.js +1 -1
  65. package/Libraries/StyleSheet/StyleSheet.win32.js +1 -1
  66. package/Libraries/StyleSheet/StyleSheetTypes.d.ts +57 -19
  67. package/Libraries/StyleSheet/StyleSheetTypes.js +60 -23
  68. package/Libraries/StyleSheet/processBackgroundImage.js +286 -0
  69. package/Libraries/StyleSheet/processBoxShadow.js +211 -0
  70. package/Libraries/StyleSheet/processFilter.js +24 -14
  71. package/Libraries/Text/Text.js +395 -212
  72. package/Libraries/Text/Text.win32.js +443 -245
  73. package/Libraries/Text/TextNativeComponent.js +7 -0
  74. package/Libraries/Text/TextNativeComponent.win32.js +7 -0
  75. package/Libraries/TurboModule/TurboModuleRegistry.js +13 -50
  76. package/Libraries/Types/CodegenTypes.js +3 -1
  77. package/Libraries/Utilities/HMRClient.js +1 -0
  78. package/Libraries/Utilities/Platform.android.js +1 -1
  79. package/Libraries/Utilities/Platform.d.ts +1 -1
  80. package/Libraries/Utilities/Platform.flow.js +2 -2
  81. package/Libraries/Utilities/Platform.flow.win32.js +3 -3
  82. package/Libraries/Utilities/Platform.ios.js +1 -1
  83. package/Libraries/Utilities/Platform.win32.js +1 -1
  84. package/Libraries/Utilities/ReactNativeTestTools.js +1 -1
  85. package/Libraries/WebSocket/WebSocket.js +1 -1
  86. package/Libraries/vendor/emitter/EventEmitter.js +1 -0
  87. package/flow/jest.js +2 -2
  88. package/index.js +1 -0
  89. package/index.win32.js +1 -0
  90. package/jest/mockModal.js +1 -3
  91. package/jest/mockScrollView.js +1 -1
  92. package/jest/renderer.js +2 -2
  93. package/jest/setup.js +16 -9
  94. package/overrides.json +16 -16
  95. package/package.json +15 -15
  96. package/src/private/{core/components → components}/HScrollViewNativeComponents.js +8 -8
  97. package/src/private/{core/components → components}/VScrollViewNativeComponents.js +7 -7
  98. package/src/private/{core/components → components}/useSyncOnScroll.js +2 -2
  99. package/src/private/featureflags/ReactNativeFeatureFlags.js +143 -19
  100. package/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js +25 -5
  101. package/src/private/hooks/DebouncedEffectImplementation.js +148 -0
  102. package/src/private/hooks/useDebouncedEffect.js +23 -0
  103. package/{Libraries/Core → src/private/renderer/errorhandling}/ErrorHandlers.js +14 -4
  104. package/src/private/setup/setUpDOM.js +28 -0
  105. package/src/private/setup/setUpIntersectionObserver.js +27 -0
  106. package/src/private/setup/setUpMutationObserver.js +26 -0
  107. package/src/private/setup/setUpPerformanceObserver.js +64 -0
  108. package/src/private/specs/modules/NativeDebuggerSessionObserver.js +23 -0
  109. package/src/private/specs/modules/NativeLinkingManager.js +1 -1
  110. package/src/private/specs/modules/NativePlatformConstantsAndroid.js +1 -1
  111. package/src/private/specs/modules/NativePlatformConstantsIOS.js +1 -1
  112. package/src/private/specs/modules/NativePlatformConstantsWin.js +8 -1
  113. package/src/private/webapis/dom/nodes/ReadOnlyNode.js +6 -4
  114. package/{Libraries/IntersectionObserver → src/private/webapis/intersectionobserver}/IntersectionObserver.js +1 -1
  115. package/{Libraries/IntersectionObserver → src/private/webapis/intersectionobserver}/IntersectionObserverEntry.js +3 -3
  116. package/{Libraries/IntersectionObserver → src/private/webapis/intersectionobserver}/IntersectionObserverManager.js +5 -8
  117. package/src/private/{specs/modules → webapis/intersectionobserver/specs}/NativeIntersectionObserver.js +2 -2
  118. package/{Libraries/IntersectionObserver → src/private/webapis/intersectionobserver/specs}/__mocks__/NativeIntersectionObserver.js +4 -4
  119. package/{Libraries/MutationObserver → src/private/webapis/mutationobserver}/MutationObserver.js +1 -1
  120. package/{Libraries/MutationObserver → src/private/webapis/mutationobserver}/MutationObserverManager.js +5 -5
  121. package/{Libraries/MutationObserver → src/private/webapis/mutationobserver}/MutationRecord.js +4 -6
  122. package/src/private/{specs/modules → webapis/mutationobserver/specs}/NativeMutationObserver.js +2 -2
  123. package/{Libraries/MutationObserver → src/private/webapis/mutationobserver/specs}/__mocks__/NativeMutationObserver.js +5 -5
  124. package/src/private/webapis/performance/{EventCounts.js → EventTiming.js} +65 -3
  125. package/src/private/webapis/performance/LongTasks.js +39 -0
  126. package/src/private/webapis/performance/Performance.js +22 -9
  127. package/src/private/webapis/performance/PerformanceEntry.js +36 -18
  128. package/src/private/webapis/performance/PerformanceObserver.js +29 -43
  129. package/src/private/webapis/performance/RawPerformanceEntry.js +24 -1
  130. package/src/private/webapis/performance/UserTiming.js +17 -12
  131. package/src/private/webapis/performance/specs/NativePerformanceObserver.js +1 -1
  132. package/src-win/Libraries/Components/View/ViewAccessibility.d.ts +15 -0
  133. package/types/experimental.d.ts +20 -1
  134. package/Libraries/Core/setUpIntersectionObserver.js +0 -16
  135. package/Libraries/Core/setUpMutationObserver.js +0 -16
  136. package/Libraries/Core/setUpPerformanceObserver.js +0 -18
  137. package/Libraries/IntersectionObserver/NativeIntersectionObserver.js +0 -13
  138. package/Libraries/MutationObserver/NativeMutationObserver.js +0 -13
  139. package/Libraries/Text/TextOptimized.js +0 -538
  140. package/src/private/core/setUpDOM.js +0 -18
  141. package/src/private/webapis/performance/PerformanceEventTiming.js +0 -55
  142. /package/src/private/{core → styles}/composeStyles.js +0 -0
@@ -11,25 +11,6 @@ import {Animated} from '../Animated/Animated';
11
11
  import {ImageResizeMode} from '../Image/ImageResizeMode';
12
12
  import {ColorValue} from './StyleSheet';
13
13
 
14
- export type FilterPrimitive =
15
- | {brightness: number | string}
16
- | {blur: number | string}
17
- | {contrast: number | string}
18
- | {grayscale: number | string}
19
- | {'hue-rotate': number | string}
20
- | {invert: number | string}
21
- | {opacity: number | string}
22
- | {saturate: number | string}
23
- | {sepia: number | string}
24
- | {'drop-shadow': DropShadowPrimitive | string};
25
-
26
- export type DropShadowPrimitive = {
27
- offsetX: number | string;
28
- offsetY: number | string;
29
- standardDeviation?: number | string | undefined;
30
- color?: ColorValue | number | undefined;
31
- };
32
-
33
14
  type FlexAlignType =
34
15
  | 'flex-start'
35
16
  | 'flex-end'
@@ -246,6 +227,62 @@ export interface TransformsStyle {
246
227
  translateY?: AnimatableNumericValue | undefined;
247
228
  }
248
229
 
230
+ export type FilterFunction =
231
+ | {brightness: number | string}
232
+ | {blur: number | string}
233
+ | {contrast: number | string}
234
+ | {grayscale: number | string}
235
+ | {hueRotate: number | string}
236
+ | {invert: number | string}
237
+ | {opacity: number | string}
238
+ | {saturate: number | string}
239
+ | {sepia: number | string}
240
+ | {dropShadow: DropShadowPrimitive | string};
241
+
242
+ export type DropShadowPrimitive = {
243
+ offsetX: number | string;
244
+ offsetY: number | string;
245
+ standardDeviation?: number | string | undefined;
246
+ color?: ColorValue | number | undefined;
247
+ };
248
+
249
+ export type BoxShadowPrimitive = {
250
+ offsetX: number | string;
251
+ offsetY: number | string;
252
+ color?: string | undefined;
253
+ blurRadius?: ColorValue | number | undefined;
254
+ spreadDistance?: number | string | undefined;
255
+ inset?: boolean | undefined;
256
+ };
257
+
258
+ export type BlendMode =
259
+ | 'normal'
260
+ | 'multiply'
261
+ | 'screen'
262
+ | 'overlay'
263
+ | 'darken'
264
+ | 'lighten'
265
+ | 'color-dodge'
266
+ | 'color-burn'
267
+ | 'hard-light'
268
+ | 'soft-light'
269
+ | 'difference'
270
+ | 'exclusion'
271
+ | 'hue'
272
+ | 'saturation'
273
+ | 'color'
274
+ | 'luminosity';
275
+
276
+ export type GradientValue = {
277
+ type: 'linearGradient';
278
+ // Angle or direction enums
279
+ direction: string | undefined;
280
+ colorStops: Array<{
281
+ color: ColorValue;
282
+ position: number | undefined;
283
+ }>;
284
+ };
285
+
249
286
  /**
250
287
  * @see https://reactnative.dev/docs/view#style
251
288
  */
@@ -295,6 +332,7 @@ export interface ViewStyle extends FlexStyle, ShadowStyleIOS, TransformsStyle {
295
332
  * Controls whether the View can be the target of touch events.
296
333
  */
297
334
  pointerEvents?: 'box-none' | 'none' | 'box-only' | 'auto' | undefined;
335
+ isolation?: 'auto' | 'isolate' | undefined;
298
336
  cursor?: CursorValue | undefined;
299
337
  }
300
338
 
@@ -34,25 +34,6 @@ export type EdgeInsetsValue = {
34
34
  bottom: number,
35
35
  };
36
36
 
37
- export type FilterPrimitive =
38
- | {brightness: number | string}
39
- | {blur: number | string}
40
- | {contrast: number | string}
41
- | {grayscale: number | string}
42
- | {'hue-rotate': number | string}
43
- | {invert: number | string}
44
- | {opacity: number | string}
45
- | {saturate: number | string}
46
- | {sepia: number | string}
47
- | {'drop-shadow': DropShadowPrimitive | string};
48
-
49
- export type DropShadowPrimitive = {
50
- offsetX: number | string,
51
- offsetY: number | string,
52
- standardDeviation?: number | string,
53
- color?: ____ColorValue_Internal | number,
54
- };
55
-
56
37
  export type DimensionValue = number | string | 'auto' | AnimatedNode | null;
57
38
  export type AnimatableNumericValue = number | AnimatedNode;
58
39
 
@@ -709,15 +690,66 @@ export type ____ShadowStyle_Internal = $ReadOnly<{
709
690
  ...____ShadowStyle_InternalOverrides,
710
691
  }>;
711
692
 
712
- type ____FilterStyle_Internal = $ReadOnly<{
713
- experimental_filter?: $ReadOnlyArray<FilterPrimitive>,
714
- }>;
693
+ export type FilterFunction =
694
+ | {brightness: number | string}
695
+ | {blur: number | string}
696
+ | {contrast: number | string}
697
+ | {grayscale: number | string}
698
+ | {hueRotate: number | string}
699
+ | {invert: number | string}
700
+ | {opacity: number | string}
701
+ | {saturate: number | string}
702
+ | {sepia: number | string}
703
+ | {dropShadow: DropShadowPrimitive | string};
704
+
705
+ export type DropShadowPrimitive = {
706
+ offsetX: number | string,
707
+ offsetY: number | string,
708
+ standardDeviation?: number | string,
709
+ color?: ____ColorValue_Internal,
710
+ };
711
+
712
+ export type GradientValue = {
713
+ type: 'linearGradient',
714
+ // Angle or direction enums
715
+ direction?: string,
716
+ colorStops: $ReadOnlyArray<{
717
+ color: ____ColorValue_Internal,
718
+ position?: string,
719
+ }>,
720
+ };
721
+
722
+ export type BoxShadowPrimitive = {
723
+ offsetX: number | string,
724
+ offsetY: number | string,
725
+ color?: ____ColorValue_Internal,
726
+ blurRadius?: number | string,
727
+ spreadDistance?: number | string,
728
+ inset?: boolean,
729
+ };
730
+
731
+ type ____BlendMode_Internal =
732
+ | 'normal'
733
+ | 'multiply'
734
+ | 'screen'
735
+ | 'overlay'
736
+ | 'darken'
737
+ | 'lighten'
738
+ | 'color-dodge'
739
+ | 'color-burn'
740
+ | 'hard-light'
741
+ | 'soft-light'
742
+ | 'difference'
743
+ | 'exclusion'
744
+ | 'hue'
745
+ | 'saturation'
746
+ | 'color'
747
+ | 'luminosity';
715
748
 
716
749
  export type ____ViewStyle_InternalCore = $ReadOnly<{
717
750
  ...$Exact<____LayoutStyle_Internal>,
718
751
  ...$Exact<____ShadowStyle_Internal>,
719
752
  ...$Exact<____TransformStyle_Internal>,
720
- ...____FilterStyle_Internal,
721
753
  backfaceVisibility?: 'visible' | 'hidden',
722
754
  backgroundColor?: ____ColorValue_Internal,
723
755
  borderColor?: ____ColorValue_Internal,
@@ -756,6 +788,11 @@ export type ____ViewStyle_InternalCore = $ReadOnly<{
756
788
  elevation?: number,
757
789
  pointerEvents?: 'auto' | 'none' | 'box-none' | 'box-only',
758
790
  cursor?: CursorValue,
791
+ experimental_boxShadow?: $ReadOnlyArray<BoxShadowPrimitive> | string,
792
+ experimental_filter?: $ReadOnlyArray<FilterFunction> | string,
793
+ experimental_mixBlendMode?: ____BlendMode_Internal,
794
+ experimental_backgroundImage?: $ReadOnlyArray<GradientValue> | string,
795
+ isolation?: 'auto' | 'isolate',
759
796
  }>;
760
797
 
761
798
  export type ____ViewStyle_Internal = $ReadOnly<{
@@ -0,0 +1,286 @@
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 strict-local
9
+ */
10
+
11
+ 'use strict';
12
+
13
+ import type {ProcessedColorValue} from './processColor';
14
+ import type {GradientValue} from './StyleSheetTypes';
15
+
16
+ const processColor = require('./processColor').default;
17
+ const DIRECTION_REGEX =
18
+ /^to\s+(?:top|bottom|left|right)(?:\s+(?:top|bottom|left|right))?/;
19
+ const ANGLE_UNIT_REGEX = /^([+-]?\d*\.?\d+)(deg|grad|rad|turn)$/i;
20
+
21
+ const TO_BOTTOM_START_END_POINTS = {
22
+ start: {x: 0.5, y: 0},
23
+ end: {x: 0.5, y: 1},
24
+ };
25
+
26
+ type ParsedGradientValue = {
27
+ type: 'linearGradient',
28
+ start: {x: number, y: number},
29
+ end: {x: number, y: number},
30
+ colorStops: $ReadOnlyArray<{
31
+ color: ProcessedColorValue,
32
+ position: number,
33
+ }>,
34
+ };
35
+
36
+ export default function processBackgroundImage(
37
+ backgroundImage: ?($ReadOnlyArray<GradientValue> | string),
38
+ ): $ReadOnlyArray<ParsedGradientValue> {
39
+ let result: $ReadOnlyArray<ParsedGradientValue> = [];
40
+ if (backgroundImage == null) {
41
+ return result;
42
+ }
43
+
44
+ if (typeof backgroundImage === 'string') {
45
+ result = parseCSSLinearGradient(backgroundImage);
46
+ } else if (Array.isArray(backgroundImage)) {
47
+ for (const bgImage of backgroundImage) {
48
+ const processedColorStops = [];
49
+ 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.
64
+ return [];
65
+ }
66
+
67
+ if (processedColor != null) {
68
+ processedColorStops.push({
69
+ color: processedColor,
70
+ position: processedPosition,
71
+ });
72
+ } else {
73
+ // If a color is invalid, return an empty array and do not apply gradient. Same as web.
74
+ return [];
75
+ }
76
+ }
77
+
78
+ let points: {
79
+ start: ParsedGradientValue['start'],
80
+ end: ParsedGradientValue['end'],
81
+ } | null = null;
82
+
83
+ if (typeof bgImage.direction === 'undefined') {
84
+ points = TO_BOTTOM_START_END_POINTS;
85
+ } else if (ANGLE_UNIT_REGEX.test(bgImage.direction)) {
86
+ const angle = parseAngle(bgImage.direction);
87
+ if (angle != null) {
88
+ points = calculateStartEndPointsFromAngle(angle);
89
+ }
90
+ } else if (DIRECTION_REGEX.test(bgImage.direction)) {
91
+ const processedPoints = calculateStartEndPointsFromDirection(
92
+ bgImage.direction,
93
+ );
94
+ if (processedPoints != null) {
95
+ points = processedPoints;
96
+ }
97
+ }
98
+
99
+ if (points != null) {
100
+ result = result.concat({
101
+ type: 'linearGradient',
102
+ start: points.start,
103
+ end: points.end,
104
+ colorStops: processedColorStops,
105
+ });
106
+ }
107
+ }
108
+ }
109
+
110
+ return result;
111
+ }
112
+
113
+ function parseCSSLinearGradient(
114
+ cssString: string,
115
+ ): $ReadOnlyArray<ParsedGradientValue> {
116
+ const gradients = [];
117
+ let match;
118
+ const linearGradientRegex = /linear-gradient\s*\(((?:\([^)]*\)|[^())])*)\)/gi;
119
+
120
+ while ((match = linearGradientRegex.exec(cssString))) {
121
+ const gradientContent = match[1];
122
+ const parts = gradientContent.split(',');
123
+ let points = TO_BOTTOM_START_END_POINTS;
124
+ const trimmedDirection = parts[0].trim().toLowerCase();
125
+ const colorStopRegex =
126
+ /\s*((?:(?:rgba?|hsla?)\s*\([^)]+\))|#[0-9a-fA-F]+|[a-zA-Z]+)(?:\s+([0-9.]+%?))?\s*/gi;
127
+
128
+ if (ANGLE_UNIT_REGEX.test(trimmedDirection)) {
129
+ const angle = parseAngle(trimmedDirection);
130
+ if (angle != null) {
131
+ points = calculateStartEndPointsFromAngle(angle);
132
+ parts.shift();
133
+ } else {
134
+ // If an angle is invalid, return an empty array and do not apply any gradient. Same as web.
135
+ return [];
136
+ }
137
+ } else if (DIRECTION_REGEX.test(trimmedDirection)) {
138
+ const parsedPoints =
139
+ calculateStartEndPointsFromDirection(trimmedDirection);
140
+ if (parsedPoints != null) {
141
+ points = parsedPoints;
142
+ parts.shift();
143
+ } else {
144
+ // If a direction is invalid, return an empty array and do not apply any gradient. Same as web.
145
+ return [];
146
+ }
147
+ } else if (!colorStopRegex.test(trimmedDirection)) {
148
+ // If first part is not an angle/direction or a color stop, return an empty array and do not apply any gradient. Same as web.
149
+ return [];
150
+ }
151
+ colorStopRegex.lastIndex = 0;
152
+
153
+ const colorStops = [];
154
+ const fullColorStopsStr = parts.join(',');
155
+ let colorStopMatch;
156
+ while ((colorStopMatch = colorStopRegex.exec(fullColorStopsStr))) {
157
+ const [, color, position] = colorStopMatch;
158
+ const processedColor = processColor(color.trim().toLowerCase());
159
+ if (
160
+ processedColor != null &&
161
+ (typeof position === 'undefined' || position.endsWith('%'))
162
+ ) {
163
+ colorStops.push({
164
+ color: processedColor,
165
+ position: position ? parseFloat(position) / 100 : null,
166
+ });
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 [];
170
+ }
171
+ }
172
+
173
+ gradients.push({
174
+ type: 'linearGradient',
175
+ start: points.start,
176
+ 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
+ })),
183
+ });
184
+ }
185
+
186
+ return gradients;
187
+ }
188
+
189
+ function calculateStartEndPointsFromDirection(direction: string): ?{
190
+ start: {x: number, y: number},
191
+ end: {x: number, y: number},
192
+ } {
193
+ // Remove extra whitespace
194
+ const normalizedDirection = direction.replace(/\s+/g, ' ');
195
+
196
+ switch (normalizedDirection) {
197
+ case 'to right':
198
+ return {
199
+ start: {x: 0, y: 0.5},
200
+ end: {x: 1, y: 0.5},
201
+ };
202
+ case 'to left':
203
+ return {
204
+ start: {x: 1, y: 0.5},
205
+ end: {x: 0, y: 0.5},
206
+ };
207
+ case 'to bottom':
208
+ return TO_BOTTOM_START_END_POINTS;
209
+ case 'to top':
210
+ return {
211
+ start: {x: 0.5, y: 1},
212
+ end: {x: 0.5, y: 0},
213
+ };
214
+ case 'to bottom right':
215
+ case 'to right bottom':
216
+ return {
217
+ start: {x: 0, y: 0},
218
+ end: {x: 1, y: 1},
219
+ };
220
+ case 'to top left':
221
+ case 'to left top':
222
+ return {
223
+ start: {x: 1, y: 1},
224
+ end: {x: 0, y: 0},
225
+ };
226
+ case 'to bottom left':
227
+ case 'to left bottom':
228
+ return {
229
+ start: {x: 1, y: 0},
230
+ end: {x: 0, y: 1},
231
+ };
232
+ case 'to top right':
233
+ case 'to right top':
234
+ return {
235
+ start: {x: 0, y: 1},
236
+ end: {x: 1, y: 0},
237
+ };
238
+ default:
239
+ return null;
240
+ }
241
+ }
242
+
243
+ function calculateStartEndPointsFromAngle(angleRadians: number): {
244
+ start: {x: number, y: number},
245
+ end: {x: number, y: number},
246
+ } {
247
+ // Normalize angle to be between 0 and 2π
248
+ let angleRadiansNormalized = angleRadians % (2 * Math.PI);
249
+ if (angleRadiansNormalized < 0) {
250
+ angleRadiansNormalized += 2 * Math.PI;
251
+ }
252
+
253
+ const endX = 0.5 + 0.5 * Math.sin(angleRadiansNormalized);
254
+ const endY = 0.5 - 0.5 * Math.cos(angleRadiansNormalized);
255
+
256
+ const startX = 1 - endX;
257
+ const startY = 1 - endY;
258
+
259
+ return {
260
+ start: {x: startX, y: startY},
261
+ end: {x: endX, y: endY},
262
+ };
263
+ }
264
+
265
+ function parseAngle(angle: string): ?number {
266
+ const match = angle.match(ANGLE_UNIT_REGEX);
267
+ if (!match) {
268
+ return null;
269
+ }
270
+
271
+ const [, value, unit] = match;
272
+
273
+ const numericValue = parseFloat(value);
274
+ switch (unit) {
275
+ case 'deg':
276
+ return (numericValue * Math.PI) / 180;
277
+ case 'grad':
278
+ return (numericValue * Math.PI) / 200;
279
+ case 'rad':
280
+ return numericValue;
281
+ case 'turn':
282
+ return numericValue * 2 * Math.PI;
283
+ default:
284
+ return null;
285
+ }
286
+ }
@@ -0,0 +1,211 @@
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 {ProcessedColorValue} from './processColor';
13
+ import type {BoxShadowPrimitive} from './StyleSheetTypes';
14
+
15
+ import processColor from './processColor';
16
+
17
+ export type ParsedBoxShadow = {
18
+ offsetX: number,
19
+ offsetY: number,
20
+ color?: ProcessedColorValue,
21
+ blurRadius?: number,
22
+ spreadDistance?: number,
23
+ inset?: boolean,
24
+ };
25
+
26
+ export default function processBoxShadow(
27
+ rawBoxShadows: ?($ReadOnlyArray<BoxShadowPrimitive> | string),
28
+ ): Array<ParsedBoxShadow> {
29
+ const result: Array<ParsedBoxShadow> = [];
30
+ if (rawBoxShadows == null) {
31
+ return result;
32
+ }
33
+
34
+ const boxShadowList =
35
+ typeof rawBoxShadows === 'string'
36
+ ? parseBoxShadowString(rawBoxShadows)
37
+ : rawBoxShadows;
38
+
39
+ for (const rawBoxShadow of boxShadowList) {
40
+ const parsedBoxShadow: ParsedBoxShadow = {
41
+ offsetX: 0,
42
+ offsetY: 0,
43
+ };
44
+
45
+ let value;
46
+ for (const arg in rawBoxShadow) {
47
+ switch (arg) {
48
+ case 'offsetX':
49
+ value =
50
+ typeof rawBoxShadow.offsetX === 'string'
51
+ ? parseLength(rawBoxShadow.offsetX)
52
+ : rawBoxShadow.offsetX;
53
+ if (value == null) {
54
+ return [];
55
+ }
56
+
57
+ parsedBoxShadow.offsetX = value;
58
+ break;
59
+ case 'offsetY':
60
+ value =
61
+ typeof rawBoxShadow.offsetY === 'string'
62
+ ? parseLength(rawBoxShadow.offsetY)
63
+ : rawBoxShadow.offsetY;
64
+ if (value == null) {
65
+ return [];
66
+ }
67
+
68
+ parsedBoxShadow.offsetY = value;
69
+ break;
70
+ case 'spreadDistance':
71
+ value =
72
+ typeof rawBoxShadow.spreadDistance === 'string'
73
+ ? parseLength(rawBoxShadow.spreadDistance)
74
+ : rawBoxShadow.spreadDistance;
75
+ if (value == null) {
76
+ return [];
77
+ }
78
+
79
+ parsedBoxShadow.spreadDistance = value;
80
+ break;
81
+ case 'blurRadius':
82
+ value =
83
+ typeof rawBoxShadow.blurRadius === 'string'
84
+ ? parseLength(rawBoxShadow.blurRadius)
85
+ : rawBoxShadow.blurRadius;
86
+ if (value == null || value < 0) {
87
+ return [];
88
+ }
89
+
90
+ parsedBoxShadow.blurRadius = value;
91
+ break;
92
+ case 'color':
93
+ const color = processColor(rawBoxShadow.color);
94
+ if (color == null) {
95
+ return [];
96
+ }
97
+
98
+ parsedBoxShadow.color = color;
99
+ break;
100
+ case 'inset':
101
+ parsedBoxShadow.inset = rawBoxShadow.inset;
102
+ }
103
+ }
104
+ result.push(parsedBoxShadow);
105
+ }
106
+ return result;
107
+ }
108
+
109
+ function parseBoxShadowString(
110
+ rawBoxShadows: string,
111
+ ): Array<BoxShadowPrimitive> {
112
+ let result: Array<BoxShadowPrimitive> = [];
113
+
114
+ for (const rawBoxShadow of rawBoxShadows
115
+ .split(/,(?![^()]*\))/) // split by comma that is not in parenthesis
116
+ .map(bS => bS.trim())
117
+ .filter(bS => bS !== '')) {
118
+ const boxShadow: BoxShadowPrimitive = {
119
+ offsetX: 0,
120
+ offsetY: 0,
121
+ };
122
+ let offsetX: number | string;
123
+ let offsetY: number | string;
124
+ let keywordDetectedAfterLength = false;
125
+
126
+ let lengthCount = 0;
127
+
128
+ // split rawBoxShadow string by all whitespaces that are not in parenthesis
129
+ const args = rawBoxShadow.split(/\s+(?![^(]*\))/);
130
+ for (const arg of args) {
131
+ const processedColor = processColor(arg);
132
+ if (processedColor != null) {
133
+ if (boxShadow.color != null) {
134
+ return [];
135
+ }
136
+ if (offsetX != null) {
137
+ keywordDetectedAfterLength = true;
138
+ }
139
+ boxShadow.color = arg;
140
+ continue;
141
+ }
142
+
143
+ if (arg === 'inset') {
144
+ if (boxShadow.inset != null) {
145
+ return [];
146
+ }
147
+ if (offsetX != null) {
148
+ keywordDetectedAfterLength = true;
149
+ }
150
+ boxShadow.inset = true;
151
+ continue;
152
+ }
153
+
154
+ switch (lengthCount) {
155
+ case 0:
156
+ offsetX = arg;
157
+ lengthCount++;
158
+ break;
159
+ case 1:
160
+ if (keywordDetectedAfterLength) {
161
+ return [];
162
+ }
163
+ offsetY = arg;
164
+ lengthCount++;
165
+ break;
166
+ case 2:
167
+ if (keywordDetectedAfterLength) {
168
+ return [];
169
+ }
170
+ boxShadow.blurRadius = arg;
171
+ lengthCount++;
172
+ break;
173
+ case 3:
174
+ if (keywordDetectedAfterLength) {
175
+ return [];
176
+ }
177
+ boxShadow.spreadDistance = arg;
178
+ lengthCount++;
179
+ break;
180
+ default:
181
+ return [];
182
+ }
183
+ }
184
+
185
+ if (offsetX == null || offsetY == null) {
186
+ return [];
187
+ }
188
+
189
+ boxShadow.offsetX = offsetX;
190
+ boxShadow.offsetY = offsetY;
191
+
192
+ result.push(boxShadow);
193
+ }
194
+ return result;
195
+ }
196
+
197
+ function parseLength(length: string): ?number {
198
+ // matches on args with units like "1.5 5% -80deg"
199
+ const argsWithUnitsRegex = /([+-]?\d*(\.\d+)?)([\w\W]+)?/g;
200
+ const match = argsWithUnitsRegex.exec(length);
201
+
202
+ if (!match || Number.isNaN(match[1])) {
203
+ return null;
204
+ }
205
+
206
+ if (match[3] != null && match[3] !== 'px') {
207
+ return null;
208
+ }
209
+
210
+ return Number(match[1]);
211
+ }