1- import React , {
2- forwardRef ,
3- useRef ,
4- useLayoutEffect
5- } from 'react' ;
1+ import React , { forwardRef , useRef , useLayoutEffect } from 'react' ;
62import {
7- findNodeHandle ,
8- requireNativeComponent ,
9- StyleSheet ,
10- UIManager ,
11- type StyleProp ,
12- type ViewStyle ,
13- type LayoutChangeEvent ,
14- type ScrollViewProps ,
15- type ViewProps
3+ findNodeHandle ,
4+ requireNativeComponent ,
5+ StyleSheet ,
6+ UIManager ,
7+ type StyleProp ,
8+ type ViewStyle ,
9+ type LayoutChangeEvent ,
10+ type ScrollViewProps ,
11+ type ViewProps
1612} from 'react-native' ;
1713
1814import { isAndroid } from '../../../../lib/methods/helpers' ;
1915
20-
2116const COMMAND_SCROLL_TO = 1 ;
2217const COMMAND_SCROLL_TO_END = 2 ;
2318const COMMAND_FLASH_SCROLL_INDICATORS = 3 ;
@@ -31,11 +26,11 @@ type NativeScrollInstance = React.ComponentRef<NonNullable<typeof NativeInverted
3126
3227// Interface for the methods we are dynamically attaching
3328interface ScrollableMethods {
34- scrollTo ( options ?: { x ?: number ; y ?: number ; animated ?: boolean } ) : void ;
35- scrollToEnd ( options ?: { animated ?: boolean } ) : void ;
36- flashScrollIndicators ( ) : void ;
37- getScrollRef ( ) : NativeScrollInstance | null ;
38- setNativeProps ( props : object ) : void ;
29+ scrollTo ( options ?: { x ?: number ; y ?: number ; animated ?: boolean } ) : void ;
30+ scrollToEnd ( options ?: { animated ?: boolean } ) : void ;
31+ flashScrollIndicators ( ) : void ;
32+ getScrollRef ( ) : NativeScrollInstance | null ;
33+ setNativeProps ( props : object ) : void ;
3934}
4035
4136// The final Ref type exposed to consumers (Intersection of Native + Custom)
@@ -48,109 +43,109 @@ export type InvertedScrollViewRef = NativeScrollInstance & ScrollableMethods;
4843const NativeInvertedScrollView = isAndroid ? requireNativeComponent < ScrollViewProps > ( 'InvertedScrollView' ) : null ;
4944
5045const NativeInvertedScrollContentView = isAndroid
51- ? requireNativeComponent < ViewProps & { removeClippedSubviews ?: boolean } > ( 'InvertedScrollContentView' )
52- : null ;
53-
46+ ? requireNativeComponent < ViewProps & { removeClippedSubviews ?: boolean } > ( 'InvertedScrollContentView' )
47+ : null ;
5448
5549const InvertedScrollView = forwardRef < InvertedScrollViewRef , ScrollViewProps > ( ( props , externalRef ) => {
56- const internalRef = useRef < NativeScrollInstance | null > ( null ) ;
57- // internalRef captures the Host Instance for local manipulation
58-
59- // useLayoutEffect runs synchronously after DOM mutations.
60- // This guarantees methods are attached before the parent can access the ref.
61- useLayoutEffect ( ( ) => {
62- const node = internalRef . current ;
63-
64- if ( node ) {
65- // Cast to 'any' to allow dynamic property assignment
66- const patchedNode = node as any ;
67-
68- // 1. Implementation of scrollTo
69- patchedNode . scrollTo = ( options ?: { x ?: number ; y ?: number ; animated ?: boolean } ) => {
70- const tag = findNodeHandle ( node ) ;
71- if ( tag != null ) {
72- const x = options ?. x || 0 ;
73- const y = options ?. y || 0 ;
74- const animated = options ?. animated !== false ;
75- UIManager . dispatchViewManagerCommand ( tag , COMMAND_SCROLL_TO , [ x , y , animated ] ) ;
76- }
77- } ;
78-
79- // 2. Implementation of scrollToEnd
80- patchedNode . scrollToEnd = ( options ?: { animated ?: boolean } ) => {
81- const tag = findNodeHandle ( node ) ;
82- if ( tag != null ) {
83- const animated = options ?. animated !== false ;
84- UIManager . dispatchViewManagerCommand ( tag , COMMAND_SCROLL_TO_END , [ animated ] ) ;
85- }
86- } ;
87-
88- // 3. Implementation of flashScrollIndicators
89- patchedNode . flashScrollIndicators = ( ) => {
90- const tag = findNodeHandle ( node as any ) ;
91- if ( tag !== null ) {
92- UIManager . dispatchViewManagerCommand ( tag , COMMAND_FLASH_SCROLL_INDICATORS , [ ] ) ;
93- }
94- } ;
95-
96- // 4. Implementation of getScrollRef (Legacy support)
97- patchedNode . getScrollRef = ( ) => node ;
98-
99- // 5. Ensure setNativeProps exists (it usually does on Host Instances, but explicit is safer)
100- if ( typeof patchedNode . setNativeProps !== 'function' ) {
101- patchedNode . setNativeProps = ( nativeProps : object ) => {
102- // Check again if the underlying node has the method hidden
103- if ( node && typeof ( node as any ) . setNativeProps === 'function' ) {
104- ( node as any ) . setNativeProps ( nativeProps ) ;
105- }
106- } ;
107- }
108- }
109- } ) ;
110-
111- // Callback Ref to handle merging internal and external refs
112- const setRef = ( node : NativeScrollInstance | null ) => {
113- internalRef . current = node ;
114-
115- if ( typeof externalRef === 'function' ) {
116- externalRef ( node as InvertedScrollViewRef ) ;
117- } else if ( externalRef ) {
118- ( externalRef as React . MutableRefObject < NativeScrollInstance | null > ) . current = node ;
119- }
120- } ;
121-
122- // Extract specific props that require transformation
123- const {
124- children,
125- contentContainerStyle,
126- onContentSizeChange,
127- removeClippedSubviews,
128- maintainVisibleContentPosition,
129- snapToAlignment,
130- stickyHeaderIndices,
131- ...rest
132- } = props ;
133-
134- // Logic preserved from original class component
135- const preserveChildren = maintainVisibleContentPosition != null || ( isAndroid && snapToAlignment != null ) ;
136- const hasStickyHeaders = Array . isArray ( stickyHeaderIndices ) && stickyHeaderIndices . length > 0 ;
137-
138- // Construct styles
139- const contentContainerStyleArray = [ props . horizontal ? { flexDirection : 'row' as const } : null , contentContainerStyle ] ;
140-
141- // Wrap onContentSizeChange to normalize the event payload
142- const contentSizeChangeProps = onContentSizeChange == null
143- ? undefined
144- : {
145- onLayout : ( e : LayoutChangeEvent ) => {
146- const { width, height } = e . nativeEvent . layout ;
147- onContentSizeChange ( width , height ) ;
148- }
149- } ;
150-
151- const horizontal = ! ! props . horizontal ;
152- const baseStyle = horizontal ? styles . baseHorizontal : styles . baseVertical ;
153- const { style, ...restWithoutStyle } = rest ;
50+ const internalRef = useRef < NativeScrollInstance | null > ( null ) ;
51+ // internalRef captures the Host Instance for local manipulation
52+
53+ // useLayoutEffect runs synchronously after DOM mutations.
54+ // This guarantees methods are attached before the parent can access the ref.
55+ useLayoutEffect ( ( ) => {
56+ const node = internalRef . current ;
57+
58+ if ( node ) {
59+ // Cast to 'any' to allow dynamic property assignment
60+ const patchedNode = node as any ;
61+
62+ // 1. Implementation of scrollTo
63+ patchedNode . scrollTo = ( options ?: { x ?: number ; y ?: number ; animated ?: boolean } ) => {
64+ const tag = findNodeHandle ( node ) ;
65+ if ( tag != null ) {
66+ const x = options ?. x || 0 ;
67+ const y = options ?. y || 0 ;
68+ const animated = options ?. animated !== false ;
69+ UIManager . dispatchViewManagerCommand ( tag , COMMAND_SCROLL_TO , [ x , y , animated ] ) ;
70+ }
71+ } ;
72+
73+ // 2. Implementation of scrollToEnd
74+ patchedNode . scrollToEnd = ( options ?: { animated ?: boolean } ) => {
75+ const tag = findNodeHandle ( node ) ;
76+ if ( tag != null ) {
77+ const animated = options ?. animated !== false ;
78+ UIManager . dispatchViewManagerCommand ( tag , COMMAND_SCROLL_TO_END , [ animated ] ) ;
79+ }
80+ } ;
81+
82+ // 3. Implementation of flashScrollIndicators
83+ patchedNode . flashScrollIndicators = ( ) => {
84+ const tag = findNodeHandle ( node as any ) ;
85+ if ( tag !== null ) {
86+ UIManager . dispatchViewManagerCommand ( tag , COMMAND_FLASH_SCROLL_INDICATORS , [ ] ) ;
87+ }
88+ } ;
89+
90+ // 4. Implementation of getScrollRef (Legacy support)
91+ patchedNode . getScrollRef = ( ) => node ;
92+
93+ // 5. Ensure setNativeProps exists (it usually does on Host Instances, but explicit is safer)
94+ if ( typeof patchedNode . setNativeProps !== 'function' ) {
95+ patchedNode . setNativeProps = ( nativeProps : object ) => {
96+ // Check again if the underlying node has the method hidden
97+ if ( node && typeof ( node as any ) . setNativeProps === 'function' ) {
98+ ( node as any ) . setNativeProps ( nativeProps ) ;
99+ }
100+ } ;
101+ }
102+ }
103+ } ) ;
104+
105+ // Callback Ref to handle merging internal and external refs
106+ const setRef = ( node : NativeScrollInstance | null ) => {
107+ internalRef . current = node ;
108+
109+ if ( typeof externalRef === 'function' ) {
110+ externalRef ( node as InvertedScrollViewRef ) ;
111+ } else if ( externalRef ) {
112+ ( externalRef as React . MutableRefObject < NativeScrollInstance | null > ) . current = node ;
113+ }
114+ } ;
115+
116+ // Extract specific props that require transformation
117+ const {
118+ children,
119+ contentContainerStyle,
120+ onContentSizeChange,
121+ removeClippedSubviews,
122+ maintainVisibleContentPosition,
123+ snapToAlignment,
124+ stickyHeaderIndices,
125+ ...rest
126+ } = props ;
127+
128+ // Logic preserved from original class component
129+ const preserveChildren = maintainVisibleContentPosition != null || ( isAndroid && snapToAlignment != null ) ;
130+ const hasStickyHeaders = Array . isArray ( stickyHeaderIndices ) && stickyHeaderIndices . length > 0 ;
131+
132+ // Construct styles
133+ const contentContainerStyleArray = [ props . horizontal ? { flexDirection : 'row' as const } : null , contentContainerStyle ] ;
134+
135+ // Wrap onContentSizeChange to normalize the event payload
136+ const contentSizeChangeProps =
137+ onContentSizeChange == null
138+ ? undefined
139+ : {
140+ onLayout : ( e : LayoutChangeEvent ) => {
141+ const { width, height } = e . nativeEvent . layout ;
142+ onContentSizeChange ( width , height ) ;
143+ }
144+ } ;
145+
146+ const horizontal = ! ! props . horizontal ;
147+ const baseStyle = horizontal ? styles . baseHorizontal : styles . baseVertical ;
148+ const { style, ...restWithoutStyle } = rest ;
154149
155150 if ( ! NativeInvertedScrollView || ! NativeInvertedScrollContentView ) {
156151 return null ;
@@ -160,19 +155,13 @@ const InvertedScrollView = forwardRef<InvertedScrollViewRef, ScrollViewProps>((p
160155 const ContentView = NativeInvertedScrollContentView as React . ComponentType < ViewProps & { removeClippedSubviews ?: boolean } > ;
161156
162157 return (
163- < ScrollView
164- ref = { setRef }
165- { ...restWithoutStyle }
166- style = { StyleSheet . compose ( baseStyle , style ) }
167- horizontal = { horizontal }
168- >
158+ < ScrollView ref = { setRef } { ...restWithoutStyle } style = { StyleSheet . compose ( baseStyle , style ) } horizontal = { horizontal } >
169159 < ContentView
170160 { ...contentSizeChangeProps }
171161 removeClippedSubviews = { isAndroid && hasStickyHeaders ? false : removeClippedSubviews }
172162 collapsable = { false }
173163 collapsableChildren = { ! preserveChildren }
174- style = { contentContainerStyleArray as StyleProp < ViewStyle > }
175- >
164+ style = { contentContainerStyleArray as StyleProp < ViewStyle > } >
176165 { children }
177166 </ ContentView >
178167 </ ScrollView >
@@ -182,18 +171,18 @@ const InvertedScrollView = forwardRef<InvertedScrollViewRef, ScrollViewProps>((p
182171InvertedScrollView . displayName = 'InvertedScrollView' ;
183172
184173const styles = StyleSheet . create ( {
185- baseVertical : {
186- flexGrow : 1 ,
187- flexShrink : 1 ,
188- flexDirection : 'column' ,
189- overflow : 'scroll'
190- } ,
191- baseHorizontal : {
192- flexGrow : 1 ,
193- flexShrink : 1 ,
194- flexDirection : 'row' ,
195- overflow : 'scroll'
196- }
174+ baseVertical : {
175+ flexGrow : 1 ,
176+ flexShrink : 1 ,
177+ flexDirection : 'column' ,
178+ overflow : 'scroll'
179+ } ,
180+ baseHorizontal : {
181+ flexGrow : 1 ,
182+ flexShrink : 1 ,
183+ flexDirection : 'row' ,
184+ overflow : 'scroll'
185+ }
197186} ) ;
198187
199- export default InvertedScrollView ;
188+ export default InvertedScrollView ;
0 commit comments