react-native-timer-picker 1.10.2 → 2.0.0
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.
- package/README.md +82 -53
- package/dist/commonjs/components/DurationScroll/index.js +154 -76
- package/dist/commonjs/components/DurationScroll/index.js.map +1 -1
- package/dist/commonjs/components/DurationScroll/types.js.map +1 -1
- package/dist/commonjs/components/TimerPicker/index.js +31 -10
- package/dist/commonjs/components/TimerPicker/index.js.map +1 -1
- package/dist/commonjs/components/TimerPicker/types.js.map +1 -1
- package/dist/commonjs/tests/DurationScroll.test.js +3 -3
- package/dist/commonjs/tests/DurationScroll.test.js.map +1 -1
- package/dist/commonjs/tests/Modal.test.js +3 -3
- package/dist/commonjs/tests/Modal.test.js.map +1 -1
- package/dist/commonjs/tests/TimerPicker.test.js +4 -4
- package/dist/commonjs/tests/TimerPicker.test.js.map +1 -1
- package/dist/commonjs/tests/TimerPickerModal.test.js +6 -6
- package/dist/commonjs/tests/TimerPickerModal.test.js.map +1 -1
- package/dist/commonjs/utils/generateNumbers.js +7 -6
- package/dist/commonjs/utils/generateNumbers.js.map +1 -1
- package/dist/commonjs/utils/getAdjustedLimit.js +5 -4
- package/dist/commonjs/utils/getAdjustedLimit.js.map +1 -1
- package/dist/commonjs/utils/getDurationAndIndexFromScrollOffset.js +24 -0
- package/dist/commonjs/utils/getDurationAndIndexFromScrollOffset.js.map +1 -0
- package/dist/commonjs/utils/getInitialScrollIndex.js +19 -0
- package/dist/commonjs/utils/getInitialScrollIndex.js.map +1 -0
- package/dist/module/components/DurationScroll/index.js +154 -76
- package/dist/module/components/DurationScroll/index.js.map +1 -1
- package/dist/module/components/DurationScroll/types.js.map +1 -1
- package/dist/module/components/TimerPicker/index.js +31 -10
- package/dist/module/components/TimerPicker/index.js.map +1 -1
- package/dist/module/components/TimerPicker/types.js.map +1 -1
- package/dist/module/tests/DurationScroll.test.js +3 -3
- package/dist/module/tests/DurationScroll.test.js.map +1 -1
- package/dist/module/tests/Modal.test.js +3 -3
- package/dist/module/tests/Modal.test.js.map +1 -1
- package/dist/module/tests/TimerPicker.test.js +4 -4
- package/dist/module/tests/TimerPicker.test.js.map +1 -1
- package/dist/module/tests/TimerPickerModal.test.js +6 -6
- package/dist/module/tests/TimerPickerModal.test.js.map +1 -1
- package/dist/module/utils/generateNumbers.js +7 -6
- package/dist/module/utils/generateNumbers.js.map +1 -1
- package/dist/module/utils/getAdjustedLimit.js +5 -4
- package/dist/module/utils/getAdjustedLimit.js.map +1 -1
- package/dist/module/utils/getDurationAndIndexFromScrollOffset.js +17 -0
- package/dist/module/utils/getDurationAndIndexFromScrollOffset.js.map +1 -0
- package/dist/module/utils/getInitialScrollIndex.js +12 -0
- package/dist/module/utils/getInitialScrollIndex.js.map +1 -0
- package/dist/typescript/components/DurationScroll/types.d.ts +4 -1
- package/dist/typescript/components/TimerPicker/types.d.ts +7 -0
- package/dist/typescript/utils/generateNumbers.d.ts +3 -1
- package/dist/typescript/utils/getAdjustedLimit.d.ts +1 -1
- package/dist/typescript/utils/getDurationAndIndexFromScrollOffset.d.ts +11 -0
- package/dist/typescript/utils/{getScrollIndex.d.ts → getInitialScrollIndex.d.ts} +3 -1
- package/package.json +14 -10
- package/dist/commonjs/utils/getScrollIndex.js +0 -17
- package/dist/commonjs/utils/getScrollIndex.js.map +0 -1
- package/dist/module/utils/getScrollIndex.js +0 -10
- package/dist/module/utils/getScrollIndex.js.map +0 -1
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
|
-
|
|
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
|
-
|
|
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
|
|
428
|
-
| :------------------------------: |
|
|
429
|
-
| onDurationChange | Callback when the duration changes
|
|
430
|
-
| initialValue | Initial value for the picker
|
|
431
|
-
| hideHours | Hide the hours picker
|
|
432
|
-
| hideMinutes | Hide the minutes picker
|
|
433
|
-
| hideSeconds | Hide the seconds picker
|
|
434
|
-
| hoursPickerIsDisabled | Disable the hours picker picker
|
|
435
|
-
| minutesPickerIsDisabled | Disable the minutes picker picker
|
|
436
|
-
| secondsPickerIsDisabled | Disable the seconds picker picker
|
|
437
|
-
| hourLimit | Limit on the hours it is possible to select
|
|
438
|
-
| minuteLimit | Limit on the minutes it is possible to select
|
|
439
|
-
| secondLimit | Limit on the seconds it is possible to select
|
|
440
|
-
|
|
|
441
|
-
|
|
|
442
|
-
|
|
|
443
|
-
|
|
|
444
|
-
|
|
|
445
|
-
|
|
|
446
|
-
|
|
|
447
|
-
|
|
|
448
|
-
|
|
|
449
|
-
|
|
|
450
|
-
|
|
|
451
|
-
|
|
|
452
|
-
|
|
|
453
|
-
|
|
|
454
|
-
|
|
|
455
|
-
|
|
|
456
|
-
|
|
|
457
|
-
|
|
|
458
|
-
|
|
|
459
|
-
|
|
|
460
|
-
|
|
|
461
|
-
|
|
|
462
|
-
|
|
|
463
|
-
|
|
|
464
|
-
|
|
|
465
|
-
|
|
|
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,16 +498,14 @@ 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.
|
|
493
504
|
|
|
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
|
|
505
|
+
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.
|
|
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`
|
|
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
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
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).
|
|
@@ -9,7 +9,8 @@ var _reactNative = require("react-native");
|
|
|
9
9
|
var _colorToRgba = require("../../utils/colorToRgba");
|
|
10
10
|
var _generateNumbers = require("../../utils/generateNumbers");
|
|
11
11
|
var _getAdjustedLimit = require("../../utils/getAdjustedLimit");
|
|
12
|
-
var
|
|
12
|
+
var _getDurationAndIndexFromScrollOffset = require("../../utils/getDurationAndIndexFromScrollOffset");
|
|
13
|
+
var _getInitialScrollIndex = require("../../utils/getInitialScrollIndex");
|
|
13
14
|
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
|
14
15
|
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
|
15
16
|
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
|
|
@@ -25,45 +26,78 @@ const DurationScroll = /*#__PURE__*/(0, _react.forwardRef)((props, ref) => {
|
|
|
25
26
|
FlatList = _reactNative.FlatList,
|
|
26
27
|
Haptics,
|
|
27
28
|
initialValue = 0,
|
|
29
|
+
interval,
|
|
28
30
|
is12HourPicker,
|
|
29
31
|
isDisabled,
|
|
30
32
|
label,
|
|
31
33
|
limit,
|
|
32
34
|
LinearGradient,
|
|
33
|
-
|
|
35
|
+
maximumValue,
|
|
34
36
|
onDurationChange,
|
|
35
37
|
padNumbersWithZero = false,
|
|
36
38
|
padWithNItems,
|
|
39
|
+
pickerFeedback,
|
|
37
40
|
pickerGradientOverlayProps,
|
|
38
41
|
pmLabel,
|
|
39
42
|
repeatNumbersNTimes = 3,
|
|
43
|
+
repeatNumbersNTimesNotExplicitlySet,
|
|
40
44
|
styles,
|
|
41
45
|
testID,
|
|
42
46
|
topPickerGradientOverlayProps
|
|
43
47
|
} = props;
|
|
44
|
-
const
|
|
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]);
|
|
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
|
+
}
|
|
60
|
+
if (!disableInfiniteScroll && repeatNumbersNTimes < 2) {
|
|
61
|
+
return 2;
|
|
62
|
+
} else if (repeatNumbersNTimes < 1) {
|
|
63
|
+
return 1;
|
|
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
|
+
}
|
|
72
|
+
return Math.round(repeatNumbersNTimes);
|
|
73
|
+
}, [disableInfiniteScroll, numberOfItems, repeatNumbersNTimes, repeatNumbersNTimesNotExplicitlySet]);
|
|
74
|
+
const numbersForFlatList = (0, _react.useMemo)(() => {
|
|
45
75
|
if (is12HourPicker) {
|
|
46
76
|
return (0, _generateNumbers.generate12HourNumbers)({
|
|
47
77
|
padNumbersWithZero,
|
|
48
|
-
repeatNTimes:
|
|
78
|
+
repeatNTimes: safeRepeatNumbersNTimes,
|
|
49
79
|
disableInfiniteScroll,
|
|
50
|
-
padWithNItems
|
|
80
|
+
padWithNItems,
|
|
81
|
+
interval
|
|
51
82
|
});
|
|
52
83
|
}
|
|
53
84
|
return (0, _generateNumbers.generateNumbers)(numberOfItems, {
|
|
54
85
|
padNumbersWithZero,
|
|
55
|
-
repeatNTimes:
|
|
86
|
+
repeatNTimes: safeRepeatNumbersNTimes,
|
|
56
87
|
disableInfiniteScroll,
|
|
57
|
-
padWithNItems
|
|
88
|
+
padWithNItems,
|
|
89
|
+
interval
|
|
58
90
|
});
|
|
59
|
-
}, [disableInfiniteScroll, is12HourPicker, numberOfItems, padNumbersWithZero, padWithNItems,
|
|
60
|
-
const initialScrollIndex = (0, _react.useMemo)(() => (0,
|
|
91
|
+
}, [disableInfiniteScroll, is12HourPicker, interval, numberOfItems, padNumbersWithZero, padWithNItems, safeRepeatNumbersNTimes]);
|
|
92
|
+
const initialScrollIndex = (0, _react.useMemo)(() => (0, _getInitialScrollIndex.getInitialScrollIndex)({
|
|
93
|
+
disableInfiniteScroll,
|
|
94
|
+
interval,
|
|
61
95
|
numberOfItems,
|
|
62
96
|
padWithNItems,
|
|
63
|
-
repeatNumbersNTimes,
|
|
97
|
+
repeatNumbersNTimes: safeRepeatNumbersNTimes,
|
|
64
98
|
value: initialValue
|
|
65
|
-
}), [initialValue, numberOfItems, padWithNItems,
|
|
66
|
-
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]);
|
|
67
101
|
const numberOfItemsToShow = 1 + padWithNItems * 2;
|
|
68
102
|
|
|
69
103
|
// keep track of the latest duration as it scrolls
|
|
@@ -97,28 +131,6 @@ const DurationScroll = /*#__PURE__*/(0, _react.forwardRef)((props, ref) => {
|
|
|
97
131
|
};
|
|
98
132
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
99
133
|
}, [Audio]);
|
|
100
|
-
(0, _react.useImperativeHandle)(ref, () => ({
|
|
101
|
-
reset: options => {
|
|
102
|
-
var _flatListRef$current;
|
|
103
|
-
(_flatListRef$current = flatListRef.current) === null || _flatListRef$current === void 0 || _flatListRef$current.scrollToIndex({
|
|
104
|
-
animated: (options === null || options === void 0 ? void 0 : options.animated) ?? false,
|
|
105
|
-
index: initialScrollIndex
|
|
106
|
-
});
|
|
107
|
-
},
|
|
108
|
-
setValue: (value, options) => {
|
|
109
|
-
var _flatListRef$current2;
|
|
110
|
-
(_flatListRef$current2 = flatListRef.current) === null || _flatListRef$current2 === void 0 || _flatListRef$current2.scrollToIndex({
|
|
111
|
-
animated: (options === null || options === void 0 ? void 0 : options.animated) ?? false,
|
|
112
|
-
index: (0, _getScrollIndex.getScrollIndex)({
|
|
113
|
-
numberOfItems,
|
|
114
|
-
padWithNItems,
|
|
115
|
-
repeatNumbersNTimes,
|
|
116
|
-
value: value
|
|
117
|
-
})
|
|
118
|
-
});
|
|
119
|
-
},
|
|
120
|
-
latestDuration: latestDuration
|
|
121
|
-
}));
|
|
122
134
|
const renderItem = (0, _react.useCallback)(({
|
|
123
135
|
item
|
|
124
136
|
}) => {
|
|
@@ -151,23 +163,29 @@ const DurationScroll = /*#__PURE__*/(0, _react.forwardRef)((props, ref) => {
|
|
|
151
163
|
// this function is only used when the picker is in a modal and/or has Haptic/Audio feedback
|
|
152
164
|
// it is used to ensure that the modal gets the latest duration on clicking
|
|
153
165
|
// the confirm button, even if the scrollview is still scrolling
|
|
154
|
-
if (!aggressivelyGetLatestDuration && !Haptics && !Audio) {
|
|
166
|
+
if (!aggressivelyGetLatestDuration && !Haptics && !Audio && !pickerFeedback) {
|
|
155
167
|
return;
|
|
156
168
|
}
|
|
157
169
|
if (aggressivelyGetLatestDuration) {
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
170
|
+
const newValues = (0, _getDurationAndIndexFromScrollOffset.getDurationAndIndexFromScrollOffset)({
|
|
171
|
+
disableInfiniteScroll,
|
|
172
|
+
interval,
|
|
173
|
+
itemHeight: styles.pickerItemContainer.height,
|
|
174
|
+
numberOfItems,
|
|
175
|
+
padWithNItems,
|
|
176
|
+
yContentOffset: e.nativeEvent.contentOffset.y
|
|
177
|
+
});
|
|
178
|
+
if (newValues.duration !== latestDuration.current) {
|
|
161
179
|
// check limits
|
|
162
|
-
if (
|
|
163
|
-
|
|
164
|
-
} else if (
|
|
165
|
-
|
|
180
|
+
if (newValues.duration > adjustedLimited.max) {
|
|
181
|
+
newValues.duration = adjustedLimited.max;
|
|
182
|
+
} else if (newValues.duration < adjustedLimited.min) {
|
|
183
|
+
newValues.duration = adjustedLimited.min;
|
|
166
184
|
}
|
|
167
|
-
latestDuration.current =
|
|
185
|
+
latestDuration.current = newValues.duration;
|
|
168
186
|
}
|
|
169
187
|
}
|
|
170
|
-
if (Haptics || Audio) {
|
|
188
|
+
if (Haptics || Audio || pickerFeedback) {
|
|
171
189
|
const feedbackIndex = Math.round((e.nativeEvent.contentOffset.y + styles.pickerItemContainer.height / 2) / styles.pickerItemContainer.height);
|
|
172
190
|
if (feedbackIndex !== lastFeedbackIndex.current) {
|
|
173
191
|
// this check stops the feedback firing when the component mounts
|
|
@@ -185,70 +203,129 @@ const DurationScroll = /*#__PURE__*/(0, _react.forwardRef)((props, ref) => {
|
|
|
185
203
|
} catch {
|
|
186
204
|
// do nothing
|
|
187
205
|
}
|
|
206
|
+
|
|
207
|
+
// fire custom feedback if available
|
|
208
|
+
try {
|
|
209
|
+
pickerFeedback === null || pickerFeedback === void 0 || pickerFeedback();
|
|
210
|
+
} catch {
|
|
211
|
+
// do nothing
|
|
212
|
+
}
|
|
188
213
|
}
|
|
189
214
|
lastFeedbackIndex.current = feedbackIndex;
|
|
190
215
|
}
|
|
191
216
|
}
|
|
192
217
|
},
|
|
193
218
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
194
|
-
[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]);
|
|
195
220
|
const onMomentumScrollEnd = (0, _react.useCallback)(e => {
|
|
196
|
-
const
|
|
197
|
-
|
|
221
|
+
const newValues = (0, _getDurationAndIndexFromScrollOffset.getDurationAndIndexFromScrollOffset)({
|
|
222
|
+
disableInfiniteScroll,
|
|
223
|
+
interval,
|
|
224
|
+
itemHeight: styles.pickerItemContainer.height,
|
|
225
|
+
numberOfItems,
|
|
226
|
+
padWithNItems,
|
|
227
|
+
yContentOffset: e.nativeEvent.contentOffset.y
|
|
228
|
+
});
|
|
198
229
|
|
|
199
230
|
// check limits
|
|
200
|
-
if (
|
|
201
|
-
var _flatListRef$
|
|
202
|
-
const targetScrollIndex =
|
|
203
|
-
(_flatListRef$
|
|
231
|
+
if (newValues.duration > adjustedLimited.max) {
|
|
232
|
+
var _flatListRef$current;
|
|
233
|
+
const targetScrollIndex = newValues.index - (newValues.duration - adjustedLimited.max);
|
|
234
|
+
(_flatListRef$current = flatListRef.current) === null || _flatListRef$current === void 0 || _flatListRef$current.scrollToIndex({
|
|
204
235
|
animated: true,
|
|
205
236
|
index:
|
|
206
237
|
// guard against scrolling beyond end of list
|
|
207
238
|
targetScrollIndex >= 0 ? targetScrollIndex : adjustedLimited.max - 1
|
|
208
239
|
}); // scroll down to max
|
|
209
|
-
|
|
210
|
-
} else if (
|
|
211
|
-
var _flatListRef$
|
|
212
|
-
const targetScrollIndex =
|
|
213
|
-
(_flatListRef$
|
|
240
|
+
newValues.duration = adjustedLimited.max;
|
|
241
|
+
} else if (newValues.duration < adjustedLimited.min) {
|
|
242
|
+
var _flatListRef$current2;
|
|
243
|
+
const targetScrollIndex = newValues.index + (adjustedLimited.min - newValues.duration);
|
|
244
|
+
(_flatListRef$current2 = flatListRef.current) === null || _flatListRef$current2 === void 0 || _flatListRef$current2.scrollToIndex({
|
|
214
245
|
animated: true,
|
|
215
246
|
index:
|
|
216
247
|
// guard against scrolling beyond end of list
|
|
217
|
-
targetScrollIndex <=
|
|
248
|
+
targetScrollIndex <= numbersForFlatList.length - 1 ? targetScrollIndex : adjustedLimited.min
|
|
218
249
|
}); // scroll up to min
|
|
219
|
-
|
|
250
|
+
newValues.duration = adjustedLimited.min;
|
|
220
251
|
}
|
|
221
|
-
onDurationChange(
|
|
222
|
-
}, [
|
|
252
|
+
onDurationChange(newValues.duration);
|
|
253
|
+
}, [disableInfiniteScroll, interval, styles.pickerItemContainer.height, numberOfItems, padWithNItems, adjustedLimited.max, adjustedLimited.min, onDurationChange, numbersForFlatList.length]);
|
|
223
254
|
const onViewableItemsChanged = (0, _react.useCallback)(({
|
|
224
255
|
viewableItems
|
|
225
256
|
}) => {
|
|
226
257
|
var _viewableItems$, _viewableItems$2;
|
|
258
|
+
if (numberOfItems === 1) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
227
261
|
if ((_viewableItems$ = viewableItems[0]) !== null && _viewableItems$ !== void 0 && _viewableItems$.index && viewableItems[0].index < numberOfItems * 0.5) {
|
|
228
|
-
var _flatListRef$
|
|
229
|
-
(_flatListRef$
|
|
262
|
+
var _flatListRef$current3;
|
|
263
|
+
(_flatListRef$current3 = flatListRef.current) === null || _flatListRef$current3 === void 0 || _flatListRef$current3.scrollToIndex({
|
|
230
264
|
animated: false,
|
|
231
265
|
index: viewableItems[0].index + numberOfItems
|
|
232
266
|
});
|
|
233
|
-
} else if ((_viewableItems$2 = viewableItems[0]) !== null && _viewableItems$2 !== void 0 && _viewableItems$2.index && viewableItems[0].index >= numberOfItems * (
|
|
234
|
-
var _flatListRef$
|
|
235
|
-
(_flatListRef$
|
|
267
|
+
} else if ((_viewableItems$2 = viewableItems[0]) !== null && _viewableItems$2 !== void 0 && _viewableItems$2.index && viewableItems[0].index >= numberOfItems * (safeRepeatNumbersNTimes - 0.5)) {
|
|
268
|
+
var _flatListRef$current4;
|
|
269
|
+
(_flatListRef$current4 = flatListRef.current) === null || _flatListRef$current4 === void 0 || _flatListRef$current4.scrollToIndex({
|
|
236
270
|
animated: false,
|
|
237
|
-
index: viewableItems[0].index - numberOfItems
|
|
271
|
+
index: viewableItems[0].index - numberOfItems
|
|
238
272
|
});
|
|
239
273
|
}
|
|
240
|
-
}, [numberOfItems,
|
|
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]);
|
|
241
300
|
const getItemLayout = (0, _react.useCallback)((_, index) => ({
|
|
242
301
|
length: styles.pickerItemContainer.height,
|
|
243
302
|
offset: styles.pickerItemContainer.height * index,
|
|
244
303
|
index
|
|
245
304
|
}), [styles.pickerItemContainer.height]);
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
305
|
+
(0, _react.useImperativeHandle)(ref, () => ({
|
|
306
|
+
reset: options => {
|
|
307
|
+
var _flatListRef$current5;
|
|
308
|
+
(_flatListRef$current5 = flatListRef.current) === null || _flatListRef$current5 === void 0 || _flatListRef$current5.scrollToIndex({
|
|
309
|
+
animated: (options === null || options === void 0 ? void 0 : options.animated) ?? false,
|
|
310
|
+
index: initialScrollIndex
|
|
311
|
+
});
|
|
249
312
|
},
|
|
250
|
-
|
|
251
|
-
|
|
313
|
+
setValue: (value, options) => {
|
|
314
|
+
var _flatListRef$current6;
|
|
315
|
+
(_flatListRef$current6 = flatListRef.current) === null || _flatListRef$current6 === void 0 || _flatListRef$current6.scrollToIndex({
|
|
316
|
+
animated: (options === null || options === void 0 ? void 0 : options.animated) ?? false,
|
|
317
|
+
index: (0, _getInitialScrollIndex.getInitialScrollIndex)({
|
|
318
|
+
disableInfiniteScroll,
|
|
319
|
+
interval,
|
|
320
|
+
numberOfItems,
|
|
321
|
+
padWithNItems,
|
|
322
|
+
repeatNumbersNTimes: safeRepeatNumbersNTimes,
|
|
323
|
+
value: value
|
|
324
|
+
})
|
|
325
|
+
});
|
|
326
|
+
},
|
|
327
|
+
latestDuration: latestDuration
|
|
328
|
+
}));
|
|
252
329
|
return /*#__PURE__*/_react.default.createElement(_reactNative.View, {
|
|
253
330
|
pointerEvents: isDisabled ? "none" : undefined,
|
|
254
331
|
style: [{
|
|
@@ -257,8 +334,9 @@ const DurationScroll = /*#__PURE__*/(0, _react.forwardRef)((props, ref) => {
|
|
|
257
334
|
}, isDisabled && styles.disabledPickerContainer],
|
|
258
335
|
testID: testID
|
|
259
336
|
}, /*#__PURE__*/_react.default.createElement(FlatList, {
|
|
337
|
+
key: flatListRenderKey,
|
|
260
338
|
ref: flatListRef,
|
|
261
|
-
data:
|
|
339
|
+
data: numbersForFlatList,
|
|
262
340
|
decelerationRate: 0.88,
|
|
263
341
|
getItemLayout: getItemLayout,
|
|
264
342
|
initialScrollIndex: initialScrollIndex,
|
|
@@ -271,11 +349,11 @@ const DurationScroll = /*#__PURE__*/(0, _react.forwardRef)((props, ref) => {
|
|
|
271
349
|
scrollEventThrottle: 16,
|
|
272
350
|
showsVerticalScrollIndicator: false,
|
|
273
351
|
snapToAlignment: "start"
|
|
274
|
-
// used in place of
|
|
352
|
+
// used in place of snapToInterval due to bug on Android
|
|
275
353
|
,
|
|
276
|
-
snapToOffsets: [...Array(
|
|
354
|
+
snapToOffsets: [...Array(numbersForFlatList.length)].map((_, i) => i * styles.pickerItemContainer.height),
|
|
277
355
|
testID: "duration-scroll-flatlist",
|
|
278
|
-
viewabilityConfigCallbackPairs:
|
|
356
|
+
viewabilityConfigCallbackPairs: viewabilityConfigCallbackPairs,
|
|
279
357
|
windowSize: numberOfItemsToShow
|
|
280
358
|
}), /*#__PURE__*/_react.default.createElement(_reactNative.View, {
|
|
281
359
|
pointerEvents: "none",
|