react-native-tvos 0.87.0-0rc3 → 0.87.1-0

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 (52) hide show
  1. package/Libraries/Animated/AnimatedEvent.js +1 -1
  2. package/Libraries/Animated/AnimatedImplementation.js +6 -2
  3. package/Libraries/Animated/nodes/AnimatedNode.js +1 -1
  4. package/Libraries/Animated/nodes/AnimatedValue.js +2 -2
  5. package/Libraries/Components/ScrollView/AndroidHorizontalScrollViewNativeComponent.js +2 -0
  6. package/Libraries/Components/ScrollView/ScrollView.d.ts +26 -0
  7. package/Libraries/Components/ScrollView/ScrollView.js +32 -0
  8. package/Libraries/Components/ScrollView/ScrollViewNativeComponent.js +2 -0
  9. package/Libraries/Components/ScrollView/ScrollViewNativeComponentType.js +11 -0
  10. package/Libraries/Components/ScrollView/processScrollAnimationEasing.js +40 -0
  11. package/Libraries/Components/TV/TVFocusGuideView.js +31 -0
  12. package/Libraries/Components/TV/useFocusEnterLeave.js +112 -0
  13. package/Libraries/Core/ReactNativeVersion.js +2 -2
  14. package/README.md +17 -0
  15. package/React/Base/RCTVersion.m +2 -2
  16. package/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +30 -0
  17. package/ReactAndroid/gradle.properties +1 -1
  18. package/ReactAndroid/src/main/java/com/facebook/react/modules/intent/IntentModule.kt +7 -3
  19. package/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/ReactNativeVersion.kt +2 -2
  20. package/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.kt +36 -13
  21. package/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollViewManager.kt +13 -0
  22. package/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.kt +25 -3
  23. package/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewHelper.kt +62 -0
  24. package/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewManager.kt +13 -0
  25. package/ReactCommon/cxxreact/ReactNativeVersion.h +3 -3
  26. package/ReactCommon/react/renderer/components/scrollview/platform/android/react/renderer/components/scrollview/HostPlatformScrollViewProps.cpp +26 -0
  27. package/ReactCommon/react/renderer/components/scrollview/platform/android/react/renderer/components/scrollview/HostPlatformScrollViewProps.h +3 -0
  28. package/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTFontUtils.mm +1 -1
  29. package/package.json +11 -9
  30. package/scripts/cocoapods/rncore.rb +1 -0
  31. package/scripts/cocoapods/rndependencies.rb +31 -25
  32. package/scripts/replace-rncore-version.js +69 -129
  33. package/scripts/setup-apple-spm.js +139 -21
  34. package/scripts/spm/autolinking-plugins.js +82 -2
  35. package/scripts/spm/download-spm-artifacts.js +67 -9
  36. package/scripts/spm/expand-spm-dependencies.js +249 -30
  37. package/scripts/spm/generate-spm-autolinking-config.js +62 -2
  38. package/scripts/spm/generate-spm-autolinking.js +158 -41
  39. package/scripts/spm/generate-spm-package.js +30 -18
  40. package/scripts/spm/generate-spm-xcodeproj.js +641 -75
  41. package/scripts/spm/scaffold-package-swift.js +68 -27
  42. package/scripts/spm/spm-pbxproj.js +158 -28
  43. package/scripts/spm/spm-types.js +32 -3
  44. package/scripts/spm/spm-utils.js +117 -2
  45. package/sdks/.hermesv1version +1 -1
  46. package/sdks/hermes-engine/version.properties +1 -1
  47. package/types/public/ReactNativeTVTypes.d.ts +40 -1
  48. package/scripts/spm/__doc__/rfc-spm-xcframework.md +0 -707
  49. package/scripts/spm/__doc__/spm-autolinking-plugins.md +0 -244
  50. package/scripts/spm/__doc__/spm-header-paths-contract.md +0 -97
  51. package/scripts/spm/__doc__/spm-plugins-assessment.md +0 -128
  52. package/scripts/spm/__doc__/spm-scripts.md +0 -451
@@ -42,6 +42,7 @@ import com.facebook.react.views.scroll.ReactScrollViewCommandHelper.Companion.re
42
42
  import com.facebook.react.views.scroll.ReactScrollViewCommandHelper.ScrollCommandHandler
