@@ -45,28 +45,55 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
4545 label,
4646 limit,
4747 LinearGradient,
48- numberOfItems ,
48+ maximumValue ,
4949 onDurationChange,
5050 padNumbersWithZero = false ,
5151 padWithNItems,
5252 pickerFeedback,
5353 pickerGradientOverlayProps,
5454 pmLabel,
5555 repeatNumbersNTimes = 3 ,
56+ repeatNumbersNTimesNotExplicitlySet,
5657 styles,
5758 testID,
5859 topPickerGradientOverlayProps,
5960 } = props ;
6061
62+ const numberOfItems = useMemo ( ( ) => {
63+ // guard against negative maximum values
64+ if ( maximumValue < 0 ) {
65+ return 1 ;
66+ }
67+
68+ return maximumValue + 1 ;
69+ } , [ maximumValue ] ) ;
70+
6171 const safeRepeatNumbersNTimes = useMemo ( ( ) => {
72+ // do not repeat numbers if there is only one option
73+ if ( numberOfItems === 1 ) {
74+ return 1 ;
75+ }
76+
6277 if ( ! disableInfiniteScroll && repeatNumbersNTimes < 2 ) {
6378 return 2 ;
6479 } else if ( repeatNumbersNTimes < 1 ) {
6580 return 1 ;
6681 }
6782
83+ // if this variable is not explicitly set, we calculate a reasonable value based on
84+ // the number of items in the picker, avoiding regular jumps up/down the list
85+ // whilst avoiding rendering too many items in the picker
86+ if ( repeatNumbersNTimesNotExplicitlySet ) {
87+ return Math . max ( Math . round ( 180 / numberOfItems ) , 1 ) ;
88+ }
89+
6890 return Math . round ( repeatNumbersNTimes ) ;
69- } , [ disableInfiniteScroll , repeatNumbersNTimes ] ) ;
91+ } , [
92+ disableInfiniteScroll ,
93+ numberOfItems ,
94+ repeatNumbersNTimes ,
95+ repeatNumbersNTimesNotExplicitlySet ,
96+ ] ) ;
7097
7198 const numbersForFlatList = useMemo ( ( ) => {
7299 if ( is12HourPicker ) {
@@ -356,6 +383,10 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
356383
357384 const onViewableItemsChanged = useCallback (
358385 ( { viewableItems } : { viewableItems : ViewToken [ ] } ) => {
386+ if ( numberOfItems === 1 ) {
387+ return ;
388+ }
389+
359390 if (
360391 viewableItems [ 0 ] ?. index &&
361392 viewableItems [ 0 ] . index < numberOfItems * 0.5
@@ -378,6 +409,50 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
378409 [ numberOfItems , safeRepeatNumbersNTimes ]
379410 ) ;
380411
412+ const [
413+ viewabilityConfigCallbackPairs ,
414+ setViewabilityConfigCallbackPairs ,
415+ ] = useState < ViewabilityConfigCallbackPairs | undefined > (
416+ ! disableInfiniteScroll
417+ ? [
418+ {
419+ viewabilityConfig : {
420+ viewAreaCoveragePercentThreshold : 0 ,
421+ } ,
422+ onViewableItemsChanged : onViewableItemsChanged ,
423+ } ,
424+ ]
425+ : undefined
426+ ) ;
427+
428+ const [ flatListRenderKey , setFlatListRenderKey ] = useState ( 0 ) ;
429+
430+ const initialRender = useRef ( true ) ;
431+
432+ useEffect ( ( ) => {
433+ // don't run on first render
434+ if ( initialRender . current ) {
435+ initialRender . current = false ;
436+ return ;
437+ }
438+
439+ // if the onViewableItemsChanged callback changes, we need to update viewabilityConfigCallbackPairs
440+ // which requires the FlatList to be remounted, hence the increase of the FlatList key
441+ setFlatListRenderKey ( ( prev ) => prev + 1 ) ;
442+ setViewabilityConfigCallbackPairs (
443+ ! disableInfiniteScroll
444+ ? [
445+ {
446+ viewabilityConfig : {
447+ viewAreaCoveragePercentThreshold : 0 ,
448+ } ,
449+ onViewableItemsChanged : onViewableItemsChanged ,
450+ } ,
451+ ]
452+ : undefined
453+ ) ;
454+ } , [ disableInfiniteScroll , onViewableItemsChanged ] ) ;
455+
381456 const getItemLayout = useCallback (
382457 ( _ : ArrayLike < string > | null | undefined , index : number ) => ( {
383458 length : styles . pickerItemContainer . height ,
@@ -387,14 +462,6 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
387462 [ styles . pickerItemContainer . height ]
388463 ) ;
389464
390- const viewabilityConfigCallbackPairs =
391- useRef < ViewabilityConfigCallbackPairs > ( [
392- {
393- viewabilityConfig : { viewAreaCoveragePercentThreshold : 0 } ,
394- onViewableItemsChanged : onViewableItemsChanged ,
395- } ,
396- ] ) ;
397-
398465 useImperativeHandle ( ref , ( ) => ( {
399466 reset : ( options ) => {
400467 flatListRef . current ?. scrollToIndex ( {
@@ -431,6 +498,7 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
431498 ] }
432499 testID = { testID } >
433500 < FlatList
501+ key = { flatListRenderKey }
434502 ref = { flatListRef }
435503 data = { numbersForFlatList }
436504 decelerationRate = { 0.88 }
@@ -445,15 +513,13 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
445513 scrollEventThrottle = { 16 }
446514 showsVerticalScrollIndicator = { false }
447515 snapToAlignment = "start"
448- // used in place of snapToOffset due to bug on Android
516+ // used in place of snapToInterval due to bug on Android
449517 snapToOffsets = { [ ...Array ( numbersForFlatList . length ) ] . map (
450518 ( _ , i ) => i * styles . pickerItemContainer . height
451519 ) }
452520 testID = "duration-scroll-flatlist"
453521 viewabilityConfigCallbackPairs = {
454- ! disableInfiniteScroll
455- ? viewabilityConfigCallbackPairs ?. current
456- : undefined
522+ viewabilityConfigCallbackPairs
457523 }
458524 windowSize = { numberOfItemsToShow }
459525 />
0 commit comments