Skip to content

Commit dd5168d

Browse files
committed
Merge branch 'patch/v1.10.2'
2 parents fe52432 + b266929 commit dd5168d

File tree

10 files changed

+153
-62
lines changed

10 files changed

+153
-62
lines changed

README.md

+13
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ Includes iOS-style haptic and audio feedback 🍏
2727
- [Props 💅](#props-)
2828
- [TimerPicker ⏲️](#timerpicker-️)
2929
- [Custom Styles 👗](#custom-styles-)
30+
- [Performance](#performance)
3031
- [Custom FlatList](#custom-flatlist)
3132
- [TimerPickerModal ⏰](#timerpickermodal-)
3233
- [Custom Styles 👕](#custom-styles--1)
@@ -448,6 +449,9 @@ return (
448449
| use12HourPicker | Switch the hour picker to 12-hour format with an AM / PM label | Boolean | false | false |
449450
| amLabel | Set the AM label if using the 12-hour picker | String | am | false |
450451
| 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 |
453+
| repeatMinuteNumbersNTimes | Set the number of times the list of minutes is repeated in the picker | Number | 3 | false |
454+
| repeatSecondNumbersNTimes | Set the number of times the list of seconds is repeated in the picker | Number | 3 | false |
451455
| disableInfiniteScroll | Disable the infinite scroll feature | Boolean | false | false |
452456
| LinearGradient | Linear Gradient Component | [expo-linear-gradient](https://www.npmjs.com/package/expo-linear-gradient).LinearGradient or [react-native-linear-gradient](https://www.npmjs.com/package/react-native-linear-gradient).default | - | false |
453457
| Haptics | Haptics Namespace (required for Haptic feedback) | [expo-haptics](https://www.npmjs.com/package/expo-haptics) | - | false |
@@ -483,6 +487,15 @@ The following custom styles can be supplied to re-style the component in any way
483487
Note the minor limitations to the allowed styles for `pickerContainer` and `pickerItemContainer`. These are made because these styles are used for internal calculations and all possible `backgroundColor`/`height` types are not supported.
484488

485489

490+
#### Performance
491+
492+
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.
493+
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.
495+
496+
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.
497+
498+
486499
#### Custom FlatList
487500

488501
The library offers the ability to provide a custom component for the `<FlatList />`, instead of the default React Native component. This allows for more flexibility and integration with libraries like [react-native-gesture-handler](react-native-gesture-handler) or other components built on top of it, like [https://ui.gorhom.dev/components/bottom-sheet](https://ui.gorhom.dev/components/bottom-sheet).

example/App.tsx

+4
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ export default function App() {
107107
<TimerPickerModal
108108
Audio={Audio}
109109
closeOnOverlayPress
110+
disableInfiniteScroll
110111
Haptics={Haptics}
111112
LinearGradient={LinearGradient}
112113
modalProps={{
@@ -118,6 +119,9 @@ export default function App() {
118119
setAlarmStringExample1(formatTime(pickedDuration));
119120
setShowPickerExample1(false);
120121
}}
122+
repeatHourNumbersNTimes={1}
123+
repeatMinuteNumbersNTimes={1}
124+
repeatSecondNumbersNTimes={1}
121125
setIsVisible={setShowPickerExample1}
122126
styles={{
123127
theme: "dark",

example/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
},
1111
"dependencies": {
1212
"@expo/vector-icons": "^14.0.0",
13-
"expo": "~51.0.20",
13+
"expo": "~51.0.23",
1414
"expo-av": "~14.0.5",
1515
"expo-haptics": "~13.0.1",
1616
"expo-linear-gradient": "~13.0.2",

example/yarn.lock

+45-21
Original file line numberDiff line numberDiff line change
@@ -852,10 +852,10 @@
852852
mv "~2"
853853
safe-json-stringify "~1"
854854

855-
"@expo/[email protected].25":
856-
version "0.18.25"
857-
resolved "https://registry.yarnpkg.com/@expo/cli/-/cli-0.18.25.tgz#92f710a028db8f133fede556c129e74d9263faca"
858-
integrity sha512-Kh0uZGCxwu58Pu7Jto9T/ABlBR7nkx8QC0Wv8pI3YtISyQZIKtbtNNeTPWYbVK1ddswKwtBUj+MNhKoDL49TLg==
855+
"@expo/[email protected].26":
856+
version "0.18.26"
857+
resolved "https://registry.yarnpkg.com/@expo/cli/-/cli-0.18.26.tgz#56e60eaae12b82a8aeb33f2050bbfb6683f87187"
858+
integrity sha512-u9bTTXgcjaTloE9CHwxgrb8Me/Al4jiPykbVQpJydakH3GsIZfHy1zaLc7O39CoLjRz37WWi6Y5ZdgtQw9dCPQ==
859859
dependencies:
860860
"@babel/runtime" "^7.20.0"
861861
"@expo/code-signing-certificates" "0.0.5"
@@ -1041,7 +1041,31 @@
10411041
json5 "^2.2.2"
10421042
write-file-atomic "^2.3.0"
10431043

1044-
"@expo/[email protected]", "@expo/metro-config@~0.18.6":
1044+
1045+
version "0.18.10"
1046+
resolved "https://registry.yarnpkg.com/@expo/metro-config/-/metro-config-0.18.10.tgz#2b0ec353c38802dd89028432c3a463804df94f24"
1047+
integrity sha512-HTYQqKfV0JSuRp5aDvrPHezj5udXOWoXqHOjfTSnce2m13j6D0yYXTJNaKRhlgpPBrkg5DL7z1fL3zwDUpLM4w==
1048+
dependencies:
1049+
"@babel/core" "^7.20.0"
1050+
"@babel/generator" "^7.20.5"
1051+
"@babel/parser" "^7.20.0"
1052+
"@babel/types" "^7.20.0"
1053+
"@expo/config" "~9.0.0-beta.0"
1054+
"@expo/env" "~0.3.0"
1055+
"@expo/json-file" "~8.3.0"
1056+
"@expo/spawn-async" "^1.7.2"
1057+
chalk "^4.1.0"
1058+
debug "^4.3.2"
1059+
find-yarn-workspace-root "~2.0.0"
1060+
fs-extra "^9.1.0"
1061+
getenv "^1.0.0"
1062+
glob "^7.2.3"
1063+
jsc-safe-url "^0.2.4"
1064+
lightningcss "~1.19.0"
1065+
postcss "~8.4.32"
1066+
resolve-from "^5.0.0"
1067+
1068+
"@expo/metro-config@~0.18.6":
10451069
version "0.18.8"
10461070
resolved "https://registry.yarnpkg.com/@expo/metro-config/-/metro-config-0.18.8.tgz#2902bfdb864876da3cf5b1822a554bbb011e4a77"
10471071
integrity sha512-YGpTlVc1/6EPzPbt0LZt92Bwrpjngulup6uHSTRbwn/heMPfFaVv1Y4VE3GAUkx7/Qwu+dTVIV0Kys4pLOAIiw==
@@ -3349,10 +3373,10 @@ expo-file-system@~17.0.1:
33493373
resolved "https://registry.yarnpkg.com/expo-file-system/-/expo-file-system-17.0.1.tgz#b9f8af8c1c06ec71d96fd7a0d2567fa9e1c88f15"
33503374
integrity sha512-dYpnZJqTGj6HCYJyXAgpFkQWsiCH3HY1ek2cFZVHFoEc5tLz9gmdEgTF6nFHurvmvfmXqxi7a5CXyVm0aFYJBw==
33513375

3352-
expo-font@~12.0.8:
3353-
version "12.0.8"
3354-
resolved "https://registry.yarnpkg.com/expo-font/-/expo-font-12.0.8.tgz#23b769063ba72484c767a4fdb0ff3a26d23645d2"
3355-
integrity sha512-xK9kOEyD/vnREicM5yS2uVwzB83weu+UE6QwJRH2dhoQcdJiWGGKOj+blJ8GrtU+BqtyNijyWcwfBbYOJnf8eQ==
3376+
expo-font@~12.0.9:
3377+
version "12.0.9"
3378+
resolved "https://registry.yarnpkg.com/expo-font/-/expo-font-12.0.9.tgz#096860a6b8b5dd54152262eafd318593ec2db48c"
3379+
integrity sha512-seTCyf0tbgkAnp3ZI9ZfK9QVtURQUgFnuj+GuJ5TSnN0XsOtVe1s2RxTvmMgkfuvfkzcjJ69gyRpsZS1cC8hjw==
33563380
dependencies:
33573381
fontfaceobserver "^2.1.0"
33583382

@@ -3382,31 +3406,31 @@ [email protected]:
33823406
find-up "^5.0.0"
33833407
fs-extra "^9.1.0"
33843408

3385-
3386-
version "1.12.19"
3387-
resolved "https://registry.yarnpkg.com/expo-modules-core/-/expo-modules-core-1.12.19.tgz#3212df74206ce29d85a4aed0612f6b9d4d967105"
3388-
integrity sha512-fFsErN4oMsOdStUVYvyLpl6MX/wbD9yJSqy/Lu7ZRLIPzeKDfGS2jNl8RzryPznRpWmy49X8l40R4osRJLizhg==
3409+
3410+
version "1.12.20"
3411+
resolved "https://registry.yarnpkg.com/expo-modules-core/-/expo-modules-core-1.12.20.tgz#072dea10791f32ea5d1d3b15a7d5fd1984173429"
3412+
integrity sha512-CCXjlgT8lDAufgt912P1W7TwD+KAylfIttc1Doh1a0hAfkdkUsDRmrgthnYrrxEo2ECVpbaB71Epp1bnZ1rRrA==
33893413
dependencies:
33903414
invariant "^2.2.4"
33913415

3392-
expo@~51.0.20:
3393-
version "51.0.20"
3394-
resolved "https://registry.yarnpkg.com/expo/-/expo-51.0.20.tgz#e34ce895875e29bb46a818ff5be7178b4fce2bfb"
3395-
integrity sha512-EmNZel6j7pU4YF1QcIcgpVdYsA1lASQcEw9PPSevjreNT6nlge2CNHB6mAphAKGz5PdgcWYBn/v4qQj1/FJuZQ==
3416+
expo@~51.0.23:
3417+
version "51.0.23"
3418+
resolved "https://registry.yarnpkg.com/expo/-/expo-51.0.23.tgz#a2813c4ea45f5158838221093a6a08eae47534a1"
3419+
integrity sha512-tZv7hiDIlKMlQxzC/IxfiLEpp0xcm6VISuoeFobkvoelEWsj6bpjlsF0HP4k6pr3ELL++r7wE1VGYa/AceC6+w==
33963420
dependencies:
33973421
"@babel/runtime" "^7.20.0"
3398-
"@expo/cli" "0.18.25"
3422+
"@expo/cli" "0.18.26"
33993423
"@expo/config" "9.0.3"
34003424
"@expo/config-plugins" "8.0.8"
3401-
"@expo/metro-config" "0.18.8"
3425+
"@expo/metro-config" "0.18.10"
34023426
"@expo/vector-icons" "^14.0.0"
34033427
babel-preset-expo "~11.0.12"
34043428
expo-asset "~10.0.10"
34053429
expo-file-system "~17.0.1"
3406-
expo-font "~12.0.8"
3430+
expo-font "~12.0.9"
34073431
expo-keep-awake "~13.0.2"
34083432
expo-modules-autolinking "1.11.1"
3409-
expo-modules-core "1.12.19"
3433+
expo-modules-core "1.12.20"
34103434
fbemitter "^3.0.0"
34113435
whatwg-url-without-unicode "8.0.0-3"
34123436

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.1",
9+
"version": "1.10.2",
1010
"main": "dist/commonjs/index.js",
1111
"module": "dist/module/index.js",
1212
"types": "dist/typescript/index.d.ts",

src/components/DurationScroll/index.tsx

+63-29
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import React, {
55
useImperativeHandle,
66
useState,
77
useEffect,
8+
useMemo,
89
} from "react";
910

1011
import { View, Text, FlatList as RNFlatList } from "react-native";
@@ -49,35 +50,59 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
4950
padWithNItems,
5051
pickerGradientOverlayProps,
5152
pmLabel,
53+
repeatNumbersNTimes = 3,
5254
styles,
5355
testID,
5456
topPickerGradientOverlayProps,
5557
} = props;
5658

57-
const data = !is12HourPicker
58-
? generateNumbers(numberOfItems, {
59-
padNumbersWithZero,
60-
repeatNTimes: 3,
61-
disableInfiniteScroll,
62-
padWithNItems,
63-
})
64-
: generate12HourNumbers({
65-
padNumbersWithZero,
66-
repeatNTimes: 3,
67-
disableInfiniteScroll,
68-
padWithNItems,
69-
});
70-
71-
const numberOfItemsToShow = 1 + padWithNItems * 2;
72-
73-
const adjustedLimited = getAdjustedLimit(limit, numberOfItems);
59+
const data = useMemo(() => {
60+
if (is12HourPicker) {
61+
return generate12HourNumbers({
62+
padNumbersWithZero,
63+
repeatNTimes: repeatNumbersNTimes,
64+
disableInfiniteScroll,
65+
padWithNItems,
66+
});
67+
}
7468

75-
const initialScrollIndex = getScrollIndex({
76-
value: initialValue,
69+
return generateNumbers(numberOfItems, {
70+
padNumbersWithZero,
71+
repeatNTimes: repeatNumbersNTimes,
72+
disableInfiniteScroll,
73+
padWithNItems,
74+
});
75+
}, [
76+
disableInfiniteScroll,
77+
is12HourPicker,
7778
numberOfItems,
79+
padNumbersWithZero,
7880
padWithNItems,
79-
disableInfiniteScroll,
80-
});
81+
repeatNumbersNTimes,
82+
]);
83+
84+
const initialScrollIndex = useMemo(
85+
() =>
86+
getScrollIndex({
87+
numberOfItems,
88+
padWithNItems,
89+
repeatNumbersNTimes,
90+
value: initialValue,
91+
}),
92+
[
93+
initialValue,
94+
numberOfItems,
95+
padWithNItems,
96+
repeatNumbersNTimes,
97+
]
98+
);
99+
100+
const adjustedLimited = useMemo(
101+
() => getAdjustedLimit(limit, numberOfItems),
102+
[limit, numberOfItems]
103+
);
104+
105+
const numberOfItemsToShow = 1 + padWithNItems * 2;
81106

82107
// keep track of the latest duration as it scrolls
83108
const latestDuration = useRef(0);
@@ -129,10 +154,10 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
129154
flatListRef.current?.scrollToIndex({
130155
animated: options?.animated ?? false,
131156
index: getScrollIndex({
132-
value: value,
133157
numberOfItems,
134158
padWithNItems,
135-
disableInfiniteScroll,
159+
repeatNumbersNTimes,
160+
value: value,
136161
}),
137162
});
138163
},
@@ -241,10 +266,18 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
241266
// this check stops the feedback firing when the component mounts
242267
if (lastFeedbackIndex.current) {
243268
// fire haptic feedback if available
244-
Haptics?.selectionAsync();
269+
try {
270+
Haptics?.selectionAsync();
271+
} catch {
272+
// do nothing
273+
}
245274

246275
// play click sound if available
247-
clickSound?.replayAsync();
276+
try {
277+
clickSound?.replayAsync();
278+
} catch {
279+
// do nothing
280+
}
248281
}
249282

250283
lastFeedbackIndex.current = feedbackIndex;
@@ -329,15 +362,16 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
329362
});
330363
} else if (
331364
viewableItems[0]?.index &&
332-
viewableItems[0].index >= numberOfItems * 2.5
365+
viewableItems[0].index >=
366+
numberOfItems * (repeatNumbersNTimes - 0.5)
333367
) {
334368
flatListRef.current?.scrollToIndex({
335369
animated: false,
336-
index: viewableItems[0].index - numberOfItems,
370+
index: viewableItems[0].index - numberOfItems - 1,
337371
});
338372
}
339373
},
340-
[numberOfItems]
374+
[numberOfItems, repeatNumbersNTimes]
341375
);
342376

343377
const getItemLayout = useCallback(
@@ -352,7 +386,7 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
352386
const viewabilityConfigCallbackPairs =
353387
useRef<ViewabilityConfigCallbackPairs>([
354388
{
355-
viewabilityConfig: { viewAreaCoveragePercentThreshold: 25 },
389+
viewabilityConfig: { viewAreaCoveragePercentThreshold: 0 },
356390
onViewableItemsChanged: onViewableItemsChanged,
357391
},
358392
]);

src/components/DurationScroll/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export interface DurationScrollProps {
3838
padWithNItems: number;
3939
pickerGradientOverlayProps?: Partial<LinearGradientProps>;
4040
pmLabel?: string;
41+
repeatNumbersNTimes?: number;
4142
styles: ReturnType<typeof generateStyles>;
4243
testID?: string;
4344
topPickerGradientOverlayProps?: Partial<LinearGradientProps>;

src/components/TimerPicker/index.tsx

+14-5
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ const TimerPicker = forwardRef<TimerPickerRef, TimerPickerProps>(
3939
padWithNItems = 1,
4040
pickerContainerProps,
4141
pmLabel = "pm",
42+
repeatHourNumbersNTimes = 6,
43+
repeatMinuteNumbersNTimes = 3,
44+
repeatSecondNumbersNTimes = 3,
4245
secondLabel,
4346
secondLimit,
4447
secondsPickerIsDisabled = false,
@@ -59,11 +62,14 @@ const TimerPicker = forwardRef<TimerPickerRef, TimerPickerProps>(
5962
[checkedPadWithNItems, customStyles]
6063
);
6164

62-
const safeInitialValue = {
63-
hours: initialValue?.hours ?? 0,
64-
minutes: initialValue?.minutes ?? 0,
65-
seconds: initialValue?.seconds ?? 0,
66-
};
65+
const safeInitialValue = useMemo(
66+
() => ({
67+
hours: initialValue?.hours ?? 0,
68+
minutes: initialValue?.minutes ?? 0,
69+
seconds: initialValue?.seconds ?? 0,
70+
}),
71+
[initialValue?.hours, initialValue?.minutes, initialValue?.seconds]
72+
);
6773

6874
const [selectedHours, setSelectedHours] = useState(
6975
safeInitialValue.hours
@@ -144,6 +150,7 @@ const TimerPicker = forwardRef<TimerPickerRef, TimerPickerProps>(
144150
padNumbersWithZero={padHoursWithZero}
145151
padWithNItems={checkedPadWithNItems}
146152
pmLabel={pmLabel}
153+
repeatNumbersNTimes={repeatHourNumbersNTimes}
147154
styles={styles}
148155
testID="duration-scroll-hour"
149156
{...otherProps}
@@ -165,6 +172,7 @@ const TimerPicker = forwardRef<TimerPickerRef, TimerPickerProps>(
165172
onDurationChange={setSelectedMinutes}
166173
padNumbersWithZero={padMinutesWithZero}
167174
padWithNItems={checkedPadWithNItems}
175+
repeatNumbersNTimes={repeatMinuteNumbersNTimes}
168176
styles={styles}
169177
testID="duration-scroll-minute"
170178
{...otherProps}
@@ -186,6 +194,7 @@ const TimerPicker = forwardRef<TimerPickerRef, TimerPickerProps>(
186194
onDurationChange={setSelectedSeconds}
187195
padNumbersWithZero={padSecondsWithZero}
188196
padWithNItems={checkedPadWithNItems}
197+
repeatNumbersNTimes={repeatSecondNumbersNTimes}
189198
styles={styles}
190199
testID="duration-scroll-second"
191200
{...otherProps}

0 commit comments

Comments
 (0)