react-native-timer-picker 1.10.3 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/README.md +81 -52
  2. package/dist/commonjs/components/DurationScroll/index.js +77 -20
  3. package/dist/commonjs/components/DurationScroll/index.js.map +1 -1
  4. package/dist/commonjs/components/DurationScroll/types.js.map +1 -1
  5. package/dist/commonjs/components/TimerPicker/index.js +23 -10
  6. package/dist/commonjs/components/TimerPicker/index.js.map +1 -1
  7. package/dist/commonjs/components/TimerPicker/types.js.map +1 -1
  8. package/dist/commonjs/components/TimerPickerModal/index.js +10 -7
  9. package/dist/commonjs/components/TimerPickerModal/index.js.map +1 -1
  10. package/dist/commonjs/components/TimerPickerModal/styles.js +87 -54
  11. package/dist/commonjs/components/TimerPickerModal/styles.js.map +1 -1
  12. package/dist/commonjs/tests/DurationScroll.test.js +3 -3
  13. package/dist/commonjs/tests/DurationScroll.test.js.map +1 -1
  14. package/dist/commonjs/tests/Modal.test.js +3 -3
  15. package/dist/commonjs/tests/Modal.test.js.map +1 -1
  16. package/dist/commonjs/tests/TimerPicker.test.js +4 -4
  17. package/dist/commonjs/tests/TimerPicker.test.js.map +1 -1
  18. package/dist/commonjs/tests/TimerPickerModal.test.js +6 -6
  19. package/dist/commonjs/tests/TimerPickerModal.test.js.map +1 -1
  20. package/dist/commonjs/utils/generateNumbers.js +4 -3
  21. package/dist/commonjs/utils/generateNumbers.js.map +1 -1
  22. package/dist/commonjs/utils/getAdjustedLimit.js +5 -5
  23. package/dist/commonjs/utils/getAdjustedLimit.js.map +1 -1
  24. package/dist/commonjs/utils/getDurationAndIndexFromScrollOffset.js +2 -1
  25. package/dist/commonjs/utils/getDurationAndIndexFromScrollOffset.js.map +1 -1
  26. package/dist/commonjs/utils/getInitialScrollIndex.js +2 -1
  27. package/dist/commonjs/utils/getInitialScrollIndex.js.map +1 -1
  28. package/dist/commonjs/utils/getSafeInitialValue.js +13 -0
  29. package/dist/commonjs/utils/getSafeInitialValue.js.map +1 -0
  30. package/dist/module/components/DurationScroll/index.js +77 -20
  31. package/dist/module/components/DurationScroll/index.js.map +1 -1
  32. package/dist/module/components/DurationScroll/types.js.map +1 -1
  33. package/dist/module/components/TimerPicker/index.js +23 -10
  34. package/dist/module/components/TimerPicker/index.js.map +1 -1
  35. package/dist/module/components/TimerPicker/types.js.map +1 -1
  36. package/dist/module/components/TimerPickerModal/index.js +10 -7
  37. package/dist/module/components/TimerPickerModal/index.js.map +1 -1
  38. package/dist/module/components/TimerPickerModal/styles.js +87 -54
  39. package/dist/module/components/TimerPickerModal/styles.js.map +1 -1
  40. package/dist/module/tests/DurationScroll.test.js +3 -3
  41. package/dist/module/tests/DurationScroll.test.js.map +1 -1
  42. package/dist/module/tests/Modal.test.js +3 -3
  43. package/dist/module/tests/Modal.test.js.map +1 -1
  44. package/dist/module/tests/TimerPicker.test.js +4 -4
  45. package/dist/module/tests/TimerPicker.test.js.map +1 -1
  46. package/dist/module/tests/TimerPickerModal.test.js +6 -6
  47. package/dist/module/tests/TimerPickerModal.test.js.map +1 -1
  48. package/dist/module/utils/generateNumbers.js +4 -3
  49. package/dist/module/utils/generateNumbers.js.map +1 -1
  50. package/dist/module/utils/getAdjustedLimit.js +5 -5
  51. package/dist/module/utils/getAdjustedLimit.js.map +1 -1
  52. package/dist/module/utils/getDurationAndIndexFromScrollOffset.js +2 -1
  53. package/dist/module/utils/getDurationAndIndexFromScrollOffset.js.map +1 -1
  54. package/dist/module/utils/getInitialScrollIndex.js +2 -1
  55. package/dist/module/utils/getInitialScrollIndex.js.map +1 -1
  56. package/dist/module/utils/getSafeInitialValue.js +6 -0
  57. package/dist/module/utils/getSafeInitialValue.js.map +1 -0
  58. package/dist/typescript/components/DurationScroll/types.d.ts +4 -1
  59. package/dist/typescript/components/TimerPicker/types.d.ts +7 -0
  60. package/dist/typescript/components/TimerPickerModal/styles.d.ts +117 -8
  61. package/dist/typescript/utils/generateNumbers.d.ts +2 -0
  62. package/dist/typescript/utils/getAdjustedLimit.d.ts +1 -1
  63. package/dist/typescript/utils/getDurationAndIndexFromScrollOffset.d.ts +1 -0
  64. package/dist/typescript/utils/getInitialScrollIndex.d.ts +1 -0
  65. package/dist/typescript/utils/getSafeInitialValue.d.ts +9 -0
  66. package/package.json +14 -10