43
43
  import com.facebook.react.views.scroll.ReactScrollViewCommandHelper.ScrollToCommandData
44
44
  import com.facebook.react.views.scroll.ReactScrollViewCommandHelper.ScrollToEndCommandData
45
+ import com.facebook.react.views.scroll.ReactScrollViewHelper.createScrollAnimationInterpolator
45
46
  import com.facebook.react.views.scroll.ReactScrollViewHelper.parseOverScrollMode
46
47
  import com.facebook.react.views.scroll.ReactScrollViewHelper.parseSnapToAlignment
47
48
 
@@ -174,6 +175,18 @@ constructor(private val fpsListener: FpsListener? = null) :
174
175
  view.scrollAnimationEnabled = value
175
176
  }
176
177
 
178
+ @ReactProp(name = "scrollAnimationDuration")
179
+ public fun setScrollAnimationDuration(view: ReactHorizontalScrollView, value: Float) {
180
+ // Exposed as a float because of the JavaScript interface, see `snapToInterval`.
181
+ view.scrollAnimationDuration = value.toInt()
182
+ }
183
+
184
+ @ReactProp(name = "scrollAnimationEasing")
185
+ public fun setScrollAnimationEasing(view: ReactHorizontalScrollView, value: ReadableArray?) {
186
+ // Resolved here rather than per frame: the curve only changes when the prop does.
187
+ view.scrollAnimationInterpolator = createScrollAnimationInterpolator(value)
188
+ }
189
+
177
190
  @ReactProp(name = ReactClippingViewGroupHelper.PROP_REMOVE_CLIPPED_SUBVIEWS)
178
191
  public fun setRemoveClippedSubviews(
179
192
  view: ReactHorizontalScrollView,
@@ -8,6 +8,7 @@
8
8
  package com.facebook.react.views.scroll
9
9
 
10
10
  import android.animation.ObjectAnimator
11
+ import android.animation.TimeInterpolator
11
12
  import android.animation.ValueAnimator
12
13
  import android.content.Context
13
14
  import android.graphics.Canvas
@@ -207,6 +208,10 @@ constructor(context: Context, private val fpsListener: FpsListener? = null) :
207
208
  private var scrollsChildToFocus = true
208
209
  internal var snapToItemPadding: Int = 0
209
210
  internal var scrollAnimationEnabled: Boolean = true
211
+ /** Values <= 0 keep the platform default duration. */
212
+ internal var scrollAnimationDuration: Int = 0
213
+ /** `null` keeps the platform default curve. */
214
+ internal var scrollAnimationInterpolator: TimeInterpolator? = null
210
215
  private var blockScrollDelta: Boolean = false
211
216
 
212
217
  init {
@@ -264,6 +269,8 @@ constructor(context: Context, private val fpsListener: FpsListener? = null) :
264
269
  scrollsChildToFocus = true
265
270
  snapToItemPadding = 0
266
271
  scrollAnimationEnabled = true
272
+ scrollAnimationDuration = 0
273
+ scrollAnimationInterpolator = null
267
274
  blockScrollDelta = false
268
275
  }
269
276
 
@@ -505,12 +512,21 @@ constructor(context: Context, private val fpsListener: FpsListener? = null) :
505
512
  val currentFocused = findFocus()
506
513
  val nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused, direction)
507
514
  if (nextFocused != null && nextFocused !== currentFocused && nextFocused !== this) {
508
- nextFocused.requestFocus(direction)
515
+ if (nextFocused.requestFocus(direction)) {
516
+ ReactScrollViewHelper.playFocusNavigationSoundEffect(this, direction)
517
+ }
509
518
  return true
510
519
  }
511
520
  return false
512
521
  }
513
- return super.arrowScroll(direction)
522
+
523
+ val focusedBeforeScroll = findFocus()
524
+ val handled = super.arrowScroll(direction)
525
+ // super.arrowScroll() can scroll without moving focus, so only click when focus moved.
526
+ if (handled && findFocus() !== focusedBeforeScroll) {
527
+ ReactScrollViewHelper.playFocusNavigationSoundEffect(this, direction)
528
+ }
529
+ return handled
514
530
  }
515
531
 
