1
- import React , { useCallback , RefObject , useRef , ReactElement , ReactNode , useMemo } from 'react' ;
1
+ import React , {
2
+ useCallback ,
3
+ RefObject ,
4
+ useRef ,
5
+ ReactElement ,
6
+ ReactNode ,
7
+ useMemo ,
8
+ forwardRef ,
9
+ } from 'react' ;
2
10
import {
3
11
ScrollView ,
4
12
View ,
@@ -14,6 +22,7 @@ import {
14
22
} from '../context/ParentScrollContext' ;
15
23
import { scrollToNewlyFocusedElement } from '../helpers/scrollToNewlyfocusedElement' ;
16
24
import { useSpatialNavigationDeviceType } from '../context/DeviceContext' ;
25
+ import { mergeRefs } from '../helpers/mergeRefs' ;
17
26
18
27
type Props = {
19
28
horizontal ?: boolean ;
@@ -122,85 +131,91 @@ const getNodeRef = (node: ScrollView | null | undefined) => {
122
131
return node ;
123
132
} ;
124
133
125
- export const SpatialNavigationScrollView = ( {
126
- horizontal = false ,
127
- style,
128
- offsetFromStart = 0 ,
129
- children,
130
- ascendingArrow,
131
- ascendingArrowContainerStyle,
132
- descendingArrow,
133
- descendingArrowContainerStyle,
134
- pointerScrollSpeed = 10 ,
135
- } : Props ) => {
136
- const { scrollToNodeIfNeeded : makeParentsScrollToNodeIfNeeded } =
137
- useSpatialNavigatorParentScroll ( ) ;
138
- const scrollViewRef = useRef < ScrollView > ( null ) ;
139
-
140
- const scrollY = useRef < number > ( 0 ) ;
141
-
142
- const { ascendingArrowProps, descendingArrowProps, deviceType, deviceTypeRef } =
143
- useRemotePointerScrollviewScrollProps ( { pointerScrollSpeed, scrollY, scrollViewRef } ) ;
144
-
145
- const scrollToNode = useCallback (
146
- ( newlyFocusedElementRef : RefObject < View > , additionalOffset = 0 ) => {
147
- try {
148
- if ( deviceTypeRef . current === 'remoteKeys' ) {
149
- newlyFocusedElementRef ?. current ?. measureLayout (
150
- getNodeRef ( scrollViewRef ?. current ) ,
151
- ( left , top ) =>
152
- scrollToNewlyFocusedElement ( {
153
- newlyFocusedElementDistanceToLeftRelativeToLayout : left ,
154
- newlyFocusedElementDistanceToTopRelativeToLayout : top ,
155
- horizontal,
156
- offsetFromStart : offsetFromStart + additionalOffset ,
157
- scrollViewRef,
158
- } ) ,
159
- ( ) => { } ,
160
- ) ;
161
- }
162
- } catch {
163
- // A crash can happen when calling measureLayout when a page unmounts. No impact on focus detected in regular use cases.
164
- }
165
- makeParentsScrollToNodeIfNeeded ( newlyFocusedElementRef , additionalOffset ) ; // We need to propagate the scroll event for parents if we have nested ScrollViews/VirtualizedLists.
134
+ export const SpatialNavigationScrollView = forwardRef < ScrollView , Props > (
135
+ (
136
+ {
137
+ horizontal = false ,
138
+ style,
139
+ offsetFromStart = 0 ,
140
+ children,
141
+ ascendingArrow,
142
+ ascendingArrowContainerStyle,
143
+ descendingArrow,
144
+ descendingArrowContainerStyle,
145
+ pointerScrollSpeed = 10 ,
166
146
} ,
167
- [ makeParentsScrollToNodeIfNeeded , horizontal , offsetFromStart , deviceTypeRef ] ,
168
- ) ;
147
+ ref ,
148
+ ) => {
149
+ const { scrollToNodeIfNeeded : makeParentsScrollToNodeIfNeeded } =
150
+ useSpatialNavigatorParentScroll ( ) ;
151
+ const scrollViewRef = useRef < ScrollView > ( null ) ;
169
152
170
- const onScroll = useCallback (
171
- ( event : NativeSyntheticEvent < NativeScrollEvent > ) => {
172
- scrollY . current = event . nativeEvent . contentOffset . y ;
173
- } ,
174
- [ scrollY ] ,
175
- ) ;
153
+ const scrollY = useRef < number > ( 0 ) ;
176
154
177
- return (
178
- < SpatialNavigatorParentScrollContext . Provider value = { scrollToNode } >
179
- < ScrollView
180
- ref = { scrollViewRef }
181
- horizontal = { horizontal }
182
- style = { style }
183
- showsHorizontalScrollIndicator = { false }
184
- showsVerticalScrollIndicator = { false }
185
- scrollEnabled = { false }
186
- onScroll = { onScroll }
187
- scrollEventThrottle = { 16 }
188
- >
189
- { children }
190
- </ ScrollView >
191
- { deviceType === 'remotePointer' ? (
192
- < PointerScrollArrows
193
- descendingArrow = { descendingArrow }
194
- ascendingArrow = { ascendingArrow }
195
- descendingArrowContainerStyle = { descendingArrowContainerStyle }
196
- ascendingArrowContainerStyle = { ascendingArrowContainerStyle }
197
- ascendingArrowProps = { ascendingArrowProps }
198
- descendingArrowProps = { descendingArrowProps }
199
- />
200
- ) : undefined }
201
- </ SpatialNavigatorParentScrollContext . Provider >
202
- ) ;
203
- } ;
155
+ const { ascendingArrowProps, descendingArrowProps, deviceType, deviceTypeRef } =
156
+ useRemotePointerScrollviewScrollProps ( { pointerScrollSpeed, scrollY, scrollViewRef } ) ;
157
+
158
+ const scrollToNode = useCallback (
159
+ ( newlyFocusedElementRef : RefObject < View > , additionalOffset = 0 ) => {
160
+ try {
161
+ if ( deviceTypeRef . current === 'remoteKeys' ) {
162
+ newlyFocusedElementRef ?. current ?. measureLayout (
163
+ getNodeRef ( scrollViewRef ?. current ) ,
164
+ ( left , top ) =>
165
+ scrollToNewlyFocusedElement ( {
166
+ newlyFocusedElementDistanceToLeftRelativeToLayout : left ,
167
+ newlyFocusedElementDistanceToTopRelativeToLayout : top ,
168
+ horizontal,
169
+ offsetFromStart : offsetFromStart + additionalOffset ,
170
+ scrollViewRef,
171
+ } ) ,
172
+ ( ) => { } ,
173
+ ) ;
174
+ }
175
+ } catch {
176
+ // A crash can happen when calling measureLayout when a page unmounts. No impact on focus detected in regular use cases.
177
+ }
178
+ makeParentsScrollToNodeIfNeeded ( newlyFocusedElementRef , additionalOffset ) ; // We need to propagate the scroll event for parents if we have nested ScrollViews/VirtualizedLists.
179
+ } ,
180
+ [ makeParentsScrollToNodeIfNeeded , horizontal , offsetFromStart , deviceTypeRef ] ,
181
+ ) ;
182
+
183
+ const onScroll = useCallback (
184
+ ( event : NativeSyntheticEvent < NativeScrollEvent > ) => {
185
+ scrollY . current = event . nativeEvent . contentOffset . y ;
186
+ } ,
187
+ [ scrollY ] ,
188
+ ) ;
189
+
190
+ return (
191
+ < SpatialNavigatorParentScrollContext . Provider value = { scrollToNode } >
192
+ < ScrollView
193
+ ref = { mergeRefs ( [ ref , scrollViewRef ] ) }
194
+ horizontal = { horizontal }
195
+ style = { style }
196
+ showsHorizontalScrollIndicator = { false }
197
+ showsVerticalScrollIndicator = { false }
198
+ scrollEnabled = { false }
199
+ onScroll = { onScroll }
200
+ scrollEventThrottle = { 16 }
201
+ >
202
+ { children }
203
+ </ ScrollView >
204
+ { deviceType === 'remotePointer' ? (
205
+ < PointerScrollArrows
206
+ descendingArrow = { descendingArrow }
207
+ ascendingArrow = { ascendingArrow }
208
+ descendingArrowContainerStyle = { descendingArrowContainerStyle }
209
+ ascendingArrowContainerStyle = { ascendingArrowContainerStyle }
210
+ ascendingArrowProps = { ascendingArrowProps }
211
+ descendingArrowProps = { descendingArrowProps }
212
+ />
213
+ ) : undefined }
214
+ </ SpatialNavigatorParentScrollContext . Provider >
215
+ ) ;
216
+ } ,
217
+ ) ;
218
+ SpatialNavigationScrollView . displayName = 'SpatialNavigationScrollView' ;
204
219
205
220
const PointerScrollArrows = React . memo (
206
221
( {
0 commit comments