expo-horizontal-picker 0.1.8 → 0.2.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 CHANGED
@@ -30,14 +30,22 @@ This package requires [`react-native-reanimated`](https://docs.expo.dev/versions
30
30
  npm install expo-horizontal-picker react-native-reanimated
31
31
  ```
32
32
 
33
+ `ref` support uses React 19's ref-as-prop model, so install this package in a React 19 app.
34
+
33
35
  Make sure to follow the additional setup instructions for Reanimated in the [official docs](https://docs.expo.dev/versions/latest/sdk/reanimated/#installation).
34
36
 
35
37
  ## 🎬 Demo
38
+
39
+ ![expo-horizontal-picker demo](https://raw.githubusercontent.com/fe-dudu/expo-horizontal-picker/main/assets/demo.gif)
40
+
36
41
  ```ts
37
- import { HorizontalPicker } from 'expo-horizontal-picker';
38
- import { View } from 'react-native';
42
+ import { useRef } from 'react';
43
+ import { HorizontalPicker, type HorizontalPickerRef } from 'expo-horizontal-picker';
44
+ import { Button, View } from 'react-native';
39
45
 
40
46
  export default function App() {
47
+ const pickerRef = useRef<HorizontalPickerRef | null>(null);
48
+
41
49
  return (
42
50
  <View style={styles.container}>
43
51
  <View>
@@ -46,7 +54,7 @@ export default function App() {
46
54
  label: `${i + 1}`,
47
55
  value: i + 1,
48
56
  }))}
49
- initialScrollIndex={500}
57
+ initialScrollIndex={499}
50
58
  visibleItemCount={7}
51
59
  />
52
60
 
@@ -64,10 +72,16 @@ export default function App() {
64
72
  label: `${i + 1}h`,
65
73
  value: i + 1,
66
74
  }))}
75
+ ref={pickerRef}
67
76
  initialScrollIndex={11}
68
77
  visibleItemCount={3}
69
78
  />
70
79
 
80
+ <Button
81
+ title="Jump to 24h"
82
+ onPress={() => pickerRef.current?.scrollToEnd({ animated: true })}
83
+ />
84
+
71
85
  <HorizontalPicker