516
532
  /**
@@ -1374,7 +1390,13 @@ constructor(context: Context, private val fpsListener: FpsListener? = null) :
1374
1390
 
1375
1391
  override fun startFlingAnimator(start: Int, end: Int) {
1376
1392
  defaultFlingAnimator.cancel()
1377
- val duration = ReactScrollViewHelper.getDefaultScrollAnimationDuration(context)
1393
+ // The animator is reused, so the interpolator must be re-applied on every call to undo whatever
1394
+ // a previous `scrollAnimationEasing` left on it.
1395
+ val duration =
1396
+ if (scrollAnimationDuration > 0) scrollAnimationDuration
1397
+ else ReactScrollViewHelper.getDefaultScrollAnimationDuration(context)
1398
+ defaultFlingAnimator.interpolator =
1399
+ scrollAnimationInterpolator ?: ReactScrollViewHelper.getDefaultScrollAnimationInterpolator()
1378
1400
  defaultFlingAnimator.setDuration(duration.toLong()).setIntValues(start, end)
1379
1401
  defaultFlingAnimator.start()
1380
1402
 
@@ -8,19 +8,24 @@
8
8
  package com.facebook.react.views.scroll
9
9
 
10
10
  import android.animation.Animator
11
+ import android.animation.TimeInterpolator
11
12
  import android.animation.ValueAnimator
12
13
  import android.content.Context
13
14
  import android.graphics.Point
14
15
  import android.graphics.Rect
15
16
  import android.os.Build
17
+ import android.view.SoundEffectConstants
16
18
  import android.view.View
17
19
  import android.view.ViewGroup
20
+ import android.view.animation.AccelerateDecelerateInterpolator
21
+ import android.view.animation.PathInterpolator
18
22
  import android.widget.OverScroller
19
23
  import androidx.annotation.RequiresApi
20
24
  import androidx.core.view.ViewCompat.FocusDirection
21
25
  import androidx.core.view.ViewCompat.FocusRealDirection
22
26
  import com.facebook.common.logging.FLog
23
27
  import com.facebook.react.bridge.ReactContext
28
+ import com.facebook.react.bridge.ReadableArray
24
29
  import com.facebook.react.bridge.WritableMap
25
30
  import com.facebook.react.bridge.WritableNativeMap
26
31
  import com.facebook.react.common.ReactConstants
@@ -67,6 +72,11 @@ public object ReactScrollViewHelper {
67
72
  private var SMOOTH_SCROLL_DURATION = 250
68
73
  private var smoothScrollDurationInitialized = false
69
74
 
75
+ // The curve `ValueAnimator` applies when no interpolator is set, kept here so the reused fling
76
+ // animator can be reset to it once a custom `scrollAnimationEasing` is cleared.
77
+ private val defaultScrollAnimationInterpolator: TimeInterpolator =
78
+ AccelerateDecelerateInterpolator()
79
+
70
80
  /** Shared by [ReactScrollView] and [ReactHorizontalScrollView]. */
71
81
  @JvmStatic
72
82
  public fun <T> emitScrollEvent(scrollView: T, xVelocity: Float, yVelocity: Float)
@@ -225,6 +235,35 @@ public object ReactScrollViewHelper {
225
235
  return SMOOTH_SCROLL_DURATION
226
236
  }
227
237
 
