react-native-dates-picker 0.0.9 → 0.1.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.
Files changed (155) hide show
  1. package/lib/commonjs/CalendarContext.js.map +1 -1
  2. package/lib/commonjs/DateTimePicker.js +11 -4
  3. package/lib/commonjs/DateTimePicker.js.map +1 -1
  4. package/lib/commonjs/components/Calendar.js +12 -10
  5. package/lib/commonjs/components/Calendar.js.map +1 -1
  6. package/lib/commonjs/components/DatePicker.js +43 -37
  7. package/lib/commonjs/components/DatePicker.js.map +1 -1
  8. package/lib/commonjs/components/Day.js +16 -14
  9. package/lib/commonjs/components/Day.js.map +1 -1
  10. package/lib/commonjs/components/DaySelector.js +10 -10
  11. package/lib/commonjs/components/DaySelector.js.map +1 -1
  12. package/lib/commonjs/components/Header.js +25 -23
  13. package/lib/commonjs/components/Header.js.map +1 -1
  14. package/lib/commonjs/components/MonthSelector.js +7 -5
  15. package/lib/commonjs/components/MonthSelector.js.map +1 -1
  16. package/lib/commonjs/components/TimeSelector.js +19 -14
  17. package/lib/commonjs/components/TimeSelector.js.map +1 -1
  18. package/lib/commonjs/components/WheelPicker/Wheel.js +9 -4
  19. package/lib/commonjs/components/WheelPicker/Wheel.js.map +1 -1
  20. package/lib/commonjs/components/WheelPicker/WheelNative.js +16 -37
  21. package/lib/commonjs/components/WheelPicker/WheelNative.js.map +1 -1
  22. package/lib/commonjs/components/WheelPicker/WheelNativePicker/index.js +10 -0
  23. package/lib/commonjs/components/WheelPicker/WheelNativePicker/index.js.map +1 -0
  24. package/lib/commonjs/components/WheelPicker/WheelNativePicker/wheel-picker-item.js +124 -0
  25. package/lib/commonjs/components/WheelPicker/WheelNativePicker/wheel-picker-item.js.map +1 -0
  26. package/lib/commonjs/components/WheelPicker/WheelNativePicker/wheel-picker.js +169 -0
  27. package/lib/commonjs/components/WheelPicker/WheelNativePicker/wheel-picker.js.map +1 -0
  28. package/lib/commonjs/components/WheelPicker/WheelWeb.js +47 -40
  29. package/lib/commonjs/components/WheelPicker/WheelWeb.js.map +1 -1
  30. package/lib/commonjs/components/WheelPicker/animated-math.js +26 -0
  31. package/lib/commonjs/components/WheelPicker/animated-math.js.map +1 -0
  32. package/lib/commonjs/components/WheelPicker/period-native.js +36 -0
  33. package/lib/commonjs/components/WheelPicker/period-native.js.map +1 -0
  34. package/lib/commonjs/components/WheelPicker/period-picker.js +19 -0
  35. package/lib/commonjs/components/WheelPicker/period-picker.js.map +1 -0
  36. package/lib/commonjs/components/WheelPicker/period-web.js +34 -0
  37. package/lib/commonjs/components/WheelPicker/period-web.js.map +1 -0
  38. package/lib/commonjs/components/YearSelector.js +8 -6
  39. package/lib/commonjs/components/YearSelector.js.map +1 -1
  40. package/lib/commonjs/enums.js +5 -4
  41. package/lib/commonjs/enums.js.map +1 -1
  42. package/lib/commonjs/utils.js +4 -1
  43. package/lib/commonjs/utils.js.map +1 -1
  44. package/lib/module/CalendarContext.js.map +1 -1
  45. package/lib/module/DateTimePicker.js +7 -2
  46. package/lib/module/DateTimePicker.js.map +1 -1
  47. package/lib/module/components/Calendar.js +3 -3
  48. package/lib/module/components/Calendar.js.map +1 -1
  49. package/lib/module/components/DatePicker.js +33 -29
  50. package/lib/module/components/DatePicker.js.map +1 -1
  51. package/lib/module/components/Day.js +7 -7
  52. package/lib/module/components/Day.js.map +1 -1
  53. package/lib/module/components/DaySelector.js +2 -2
  54. package/lib/module/components/DaySelector.js.map +1 -1
  55. package/lib/module/components/Header.js +1 -1
  56. package/lib/module/components/Header.js.map +1 -1
  57. package/lib/module/components/MonthSelector.js +1 -0
  58. package/lib/module/components/MonthSelector.js.map +1 -1
  59. package/lib/module/components/TimeSelector.js +8 -5
  60. package/lib/module/components/TimeSelector.js.map +1 -1
  61. package/lib/module/components/WheelPicker/Wheel.js +6 -3
  62. package/lib/module/components/WheelPicker/Wheel.js.map +1 -1
  63. package/lib/module/components/WheelPicker/WheelNative.js +12 -35
  64. package/lib/module/components/WheelPicker/WheelNative.js.map +1 -1
  65. package/lib/module/components/WheelPicker/WheelNativePicker/index.js +3 -0
  66. package/lib/module/components/WheelPicker/WheelNativePicker/index.js.map +1 -0
  67. package/lib/module/components/WheelPicker/WheelNativePicker/wheel-picker-item.js +116 -0
  68. package/lib/module/components/WheelPicker/WheelNativePicker/wheel-picker-item.js.map +1 -0
  69. package/lib/module/components/WheelPicker/WheelNativePicker/wheel-picker.js +160 -0
  70. package/lib/module/components/WheelPicker/WheelNativePicker/wheel-picker.js.map +1 -0
  71. package/lib/module/components/WheelPicker/WheelWeb.js +45 -40
  72. package/lib/module/components/WheelPicker/WheelWeb.js.map +1 -1
  73. package/lib/module/components/WheelPicker/animated-math.js +20 -0
  74. package/lib/module/components/WheelPicker/animated-math.js.map +1 -0
  75. package/lib/module/components/WheelPicker/period-native.js +27 -0
  76. package/lib/module/components/WheelPicker/period-native.js.map +1 -0
  77. package/lib/module/components/WheelPicker/period-picker.js +10 -0
  78. package/lib/module/components/WheelPicker/period-picker.js.map +1 -0
  79. package/lib/module/components/WheelPicker/period-web.js +26 -0
  80. package/lib/module/components/WheelPicker/period-web.js.map +1 -0
  81. package/lib/module/components/YearSelector.js +1 -1
  82. package/lib/module/components/YearSelector.js.map +1 -1
  83. package/lib/module/enums.js +4 -3
  84. package/lib/module/enums.js.map +1 -1
  85. package/lib/module/utils.js +4 -1
  86. package/lib/module/utils.js.map +1 -1
  87. package/lib/typescript/CalendarContext.d.ts.map +1 -1
  88. package/lib/typescript/DateTimePicker.d.ts +2 -2
  89. package/lib/typescript/DateTimePicker.d.ts.map +1 -1
  90. package/lib/typescript/components/Calendar.d.ts +2 -2
  91. package/lib/typescript/components/Calendar.d.ts.map +1 -1
  92. package/lib/typescript/components/DatePicker.d.ts +2 -2
  93. package/lib/typescript/components/DatePicker.d.ts.map +1 -1
  94. package/lib/typescript/components/Day.d.ts +3 -4
  95. package/lib/typescript/components/Day.d.ts.map +1 -1
  96. package/lib/typescript/components/DaySelector.d.ts +2 -2
  97. package/lib/typescript/components/DaySelector.d.ts.map +1 -1
  98. package/lib/typescript/components/Header.d.ts +2 -2
  99. package/lib/typescript/components/Header.d.ts.map +1 -1
  100. package/lib/typescript/components/MonthSelector.d.ts +2 -2
  101. package/lib/typescript/components/MonthSelector.d.ts.map +1 -1
  102. package/lib/typescript/components/TimeSelector.d.ts +2 -2
  103. package/lib/typescript/components/TimeSelector.d.ts.map +1 -1
  104. package/lib/typescript/components/WheelPicker/Wheel.d.ts +5 -4
  105. package/lib/typescript/components/WheelPicker/Wheel.d.ts.map +1 -1
  106. package/lib/typescript/components/WheelPicker/WheelNative.d.ts +7 -8
  107. package/lib/typescript/components/WheelPicker/WheelNative.d.ts.map +1 -1
  108. package/lib/typescript/components/WheelPicker/WheelNativePicker/index.d.ts +3 -0
  109. package/lib/typescript/components/WheelPicker/WheelNativePicker/index.d.ts.map +1 -0
  110. package/lib/typescript/components/WheelPicker/WheelNativePicker/wheel-picker-item.d.ts +16 -0
  111. package/lib/typescript/components/WheelPicker/WheelNativePicker/wheel-picker-item.d.ts.map +1 -0
  112. package/lib/typescript/components/WheelPicker/WheelNativePicker/wheel-picker.d.ts +20 -0
  113. package/lib/typescript/components/WheelPicker/WheelNativePicker/wheel-picker.d.ts.map +1 -0
  114. package/lib/typescript/components/WheelPicker/WheelWeb.d.ts +6 -5
  115. package/lib/typescript/components/WheelPicker/WheelWeb.d.ts.map +1 -1
  116. package/lib/typescript/components/WheelPicker/animated-math.d.ts +5 -0
  117. package/lib/typescript/components/WheelPicker/animated-math.d.ts.map +1 -0
  118. package/lib/typescript/components/WheelPicker/period-native.d.ts +8 -0
  119. package/lib/typescript/components/WheelPicker/period-native.d.ts.map +1 -0
  120. package/lib/typescript/components/WheelPicker/period-picker.d.ts +8 -0
  121. package/lib/typescript/components/WheelPicker/period-picker.d.ts.map +1 -0
  122. package/lib/typescript/components/WheelPicker/period-web.d.ts +8 -0
  123. package/lib/typescript/components/WheelPicker/period-web.d.ts.map +1 -0
  124. package/lib/typescript/components/YearSelector.d.ts +2 -2
  125. package/lib/typescript/components/YearSelector.d.ts.map +1 -1
  126. package/lib/typescript/enums.d.ts +2 -1
  127. package/lib/typescript/enums.d.ts.map +1 -1
  128. package/lib/typescript/types.d.ts +4 -0
  129. package/lib/typescript/types.d.ts.map +1 -1
  130. package/lib/typescript/utils.d.ts +2 -2
  131. package/lib/typescript/utils.d.ts.map +1 -1
  132. package/package.json +7 -6
  133. package/src/DateTimePicker.tsx +14 -9
  134. package/src/components/Calendar.tsx +5 -5
  135. package/src/components/DatePicker.tsx +34 -43
  136. package/src/components/Day.tsx +7 -7
  137. package/src/components/DaySelector.tsx +3 -3
  138. package/src/components/Header.tsx +2 -2
  139. package/src/components/MonthSelector.tsx +2 -1
  140. package/src/components/TimeSelector.tsx +9 -10
  141. package/src/components/WheelPicker/Wheel.tsx +9 -8
  142. package/src/components/WheelPicker/WheelNative.tsx +17 -38
  143. package/src/components/WheelPicker/WheelNativePicker/index.ts +3 -0
  144. package/src/components/WheelPicker/WheelNativePicker/wheel-picker-item.tsx +137 -0
  145. package/src/components/WheelPicker/WheelNativePicker/wheel-picker.tsx +214 -0
  146. package/src/components/WheelPicker/WheelWeb.tsx +84 -65
  147. package/src/components/WheelPicker/animated-math.ts +33 -0
  148. package/src/components/WheelPicker/period-native.tsx +39 -0
  149. package/src/components/WheelPicker/period-picker.tsx +16 -0
  150. package/src/components/WheelPicker/period-web.tsx +37 -0
  151. package/src/components/YearSelector.tsx +2 -2
  152. package/src/enums.ts +4 -3
  153. package/src/types.ts +5 -0
  154. package/src/utils.ts +3 -3
  155. /package/src/{CalendarContext.tsx → CalendarContext.ts} +0 -0
