Skip to content

Commit 49dd757

Browse files
committed
Merge branch 'patch/v1.10.3'
2 parents dd5168d + a3ada93 commit 49dd757

File tree

10 files changed

+162
-111
lines changed

10 files changed

+162
-111
lines changed

README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -449,7 +449,7 @@ return (
449449
| use12HourPicker | Switch the hour picker to 12-hour format with an AM / PM label | Boolean | false | false |
450450
| amLabel | Set the AM label if using the 12-hour picker | String | am | false |
451451
| pmLabel | Set the PM label if using the 12-hour picker | String | pm | false |
452-
| repeatHourNumbersNTimes | Set the number of times the list of hours is repeated in the picker | Number | 6 | false |
452+
| repeatHourNumbersNTimes | Set the number of times the list of hours is repeated in the picker | Number | 7 | false |
453453
| repeatMinuteNumbersNTimes | Set the number of times the list of minutes is repeated in the picker | Number | 3 | false |
454454
| repeatSecondNumbersNTimes | Set the number of times the list of seconds is repeated in the picker | Number | 3 | false |
455455
| disableInfiniteScroll | Disable the infinite scroll feature | Boolean | false | false |
@@ -491,7 +491,7 @@ Note the minor limitations to the allowed styles for `pickerContainer` and `pick
491491

492492
When the `disableInfiniteScroll` prop is not set, the picker gives the appearance of an infinitely scrolling picker by auto-scrolling forward/back when you near the start/end of the list. When the picker auto-scrolls, a momentary flicker is visible if you are scrolling very slowly.
493493

494-
To mitigate for this, you can modify the `repeatHourNumbersNTimes`, `repeatMinuteNumbersNTimes` and `repeatSecondNumbersNTimes` props. These set the number of times the list of numbers in each picker is repeated. These have a performance trade-off: higher values mean the picker has to auto-scroll less to maintain the infinite scroll, but has to render a longer list of numbers. By default, the props are set to 6, 3 and 3, respectively, which balances that trade-off effectively.
494+
To mitigate for this, you can modify the `repeatHourNumbersNTimes`, `repeatMinuteNumbersNTimes` and `repeatSecondNumbersNTimes` props. These set the number of times the list of numbers in each picker is repeated. These have a performance trade-off: higher values mean the picker has to auto-scroll less to maintain the infinite scroll, but has to render a longer list of numbers. By default, the props are set to 7, 3 and 3, respectively, which balances that trade-off effectively.
495495

496496
Note that you can avoid the auto-scroll flickering entirely by disabling infinite scroll. You could then set the above props to high values, so that a user has to scroll far down/up the list to reach the end of the list.
497497

example/App.tsx

-4
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,6 @@ export default function App() {
107107
<TimerPickerModal
108108
Audio={Audio}
109109
closeOnOverlayPress
110-
disableInfiniteScroll
111110
Haptics={Haptics}
112111
LinearGradient={LinearGradient}
113112
modalProps={{
@@ -119,9 +118,6 @@ export default function App() {
119118
setAlarmStringExample1(formatTime(pickedDuration));
120119
setShowPickerExample1(false);
121120
}}
122-
repeatHourNumbersNTimes={1}
123-
repeatMinuteNumbersNTimes={1}
124-
repeatSecondNumbersNTimes={1}
125121
setIsVisible={setShowPickerExample1}
126122
styles={{
127123
theme: "dark",

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"url": "https://github.com/troberts-28"
77
},
88
"license": "MIT",
9-
"version": "1.10.2",
9+
"version": "1.10.3",
1010
"main": "dist/commonjs/index.js",
1111
"module": "dist/module/index.js",
1212
"types": "dist/typescript/index.d.ts",

src/components/DurationScroll/index.tsx

+80-67
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ import {
2222
generateNumbers,
2323
} from "../../utils/generateNumbers";
2424
import { getAdjustedLimit } from "../../utils/getAdjustedLimit";
25-
import { getScrollIndex } from "../../utils/getScrollIndex";
25+
import { getDurationAndIndexFromScrollOffset } from "../../utils/getDurationAndIndexFromScrollOffset";
26+
import { getInitialScrollIndex } from "../../utils/getInitialScrollIndex";
2627

2728
import type { DurationScrollProps, DurationScrollRef } from "./types";
2829

@@ -56,19 +57,29 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
5657
topPickerGradientOverlayProps,
5758
} = props;
5859

59-
const data = useMemo(() => {
60+
const safeRepeatNumbersNTimes = useMemo(() => {
61+
if (!disableInfiniteScroll && repeatNumbersNTimes < 2) {
62+
return 2;
63+
} else if (repeatNumbersNTimes < 1) {
64+
return 1;
65+
}
66+
67+
return Math.round(repeatNumbersNTimes);
68+
}, [disableInfiniteScroll, repeatNumbersNTimes]);
69+
70+
const numbersForFlatList = useMemo(() => {
6071
if (is12HourPicker) {
6172
return generate12HourNumbers({
6273
padNumbersWithZero,
63-
repeatNTimes: repeatNumbersNTimes,
74+
repeatNTimes: safeRepeatNumbersNTimes,
6475
disableInfiniteScroll,
6576
padWithNItems,
6677
});
6778
}
6879

6980
return generateNumbers(numberOfItems, {
7081
padNumbersWithZero,
71-
repeatNTimes: repeatNumbersNTimes,
82+
repeatNTimes: safeRepeatNumbersNTimes,
7283
disableInfiniteScroll,
7384
padWithNItems,
7485
});
@@ -78,22 +89,24 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
7889
numberOfItems,
7990
padNumbersWithZero,
8091
padWithNItems,
81-
repeatNumbersNTimes,
92+
safeRepeatNumbersNTimes,
8293
]);
8394

8495
const initialScrollIndex = useMemo(
8596
() =>
86-
getScrollIndex({
97+
getInitialScrollIndex({
98+
disableInfiniteScroll,
8799
numberOfItems,
88100
padWithNItems,
89-
repeatNumbersNTimes,
101+
repeatNumbersNTimes: safeRepeatNumbersNTimes,
90102
value: initialValue,
91103
}),
92104
[
105+
disableInfiniteScroll,
93106
initialValue,
94107
numberOfItems,
95108
padWithNItems,
96-
repeatNumbersNTimes,
109+
safeRepeatNumbersNTimes,
97110
]
98111
);
99112

@@ -134,6 +147,7 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
134147
setClickSound(sound);
135148
}
136149
};
150+
137151
loadSound();
138152

139153
// Unload sound when component unmounts
@@ -143,27 +157,6 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
143157
// eslint-disable-next-line react-hooks/exhaustive-deps
144158
}, [Audio]);
145159

146-
useImperativeHandle(ref, () => ({
147-
reset: (options) => {
148-
flatListRef.current?.scrollToIndex({
149-
animated: options?.animated ?? false,
150-
index: initialScrollIndex,
151-
});
152-
},
153-
setValue: (value, options) => {
154-
flatListRef.current?.scrollToIndex({
155-
animated: options?.animated ?? false,
156-
index: getScrollIndex({
157-
numberOfItems,
158-
padWithNItems,
159-
repeatNumbersNTimes,
160-
value: value,
161-
}),
162-
});
163-
},
164-
latestDuration: latestDuration,
165-
}));
166-
167160
const renderItem = useCallback(
168161
({ item }: { item: string }) => {
169162
let stringItem = item;
@@ -233,25 +226,23 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
233226
}
234227

235228
if (aggressivelyGetLatestDuration) {
236-
const newIndex = Math.round(
237-
e.nativeEvent.contentOffset.y /
238-
styles.pickerItemContainer.height
239-
);
240-
let newDuration =
241-
(disableInfiniteScroll
242-
? newIndex
243-
: newIndex + padWithNItems) %
244-
(numberOfItems + 1);
229+
const newValues = getDurationAndIndexFromScrollOffset({
230+
disableInfiniteScroll,
231+
itemHeight: styles.pickerItemContainer.height,
232+
numberOfItems,
233+
padWithNItems,
234+
yContentOffset: e.nativeEvent.contentOffset.y,
235+
});
245236

246-
if (newDuration !== latestDuration.current) {
237+
if (newValues.duration !== latestDuration.current) {
247238
// check limits
248-
if (newDuration > adjustedLimited.max) {
249-
newDuration = adjustedLimited.max;
250-
} else if (newDuration < adjustedLimited.min) {
251-
newDuration = adjustedLimited.min;
239+
if (newValues.duration > adjustedLimited.max) {
240+
newValues.duration = adjustedLimited.max;
241+
} else if (newValues.duration < adjustedLimited.min) {
242+
newValues.duration = adjustedLimited.min;
252243
}
253244

254-
latestDuration.current = newDuration;
245+
latestDuration.current = newValues.duration;
255246
}
256247
}
257248

@@ -299,20 +290,19 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
299290

300291
const onMomentumScrollEnd = useCallback(
301292
(e: NativeSyntheticEvent<NativeScrollEvent>) => {
302-
const newIndex = Math.round(
303-
e.nativeEvent.contentOffset.y /
304-
styles.pickerItemContainer.height
305-
);
306-
let newDuration =
307-
(disableInfiniteScroll
308-
? newIndex
309-
: newIndex + padWithNItems) %
310-
(numberOfItems + 1);
293+
const newValues = getDurationAndIndexFromScrollOffset({
294+
disableInfiniteScroll,
295+
itemHeight: styles.pickerItemContainer.height,
296+
numberOfItems,
297+
padWithNItems,
298+
yContentOffset: e.nativeEvent.contentOffset.y,
299+
});
311300

312301
// check limits
313-
if (newDuration > adjustedLimited.max) {
302+
if (newValues.duration > adjustedLimited.max) {
314303
const targetScrollIndex =
315-
newIndex - (newDuration - adjustedLimited.max);
304+
newValues.index -
305+
(newValues.duration - adjustedLimited.max);
316306
flatListRef.current?.scrollToIndex({
317307
animated: true,
318308
index:
@@ -321,27 +311,28 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
321311
? targetScrollIndex
322312
: adjustedLimited.max - 1,
323313
}); // scroll down to max
324-
newDuration = adjustedLimited.max;
325-
} else if (newDuration < adjustedLimited.min) {
314+
newValues.duration = adjustedLimited.max;
315+
} else if (newValues.duration < adjustedLimited.min) {
326316
const targetScrollIndex =
327-
newIndex + (adjustedLimited.min - newDuration);
317+
newValues.index +
318+
(adjustedLimited.min - newValues.duration);
328319
flatListRef.current?.scrollToIndex({
329320
animated: true,
330321
index:
331322
// guard against scrolling beyond end of list
332-
targetScrollIndex <= data.length - 1
323+
targetScrollIndex <= numbersForFlatList.length - 1
333324
? targetScrollIndex
334325
: adjustedLimited.min,
335326
}); // scroll up to min
336-
newDuration = adjustedLimited.min;
327+
newValues.duration = adjustedLimited.min;
337328
}
338329

339-
onDurationChange(newDuration);
330+
onDurationChange(newValues.duration);
340331
},
341332
[
342333
adjustedLimited.max,
343334
adjustedLimited.min,
344-
data.length,
335+
numbersForFlatList.length,
345336
disableInfiniteScroll,
346337
numberOfItems,
347338
onDurationChange,
@@ -363,15 +354,15 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
363354
} else if (
364355
viewableItems[0]?.index &&
365356
viewableItems[0].index >=
366-
numberOfItems * (repeatNumbersNTimes - 0.5)
357+
numberOfItems * (safeRepeatNumbersNTimes - 0.5)
367358
) {
368359
flatListRef.current?.scrollToIndex({
369360
animated: false,
370-
index: viewableItems[0].index - numberOfItems - 1,
361+
index: viewableItems[0].index - numberOfItems,
371362
});
372363
}
373364
},
374-
[numberOfItems, repeatNumbersNTimes]
365+
[numberOfItems, safeRepeatNumbersNTimes]
375366
);
376367

377368
const getItemLayout = useCallback(
@@ -391,6 +382,28 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
391382
},
392383
]);
393384

385+
useImperativeHandle(ref, () => ({
386+
reset: (options) => {
387+
flatListRef.current?.scrollToIndex({
388+
animated: options?.animated ?? false,
389+
index: initialScrollIndex,
390+
});
391+
},
392+
setValue: (value, options) => {
393+
flatListRef.current?.scrollToIndex({
394+
animated: options?.animated ?? false,
395+
index: getInitialScrollIndex({
396+
disableInfiniteScroll,
397+
numberOfItems,
398+
padWithNItems,
399+
repeatNumbersNTimes: safeRepeatNumbersNTimes,
400+
value: value,
401+
}),
402+
});
403+
},
404+
latestDuration: latestDuration,
405+
}));
406+
394407
return (
395408
<View
396409
pointerEvents={isDisabled ? "none" : undefined}
@@ -406,7 +419,7 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
406419
testID={testID}>
407420
<FlatList
408421
ref={flatListRef}
409-
data={data}
422+
data={numbersForFlatList}
410423
decelerationRate={0.88}
411424
getItemLayout={getItemLayout}
412425
initialScrollIndex={initialScrollIndex}
@@ -420,7 +433,7 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
420433
showsVerticalScrollIndicator={false}
421434
snapToAlignment="start"
422435
// used in place of snapToOffset due to bug on Android
423-
snapToOffsets={[...Array(data.length)].map(
436+
snapToOffsets={[...Array(numbersForFlatList.length)].map(
424437
(_, i) => i * styles.pickerItemContainer.height
425438
)}
426439
testID="duration-scroll-flatlist"

0 commit comments

Comments
 (0)