72
86
  items={Array.from({ length: 5 }, (_, i) => ({
73
87
  label: `${(i + 1) * 10000}`,
@@ -89,6 +103,39 @@ const styles = {
89
103
  };
90
104
  ```
91
105
 
106
+ ## Ref Usage
107
+
108
+ Pass a ref when you need the picker scroll methods: `scrollToEnd`, `scrollToIndex`, `scrollToItem`, or `scrollToOffset`.
109
+
110
+ ```ts
111
+ import { useRef } from 'react';
112
+ import { Button } from 'react-native';
113
+ import { HorizontalPicker, type HorizontalPickerRef } from 'expo-horizontal-picker';
114
+
115
+ export default function RefExample() {
116
+ const pickerRef = useRef<HorizontalPickerRef | null>(null);
117
+
118
+ return (
119
+ <>
120
+ <HorizontalPicker ref={pickerRef} items={items} />
121
+ <Button title="Jump to start" onPress={() => pickerRef.current?.scrollToOffset({ offset: 0, animated: true })} />
122
+ </>
123
+ );
124
+ }
125
+ ```
126
+
127
+ ## 📱 Example App
128
+
129
+ A runnable Expo example app is included in [`example`](./example). It mirrors the README demo and includes ref-driven scroll controls.
130
+
131
+ ```bash
132
+ cd example
133
+ yarn
134
+ yarn start
135
+ ```
136
+
137
+ The example app resolves `expo-horizontal-picker` to this repository's local `src` directory through Metro, so you can iterate on the library and see changes immediately in the app.
138
+
92
139
  ## 🎨 Customization Example
93
140
 
94
141
  Customize the focused and unfocused item styles with GPU-accelerated properties:
@@ -118,6 +165,7 @@ Customize the focused and unfocused item styles with GPU-accelerated properties:
118
165
  | `focusedOpacityStyle` | `number` | `1` | Opacity value for the focused item (GPU-accelerated). |
119
166
  | `unfocusedOpacityStyle` | `number` | `0.2` | Opacity value for unfocused items (GPU-accelerated). |
120
167
  | `pickerItemStyle` | `ViewStyle` | – | Style applied to each picker item container. |
168
+ | `pickerItemTextStyle` | `TextStyle` | – | Style applied to the text inside each picker item. |
121
169
  | `style` | `ViewStyle` | – | Style applied to the scroll container. |
122
170
 
123
171
  ### Additional FlatList Props
@@ -131,7 +179,8 @@ The component extends `FlatListPropsWithLayout`, so you can also pass any valid
131
179
  - `initialNumToRender` (default: `15`)
132
180
  - `maxToRenderPerBatch` (default: `15`)
133
181
  - `removeClippedSubviews` (default: `true`)
182
+ - `ref` to call `scrollToEnd`, `scrollToIndex`, `scrollToItem`, and `scrollToOffset`
134
183
 
135
184
  ## ⚡ Performance Notes
136
185
 
137
- The `focusedTransformStyle` and `unfocusedTransformStyle` props only accept GPU-accelerated transform properties (e.g., `scale`, `translateX`, `translateY`, `rotate`) for optimal performance. These properties are processed directly on the GPU without triggering layout recalculations.
186
+ The `focusedTransformStyle` and `unfocusedTransformStyle` props only accept GPU-accelerated transform properties (e.g., `scale`, `translateX`, `translateY`, `rotate`) for optimal performance. These properties are processed directly on the GPU without triggering layout recalculations.
package/build/index.d.ts CHANGED
@@ -1,12 +1,35 @@
1
- import { type ViewStyle } from 'react-native';
1
+ import { type Ref } from 'react';
2
+ import { type TextStyle, type ViewStyle } from 'react-native';
2
3
  import { type FlatListPropsWithLayout } from 'react-native-reanimated';
3
- interface PickerOption {
4
+ interface FlatListProps extends Omit<FlatListPropsWithLayout<PickerOption>, 'ref' | 'horizontal' | 'data' | 'renderItem' | 'initialScrollIndex' | 'onScroll' | 'snapToOffsets' | 'contentContainerStyle' | 'getItemLayout'> {
5
+ }
6
+ export interface PickerOption {
4
7
  label: string;
5
8
  value: string | number;
6
9
  }
7
- interface FlatListProps extends Omit<FlatListPropsWithLayout<PickerOption>, 'ref' | 'horizontal' | 'data' | 'renderItem' | 'initialScrollIndex' | 'onScroll' | 'snapToOffsets' | 'contentContainerStyle' | 'getItemLayout'> {
10
+ export interface HorizontalPickerRef {
11
+ scrollToEnd: (params?: {
12
+ animated?: boolean | null;
13
+ }) => void;
14
+ scrollToIndex: (params: {
15
+ animated?: boolean | null;
16
+ index: number;
17
+ viewOffset?: number;
18
+ viewPosition?: number;
19
+ }) => void;
20
+ scrollToItem: (params: {
21
+ animated?: boolean | null;
22
+ item: PickerOption;
23
+ viewOffset?: number;
24
+ viewPosition?: number;
25
+ }) => void;
26
+ scrollToOffset: (params: {
27
+ animated?: boolean | null;
28
+ offset: number;
29
+ }) => void;
8
30
  }
9
31
  export interface HorizontalPickerProps extends FlatListProps {
32
+ ref?: Ref<HorizontalPickerRef | null>;
10
33
  items: PickerOption[];
11
34
  initialScrollIndex?: number;
12
35
  visibleItemCount?: number;
@@ -16,7 +39,8 @@ export interface HorizontalPickerProps extends FlatListProps {
16
39
  focusedOpacityStyle?: number;
17
40
  unfocusedOpacityStyle?: number;
18
41
  pickerItemStyle?: ViewStyle;
42
+ pickerItemTextStyle?: TextStyle;
19
43
  }
20
- export declare function HorizontalPicker({ items, initialScrollIndex, visibleItemCount, onChange, keyExtractor, scrollEventThrottle, decelerationRate, onLayout, showsHorizontalScrollIndicator, initialNumToRender, maxToRenderPerBatch, removeClippedSubviews, focusedTransformStyle, unfocusedTransformStyle, focusedOpacityStyle, unfocusedOpacityStyle, pickerItemStyle, style, ...props }: HorizontalPickerProps): import("react").JSX.Element;
44
+ export declare function HorizontalPicker({ ref, items, initialScrollIndex, visibleItemCount, onChange, keyExtractor, scrollEventThrottle, decelerationRate, onLayout, showsHorizontalScrollIndicator, initialNumToRender, maxToRenderPerBatch, removeClippedSubviews, focusedTransformStyle, unfocusedTransformStyle, focusedOpacityStyle, unfocusedOpacityStyle, pickerItemStyle, pickerItemTextStyle, style, ...props }: HorizontalPickerProps): import("react").JSX.Element;
21
45
  export {};
22
46
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AACA,OAAO,EAA2C,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AACvF,OAAiB,EACf,KAAK,uBAAuB,EAO7B,MAAM,yBAAyB,CAAC;AAEjC,UAAU,YAAY;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;CACxB;AAED,UAAU,aACR,SAAQ,IAAI,CACV,uBAAuB,CAAC,YAAY,CAAC,EACnC,KAAK,GACL,YAAY,GACZ,MAAM,GACN,YAAY,GACZ,oBAAoB,GACpB,UAAU,GACV,eAAe,GACf,uBAAuB,GACvB,eAAe,CAClB;CAAG;AAEN,MAAM,WAAW,qBAAsB,SAAQ,aAAa;IAC1D,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACjE,qBAAqB,CAAC,EAAE,SAAS,CAAC,WAAW,CAAC,CAAC;IAC/C,uBAAuB,CAAC,EAAE,SAAS,CAAC,WAAW,CAAC,CAAC;IACjD,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,eAAe,CAAC,EAAE,SAAS,CAAC;CAC7B;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,KAAK,EACL,kBAAsB,EACtB,gBAAoB,EACpB,QAAQ,EACR,YAAwD,EACxD,mBAAwB,EACxB,gBAAyB,EACzB,QAAQ,EACR,8BAAsC,EACtC,kBAAuB,EACvB,mBAAwB,EACxB,qBAA4B,EAC5B,qBAAyC,EACzC,uBAAwC,EACxC,mBAAuB,EACvB,qBAA2B,EAC3B,eAAe,EACf,KAAK,EACL,GAAG,KAAK,EACT,EAAE,qBAAqB,+BA8FvB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,GAAG,EAAkD,MAAM,OAAO,CAAC;AACjF,OAAO,EAKL,KAAK,SAAS,EAEd,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AACtB,OAAiB,EACf,KAAK,uBAAuB,EAO7B,MAAM,yBAAyB,CAAC;AAEjC,UAAU,aACR,SAAQ,IAAI,CACV,uBAAuB,CAAC,YAAY,CAAC,EACnC,KAAK,GACL,YAAY,GACZ,MAAM,GACN,YAAY,GACZ,oBAAoB,GACpB,UAAU,GACV,eAAe,GACf,uBAAuB,GACvB,eAAe,CAClB;CAAG;AAEN,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,CAAC,MAAM,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,GAAG,IAAI,CAAA;KAAE,KAAK,IAAI,CAAC;IAC9D,aAAa,EAAE,CAAC,MAAM,EAAE;QACtB,QAAQ,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;QAC1B,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,YAAY,CAAC,EAAE,MAAM,CAAC;KACvB,KAAK,IAAI,CAAC;IACX,YAAY,EAAE,CAAC,MAAM,EAAE;QACrB,QAAQ,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;QAC1B,IAAI,EAAE,YAAY,CAAC;QACnB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,YAAY,CAAC,EAAE,MAAM,CAAC;KACvB,KAAK,IAAI,CAAC;IACX,cAAc,EAAE,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CACjF;AAED,MAAM,WAAW,qBAAsB,SAAQ,aAAa;IAC1D,GAAG,CAAC,EAAE,GAAG,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAAC;IACtC,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACjE,qBAAqB,CAAC,EAAE,SAAS,CAAC,WAAW,CAAC,CAAC;IAC/C,uBAAuB,CAAC,EAAE,SAAS,CAAC,WAAW,CAAC,CAAC;IACjD,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,eAAe,CAAC,EAAE,SAAS,CAAC;IAC5B,mBAAmB,CAAC,EAAE,SAAS,CAAC;CACjC;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,GAAG,EACH,KAAK,EACL,kBAAsB,EACtB,gBAAoB,EACpB,QAAQ,EACR,YAAwD,EACxD,mBAAwB,EACxB,gBAAyB,EACzB,QAAQ,EACR,8BAAsC,EACtC,kBAAuB,EACvB,mBAAwB,EACxB,qBAA4B,EAC5B,qBAAyC,EACzC,uBAAwC,EACxC,mBAAuB,EACvB,qBAA2B,EAC3B,eAAe,EACf,mBAAmB,EACnB,KAAK,EACL,GAAG,KAAK,EACT,EAAE,qBAAqB,+BAwGvB"}
package/build/index.js CHANGED
@@ -1,10 +1,9 @@
1
- import { useMemo, useRef, useState } from 'react';
2
- import { PixelRatio, Pressable, StyleSheet, View } from 'react-native';
1
+ import { useImperativeHandle, useMemo, useRef, useState } from 'react';
2
+ import { PixelRatio, Pressable, StyleSheet, View, } from 'react-native';
3
3
  import Animated, { runOnJS, useAnimatedScrollHandler, useAnimatedStyle, useDerivedValue, useSharedValue, } from 'react-native-reanimated';
4
- export function HorizontalPicker({ items, initialScrollIndex = 0, visibleItemCount = 7, onChange, keyExtractor = (item, index) => `${item.value}-${index}`, scrollEventThrottle = 16, decelerationRate = 'fast', onLayout, showsHorizontalScrollIndicator = false, initialNumToRender = 15, maxToRenderPerBatch = 15, removeClippedSubviews = true, focusedTransformStyle = [{ scale: 1.15 }], unfocusedTransformStyle = [{ scale: 1 }], focusedOpacityStyle = 1, unfocusedOpacityStyle = 0.2, pickerItemStyle, style, ...props }) {
4
+ export function HorizontalPicker({ ref, items, initialScrollIndex = 0, visibleItemCount = 7, onChange, keyExtractor = (item, index) => `${item.value}-${index}`, scrollEventThrottle = 16, decelerationRate = 'fast', onLayout, showsHorizontalScrollIndicator = false, initialNumToRender = 15, maxToRenderPerBatch = 15, removeClippedSubviews = true, focusedTransformStyle = [{ scale: 1.15 }], unfocusedTransformStyle = [{ scale: 1 }], focusedOpacityStyle = 1, unfocusedOpacityStyle = 0.2, pickerItemStyle, pickerItemTextStyle, style, ...props }) {
5
5
  const listRef = useRef(null);
6
6
  const [width, setWidth] = useState(0);
7
- const scrollX = useSharedValue(0);
8
7
  const currentIndex = useSharedValue(initialScrollIndex);
9
8
  const { itemWidth, paddingSide } = useMemo(() => {
10
9
  const itemWidth = PixelRatio.roundToNearestPixel(width / visibleItemCount);
@@ -31,7 +30,6 @@ export function HorizontalPicker({ items, initialScrollIndex = 0, visibleItemCou
31
30
  };
32
31
  const onScroll = useAnimatedScrollHandler({
33
32
  onScroll: (e) => {
34
- scrollX.value = e.contentOffset.x;
35
33
  const newIndex = Math.round(e.contentOffset.x / itemWidth);
36
34
  const safeIndex = Math.max(0, Math.min(items.length - 1, newIndex));
37
35
  currentIndex.value = safeIndex;
@@ -43,7 +41,13 @@ export function HorizontalPicker({ items, initialScrollIndex = 0, visibleItemCou
43
41
  runOnJS(handleOnChange)(safeIndex);
44
42
  },
45
43
  });
46
- return (<Animated.FlatList {...props} ref={listRef} horizontal={true} data={items} keyExtractor={keyExtractor} renderItem={({ item, index }) => (<PickerItem label={item.label} index={index} itemWidth={itemWidth} currentIndex={currentIndex} onPress={() => scrollToIndex(index)} focusedTransformStyle={focusedTransformStyle} unfocusedTransformStyle={unfocusedTransformStyle} focusedOpacityStyle={focusedOpacityStyle} unfocusedOpacityStyle={unfocusedOpacityStyle} pickerItemStyle={pickerItemStyle}/>)} onScroll={onScroll} scrollEventThrottle={scrollEventThrottle} decelerationRate={decelerationRate} onLayout={(e) => {
44
+ useImperativeHandle(ref, () => ({
45
+ scrollToEnd: (params) => listRef.current?.scrollToEnd(params),
46
+ scrollToIndex: (params) => listRef.current?.scrollToIndex(params),
47
+ scrollToItem: (params) => listRef.current?.scrollToItem(params),
48
+ scrollToOffset: (params) => listRef.current?.scrollToOffset(params),
49
+ }), []);
50
+ return (<Animated.FlatList {...props} ref={listRef} horizontal={true} data={items} keyExtractor={keyExtractor} renderItem={({ item, index }) => (<PickerItem label={item.label} index={index} itemWidth={itemWidth} currentIndex={currentIndex} onPress={() => scrollToIndex(index)} focusedTransformStyle={focusedTransformStyle} unfocusedTransformStyle={unfocusedTransformStyle} focusedOpacityStyle={focusedOpacityStyle} unfocusedOpacityStyle={unfocusedOpacityStyle} pickerItemStyle={pickerItemStyle} pickerItemTextStyle={pickerItemTextStyle}/>)} onScroll={onScroll} scrollEventThrottle={scrollEventThrottle} decelerationRate={decelerationRate} onLayout={(e) => {
47
51
  if (typeof onLayout === 'function') {
48
52
  onLayout(e);
49
53
  }
@@ -54,7 +58,7 @@ export function HorizontalPicker({ items, initialScrollIndex = 0, visibleItemCou
54
58
  index,
55
59
  })} initialScrollIndex={initialScrollIndex} initialNumToRender={initialNumToRender} maxToRenderPerBatch={maxToRenderPerBatch} removeClippedSubviews={removeClippedSubviews} style={[styles.container, style]}/>);
56
60
  }
57
- function PickerItem({ label, index, itemWidth, currentIndex, onPress, focusedTransformStyle, unfocusedTransformStyle, focusedOpacityStyle, unfocusedOpacityStyle, pickerItemStyle, }) {
61
+ function PickerItem({ label, index, itemWidth, currentIndex, onPress, focusedTransformStyle, unfocusedTransformStyle, focusedOpacityStyle, unfocusedOpacityStyle, pickerItemStyle, pickerItemTextStyle, }) {
58
62
  const isFocused = useDerivedValue(() => currentIndex.value === index);
59
63
  const animatedStyle = useAnimatedStyle(() => {
60
64
  return {
@@ -64,7 +68,7 @@ function PickerItem({ label, index, itemWidth, currentIndex, onPress, focusedTra
64
68
  }, [index]);
65
69
  return (<Pressable onPress={onPress}>
66
70
  <View style={[styles.itemContainer, { width: itemWidth }, pickerItemStyle]}>
67
- <Animated.Text style={[styles.itemText, animatedStyle]}>{label}</Animated.Text>
71
+ <Animated.Text style={[styles.itemText, animatedStyle, pickerItemTextStyle]}>{label}</Animated.Text>
68
72
  </View>
69
73
  </Pressable>);
70
74
  }
@@ -75,16 +79,10 @@ const styles = StyleSheet.create({
75
79
  itemContainer: {
76
80
  justifyContent: 'center',
77
81
  alignItems: 'center',
78
- height: 60,
79
82
  },
80
83
  itemText: {
81
84
  fontSize: 13,
82
85
  fontWeight: '700',
83
- color: '#C9CED9',
84
- },
85
- itemTextSelected: {
86
- fontSize: 15,
87
- fontWeight: '800',
88
86
  color: '#000000',
89
87
  },
90
88
  });
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,IAAI,EAAkB,MAAM,cAAc,CAAC;AACvF,OAAO,QAAQ,EAAE,EAGf,OAAO,EACP,wBAAwB,EACxB,gBAAgB,EAChB,eAAe,EACf,cAAc,GACf,MAAM,yBAAyB,CAAC;AAiCjC,MAAM,UAAU,gBAAgB,CAAC,EAC/B,KAAK,EACL,kBAAkB,GAAG,CAAC,EACtB,gBAAgB,GAAG,CAAC,EACpB,QAAQ,EACR,YAAY,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,KAAK,IAAI,KAAK,EAAE,EACxD,mBAAmB,GAAG,EAAE,EACxB,gBAAgB,GAAG,MAAM,EACzB,QAAQ,EACR,8BAA8B,GAAG,KAAK,EACtC,kBAAkB,GAAG,EAAE,EACvB,mBAAmB,GAAG,EAAE,EACxB,qBAAqB,GAAG,IAAI,EAC5B,qBAAqB,GAAG,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,EACzC,uBAAuB,GAAG,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,EACxC,mBAAmB,GAAG,CAAC,EACvB,qBAAqB,GAAG,GAAG,EAC3B,eAAe,EACf,KAAK,EACL,GAAG,KAAK,EACc;IACtB,MAAM,OAAO,GAAG,MAAM,CAAkC,IAAI,CAAC,CAAC;IAC9D,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAS,CAAC,CAAC,CAAC;IAE9C,MAAM,OAAO,GAAG,cAAc,CAAS,CAAC,CAAC,CAAC;IAC1C,MAAM,YAAY,GAAG,cAAc,CAAS,kBAAkB,CAAC,CAAC;IAEhE,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE;QAC9C,MAAM,SAAS,GAAG,UAAU,CAAC,mBAAmB,CAAC,KAAK,GAAG,gBAAgB,CAAC,CAAC;QAC3E,MAAM,WAAW,GAAG,UAAU,CAAC,mBAAmB,CAAC,KAAK,GAAG,CAAC,GAAG,SAAS,GAAG,CAAC,CAAC,CAAC;QAC9E,OAAO;YACL,SAAS,EAAE,SAAS;YACpB,WAAW,EAAE,WAAW;SACzB,CAAC;IACJ,CAAC,EAAE,CAAC,KAAK,EAAE,gBAAgB,CAAC,CAAC,CAAC;IAE9B,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,EAAE;QAC/B,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,UAAU,CAAC,mBAAmB,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC,CAAC;IACpF,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC;IAEvB,MAAM,cAAc,GAAG,CAAC,KAAa,EAAE,EAAE;QACvC,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC;QAC1B,IAAI,IAAI,EAAE,CAAC;YACT,QAAQ,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QAChC,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,aAAa,GAAG,CAAC,KAAa,EAAE,EAAE;QACtC,OAAO,CAAC,OAAO,EAAE,cAAc,CAAC;YAC9B,MAAM,EAAE,KAAK,GAAG,SAAS;YACzB,QAAQ,EAAE,IAAI;SACf,CAAC,CAAC;IACL,CAAC,CAAC;IAEF,MAAM,QAAQ,GAAG,wBAAwB,CAAC;QACxC,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE;YACd,OAAO,CAAC,KAAK,GAAG,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC;YAClC,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;YAC3D,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC;YACpE,YAAY,CAAC,KAAK,GAAG,SAAS,CAAC;QACjC,CAAC;QACD,aAAa,EAAE,CAAC,CAAC,EAAE,EAAE;YACnB,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;YAC3D,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC;YACpE,YAAY,CAAC,KAAK,GAAG,SAAS,CAAC;YAC/B,OAAO,CAAC,cAAc,CAAC,CAAC,SAAS,CAAC,CAAC;QACrC,CAAC;KACF,CAAC,CAAC;IAEH,OAAO,CACL,CAAC,QAAQ,CAAC,QAAQ,CAChB,IAAI,KAAK,CAAC,CACV,GAAG,CAAC,CAAC,OAAO,CAAC,CACb,UAAU,CAAC,CAAC,IAAI,CAAC,CACjB,IAAI,CAAC,CAAC,KAAK,CAAC,CACZ,YAAY,CAAC,CAAC,YAAY,CAAC,CAC3B,UAAU,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAC/B,CAAC,UAAU,CACT,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAClB,KAAK,CAAC,CAAC,KAAK,CAAC,CACb,SAAS,CAAC,CAAC,SAAS,CAAC,CACrB,YAAY,CAAC,CAAC,YAAY,CAAC,CAC3B,OAAO,CAAC,CAAC,GAAG,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CACpC,qBAAqB,CAAC,CAAC,qBAAqB,CAAC,CAC7C,uBAAuB,CAAC,CAAC,uBAAuB,CAAC,CACjD,mBAAmB,CAAC,CAAC,mBAAmB,CAAC,CACzC,qBAAqB,CAAC,CAAC,qBAAqB,CAAC,CAC7C,eAAe,CAAC,CAAC,eAAe,CAAC,EACjC,CACH,CAAC,CACF,QAAQ,CAAC,CAAC,QAAQ,CAAC,CACnB,mBAAmB,CAAC,CAAC,mBAAmB,CAAC,CACzC,gBAAgB,CAAC,CAAC,gBAAgB,CAAC,CACnC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE;YACd,IAAI,OAAO,QAAQ,KAAK,UAAU,EAAE,CAAC;gBACnC,QAAQ,CAAC,CAAC,CAAC,CAAC;YACd,CAAC;YACD,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACvC,CAAC,CAAC,CACF,8BAA8B,CAAC,CAAC,8BAA8B,CAAC,CAC/D,aAAa,CAAC,CAAC,WAAW,CAAC,CAC3B,qBAAqB,CAAC,CAAC,EAAE,iBAAiB,EAAE,WAAW,EAAE,CAAC,CAC1D,aAAa,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;YAC5B,MAAM,EAAE,SAAS;YACjB,MAAM,EAAE,SAAS,GAAG,KAAK;YACzB,KAAK;SACN,CAAC,CAAC,CACH,kBAAkB,CAAC,CAAC,kBAAkB,CAAC,CACvC,kBAAkB,CAAC,CAAC,kBAAkB,CAAC,CACvC,mBAAmB,CAAC,CAAC,mBAAmB,CAAC,CACzC,qBAAqB,CAAC,CAAC,qBAAqB,CAAC,CAC7C,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC,EACjC,CACH,CAAC;AACJ,CAAC;AAiBD,SAAS,UAAU,CAAC,EAClB,KAAK,EACL,KAAK,EACL,SAAS,EACT,YAAY,EACZ,OAAO,EACP,qBAAqB,EACrB,uBAAuB,EACvB,mBAAmB,EACnB,qBAAqB,EACrB,eAAe,GACC;IAChB,MAAM,SAAS,GAAG,eAAe,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,KAAK,KAAK,KAAK,CAAC,CAAC;IAEtE,MAAM,aAAa,GAAG,gBAAgB,CAAC,GAAG,EAAE;QAC1C,OAAO;YACL,SAAS,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,uBAAuB;YAC5E,OAAO,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,qBAAqB;SACvE,CAAC;IACJ,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IAEZ,OAAO,CACL,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAC1B;MAAA,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,aAAa,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,eAAe,CAAC,CAAC,CACzE;QAAA,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC,IAAI,CAChF;MAAA,EAAE,IAAI,CACR;IAAA,EAAE,SAAS,CAAC,CACb,CAAC;AACJ,CAAC;AAED,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC;IAC/B,SAAS,EAAE;QACT,KAAK,EAAE,MAAM;KACd;IACD,aAAa,EAAE;QACb,cAAc,EAAE,QAAQ;QACxB,UAAU,EAAE,QAAQ;QACpB,MAAM,EAAE,EAAE;KACX;IACD,QAAQ,EAAE;QACR,QAAQ,EAAE,EAAE;QACZ,UAAU,EAAE,KAAK;QACjB,KAAK,EAAE,SAAS;KACjB;IACD,gBAAgB,EAAE;QAChB,QAAQ,EAAE,EAAE;QACZ,UAAU,EAAE,KAAK;QACjB,KAAK,EAAE,SAAS;KACjB;CACF,CAAC,CAAC","sourcesContent":["import { useMemo, useRef, useState } from 'react';\nimport { PixelRatio, Pressable, StyleSheet, View, type ViewStyle } from 'react-native';\nimport Animated, {\n type FlatListPropsWithLayout,\n type SharedValue,\n runOnJS,\n useAnimatedScrollHandler,\n useAnimatedStyle,\n useDerivedValue,\n useSharedValue,\n} from 'react-native-reanimated';\n\ninterface PickerOption {\n label: string;\n value: string | number;\n}\n\ninterface FlatListProps\n extends Omit<\n FlatListPropsWithLayout<PickerOption>,\n | 'ref'\n | 'horizontal'\n | 'data'\n | 'renderItem'\n | 'initialScrollIndex'\n | 'onScroll'\n | 'snapToOffsets'\n | 'contentContainerStyle'\n | 'getItemLayout'\n > {}\n\nexport interface HorizontalPickerProps extends FlatListProps {\n items: PickerOption[];\n initialScrollIndex?: number;\n visibleItemCount?: number;\n onChange?: (value: PickerOption['value'], index: number) => void;\n focusedTransformStyle?: ViewStyle['transform'];\n unfocusedTransformStyle?: ViewStyle['transform'];\n focusedOpacityStyle?: number;\n unfocusedOpacityStyle?: number;\n pickerItemStyle?: ViewStyle;\n}\n\nexport function HorizontalPicker({\n items,\n initialScrollIndex = 0,\n visibleItemCount = 7,\n onChange,\n keyExtractor = (item, index) => `${item.value}-${index}`,\n scrollEventThrottle = 16,\n decelerationRate = 'fast',\n onLayout,\n showsHorizontalScrollIndicator = false,\n initialNumToRender = 15,\n maxToRenderPerBatch = 15,\n removeClippedSubviews = true,\n focusedTransformStyle = [{ scale: 1.15 }],\n unfocusedTransformStyle = [{ scale: 1 }],\n focusedOpacityStyle = 1,\n unfocusedOpacityStyle = 0.2,\n pickerItemStyle,\n style,\n ...props\n}: HorizontalPickerProps) {\n const listRef = useRef<Animated.FlatList<PickerOption>>(null);\n const [width, setWidth] = useState<number>(0);\n\n const scrollX = useSharedValue<number>(0);\n const currentIndex = useSharedValue<number>(initialScrollIndex);\n\n const { itemWidth, paddingSide } = useMemo(() => {\n const itemWidth = PixelRatio.roundToNearestPixel(width / visibleItemCount);\n const paddingSide = PixelRatio.roundToNearestPixel(width / 2 - itemWidth / 2);\n return {\n itemWidth: itemWidth,\n paddingSide: paddingSide,\n };\n }, [width, visibleItemCount]);\n\n const snapOffsets = useMemo(() => {\n return items.map((_, index) => PixelRatio.roundToNearestPixel(index * itemWidth));\n }, [items, itemWidth]);\n\n const handleOnChange = (index: number) => {\n const item = items[index];\n if (item) {\n onChange?.(item.value, index);\n }\n };\n\n const scrollToIndex = (index: number) => {\n listRef.current?.scrollToOffset({\n offset: index * itemWidth,\n animated: true,\n });\n };\n\n const onScroll = useAnimatedScrollHandler({\n onScroll: (e) => {\n scrollX.value = e.contentOffset.x;\n const newIndex = Math.round(e.contentOffset.x / itemWidth);\n const safeIndex = Math.max(0, Math.min(items.length - 1, newIndex));\n currentIndex.value = safeIndex;\n },\n onMomentumEnd: (e) => {\n const newIndex = Math.round(e.contentOffset.x / itemWidth);\n const safeIndex = Math.max(0, Math.min(items.length - 1, newIndex));\n currentIndex.value = safeIndex;\n runOnJS(handleOnChange)(safeIndex);\n },\n });\n\n return (\n <Animated.FlatList\n {...props}\n ref={listRef}\n horizontal={true}\n data={items}\n keyExtractor={keyExtractor}\n renderItem={({ item, index }) => (\n <PickerItem\n label={item.label}\n index={index}\n itemWidth={itemWidth}\n currentIndex={currentIndex}\n onPress={() => scrollToIndex(index)}\n focusedTransformStyle={focusedTransformStyle}\n unfocusedTransformStyle={unfocusedTransformStyle}\n focusedOpacityStyle={focusedOpacityStyle}\n unfocusedOpacityStyle={unfocusedOpacityStyle}\n pickerItemStyle={pickerItemStyle}\n />\n )}\n onScroll={onScroll}\n scrollEventThrottle={scrollEventThrottle}\n decelerationRate={decelerationRate}\n onLayout={(e) => {\n if (typeof onLayout === 'function') {\n onLayout(e);\n }\n setWidth(e.nativeEvent.layout.width);\n }}\n showsHorizontalScrollIndicator={showsHorizontalScrollIndicator}\n snapToOffsets={snapOffsets}\n contentContainerStyle={{ paddingHorizontal: paddingSide }}\n getItemLayout={(_, index) => ({\n length: itemWidth,\n offset: itemWidth * index,\n index,\n })}\n initialScrollIndex={initialScrollIndex}\n initialNumToRender={initialNumToRender}\n maxToRenderPerBatch={maxToRenderPerBatch}\n removeClippedSubviews={removeClippedSubviews}\n style={[styles.container, style]}\n />\n );\n}\ninterface PickerItemProps\n extends Pick<\n HorizontalPickerProps,\n | 'focusedTransformStyle'\n | 'unfocusedTransformStyle'\n | 'focusedOpacityStyle'\n | 'unfocusedOpacityStyle'\n | 'pickerItemStyle'\n > {\n label: string;\n index: number;\n itemWidth: number;\n currentIndex: SharedValue<number>;\n onPress: () => void;\n}\n\nfunction PickerItem({\n label,\n index,\n itemWidth,\n currentIndex,\n onPress,\n focusedTransformStyle,\n unfocusedTransformStyle,\n focusedOpacityStyle,\n unfocusedOpacityStyle,\n pickerItemStyle,\n}: PickerItemProps) {\n const isFocused = useDerivedValue(() => currentIndex.value === index);\n\n const animatedStyle = useAnimatedStyle(() => {\n return {\n transform: isFocused.value ? focusedTransformStyle : unfocusedTransformStyle,\n opacity: isFocused.value ? focusedOpacityStyle : unfocusedOpacityStyle,\n };\n }, [index]);\n\n return (\n <Pressable onPress={onPress}>\n <View style={[styles.itemContainer, { width: itemWidth }, pickerItemStyle]}>\n <Animated.Text style={[styles.itemText, animatedStyle]}>{label}</Animated.Text>\n </View>\n </Pressable>\n );\n}\n\nconst styles = StyleSheet.create({\n container: {\n width: '100%',\n },\n itemContainer: {\n justifyContent: 'center',\n alignItems: 'center',\n height: 60,\n },\n itemText: {\n fontSize: 13,\n fontWeight: '700',\n color: '#C9CED9',\n },\n itemTextSelected: {\n fontSize: 15,\n fontWeight: '800',\n color: '#000000',\n },\n});\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"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","sourcesContent":["import { type Ref, useImperativeHandle, useMemo, useRef, useState } from 'react';\nimport {\n type FlatList as NativeFlatList,\n PixelRatio,\n Pressable,\n StyleSheet,\n type TextStyle,\n View,\n type ViewStyle,\n} from 'react-native';\nimport Animated, {\n type FlatListPropsWithLayout,\n type SharedValue,\n runOnJS,\n useAnimatedScrollHandler,\n useAnimatedStyle,\n useDerivedValue,\n useSharedValue,\n} from 'react-native-reanimated';\n\ninterface FlatListProps\n extends Omit<\n FlatListPropsWithLayout<PickerOption>,\n | 'ref'\n | 'horizontal'\n | 'data'\n | 'renderItem'\n | 'initialScrollIndex'\n | 'onScroll'\n | 'snapToOffsets'\n | 'contentContainerStyle'\n | 'getItemLayout'\n > {}\n\nexport interface PickerOption {\n label: string;\n value: string | number;\n}\n\nexport interface HorizontalPickerRef {\n scrollToEnd: (params?: { animated?: boolean | null }) => void;\n scrollToIndex: (params: {\n animated?: boolean | null;\n index: number;\n viewOffset?: number;\n viewPosition?: number;\n }) => void;\n scrollToItem: (params: {\n animated?: boolean | null;\n item: PickerOption;\n viewOffset?: number;\n viewPosition?: number;\n }) => void;\n scrollToOffset: (params: { animated?: boolean | null; offset: number }) => void;\n}\n\nexport interface HorizontalPickerProps extends FlatListProps {\n ref?: Ref<HorizontalPickerRef | null>;\n items: PickerOption[];\n initialScrollIndex?: number;\n visibleItemCount?: number;\n onChange?: (value: PickerOption['value'], index: number) => void;\n focusedTransformStyle?: ViewStyle['transform'];\n unfocusedTransformStyle?: ViewStyle['transform'];\n focusedOpacityStyle?: number;\n unfocusedOpacityStyle?: number;\n pickerItemStyle?: ViewStyle;\n pickerItemTextStyle?: TextStyle;\n}\n\nexport function HorizontalPicker({\n ref,\n items,\n initialScrollIndex = 0,\n visibleItemCount = 7,\n onChange,\n keyExtractor = (item, index) => `${item.value}-${index}`,\n scrollEventThrottle = 16,\n decelerationRate = 'fast',\n onLayout,\n showsHorizontalScrollIndicator = false,\n initialNumToRender = 15,\n maxToRenderPerBatch = 15,\n removeClippedSubviews = true,\n focusedTransformStyle = [{ scale: 1.15 }],\n unfocusedTransformStyle = [{ scale: 1 }],\n focusedOpacityStyle = 1,\n unfocusedOpacityStyle = 0.2,\n pickerItemStyle,\n pickerItemTextStyle,\n style,\n ...props\n}: HorizontalPickerProps) {\n const listRef = useRef<NativeFlatList<PickerOption> | null>(null);\n const [width, setWidth] = useState<number>(0);\n\n const currentIndex = useSharedValue<number>(initialScrollIndex);\n\n const { itemWidth, paddingSide } = useMemo(() => {\n const itemWidth = PixelRatio.roundToNearestPixel(width / visibleItemCount);\n const paddingSide = PixelRatio.roundToNearestPixel(width / 2 - itemWidth / 2);\n return {\n itemWidth: itemWidth,\n paddingSide: paddingSide,\n };\n }, [width, visibleItemCount]);\n\n const snapOffsets = useMemo(() => {\n return items.map((_, index) => PixelRatio.roundToNearestPixel(index * itemWidth));\n }, [items, itemWidth]);\n\n const handleOnChange = (index: number) => {\n const item = items[index];\n if (item) {\n onChange?.(item.value, index);\n }\n };\n\n const scrollToIndex = (index: number) => {\n listRef.current?.scrollToOffset({\n offset: index * itemWidth,\n animated: true,\n });\n };\n\n const onScroll = useAnimatedScrollHandler({\n onScroll: (e) => {\n const newIndex = Math.round(e.contentOffset.x / itemWidth);\n const safeIndex = Math.max(0, Math.min(items.length - 1, newIndex));\n currentIndex.value = safeIndex;\n },\n onMomentumEnd: (e) => {\n const newIndex = Math.round(e.contentOffset.x / itemWidth);\n const safeIndex = Math.max(0, Math.min(items.length - 1, newIndex));\n currentIndex.value = safeIndex;\n runOnJS(handleOnChange)(safeIndex);\n },\n });\n\n useImperativeHandle(\n ref,\n () => ({\n scrollToEnd: (params) => listRef.current?.scrollToEnd(params),\n scrollToIndex: (params) => listRef.current?.scrollToIndex(params),\n scrollToItem: (params) => listRef.current?.scrollToItem(params),\n scrollToOffset: (params) => listRef.current?.scrollToOffset(params),\n }),\n [],\n );\n\n return (\n <Animated.FlatList\n {...props}\n ref={listRef}\n horizontal={true}\n data={items}\n keyExtractor={keyExtractor}\n renderItem={({ item, index }) => (\n <PickerItem\n label={item.label}\n index={index}\n itemWidth={itemWidth}\n currentIndex={currentIndex}\n onPress={() => scrollToIndex(index)}\n focusedTransformStyle={focusedTransformStyle}\n unfocusedTransformStyle={unfocusedTransformStyle}\n focusedOpacityStyle={focusedOpacityStyle}\n unfocusedOpacityStyle={unfocusedOpacityStyle}\n pickerItemStyle={pickerItemStyle}\n pickerItemTextStyle={pickerItemTextStyle}\n />\n )}\n onScroll={onScroll}\n scrollEventThrottle={scrollEventThrottle}\n decelerationRate={decelerationRate}\n onLayout={(e) => {\n if (typeof onLayout === 'function') {\n onLayout(e);\n }\n setWidth(e.nativeEvent.layout.width);\n }}\n showsHorizontalScrollIndicator={showsHorizontalScrollIndicator}\n snapToOffsets={snapOffsets}\n contentContainerStyle={{ paddingHorizontal: paddingSide }}\n getItemLayout={(_, index) => ({\n length: itemWidth,\n offset: itemWidth * index,\n index,\n })}\n initialScrollIndex={initialScrollIndex}\n initialNumToRender={initialNumToRender}\n maxToRenderPerBatch={maxToRenderPerBatch}\n removeClippedSubviews={removeClippedSubviews}\n style={[styles.container, style]}\n />\n );\n}\ninterface PickerItemProps\n extends Pick<\n HorizontalPickerProps,\n | 'focusedTransformStyle'\n | 'unfocusedTransformStyle'\n | 'focusedOpacityStyle'\n | 'unfocusedOpacityStyle'\n | 'pickerItemStyle'\n | 'pickerItemTextStyle'\n > {\n label: string;\n index: number;\n itemWidth: number;\n currentIndex: SharedValue<number>;\n onPress: () => void;\n}\n\nfunction PickerItem({\n label,\n index,\n itemWidth,\n currentIndex,\n onPress,\n focusedTransformStyle,\n unfocusedTransformStyle,\n focusedOpacityStyle,\n unfocusedOpacityStyle,\n pickerItemStyle,\n pickerItemTextStyle,\n}: PickerItemProps) {\n const isFocused = useDerivedValue(() => currentIndex.value === index);\n\n const animatedStyle = useAnimatedStyle(() => {\n return {\n transform: isFocused.value ? focusedTransformStyle : unfocusedTransformStyle,\n opacity: isFocused.value ? focusedOpacityStyle : unfocusedOpacityStyle,\n };\n }, [index]);\n\n return (\n <Pressable onPress={onPress}>\n <View style={[styles.itemContainer, { width: itemWidth }, pickerItemStyle]}>\n <Animated.Text style={[styles.itemText, animatedStyle, pickerItemTextStyle]}>{label}</Animated.Text>\n </View>\n </Pressable>\n );\n}\n\nconst styles = StyleSheet.create({\n container: {\n width: '100%',\n },\n itemContainer: {\n justifyContent: 'center',\n alignItems: 'center',\n },\n itemText: {\n fontSize: 13,\n fontWeight: '700',\n color: '#000000',\n },\n});\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-horizontal-picker",
3
- "version": "0.1.8",
3
+ "version": "0.2.0",
4
4
  "description": "A performant horizontal picker component for React Native and Expo apps",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -54,7 +54,7 @@
54
54
  },
55
55
  "peerDependencies": {
56
56
  "expo": "*",
57
- "react": "*",
57
+ "react": "^19.0.0",
58
58
  "react-native": "*",
59
59
  "react-native-reanimated": ">=2.10.0"
60
60
  },
package/src/index.tsx CHANGED
@@ -1,5 +1,13 @@
1
- import { useMemo, useRef, useState } from 'react';
2
- import { PixelRatio, Pressable, StyleSheet, View, type ViewStyle } from 'react-native';
1
+ import { type Ref, useImperativeHandle, useMemo, useRef, useState } from 'react';
2
+ import {
3
+ type FlatList as NativeFlatList,
4
+ PixelRatio,
5
+ Pressable,
6
+ StyleSheet,
7
+ type TextStyle,
8
+ View,
9
+ type ViewStyle,
10
+ } from 'react-native';
3
11
  import Animated, {
4
12
  type FlatListPropsWithLayout,
5
13
  type SharedValue,
@@ -10,11 +18,6 @@ import Animated, {
10
18
  useSharedValue,
11
19
  } from 'react-native-reanimated';
12
20
 
13
- interface PickerOption {
14
- label: string;
15
- value: string | number;
16
- }
17
-
18
21
  interface FlatListProps
19
22
  extends Omit<
20
23
  FlatListPropsWithLayout<PickerOption>,
@@ -29,7 +32,30 @@ interface FlatListProps
29
32
  | 'getItemLayout'
30
33
  > {}
31
34
 
35
+ export interface PickerOption {
36
+ label: string;
37
+ value: string | number;
38
+ }
39
+
40
+ export interface HorizontalPickerRef {
41
+ scrollToEnd: (params?: { animated?: boolean | null }) => void;
42
+ scrollToIndex: (params: {
43
+ animated?: boolean | null;
44
+ index: number;
45
+ viewOffset?: number;
46
+ viewPosition?: number;
47
+ }) => void;
48
+ scrollToItem: (params: {
49
+ animated?: boolean | null;
50
+ item: PickerOption;
51
+ viewOffset?: number;
52
+ viewPosition?: number;
53
+ }) => void;
54
+ scrollToOffset: (params: { animated?: boolean | null; offset: number }) => void;
55
+ }
56
+
32
57
  export interface HorizontalPickerProps extends FlatListProps {
58
+ ref?: Ref<HorizontalPickerRef | null>;
33
59
  items: PickerOption[];
34
60
  initialScrollIndex?: number;
35
61
  visibleItemCount?: number;
@@ -39,9 +65,11 @@ export interface HorizontalPickerProps extends FlatListProps {
39
65
  focusedOpacityStyle?: number;
40
66
  unfocusedOpacityStyle?: number;
41
67
  pickerItemStyle?: ViewStyle;
68
+ pickerItemTextStyle?: TextStyle;
42
69
  }
43
70
 
44
71
  export function HorizontalPicker({
72
+ ref,
45
73
  items,
46
74
  initialScrollIndex = 0,
47
75
  visibleItemCount = 7,
@@ -59,13 +87,13 @@ export function HorizontalPicker({
59
87
  focusedOpacityStyle = 1,
60
88
  unfocusedOpacityStyle = 0.2,
61
89
  pickerItemStyle,
90
+ pickerItemTextStyle,
62
91
  style,
63
92
  ...props
64
93
  }: HorizontalPickerProps) {
65
- const listRef = useRef<Animated.FlatList<PickerOption>>(null);
94
+ const listRef = useRef<NativeFlatList<PickerOption> | null>(null);
66
95
  const [width, setWidth] = useState<number>(0);
67
96
 
68
- const scrollX = useSharedValue<number>(0);
69
97
  const currentIndex = useSharedValue<number>(initialScrollIndex);
70
98
 
71
99
  const { itemWidth, paddingSide } = useMemo(() => {
@@ -97,7 +125,6 @@ export function HorizontalPicker({
97
125
 
98
126
  const onScroll = useAnimatedScrollHandler({
99
127
  onScroll: (e) => {
100
- scrollX.value = e.contentOffset.x;
101
128
  const newIndex = Math.round(e.contentOffset.x / itemWidth);
102
129
  const safeIndex = Math.max(0, Math.min(items.length - 1, newIndex));
103
130
  currentIndex.value = safeIndex;
@@ -110,6 +137,17 @@ export function HorizontalPicker({
110
137
  },
111
138
  });
112
139
 
140
+ useImperativeHandle(
141
+ ref,
142
+ () => ({
143
+ scrollToEnd: (params) => listRef.current?.scrollToEnd(params),
144
+ scrollToIndex: (params) => listRef.current?.scrollToIndex(params),
145
+ scrollToItem: (params) => listRef.current?.scrollToItem(params),
146
+ scrollToOffset: (params) => listRef.current?.scrollToOffset(params),
147
+ }),
148
+ [],
149
+ );
150
+
113
151
  return (
114
152
  <Animated.FlatList
115
153
  {...props}
@@ -129,6 +167,7 @@ export function HorizontalPicker({
129
167
  focusedOpacityStyle={focusedOpacityStyle}
130
168
  unfocusedOpacityStyle={unfocusedOpacityStyle}
131
169
  pickerItemStyle={pickerItemStyle}
170
+ pickerItemTextStyle={pickerItemTextStyle}
132
171
  />
133
172
  )}
134
173
  onScroll={onScroll}
@@ -164,6 +203,7 @@ interface PickerItemProps
164
203
  | 'focusedOpacityStyle'
165
204
  | 'unfocusedOpacityStyle'
166
205
  | 'pickerItemStyle'
206
+ | 'pickerItemTextStyle'
167
207
  > {
168
208
  label: string;
169
209
  index: number;
@@ -183,6 +223,7 @@ function PickerItem({
183
223
  focusedOpacityStyle,
184
224
  unfocusedOpacityStyle,
185
225
  pickerItemStyle,
226
+ pickerItemTextStyle,
186
227
  }: PickerItemProps) {
187
228
  const isFocused = useDerivedValue(() => currentIndex.value === index);
188
229
 
@@ -196,7 +237,7 @@ function PickerItem({
196
237
  return (
197
238
  <Pressable onPress={onPress}>
198
239
  <View style={[styles.itemContainer, { width: itemWidth }, pickerItemStyle]}>
199
- <Animated.Text style={[styles.itemText, animatedStyle]}>{label}</Animated.Text>
240
+ <Animated.Text style={[styles.itemText, animatedStyle, pickerItemTextStyle]}>{label}</Animated.Text>
200
241
  </View>
201
242
  </Pressable>
202
243
  );
@@ -209,16 +250,10 @@ const styles = StyleSheet.create({
209
250
  itemContainer: {
210
251
  justifyContent: 'center',
211
252
  alignItems: 'center',
212
- height: 60,
213
253
  },
214
254
  itemText: {
215
255
  fontSize: 13,
216
256
  fontWeight: '700',
217
- color: '#C9CED9',
218
- },
219
- itemTextSelected: {
220
- fontSize: 15,
221
- fontWeight: '800',
222
257
  color: '#000000',
223
258
  },
224
259
  });