238
+ /** Curve used by the fling animator when `scrollAnimationEasing` is not set. */
239
+ @JvmStatic
240
+ public fun getDefaultScrollAnimationInterpolator(): TimeInterpolator =
241
+ defaultScrollAnimationInterpolator
242
+
243
+ /**
244
+ * Builds the `scrollAnimationEasing` curve from the CSS cubic-bezier control points `[x1, y1, x2,
245
+ * y2]` sent by JS. Returns `null` when the value is missing or does not describe a usable curve,
246
+ * so callers fall back to [getDefaultScrollAnimationInterpolator].
247
+ */
248
+ @JvmStatic
249
+ public fun createScrollAnimationInterpolator(controlPoints: ReadableArray?): TimeInterpolator? {
250
+ if (controlPoints == null || controlPoints.size() != 4) {
251
+ return null
252
+ }
253
+ return try {
254
+ PathInterpolator(
255
+ controlPoints.getDouble(0).toFloat(),
256
+ controlPoints.getDouble(1).toFloat(),
257
+ controlPoints.getDouble(2).toFloat(),
258
+ controlPoints.getDouble(3).toFloat(),
259
+ )
260
+ } catch (e: IllegalArgumentException) {
261
+ // PathInterpolator rejects curves that leave 0..1 on x or loop back on themselves.
262
+ FLog.w(ReactConstants.TAG, e, "Invalid scrollAnimationEasing control points")
263
+ null
264
+ }
265
+ }
266
+
228
267
  /**
229
268
  * Adds a scroll listener.
230
269
  *
@@ -574,6 +613,29 @@ public object ReactScrollViewHelper {
574
613
  return host.findViewById(nextFocusableViewId)
575
614
  }
576
615
 
616
+ /**
617
+ * Plays the system focus navigation sound for a D-pad move the scroll view handled itself.
618
+ *
619
+ * ViewRootImpl only sonifies focus moves it performs, so any key event a scroll view consumes
620
+ * (which it does as soon as its content is scrollable) navigates silently. Call this after a
621
+ * successful requestFocus to keep the click consistent. It is a no-op when the user has system
622
+ * sounds off.
623
+ */
624
+ @JvmStatic
625
+ public fun playFocusNavigationSoundEffect(view: View, @FocusDirection direction: Int) {
626
+ val soundConstant =
627
+ when (direction) {
628
+ View.FOCUS_UP,
629
+ View.FOCUS_BACKWARD -> SoundEffectConstants.NAVIGATION_UP
630
+ View.FOCUS_DOWN,
631
+ View.FOCUS_FORWARD -> SoundEffectConstants.NAVIGATION_DOWN
632
+ View.FOCUS_LEFT -> SoundEffectConstants.NAVIGATION_LEFT
633
+ View.FOCUS_RIGHT -> SoundEffectConstants.NAVIGATION_RIGHT
634
+ else -> return
635
+ }
636
+ view.playSoundEffect(soundConstant)
637
+ }
638
+
577
639
  /**
578
640
  * Walks up the view hierarchy from the focused view to find a ReactViewGroup with
579
641
  * either scrollSnapAlign or scrollSnapOffset set. Returns a Triple of (snapTarget,
@@ -41,6 +41,7 @@ import com.facebook.react.views.scroll.ReactScrollViewCommandHelper.Companion.re
41
41
  import com.facebook.react.views.scroll.ReactScrollViewCommandHelper.ScrollCommandHandler
42
42
  import com.facebook.react.views.scroll.ReactScrollViewCommandHelper.ScrollToCommandData
43
43
  import com.facebook.react.views.scroll.ReactScrollViewCommandHelper.ScrollToEndCommandData
44
+ import com.facebook.react.views.scroll.ReactScrollViewHelper.createScrollAnimationInterpolator
44
45
  import com.facebook.react.views.scroll.ReactScrollViewHelper.parseOverScrollMode
45
46
  import com.facebook.react.views.scroll.ReactScrollViewHelper.parseSnapToAlignment
46
47
  import com.facebook.react.views.scroll.ScrollEventType.Companion.getJSEventName
@@ -158,6 +159,18 @@ constructor(private val fpsListener: FpsListener? = null) :
158
159
  view.scrollAnimationEnabled = value
159
160
  }
160
161
 
162
+ @ReactProp(name = "scrollAnimationDuration")
163
+ public fun setScrollAnimationDuration(view: ReactScrollView, value: Float) {
164
+ // Exposed as a float because of the JavaScript interface, see `snapToInterval`.
165
+ view.scrollAnimationDuration = value.toInt()
166
+ }
167
+
168
+ @ReactProp(name = "scrollAnimationEasing")
169
+ public fun setScrollAnimationEasing(view: ReactScrollView, value: ReadableArray?) {
170
+ // Resolved here rather than per frame: the curve only changes when the prop does.
171
+ view.scrollAnimationInterpolator = createScrollAnimationInterpolator(value)
172
+ }
173
+
161
174
  @ReactProp(name = ReactClippingViewGroupHelper.PROP_REMOVE_CLIPPED_SUBVIEWS)
162
175
  public fun setRemoveClippedSubviews(view: ReactScrollView, removeClippedSubviews: Boolean) {
163
176
  view.removeClippedSubviews = removeClippedSubviews
@@ -14,15 +14,15 @@
14
14
 
15
15
  #define REACT_NATIVE_VERSION_MAJOR 0
16
16
  #define REACT_NATIVE_VERSION_MINOR 87
17
- #define REACT_NATIVE_VERSION_PATCH 0
17
+ #define REACT_NATIVE_VERSION_PATCH 1
18
18
 
19
19
  namespace facebook::react {
20
20
 
21
21
  struct ReactNativeVersionType {
22
22
  int32_t Major = 0;
23
23
  int32_t Minor = 87;
24
- int32_t Patch = 0;
25
- std::string_view Prerelease = "0rc3";
24
+ int32_t Patch = 1;
25
+ std::string_view Prerelease = "0";
26
26
  };
27
27
 
28
28
  constexpr ReactNativeVersionType ReactNativeVersion;
@@ -55,6 +55,18 @@ HostPlatformScrollViewProps::HostPlatformScrollViewProps(
55
55
  rawProps,
56
56
  "snapToItemPadding",
57
57
  sourceProps.snapToItemPadding,
58
+ {})),
59
+ scrollAnimationDuration(convertRawProp(
60
+ context,
61
+ rawProps,
62
+ "scrollAnimationDuration",
63
+ sourceProps.scrollAnimationDuration,
64
+ {})),
65
+ scrollAnimationEasing(convertRawProp(
66
+ context,
67
+ rawProps,
68
+ "scrollAnimationEasing",
69
+ sourceProps.scrollAnimationEasing,
58
70
  {})) {}
59
71
 
60
72
  void HostPlatformScrollViewProps::setProp(
@@ -76,6 +88,8 @@ void HostPlatformScrollViewProps::setProp(
76
88
  RAW_SET_PROP_SWITCH_CASE_BASIC(overScrollMode);
77
89
  RAW_SET_PROP_SWITCH_CASE_BASIC(endFillColor);
78
90
  RAW_SET_PROP_SWITCH_CASE_BASIC(snapToItemPadding);
91
+ RAW_SET_PROP_SWITCH_CASE_BASIC(scrollAnimationDuration);
92
+ RAW_SET_PROP_SWITCH_CASE_BASIC(scrollAnimationEasing);
79
93
  }
80
94
  }
81
95
 
@@ -394,6 +408,18 @@ folly::dynamic HostPlatformScrollViewProps::getDiffProps(
394
408
  result["snapToItemPadding"] = snapToItemPadding;
395
409
  }
396
410
 
411
+ if (scrollAnimationDuration != oldProps->scrollAnimationDuration) {
412
+ result["scrollAnimationDuration"] = scrollAnimationDuration;
413
+ }
414
+
415
+ if (scrollAnimationEasing != oldProps->scrollAnimationEasing) {
416
+ auto scrollAnimationEasingArray = folly::dynamic::array();
417
+ for (const auto& controlPoint : scrollAnimationEasing) {
418
+ scrollAnimationEasingArray.push_back(controlPoint);
419
+ }
420
+ result["scrollAnimationEasing"] = scrollAnimationEasingArray;
421
+ }
422
+
397
423
  return result;
398
424
  }
399
425
 
@@ -35,6 +35,9 @@ class HostPlatformScrollViewProps : public BaseScrollViewProps {
35
35
 
36
36
  Float snapToItemPadding{0};
37
37
 
38
+ Float scrollAnimationDuration{0};
39
+ std::vector<Float> scrollAnimationEasing{};
40
+
38
41
  #pragma mark - DebugStringConvertible
39
42
 
40
43
  #if RN_DEBUG_STRING_CONVERTIBLE
@@ -361,7 +361,7 @@ UIFont *RCTFontWithFontProperties(RCTFontProperties fontProperties)
361
361
  font = [UIFont fontWithName:fontProperties.family size:effectiveFontSize];
362
362
  if (font != nullptr) {
363
363
  fontNames = [UIFont fontNamesForFamilyName:font.familyName];
364
- fontWeight = (fontWeight != 0.0) ?: RCTGetFontWeight(font);
364
+ fontWeight = (fontWeight != 0.0) ? fontWeight : RCTGetFontWeight(font);
365
365
  } else {
366
366
  // Failback to system font.
367
367
  font = RCTDefaultFontWithFontProperties(fontProperties);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-tvos",
3
- "version": "0.87.0-0rc3",
3
+ "version": "0.87.1-0",
4
4
  "description": "A framework for building native apps using React",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -45,7 +45,9 @@
45
45
  "types": null,
46
46
  "default": "./Libraries/*.js"
47
47
  },
48
- "./scripts/*": "./scripts/*",
48
+ "./scripts/*": "./scripts/*.js",
49
+ "./scripts/*.sh": "./scripts/*.sh",
50
+ "./scripts/*.rb": "./scripts/*.rb",
49
51
  "./asset-registry": {
50
52
  "types": null,
51
53
  "default": "./src/asset-registry.js"
@@ -153,18 +155,18 @@
153
155
  }
154
156
  },
155
157
  "dependencies": {
156
- "@react-native/asset-utils": "0.87.0-rc.3",
157
- "@react-native/codegen": "0.87.0-rc.3",
158
- "@react-native/community-cli-plugin": "0.87.0-rc.3",
159
- "@react-native/gradle-plugin": "0.87.0-rc.3",
160
- "@react-native/normalize-colors": "0.87.0-rc.3",
158
+ "@react-native/asset-utils": "0.87.1",
159
+ "@react-native/codegen": "0.87.1",
160
+ "@react-native/community-cli-plugin": "0.87.1",
161
+ "@react-native/gradle-plugin": "0.87.1",
162
+ "@react-native/normalize-colors": "0.87.1",
161
163
  "anser": "^1.4.9",
162
164
  "ansi-regex": "^5.0.0",
163
165
  "babel-plugin-syntax-hermes-parser": "0.36.1",
164
166
  "base64-js": "^1.5.1",
165
167
  "commander": "^12.0.0",
166
168
  "flow-enums-runtime": "^0.0.6",
167
- "hermes-compiler": "250829098.0.16",
169
+ "hermes-compiler": "250829098.0.17",
168
170
  "invariant": "^2.2.4",
169
171
  "memoize-one": "^5.0.0",
170
172
  "metro-runtime": "^0.87.0",
@@ -182,7 +184,7 @@
182
184
  "whatwg-fetch": "^3.0.0",
183
185
  "ws": "^7.5.10",
184
186
  "yargs": "^17.6.2",
185
- "@react-native-tvos/virtualized-lists": "0.87.0-0rc3"
187
+ "@react-native-tvos/virtualized-lists": "0.87.1-0"
186
188
  },
187
189
  "codegenConfig": {
188
190
  "libraries": [
@@ -587,6 +587,7 @@ class ReactNativeCoreUtils
587
587
  # Quoted so a $(PODS_ROOT) containing spaces stays a single clang argument.
588
588
  module_map_flag = " \"-fmodule-map-file=$(PODS_ROOT)/React-Core-prebuilt/Headers/module.modulemap\""
589
589
  ReactNativePodsUtils.add_flag_to_map_with_inheritance(attributes, "OTHER_CFLAGS", module_map_flag)
590
+ ReactNativePodsUtils.add_flag_to_map_with_inheritance(attributes, "OTHER_CPLUSPLUSFLAGS", module_map_flag)
590
591
  ReactNativePodsUtils.add_flag_to_map_with_inheritance(attributes, "OTHER_SWIFT_FLAGS", " -Xcc" + module_map_flag)
591
592
  end
592
593
  end
@@ -6,7 +6,6 @@
6
6
  require "json"
7
7
  require 'net/http'
8
8
  require 'rexml/document'
9
- require 'shellwords'
10
9
 
11
10
  require_relative './utils.rb'
12
11
 
@@ -34,38 +33,26 @@ def add_rn_third_party_dependencies(s)
34
33
  s.dependency "RCT-Folly/Fabric"
35
34
  end
36
35
 
37
- header_search_paths = current_pod_target_xcconfig["HEADER_SEARCH_PATHS"] || []
38
-
39
- if header_search_paths.is_a?(String)
40
- header_search_paths = Shellwords.shellsplit(header_search_paths)
41
- end
42
-
43
- header_search_paths << "$(PODS_ROOT)/glog"
44
- header_search_paths << "$(PODS_ROOT)/boost"
45
- header_search_paths << "$(PODS_ROOT)/DoubleConversion"
46
- header_search_paths << "$(PODS_ROOT)/fast_float/include"
47
- header_search_paths << "$(PODS_ROOT)/fmt/include"
48
- header_search_paths << "$(PODS_ROOT)/SocketRocket"
49
- header_search_paths << "$(PODS_ROOT)/RCT-Folly"
50
-
51
- # uniq so a second call on the same spec can't duplicate entries.
52
- current_pod_target_xcconfig["HEADER_SEARCH_PATHS"] = header_search_paths.uniq
36
+ ReactNativeDependenciesUtils.append_header_search_paths(current_pod_target_xcconfig, [
37
+ "$(PODS_ROOT)/glog",
38
+ "$(PODS_ROOT)/boost",
39
+ "$(PODS_ROOT)/DoubleConversion",
40
+ "$(PODS_ROOT)/fast_float/include",
41
+ "$(PODS_ROOT)/fmt/include",
42
+ "$(PODS_ROOT)/SocketRocket",
43
+ "$(PODS_ROOT)/RCT-Folly",
44
+ ])
53
45
  else
54
46
  # Prebuilt-deps mode: this pod SELF-SERVES the third-party headers from its
55
47
  # own xcframework (incl. SocketRocket - sole supplier in this mode). See
56
48
  # scripts/cocoapods/__docs__/prebuilt-deps.md for the full contract.
57
49
  s.dependency "ReactNativeDependencies"
58
50
 
59
- header_search_paths = current_pod_target_xcconfig["HEADER_SEARCH_PATHS"] || []
60
- if header_search_paths.is_a?(String)
61
- header_search_paths = Shellwords.shellsplit(header_search_paths)
62
- end
63
51
  # Artifact headers are flattened into the pod-local Headers/ by the podspec
64
52
  # prepare_command (see __docs__/prebuilt-deps.md).
65
- header_search_paths << "$(PODS_ROOT)/ReactNativeDependencies/Headers"
66
-
67
- # uniq so a second call on the same spec can't duplicate entries.
68
- current_pod_target_xcconfig["HEADER_SEARCH_PATHS"] = header_search_paths.uniq
53
+ ReactNativeDependenciesUtils.append_header_search_paths(current_pod_target_xcconfig, [
54
+ "$(PODS_ROOT)/ReactNativeDependencies/Headers",
55
+ ])
69
56
  end
70
57
 
71
58
  s.pod_target_xcconfig = current_pod_target_xcconfig
@@ -148,6 +135,25 @@ class ReactNativeDependenciesUtils
148
135
  end
149
136
  end
150
137
 
138
+ # Xcode splits HEADER_SEARCH_PATHS on whitespace, so every path we add is
139
+ # quoted - PODS_ROOT can expand to a directory with spaces in its name.
140
+ # Paths already in the xcconfig are left untouched: they carry the podspec
141
+ # author's own quoting, and re-quoting them would break it.
142
+ def self.append_header_search_paths(xcconfig, paths)
143
+ quoted = paths.map { |path| "\"#{path}\"" }
144
+ existing = xcconfig["HEADER_SEARCH_PATHS"]
145
+
146
+ # reject so a second call on the same spec can't duplicate entries.
147
+ case existing
148
+ when nil
149
+ xcconfig["HEADER_SEARCH_PATHS"] = quoted
150
+ when Array
151
+ xcconfig["HEADER_SEARCH_PATHS"] = existing + quoted.reject { |path| existing.include?(path) }
152
+ else
153
+ xcconfig["HEADER_SEARCH_PATHS"] = ([existing] + quoted.reject { |path| existing.include?(path) }).join(" ")
154
+ end
155
+ end
156
+
151
157
  def self.abort_if_use_local_rndeps_with_no_file()
152
158
  if !File.exist?(ENV["RCT_USE_LOCAL_RN_DEP"])
153
159
  abort("RCT_USE_LOCAL_RN_DEP is set to #{ENV["RCT_USE_LOCAL_RN_DEP"]} but the file does not exist!")