package/README.md CHANGED
@@ -29,6 +29,7 @@ Includes iOS-style haptic and audio feedback 🍏
29
29
  - [Custom Styles 👗](#custom-styles-)
30
30
  - [Performance](#performance)
31
31
  - [Custom FlatList](#custom-flatlist)
32
+ - [Generic feedback](#generic-feedback)
32
33
  - [TimerPickerModal ⏰](#timerpickermodal-)
33
34
  - [Custom Styles 👕](#custom-styles--1)
34
35
  - [Methods 🔄](#methods-)
@@ -72,15 +73,17 @@ If you want the numbers to fade in/out at the top and bottom of the picker, you
72
73
 
73
74
  ### Haptic Feedback
74
75
 
75
- This is currently only supported on Expo with the [expo-haptics](https://www.npmjs.com/package/expo-haptics) module:
76
+ Enable haptic feedback with the [expo-haptics](https://www.npmjs.com/package/expo-haptics) module:
76
77
 
77
78
  `import * as Haptics from "expo-haptics";`
78
79
 
79
80
  **To enable haptic feedback, you need to supply the imported `Haptics` namespace as a prop to either TimerPickerModal or TimerPicker.**
80
81
 
82
+ [Generic feedback](#generic-feedback) support is possible with the `pickerFeeback` prop.
83
+
81
84
  ### Audio Feedback (Click Sound)
82
85
 
83
- This is currently only supported on Expo with the [expo-av](https://www.npmjs.com/package/expo-av) module:
86
+ Enable audio feedback with the [expo-av](https://www.npmjs.com/package/expo-av) module:
84
87
 
85
88
  `import { Audio } from "expo-av";`
86
89
 
@@ -89,6 +92,8 @@ This is currently only supported on Expo with the [expo-av](https://www.npmjs.co
89
92
  Please note that the default click sound uses a hosted mp3 file. To make the click sound work offline, you need to supply your own
90
93
  sound asset through the `clickSoundAsset` prop. You can download the default click sound [here](https://drive.google.com/uc?export=download&id=10e1YkbNsRh-vGx1jmS1Nntz8xzkBp4_I).
91
94
 
95
+ [Generic feedback](#generic-feedback) support is possible with the `pickerFeeback` prop.
96
+
92
97
  <br>
93
98
 
94
99
  ## Installation 🚀
@@ -424,45 +429,52 @@ return (
424
429
 
425
430
  ### TimerPicker ⏲️
426
431
 
427
- | Prop | Description | Type | Default | Required |
428
- | :------------------------------: | :------------------------------------------------------------------------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----: | :------: |
429
- | onDurationChange | Callback when the duration changes | `(duration: { hours: number, minutes: number, seconds: number }) => void` | - | false |
430
- | initialValue | Initial value for the picker | `{ hours?: number, minutes?: number, seconds?: number }` | - | false |
431
- | hideHours | Hide the hours picker | Boolean | false | false |
432
- | hideMinutes | Hide the minutes picker | Boolean | false | false |
433
- | hideSeconds | Hide the seconds picker | Boolean | false | false |
434
- | hoursPickerIsDisabled | Disable the hours picker picker | Boolean | false | false |
435
- | minutesPickerIsDisabled | Disable the minutes picker picker | Boolean | false | false |
436
- | secondsPickerIsDisabled | Disable the seconds picker picker | Boolean | false | false |
437
- | hourLimit | Limit on the hours it is possible to select | `{ max?: Number, min?: Number }` | - | false |
438
- | minuteLimit | Limit on the minutes it is possible to select | `{ max?: Number, min?: Number }` | - | false |
439
- | secondLimit | Limit on the seconds it is possible to select | `{ max?: Number, min?: Number }` | - | false |
440
- | hourLabel | Label for the hours picker | String \| React.ReactElement | h | false |
441
- | minuteLabel | Label for the minutes picker | String \| React.ReactElement | m | false |
442
- | secondLabel | Label for the seconds picker | String \| React.ReactElement | s | false |
443
- | padHoursWithZero | Pad single-digit hours in the picker with a zero | Boolean | false | false |
444
- | padMinutesWithZero | Pad single-digit minutes in the picker with a zero | Boolean | true | false |
445
- | padSecondsWithZero | Pad single-digit seconds in the picker with a zero | Boolean | true | false |
446
- | padWithNItems | Number of items to pad the picker with on either side | Number | 1 | false |
447
- | aggressivelyGetLatestDuration | Set to True to ask DurationScroll to aggressively update the latestDuration ref | Boolean | false | false |
448
- | allowFontScaling | Allow font in the picker to scale with accessibility settings | Boolean | false | false |
449
- | use12HourPicker | Switch the hour picker to 12-hour format with an AM / PM label | Boolean | false | false |
450
- | amLabel | Set the AM label if using the 12-hour picker | String | am | false |
451
- | 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 | 7 | 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 |
455
- | disableInfiniteScroll | Disable the infinite scroll feature | Boolean | false | false |
456
- | 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 |
457
- | Haptics | Haptics Namespace (required for Haptic feedback) | [expo-haptics](https://www.npmjs.com/package/expo-haptics) | - | false |
458
- | Audio | Audio Class (required for audio feedback i.e. click sound) | [expo-av](https://www.npmjs.com/package/expo-av).Audio | - | false |
459
- | FlatList | FlatList component used internally to implement each picker (hour, minutes and seconds). More info [below](#custom-flatlist) | [react-native](https://reactnative.dev/docs/flatlist).FlatList | `FlatList` from `react-native` | false |
460
- | clickSoundAsset | Custom sound asset for click sound (required for offline click sound - download default [here](https://drive.google.com/uc?export=download&id=10e1YkbNsRh-vGx1jmS1Nntz8xzkBp4_I)) | require(.../somefolderpath) or {uri: www.someurl} | - | false |
461
- | pickerContainerProps | Props for the picker container | `React.ComponentProps<typeof View>` | - | false |
462
- | pickerGradientOverlayProps | Props for both gradient overlays | `Partial<LinearGradientProps>` | - | false |
463
- | topPickerGradientOverlayProps | Props for the top gradient overlay | `Partial<LinearGradientProps>` | - | false |
464
- | bottomPickerGradientOverlayProps | Props for the bottom gradient overlay | `Partial<LinearGradientProps>` | - | false |
465
- | styles | Custom styles for the timer picker | [CustomTimerPickerStyles](#custom-styles-) | - | false |
432
+ | Prop | Description | Type | Default | Required |
433
+ | :------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------: | :------: |
434
+ | onDurationChange | Callback when the duration changes | `(duration: { hours: number, minutes: number, seconds: number }) => void` | - | false |
435
+ | initialValue | Initial value for the picker | `{ hours?: number, minutes?: number, seconds?: number }` | - | false |
436
+ | hideHours | Hide the hours picker | Boolean | false | false |
437
+ | hideMinutes | Hide the minutes picker | Boolean | false | false |
438
+ | hideSeconds | Hide the seconds picker | Boolean | false | false |
439
+ | hoursPickerIsDisabled | Disable the hours picker picker | Boolean | false | false |
440
+ | minutesPickerIsDisabled | Disable the minutes picker picker | Boolean | false | false |
441
+ | secondsPickerIsDisabled | Disable the seconds picker picker | Boolean | false | false |
442
+ | hourLimit | Limit on the hours it is possible to select | `{ max?: Number, min?: Number }` | - | false |
443
+ | minuteLimit | Limit on the minutes it is possible to select | `{ max?: Number, min?: Number }` | - | false |
444
+ | secondLimit | Limit on the seconds it is possible to select | `{ max?: Number, min?: Number }` | - | false |
445
+ | maximumHours | The highest value on the hours picker | Number | 23 | false |
446
+ | maximumMinutes | The highest value on the minutes picker | Number | 59 | false |
447
+ | maximumSeconds | The highest value on the seconds picker | Number | 59 | false |
448
+ | hourInterval | The interval between values on the hours picker | Number | 1 | false |
449
+ | minuteInterval | The interval between values on the minutes picker | Number | 1 | false |
450
+ | secondInterval | The interval between values on the seconds picker | Number | 1 | false |
451
+ | hourLabel | Label for the hours picker | String \| React.ReactElement | h | false |
452
+ | minuteLabel | Label for the minutes picker | String \| React.ReactElement | m | false |
453
+ | secondLabel | Label for the seconds picker | String \| React.ReactElement | s | false |
454
+ | padHoursWithZero | Pad single-digit hours in the picker with a zero | Boolean | false | false |
455
+ | padMinutesWithZero | Pad single-digit minutes in the picker with a zero | Boolean | true | false |
456
+ | padSecondsWithZero | Pad single-digit seconds in the picker with a zero | Boolean | true | false |
457
+ | padWithNItems | Number of items to pad the picker with on either side | Number | 1 | false |
458
+ | aggressivelyGetLatestDuration | Set to True to ask DurationScroll to aggressively update the latestDuration ref | Boolean | false | false |
459
+ | allowFontScaling | Allow font in the picker to scale with accessibility settings | Boolean | false | false |
460
+ | use12HourPicker | Switch the hour picker to 12-hour format with an AM / PM label | Boolean | false | false |
461
+ | amLabel | Set the AM label if using the 12-hour picker | String | am | false |
462
+ | pmLabel | Set the PM label if using the 12-hour picker | String | pm | false |
463
+ | repeatHourNumbersNTimes | Set the number of times the list of hours is repeated in the picker | Number | 7 | false |
464
+ | repeatMinuteNumbersNTimes | Set the number of times the list of minutes is repeated in the picker | Number | 3 | false |
465
+ | repeatSecondNumbersNTimes | Set the number of times the list of seconds is repeated in the picker | Number | 3 | false |
466
+ | disableInfiniteScroll | Disable the infinite scroll feature | Boolean | false | false |
467
+ | 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 |
468
+ | Haptics | Haptics Namespace (required for Haptic feedback) | [expo-haptics](https://www.npmjs.com/package/expo-haptics) | - | false |
469
+ | Audio | Audio Class (required for audio feedback i.e. click sound) | [expo-av](https://www.npmjs.com/package/expo-av).Audio | - | false |
470
+ | pickerFeedback | Generic picker feedback as alternative to the below Expo feedback support | `() => void \| Promise<void> ` | - | false |
471
+ | FlatList | FlatList component used internally to implement each picker (hour, minutes and seconds). More info [below](#custom-flatlist) | [react-native](https://reactnative.dev/docs/flatlist).FlatList | `FlatList` from `react-native` | false |
472
+ | clickSoundAsset | Custom sound asset for click sound (required for offline click sound - download default [here](https://drive.google.com/uc?export=download&id=10e1YkbNsRh-vGx1jmS1Nntz8xzkBp4_I)) | require(.../somefolderpath) or {uri: www.someurl} | - | false |
473
+ | pickerContainerProps | Props for the picker container | `React.ComponentProps<typeof View>` | - | false |
474
+ | pickerGradientOverlayProps | Props for both gradient overlays | `Partial<LinearGradientProps>` | - | false |
475
+ | topPickerGradientOverlayProps | Props for the top gradient overlay | `Partial<LinearGradientProps>` | - | false |
476
+ | bottomPickerGradientOverlayProps | Props for the bottom gradient overlay | `Partial<LinearGradientProps>` | - | false |
477
+ | styles | Custom styles for the timer picker | [CustomTimerPickerStyles](#custom-styles-) | - | false |
466
478
 
467
479
  #### Custom Styles 👗
468
480
 
@@ -486,7 +498,6 @@ The following custom styles can be supplied to re-style the component in any way
486
498
 
487
499
  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.
488
500
 
489
-
490
501
  #### Performance
491
502
 
492
503
  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.
@@ -495,7 +506,6 @@ To mitigate for this, you can modify the `repeatHourNumbersNTimes`, `repeatMinut
495
506
 
496
507
  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
508
 
498
-
499
509
  #### Custom FlatList
500
510
 
501
511
  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).
@@ -518,6 +528,23 @@ import { TimerPicker } from "react-native-timer-picker";
518
528
  **Important**:
519
529
  The custom component needs to have the same interface as React Native's `<FlatList />` in order for it to work as expected. A complete reference of the current usage can be found [here](/src/components/DurationScroll/index.tsx)
520
530
 
531
+ #### Generic feedback
532
+
533
+ To enable haptic feedback from the non-Expo module [react-native-haptic-feedback](https://github.com/mkuczera/react-native-haptic-feedback) or provide feedback in any other form you can use the generic feedback callback prop `pickerFeedback`. This function is called whenever any of the pickers tick onto a new number.
534
+
535
+ ```Jsx
536
+ import { trigger } from 'react-native-haptic-feedback';
537
+ import { TimerPicker } from "react-native-timer-picker";
538
+
539
+ // ...
540
+
541
+ <TimerPicker
542
+ {...props}
543
+ pickerFeedback={() => trigger('impactLight')}
544
+ />
545
+
546
+ ```
547
+
521
548
  ### TimerPickerModal ⏰
522
549
 
523
550
  The TimerPickerModal component accepts all [TimerPicker props](#timerpicker-️), and the below additional props.
@@ -609,16 +636,12 @@ Contributions to this project are more than welcome.
609
636
  To get this project running locally:
610
637
 
611
638
  1. Clone the Git repo.
612
- 2. Run `yarn` to install the base dependencies
613
- 3. Run `yarn setup` from the project root (this installs the example's additional dependencies)
614
- 4. Run `yarn start` to start the example in Expo Go.
615
- 5. Start adding cool stuff! Your changes should be immediately reflected in the Expo Go app.
639
+ 2. Run `yarn setup` from the project root (this installs the project dependencies and the examples' additional dependencies)
616
640
 
617
- You can also run the library in bare React Native:
618
- 1. Clone the Git repo.
619
- 2. Run `yarn` to install the base dependencies
620
- 3. Run `yarn setup-dev`.
621
- 4. Run `yarn start-bare:android` or `start-bare:ios` to start the project on an emulator/device.
641
+ You can then start either the Expo example or the bare React Native example:
642
+
643
+ - For Expo, run `yarn start` to start the Expo example in Expo Go.
644
+ - For bare React Native, run `yarn start-bare:android` or `start-bare:ios` to start the project on an emulator/device.
622
645
 
623
646
  ### GitHub Guidelines
624
647
 
@@ -630,6 +653,12 @@ There are two permenant branches: `main` and `develop`. You should never work di
630
653
 
631
654
  <br>
632
655
 
656
+ ## Limitations ⚠
657
+
658
+ The project is not compatibile with React Native versions prior to `v0.72.0` due to this [React Native issue](https://github.com/facebook/react-native/issues/36329).
659
+
660
+ <br>
661
+
633
662
  ## License 📝
634
663
 
635
664
  This project is licensed under the [MIT License](LICENSE).
@@ -26,54 +26,78 @@ const DurationScroll = /*#__PURE__*/(0, _react.forwardRef)((props, ref) => {
26
26
  FlatList = _reactNative.FlatList,
27
27
  Haptics,
28
28
  initialValue = 0,
29
+ interval,
29
30
  is12HourPicker,
30
31
  isDisabled,
31
32
  label,
32
33
  limit,
33
34
  LinearGradient,
34
- numberOfItems,
35
+ maximumValue,
35
36
  onDurationChange,
36
37
  padNumbersWithZero = false,
37
38
  padWithNItems,
39
+ pickerFeedback,
38
40
  pickerGradientOverlayProps,
39
41
  pmLabel,
40
42
  repeatNumbersNTimes = 3,
43
+ repeatNumbersNTimesNotExplicitlySet,
41
44
  styles,
42
45
  testID,
43
46
  topPickerGradientOverlayProps
44
47
  } = props;
48
+ const numberOfItems = (0, _react.useMemo)(() => {
49
+ // guard against negative maximum values
50
+ if (maximumValue < 0) {
51
+ return 1;
52
+ }
53
+ return Math.floor(maximumValue / interval) + 1;
54
+ }, [interval, maximumValue]);
45
55
  const safeRepeatNumbersNTimes = (0, _react.useMemo)(() => {
56
+ // do not repeat numbers if there is only one option
57
+ if (numberOfItems === 1) {
58
+ return 1;
59
+ }
46
60
  if (!disableInfiniteScroll && repeatNumbersNTimes < 2) {
47
61
  return 2;
48
- } else if (repeatNumbersNTimes < 1) {
62
+ } else if (repeatNumbersNTimes < 1 || isNaN(repeatNumbersNTimes)) {
49
63
  return 1;
50
64
  }
65
+
66
+ // if this variable is not explicitly set, we calculate a reasonable value based on
67
+ // the number of items in the picker, avoiding regular jumps up/down the list
68
+ // whilst avoiding rendering too many items in the picker
69
+ if (repeatNumbersNTimesNotExplicitlySet) {
70
+ return Math.max(Math.round(180 / numberOfItems), 1);
71
+ }
51
72
  return Math.round(repeatNumbersNTimes);
52
- }, [disableInfiniteScroll, repeatNumbersNTimes]);
73
+ }, [disableInfiniteScroll, numberOfItems, repeatNumbersNTimes, repeatNumbersNTimesNotExplicitlySet]);
53
74
  const numbersForFlatList = (0, _react.useMemo)(() => {
54
75
  if (is12HourPicker) {
55
76
  return (0, _generateNumbers.generate12HourNumbers)({
56
77
  padNumbersWithZero,
57
78
  repeatNTimes: safeRepeatNumbersNTimes,
58
79
  disableInfiniteScroll,
59
- padWithNItems
80
+ padWithNItems,
81
+ interval
60
82
  });
61
83
  }
62
84
  return (0, _generateNumbers.generateNumbers)(numberOfItems, {
63
85
  padNumbersWithZero,
64
86
  repeatNTimes: safeRepeatNumbersNTimes,
65
87
  disableInfiniteScroll,
66
- padWithNItems
88
+ padWithNItems,
89
+ interval
67
90
  });
68
- }, [disableInfiniteScroll, is12HourPicker, numberOfItems, padNumbersWithZero, padWithNItems, safeRepeatNumbersNTimes]);
91
+ }, [disableInfiniteScroll, is12HourPicker, interval, numberOfItems, padNumbersWithZero, padWithNItems, safeRepeatNumbersNTimes]);
69
92
  const initialScrollIndex = (0, _react.useMemo)(() => (0, _getInitialScrollIndex.getInitialScrollIndex)({
70
93
  disableInfiniteScroll,
94
+ interval,
71
95
  numberOfItems,
72
96
  padWithNItems,
73
97
  repeatNumbersNTimes: safeRepeatNumbersNTimes,
74
98
  value: initialValue
75
- }), [disableInfiniteScroll, initialValue, numberOfItems, padWithNItems, safeRepeatNumbersNTimes]);
76
- const adjustedLimited = (0, _react.useMemo)(() => (0, _getAdjustedLimit.getAdjustedLimit)(limit, numberOfItems), [limit, numberOfItems]);
99
+ }), [disableInfiniteScroll, initialValue, interval, numberOfItems, padWithNItems, safeRepeatNumbersNTimes]);
100
+ const adjustedLimited = (0, _react.useMemo)(() => (0, _getAdjustedLimit.getAdjustedLimit)(limit, numberOfItems, interval), [interval, limit, numberOfItems]);
77
101
  const numberOfItemsToShow = 1 + padWithNItems * 2;
78
102
 
79
103
  // keep track of the latest duration as it scrolls
@@ -139,12 +163,13 @@ const DurationScroll = /*#__PURE__*/(0, _react.forwardRef)((props, ref) => {
139
163
  // this function is only used when the picker is in a modal and/or has Haptic/Audio feedback
140
164
  // it is used to ensure that the modal gets the latest duration on clicking
141
165
  // the confirm button, even if the scrollview is still scrolling
142
- if (!aggressivelyGetLatestDuration && !Haptics && !Audio) {
166
+ if (!aggressivelyGetLatestDuration && !Haptics && !Audio && !pickerFeedback) {
143
167
  return;
144
168
  }
145
169
  if (aggressivelyGetLatestDuration) {
146
170
  const newValues = (0, _getDurationAndIndexFromScrollOffset.getDurationAndIndexFromScrollOffset)({
147
171
  disableInfiniteScroll,
172
+ interval,
148
173
  itemHeight: styles.pickerItemContainer.height,
149
174
  numberOfItems,
150
175
  padWithNItems,
@@ -160,7 +185,7 @@ const DurationScroll = /*#__PURE__*/(0, _react.forwardRef)((props, ref) => {
160
185
  latestDuration.current = newValues.duration;
161
186
  }
162
187
  }
163
- if (Haptics || Audio) {
188
+ if (Haptics || Audio || pickerFeedback) {
164
189
  const feedbackIndex = Math.round((e.nativeEvent.contentOffset.y + styles.pickerItemContainer.height / 2) / styles.pickerItemContainer.height);
165
190
  if (feedbackIndex !== lastFeedbackIndex.current) {
166
191
  // this check stops the feedback firing when the component mounts
@@ -178,16 +203,24 @@ const DurationScroll = /*#__PURE__*/(0, _react.forwardRef)((props, ref) => {
178
203
  } catch {
179
204
  // do nothing
180
205
  }
206
+
207
+ // fire custom feedback if available
208
+ try {
209
+ pickerFeedback === null || pickerFeedback === void 0 || pickerFeedback();
210
+ } catch {
211
+ // do nothing
212
+ }
181
213
  }
182
214
  lastFeedbackIndex.current = feedbackIndex;
183
215
  }
184
216
  }
185
217
  },
186
218
  // eslint-disable-next-line react-hooks/exhaustive-deps
187
- [adjustedLimited.max, adjustedLimited.min, aggressivelyGetLatestDuration, clickSound, disableInfiniteScroll, numberOfItems, padWithNItems, styles.pickerItemContainer.height]);
219
+ [adjustedLimited.max, adjustedLimited.min, aggressivelyGetLatestDuration, clickSound, disableInfiniteScroll, interval, numberOfItems, padWithNItems, styles.pickerItemContainer.height]);
188
220
  const onMomentumScrollEnd = (0, _react.useCallback)(e => {
189
221
  const newValues = (0, _getDurationAndIndexFromScrollOffset.getDurationAndIndexFromScrollOffset)({
190
222
  disableInfiniteScroll,
223
+ interval,
191
224
  itemHeight: styles.pickerItemContainer.height,
192
225
  numberOfItems,
193
226
  padWithNItems,
@@ -217,11 +250,14 @@ const DurationScroll = /*#__PURE__*/(0, _react.forwardRef)((props, ref) => {
217
250
  newValues.duration = adjustedLimited.min;
218
251
  }
219
252
  onDurationChange(newValues.duration);
220
- }, [adjustedLimited.max, adjustedLimited.min, numbersForFlatList.length, disableInfiniteScroll, numberOfItems, onDurationChange, padWithNItems, styles.pickerItemContainer.height]);
253
+ }, [disableInfiniteScroll, interval, styles.pickerItemContainer.height, numberOfItems, padWithNItems, adjustedLimited.max, adjustedLimited.min, onDurationChange, numbersForFlatList.length]);
221
254
  const onViewableItemsChanged = (0, _react.useCallback)(({
222
255
  viewableItems
223
256
  }) => {
224
257
  var _viewableItems$, _viewableItems$2;
258
+ if (numberOfItems === 1) {
259
+ return;
260
+ }
225
261
  if ((_viewableItems$ = viewableItems[0]) !== null && _viewableItems$ !== void 0 && _viewableItems$.index && viewableItems[0].index < numberOfItems * 0.5) {
226
262
  var _flatListRef$current3;
227
263
  (_flatListRef$current3 = flatListRef.current) === null || _flatListRef$current3 === void 0 || _flatListRef$current3.scrollToIndex({
@@ -236,17 +272,36 @@ const DurationScroll = /*#__PURE__*/(0, _react.forwardRef)((props, ref) => {
236
272
  });
237
273
  }
238
274
  }, [numberOfItems, safeRepeatNumbersNTimes]);
275
+ const [viewabilityConfigCallbackPairs, setViewabilityConfigCallbackPairs] = (0, _react.useState)(!disableInfiniteScroll ? [{
276
+ viewabilityConfig: {
277
+ viewAreaCoveragePercentThreshold: 0
278
+ },
279
+ onViewableItemsChanged: onViewableItemsChanged
280
+ }] : undefined);
281
+ const [flatListRenderKey, setFlatListRenderKey] = (0, _react.useState)(0);
282
+ const initialRender = (0, _react.useRef)(true);
283
+ (0, _react.useEffect)(() => {
284
+ // don't run on first render
285
+ if (initialRender.current) {
286
+ initialRender.current = false;
287
+ return;
288
+ }
289
+
290
+ // if the onViewableItemsChanged callback changes, we need to update viewabilityConfigCallbackPairs
291
+ // which requires the FlatList to be remounted, hence the increase of the FlatList key
292
+ setFlatListRenderKey(prev => prev + 1);
293
+ setViewabilityConfigCallbackPairs(!disableInfiniteScroll ? [{
294
+ viewabilityConfig: {
295
+ viewAreaCoveragePercentThreshold: 0
296
+ },
297
+ onViewableItemsChanged: onViewableItemsChanged
298
+ }] : undefined);
299
+ }, [disableInfiniteScroll, onViewableItemsChanged]);
239
300
  const getItemLayout = (0, _react.useCallback)((_, index) => ({
240
301
  length: styles.pickerItemContainer.height,
241
302
  offset: styles.pickerItemContainer.height * index,
242
303
  index
243
304
  }), [styles.pickerItemContainer.height]);
244
- const viewabilityConfigCallbackPairs = (0, _react.useRef)([{
245
- viewabilityConfig: {
246
- viewAreaCoveragePercentThreshold: 0
247
- },
248
- onViewableItemsChanged: onViewableItemsChanged
249
- }]);
250
305
  (0, _react.useImperativeHandle)(ref, () => ({
251
306
  reset: options => {
252
307
  var _flatListRef$current5;
@@ -261,6 +316,7 @@ const DurationScroll = /*#__PURE__*/(0, _react.forwardRef)((props, ref) => {
261
316
  animated: (options === null || options === void 0 ? void 0 : options.animated) ?? false,
262
317
  index: (0, _getInitialScrollIndex.getInitialScrollIndex)({
263
318
  disableInfiniteScroll,
319
+ interval,
264
320
  numberOfItems,
265
321
  padWithNItems,
266
322
  repeatNumbersNTimes: safeRepeatNumbersNTimes,
@@ -278,6 +334,7 @@ const DurationScroll = /*#__PURE__*/(0, _react.forwardRef)((props, ref) => {
278
334
  }, isDisabled && styles.disabledPickerContainer],
279
335
  testID: testID
280
336
  }, /*#__PURE__*/_react.default.createElement(FlatList, {
337
+ key: flatListRenderKey,
281
338
  ref: flatListRef,
282
339
  data: numbersForFlatList,
283
340
  decelerationRate: 0.88,
@@ -292,11 +349,11 @@ const DurationScroll = /*#__PURE__*/(0, _react.forwardRef)((props, ref) => {
292
349
  scrollEventThrottle: 16,
293
350
  showsVerticalScrollIndicator: false,
294
351
  snapToAlignment: "start"
295
- // used in place of snapToOffset due to bug on Android
352
+ // used in place of snapToInterval due to bug on Android
296
353
  ,
297
354
  snapToOffsets: [...Array(numbersForFlatList.length)].map((_, i) => i * styles.pickerItemContainer.height),
298
355
  testID: "duration-scroll-flatlist",
299
- viewabilityConfigCallbackPairs: !disableInfiniteScroll ? viewabilityConfigCallbackPairs === null || viewabilityConfigCallbackPairs === void 0 ? void 0 : viewabilityConfigCallbackPairs.current : undefined,
356
+ viewabilityConfigCallbackPairs: viewabilityConfigCallbackPairs,
300
357
  windowSize: numberOfItemsToShow
301
358
  }), /*#__PURE__*/_react.default.createElement(_reactNative.View, {
302
359
  pointerEvents: "none",