react-native-tvos 0.77.0-0rc1 → 0.77.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.
- package/Libraries/Animated/animations/Animation.js +1 -1
- package/Libraries/Components/Pressable/Pressable.d.ts +2 -2
- package/Libraries/Components/Pressable/Pressable.js +3 -3
- package/Libraries/Components/ScrollView/ScrollView.js +3 -2
- package/Libraries/Components/TV/TVFocusGuideView.js +2 -6
- package/Libraries/Components/TextInput/TextInput.js +1 -1
- package/Libraries/Components/Touchable/TouchableNativeFeedback.js +1 -1
- package/Libraries/Components/Touchable/TouchableOpacity.js +1 -2
- package/Libraries/Components/View/View.js +2 -3
- package/Libraries/Components/View/ViewNativeComponent.js +2 -2
- package/Libraries/Components/View/ViewPropTypes.d.ts +4 -2
- package/Libraries/Core/ReactNativeVersion.js +2 -2
- package/Libraries/Core/setUpDeveloperTools.js +2 -3
- package/Libraries/Image/RCTImageLoader.mm +9 -1
- package/Libraries/LogBox/LogBoxNotificationContainer.js +1 -1
- package/Libraries/LogBox/UI/LogBoxButton.js +2 -2
- package/Libraries/LogBox/UI/LogBoxNotification.js +1 -1
- package/Libraries/Pressability/Pressability.js +2 -2
- package/Libraries/Text/TextInput/RCTBaseTextInputView.mm +1 -1
- package/Libraries/Utilities/BackHandler.ios.js +3 -8
- package/Libraries/Utilities/HMRClient.js +0 -28
- package/Libraries/Utilities/HMRClientProdShim.js +0 -1
- package/Libraries/Utilities/setAndForwardRef.js +2 -2
- package/React/Base/RCTConvert.mm +3 -1
- package/React/Base/RCTVersion.m +2 -2
- package/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm +89 -40
- package/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +11 -1
- package/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +5 -2
- package/React/Views/RCTTVView.m +5 -2
- package/React/Views/ScrollView/RCTScrollView.m +63 -26
- package/ReactAndroid/api/ReactAndroid.api +5 -0
- package/ReactAndroid/cmake-utils/ReactNative-application.cmake +13 -3
- package/ReactAndroid/cmake-utils/default-app-setup/OnLoad.cpp +0 -13
- package/ReactAndroid/gradle.properties +2 -2
- package/ReactAndroid/src/main/java/com/facebook/react/HeadlessJsTaskService.java +12 -13
- package/ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java +8 -0
- package/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java +8 -0
- package/ReactAndroid/src/main/java/com/facebook/react/ReactDelegate.java +37 -0
- package/ReactAndroid/src/main/java/com/facebook/react/bridge/BaseJavaModule.java +1 -1
- package/ReactAndroid/src/main/java/com/facebook/react/devsupport/StackTraceHelper.java +6 -2
- package/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.java +15 -8
- package/ReactAndroid/src/main/java/com/facebook/react/module/model/ReactModuleInfo.kt +18 -0
- package/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/ReactNativeVersion.java +2 -2
- package/ReactAndroid/src/main/java/com/facebook/react/uimanager/drawable/CompositeBackgroundDrawable.kt +9 -8
- package/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt +22 -2
- package/ReactAndroid/src/main/java/com/facebook/react/views/text/TextAttributeProps.java +16 -2
- package/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactClippingViewManager.kt +0 -3
- package/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java +33 -0
- package/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.kt +12 -0
- package/ReactCommon/cxxreact/ReactNativeVersion.h +2 -2
- package/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTInteropTurboModule.mm +13 -4
- package/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.h +5 -0
- package/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.mm +27 -9
- package/ReactCommon/react/renderer/attributedstring/TextAttributes.cpp +6 -0
- package/ReactCommon/react/renderer/attributedstring/TextAttributes.h +2 -0
- package/ReactCommon/react/renderer/attributedstring/conversions.h +5 -0
- package/ReactCommon/react/renderer/components/text/BaseTextProps.cpp +12 -0
- package/ReactCommon/react/renderer/textlayoutmanager/TextMeasureCache.h +2 -3
- package/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.mm +7 -3
- package/ReactCommon/react/runtime/ReactInstance.cpp +39 -35
- package/ReactCommon/react/runtime/ReactInstance.h +2 -1
- package/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.mm +3 -2
- package/index.js +5 -5
- package/package.json +13 -10
- package/scripts/cocoapods/utils.rb +6 -6
- package/scripts/codegen/generate-artifacts-executor.js +10 -0
- package/sdks/hermes-engine/hermes-engine.podspec +1 -1
- package/sdks/hermes-engine/hermes-utils.rb +2 -2
- package/sdks/hermesc/linux64-bin/hermesc +0 -0
- package/sdks/hermesc/osx-bin/hermes +0 -0
- package/sdks/hermesc/osx-bin/hermesc +0 -0
- package/sdks/hermesc/win64-bin/hermesc.exe +0 -0
- package/src/private/featureflags/ReactNativeFeatureFlags.js +2 -2
- package/types/public/ReactNativeTVTypes.d.ts +9 -7
|
@@ -144,6 +144,14 @@ public abstract class ReactActivity extends AppCompatActivity
|
|
|
144
144
|
mDelegate.onConfigurationChanged(newConfig);
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
+
public boolean onDialogKeyUp(int keyCode, KeyEvent event) {
|
|
148
|
+
return mDelegate != null && mDelegate.onDialogKeyUp(keyCode, event);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
public boolean onDialogKeyDown(int keyCode, KeyEvent event) {
|
|
152
|
+
return mDelegate != null && mDelegate.onDialogKeyDown(keyCode, event);
|
|
153
|
+
}
|
|
154
|
+
|
|
147
155
|
protected final ReactNativeHost getReactNativeHost() {
|
|
148
156
|
return mDelegate.getReactNativeHost();
|
|
149
157
|
}
|
|
@@ -199,6 +199,14 @@ public class ReactActivityDelegate {
|
|
|
199
199
|
return mReactDelegate.onBackPressed();
|
|
200
200
|
}
|
|
201
201
|
|
|
202
|
+
public boolean onDialogKeyUp(int keyCode, KeyEvent event) {
|
|
203
|
+
return mReactDelegate != null && mReactDelegate.onDialogKeyUp(keyCode, event);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
public boolean onDialogKeyDown(int keyCode, KeyEvent event) {
|
|
207
|
+
return mReactDelegate != null && mReactDelegate.onDialogKeyDown(keyCode, event);
|
|
208
|
+
}
|
|
209
|
+
|
|
202
210
|
public boolean onNewIntent(Intent intent) {
|
|
203
211
|
return mReactDelegate.onNewIntent(intent);
|
|
204
212
|
}
|
|
@@ -247,6 +247,43 @@ public class ReactDelegate {
|
|
|
247
247
|
return false;
|
|
248
248
|
}
|
|
249
249
|
|
|
250
|
+
/**
|
|
251
|
+
* Handles key up events for a dialog.
|
|
252
|
+
* <p>
|
|
253
|
+
* This method currently does not perform any action and always returns {@code false}. Subclasses
|
|
254
|
+
* can override this method to provide custom behavior.
|
|
255
|
+
* </p>
|
|
256
|
+
*
|
|
257
|
+
* @param keyCode the code of the key that was released
|
|
258
|
+
* @param event the {@link KeyEvent} object containing full information about the event
|
|
259
|
+
* @return {@code true} if the event was handled, {@code false} otherwise
|
|
260
|
+
*
|
|
261
|
+
* @see KeyEvent
|
|
262
|
+
* @see <a href="https://github.com/react-native-tvos/react-native-tvos/issues/829">GitHub Issue #829</a>
|
|
263
|
+
*/
|
|
264
|
+
public boolean onDialogKeyUp(int keyCode, KeyEvent event) {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Handles key down events for a dialog.
|
|
270
|
+
* <p>
|
|
271
|
+
* This method currently does not perform any action and always returns {@code false}. Subclasses
|
|
272
|
+
* can override this method to provide custom behavior.
|
|
273
|
+
* </p>
|
|
274
|
+
*
|
|
275
|
+
* @param keyCode the code of the key that was pressed
|
|
276
|
+
* @param event the {@link KeyEvent} object containing full information about the event
|
|
277
|
+
* @return {@code false} indicating the event was not handled
|
|
278
|
+
*
|
|
279
|
+
* @see KeyEvent
|
|
280
|
+
* @see <a href="https://github.com/react-native-tvos/react-native-tvos/issues/829">GitHub Issue #829</a>
|
|
281
|
+
*/
|
|
282
|
+
public boolean onDialogKeyDown(int keyCode, KeyEvent event) {
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
|
|
250
287
|
public void reload() {
|
|
251
288
|
DevSupportManager devSupportManager = getDevSupportManager();
|
|
252
289
|
if (devSupportManager == null) {
|
|
@@ -134,7 +134,7 @@ public abstract class BaseJavaModule implements NativeModule {
|
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
@DoNotStrip
|
|
137
|
-
|
|
137
|
+
protected void setEventEmitterCallback(CxxCallbackImpl eventEmitterCallback) {
|
|
138
138
|
mEventEmitterCallback = eventEmitterCallback;
|
|
139
139
|
}
|
|
140
140
|
}
|
|
@@ -268,8 +268,12 @@ public class StackTraceHelper {
|
|
|
268
268
|
List<ReadableMap> readableMapList = new ArrayList<>();
|
|
269
269
|
for (ParsedError.StackFrame frame : frames) {
|
|
270
270
|
JavaOnlyMap map = new JavaOnlyMap();
|
|
271
|
-
|
|
272
|
-
|
|
271
|
+
if (frame.getColumn() != null) {
|
|
272
|
+
map.putDouble(COLUMN_KEY, frame.getColumn());
|
|
273
|
+
}
|
|
274
|
+
if (frame.getLineNumber() != null) {
|
|
275
|
+
map.putDouble(LINE_NUMBER_KEY, frame.getLineNumber());
|
|
276
|
+
}
|
|
273
277
|
map.putString(FILE_KEY, (String) frame.getFile());
|
|
274
278
|
map.putString(METHOD_NAME_KEY, (String) frame.getMethodName());
|
|
275
279
|
readableMapList.add(map);
|
|
@@ -330,14 +330,11 @@ public class MountingManager {
|
|
|
330
330
|
@AnyThread
|
|
331
331
|
@ThreadConfined(ANY)
|
|
332
332
|
public @Nullable EventEmitterWrapper getEventEmitter(int surfaceId, int reactTag) {
|
|
333
|
-
SurfaceMountingManager
|
|
334
|
-
|
|
335
|
-
? getSurfaceManagerForView(reactTag)
|
|
336
|
-
: getSurfaceManager(surfaceId));
|
|
337
|
-
if (surfaceMountingManager == null) {
|
|
333
|
+
SurfaceMountingManager smm = getSurfaceMountingManager(surfaceId, reactTag);
|
|
334
|
+
if (smm == null) {
|
|
338
335
|
return null;
|
|
339
336
|
}
|
|
340
|
-
return
|
|
337
|
+
return smm.getEventEmitter(reactTag);
|
|
341
338
|
}
|
|
342
339
|
|
|
343
340
|
/**
|
|
@@ -434,11 +431,21 @@ public class MountingManager {
|
|
|
434
431
|
boolean canCoalesceEvent,
|
|
435
432
|
@Nullable WritableMap params,
|
|
436
433
|
@EventCategoryDef int eventCategory) {
|
|
437
|
-
|
|
434
|
+
SurfaceMountingManager smm = getSurfaceMountingManager(surfaceId, reactTag);
|
|
438
435
|
if (smm == null) {
|
|
439
|
-
|
|
436
|
+
FLog.d(
|
|
437
|
+
TAG,
|
|
438
|
+
"Cannot queue event without valid surface mounting manager for tag: %d, surfaceId: %d",
|
|
439
|
+
reactTag,
|
|
440
|
+
surfaceId);
|
|
440
441
|
return;
|
|
441
442
|
}
|
|
442
443
|
smm.enqueuePendingEvent(reactTag, eventName, canCoalesceEvent, params, eventCategory);
|
|
443
444
|
}
|
|
445
|
+
|
|
446
|
+
private @Nullable SurfaceMountingManager getSurfaceMountingManager(int surfaceId, int reactTag) {
|
|
447
|
+
return (surfaceId == ViewUtil.NO_SURFACE_ID
|
|
448
|
+
? getSurfaceManagerForView(reactTag)
|
|
449
|
+
: getSurfaceManager(surfaceId));
|
|
450
|
+
}
|
|
444
451
|
}
|
|
@@ -21,6 +21,24 @@ public class ReactModuleInfo(
|
|
|
21
21
|
public val isCxxModule: Boolean,
|
|
22
22
|
public val isTurboModule: Boolean
|
|
23
23
|
) {
|
|
24
|
+
|
|
25
|
+
@Deprecated(
|
|
26
|
+
"This constructor is deprecated and will be removed in the future. Use ReactModuleInfo(String, String, boolean, boolean, boolean, boolean)]",
|
|
27
|
+
replaceWith =
|
|
28
|
+
ReplaceWith(
|
|
29
|
+
expression =
|
|
30
|
+
"ReactModuleInfo(name, className, canOverrideExistingModule, needsEagerInit, isCxxModule, isTurboModule)"),
|
|
31
|
+
level = DeprecationLevel.WARNING)
|
|
32
|
+
public constructor(
|
|
33
|
+
name: String,
|
|
34
|
+
className: String,
|
|
35
|
+
canOverrideExistingModule: Boolean,
|
|
36
|
+
needsEagerInit: Boolean,
|
|
37
|
+
@Suppress("UNUSED_PARAMETER") hasConstants: Boolean,
|
|
38
|
+
isCxxModule: Boolean,
|
|
39
|
+
isTurboModule: Boolean
|
|
40
|
+
) : this(name, className, canOverrideExistingModule, needsEagerInit, isCxxModule, isTurboModule)
|
|
41
|
+
|
|
24
42
|
public companion object {
|
|
25
43
|
/**
|
|
26
44
|
* Checks if the passed class is a TurboModule. Useful to populate the parameter [isTurboModule]
|
|
@@ -16,6 +16,7 @@ import android.graphics.drawable.LayerDrawable
|
|
|
16
16
|
import android.os.Build
|
|
17
17
|
import com.facebook.react.common.annotations.UnstableReactNativeAPI
|
|
18
18
|
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
|
|
19
|
+
import com.facebook.react.uimanager.PixelUtil.dpToPx
|
|
19
20
|
import com.facebook.react.uimanager.style.BorderInsets
|
|
20
21
|
import com.facebook.react.uimanager.style.BorderRadiusStyle
|
|
21
22
|
|
|
@@ -200,14 +201,14 @@ internal class CompositeBackgroundDrawable(
|
|
|
200
201
|
pathForOutline.addRoundRect(
|
|
201
202
|
RectF(bounds),
|
|
202
203
|
floatArrayOf(
|
|
203
|
-
it.topLeft.horizontal + (computedBorderInsets?.left ?: 0f),
|
|
204
|
-
it.topLeft.vertical + (computedBorderInsets?.top ?: 0f),
|
|
205
|
-
it.topRight.horizontal + (computedBorderInsets?.right ?: 0f),
|
|
206
|
-
it.topRight.vertical + (computedBorderInsets?.top ?: 0f),
|
|
207
|
-
it.bottomRight.horizontal + (computedBorderInsets?.right ?: 0f),
|
|
208
|
-
it.bottomRight.vertical + (computedBorderInsets?.bottom ?: 0f),
|
|
209
|
-
it.bottomLeft.horizontal + (computedBorderInsets?.left ?: 0f),
|
|
210
|
-
it.bottomLeft.vertical
|
|
204
|
+
(it.topLeft.horizontal + (computedBorderInsets?.left ?: 0f)).dpToPx(),
|
|
205
|
+
(it.topLeft.vertical + (computedBorderInsets?.top ?: 0f)).dpToPx(),
|
|
206
|
+
(it.topRight.horizontal + (computedBorderInsets?.right ?: 0f)).dpToPx(),
|
|
207
|
+
(it.topRight.vertical + (computedBorderInsets?.top ?: 0f)).dpToPx(),
|
|
208
|
+
(it.bottomRight.horizontal + (computedBorderInsets?.right ?: 0f)).dpToPx(),
|
|
209
|
+
(it.bottomRight.vertical + (computedBorderInsets?.bottom ?: 0f)).dpToPx(),
|
|
210
|
+
(it.bottomLeft.horizontal + (computedBorderInsets?.left ?: 0f)).dpToPx(),
|
|
211
|
+
(it.bottomLeft.vertical + (computedBorderInsets?.bottom ?: 0f)).dpToPx()),
|
|
211
212
|
Path.Direction.CW)
|
|
212
213
|
}
|
|
213
214
|
|
|
@@ -29,6 +29,7 @@ import android.widget.FrameLayout
|
|
|
29
29
|
import androidx.annotation.UiThread
|
|
30
30
|
import com.facebook.common.logging.FLog
|
|
31
31
|
import com.facebook.react.R
|
|
32
|
+
import com.facebook.react.ReactActivity
|
|
32
33
|
import com.facebook.react.bridge.GuardedRunnable
|
|
33
34
|
import com.facebook.react.bridge.LifecycleEventListener
|
|
34
35
|
import com.facebook.react.bridge.ReactContext
|
|
@@ -275,12 +276,31 @@ public class ReactModalHostView(context: ThemedReactContext) :
|
|
|
275
276
|
// activity expects to receive those events and react to them, ie. in the case of
|
|
276
277
|
// the dev menu
|
|
277
278
|
val innerCurrentActivity =
|
|
278
|
-
(this@ReactModalHostView.context as ReactContext).currentActivity
|
|
279
|
+
(this@ReactModalHostView.context as ReactContext).currentActivity as? ReactActivity
|
|
279
280
|
if (innerCurrentActivity != null) {
|
|
280
|
-
return innerCurrentActivity.
|
|
281
|
+
return innerCurrentActivity.onDialogKeyUp(keyCode, event)
|
|
281
282
|
}
|
|
282
283
|
}
|
|
283
284
|
}
|
|
285
|
+
|
|
286
|
+
// @see https://github.com/react-native-tvos/react-native-tvos/issues/829
|
|
287
|
+
if (event.action == KeyEvent.ACTION_DOWN) {
|
|
288
|
+
// Allow BACK and ESCAPE keys to propagate to the dialog's activity.
|
|
289
|
+
// Returning false ensures these keys are not consumed here, so they
|
|
290
|
+
// can be processed during the ACTION_UP event where their functionality
|
|
291
|
+
// is handled by JavaScript.
|
|
292
|
+
if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE) {
|
|
293
|
+
return false
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Redirect all other key down events to the current activity.
|
|
297
|
+
val innerCurrentActivity =
|
|
298
|
+
(this@ReactModalHostView.context as ReactContext).currentActivity as? ReactActivity
|
|
299
|
+
if (innerCurrentActivity != null) {
|
|
300
|
+
return innerCurrentActivity.onDialogKeyDown(keyCode, event)
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
284
304
|
return false
|
|
285
305
|
}
|
|
286
306
|
})
|
|
@@ -61,6 +61,7 @@ public class TextAttributeProps {
|
|
|
61
61
|
public static final short TA_KEY_LINE_BREAK_STRATEGY = 25;
|
|
62
62
|
public static final short TA_KEY_ROLE = 26;
|
|
63
63
|
public static final short TA_KEY_TEXT_TRANSFORM = 27;
|
|
64
|
+
public static final short TA_KEY_MAX_FONT_SIZE_MULTIPLIER = 29;
|
|
64
65
|
|
|
65
66
|
public static final int UNSET = -1;
|
|
66
67
|
|
|
@@ -81,6 +82,7 @@ public class TextAttributeProps {
|
|
|
81
82
|
protected float mLineHeight = Float.NaN;
|
|
82
83
|
protected boolean mIsColorSet = false;
|
|
83
84
|
protected boolean mAllowFontScaling = true;
|
|
85
|
+
protected float mMaxFontSizeMultiplier = Float.NaN;
|
|
84
86
|
protected int mColor;
|
|
85
87
|
protected boolean mIsBackgroundColorSet = false;
|
|
86
88
|
protected int mBackgroundColor;
|
|
@@ -227,6 +229,9 @@ public class TextAttributeProps {
|
|
|
227
229
|
case TA_KEY_TEXT_TRANSFORM:
|
|
228
230
|
result.setTextTransform(entry.getStringValue());
|
|
229
231
|
break;
|
|
232
|
+
case TA_KEY_MAX_FONT_SIZE_MULTIPLIER:
|
|
233
|
+
result.setMaxFontSizeMultiplier((float) entry.getDoubleValue());
|
|
234
|
+
break;
|
|
230
235
|
}
|
|
231
236
|
}
|
|
232
237
|
|
|
@@ -243,6 +248,8 @@ public class TextAttributeProps {
|
|
|
243
248
|
result.setLineHeight(getFloatProp(props, ViewProps.LINE_HEIGHT, ReactConstants.UNSET));
|
|
244
249
|
result.setLetterSpacing(getFloatProp(props, ViewProps.LETTER_SPACING, Float.NaN));
|
|
245
250
|
result.setAllowFontScaling(getBooleanProp(props, ViewProps.ALLOW_FONT_SCALING, true));
|
|
251
|
+
result.setMaxFontSizeMultiplier(
|
|
252
|
+
getFloatProp(props, ViewProps.MAX_FONT_SIZE_MULTIPLIER, Float.NaN));
|
|
246
253
|
result.setFontSize(getFloatProp(props, ViewProps.FONT_SIZE, ReactConstants.UNSET));
|
|
247
254
|
result.setColor(props.hasKey(ViewProps.COLOR) ? props.getInt(ViewProps.COLOR, 0) : null);
|
|
248
255
|
result.setColor(
|
|
@@ -411,7 +418,14 @@ public class TextAttributeProps {
|
|
|
411
418
|
mAllowFontScaling = allowFontScaling;
|
|
412
419
|
setFontSize(mFontSizeInput);
|
|
413
420
|
setLineHeight(mLineHeightInput);
|
|
414
|
-
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
private void setMaxFontSizeMultiplier(float maxFontSizeMultiplier) {
|
|
425
|
+
if (maxFontSizeMultiplier != mMaxFontSizeMultiplier) {
|
|
426
|
+
mMaxFontSizeMultiplier = maxFontSizeMultiplier;
|
|
427
|
+
setFontSize(mFontSizeInput);
|
|
428
|
+
setLineHeight(mLineHeightInput);
|
|
415
429
|
}
|
|
416
430
|
}
|
|
417
431
|
|
|
@@ -420,7 +434,7 @@ public class TextAttributeProps {
|
|
|
420
434
|
if (fontSize != ReactConstants.UNSET) {
|
|
421
435
|
fontSize =
|
|
422
436
|
mAllowFontScaling
|
|
423
|
-
? (float) Math.ceil(PixelUtil.toPixelFromSP(fontSize))
|
|
437
|
+
? (float) Math.ceil(PixelUtil.toPixelFromSP(fontSize, mMaxFontSizeMultiplier))
|
|
424
438
|
: (float) Math.ceil(PixelUtil.toPixelFromDIP(fontSize));
|
|
425
439
|
}
|
|
426
440
|
mFontSize = (int) fontSize;
|
package/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactClippingViewManager.kt
CHANGED
|
@@ -62,9 +62,6 @@ public abstract class ReactClippingViewManager<T : ReactViewGroup> : ViewGroupMa
|
|
|
62
62
|
if (removeClippedSubviews) {
|
|
63
63
|
val child = getChildAt(parent, index)
|
|
64
64
|
if (child != null) {
|
|
65
|
-
if (child.parent != null) {
|
|
66
|
-
parent.removeView(child)
|
|
67
|
-
}
|
|
68
65
|
parent.removeViewWithSubviewClippingEnabled(child)
|
|
69
66
|
}
|
|
70
67
|
} else {
|
|
@@ -77,6 +77,8 @@ import com.facebook.react.uimanager.style.BorderRadiusProp;
|
|
|
77
77
|
import com.facebook.react.uimanager.style.BorderStyle;
|
|
78
78
|
import com.facebook.react.uimanager.style.LogicalEdge;
|
|
79
79
|
import com.facebook.react.uimanager.style.Overflow;
|
|
80
|
+
import java.util.HashSet;
|
|
81
|
+
import java.util.Set;
|
|
80
82
|
|
|
81
83
|
import java.lang.ref.WeakReference;
|
|
82
84
|
import java.util.ArrayList;
|
|
@@ -108,6 +110,7 @@ public class ReactViewGroup extends ViewGroup
|
|
|
108
110
|
private boolean trapFocusDown = false;
|
|
109
111
|
private boolean trapFocusLeft = false;
|
|
110
112
|
private boolean trapFocusRight = false;
|
|
113
|
+
public boolean hasTVPreferredFocus = false;
|
|
111
114
|
|
|
112
115
|
|
|
113
116
|
/**
|
|
@@ -170,6 +173,7 @@ public class ReactViewGroup extends ViewGroup
|
|
|
170
173
|
private @Nullable ViewGroupDrawingOrderHelper mDrawingOrderHelper;
|
|
171
174
|
private float mBackfaceOpacity;
|
|
172
175
|
private String mBackfaceVisibility;
|
|
176
|
+
private @Nullable Set<Integer> mChildrenRemovedWhileTransitioning;
|
|
173
177
|
|
|
174
178
|
/**
|
|
175
179
|
* Creates a new `ReactViewGroup` instance.
|
|
@@ -203,6 +207,7 @@ public class ReactViewGroup extends ViewGroup
|
|
|
203
207
|
mDrawingOrderHelper = null;
|
|
204
208
|
mBackfaceOpacity = 1.f;
|
|
205
209
|
mBackfaceVisibility = "visible";
|
|
210
|
+
mChildrenRemovedWhileTransitioning = null;
|
|
206
211
|
}
|
|
207
212
|
|
|
208
213
|
/* package */ void recycleView() {
|
|
@@ -387,6 +392,7 @@ public class ReactViewGroup extends ViewGroup
|
|
|
387
392
|
return;
|
|
388
393
|
}
|
|
389
394
|
mRemoveClippedSubviews = removeClippedSubviews;
|
|
395
|
+
mChildrenRemovedWhileTransitioning = null;
|
|
390
396
|
if (removeClippedSubviews) {
|
|
391
397
|
mClippingRect = new Rect();
|
|
392
398
|
ReactClippingViewGroupHelper.calculateClippingRect(this, mClippingRect);
|
|
@@ -440,6 +446,26 @@ public class ReactViewGroup extends ViewGroup
|
|
|
440
446
|
updateClippingToRect(mClippingRect);
|
|
441
447
|
}
|
|
442
448
|
|
|
449
|
+
@Override
|
|
450
|
+
public void endViewTransition(View view) {
|
|
451
|
+
super.endViewTransition(view);
|
|
452
|
+
if (mChildrenRemovedWhileTransitioning != null) {
|
|
453
|
+
mChildrenRemovedWhileTransitioning.remove(view.getId());
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
private void trackChildViewTransition(int childId) {
|
|
458
|
+
if (mChildrenRemovedWhileTransitioning == null) {
|
|
459
|
+
mChildrenRemovedWhileTransitioning = new HashSet<>();
|
|
460
|
+
}
|
|
461
|
+
mChildrenRemovedWhileTransitioning.add(childId);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
private boolean isChildRemovedWhileTransitioning(View child) {
|
|
465
|
+
return mChildrenRemovedWhileTransitioning != null
|
|
466
|
+
&& mChildrenRemovedWhileTransitioning.contains(child.getId());
|
|
467
|
+
}
|
|
468
|
+
|
|
443
469
|
private void updateClippingToRect(Rect clippingRect) {
|
|
444
470
|
Assertions.assertNotNull(mAllChildren);
|
|
445
471
|
int clippedSoFar = 0;
|
|
@@ -670,6 +696,12 @@ public class ReactViewGroup extends ViewGroup
|
|
|
670
696
|
} else {
|
|
671
697
|
setChildrenDrawingOrderEnabled(false);
|
|
672
698
|
}
|
|
699
|
+
|
|
700
|
+
// The parent might not be null in case the child is transitioning.
|
|
701
|
+
if (child.getParent() != null) {
|
|
702
|
+
trackChildViewTransition(child.getId());
|
|
703
|
+
}
|
|
704
|
+
|
|
673
705
|
super.onViewRemoved(child);
|
|
674
706
|
}
|
|
675
707
|
|
|
@@ -799,6 +831,7 @@ public class ReactViewGroup extends ViewGroup
|
|
|
799
831
|
}
|
|
800
832
|
}
|
|
801
833
|
removeViewsInLayout(index - clippedSoFar, 1);
|
|
834
|
+
invalidate();
|
|
802
835
|
}
|
|
803
836
|
removeFromArray(index);
|
|
804
837
|
}
|
|
@@ -82,6 +82,7 @@ public open class ReactViewManager : ReactClippingViewManager<ReactViewGroup>()
|
|
|
82
82
|
return view
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
|
|
85
86
|
@ReactProp(name = "accessible")
|
|
86
87
|
public open fun setAccessible(view: ReactViewGroup, accessible: Boolean) {
|
|
87
88
|
view.isFocusable = accessible
|
|
@@ -109,6 +110,17 @@ public open class ReactViewManager : ReactClippingViewManager<ReactViewGroup>()
|
|
|
109
110
|
|
|
110
111
|
@ReactProp(name = "hasTVPreferredFocus")
|
|
111
112
|
public open fun setTVPreferredFocus(view: ReactViewGroup, hasTVPreferredFocus: Boolean) {
|
|
113
|
+
/*
|
|
114
|
+
* React prop functions like this one gets called repeatedly on the New Architecture
|
|
115
|
+
* no matter the prop has changed or not. Contrary to others, `hasTVPreferredFocus` has
|
|
116
|
+
* a side effect, calling `requestFocus` function on the view which disrupts the user flow
|
|
117
|
+
* and should only called once when the property changes to `true.
|
|
118
|
+
* We keep a special state in the View class and run a comparison here to mitigate
|
|
119
|
+
* that problem.
|
|
120
|
+
*/
|
|
121
|
+
if (view.hasTVPreferredFocus == hasTVPreferredFocus) return;
|
|
122
|
+
view.hasTVPreferredFocus = hasTVPreferredFocus;
|
|
123
|
+
|
|
112
124
|
if (hasTVPreferredFocus) {
|
|
113
125
|
view.isFocusable = true
|
|
114
126
|
view.isFocusableInTouchMode = true
|
|
@@ -17,8 +17,8 @@ namespace facebook::react {
|
|
|
17
17
|
constexpr struct {
|
|
18
18
|
int32_t Major = 0;
|
|
19
19
|
int32_t Minor = 77;
|
|
20
|
-
int32_t Patch =
|
|
21
|
-
std::string_view Prerelease = "
|
|
20
|
+
int32_t Patch = 1;
|
|
21
|
+
std::string_view Prerelease = "0";
|
|
22
22
|
} ReactNativeVersion;
|
|
23
23
|
|
|
24
24
|
} // namespace facebook::react
|
package/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTInteropTurboModule.mm
CHANGED
|
@@ -123,6 +123,15 @@ std::vector<ExportedMethod> parseExportedMethods(std::string moduleName, Class m
|
|
|
123
123
|
NSArray<RCTMethodArgument *> *arguments;
|
|
124
124
|
SEL objCMethodSelector = NSSelectorFromString(RCTParseMethodSignature(methodInfo->objcName, &arguments));
|
|
125
125
|
NSMethodSignature *objCMethodSignature = [moduleClass instanceMethodSignatureForSelector:objCMethodSelector];
|
|
126
|
+
if (objCMethodSignature == nullptr) {
|
|
127
|
+
RCTLogWarn(
|
|
128
|
+
@"The objective-c `%s` method signature for the JS method `%@` can not be found in the ObjecitveC definition of the %s module.\nThe `%@` JS method will not be available.",
|
|
129
|
+
methodInfo->objcName,
|
|
130
|
+
jsMethodName,
|
|
131
|
+
moduleName.c_str(),
|
|
132
|
+
jsMethodName);
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
126
135
|
std::string objCMethodReturnType = [objCMethodSignature methodReturnType];
|
|
127
136
|
|
|
128
137
|
if (objCMethodSignature.numberOfArguments - 2 != [arguments count]) {
|
|
@@ -337,7 +346,7 @@ void ObjCInteropTurboModule::setInvocationArg(
|
|
|
337
346
|
SEL selector = selectorForType(argumentType);
|
|
338
347
|
|
|
339
348
|
if ([RCTConvert respondsToSelector:selector]) {
|
|
340
|
-
id objCArg = TurboModuleConvertUtils::convertJSIValueToObjCObject(runtime, jsiArg, jsInvoker_);
|
|
349
|
+
id objCArg = TurboModuleConvertUtils::convertJSIValueToObjCObject(runtime, jsiArg, jsInvoker_, YES);
|
|
341
350
|
|
|
342
351
|
if (objCArgType == @encode(char)) {
|
|
343
352
|
char arg = RCTConvertTo<char>(selector, objCArg);
|
|
@@ -491,7 +500,7 @@ void ObjCInteropTurboModule::setInvocationArg(
|
|
|
491
500
|
}
|
|
492
501
|
|
|
493
502
|
RCTResponseSenderBlock arg =
|
|
494
|
-
(RCTResponseSenderBlock)TurboModuleConvertUtils::convertJSIValueToObjCObject(runtime, jsiArg, jsInvoker_);
|
|
503
|
+
(RCTResponseSenderBlock)TurboModuleConvertUtils::convertJSIValueToObjCObject(runtime, jsiArg, jsInvoker_, YES);
|
|
495
504
|
if (arg) {
|
|
496
505
|
[retainedObjectsForInvocation addObject:arg];
|
|
497
506
|
}
|
|
@@ -506,7 +515,7 @@ void ObjCInteropTurboModule::setInvocationArg(
|
|
|
506
515
|
}
|
|
507
516
|
|
|
508
517
|
RCTResponseSenderBlock senderBlock =
|
|
509
|
-
(RCTResponseSenderBlock)TurboModuleConvertUtils::convertJSIValueToObjCObject(runtime, jsiArg, jsInvoker_);
|
|
518
|
+
(RCTResponseSenderBlock)TurboModuleConvertUtils::convertJSIValueToObjCObject(runtime, jsiArg, jsInvoker_, YES);
|
|
510
519
|
RCTResponseErrorBlock arg = ^(NSError *error) {
|
|
511
520
|
senderBlock(@[ RCTJSErrorFromNSError(error) ]);
|
|
512
521
|
};
|
|
@@ -536,7 +545,7 @@ void ObjCInteropTurboModule::setInvocationArg(
|
|
|
536
545
|
runtime, errorPrefix + "JavaScript argument must be a plain object. Got " + getType(runtime, jsiArg));
|
|
537
546
|
}
|
|
538
547
|
|
|
539
|
-
id arg = TurboModuleConvertUtils::convertJSIValueToObjCObject(runtime, jsiArg, jsInvoker_);
|
|
548
|
+
id arg = TurboModuleConvertUtils::convertJSIValueToObjCObject(runtime, jsiArg, jsInvoker_, YES);
|
|
540
549
|
|
|
541
550
|
RCTManagedPointer *(*convert)(id, SEL, id) = (__typeof__(convert))objc_msgSend;
|
|
542
551
|
RCTManagedPointer *box = convert([RCTCxxConvert class], selector, arg);
|
|
@@ -32,6 +32,11 @@ using EventEmitterCallback = std::function<void(const std::string &, id)>;
|
|
|
32
32
|
namespace TurboModuleConvertUtils {
|
|
33
33
|
jsi::Value convertObjCObjectToJSIValue(jsi::Runtime &runtime, id value);
|
|
34
34
|
id convertJSIValueToObjCObject(jsi::Runtime &runtime, const jsi::Value &value, std::shared_ptr<CallInvoker> jsInvoker);
|
|
35
|
+
id convertJSIValueToObjCObject(
|
|
36
|
+
jsi::Runtime &runtime,
|
|
37
|
+
const jsi::Value &value,
|
|
38
|
+
std::shared_ptr<CallInvoker> jsInvoker,
|
|
39
|
+
BOOL useNSNull);
|
|
35
40
|
} // namespace TurboModuleConvertUtils
|
|
36
41
|
|
|
37
42
|
template <>
|
|
@@ -111,21 +111,27 @@ static NSString *convertJSIStringToNSString(jsi::Runtime &runtime, const jsi::St
|
|
|
111
111
|
return [NSString stringWithUTF8String:value.utf8(runtime).c_str()];
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
static NSArray *
|
|
115
|
-
|
|
114
|
+
static NSArray *convertJSIArrayToNSArray(
|
|
115
|
+
jsi::Runtime &runtime,
|
|
116
|
+
const jsi::Array &value,
|
|
117
|
+
std::shared_ptr<CallInvoker> jsInvoker,
|
|
118
|
+
BOOL useNSNull)
|
|
116
119
|
{
|
|
117
120
|
size_t size = value.size(runtime);
|
|
118
121
|
NSMutableArray *result = [NSMutableArray new];
|
|
119
122
|
for (size_t i = 0; i < size; i++) {
|
|
120
123
|
// Insert kCFNull when it's `undefined` value to preserve the indices.
|
|
121
|
-
id convertedObject = convertJSIValueToObjCObject(runtime, value.getValueAtIndex(runtime, i), jsInvoker);
|
|
124
|
+
id convertedObject = convertJSIValueToObjCObject(runtime, value.getValueAtIndex(runtime, i), jsInvoker, useNSNull);
|
|
122
125
|
[result addObject:convertedObject ? convertedObject : (id)kCFNull];
|
|
123
126
|
}
|
|
124
127
|
return [result copy];
|
|
125
128
|
}
|
|
126
129
|
|
|
127
|
-
static NSDictionary *
|
|
128
|
-
|
|
130
|
+
static NSDictionary *convertJSIObjectToNSDictionary(
|
|
131
|
+
jsi::Runtime &runtime,
|
|
132
|
+
const jsi::Object &value,
|
|
133
|
+
std::shared_ptr<CallInvoker> jsInvoker,
|
|
134
|
+
BOOL useNSNull)
|
|
129
135
|
{
|
|
130
136
|
jsi::Array propertyNames = value.getPropertyNames(runtime);
|
|
131
137
|
size_t size = propertyNames.size(runtime);
|
|
@@ -133,7 +139,7 @@ convertJSIObjectToNSDictionary(jsi::Runtime &runtime, const jsi::Object &value,
|
|
|
133
139
|
for (size_t i = 0; i < size; i++) {
|
|
134
140
|
jsi::String name = propertyNames.getValueAtIndex(runtime, i).getString(runtime);
|
|
135
141
|
NSString *k = convertJSIStringToNSString(runtime, name);
|
|
136
|
-
id v = convertJSIValueToObjCObject(runtime, value.getProperty(runtime, name), jsInvoker);
|
|
142
|
+
id v = convertJSIValueToObjCObject(runtime, value.getProperty(runtime, name), jsInvoker, useNSNull);
|
|
137
143
|
if (v) {
|
|
138
144
|
result[k] = v;
|
|
139
145
|
}
|
|
@@ -161,9 +167,21 @@ convertJSIFunctionToCallback(jsi::Runtime &rt, jsi::Function &&function, std::sh
|
|
|
161
167
|
|
|
162
168
|
id convertJSIValueToObjCObject(jsi::Runtime &runtime, const jsi::Value &value, std::shared_ptr<CallInvoker> jsInvoker)
|
|
163
169
|
{
|
|
164
|
-
|
|
170
|
+
return convertJSIValueToObjCObject(runtime, value, jsInvoker, NO);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
id convertJSIValueToObjCObject(
|
|
174
|
+
jsi::Runtime &runtime,
|
|
175
|
+
const jsi::Value &value,
|
|
176
|
+
std::shared_ptr<CallInvoker> jsInvoker,
|
|
177
|
+
BOOL useNSNull)
|
|
178
|
+
{
|
|
179
|
+
if (value.isUndefined() || (value.isNull() && !useNSNull)) {
|
|
165
180
|
return nil;
|
|
166
181
|
}
|
|
182
|
+
if (value.isNull() && useNSNull) {
|
|
183
|
+
return [NSNull null];
|
|
184
|
+
}
|
|
167
185
|
if (value.isBool()) {
|
|
168
186
|
return @(value.getBool());
|
|
169
187
|
}
|
|
@@ -176,12 +194,12 @@ id convertJSIValueToObjCObject(jsi::Runtime &runtime, const jsi::Value &value, s
|
|
|
176
194
|
if (value.isObject()) {
|
|
177
195
|
jsi::Object o = value.getObject(runtime);
|
|
178
196
|
if (o.isArray(runtime)) {
|
|
179
|
-
return convertJSIArrayToNSArray(runtime, o.getArray(runtime), jsInvoker);
|
|
197
|
+
return convertJSIArrayToNSArray(runtime, o.getArray(runtime), jsInvoker, useNSNull);
|
|
180
198
|
}
|
|
181
199
|
if (o.isFunction(runtime)) {
|
|
182
200
|
return convertJSIFunctionToCallback(runtime, o.getFunction(runtime), jsInvoker);
|
|
183
201
|
}
|
|
184
|
-
return convertJSIObjectToNSDictionary(runtime, o, jsInvoker);
|
|
202
|
+
return convertJSIObjectToNSDictionary(runtime, o, jsInvoker, useNSNull);
|
|
185
203
|
}
|
|
186
204
|
|
|
187
205
|
throw std::runtime_error("Unsupported jsi::Value kind");
|
|
@@ -46,6 +46,9 @@ void TextAttributes::apply(TextAttributes textAttributes) {
|
|
|
46
46
|
allowFontScaling = textAttributes.allowFontScaling.has_value()
|
|
47
47
|
? textAttributes.allowFontScaling
|
|
48
48
|
: allowFontScaling;
|
|
49
|
+
maxFontSizeMultiplier = !std::isnan(textAttributes.maxFontSizeMultiplier)
|
|
50
|
+
? textAttributes.maxFontSizeMultiplier
|
|
51
|
+
: maxFontSizeMultiplier;
|
|
49
52
|
dynamicTypeRamp = textAttributes.dynamicTypeRamp.has_value()
|
|
50
53
|
? textAttributes.dynamicTypeRamp
|
|
51
54
|
: dynamicTypeRamp;
|
|
@@ -168,6 +171,7 @@ bool TextAttributes::operator==(const TextAttributes& rhs) const {
|
|
|
168
171
|
rhs.accessibilityRole,
|
|
169
172
|
rhs.role,
|
|
170
173
|
rhs.textTransform) &&
|
|
174
|
+
floatEquality(maxFontSizeMultiplier, rhs.maxFontSizeMultiplier) &&
|
|
171
175
|
floatEquality(opacity, rhs.opacity) &&
|
|
172
176
|
floatEquality(fontSize, rhs.fontSize) &&
|
|
173
177
|
floatEquality(fontSizeMultiplier, rhs.fontSizeMultiplier) &&
|
|
@@ -211,6 +215,8 @@ SharedDebugStringConvertibleList TextAttributes::getDebugProps() const {
|
|
|
211
215
|
debugStringConvertibleItem("fontStyle", fontStyle),
|
|
212
216
|
debugStringConvertibleItem("fontVariant", fontVariant),
|
|
213
217
|
debugStringConvertibleItem("allowFontScaling", allowFontScaling),
|
|
218
|
+
debugStringConvertibleItem(
|
|
219
|
+
"maxFontSizeMultiplier", maxFontSizeMultiplier),
|
|
214
220
|
debugStringConvertibleItem("dynamicTypeRamp", dynamicTypeRamp),
|
|
215
221
|
debugStringConvertibleItem("letterSpacing", letterSpacing),
|
|
216
222
|
|
|
@@ -51,6 +51,7 @@ class TextAttributes : public DebugStringConvertible {
|
|
|
51
51
|
std::optional<FontStyle> fontStyle{};
|
|
52
52
|
std::optional<FontVariant> fontVariant{};
|
|
53
53
|
std::optional<bool> allowFontScaling{};
|
|
54
|
+
Float maxFontSizeMultiplier{std::numeric_limits<Float>::quiet_NaN()};
|
|
54
55
|
std::optional<DynamicTypeRamp> dynamicTypeRamp{};
|
|
55
56
|
Float letterSpacing{std::numeric_limits<Float>::quiet_NaN()};
|
|
56
57
|
std::optional<TextTransform> textTransform{};
|
|
@@ -117,6 +118,7 @@ struct hash<facebook::react::TextAttributes> {
|
|
|
117
118
|
textAttributes.opacity,
|
|
118
119
|
textAttributes.fontFamily,
|
|
119
120
|
textAttributes.fontSize,
|
|
121
|
+
textAttributes.maxFontSizeMultiplier,
|
|
120
122
|
textAttributes.fontSizeMultiplier,
|
|
121
123
|
textAttributes.fontWeight,
|
|
122
124
|
textAttributes.fontStyle,
|
|
@@ -910,6 +910,7 @@ constexpr static MapBuffer::Key TA_KEY_LINE_BREAK_STRATEGY = 25;
|
|
|
910
910
|
constexpr static MapBuffer::Key TA_KEY_ROLE = 26;
|
|
911
911
|
constexpr static MapBuffer::Key TA_KEY_TEXT_TRANSFORM = 27;
|
|
912
912
|
constexpr static MapBuffer::Key TA_KEY_ALIGNMENT_VERTICAL = 28;
|
|
913
|
+
constexpr static MapBuffer::Key TA_KEY_MAX_FONT_SIZE_MULTIPLIER = 29;
|
|
913
914
|
|
|
914
915
|
// constants for ParagraphAttributes serialization
|
|
915
916
|
constexpr static MapBuffer::Key PA_KEY_MAX_NUMBER_OF_LINES = 0;
|
|
@@ -1004,6 +1005,10 @@ inline MapBuffer toMapBuffer(const TextAttributes& textAttributes) {
|
|
|
1004
1005
|
builder.putBool(
|
|
1005
1006
|
TA_KEY_ALLOW_FONT_SCALING, *textAttributes.allowFontScaling);
|
|
1006
1007
|
}
|
|
1008
|
+
if (!std::isnan(textAttributes.maxFontSizeMultiplier)) {
|
|
1009
|
+
builder.putDouble(
|
|
1010
|
+
TA_KEY_MAX_FONT_SIZE_MULTIPLIER, textAttributes.maxFontSizeMultiplier);
|
|
1011
|
+
}
|
|
1007
1012
|
if (!std::isnan(textAttributes.letterSpacing)) {
|
|
1008
1013
|
builder.putDouble(TA_KEY_LETTER_SPACING, textAttributes.letterSpacing);
|
|
1009
1014
|
}
|