@@ -1,4 +1,4 @@
1
- import { useMemo, useCallback } from 'react';
1
+ import React, { useMemo, useCallback } from 'react';
2
2
  import { Text, View, StyleSheet } from 'react-native';
3
3
  import dayjs from 'dayjs';
4
4
  import { useCalendarContext } from '../CalendarContext';
@@ -14,7 +14,7 @@ import {
14
14
  getFormatted,
15
15
  } from '../utils';
16
16
 
17
- const DaySelector = () => {
17
+ const DaySelector: React.FC = () => {
18
18
  const {
19
19
  mode,
20
20
  date,
@@ -117,7 +117,7 @@ const DaySelector = () => {
117
117
  if (inRange && !leftCrop && !rightCrop) isSelected = false;
118
118
  }
119
119
  } else if (mode === 'single')
120
- isSelected = areDatesOnSameDay(day.date, currentDate);
120
+ isSelected = areDatesOnSameDay(day.date, date);
121
121
 
122
122
  return {
123
123
  ...day,
@@ -1,11 +1,11 @@
1
- import { useCallback } from 'react';
1
+ import React, { useCallback } from 'react';
2
2
  import { View, Text, Pressable, StyleSheet, Image } from 'react-native';
3
3
  import { useCalendarContext } from '../CalendarContext';
4
4
  import dayjs from 'dayjs';
5
5
  import type { HeaderProps } from '../types';
6
6
  import { getDateYear, getYearRange, YEAR_PAGE_SIZE } from '../utils';
7
7
 
8
- const Header = ({ buttonPrevIcon, buttonNextIcon }: HeaderProps) => {
8
+ const Header: React.FC<HeaderProps> = ({ buttonPrevIcon, buttonNextIcon }) => {
9
9
  const {
10
10
  mode,
11
11
  date,
@@ -1,8 +1,9 @@
1
+ import React from 'react';
1
2
  import { Text, View, Pressable, StyleSheet } from 'react-native';
2
3
  import { useCalendarContext } from '../CalendarContext';
3
4
  import { getParsedDate, getMonths } from '../utils';
4
5
 
5
- const MonthSelector = () => {
6
+ const MonthSelector: React.FC = () => {
6
7
  const { currentDate, onSelectMonth, theme } = useCalendarContext();
7
8
  const { month } = getParsedDate(currentDate);
8
9
 
@@ -1,23 +1,22 @@
1
- import { useCallback } from 'react';
1
+ import React, { useCallback } from 'react';
2
2
  import { Text, View, StyleSheet } from 'react-native';
3
3
  import { useCalendarContext } from '../CalendarContext';
4
4
  import Wheel from './WheelPicker/Wheel';
5
- import { CALENDAR_HEIGHT } from '../enums';
5
+ import { CONTAINER_HEIGHT } from '../enums';
6
6
  import { getParsedDate, getDate, getFormatted } from '../utils';
7
7
 
8
8
  function createNumberList(num: number) {
9
- return new Array(num)
10
- .fill(0)
11
- .map((_, index) =>
12
- index < 10 ? `0${index.toString()}` : index.toString()
13
- );
9
+ return new Array(num).fill(0).map((_, index) => ({
10
+ text: index.toString().padStart(2, '0'),
11
+ value: index,
12
+ }));
14
13
  }
15
14
 
16
15
  const hours = createNumberList(24);
17
16
  const minutes = createNumberList(60);
18
17
  const seconds = createNumberList(60);
19
18
 
20
- const TimeSelector = () => {
19
+ const TimeSelector: React.FC = () => {
21
20
  const { date, onSelectDate, theme } = useCalendarContext();
22
21
  const { hour, minute, second } = getParsedDate(date);
23
22
 
@@ -87,8 +86,8 @@ const styles = StyleSheet.create({
87
86
  flexDirection: 'row',
88
87
  alignItems: 'center',
89
88
  justifyContent: 'center',
90
- width: '80%',
91
- height: CALENDAR_HEIGHT / 2,
89
+ width: CONTAINER_HEIGHT / 2,
90
+ height: CONTAINER_HEIGHT / 2,
92
91
  },
93
92
  timePickerText: {
94
93
  fontSize: 24,
@@ -1,18 +1,19 @@
1
+ import React, { memo } from 'react';
1
2
  import { Platform, type ViewStyle } from 'react-native';
2
3
  import WheelNative from './WheelNative';
3
4
  import WheelWeb from './WheelWeb';
5
+ import { PickerOption } from '../../types';
4
6
 
5
7
  interface WheelProps {
6
8
  value: number;
7
9
  setValue?: (value: number) => void;
8
- items: string[];
10
+ items: Array<PickerOption>;
9
11
  indicatorStyle?: ViewStyle;
10
12
  }
11
13
 
12
- export default function Wheel(props: WheelProps) {
13
- return Platform.OS === 'web' ? (
14
- <WheelWeb {...props} />
15
- ) : (
16
- <WheelNative {...props} />
17
- );
18
- }
14
+ const Wheel: React.FC<WheelProps> = (props) => {
15
+ const Component = Platform.OS === 'web' ? WheelWeb : WheelNative;
16
+ return <Component {...props} />;
17
+ };
18
+
19
+ export default memo(Wheel);
@@ -1,48 +1,34 @@
1
- import { StyleSheet, Platform, type ViewStyle } from 'react-native';
2
- import WheelPicker from 'react-native-wheely';
3
- import { useCalendarContext } from '../../CalendarContext';
1
+ import React, { memo } from 'react';
2
+ import { StyleSheet, Platform } from 'react-native';
3
+ import WheelPicker from './WheelNativePicker';
4
+ import { PickerOption } from '../../types';
4
5
 
5
6
  interface WheelProps {
6
- value: number;
7
- setValue?: (value: number) => void;
8
- items: string[];
9
- indicatorStyle?: ViewStyle;
7
+ value: number | string;
8
+ setValue?: (value: any) => void;
9
+ items: PickerOption[];
10
10
  }
11
11
 
12
- export default function WheelNative({
12
+ const WheelNative: React.FC<WheelProps> = ({
13
13
  value,
14
14
  setValue = () => {},
15
15
  items,
16
- indicatorStyle,
17
- }: WheelProps) {
18
- const { theme } = useCalendarContext();
19
-
16
+ }) => {
20
17
  return (
21
18
  <WheelPicker
22
- selectedIndex={value < 0 ? 0 : value}
19
+ value={value}
23
20
  options={items}
24
21
  onChange={setValue}
25
- containerStyle={{
26
- ...styles.container,
27
- ...theme?.wheelPickerContainerStyle,
28
- }}
29
- itemStyle={theme?.wheelPickerItemStyle}
30
- itemTextStyle={{
31
- ...styles.wheelPickerText,
32
- ...theme?.wheelPickerTextStyle,
33
- }}
34
- selectedIndicatorStyle={{
35
- ...styles.wheelSelectedIndicator,
36
- ...indicatorStyle,
37
- ...theme?.wheelPickerIndicatorStyle,
38
- }}
39
- itemHeight={45}
40
- decelerationRate={theme?.wheelPickerDecelerationRate}
22
+ containerStyle={defaultStyles.container}
23
+ itemHeight={44}
24
+ decelerationRate="fast"
41
25
  />
42
26
  );
43
- }
27
+ };
28
+
29
+ export default memo(WheelNative);
44
30
 
45
- const styles = StyleSheet.create({
31
+ const defaultStyles = StyleSheet.create({
46
32
  container: {
47
33
  display: 'flex',
48
34
  ...Platform.select({
@@ -51,11 +37,4 @@ const styles = StyleSheet.create({
51
37
  },
52
38
  }),
53
39
  },
54
- wheelSelectedIndicator: {
55
- borderRadius: 10,
56
- },
57
- wheelPickerText: {
58
- fontSize: 18,
59
- fontWeight: '500',
60
- },
61
40
  });
@@ -0,0 +1,3 @@
1
+ import WheelPicker from './wheel-picker';
2
+
3
+ export default WheelPicker;
@@ -0,0 +1,137 @@
1
+ import React, { memo } from 'react';
2
+ import { Animated, Text, StyleSheet } from 'react-native';
3
+ import { PickerOption } from 'src/types';
4
+
5
+ interface ItemProps {
6
+ option: PickerOption | null;
7
+ height: number;
8
+ index: number;
9
+ currentScrollIndex: Animated.AnimatedAddition<number>;
10
+ visibleRest: number;
11
+ rotationFunction: (x: number) => number;
12
+ opacityFunction: (x: number) => number;
13
+ scaleFunction: (x: number) => number;
14
+ }
15
+
16
+ const WheelPickerItem: React.FC<ItemProps> = ({
17
+ height,
18
+ option,
19
+ index,
20
+ visibleRest,
21
+ currentScrollIndex,
22
+ opacityFunction,
23
+ rotationFunction,
24
+ scaleFunction,
25
+ }) => {
26
+ const relativeScrollIndex = Animated.subtract(index, currentScrollIndex);
27
+
28
+ const translateY = relativeScrollIndex.interpolate({
29
+ inputRange: (() => {
30
+ const range = [0];
31
+ for (let i = 1; i <= visibleRest + 1; i++) {
32
+ range.unshift(-i);
33
+ range.push(i);
34
+ }
35
+ return range;
36
+ })(),
37
+ outputRange: (() => {
38
+ const range = [0];
39
+ for (let i = 1; i <= visibleRest + 1; i++) {
40
+ let y =
41
+ (height / 2) * (1 - Math.sin(Math.PI / 2 - rotationFunction(i)));
42
+ for (let j = 1; j < i; j++) {
43
+ y += height * (1 - Math.sin(Math.PI / 2 - rotationFunction(j)));
44
+ }
45
+ range.unshift(y);
46
+ range.push(-y);
47
+ }
48
+ return range;
49
+ })(),
50
+ });
51
+
52
+ const opacity = relativeScrollIndex.interpolate({
53
+ inputRange: (() => {
54
+ const range = [0];
55
+ for (let i = 1; i <= visibleRest + 1; i++) {
56
+ range.unshift(-i);
57
+ range.push(i);
58
+ }
59
+ return range;
60
+ })(),
61
+ outputRange: (() => {
62
+ const range = [1];
63
+ for (let x = 1; x <= visibleRest + 1; x++) {
64
+ const y = opacityFunction(x);
65
+ range.unshift(y);
66
+ range.push(y);
67
+ }
68
+ return range;
69
+ })(),
70
+ });
71
+
72
+ const scale = relativeScrollIndex.interpolate({
73
+ inputRange: (() => {
74
+ const range = [0];
75
+ for (let i = 1; i <= visibleRest + 1; i++) {
76
+ range.unshift(-i);
77
+ range.push(i);
78
+ }
79
+ return range;
80
+ })(),
81
+ outputRange: (() => {
82
+ const range = [1.0];
83
+ for (let x = 1; x <= visibleRest + 1; x++) {
84
+ const y = scaleFunction(x);
85
+ range.unshift(y);
86
+ range.push(y);
87
+ }
88
+ return range;
89
+ })(),
90
+ });
91
+
92
+ const rotateX = relativeScrollIndex.interpolate({
93
+ inputRange: (() => {
94
+ const range = [0];
95
+ for (let i = 1; i <= visibleRest + 1; i++) {
96
+ range.unshift(-i);
97
+ range.push(i);
98
+ }
99
+ return range;
100
+ })(),
101
+ outputRange: (() => {
102
+ const range = ['0deg'];
103
+ for (let x = 1; x <= visibleRest + 1; x++) {
104
+ const y = rotationFunction(x);
105
+ range.unshift(`${y}deg`);
106
+ range.push(`${y}deg`);
107
+ }
108
+ return range;
109
+ })(),
110
+ });
111
+
112
+ return (
113
+ <Animated.View
114
+ style={[
115
+ styles.option,
116
+ {
117
+ height,
118
+ opacity,
119
+ transform: [{ translateY }, { rotateX }, { scale }],
120
+ },
121
+ ]}
122
+ >
123
+ <Text>{option?.text}</Text>
124
+ </Animated.View>
125
+ );
126
+ };
127
+
128
+ const styles = StyleSheet.create({
129
+ option: {
130
+ alignItems: 'center',
131
+ justifyContent: 'center',
132
+ paddingHorizontal: 10,
133
+ zIndex: 100,
134
+ },
135
+ });
136
+
137
+ export default memo(WheelPickerItem);
@@ -0,0 +1,214 @@
1
+ import React, { useEffect, useMemo, useRef, useState, memo } from 'react';
2
+ import {
3
+ NativeSyntheticEvent,
4
+ NativeScrollEvent,
5
+ Animated,
6
+ ViewStyle,
7
+ View,
8
+ ViewProps,
9
+ FlatListProps,
10
+ FlatList,
11
+ Platform,
12
+ StyleSheet,
13
+ } from 'react-native';
14
+ import WheelPickerItem from './wheel-picker-item';
15
+ import { PickerOption } from '../../../types';
16
+
17
+ interface Props {
18
+ value: number | string;
19
+ options: PickerOption[];
20
+ onChange: (index: number | string) => void;
21
+ itemHeight?: number;
22
+ containerStyle?: ViewStyle;
23
+ containerProps?: Omit<ViewProps, 'style'>;
24
+ scaleFunction?: (x: number) => number;
25
+ rotationFunction?: (x: number) => number;
26
+ opacityFunction?: (x: number) => number;
27
+ visibleRest?: number;
28
+ decelerationRate?: 'normal' | 'fast' | number;
29
+ flatListProps?: Omit<FlatListProps<string | null>, 'data' | 'renderItem'>;
30
+ }
31
+
32
+ const WheelPicker: React.FC<Props> = ({
33
+ value,
34
+ options,
35
+ onChange,
36
+ itemHeight = 40,
37
+ scaleFunction = (x: number) => 1.0 ** x,
38
+ rotationFunction = (x: number) => 1 - Math.pow(1 / 2, x),
39
+ opacityFunction = (x: number) => Math.pow(1 / 3, x),
40
+ visibleRest = 2,
41
+ decelerationRate = 'normal',
42
+ containerProps = {},
43
+ flatListProps = {},
44
+ }) => {
45
+ const momentumStarted = useRef(false);
46
+ const selectedIndex = options.findIndex((item) => item.value === value);
47
+
48
+ const flatListRef = useRef<FlatList>(null);
49
+ const [scrollY] = useState(new Animated.Value(selectedIndex * itemHeight));
50
+
51
+ const containerHeight = (1 + visibleRest * 2) * itemHeight;
52
+ const paddedOptions = useMemo(() => {
53
+ const array: (PickerOption | null)[] = [...options];
54
+ for (let i = 0; i < visibleRest; i++) {
55
+ array.unshift(null);
56
+ array.push(null);
57
+ }
58
+ return array;
59
+ }, [options, visibleRest]);
60
+
61
+ const offsets = useMemo(
62
+ () => [...Array(paddedOptions.length)].map((_, i) => i * itemHeight),
63
+ [paddedOptions, itemHeight]
64
+ );
65
+
66
+ const currentScrollIndex = useMemo(
67
+ () => Animated.add(Animated.divide(scrollY, itemHeight), visibleRest),
68
+ [visibleRest, scrollY, itemHeight]
69
+ );
70
+
71
+ const handleScrollEnd = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
72
+ const offsetY = Math.min(
73
+ itemHeight * (options.length - 1),
74
+ Math.max(event.nativeEvent.contentOffset.y, 0)
75
+ );
76
+
77
+ let index = Math.floor(offsetY / itemHeight);
78
+ const remainder = offsetY % itemHeight;
79
+ if (remainder > itemHeight / 2) {
80
+ index++;
81
+ }
82
+
83
+ if (index !== selectedIndex) {
84
+ onChange(options[index]?.value || 0);
85
+ }
86
+ };
87
+
88
+ const handleMomentumScrollBegin = () => {
89
+ momentumStarted.current = true;
90
+ };
91
+
92
+ const handleMomentumScrollEnd = (
93
+ event: NativeSyntheticEvent<NativeScrollEvent>
94
+ ) => {
95
+ momentumStarted.current = false;
96
+ handleScrollEnd(event);
97
+ };
98
+
99
+ const handleScrollEndDrag = (
100
+ event: NativeSyntheticEvent<NativeScrollEvent>
101
+ ) => {
102
+ // Capture the offset value immediately
103
+ const offsetY = event.nativeEvent.contentOffset?.y;
104
+
105
+ // We'll start a short timer to see if momentum scroll begins
106
+ setTimeout(() => {
107
+ // If momentum scroll hasn't started within the timeout,
108
+ // then it was a slow scroll that won't trigger momentum
109
+ if (!momentumStarted.current && offsetY !== undefined) {
110
+ // Create a synthetic event with just the data we need
111
+ const syntheticEvent = {
112
+ nativeEvent: {
113
+ contentOffset: { y: offsetY },
114
+ },
115
+ };
116
+ handleScrollEnd(syntheticEvent as any);
117
+ }
118
+ }, 50);
119
+ };
120
+
121
+ useEffect(() => {
122
+ if (selectedIndex < 0 || selectedIndex >= options.length) {
123
+ throw new Error(
124
+ `Selected index ${selectedIndex} is out of bounds [0, ${
125
+ options.length - 1
126
+ }]`
127
+ );
128
+ }
129
+ }, [selectedIndex, options]);
130
+
131
+ /**
132
+ * If selectedIndex is changed from outside (not via onChange) we need to scroll to the specified index.
133
+ * This ensures that what the user sees as selected in the picker always corresponds to the value state.
134
+ */
135
+ useEffect(() => {
136
+ flatListRef.current?.scrollToIndex({
137
+ index: selectedIndex,
138
+ animated: Platform.OS === 'ios',
139
+ });
140
+ }, [selectedIndex, itemHeight]);
141
+
142
+ return (
143
+ <View
144
+ style={[styles.container, { height: containerHeight }]}
145
+ {...containerProps}
146
+ >
147
+ <View
148
+ style={[
149
+ styles.selectedIndicator,
150
+ {
151
+ transform: [{ translateY: -itemHeight / 2 }],
152
+ height: itemHeight,
153
+ },
154
+ ]}
155
+ />
156
+ <Animated.FlatList
157
+ {...flatListProps}
158
+ ref={flatListRef}
159
+ nestedScrollEnabled
160
+ style={styles.scrollView}
161
+ showsVerticalScrollIndicator={false}
162
+ onScroll={Animated.event(
163
+ [{ nativeEvent: { contentOffset: { y: scrollY } } }],
164
+ { useNativeDriver: true }
165
+ )}
166
+ onScrollEndDrag={handleScrollEndDrag}
167
+ onMomentumScrollBegin={handleMomentumScrollBegin}
168
+ onMomentumScrollEnd={handleMomentumScrollEnd}
169
+ snapToOffsets={offsets}
170
+ decelerationRate={decelerationRate}
171
+ initialScrollIndex={selectedIndex}
172
+ getItemLayout={(_, index) => ({
173
+ length: itemHeight,
174
+ offset: itemHeight * index,
175
+ index,
176
+ })}
177
+ data={paddedOptions}
178
+ keyExtractor={(item, index) =>
179
+ item ? `${item.value}-${item.text}-${index}` : `null-${index}`
180
+ }
181
+ renderItem={({ item: option, index }) => (
182
+ <WheelPickerItem
183
+ key={`option-${index}`}
184
+ index={index}
185
+ option={option}
186
+ height={itemHeight}
187
+ currentScrollIndex={currentScrollIndex}
188
+ scaleFunction={scaleFunction}
189
+ rotationFunction={rotationFunction}
190
+ opacityFunction={opacityFunction}
191
+ visibleRest={visibleRest}
192
+ />
193
+ )}
194
+ />
195
+ </View>
196
+ );
197
+ };
198
+
199
+ const styles = StyleSheet.create({
200
+ container: {
201
+ position: 'relative',
202
+ },
203
+ selectedIndicator: {
204
+ position: 'absolute',
205
+ width: '100%',
206
+ top: '50%',
207
+ },
208
+ scrollView: {
209
+ overflow: 'hidden',
210
+ flex: 1,
211
+ },
212
+ });
213
+
214
+ export default memo(WheelPicker);