react-native-timer-picker 1.1.4 → 1.2.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.
package/README.md CHANGED
@@ -308,9 +308,12 @@ return (
308
308
  | hideHours | Hide the hours picker | Boolean | false | false |
309
309
  | hideMinutes | Hide the minutes picker | Boolean | false | false |
310
310
  | hideSeconds | Hide the seconds picker | Boolean | false | false |
311
- | hourLabel | Label for the hours picker | String | h | false |
312
- | minuteLabel | Label for the minutes picker | String | m | false |
313
- | secondLabel | Label for the seconds picker | String | s | false |
311
+ | hourLimit | Limit on the hours it is possible to select | { max?: Number, min?: Number } | - | false |
312
+ | minuteLimit | Limit on the minutes it is possible to select | { max?: Number, min?: Number } | - | false |
313
+ | secondLimit | Limit on the seconds it is possible to select | { max?: Number, min?: Number } | - | false |
314
+ | hourLabel | Label for the hours picker | String \| React.ReactElement | h | false |
315
+ | minuteLabel | Label for the minutes picker | String \| React.ReactElement | m | false |
316
+ | secondLabel | Label for the seconds picker | String \| React.ReactElement | s | false |
314
317
  | padWithNItems | Number of items to pad the picker with on either side | Number | 1 | false |
315
318
  | disableInfiniteScroll | Disable the infinite scroll feature | Boolean | false | false |
316
319
  | 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 |
@@ -332,6 +335,7 @@ The following custom styles can be supplied to re-style the component in any way
332
335
  | pickerLabel | Style for the picker's labels | TextStyle |
333
336
  | pickerItemContainer | Container for each number in the picker | ViewStyle |
334
337
  | pickerItem | Style for each individual picker number | TextStyle |
338
+ | disabledPickerItem | Style for any numbers outside any set limits | TextStyle |
335
339
  | pickerGradientOverlay | Style for the gradient overlay (fade out) | ViewStyle |
336
340
 
337
341
  ### TimerPickerModal ⏰
@@ -373,14 +377,32 @@ The following custom styles can be supplied to re-style the component in any way
373
377
 
374
378
  ## Methods 🔄
375
379
 
376
- ### TimerPickerModal
380
+ ### TimerPicker
381
+
382
+ The library exposes a TimerPickerRef type, which can be used to type your ref to the picker:
383
+
384
+ ```javascript
385
+ const timerPickerRef = useRef<TimerPickerRef>(null);
386
+ ```
387
+
388
+ It has the following available methods:
377
389
 
378
390
  `reset` - imperative method to reset the selected duration to their initial values.
379
391
 
380
392
  ```javascript
381
- timerPickerModalRef.current.reset();
393
+ timerPickerRef.current.reset(options?: { animated: boolean });
382
394
  ```
383
395
 
396
+ `setValue` - imperative method to set the selected duration to a particular value
397
+
398
+ ```javascript
399
+ timerPickerRef.current.setValue({ hours: number, minutes: number, seconds: number }, options?: { animated: boolean });
400
+ ```
401
+
402
+ ### TimerPickerModal
403
+
404
+ An identical ref is also exposed for the TimerPickerModal component.
405
+
384
406
  ## License 📝
385
407
 
386
408
  This project is licensed under the [MIT License](LICENSE).
@@ -11,5 +11,6 @@ interface ModalProps {
11
11
  overlayStyle?: any;
12
12
  testID?: string;
13
13
  }
14
- export declare const Modal: ({ children, onOverlayPress, onHide, isVisible, animationDuration, overlayOpacity, modalProps, contentStyle, overlayStyle, testID }: ModalProps) => React.ReactElement;
15
- export default Modal;
14
+ export declare const Modal: ({ children, onOverlayPress, onHide, isVisible, animationDuration, overlayOpacity, modalProps, contentStyle, overlayStyle, testID, }: ModalProps) => React.ReactElement;
15
+ declare const _default: React.MemoExoticComponent<({ children, onOverlayPress, onHide, isVisible, animationDuration, overlayOpacity, modalProps, contentStyle, overlayStyle, testID, }: ModalProps) => React.ReactElement<any, string | React.JSXElementConstructor<any>>>;
16
+ export default _default;
@@ -76,7 +76,7 @@ var Modal = function (_a) {
76
76
  react_native_1.Animated.timing(animatedOpacity.current, {
77
77
  easing: react_native_1.Easing.inOut(react_native_1.Easing.quad),
78
78
  // Using native driver in the modal makes the content flash
79
- useNativeDriver: false,
79
+ useNativeDriver: true,
80
80
  duration: animationDuration,
81
81
  toValue: 1,
82
82
  }).start();
@@ -85,7 +85,7 @@ var Modal = function (_a) {
85
85
  react_native_1.Animated.timing(animatedOpacity.current, {
86
86
  easing: react_native_1.Easing.inOut(react_native_1.Easing.quad),
87
87
  // Using native driver in the modal makes the content flash
88
- useNativeDriver: false,
88
+ useNativeDriver: true,
89
89
  duration: animationDuration,
90
90
  toValue: 0,
91
91
  }).start(function () {
@@ -118,4 +118,4 @@ var Modal = function (_a) {
118
118
  </react_native_1.Modal>);
119
119
  };
120
120
  exports.Modal = Modal;
121
- exports.default = exports.Modal;
121
+ exports.default = react_1.default.memo(exports.Modal);
@@ -1,6 +1,14 @@
1
1
  import React from "react";
2
2
  import { View } from "react-native";
3
3
  import { generateStyles } from "./TimerPicker.styles";
4
+ export interface DurationScrollRef {
5
+ reset: (options?: {
6
+ animated?: boolean;
7
+ }) => void;
8
+ setValue: (value: number, options?: {
9
+ animated?: boolean;
10
+ }) => void;
11
+ }
4
12
  type LinearGradientPoint = {
5
13
  x: number;
6
14
  y: number;
@@ -11,18 +19,23 @@ export type LinearGradientProps = React.ComponentProps<typeof View> & {
11
19
  start?: LinearGradientPoint | null;
12
20
  end?: LinearGradientPoint | null;
13
21
  };
22
+ export type LimitType = {
23
+ max?: number;
24
+ min?: number;
25
+ };
14
26
  interface DurationScrollProps {
15
27
  numberOfItems: number;
16
- label?: string;
17
- initialIndex?: number;
28
+ label?: string | React.ReactElement;
29
+ initialValue?: number;
18
30
  onDurationChange: (duration: number) => void;
19
31
  padNumbersWithZero?: boolean;
20
32
  disableInfiniteScroll?: boolean;
33
+ limit?: LimitType;
21
34
  padWithNItems: number;
22
35
  pickerGradientOverlayProps?: LinearGradientProps;
23
36
  LinearGradient?: any;
24
37
  testID?: string;
25
38
  styles: ReturnType<typeof generateStyles>;
26
39
  }
27
- declare const DurationScroll: ({ numberOfItems, label, initialIndex, onDurationChange, padNumbersWithZero, disableInfiniteScroll, padWithNItems, pickerGradientOverlayProps, LinearGradient, testID, styles, }: DurationScrollProps) => React.ReactElement;
28
- export default DurationScroll;
40
+ declare const _default: React.MemoExoticComponent<React.ForwardRefExoticComponent<DurationScrollProps & React.RefAttributes<DurationScrollRef>>>;
41
+ export default _default;
@@ -37,9 +37,13 @@ var react_1 = __importStar(require("react"));
37
37
  var react_native_1 = require("react-native");
38
38
  var generateNumbers_1 = require("../../utils/generateNumbers");
39
39
  var colorToRgba_1 = require("../../utils/colorToRgba");
40
- var DurationScroll = function (_a) {
40
+ var getAdjustedLimit_1 = require("../../utils/getAdjustedLimit");
41
+ var getScrollIndex_1 = require("../../utils/getScrollIndex");
42
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
43
+ var KEY_EXTRACTOR = function (_, index) { return index.toString(); };
44
+ var DurationScroll = (0, react_1.forwardRef)(function (_a, ref) {
41
45
  var _b, _c, _d, _e;
42
- var numberOfItems = _a.numberOfItems, label = _a.label, _f = _a.initialIndex, initialIndex = _f === void 0 ? 0 : _f, onDurationChange = _a.onDurationChange, _g = _a.padNumbersWithZero, padNumbersWithZero = _g === void 0 ? false : _g, _h = _a.disableInfiniteScroll, disableInfiniteScroll = _h === void 0 ? false : _h, padWithNItems = _a.padWithNItems, pickerGradientOverlayProps = _a.pickerGradientOverlayProps, LinearGradient = _a.LinearGradient, testID = _a.testID, styles = _a.styles;
46
+ var numberOfItems = _a.numberOfItems, label = _a.label, _f = _a.initialValue, initialValue = _f === void 0 ? 0 : _f, onDurationChange = _a.onDurationChange, _g = _a.padNumbersWithZero, padNumbersWithZero = _g === void 0 ? false : _g, _h = _a.disableInfiniteScroll, disableInfiniteScroll = _h === void 0 ? false : _h, limit = _a.limit, padWithNItems = _a.padWithNItems, pickerGradientOverlayProps = _a.pickerGradientOverlayProps, LinearGradient = _a.LinearGradient, testID = _a.testID, styles = _a.styles;
43
47
  var flatListRef = (0, react_1.useRef)(null);
44
48
  var data = (0, generateNumbers_1.generateNumbers)(numberOfItems, {
45
49
  padWithZero: padNumbersWithZero,
@@ -48,12 +52,99 @@ var DurationScroll = function (_a) {
48
52
  padWithNItems: padWithNItems,
49
53
  });
50
54
  var numberOfItemsToShow = 1 + padWithNItems * 2;
51
- var renderItem = function (_a) {
55
+ var adjustedLimited = (0, getAdjustedLimit_1.getAdjustedLimit)(limit, numberOfItems);
56
+ var initialScrollIndex = (0, getScrollIndex_1.getScrollIndex)({
57
+ value: initialValue,
58
+ numberOfItems: numberOfItems,
59
+ padWithNItems: padWithNItems,
60
+ disableInfiniteScroll: disableInfiniteScroll,
61
+ });
62
+ (0, react_1.useImperativeHandle)(ref, function () { return ({
63
+ reset: function (options) {
64
+ var _a, _b;
65
+ (_a = flatListRef.current) === null || _a === void 0 ? void 0 : _a.scrollToIndex({
66
+ animated: (_b = options === null || options === void 0 ? void 0 : options.animated) !== null && _b !== void 0 ? _b : false,
67
+ index: initialScrollIndex,
68
+ });
69
+ },
70
+ setValue: function (value, options) {
71
+ var _a, _b;
72
+ (_a = flatListRef.current) === null || _a === void 0 ? void 0 : _a.scrollToIndex({
73
+ animated: (_b = options === null || options === void 0 ? void 0 : options.animated) !== null && _b !== void 0 ? _b : false,
74
+ index: (0, getScrollIndex_1.getScrollIndex)({
75
+ value: value,
76
+ numberOfItems: numberOfItems,
77
+ padWithNItems: padWithNItems,
78
+ disableInfiniteScroll: disableInfiniteScroll,
79
+ }),
80
+ });
81
+ },
82
+ }); });
83
+ var renderItem = (0, react_1.useCallback)(function (_a) {
52
84
  var item = _a.item;
85
+ var intItem = parseInt(item);
53
86
  return (<react_native_1.View key={item} style={styles.pickerItemContainer} testID="picker-item">
54
- <react_native_1.Text style={styles.pickerItem}>{item}</react_native_1.Text>
55
- </react_native_1.View>);
56
- };
87
+ <react_native_1.Text style={[
88
+ styles.pickerItem,
89
+ intItem > adjustedLimited.max ||
90
+ intItem < adjustedLimited.min
91
+ ? styles.disabledPickerItem
92
+ : {},
93
+ ]}>
94
+ {item}
95
+ </react_native_1.Text>
96
+ </react_native_1.View>);
97
+ }, [
98
+ adjustedLimited.max,
99
+ adjustedLimited.min,
100
+ styles.disabledPickerItem,
101
+ styles.pickerItem,
102
+ styles.pickerItemContainer,
103
+ ]);
104
+ var onMomentumScrollEnd = (0, react_1.useCallback)(function (e) {
105
+ var _a, _b;
106
+ var newIndex = Math.round(e.nativeEvent.contentOffset.y /
107
+ styles.pickerItemContainer.height);
108
+ var newDuration = (disableInfiniteScroll
109
+ ? newIndex
110
+ : newIndex + padWithNItems) %
111
+ (numberOfItems + 1);
112
+ // check limits
113
+ if (newDuration > adjustedLimited.max) {
114
+ var targetScrollIndex = newIndex - (newDuration - adjustedLimited.max);
115
+ (_a = flatListRef.current) === null || _a === void 0 ? void 0 : _a.scrollToIndex({
116
+ animated: true,
117
+ index:
118
+ // guard against scrolling beyond end of list
119
+ targetScrollIndex >= 0
120
+ ? targetScrollIndex
121
+ : adjustedLimited.max - 1,
122
+ }); // scroll down to max
123
+ newDuration = adjustedLimited.max;
124
+ }
125
+ else if (newDuration < adjustedLimited.min) {
126
+ var targetScrollIndex = newIndex + (adjustedLimited.min - newDuration);
127
+ (_b = flatListRef.current) === null || _b === void 0 ? void 0 : _b.scrollToIndex({
128
+ animated: true,
129
+ index:
130
+ // guard against scrolling beyond end of list
131
+ targetScrollIndex <= data.length - 1
132
+ ? targetScrollIndex
133
+ : adjustedLimited.min,
134
+ }); // scroll up to min
135
+ newDuration = adjustedLimited.min;
136
+ }
137
+ onDurationChange(newDuration);
138
+ }, [
139
+ adjustedLimited.max,
140
+ adjustedLimited.min,
141
+ data.length,
142
+ disableInfiniteScroll,
143
+ numberOfItems,
144
+ onDurationChange,
145
+ padWithNItems,
146
+ styles.pickerItemContainer.height,
147
+ ]);
57
148
  var onViewableItemsChanged = (0, react_1.useCallback)(function (_a) {
58
149
  var _b, _c, _d, _e;
59
150
  var viewableItems = _a.viewableItems;
@@ -72,6 +163,11 @@ var DurationScroll = function (_a) {
72
163
  });
73
164
  }
74
165
  }, [numberOfItems]);
166
+ var getItemLayout = (0, react_1.useCallback)(function (_, index) { return ({
167
+ length: styles.pickerItemContainer.height,
168
+ offset: styles.pickerItemContainer.height * index,
169
+ index: index,
170
+ }); }, [styles.pickerItemContainer.height]);
75
171
  var viewabilityConfigCallbackPairs = (0, react_1.useRef)([
76
172
  {
77
173
  viewabilityConfig: { viewAreaCoveragePercentThreshold: 25 },
@@ -82,42 +178,35 @@ var DurationScroll = function (_a) {
82
178
  height: styles.pickerItemContainer.height * numberOfItemsToShow,
83
179
  overflow: "hidden",
84
180
  }}>
85
- <react_native_1.FlatList ref={flatListRef} data={data} getItemLayout={function (_, index) { return ({
86
- length: styles.pickerItemContainer.height,
87
- offset: styles.pickerItemContainer.height * index,
88
- index: index,
89
- }); }} initialScrollIndex={(initialIndex % numberOfItems) +
90
- numberOfItems +
91
- (disableInfiniteScroll ? padWithNItems : 0) -
92
- (padWithNItems - 1)} windowSize={numberOfItemsToShow} renderItem={renderItem} keyExtractor={function (_, index) { return index.toString(); }} showsVerticalScrollIndicator={false} decelerationRate="fast" scrollEventThrottle={16} snapToAlignment="start"
181
+ <react_native_1.FlatList ref={flatListRef} data={data} getItemLayout={getItemLayout} initialScrollIndex={initialScrollIndex} windowSize={numberOfItemsToShow} renderItem={renderItem} keyExtractor={KEY_EXTRACTOR} showsVerticalScrollIndicator={false} decelerationRate="fast" scrollEventThrottle={16} snapToAlignment="start"
93
182
  // used in place of snapToOffset due to bug on Android
94
183
  snapToOffsets={__spreadArray([], Array(data.length), true).map(function (_, i) { return i * styles.pickerItemContainer.height; })} viewabilityConfigCallbackPairs={!disableInfiniteScroll
95
184
  ? viewabilityConfigCallbackPairs === null || viewabilityConfigCallbackPairs === void 0 ? void 0 : viewabilityConfigCallbackPairs.current
96
- : undefined} onMomentumScrollEnd={function (e) {
97
- var newIndex = Math.round(e.nativeEvent.contentOffset.y /
98
- styles.pickerItemContainer.height);
99
- onDurationChange((disableInfiniteScroll ? newIndex : newIndex + padWithNItems) %
100
- (numberOfItems + 1));
101
- }} testID="duration-scroll-flatlist"/>
102
- <react_native_1.View style={styles.pickerLabelContainer}>
103
- <react_native_1.Text style={styles.pickerLabel}>{label}</react_native_1.Text>
104
- </react_native_1.View>
105
- {LinearGradient ? (<>
106
- <LinearGradient colors={[
185
+ : undefined} onMomentumScrollEnd={onMomentumScrollEnd} testID="duration-scroll-flatlist"/>
186
+ <react_native_1.View style={styles.pickerLabelContainer}>
187
+ {typeof label === "string" ? (<react_native_1.Text style={styles.pickerLabel}>{label}</react_native_1.Text>) : (label !== null && label !== void 0 ? label : null)}
188
+ </react_native_1.View>
189
+ {LinearGradient ? (<>
190
+ <LinearGradient colors={[
107
191
  (_b = styles.pickerContainer.backgroundColor) !== null && _b !== void 0 ? _b : "white",
108
192
  (0, colorToRgba_1.colorToRgba)({
109
- color: (_c = styles.pickerContainer.backgroundColor) !== null && _c !== void 0 ? _c : "white",
193
+ color: (_c = styles.pickerContainer
194
+ .backgroundColor) !== null && _c !== void 0 ? _c : "white",
110
195
  opacity: 0,
111
196
  }),
112
197
  ]} start={{ x: 1, y: 0.3 }} end={{ x: 1, y: 1 }} {...pickerGradientOverlayProps} style={[styles.pickerGradientOverlay, { top: 0 }]}/>
113
- <LinearGradient colors={[
198
+ <LinearGradient colors={[
114
199
  (0, colorToRgba_1.colorToRgba)({
115
- color: (_d = styles.pickerContainer.backgroundColor) !== null && _d !== void 0 ? _d : "white",
200
+ color: (_d = styles.pickerContainer
201
+ .backgroundColor) !== null && _d !== void 0 ? _d : "white",
116
202
  opacity: 0,
117
203
  }),
118
204
  (_e = styles.pickerContainer.backgroundColor) !== null && _e !== void 0 ? _e : "white",
119
- ]} start={{ x: 1, y: 0 }} end={{ x: 1, y: 0.7 }} {...pickerGradientOverlayProps} style={[styles.pickerGradientOverlay, { bottom: -1 }]}/>
120
- </>) : null}
121
- </react_native_1.View>);
122
- };
123
- exports.default = DurationScroll;
205
+ ]} start={{ x: 1, y: 0 }} end={{ x: 1, y: 0.7 }} {...pickerGradientOverlayProps} style={[
206
+ styles.pickerGradientOverlay,
207
+ { bottom: -1 },
208
+ ]}/>
209
+ </>) : null}
210
+ </react_native_1.View>);
211
+ });
212
+ exports.default = react_1.default.memo(DurationScroll);
@@ -7,6 +7,7 @@ export interface CustomTimerPickerStyles {
7
7
  pickerLabel?: any;
8
8
  pickerItemContainer?: any;
9
9
  pickerItem?: any;
10
+ disabledPickerItem?: any;
10
11
  pickerGradientOverlay?: any;
11
12
  }
12
13
  export declare const generateStyles: (customStyles: CustomTimerPickerStyles | undefined, options: {
@@ -17,5 +18,6 @@ export declare const generateStyles: (customStyles: CustomTimerPickerStyles | un
17
18
  pickerLabel: any;
18
19
  pickerItemContainer: any;
19
20
  pickerItem: any;
21
+ disabledPickerItem: any;
20
22
  pickerGradientOverlay: any;
21
23
  };
@@ -32,6 +32,7 @@ var generateStyles = function (customStyles, options) {
32
32
  pickerItem: __assign(__assign({ textAlignVertical: "center", fontSize: 25, color: (customStyles === null || customStyles === void 0 ? void 0 : customStyles.theme) === "dark"
33
33
  ? DARK_MODE_TEXT_COLOR
34
34
  : LIGHT_MODE_TEXT_COLOR }, customStyles === null || customStyles === void 0 ? void 0 : customStyles.text), customStyles === null || customStyles === void 0 ? void 0 : customStyles.pickerItem),
35
+ disabledPickerItem: __assign({ opacity: 0.2 }, customStyles === null || customStyles === void 0 ? void 0 : customStyles.disabledPickerItem),
35
36
  pickerGradientOverlay: __assign({ position: "absolute", left: 0, right: 0, height: options.padWithNItems === 0
36
37
  ? "30%"
37
38
  : ((_g = (_f = customStyles === null || customStyles === void 0 ? void 0 : customStyles.pickerItemContainer) === null || _f === void 0 ? void 0 : _f.height) !== null && _g !== void 0 ? _g : 50) * 0.8 }, customStyles === null || customStyles === void 0 ? void 0 : customStyles.pickerGradientOverlay),
@@ -1,7 +1,20 @@
1
1
  import React from "react";
2
2
  import { View } from "react-native";
3
+ import { LimitType } from "./DurationScroll";
3
4
  import { CustomTimerPickerStyles } from "./TimerPicker.styles";
4
5
  import { LinearGradientProps } from "./DurationScroll";
6
+ export interface TimerPickerRef {
7
+ reset: (options?: {
8
+ animated?: boolean;
9
+ }) => void;
10
+ setValue: (value: {
11
+ hours: number;
12
+ minutes: number;
13
+ seconds: number;
14
+ }, options?: {
15
+ animated?: boolean;
16
+ }) => void;
17
+ }
5
18
  export interface TimerPickerProps {
6
19
  onDurationChange?: (duration: {
7
20
  hours: number;
@@ -14,9 +27,12 @@ export interface TimerPickerProps {
14
27
  hideHours?: boolean;
15
28
  hideMinutes?: boolean;
16
29
  hideSeconds?: boolean;
17
- hourLabel?: string;
18
- minuteLabel?: string;
19
- secondLabel?: string;
30
+ hourLimit?: LimitType;
31
+ minuteLimit?: LimitType;
32
+ secondLimit?: LimitType;
33
+ hourLabel?: string | React.ReactElement;
34
+ minuteLabel?: string | React.ReactElement;
35
+ secondLabel?: string | React.ReactElement;
20
36
  padWithNItems?: number;
21
37
  disableInfiniteScroll?: boolean;
22
38
  LinearGradient?: any;
@@ -24,5 +40,5 @@ export interface TimerPickerProps {
24
40
  pickerGradientOverlayProps?: LinearGradientProps;
25
41
  styles?: CustomTimerPickerStyles;
26
42
  }
27
- declare const TimerPicker: ({ onDurationChange, initialHours, initialMinutes, initialSeconds, hideHours, hideMinutes, hideSeconds, hourLabel, minuteLabel, secondLabel, padWithNItems, disableInfiniteScroll, LinearGradient, pickerContainerProps, pickerGradientOverlayProps, styles: customStyles, }: TimerPickerProps) => React.ReactElement;
28
- export default TimerPicker;
43
+ declare const _default: React.MemoExoticComponent<React.ForwardRefExoticComponent<TimerPickerProps & React.RefAttributes<TimerPickerRef>>>;
44
+ export default _default;
@@ -30,12 +30,14 @@ var react_1 = __importStar(require("react"));
30
30
  var react_native_1 = require("react-native");
31
31
  var DurationScroll_1 = __importDefault(require("./DurationScroll"));
32
32
  var TimerPicker_styles_1 = require("./TimerPicker.styles");
33
- var TimerPicker = function (_a) {
34
- var onDurationChange = _a.onDurationChange, _b = _a.initialHours, initialHours = _b === void 0 ? 0 : _b, _c = _a.initialMinutes, initialMinutes = _c === void 0 ? 0 : _c, _d = _a.initialSeconds, initialSeconds = _d === void 0 ? 0 : _d, _e = _a.hideHours, hideHours = _e === void 0 ? false : _e, _f = _a.hideMinutes, hideMinutes = _f === void 0 ? false : _f, _g = _a.hideSeconds, hideSeconds = _g === void 0 ? false : _g, _h = _a.hourLabel, hourLabel = _h === void 0 ? "h" : _h, _j = _a.minuteLabel, minuteLabel = _j === void 0 ? "m" : _j, _k = _a.secondLabel, secondLabel = _k === void 0 ? "s" : _k, _l = _a.padWithNItems, padWithNItems = _l === void 0 ? 1 : _l, _m = _a.disableInfiniteScroll, disableInfiniteScroll = _m === void 0 ? false : _m, LinearGradient = _a.LinearGradient, pickerContainerProps = _a.pickerContainerProps, pickerGradientOverlayProps = _a.pickerGradientOverlayProps, customStyles = _a.styles;
33
+ var TimerPicker = (0, react_1.forwardRef)(function (_a, ref) {
34
+ var onDurationChange = _a.onDurationChange, _b = _a.initialHours, initialHours = _b === void 0 ? 0 : _b, _c = _a.initialMinutes, initialMinutes = _c === void 0 ? 0 : _c, _d = _a.initialSeconds, initialSeconds = _d === void 0 ? 0 : _d, _e = _a.hideHours, hideHours = _e === void 0 ? false : _e, _f = _a.hideMinutes, hideMinutes = _f === void 0 ? false : _f, _g = _a.hideSeconds, hideSeconds = _g === void 0 ? false : _g, hourLimit = _a.hourLimit, minuteLimit = _a.minuteLimit, secondLimit = _a.secondLimit, _h = _a.hourLabel, hourLabel = _h === void 0 ? "h" : _h, _j = _a.minuteLabel, minuteLabel = _j === void 0 ? "m" : _j, _k = _a.secondLabel, secondLabel = _k === void 0 ? "s" : _k, _l = _a.padWithNItems, padWithNItems = _l === void 0 ? 1 : _l, _m = _a.disableInfiniteScroll, disableInfiniteScroll = _m === void 0 ? false : _m, LinearGradient = _a.LinearGradient, pickerContainerProps = _a.pickerContainerProps, pickerGradientOverlayProps = _a.pickerGradientOverlayProps, customStyles = _a.styles;
35
35
  var checkedPadWithNItems = padWithNItems >= 0 ? Math.round(padWithNItems) : 0;
36
- var styles = (0, TimerPicker_styles_1.generateStyles)(customStyles, {
37
- padWithNItems: checkedPadWithNItems,
38
- });
36
+ var styles = (0, react_1.useMemo)(function () {
37
+ return (0, TimerPicker_styles_1.generateStyles)(customStyles, {
38
+ padWithNItems: checkedPadWithNItems,
39
+ });
40
+ }, [checkedPadWithNItems, customStyles]);
39
41
  var _o = (0, react_1.useState)(initialHours), selectedHours = _o[0], setSelectedHours = _o[1];
40
42
  var _p = (0, react_1.useState)(initialMinutes), selectedMinutes = _p[0], setSelectedMinutes = _p[1];
41
43
  var _q = (0, react_1.useState)(initialSeconds), selectedSeconds = _q[0], setSelectedSeconds = _q[1];
@@ -47,10 +49,33 @@ var TimerPicker = function (_a) {
47
49
  });
48
50
  // eslint-disable-next-line react-hooks/exhaustive-deps
49
51
  }, [selectedHours, selectedMinutes, selectedSeconds]);
52
+ var hoursDurationScrollRef = (0, react_1.useRef)(null);
53
+ var minutesDurationScrollRef = (0, react_1.useRef)(null);
54
+ var secondsDurationScrollRef = (0, react_1.useRef)(null);
55
+ (0, react_1.useImperativeHandle)(ref, function () { return ({
56
+ reset: function (options) {
57
+ var _a, _b, _c;
58
+ setSelectedHours(initialHours);
59
+ setSelectedMinutes(initialMinutes);
60
+ setSelectedSeconds(initialSeconds);
61
+ (_a = hoursDurationScrollRef.current) === null || _a === void 0 ? void 0 : _a.reset(options);
62
+ (_b = minutesDurationScrollRef.current) === null || _b === void 0 ? void 0 : _b.reset(options);
63
+ (_c = secondsDurationScrollRef.current) === null || _c === void 0 ? void 0 : _c.reset(options);
64
+ },
65
+ setValue: function (value, options) {
66
+ var _a, _b, _c;
67
+ setSelectedHours(value.hours);
68
+ setSelectedMinutes(value.minutes);
69
+ setSelectedSeconds(value.seconds);
70
+ (_a = hoursDurationScrollRef.current) === null || _a === void 0 ? void 0 : _a.setValue(value.hours, options);
71
+ (_b = minutesDurationScrollRef.current) === null || _b === void 0 ? void 0 : _b.setValue(value.minutes, options);
72
+ (_c = secondsDurationScrollRef.current) === null || _c === void 0 ? void 0 : _c.setValue(value.seconds, options);
73
+ },
74
+ }); });
50
75
  return (<react_native_1.View {...pickerContainerProps} style={styles.pickerContainer} testID="timer-picker">
51
- {!hideHours ? (<DurationScroll_1.default numberOfItems={23} label={hourLabel} initialIndex={initialHours} onDurationChange={setSelectedHours} pickerGradientOverlayProps={pickerGradientOverlayProps} disableInfiniteScroll={disableInfiniteScroll} padWithNItems={checkedPadWithNItems} LinearGradient={LinearGradient} styles={styles} testID="duration-scroll-hour"/>) : null}
52
- {!hideMinutes ? (<DurationScroll_1.default numberOfItems={59} label={minuteLabel} initialIndex={initialMinutes} onDurationChange={setSelectedMinutes} padNumbersWithZero pickerGradientOverlayProps={pickerGradientOverlayProps} disableInfiniteScroll={disableInfiniteScroll} padWithNItems={checkedPadWithNItems} LinearGradient={LinearGradient} styles={styles} testID="duration-scroll-minute"/>) : null}
53
- {!hideSeconds ? (<DurationScroll_1.default numberOfItems={59} label={secondLabel} initialIndex={initialSeconds} onDurationChange={setSelectedSeconds} padNumbersWithZero pickerGradientOverlayProps={pickerGradientOverlayProps} disableInfiniteScroll={disableInfiniteScroll} padWithNItems={checkedPadWithNItems} LinearGradient={LinearGradient} styles={styles} testID="duration-scroll-second"/>) : null}
54
- </react_native_1.View>);
55
- };
56
- exports.default = TimerPicker;
76
+ {!hideHours ? (<DurationScroll_1.default ref={hoursDurationScrollRef} numberOfItems={23} label={hourLabel} initialValue={initialHours} onDurationChange={setSelectedHours} pickerGradientOverlayProps={pickerGradientOverlayProps} disableInfiniteScroll={disableInfiniteScroll} padWithNItems={checkedPadWithNItems} limit={hourLimit} LinearGradient={LinearGradient} styles={styles} testID="duration-scroll-hour"/>) : null}
77
+ {!hideMinutes ? (<DurationScroll_1.default ref={minutesDurationScrollRef} numberOfItems={59} label={minuteLabel} initialValue={initialMinutes} onDurationChange={setSelectedMinutes} padNumbersWithZero pickerGradientOverlayProps={pickerGradientOverlayProps} disableInfiniteScroll={disableInfiniteScroll} padWithNItems={checkedPadWithNItems} limit={minuteLimit} LinearGradient={LinearGradient} styles={styles} testID="duration-scroll-minute"/>) : null}
78
+ {!hideSeconds ? (<DurationScroll_1.default ref={secondsDurationScrollRef} numberOfItems={59} label={secondLabel} initialValue={initialSeconds} onDurationChange={setSelectedSeconds} padNumbersWithZero pickerGradientOverlayProps={pickerGradientOverlayProps} disableInfiniteScroll={disableInfiniteScroll} padWithNItems={checkedPadWithNItems} limit={secondLimit} LinearGradient={LinearGradient} styles={styles} testID="duration-scroll-second"/>) : null}
79
+ </react_native_1.View>);
80
+ });
81
+ exports.default = react_1.default.memo(TimerPicker);
@@ -3,8 +3,17 @@ import { View, Text } from "react-native";
3
3
  import { TimerPickerProps } from "./TimerPicker";
4
4
  import Modal from "./Modal";
5
5
  import { CustomTimerPickerModalStyles } from "./TimerPickerModal.styles";
6
- interface TimePickerModalRef {
7
- reset: () => void;
6
+ export interface TimerPickerModalRef {
7
+ reset: (options?: {
8
+ animated?: boolean;
9
+ }) => void;
10
+ setValue: (value: {
11
+ hours: number;
12
+ minutes: number;
13
+ seconds: number;
14
+ }, options?: {
15
+ animated?: boolean;
16
+ }) => void;
8
17
  }
9
18
  export interface TimerPickerModalProps extends TimerPickerProps {
10
19
  visible: boolean;
@@ -27,5 +36,5 @@ export interface TimerPickerModalProps extends TimerPickerProps {
27
36
  modalTitleProps?: React.ComponentProps<typeof Text>;
28
37
  styles?: CustomTimerPickerModalStyles;
29
38
  }
30
- declare const TimerPickerModal: React.ForwardRefExoticComponent<TimerPickerModalProps & React.RefAttributes<TimePickerModalRef>>;
31
- export default TimerPickerModal;
39
+ declare const _default: React.MemoExoticComponent<React.ForwardRefExoticComponent<TimerPickerModalProps & React.RefAttributes<TimerPickerModalRef>>>;
40
+ export default _default;
@@ -32,7 +32,7 @@ var TimerPicker_1 = __importDefault(require("./TimerPicker"));
32
32
  var Modal_1 = __importDefault(require("./Modal"));
33
33
  var TimerPickerModal_styles_1 = require("./TimerPickerModal.styles");
34
34
  var TimerPickerModal = (0, react_1.forwardRef)(function (_a, ref) {
35
- var visible = _a.visible, setIsVisible = _a.setIsVisible, onConfirm = _a.onConfirm, onCancel = _a.onCancel, onDurationChange = _a.onDurationChange, closeOnOverlayPress = _a.closeOnOverlayPress, _b = _a.initialHours, initialHours = _b === void 0 ? 0 : _b, _c = _a.initialMinutes, initialMinutes = _c === void 0 ? 0 : _c, _d = _a.initialSeconds, initialSeconds = _d === void 0 ? 0 : _d, _e = _a.hideHours, hideHours = _e === void 0 ? false : _e, _f = _a.hideMinutes, hideMinutes = _f === void 0 ? false : _f, _g = _a.hideSeconds, hideSeconds = _g === void 0 ? false : _g, _h = _a.hourLabel, hourLabel = _h === void 0 ? "h" : _h, _j = _a.minuteLabel, minuteLabel = _j === void 0 ? "m" : _j, _k = _a.secondLabel, secondLabel = _k === void 0 ? "s" : _k, _l = _a.padWithNItems, padWithNItems = _l === void 0 ? 1 : _l, _m = _a.disableInfiniteScroll, disableInfiniteScroll = _m === void 0 ? false : _m, _o = _a.hideCancelButton, hideCancelButton = _o === void 0 ? false : _o, _p = _a.confirmButtonText, confirmButtonText = _p === void 0 ? "Confirm" : _p, _q = _a.cancelButtonText, cancelButtonText = _q === void 0 ? "Cancel" : _q, modalTitle = _a.modalTitle, LinearGradient = _a.LinearGradient, modalProps = _a.modalProps, containerProps = _a.containerProps, contentContainerProps = _a.contentContainerProps, pickerContainerProps = _a.pickerContainerProps, buttonContainerProps = _a.buttonContainerProps, modalTitleProps = _a.modalTitleProps, pickerGradientOverlayProps = _a.pickerGradientOverlayProps, customStyles = _a.styles;
35
+ var visible = _a.visible, setIsVisible = _a.setIsVisible, onConfirm = _a.onConfirm, onCancel = _a.onCancel, onDurationChange = _a.onDurationChange, closeOnOverlayPress = _a.closeOnOverlayPress, _b = _a.initialHours, initialHours = _b === void 0 ? 0 : _b, _c = _a.initialMinutes, initialMinutes = _c === void 0 ? 0 : _c, _d = _a.initialSeconds, initialSeconds = _d === void 0 ? 0 : _d, _e = _a.hideHours, hideHours = _e === void 0 ? false : _e, _f = _a.hideMinutes, hideMinutes = _f === void 0 ? false : _f, _g = _a.hideSeconds, hideSeconds = _g === void 0 ? false : _g, hourLimit = _a.hourLimit, minuteLimit = _a.minuteLimit, secondLimit = _a.secondLimit, _h = _a.hourLabel, hourLabel = _h === void 0 ? "h" : _h, _j = _a.minuteLabel, minuteLabel = _j === void 0 ? "m" : _j, _k = _a.secondLabel, secondLabel = _k === void 0 ? "s" : _k, _l = _a.padWithNItems, padWithNItems = _l === void 0 ? 1 : _l, _m = _a.disableInfiniteScroll, disableInfiniteScroll = _m === void 0 ? false : _m, _o = _a.hideCancelButton, hideCancelButton = _o === void 0 ? false : _o, _p = _a.confirmButtonText, confirmButtonText = _p === void 0 ? "Confirm" : _p, _q = _a.cancelButtonText, cancelButtonText = _q === void 0 ? "Cancel" : _q, modalTitle = _a.modalTitle, LinearGradient = _a.LinearGradient, modalProps = _a.modalProps, containerProps = _a.containerProps, contentContainerProps = _a.contentContainerProps, pickerContainerProps = _a.pickerContainerProps, buttonContainerProps = _a.buttonContainerProps, modalTitleProps = _a.modalTitleProps, pickerGradientOverlayProps = _a.pickerGradientOverlayProps, customStyles = _a.styles;
36
36
  var styles = (0, TimerPickerModal_styles_1.generateStyles)(customStyles);
37
37
  var _r = (0, react_1.useState)({
38
38
  hours: initialHours,
@@ -61,8 +61,15 @@ var TimerPickerModal = (0, react_1.forwardRef)(function (_a, ref) {
61
61
  setSelectedDuration(confirmedDuration);
62
62
  onCancel === null || onCancel === void 0 ? void 0 : onCancel();
63
63
  };
64
+ // wrapped in useCallback to avoid unnecessary re-renders of TimerPicker
65
+ var durationChange = (0, react_1.useCallback)(function (duration) {
66
+ setSelectedDuration(duration);
67
+ onDurationChange === null || onDurationChange === void 0 ? void 0 : onDurationChange(duration);
68
+ }, [onDurationChange]);
69
+ var timerPickerRef = (0, react_1.useRef)(null);
64
70
  (0, react_1.useImperativeHandle)(ref, function () { return ({
65
- reset: function () {
71
+ reset: function (options) {
72
+ var _a;
66
73
  var initialDuration = {
67
74
  hours: initialHours,
68
75
  minutes: initialMinutes,
@@ -70,7 +77,13 @@ var TimerPickerModal = (0, react_1.forwardRef)(function (_a, ref) {
70
77
  };
71
78
  setSelectedDuration(initialDuration);
72
79
  setConfirmedDuration(initialDuration);
73
- setIsVisible(false);
80
+ (_a = timerPickerRef.current) === null || _a === void 0 ? void 0 : _a.reset(options);
81
+ },
82
+ setValue: function (value, options) {
83
+ var _a;
84
+ setSelectedDuration(value);
85
+ setConfirmedDuration(value);
86
+ (_a = timerPickerRef.current) === null || _a === void 0 ? void 0 : _a.setValue(value, options);
74
87
  },
75
88
  }); });
76
89
  return (<Modal_1.default isVisible={visible} onOverlayPress={closeOnOverlayPress ? hideModal : undefined} {...modalProps} testID="timer-picker-modal">
@@ -79,10 +92,7 @@ var TimerPickerModal = (0, react_1.forwardRef)(function (_a, ref) {
79
92
  {modalTitle ? (<react_native_1.Text {...modalTitleProps} style={styles.modalTitle}>
80
93
  {modalTitle}
81
94
  </react_native_1.Text>) : null}
82
- <TimerPicker_1.default onDurationChange={function (duration) {
83
- setSelectedDuration(duration);
84
- onDurationChange === null || onDurationChange === void 0 ? void 0 : onDurationChange(duration);
85
- }} initialHours={confirmedDuration.hours} initialMinutes={confirmedDuration.minutes} initialSeconds={confirmedDuration.seconds} hideHours={hideHours} hideMinutes={hideMinutes} hideSeconds={hideSeconds} hourLabel={hourLabel} minuteLabel={minuteLabel} secondLabel={secondLabel} padWithNItems={padWithNItems} disableInfiniteScroll={disableInfiniteScroll} LinearGradient={LinearGradient} pickerContainerProps={pickerContainerProps} pickerGradientOverlayProps={pickerGradientOverlayProps} styles={customStyles}/>
95
+ <TimerPicker_1.default ref={timerPickerRef} onDurationChange={durationChange} initialHours={confirmedDuration.hours} initialMinutes={confirmedDuration.minutes} initialSeconds={confirmedDuration.seconds} hideHours={hideHours} hideMinutes={hideMinutes} hideSeconds={hideSeconds} hourLimit={hourLimit} minuteLimit={minuteLimit} secondLimit={secondLimit} hourLabel={hourLabel} minuteLabel={minuteLabel} secondLabel={secondLabel} padWithNItems={padWithNItems} disableInfiniteScroll={disableInfiniteScroll} LinearGradient={LinearGradient} pickerContainerProps={pickerContainerProps} pickerGradientOverlayProps={pickerGradientOverlayProps} styles={customStyles}/>
86
96
  <react_native_1.View {...buttonContainerProps} style={styles.buttonContainer}>
87
97
  {!hideCancelButton ? (<react_native_1.TouchableOpacity onPress={cancel}>
88
98
  <react_native_1.Text style={[
@@ -105,4 +115,4 @@ var TimerPickerModal = (0, react_1.forwardRef)(function (_a, ref) {
105
115
  </react_native_1.View>
106
116
  </Modal_1.default>);
107
117
  });
108
- exports.default = TimerPickerModal;
118
+ exports.default = react_1.default.memo(TimerPickerModal);
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { default as TimerPickerModal, TimerPickerModalProps, } from "./components";
2
- export { default as TimerPicker, TimerPickerProps, } from "./components/TimerPicker";
1
+ export { default as TimerPickerModal, TimerPickerModalProps, TimerPickerModalRef, } from "./components";
2
+ export { default as TimerPicker, TimerPickerProps, TimerPickerRef, } from "./components/TimerPicker";
3
3
  export { CustomTimerPickerModalStyles } from "./components/TimerPickerModal.styles";
4
4
  export { CustomTimerPickerStyles } from "./components/TimerPicker/TimerPicker.styles";
@@ -0,0 +1,5 @@
1
+ import type { LimitType } from "../components/TimerPicker/DurationScroll";
2
+ export declare const getAdjustedLimit: (limit: LimitType | undefined, numberOfItems: number) => {
3
+ max: number;
4
+ min: number;
5
+ };
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getAdjustedLimit = void 0;
4
+ var getAdjustedLimit = function (limit, numberOfItems) {
5
+ if (!limit || (!limit.max && !limit.min)) {
6
+ return {
7
+ max: numberOfItems,
8
+ min: 0,
9
+ };
10
+ }
11
+ // guard against limits that are out of bounds
12
+ var adjustedMaxLimit = limit.max
13
+ ? Math.min(limit.max, numberOfItems)
14
+ : numberOfItems;
15
+ var adjustedMinLimit = limit.min ? Math.max(limit.min, 0) : 0;
16
+ // guard against invalid limits
17
+ if (adjustedMaxLimit < adjustedMinLimit) {
18
+ return {
19
+ max: numberOfItems,
20
+ min: 0,
21
+ };
22
+ }
23
+ return {
24
+ max: adjustedMaxLimit,
25
+ min: adjustedMinLimit,
26
+ };
27
+ };
28
+ exports.getAdjustedLimit = getAdjustedLimit;
@@ -0,0 +1,6 @@
1
+ export declare const getScrollIndex: (variables: {
2
+ value: number;
3
+ numberOfItems: number;
4
+ padWithNItems: number;
5
+ disableInfiniteScroll?: boolean | undefined;
6
+ }) => number;
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getScrollIndex = void 0;
4
+ var getScrollIndex = function (variables) {
5
+ var value = variables.value, numberOfItems = variables.numberOfItems, padWithNItems = variables.padWithNItems, disableInfiniteScroll = variables.disableInfiniteScroll;
6
+ return (((value + numberOfItems) % (numberOfItems * 3)) +
7
+ (disableInfiniteScroll ? padWithNItems : 0) -
8
+ (padWithNItems - 1));
9
+ };
10
+ exports.getScrollIndex = getScrollIndex;
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "url": "https://github.com/troberts-28"
7
7
  },
8
8
  "license": "MIT",
9
- "version": "1.1.4",
9
+ "version": "1.2.1",
10
10
  "main": "dist/index.js",
11
11
  "types": "dist/index.d.ts",
12
12
  "scripts": {