react-native-tvos 0.85.3-0 → 0.85.3-2
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/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +1 -2
- 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 +2 -2
- 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
|
|}>;
|
|
@@ -153,9 +153,13 @@ component View(
|
|
|
153
153
|
delete processedProps.isTVSelectable;
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
-
// Views with scrollSnapAlign must not be flattened by
|
|
157
|
-
// the prop never reaches the native view and scroll
|
|
158
|
-
|
|
156
|
+
// Views with scrollSnapAlign or scrollSnapOffset must not be flattened by
|
|
157
|
+
// Fabric, otherwise the prop never reaches the native view and scroll
|
|
158
|
+
// snapping breaks.
|
|
159
|
+
if (
|
|
160
|
+
processedProps.scrollSnapAlign != null ||
|
|
161
|
+
processedProps.scrollSnapOffset != null
|
|
162
|
+
) {
|
|
159
163
|
processedProps.collapsable = false;
|
|
160
164
|
}
|
|
161
165
|
|
|
@@ -29,7 +29,7 @@ export default class ReactNativeVersion {
|
|
|
29
29
|
static major: number = 0;
|
|
30
30
|
static minor: number = 85;
|
|
31
31
|
static patch: number = 3;
|
|
32
|
-
static prerelease: string | null = '
|
|
32
|
+
static prerelease: string | null = '2';
|
|
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;
|
|
@@ -611,11 +611,10 @@ const CGFloat BACKGROUND_COLOR_ZPOSITION = -1024.0f;
|
|
|
611
611
|
// [self isTVFocusGuide] is false when autofocus and destinations are not used, so we cannot use that.
|
|
612
612
|
// Generally speaking, it would happen for any non-collapsable `View`.
|
|
613
613
|
} else if (context.previouslyFocusedView == self) {
|
|
614
|
-
[self disableDirectionalFocusGuides];
|
|
615
614
|
[coordinator addCoordinatedAnimations:^(void){
|
|
616
615
|
[self removeParallaxMotionEffects];
|
|
617
616
|
if (self->_eventEmitter) self->_eventEmitter->onBlur();
|
|
618
|
-
} completion:^(void){}];
|
|
617
|
+
} completion:^(void){[self disableDirectionalFocusGuides];}];
|
|
619
618
|
[self resignFirstResponder];
|
|
620
619
|
}
|
|
621
620
|
}
|
package/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java
CHANGED
|
@@ -586,29 +586,39 @@ public class ReactHorizontalScrollView extends HorizontalScrollView
|
|
|
586
586
|
}
|
|
587
587
|
|
|
588
588
|
/**
|
|
589
|
-
* Attempts to scroll-snap to the focused child based on snapToAlignment/scrollSnapAlign
|
|
590
|
-
* Returns true if snap scrolling was performed, false otherwise.
|
|
589
|
+
* Attempts to scroll-snap to the focused child based on snapToAlignment/scrollSnapAlign
|
|
590
|
+
* or scrollSnapOffset. Returns true if snap scrolling was performed, false otherwise.
|
|
591
591
|
*/
|
|
592
592
|
private boolean tryScrollSnapToChild(View focused) {
|
|
593
593
|
if (mSnapToAlignment != SNAP_ALIGNMENT_ITEM) {
|
|
594
594
|
return false;
|
|
595
595
|
}
|
|
596
596
|
|
|
597
|
-
kotlin.
|
|
597
|
+
kotlin.Triple<View, String, Integer> result =
|
|
598
|
+
ReactScrollViewHelper.findScrollSnap(focused, this);
|
|
598
599
|
if (result == null) {
|
|
599
600
|
return false;
|
|
600
601
|
}
|
|
601
602
|
|
|
602
603
|
View snapTarget = result.getFirst();
|
|
603
604
|
String alignment = result.getSecond();
|
|
605
|
+
Integer snapOffset = result.getThird();
|
|
604
606
|
|
|
605
607
|
Rect rect = new Rect();
|
|
606
608
|
snapTarget.getDrawingRect(rect);
|
|
607
609
|
offsetDescendantRectToMyCoords(snapTarget, rect);
|
|
608
610
|
|
|
609
|
-
int viewportWidth = getWidth() - getPaddingLeft() - getPaddingRight();
|
|
610
611
|
int maxScrollX = Math.max(0, computeHorizontalScrollRange() - getWidth());
|
|
611
612
|
|
|
613
|
+
if (snapOffset != null) {
|
|
614
|
+
int targetOffset = ReactScrollViewHelper.computeScrollSnapTargetForOffset(
|
|
615
|
+
rect.left, snapOffset, mSnapInterval, maxScrollX);
|
|
616
|
+
reactSmoothScrollTo(targetOffset, getScrollY());
|
|
617
|
+
return true;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
int viewportWidth = getWidth() - getPaddingLeft() - getPaddingRight();
|
|
621
|
+
|
|
612
622
|
Integer targetOffset = ReactScrollViewHelper.computeScrollSnapOffset(
|
|
613
623
|
rect.left, rect.right, viewportWidth, alignment, mSnapInterval, mSnapToItemPadding, maxScrollX);
|
|
614
624
|
if (targetOffset == null) {
|
|
@@ -519,29 +519,39 @@ public class ReactScrollView extends ScrollView
|
|
|
519
519
|
}
|
|
520
520
|
|
|
521
521
|
/**
|
|
522
|
-
* Attempts to scroll-snap to the focused child based on snapToAlignment/scrollSnapAlign
|
|
523
|
-
* Returns true if snap scrolling was performed, false otherwise.
|
|
522
|
+
* Attempts to scroll-snap to the focused child based on snapToAlignment/scrollSnapAlign
|
|
523
|
+
* or scrollSnapOffset. Returns true if snap scrolling was performed, false otherwise.
|
|
524
524
|
*/
|
|
525
525
|
private boolean tryScrollSnapToChild(View focused) {
|
|
526
526
|
if (mSnapToAlignment != SNAP_ALIGNMENT_ITEM) {
|
|
527
527
|
return false;
|
|
528
528
|
}
|
|
529
529
|
|
|
530
|
-
kotlin.
|
|
530
|
+
kotlin.Triple<View, String, Integer> result =
|
|
531
|
+
ReactScrollViewHelper.findScrollSnap(focused, this);
|
|
531
532
|
if (result == null) {
|
|
532
533
|
return false;
|
|
533
534
|
}
|
|
534
535
|
|
|
535
536
|
View snapTarget = result.getFirst();
|
|
536
537
|
String alignment = result.getSecond();
|
|
538
|
+
Integer snapOffset = result.getThird();
|
|
537
539
|
|
|
538
540
|
Rect rect = new Rect();
|
|
539
541
|
snapTarget.getDrawingRect(rect);
|
|
540
542
|
offsetDescendantRectToMyCoords(snapTarget, rect);
|
|
541
543
|
|
|
542
|
-
int viewportHeight = getHeight() - getPaddingTop() - getPaddingBottom();
|
|
543
544
|
int maxScrollY = getMaxScrollY();
|
|
544
545
|
|
|
546
|
+
if (snapOffset != null) {
|
|
547
|
+
int targetOffset = ReactScrollViewHelper.computeScrollSnapTargetForOffset(
|
|
548
|
+
rect.top, snapOffset, mSnapInterval, maxScrollY);
|
|
549
|
+
reactSmoothScrollTo(getScrollX(), targetOffset);
|
|
550
|
+
return true;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
int viewportHeight = getHeight() - getPaddingTop() - getPaddingBottom();
|
|
554
|
+
|
|
545
555
|
Integer targetOffset = ReactScrollViewHelper.computeScrollSnapOffset(
|
|
546
556
|
rect.top, rect.bottom, viewportHeight, alignment, mSnapInterval, mSnapToItemPadding, maxScrollY);
|
|
547
557
|
if (targetOffset == null) {
|
|
@@ -569,27 +569,67 @@ public object ReactScrollViewHelper {
|
|
|
569
569
|
|
|
570
570
|
/**
|
|
571
571
|
* Walks up the view hierarchy from the focused view to find a ReactViewGroup with
|
|
572
|
-
* scrollSnapAlign set. Returns a
|
|
572
|
+
* either scrollSnapAlign or scrollSnapOffset set. Returns a Triple of (snapTarget,
|
|
573
|
+
* alignment, offsetPx) or null if neither marker was found. Exactly one of
|
|
574
|
+
* alignment/offsetPx is non-null on return.
|
|
575
|
+
*
|
|
576
|
+
* Walk is inner→outer, latest marker wins. On a single view declaring both,
|
|
577
|
+
* scrollSnapOffset wins as the more specific config.
|
|
573
578
|
*
|
|
574
579
|
* Shared by [ReactScrollView] and [ReactHorizontalScrollView].
|
|
575
580
|
*/
|
|
576
581
|
@JvmStatic
|
|
577
|
-
public fun
|
|
582
|
+
public fun findScrollSnap(focused: View, scrollView: ViewGroup): Triple<View, String?, Int?>? {
|
|
578
583
|
var view: View? = focused
|
|
579
584
|
var snapTarget: View? = null
|
|
580
585
|
var alignment: String? = null
|
|
586
|
+
var offset: Int? = null
|
|
581
587
|
while (view != null && view !== scrollView) {
|
|
582
588
|
if (view is ReactViewGroup) {
|
|
583
|
-
val
|
|
584
|
-
|
|
585
|
-
|
|
589
|
+
val o = view.scrollSnapOffset
|
|
590
|
+
val a = view.scrollSnapAlign
|
|
591
|
+
if (o != null) {
|
|
592
|
+
offset = o
|
|
593
|
+
alignment = null
|
|
594
|
+
snapTarget = view
|
|
595
|
+
} else if (a != null) {
|
|
596
|
+
alignment = a
|
|
597
|
+
offset = null
|
|
586
598
|
snapTarget = view
|
|
587
599
|
}
|
|
588
600
|
}
|
|
589
601
|
val parent = view.parent
|
|
590
602
|
view = if (parent is View) parent else null
|
|
591
603
|
}
|
|
592
|
-
return if (
|
|
604
|
+
return if (snapTarget != null && (alignment != null || offset != null))
|
|
605
|
+
Triple(snapTarget, alignment, offset) else null
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
/**
|
|
609
|
+
* Computes the target scroll offset for the per-item scrollSnapOffset path:
|
|
610
|
+
* land the snap target's leading edge at `snapOffset` pixels from the viewport origin.
|
|
611
|
+
*
|
|
612
|
+
* Returns the clamped target offset.
|
|
613
|
+
*
|
|
614
|
+
* Shared by [ReactScrollView] and [ReactHorizontalScrollView].
|
|
615
|
+
*
|
|
616
|
+
* @param focusedStart the start coordinate of the snap target in scroll view coordinates
|
|
617
|
+
* @param snapOffset the per-item pixel offset from the viewport origin
|
|
618
|
+
* @param snapInterval the snap interval (0 if not set)
|
|
619
|
+
* @param maxScrollOffset the maximum scroll offset for clamping
|
|
620
|
+
*/
|
|
621
|
+
@JvmStatic
|
|
622
|
+
public fun computeScrollSnapTargetForOffset(
|
|
623
|
+
focusedStart: Int,
|
|
624
|
+
snapOffset: Int,
|
|
625
|
+
snapInterval: Int,
|
|
626
|
+
maxScrollOffset: Int,
|
|
627
|
+
): Int {
|
|
628
|
+
var targetOffset = focusedStart - snapOffset
|
|
629
|
+
if (snapInterval > 0) {
|
|
630
|
+
targetOffset = (Math.floor(targetOffset.toDouble() / snapInterval) * snapInterval).toInt()
|
|
631
|
+
}
|
|
632
|
+
return Math.max(0, Math.min(targetOffset, maxScrollOffset))
|
|
593
633
|
}
|
|
594
634
|
|
|
595
635
|
/**
|
|
@@ -174,6 +174,7 @@ public open class ReactViewGroup public constructor(context: Context?) :
|
|
|
174
174
|
private var onInterceptTouchEventListener: OnInterceptTouchEventListener? = null
|
|
175
175
|
private var needsOffscreenAlphaCompositing = false
|
|
176
176
|
public var scrollSnapAlign: String? = null
|
|
177
|
+
public var scrollSnapOffset: Int? = null
|
|
177
178
|
private var backfaceOpacity = 0f
|
|
178
179
|
private var backfaceVisible = false
|
|
179
180
|
private var childrenRemovedWhileTransitioning: MutableSet<Int>? = null
|
|
@@ -195,6 +195,11 @@ public open class ReactViewManager : ReactClippingViewManager<ReactViewGroup>()
|
|
|
195
195
|
view.scrollSnapAlign = value
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
+
@ReactProp(name = "scrollSnapOffset", defaultFloat = Float.NaN)
|
|
199
|
+
public open fun setScrollSnapOffset(view: ReactViewGroup, value: Float) {
|
|
200
|
+
view.scrollSnapOffset = if (value.isNaN()) null else value.dpToPx().toInt()
|
|
201
|
+
}
|
|
202
|
+
|
|
198
203
|
@ReactProp(name = ViewProps.BACKGROUND_IMAGE, customType = "BackgroundImage")
|
|
199
204
|
public open fun setBackgroundImage(view: ReactViewGroup, backgroundImage: ReadableArray?) {
|
|
200
205
|
if (ViewUtil.getUIManagerType(view) == UIManagerType.FABRIC) {
|
|
@@ -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
|
|
@@ -212,6 +221,7 @@ void HostPlatformViewProps::setProp(
|
|
|
212
221
|
RAW_SET_PROP_SWITCH_CASE_BASIC(focusable);
|
|
213
222
|
RAW_SET_PROP_SWITCH_CASE_BASIC(hasTVPreferredFocus);
|
|
214
223
|
RAW_SET_PROP_SWITCH_CASE_BASIC(scrollSnapAlign);
|
|
224
|
+
RAW_SET_PROP_SWITCH_CASE_BASIC(scrollSnapOffset);
|
|
215
225
|
RAW_SET_PROP_SWITCH_CASE_BASIC(needsOffscreenAlphaCompositing);
|
|
216
226
|
RAW_SET_PROP_SWITCH_CASE_BASIC(renderToHardwareTextureAndroid);
|
|
217
227
|
RAW_SET_PROP_SWITCH_CASE_BASIC(screenReaderFocusable);
|
|
@@ -1065,6 +1075,14 @@ folly::dynamic HostPlatformViewProps::getDiffProps(
|
|
|
1065
1075
|
}
|
|
1066
1076
|
}
|
|
1067
1077
|
|
|
1078
|
+
if (scrollSnapOffset != oldProps->scrollSnapOffset) {
|
|
1079
|
+
if (scrollSnapOffset.has_value()) {
|
|
1080
|
+
result["scrollSnapOffset"] = scrollSnapOffset.value();
|
|
1081
|
+
} else {
|
|
1082
|
+
result["scrollSnapOffset"] = folly::dynamic(nullptr);
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1068
1086
|
return result;
|
|
1069
1087
|
}
|
|
1070
1088
|
|
|
@@ -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.85.3-
|
|
3
|
+
"version": "0.85.3-2",
|
|
4
4
|
"description": "A framework for building native apps using React",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -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.85.3-
|
|
201
|
+
"@react-native-tvos/virtualized-lists": "0.85.3-2"
|
|
202
202
|
},
|
|
203
203
|
"codegenConfig": {
|
|
204
204
|
"libraries": [
|
|
@@ -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
|
*/
|