react-native-tvos 0.86.0-0rc2 → 0.86.0-0rc3
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/Components/ScrollView/ScrollView.d.ts +1 -1
- package/Libraries/Components/ScrollView/ScrollView.js +1 -1
- package/Libraries/Components/TV/TVViewPropTypes.js +9 -0
- package/Libraries/Components/View/View.js +7 -3
- package/Libraries/Core/ReactNativeVersion.js +1 -1
- package/Libraries/NativeComponent/TVViewConfig.js +1 -0
- package/README.md +13 -1
- package/React/Base/RCTVersion.m +1 -1
- package/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm +35 -15
- package/ReactAndroid/gradle.properties +1 -1
- package/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/ReactNativeVersion.kt +1 -1
- package/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java +14 -4
- package/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java +14 -4
- package/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewHelper.kt +46 -6
- package/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.kt +1 -0
- package/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.kt +5 -0
- package/ReactCommon/cxxreact/ReactNativeVersion.h +1 -1
- package/ReactCommon/react/renderer/components/view/BaseViewProps.cpp +7 -0
- package/ReactCommon/react/renderer/components/view/BaseViewProps.h +1 -0
- package/ReactCommon/react/renderer/components/view/platform/android/react/renderer/components/view/HostPlatformViewProps.cpp +18 -0
- package/ReactCommon/react/renderer/components/view/platform/android/react/renderer/components/view/HostPlatformViewProps.h +1 -0
- package/package.json +10 -10
- package/sdks/.hermesv1version +1 -1
- package/sdks/hermes-engine/version.properties +1 -1
- package/types/public/ReactNativeTVTypes.d.ts +9 -0
|
@@ -515,7 +515,7 @@ export interface ScrollViewPropsIOS {
|
|
|
515
515
|
* - `start` (the default) will align the snap at the left (horizontal) or top (vertical)
|
|
516
516
|
* - `center` will align the snap in the center
|
|
517
517
|
* - `end` will align the snap at the right (horizontal) or bottom (vertical)
|
|
518
|
-
* - `item` will align the snap according to
|
|
518
|
+
* - `item` will align the snap according to each item's own `scrollSnapAlign` or `scrollSnapOffset` prop (TV platforms only).
|
|
519
519
|
*/
|
|
520
520
|
snapToAlignment?: 'start' | 'center' | 'end' | 'item' | undefined;
|
|
521
521
|
|
|
@@ -620,7 +620,7 @@ type ScrollViewBaseProps = Readonly<{
|
|
|
620
620
|
* - `'start'` (the default) will align the snap at the left (horizontal) or top (vertical)
|
|
621
621
|
* - `'center'` will align the snap in the center
|
|
622
622
|
* - `'end'` will align the snap at the right (horizontal) or bottom (vertical)
|
|
623
|
-
* - `'item'` will align the snap according to
|
|
623
|
+
* - `'item'` will align the snap according to each item's own `scrollSnapAlign` or `scrollSnapOffset` prop (TV platforms only).
|
|
624
624
|
*/
|
|
625
625
|
snapToAlignment?: ?('start' | 'center' | 'end' | 'item'),
|
|
626
626
|
/**
|
|
@@ -131,4 +131,13 @@ export type TVViewProps = $ReadOnly<{|
|
|
|
131
131
|
*/
|
|
132
132
|
scrollSnapAlign?: ?('start' | 'center' | 'end'),
|
|
133
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Per-item scroll snap offset in dp/pt. When set on a View inside a
|
|
136
|
+
* ScrollView with `snapToAlignment="item"`, the focus engine lands the
|
|
137
|
+
* View's top at viewport y = scrollSnapOffset. Standalone alternative to
|
|
138
|
+
* `scrollSnapAlign` — set one or the other; if both are set on the same
|
|
139
|
+
* View, `scrollSnapOffset` takes precedence.
|
|
140
|
+
*/
|
|
141
|
+
scrollSnapOffset?: ?number,
|
|
142
|
+
|
|
134
143
|
|}>;
|
|
@@ -155,9 +155,13 @@ component View(
|
|
|
155
155
|
delete processedProps.isTVSelectable;
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
-
// Views with scrollSnapAlign must not be flattened by
|
|
159
|
-
// the prop never reaches the native view and scroll
|
|
160
|
-
|
|
158
|
+
// Views with scrollSnapAlign or scrollSnapOffset must not be flattened by
|
|
159
|
+
// Fabric, otherwise the prop never reaches the native view and scroll
|
|
160
|
+
// snapping breaks.
|
|
161
|
+
if (
|
|
162
|
+
processedProps.scrollSnapAlign != null ||
|
|
163
|
+
processedProps.scrollSnapOffset != null
|
|
164
|
+
) {
|
|
161
165
|
processedProps.collapsable = false;
|
|
162
166
|
}
|
|
163
167
|
|
|
@@ -29,7 +29,7 @@ export default class ReactNativeVersion {
|
|
|
29
29
|
static major: number = 0;
|
|
30
30
|
static minor: number = 86;
|
|
31
31
|
static patch: number = 0;
|
|
32
|
-
static prerelease: string | null = '
|
|
32
|
+
static prerelease: string | null = '0rc3';
|
|
33
33
|
|
|
34
34
|
static getVersionString(): string {
|
|
35
35
|
return `${this.major}.${this.minor}.${this.patch}${this.prerelease != null ? `-${this.prerelease}` : ''}`;
|
package/README.md
CHANGED
|
@@ -272,6 +272,7 @@ class Game2048 extends React.Component {
|
|
|
272
272
|
| Prop (View) | Value | Description |
|
|
273
273
|
|---|---|---|
|
|
274
274
|
| scrollSnapAlign | `'start'` \| `'center'` \| `'end'` | Controls where this item snaps inside its parent ScrollView. Only used when the parent has `snapToAlignment="item"`. |
|
|
275
|
+
| scrollSnapOffset | `number` | Per-item pixel offset (in dp/pt). When set, the focus engine lands this item's leading edge at `scrollSnapOffset` from the viewport origin. Standalone alternative to `scrollSnapAlign` — takes precedence if both are set on the same item. Only used when the parent has `snapToAlignment="item"`. |
|
|
275
276
|
|
|
276
277
|
```jsx
|
|
277
278
|
<ScrollView
|
|
@@ -291,6 +292,16 @@ class Game2048 extends React.Component {
|
|
|
291
292
|
</ScrollView>
|
|
292
293
|
```
|
|
293
294
|
|
|
295
|
+
`scrollSnapOffset` is useful when different items in the same ScrollView need to land at different positions — e.g. a hero row peeking 27dp from the top while poster rows peek 550dp:
|
|
296
|
+
|
|
297
|
+
```jsx
|
|
298
|
+
<ScrollView snapToAlignment="item">
|
|
299
|
+
<View scrollSnapOffset={27}><Hero /></View>
|
|
300
|
+
<View scrollSnapOffset={100}><Featured /></View>
|
|
301
|
+
<View scrollSnapOffset={550}><PosterRow /></View>
|
|
302
|
+
</ScrollView>
|
|
303
|
+
```
|
|
304
|
+
|
|
294
305
|
- _ScrollView animation control_: The `scrollAnimationEnabled` prop allows disabling scroll animations when focus changes on TV. When set to `false`, the scroll view jumps instantly to the focused item instead of animating. This only affects TV platforms and has no effect on mobile.
|
|
295
306
|
|
|
296
307
|
| Prop (ScrollView) | Value | Description |
|
|
@@ -307,7 +318,8 @@ class Game2048 extends React.Component {
|
|
|
307
318
|
|
|
308
319
|
- _Interaction with existing ScrollView props_: The TV scroll props build on top of existing React Native ScrollView behavior. Here is how they interact:
|
|
309
320
|
|
|
310
|
-
- `snapToAlignment="item"` does not require `snapToInterval` to be set. It works as a standalone snapping mode where each child's `scrollSnapAlign` determines the snap position. If `snapToInterval` is also set, the interval is applied as an additional constraint after the item offset is computed.
|
|
321
|
+
- `snapToAlignment="item"` does not require `snapToInterval` to be set. It works as a standalone snapping mode where each child's `scrollSnapAlign` or `scrollSnapOffset` determines the snap position. If `snapToInterval` is also set, the interval is applied as an additional constraint after the item offset is computed.
|
|
322
|
+
- `scrollSnapOffset` ignores `snapToItemPadding`. The padding is a global value applied to `scrollSnapAlign` cases; `scrollSnapOffset` is itself a per-item pixel offset that fully specifies where the item lands.
|
|
311
323
|
- `snapToAlignment="item"` should not be combined with `pagingEnabled`. Both attempt to control scroll positioning independently, which leads to unpredictable behavior. Use one or the other.
|
|
312
324
|
- `snapToStart` and `snapToEnd` work independently of `snapToAlignment="item"`. They control edge behavior during swipe/drag momentum (whether the scroll view snaps to the first or last position), but do not affect focus driven item snapping.
|
|
313
325
|
- `scrollAnimationEnabled={false}` disables all scroll animations, including programmatic `scrollTo({animated: true})` calls. When disabled, all scrolling is instant.
|
package/React/Base/RCTVersion.m
CHANGED
|
@@ -1171,13 +1171,20 @@ static inline UIViewAnimationOptions animationOptionsWithCurve(UIViewAnimationCu
|
|
|
1171
1171
|
self->_eventEmitter->onBlur();
|
|
1172
1172
|
}
|
|
1173
1173
|
|
|
1174
|
-
// Focus marker helper: traverses view hierarchy to find scrollSnapAlign
|
|
1175
|
-
// Returns the view that has the property via the output
|
|
1176
|
-
|
|
1174
|
+
// Focus marker helper: traverses view hierarchy to find either scrollSnapAlign
|
|
1175
|
+
// or scrollSnapOffset. Returns the view that has the property via the output
|
|
1176
|
+
// parameter and the marker type via outAlign/outOffset (exactly one non-nil on
|
|
1177
|
+
// return). Walk is inner→outer, latest marker wins (same as PR-1068's
|
|
1178
|
+
// scrollSnapAlign behavior). On a single view declaring both, scrollSnapOffset
|
|
1179
|
+
// wins as the more specific config.
|
|
1180
|
+
- (UIView *)findScrollSnapInView:(UIView *)view
|
|
1181
|
+
outAlign:(NSString **)outAlign
|
|
1182
|
+
outOffset:(NSNumber **)outOffset
|
|
1177
1183
|
{
|
|
1178
1184
|
UIView *testView = view;
|
|
1179
1185
|
UIView *snapTarget;
|
|
1180
|
-
NSString *
|
|
1186
|
+
NSString *align;
|
|
1187
|
+
NSNumber *offset;
|
|
1181
1188
|
|
|
1182
1189
|
while (testView && testView != self) {
|
|
1183
1190
|
if (![testView isKindOfClass:RCTViewComponentView.class])
|
|
@@ -1188,28 +1195,37 @@ static inline UIViewAnimationOptions animationOptionsWithCurve(UIViewAnimationCu
|
|
|
1188
1195
|
RCTViewComponentView *componentView = (RCTViewComponentView *)testView;
|
|
1189
1196
|
|
|
1190
1197
|
const auto &viewProps = static_cast<const facebook::react::BaseViewProps &>(*componentView.props);
|
|
1191
|
-
if (viewProps.
|
|
1192
|
-
|
|
1198
|
+
if (viewProps.scrollSnapOffset.has_value()) {
|
|
1199
|
+
offset = @(viewProps.scrollSnapOffset.value());
|
|
1200
|
+
align = nil;
|
|
1201
|
+
snapTarget = componentView;
|
|
1202
|
+
} else if (viewProps.scrollSnapAlign.has_value() && !viewProps.scrollSnapAlign.value().empty()) {
|
|
1203
|
+
align = [NSString stringWithUTF8String:viewProps.scrollSnapAlign.value().c_str()];
|
|
1204
|
+
offset = nil;
|
|
1193
1205
|
snapTarget = componentView;
|
|
1194
1206
|
}
|
|
1195
1207
|
|
|
1196
1208
|
testView = [testView superview];
|
|
1197
1209
|
}
|
|
1198
|
-
*
|
|
1199
|
-
|
|
1210
|
+
*outAlign = align;
|
|
1211
|
+
*outOffset = offset;
|
|
1212
|
+
return snapTarget;
|
|
1200
1213
|
}
|
|
1201
1214
|
|
|
1202
1215
|
- (void)_handleScrollSnapForFocusedView:(UIView *)focusedView
|
|
1203
1216
|
{
|
|
1204
1217
|
const auto &scrollProps = static_cast<const ScrollViewProps &>(*_props);
|
|
1205
|
-
|
|
1206
|
-
NSString *scrollSnapAlign =
|
|
1207
|
-
|
|
1218
|
+
|
|
1219
|
+
NSString *scrollSnapAlign = nil;
|
|
1220
|
+
NSNumber *scrollSnapOffset = nil;
|
|
1221
|
+
UIView *snapTargetView = [self findScrollSnapInView:focusedView
|
|
1222
|
+
outAlign:&scrollSnapAlign
|
|
1223
|
+
outOffset:&scrollSnapOffset];
|
|
1224
|
+
if (snapTargetView == nil || (scrollSnapAlign == nil && scrollSnapOffset == nil)) {
|
|
1208
1225
|
return;
|
|
1209
1226
|
}
|
|
1210
|
-
|
|
1211
1227
|
RCTEnhancedScrollView *scrollView = (RCTEnhancedScrollView *)_scrollView;
|
|
1212
|
-
CGRect focusedFrame = [
|
|
1228
|
+
CGRect focusedFrame = [snapTargetView convertRect:snapTargetView.bounds toView:_scrollView];
|
|
1213
1229
|
CGFloat targetOffset;
|
|
1214
1230
|
CGFloat snapToItemPadding = scrollProps.snapToItemPadding;
|
|
1215
1231
|
|
|
@@ -1229,8 +1245,12 @@ static inline UIViewAnimationOptions animationOptionsWithCurve(UIViewAnimationCu
|
|
|
1229
1245
|
currentOffset = scrollView.contentOffset.y;
|
|
1230
1246
|
maxContentSize = scrollView.contentSize.height;
|
|
1231
1247
|
}
|
|
1232
|
-
|
|
1233
|
-
if (
|
|
1248
|
+
|
|
1249
|
+
if (scrollSnapOffset != nil) {
|
|
1250
|
+
// Per-item pixel offset path: land the snap target's leading edge
|
|
1251
|
+
// at viewport coordinate = scrollSnapOffset.
|
|
1252
|
+
targetOffset = focusedOrigin - scrollSnapOffset.doubleValue;
|
|
1253
|
+
} else if ([scrollSnapAlign isEqualToString:@"start"]) {
|
|
1234
1254
|
targetOffset = focusedOrigin - snapToItemPadding;
|
|
1235
1255
|
} else if ([scrollSnapAlign isEqualToString:@"center"]) {
|
|
1236
1256
|
CGFloat viewportCenter = viewportSize / 2;
|
package/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java
CHANGED
|
@@ -578,29 +578,39 @@ public class ReactHorizontalScrollView extends HorizontalScrollView
|
|
|
578
578
|
}
|
|
579
579
|
|
|
580
580
|
/**
|
|
581
|
-
* Attempts to scroll-snap to the focused child based on snapToAlignment/scrollSnapAlign
|
|
582
|
-
* Returns true if snap scrolling was performed, false otherwise.
|
|
581
|
+
* Attempts to scroll-snap to the focused child based on snapToAlignment/scrollSnapAlign
|
|
582
|
+
* or scrollSnapOffset. Returns true if snap scrolling was performed, false otherwise.
|
|
583
583
|
*/
|
|
584
584
|
private boolean tryScrollSnapToChild(View focused) {
|
|
585
585
|
if (mSnapToAlignment != SNAP_ALIGNMENT_ITEM) {
|
|
586
586
|
return false;
|
|
587
587
|
}
|
|
588
588
|
|
|
589
|
-
kotlin.
|
|
589
|
+
kotlin.Triple<View, String, Integer> result =
|
|
590
|
+
ReactScrollViewHelper.findScrollSnap(focused, this);
|
|
590
591
|
if (result == null) {
|
|
591
592
|
return false;
|
|
592
593
|
}
|
|
593
594
|
|
|
594
595
|
View snapTarget = result.getFirst();
|
|
595
596
|
String alignment = result.getSecond();
|
|
597
|
+
Integer snapOffset = result.getThird();
|
|
596
598
|
|
|
597
599
|
Rect rect = new Rect();
|
|
598
600
|
snapTarget.getDrawingRect(rect);
|
|
599
601
|
offsetDescendantRectToMyCoords(snapTarget, rect);
|
|
600
602
|
|
|
601
|
-
int viewportWidth = getWidth() - getPaddingLeft() - getPaddingRight();
|
|
602
603
|
int maxScrollX = Math.max(0, computeHorizontalScrollRange() - getWidth());
|
|
603
604
|
|
|
605
|
+
if (snapOffset != null) {
|
|
606
|
+
int targetOffset = ReactScrollViewHelper.computeScrollSnapTargetForOffset(
|
|
607
|
+
rect.left, snapOffset, mSnapInterval, maxScrollX);
|
|
608
|
+
reactSmoothScrollTo(targetOffset, getScrollY());
|
|
609
|
+
return true;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
int viewportWidth = getWidth() - getPaddingLeft() - getPaddingRight();
|
|
613
|
+
|
|
604
614
|
Integer targetOffset = ReactScrollViewHelper.computeScrollSnapOffset(
|
|
605
615
|
rect.left, rect.right, viewportWidth, alignment, mSnapInterval, mSnapToItemPadding, maxScrollX);
|
|
606
616
|
if (targetOffset == null) {
|
|
@@ -520,29 +520,39 @@ public class ReactScrollView extends ScrollView
|
|
|
520
520
|
}
|
|
521
521
|
|
|
522
522
|
/**
|
|
523
|
-
* Attempts to scroll-snap to the focused child based on snapToAlignment/scrollSnapAlign
|
|
524
|
-
* Returns true if snap scrolling was performed, false otherwise.
|
|
523
|
+
* Attempts to scroll-snap to the focused child based on snapToAlignment/scrollSnapAlign
|
|
524
|
+
* or scrollSnapOffset. Returns true if snap scrolling was performed, false otherwise.
|
|
525
525
|
*/
|
|
526
526
|
private boolean tryScrollSnapToChild(View focused) {
|
|
527
527
|
if (mSnapToAlignment != SNAP_ALIGNMENT_ITEM) {
|
|
528
528
|
return false;
|
|
529
529
|
}
|
|
530
530
|
|
|
531
|
-
kotlin.
|
|
531
|
+
kotlin.Triple<View, String, Integer> result =
|
|
532
|
+
ReactScrollViewHelper.findScrollSnap(focused, this);
|
|
532
533
|
if (result == null) {
|
|
533
534
|
return false;
|
|
534
535
|
}
|
|
535
536
|
|
|
536
537
|
View snapTarget = result.getFirst();
|
|
537
538
|
String alignment = result.getSecond();
|
|
539
|
+
Integer snapOffset = result.getThird();
|
|
538
540
|
|
|
539
541
|
Rect rect = new Rect();
|
|
540
542
|
snapTarget.getDrawingRect(rect);
|
|
541
543
|
offsetDescendantRectToMyCoords(snapTarget, rect);
|
|
542
544
|
|
|
543
|
-
int viewportHeight = getHeight() - getPaddingTop() - getPaddingBottom();
|
|
544
545
|
int maxScrollY = getMaxScrollY();
|
|
545
546
|
|
|
547
|
+
if (snapOffset != null) {
|
|
548
|
+
int targetOffset = ReactScrollViewHelper.computeScrollSnapTargetForOffset(
|
|
549
|
+
rect.top, snapOffset, mSnapInterval, maxScrollY);
|
|
550
|
+
reactSmoothScrollTo(getScrollX(), targetOffset);
|
|
551
|
+
return true;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
int viewportHeight = getHeight() - getPaddingTop() - getPaddingBottom();
|
|
555
|
+
|
|
546
556
|
Integer targetOffset = ReactScrollViewHelper.computeScrollSnapOffset(
|
|
547
557
|
rect.top, rect.bottom, viewportHeight, alignment, mSnapInterval, mSnapToItemPadding, maxScrollY);
|
|
548
558
|
if (targetOffset == null) {
|
|
@@ -576,27 +576,67 @@ public object ReactScrollViewHelper {
|
|
|
576
576
|
|
|
577
577
|
/**
|
|
578
578
|
* Walks up the view hierarchy from the focused view to find a ReactViewGroup with
|
|
579
|
-
* scrollSnapAlign set. Returns a
|
|
579
|
+
* either scrollSnapAlign or scrollSnapOffset set. Returns a Triple of (snapTarget,
|
|
580
|
+
* alignment, offsetPx) or null if neither marker was found. Exactly one of
|
|
581
|
+
* alignment/offsetPx is non-null on return.
|
|
582
|
+
*
|
|
583
|
+
* Walk is inner→outer, latest marker wins. On a single view declaring both,
|
|
584
|
+
* scrollSnapOffset wins as the more specific config.
|
|
580
585
|
*
|
|
581
586
|
* Shared by [ReactScrollView] and [ReactHorizontalScrollView].
|
|
582
587
|
*/
|
|
583
588
|
@JvmStatic
|
|
584
|
-
public fun
|
|
589
|
+
public fun findScrollSnap(focused: View, scrollView: ViewGroup): Triple<View, String?, Int?>? {
|
|
585
590
|
var view: View? = focused
|
|
586
591
|
var snapTarget: View? = null
|
|
587
592
|
var alignment: String? = null
|
|
593
|
+
var offset: Int? = null
|
|
588
594
|
while (view != null && view !== scrollView) {
|
|
589
595
|
if (view is ReactViewGroup) {
|
|
590
|
-
val
|
|
591
|
-
|
|
592
|
-
|
|
596
|
+
val o = view.scrollSnapOffset
|
|
597
|
+
val a = view.scrollSnapAlign
|
|
598
|
+
if (o != null) {
|
|
599
|
+
offset = o
|
|
600
|
+
alignment = null
|
|
601
|
+
snapTarget = view
|
|
602
|
+
} else if (a != null) {
|
|
603
|
+
alignment = a
|
|
604
|
+
offset = null
|
|
593
605
|
snapTarget = view
|
|
594
606
|
}
|
|
595
607
|
}
|
|
596
608
|
val parent = view.parent
|
|
597
609
|
view = if (parent is View) parent else null
|
|
598
610
|
}
|
|
599
|
-
return if (
|
|
611
|
+
return if (snapTarget != null && (alignment != null || offset != null))
|
|
612
|
+
Triple(snapTarget, alignment, offset) else null
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* Computes the target scroll offset for the per-item scrollSnapOffset path:
|
|
617
|
+
* land the snap target's leading edge at `snapOffset` pixels from the viewport origin.
|
|
618
|
+
*
|
|
619
|
+
* Returns the clamped target offset.
|
|
620
|
+
*
|
|
621
|
+
* Shared by [ReactScrollView] and [ReactHorizontalScrollView].
|
|
622
|
+
*
|
|
623
|
+
* @param focusedStart the start coordinate of the snap target in scroll view coordinates
|
|
624
|
+
* @param snapOffset the per-item pixel offset from the viewport origin
|
|
625
|
+
* @param snapInterval the snap interval (0 if not set)
|
|
626
|
+
* @param maxScrollOffset the maximum scroll offset for clamping
|
|
627
|
+
*/
|
|
628
|
+
@JvmStatic
|
|
629
|
+
public fun computeScrollSnapTargetForOffset(
|
|
630
|
+
focusedStart: Int,
|
|
631
|
+
snapOffset: Int,
|
|
632
|
+
snapInterval: Int,
|
|
633
|
+
maxScrollOffset: Int,
|
|
634
|
+
): Int {
|
|
635
|
+
var targetOffset = focusedStart - snapOffset
|
|
636
|
+
if (snapInterval > 0) {
|
|
637
|
+
targetOffset = (Math.floor(targetOffset.toDouble() / snapInterval) * snapInterval).toInt()
|
|
638
|
+
}
|
|
639
|
+
return Math.max(0, Math.min(targetOffset, maxScrollOffset))
|
|
600
640
|
}
|
|
601
641
|
|
|
602
642
|
/**
|
|
@@ -175,6 +175,7 @@ public open class ReactViewGroup public constructor(context: Context?) :
|
|
|
175
175
|
private var onInterceptTouchEventListener: OnInterceptTouchEventListener? = null
|
|
176
176
|
private var needsOffscreenAlphaCompositing = false
|
|
177
177
|
public var scrollSnapAlign: String? = null
|
|
178
|
+
public var scrollSnapOffset: Int? = null
|
|
178
179
|
private var backfaceOpacity = 0f
|
|
179
180
|
private var backfaceVisible = false
|
|
180
181
|
private var childrenRemovedWhileTransitioning: MutableSet<Int>? = null
|
|
@@ -192,6 +192,11 @@ public open class ReactViewManager : ReactClippingViewManager<ReactViewGroup>()
|
|
|
192
192
|
view.scrollSnapAlign = value
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
+
@ReactProp(name = "scrollSnapOffset", defaultFloat = Float.NaN)
|
|
196
|
+
public open fun setScrollSnapOffset(view: ReactViewGroup, value: Float) {
|
|
197
|
+
view.scrollSnapOffset = if (value.isNaN()) null else value.dpToPx().toInt()
|
|
198
|
+
}
|
|
199
|
+
|
|
195
200
|
@ReactProp(name = ViewProps.BACKGROUND_IMAGE, customType = "BackgroundImage")
|
|
196
201
|
public open fun setBackgroundImage(view: ReactViewGroup, backgroundImage: ReadableArray?) {
|
|
197
202
|
if (backgroundImage != null && backgroundImage.size() > 0) {
|
|
@@ -455,6 +455,12 @@ BaseViewProps::BaseViewProps(
|
|
|
455
455
|
"scrollSnapAlign",
|
|
456
456
|
sourceProps.scrollSnapAlign,
|
|
457
457
|
{}))
|
|
458
|
+
,scrollSnapOffset(ReactNativeFeatureFlags::enableCppPropsIteratorSetter() ? sourceProps.scrollSnapOffset : convertRawProp(
|
|
459
|
+
context,
|
|
460
|
+
rawProps,
|
|
461
|
+
"scrollSnapOffset",
|
|
462
|
+
sourceProps.scrollSnapOffset,
|
|
463
|
+
{}))
|
|
458
464
|
#endif
|
|
459
465
|
{}
|
|
460
466
|
|
|
@@ -528,6 +534,7 @@ void BaseViewProps::setProp(
|
|
|
528
534
|
RAW_SET_PROP_SWITCH_CASE_BASIC(trapFocusLeft);
|
|
529
535
|
RAW_SET_PROP_SWITCH_CASE_BASIC(trapFocusRight);
|
|
530
536
|
RAW_SET_PROP_SWITCH_CASE_BASIC(scrollSnapAlign);
|
|
537
|
+
RAW_SET_PROP_SWITCH_CASE_BASIC(scrollSnapOffset);
|
|
531
538
|
#endif
|
|
532
539
|
// events field
|
|
533
540
|
VIEW_EVENT_CASE(PointerEnter);
|
|
@@ -109,6 +109,15 @@ HostPlatformViewProps::HostPlatformViewProps(
|
|
|
109
109
|
"scrollSnapAlign",
|
|
110
110
|
sourceProps.scrollSnapAlign,
|
|
111
111
|
{})),
|
|
112
|
+
scrollSnapOffset(
|
|
113
|
+
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
|
|
114
|
+
? sourceProps.scrollSnapOffset
|
|
115
|
+
: convertRawProp(
|
|
116
|
+
context,
|
|
117
|
+
rawProps,
|
|
118
|
+
"scrollSnapOffset",
|
|
119
|
+
sourceProps.scrollSnapOffset,
|
|
120
|
+
{})),
|
|
112
121
|
needsOffscreenAlphaCompositing(
|
|
113
122
|
ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
|
|
114
123
|
? sourceProps.needsOffscreenAlphaCompositing
|
|
@@ -229,6 +238,7 @@ void HostPlatformViewProps::setProp(
|
|
|
229
238
|
RAW_SET_PROP_SWITCH_CASE_BASIC(focusable);
|
|
230
239
|
RAW_SET_PROP_SWITCH_CASE_BASIC(hasTVPreferredFocus);
|
|
231
240
|
RAW_SET_PROP_SWITCH_CASE_BASIC(scrollSnapAlign);
|
|
241
|
+
RAW_SET_PROP_SWITCH_CASE_BASIC(scrollSnapOffset);
|
|
232
242
|
RAW_SET_PROP_SWITCH_CASE_BASIC(needsOffscreenAlphaCompositing);
|
|
233
243
|
RAW_SET_PROP_SWITCH_CASE_BASIC(renderToHardwareTextureAndroid);
|
|
234
244
|
RAW_SET_PROP_SWITCH_CASE_BASIC(screenReaderFocusable);
|
|
@@ -1113,6 +1123,14 @@ folly::dynamic HostPlatformViewProps::getDiffProps(
|
|
|
1113
1123
|
}
|
|
1114
1124
|
}
|
|
1115
1125
|
|
|
1126
|
+
if (scrollSnapOffset != oldProps->scrollSnapOffset) {
|
|
1127
|
+
if (scrollSnapOffset.has_value()) {
|
|
1128
|
+
result["scrollSnapOffset"] = scrollSnapOffset.value();
|
|
1129
|
+
} else {
|
|
1130
|
+
result["scrollSnapOffset"] = folly::dynamic(nullptr);
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1116
1134
|
return result;
|
|
1117
1135
|
}
|
|
1118
1136
|
|
|
@@ -48,6 +48,7 @@ class HostPlatformViewProps : public BaseViewProps {
|
|
|
48
48
|
bool trapFocusLeft{false};
|
|
49
49
|
bool trapFocusRight{false};
|
|
50
50
|
std::optional<std::string> scrollSnapAlign{};
|
|
51
|
+
std::optional<int> scrollSnapOffset{};
|
|
51
52
|
|
|
52
53
|
bool needsOffscreenAlphaCompositing{false};
|
|
53
54
|
bool renderToHardwareTextureAndroid{false};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-tvos",
|
|
3
|
-
"version": "0.86.0-
|
|
3
|
+
"version": "0.86.0-0rc3",
|
|
4
4
|
"description": "A framework for building native apps using React",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -154,7 +154,7 @@
|
|
|
154
154
|
"featureflags-check": "node ./scripts/featureflags/index.js --verify-unchanged"
|
|
155
155
|
},
|
|
156
156
|
"peerDependencies": {
|
|
157
|
-
"@react-native/jest-preset": "0.86.0-rc.
|
|
157
|
+
"@react-native/jest-preset": "0.86.0-rc.3",
|
|
158
158
|
"@types/react": "^19.1.1",
|
|
159
159
|
"react": "^19.2.3"
|
|
160
160
|
},
|
|
@@ -167,12 +167,12 @@
|
|
|
167
167
|
}
|
|
168
168
|
},
|
|
169
169
|
"dependencies": {
|
|
170
|
-
"@react-native/assets-registry": "0.86.0-rc.
|
|
171
|
-
"@react-native/codegen": "0.86.0-rc.
|
|
172
|
-
"@react-native/community-cli-plugin": "0.86.0-rc.
|
|
173
|
-
"@react-native/gradle-plugin": "0.86.0-rc.
|
|
174
|
-
"@react-native/js-polyfills": "0.86.0-rc.
|
|
175
|
-
"@react-native/normalize-colors": "0.86.0-rc.
|
|
170
|
+
"@react-native/assets-registry": "0.86.0-rc.3",
|
|
171
|
+
"@react-native/codegen": "0.86.0-rc.3",
|
|
172
|
+
"@react-native/community-cli-plugin": "0.86.0-rc.3",
|
|
173
|
+
"@react-native/gradle-plugin": "0.86.0-rc.3",
|
|
174
|
+
"@react-native/js-polyfills": "0.86.0-rc.3",
|
|
175
|
+
"@react-native/normalize-colors": "0.86.0-rc.3",
|
|
176
176
|
"abort-controller": "^3.0.0",
|
|
177
177
|
"anser": "^1.4.9",
|
|
178
178
|
"ansi-regex": "^5.0.0",
|
|
@@ -180,7 +180,7 @@
|
|
|
180
180
|
"base64-js": "^1.5.1",
|
|
181
181
|
"commander": "^12.0.0",
|
|
182
182
|
"flow-enums-runtime": "^0.0.6",
|
|
183
|
-
"hermes-compiler": "250829098.0.
|
|
183
|
+
"hermes-compiler": "250829098.0.14",
|
|
184
184
|
"invariant": "^2.2.4",
|
|
185
185
|
"memoize-one": "^5.0.0",
|
|
186
186
|
"metro-runtime": "^0.84.3",
|
|
@@ -198,7 +198,7 @@
|
|
|
198
198
|
"whatwg-fetch": "^3.0.0",
|
|
199
199
|
"ws": "^7.5.10",
|
|
200
200
|
"yargs": "^17.6.2",
|
|
201
|
-
"@react-native-tvos/virtualized-lists": "0.86.0-
|
|
201
|
+
"@react-native-tvos/virtualized-lists": "0.86.0-0rc3"
|
|
202
202
|
},
|
|
203
203
|
"codegenConfig": {
|
|
204
204
|
"libraries": [
|
package/sdks/.hermesv1version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
hermes-v250829098.0.
|
|
1
|
+
hermes-v250829098.0.14
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
HERMES_VERSION_NAME=0.17.0
|
|
2
|
-
HERMES_V1_VERSION_NAME=250829098.0.
|
|
2
|
+
HERMES_V1_VERSION_NAME=250829098.0.14
|
|
@@ -30,6 +30,15 @@ declare module 'react-native' {
|
|
|
30
30
|
*/
|
|
31
31
|
scrollSnapAlign?: 'start' | 'center' | 'end' | undefined;
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Per-item scroll snap offset in dp/pt. When set, the focus engine lands
|
|
35
|
+
* this view's top at viewport y = scrollSnapOffset. Standalone alternative
|
|
36
|
+
* to `scrollSnapAlign`; takes precedence if both are set on the same View.
|
|
37
|
+
*
|
|
38
|
+
* @platform tv
|
|
39
|
+
*/
|
|
40
|
+
scrollSnapOffset?: number | undefined;
|
|
41
|
+
|
|
33
42
|
/**
|
|
34
43
|
* Android TV only prop
|
|
35
44
|
*/
|