react-native-native-select 1.2.0 → 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
@@ -2,20 +2,20 @@
2
2
 
3
3
  <p>
4
4
  <a href="https://github.com/wneel/react-native-native-select/blob/HEAD/LICENSE">
5
- <img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="React Native Wheel Picker is released under the MIT license." />
5
+ <img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="react-native-native-select is released under the MIT license." />
6
6
  </a>
7
7
  <a href="https://www.npmjs.com/package/react-native-native-select">
8
8
  <img src="https://img.shields.io/npm/v/react-native-native-select?color=brightgreen&label=npm%20package" alt="Current npm package version." />
9
9
  </a>
10
10
  <a href="https://www.npmjs.com/package/react-native-native-select">
11
- <img src="https://img.shields.io/npm/dm/react-native-native-select" alt="Number of downloads per week." />
11
+ <img src="https://img.shields.io/npm/dm/react-native-native-select" alt="Number of downloads per month." />
12
12
  </a>
13
13
  </p>
14
14
 
15
15
 
16
16
  A strictly native, performant Select component for React Native built exclusively for the **New Architecture**.
17
17
 
18
- It leverages actual OS primitives: `UIMenu` (iOS 14+), `UIPickerView` (iOS Wheel), and `AppCompatSpinner` (Android), offering a truly native look and feel that JS-based libraries cannot match.
18
+ There is no JS rendering logic in this package. When your user opens the list, they are touching `UIMenu`, `UIPickerView` or `AppCompatSpinner` directly: the same widgets the operating system uses for its own pickers, with their animations, their dark mode and their accessibility tree.
19
19
 
20
20
  ## Preview
21
21
 
@@ -24,22 +24,47 @@ It leverages actual OS primitives: `UIMenu` (iOS 14+), `UIPickerView` (iOS Wheel
24
24
  | <img src="https://github.com/user-attachments/assets/8a65c76b-f0a8-4f89-88d3-49000abe354d" height="300" /> | <img src="https://github.com/user-attachments/assets/71d98f6c-984a-49e8-bc98-abbfb86de535" height="300" /> | <img src="https://github.com/user-attachments/assets/5936637a-e63a-445c-9c9f-3a05940b6f9e" height="300" /> |
25
25
  | *Native UIMenu (iOS 14+)* | *Classic UIPickerView* | *Native AppCompatSpinner* |
26
26
 
27
- ## Why this library?
27
+ ## What it actually renders
28
28
 
29
- Most Select/Picker libraries in the React Native ecosystem fall into two categories:
30
- 1. **JS-based Simulations:** They render a Modal with a FlatList. They are customizable but feel "off" compared to the OS native UI and often lack accessibility standards like [`react-native-picker-select`](https://www.npmjs.com/package/react-native-picker-select).
31
- 2. **Legacy Wrappers:** Libraries like [`@react-native-picker/picker`](https://www.npmjs.com/package/@react-native-picker/picker) are excellent but often rely on the old bridge or split functionality across different components.
29
+ | Platform | Mode | Native widget | Source |
30
+ | :--- | :--- | :--- | :--- |
31
+ | iOS | `dropdown` | A real `UIMenu` built from `UIAction`s, attached to a `UIButton` with `showsMenuAsPrimaryAction = YES` (iOS 14+). The selected row carries `UIMenuElementStateOn`, so the system draws its own checkmark. | `ios/RTNSelect.mm` |
32
+ | iOS | `dialog` (default) | `UIPickerView`, the classic wheel, with this view as its own dataSource and delegate. | `ios/RTNSelect.mm` |
33
+ | Android | any | `AppCompatSpinner` with an `ArrayAdapter` over the platform `simple_spinner_item` and `simple_spinner_dropdown_item` layouts. | `android/src/main/java/com/rtnselect/SelectView.java` |
32
34
 
33
- **react-native-native-select** is designed for modern apps:
34
- * **Zero JS Thread Overhead:** Fully native implementation using Fabric.
35
- * **Modern iOS UI:** Supports the iOS 14+ `pull-down` menu style out of the box.
36
- * **Native Performance:** Interactions run on the UI thread.
35
+ On iOS a single `RCTViewComponentView` holds both the picker and the button, and `mode` decides which one is hidden. Nothing is custom-drawn on either platform: no `Modal`, no `FlatList`, no wheel re-implemented in JS.
36
+
37
+ ## How it compares
38
+
39
+ The popular alternatives are mature, well documented and far more featureful than this one. Here is where each of them sits, with weekly npm downloads so the size difference is on the table (figures pulled from the npm registry for the week of 2026-08-18).
40
+
41
+ | Library | Downloads / week | What draws the list | New Architecture | Runtime deps | Multi-select |
42
+ | :--- | ---: | :--- | :--- | :--- | :--- |
43
+ | **react-native-native-select** | see the badge above | The OS widget itself: `UIMenu`, `UIPickerView`, `AppCompatSpinner` | Required, old bridge unsupported | none | no |
44
+ | [`@react-native-picker/picker`](https://www.npmjs.com/package/@react-native-picker/picker) | 959k | Native pickers too (`RNCPicker` on iOS, dialog and dropdown pickers on Android), plus macOS and Windows | Supported, and it still ships the old-architecture code path | none | no, one `selectedValue` |
45
+ | [`react-native-picker-select`](https://www.npmjs.com/package/react-native-picker-select) | 175k | A JS wrapper that puts `@react-native-picker/picker` inside a React `Modal` on iOS | Inherits whatever its peer picker supports | `lodash.isequal`, `lodash.isobject`, and a peer on `@react-native-picker/picker` | no |
46
+ | [`react-native-element-dropdown`](https://www.npmjs.com/package/react-native-element-dropdown) | 167k | Pure JS: a `Modal` with a `FlatList` of rows | Works, it is JS only | `lodash` | yes, a `MultiSelect` component |
47
+ | [`react-native-dropdown-picker`](https://www.npmjs.com/package/react-native-dropdown-picker) | 157k | Pure JS, no native code in the package at all | Works, it is JS only | none | yes, single and multiple |
48
+
49
+ Read that table as a positioning statement, not a verdict. Those four libraries give you searchable lists, `{label, value}` items, icons, badges, themes, RTL, placeholders, and multi-select. This one gives you none of that.
50
+
51
+ What it gives you instead is narrow and specific:
52
+
53
+ * **The real widget.** Not an imitation of the platform look, the platform widget. It ages with the OS instead of against it.
54
+ * **The interaction never touches JS.** Opening the menu, scrolling the wheel and highlighting a row are UIKit and Android View work. JS hears about it once, when the value changes.
55
+ * **A long list costs nothing.** A `UIMenu` of 250 rows is 250 `UIAction`s handed to UIKit, not 250 React elements virtualised by a `FlatList`.
56
+ * **Nothing to keep in sync.** No dependencies, and the whole surface is five props.
57
+
58
+ If you need any of the features in the right-hand columns, use one of those libraries. If you want the OS widget and a component you can read end to end in ten minutes, keep reading.
37
59
 
38
60
  ## Requirements
39
61
 
40
- * **React Native:** >= 0.71.0
41
- * **Architecture:** New Architecture **Enabled**
42
- * **iOS:** 14.0+ (for Dropdown mode), 11.0+ (for Dialog mode)
62
+ * **React Native:** `>= 0.71.0` (declared as a peer dependency, along with `react`).
63
+ * **Architecture:** the New Architecture, enabled. `RCT_NEW_ARCH_ENABLED=1` must have been set when you ran `pod install`.
64
+ * **iOS:** 14.0+ for `dropdown` mode, since `UIMenu` and `showsMenuAsPrimaryAction` are gated behind an availability check. The podspec deployment target is 11.0, and `dialog` mode works all the way down.
65
+ * **Android:** `compileSdkVersion` and `targetSdkVersion` default to 31 and `minSdkVersion` to 21, each read through `safeExtGet` so your root `ext` values win.
66
+
67
+ The old bridge is genuinely not supported, and that is a compile-time fact rather than a policy: `ios/RTNSelect.mm` imports the codegen-generated `RTNSelectSpec` headers unconditionally, and `SelectViewManager` implements the generated `RTNSelectManagerInterface`. Without codegen output, neither side builds.
43
68
 
44
69
  ## Installation
45
70
 
@@ -49,21 +74,28 @@ npm install react-native-native-select
49
74
  yarn add react-native-native-select
50
75
  ```
51
76
 
52
- ### iOS Setup
53
-
54
- Since this library uses native modules, you must run pod install:
77
+ ### iOS
55
78
 
56
79
  ```bash
57
80
  cd ios && pod install
58
81
  ```
59
82
 
60
- ## Usage
83
+ ### Android
84
+
85
+ Nothing to do. Autolinking picks up the library's own `SelectViewPackage`, and the shipped `AndroidManifest.xml` declares nothing but the package name: no permissions, no activities, no `MainApplication` edit, no Proguard rule.
86
+
87
+ ## Quick start
61
88
 
62
89
  ```tsx
63
- import { StyleSheet, View, Text } from 'react-native';
90
+ import { useState } from 'react';
91
+ import { StyleSheet, Text, View } from 'react-native';
64
92
  import { Select } from 'react-native-native-select';
65
93
 
94
+ const FRUITS = ['Apple', 'Banana', 'Orange', 'Mango'];
95
+
66
96
  export default function App() {
97
+ const [index, setIndex] = useState(0);
98
+
67
99
  return (
68
100
  <View style={styles.container}>
69
101
  <Text style={styles.label}>Choose a fruit:</Text>
@@ -71,53 +103,277 @@ export default function App() {
71
103
  <Select
72
104
  style={styles.select}
73
105
  mode="dropdown"
74
- options={["Apple", "Banana", "Orange", "Mango"]}
75
- selectedIndex={0}
106
+ options={FRUITS}
107
+ selectedIndex={index}
76
108
  onValueChange={(e) => {
77
- console.log("Selected Item:", e.nativeEvent.value); // Ex: "Apple"
78
- console.log("Selected Index:", e.nativeEvent.index); // Ex: 0
109
+ setIndex(e.nativeEvent.index); // 0
110
+ console.log(e.nativeEvent.value); // "Apple"
79
111
  }}
80
112
  />
113
+
114
+ <Text>Selected: {FRUITS[index]}</Text>
81
115
  </View>
82
116
  );
83
117
  }
84
118
 
85
119
  const styles = StyleSheet.create({
86
- container: {
87
- flex: 1,
88
- justifyContent: 'center',
89
- padding: 20,
90
- },
91
- label: {
92
- marginBottom: 10,
93
- fontSize: 16,
94
- },
120
+ container: { flex: 1, justifyContent: 'center', padding: 20 },
121
+ label: { marginBottom: 10, fontSize: 16 },
95
122
  select: {
96
123
  width: '100%',
97
- height: 50, // Height is required for layout calculation
124
+ height: 50, // width and height are load-bearing, see Troubleshooting
98
125
  },
99
126
  });
100
127
  ```
101
128
 
102
- ### Android Usage
129
+ Two runnable screens live in [`tests/ios_demo.tsx`](./tests/ios_demo.tsx) and [`tests/android_demo.tsx`](./tests/android_demo.tsx). Drop either one in as your `App` component.
130
+
131
+ ## The overlay pattern
132
+
133
+ This is the part worth reading twice, and the recommended way to ship this component inside a real design system.
134
+
135
+ The native widget draws itself. You cannot give `UIMenu` your border radius or teach `AppCompatSpinner` your font, and on Android the spinner does not reliably reflect a controlled `selectedIndex` (the visual state can lag behind the prop). So do not fight it: **render your own label, and stretch the `Select` over it invisibly.** The tap opens the real OS widget, the state comes back through `onValueChange`, and the box the user actually looks at is 100% yours.
136
+
137
+ Since v1.2.0 the Android view calls `setBackground(null)` on init, so the spinner contributes no background of its own underneath your styling.
138
+
139
+ ### The 0.02 constant
140
+
141
+ ```tsx
142
+ opacity: 0.02 // never 0
143
+ ```
144
+
145
+ That number is the whole trick. iOS `hitTest:withEvent:` ignores views whose alpha is at or below 0.01, so an overlay at `opacity: 0` is invisible **and** untappable: you get a beautiful button that does nothing. `0.02` clears that threshold with a margin to spare and is imperceptible on screen. Android is more forgiving (the demo screen uses `0` and works), so use `0.02` everywhere and stop thinking about it.
146
+
147
+ ### A complete field component
148
+
149
+ ```tsx
150
+ import { useState } from 'react';
151
+ import { StyleSheet, Text, View } from 'react-native';
152
+ import { Select } from 'react-native-native-select';
153
+
154
+ type Props = {
155
+ label: string;
156
+ options: string[];
157
+ selectedIndex: number;
158
+ onSelect: (index: number, value: string) => void;
159
+ };
160
+
161
+ export function SelectField({ label, options, selectedIndex, onSelect }: Props) {
162
+ return (
163
+ <View style={styles.field}>
164
+ <Text style={styles.label} accessible={false}>
165
+ {label}
166
+ </Text>
167
+
168
+ <View
169
+ accessible={true}
170
+ accessibilityRole="button"
171
+ accessibilityLabel={`${label}, ${options[selectedIndex]}`}
172
+ accessibilityHint="Opens the list of options"
173
+ >
174
+ {/* Your design system draws this. */}
175
+ <Text style={styles.value} accessible={false}>
176
+ {options[selectedIndex]}
177
+ </Text>
178
+
179
+ {/* The real widget, stretched over it, all but invisible. */}
180
+ <Select
181
+ style={styles.overlay}
182
+ mode="dropdown"
183
+ options={options}
184
+ selectedIndex={selectedIndex}
185
+ onValueChange={(e) => onSelect(e.nativeEvent.index, e.nativeEvent.value)}
186
+ />
187
+ </View>
188
+ </View>
189
+ );
190
+ }
191
+
192
+ const styles = StyleSheet.create({
193
+ field: { marginBottom: 16 },
194
+ label: { fontSize: 13, marginBottom: 6, color: '#444' },
195
+ value: {
196
+ height: 48,
197
+ lineHeight: 48,
198
+ paddingHorizontal: 14,
199
+ borderRadius: 12,
200
+ borderWidth: 1,
201
+ borderColor: '#2483ff',
202
+ fontWeight: '600',
203
+ },
204
+ overlay: {
205
+ position: 'absolute',
206
+ top: 0,
207
+ bottom: 0,
208
+ left: 0,
209
+ right: 0,
210
+ opacity: 0.02, // never 0: iOS hitTest drops taps at alpha <= 0.01
211
+ },
212
+ });
213
+ ```
214
+
215
+ Usage:
216
+
217
+ ```tsx
218
+ const [size, setSize] = useState(0);
219
+
220
+ <SelectField
221
+ label="Size"
222
+ options={['Small', 'Medium', 'Large']}
223
+ selectedIndex={size}
224
+ onSelect={(index) => setSize(index)}
225
+ />
226
+ ```
227
+
228
+ Three details make the difference between this working and nearly working:
103
229
 
104
- Please take a look at this [Android example usage](./tests/android_demo.tsx) because the native component is not updating the visual state at the moment
230
+ 1. `position: 'absolute'` with all four edges pinned, so the overlay covers exactly the box you drew, no more.
231
+ 2. `opacity: 0.02`, for the reason above.
232
+ 3. `mode="dropdown"`, so iOS anchors a `UIMenu` to the invisible button rather than showing a wheel where your field is.
105
233
 
106
234
  ## Props
107
235
 
236
+ `NativeProps` extends `ViewProps`, so every standard view prop (`style`, `testID`, `pointerEvents`, the accessibility props) is accepted on top of these.
237
+
108
238
  | Prop | Type | Required | Description |
109
239
  | :--- | :--- | :---: | :--- |
110
- | **`options`** | `string[]` | **Yes** | An array of strings to display in the list. |
111
- | **`selectedIndex`** | `number` | No | The index of the currently selected item. Defaults to `0`. |
112
- | **`mode`** | `'dropdown' \| 'dialog'` | No | **iOS Only.** <br>`dropdown`: Uses `UIMenu` (Modern iOS 14+). <br>`dialog`: Uses `UIPickerView` (Classic Wheel). <br> *On Android, this prop is ignored as it always uses the native Spinner.* |
113
- | **`textColor`** | `ColorValue` | No | **iOS only.** Custom text color for the picker. Overrides the system's default Light/Dark mode colors. Accepts any standard React Native color format (e.g., `'#FF0000'`, `'red'`, `'rgba(0,0,0,0.5)'`). |
114
- | **`onValueChange`** | `function` | No | Callback fired when an item is selected. Returns `{ value: string, index: number }`. |
115
- | **`style`** | `ViewStyle` | No | Standard style prop. **Note:** You must define `width` and `height` for the view to render correctly. |
240
+ | **`options`** | `ReadonlyArray<string>` | **Yes** | The rows to display. Strings only. |
241
+ | **`selectedIndex`** | `number` | No | Index of the selected row. The native views start at `0`. Behaves as a controlled value on iOS; see the Android caveat below. |
242
+ | **`mode`** | `'dialog' \| 'dropdown'` | No | **iOS only.** `dialog` (the codegen default) uses `UIPickerView`, the classic wheel. `dropdown` uses the iOS 14+ `UIMenu` pull-down. Ignored on Android, which always draws its spinner. |
243
+ | **`textColor`** | `ColorValue` | No | **iOS only.** Overrides the text color of both the wheel and the button title. Defaults to `UIColor.labelColor`, which already follows light and dark mode, so set this only when you need to override the system. |
244
+ | **`onValueChange`** | `(event) => void` | No | Fired on selection. Read `event.nativeEvent.value` (`string`) and `event.nativeEvent.index` (`number`). |
245
+ | **`style`** | `ViewStyle` | No | Standard style prop. Give it a `width` and a `height`, or Flexbox constraints that produce them. |
246
+
247
+ ```tsx
248
+ onValueChange={(e) => {
249
+ e.nativeEvent.value; // "Banana"
250
+ e.nativeEvent.index; // 1
251
+ }}
252
+ ```
253
+
254
+ ## Platform differences
255
+
256
+ Nothing here is a bug to be fixed later, it is what wrapping two different OS widgets behind one component costs. Better said out loud.
257
+
258
+ * **`mode` and `textColor` do nothing on Android.** In `SelectViewManager`, `setMode` has an empty body and `setTextColor` carries a comment saying so. They exist only because the codegen-generated `RTNSelectManagerInterface` requires them, and a missing method there is a build failure (that is exactly what v1.1.1 fixed). Style Android text through the overlay pattern instead.
259
+ * **Android does not reliably reflect a controlled `selectedIndex`.** `AppCompatSpinner` owns its own selection state and the manager pushes the prop through `setSelection`, which posts the update to the next frame. Treat the Android widget as uncontrolled and render the current value yourself, which is the overlay pattern again.
260
+ * **Writing `selectedIndex` on Android emits a change event.** `setSelection` reports back through `topValueChange` as well as the adapter's own `onItemSelected`, so a state update driven from JS can echo. If your handler does more than `setState`, guard it against the value it already holds.
261
+ * **Below iOS 14, `dropdown` degrades to an inert button.** Both `showsMenuAsPrimaryAction` and the menu construction sit behind `@available(iOS 14.0, *)`, so on iOS 11 to 13 the button renders with no menu attached. Use `dialog` if you still support those versions.
262
+ * **iOS applies props on the main queue.** `updateProps` diffs `options`, `selectedIndex`, `mode` and `textColor`, then dispatches the UIKit work asynchronously. A `selectedIndex` change animates the wheel; expect the visual update one turn later, not synchronously with your `setState`.
263
+
264
+ ## Limitations
265
+
266
+ * **Single-select only.** One `selectedIndex` in, one `{value, index}` out, and iOS pins `numberOfComponentsInPickerView` to `1`. There is no multiple, no chips, no checkbox list.
267
+ * **`options` is `string[]`.** No `{label, value}` objects, no icons, no per-row styling, no section headers, no disabled rows. Map to labels before rendering and map back through the index you get in the event.
268
+ * **No search or filter.** A 250-row `UIMenu` scrolls, it does not filter.
269
+ * **No Expo Go, and no config plugin.** The package ships native code and requires codegen, so it needs bare React Native or an Expo prebuild.
270
+ * **`mode` and `textColor` are iOS only** (see above).
271
+ * **No accessibility props of its own** (see the next section).
272
+ * **No placeholder or empty state.** With an empty `options` array the widget renders empty; there is no "Select an option" row unless you put one in `options` yourself.
273
+
274
+ ### If you need multi-select
275
+
276
+ Compose it in JS on top of the component. The library will not do it for you, and it is a dozen lines:
277
+
278
+ ```tsx
279
+ const ALL = ['Cat', 'Dog', 'Rabbit', 'Ferret'];
280
+
281
+ const [picked, setPicked] = useState<string[]>([]);
282
+ const remaining = ALL.filter((option) => !picked.includes(option));
283
+
284
+ return (
285
+ <>
286
+ {picked.map((value) => (
287
+ <Chip key={value} label={value} onRemove={() => setPicked(picked.filter((p) => p !== value))} />
288
+ ))}
289
+
290
+ {remaining.length > 0 && (
291
+ <Select
292
+ style={{ width: 200, height: 48 }}
293
+ mode="dropdown"
294
+ options={remaining}
295
+ onValueChange={(e) => setPicked([...picked, e.nativeEvent.value])}
296
+ />
297
+ )}
298
+ </>
299
+ );
300
+ ```
301
+
302
+ The `Select` stays uncontrolled here (no `selectedIndex`), and shrinking `options` after each pick is what stops the same row being chosen twice.
303
+
304
+ ## Accessibility
305
+
306
+ The library sets no `accessibilityRole` and no `accessibilityLabel` of its own. It does not need to for the widget itself, and it cannot for your field.
307
+
308
+ What comes free, because the rendered thing genuinely is the platform control:
309
+
310
+ * **VoiceOver** reads a `UIMenu` as a menu and a `UIPickerView` as an adjustable picker, with the announcements and gestures users already know from the rest of iOS. **TalkBack** reads `AppCompatSpinner` as a spinner.
311
+ * **Dark mode** works with no code from you: the iOS default text color is `UIColor.labelColor`, and the Android spinner takes your app theme.
312
+ * **Dynamic Type and font scale** apply, since the OS is drawing the text.
313
+ * **Reduce Motion, Switch Control, Voice Control, external keyboards:** the platform's job, and already done.
314
+
315
+ What you own is the wrapper. When you use the overlay pattern, the visual field is your own views, so give the group the semantics of one control:
316
+
317
+ ```tsx
318
+ <View
319
+ accessible={true}
320
+ accessibilityRole="button"
321
+ accessibilityLabel={`${label}, ${options[selectedIndex]}`}
322
+ accessibilityHint="Opens the list of options"
323
+ accessibilityState={{ disabled }}
324
+ >
325
+ <Text accessible={false}>{options[selectedIndex]}</Text>
326
+ <Select style={styles.overlay} mode="dropdown" options={options} selectedIndex={selectedIndex} onValueChange={handleChange} />
327
+ </View>
328
+ ```
329
+
330
+ Three things to get right there:
331
+
332
+ 1. `accessible={true}` on the wrapper, `accessible={false}` on the visual children, so the screen reader announces one control instead of a label, a value and an unnamed native view.
333
+ 2. An `accessibilityLabel` that carries both the field name and the current value. "Size, Medium" tells a blind user where they are; "Medium" alone does not.
334
+ 3. `accessibilityRole="button"`, because from the outside that is what your field is: a thing you press to open a list.
335
+
336
+ ## Under the hood
337
+
338
+ Worth knowing before you depend on it, since the whole implementation is short enough to audit in one sitting.
339
+
340
+ * **A true Fabric component via codegen.** `src/RTNSelectNativeComponent.ts` is a single `codegenNativeComponent<NativeProps>('RTNSelect')` call, and `codegenConfig` in `package.json` names the spec `RTNSelectSpec`. The rest of the implementation is Objective-C++ and Java.
341
+ * **The package ships raw TypeScript.** `main`, `react-native`, `types` and `source` all point at `src/index.ts`. There is no build step and no `dist/`, so the code you read on GitHub is exactly the code your bundler compiles.
342
+ * **Two TypeScript files, three iOS source files, three Java files.** No dependencies, no vendored assets, no generated code checked in.
343
+ * **iOS event path:** selection funnels through one `selectIndex:fromSource:` method that updates state, re-marks the `UIMenu` checkmark, and emits through the Fabric `RTNSelectEventEmitter`.
344
+ * **Android event path:** `SelectView` dispatches `topValueChange`, which `getExportedCustomDirectEventTypeConstants` maps to the JS prop `onValueChange`.
345
+ * **One thing to know about `textColor`:** on iOS it reaches the wheel through `setValue:forKey:@"textColor"`, key-value coding against a `UIPickerView` key that Apple does not document. It has worked for years and it is how everyone colors that widget, but it is not public API, and it is the one line in this package that could break on a future iOS release. The dropdown button's title color goes through the documented `setTitleColor:forState:`.
346
+
347
+ ## Used in production
348
+
349
+ This component ships in [Animalert](https://animalert.app), a live lost-pet reporting platform, on iOS and Android. It is on the [App Store](https://apps.apple.com/app/id6480419312) and [Google Play](https://play.google.com/store/apps/details?id=com.animalert), so what follows is checkable rather than asserted.
350
+
351
+ It backs a dozen direct call sites there plus a shared form field, itself reused a dozen more times, so essentially every select in the app is this component. The most demanding case is the country dial-code picker on the phone-number field: roughly 250 countries, rendered as a native `UIMenu` behind an invisible overlay rather than as a 250-row JS list. That field is the reason the overlay pattern and the `0.02` constant are documented above rather than rediscovered by everyone.
116
352
 
117
353
  ## Troubleshooting
118
354
 
119
- **The component is visible but empty (Empty space)**
120
- Ensure you have defined a `width` and `height` in the `style` prop. Native views on iOS require explicit dimensions or Flexbox constraints to render their subviews correctly.
355
+ **The component takes up space but shows nothing.**
356
+ Give the `style` an explicit `width` and `height`, or Flexbox constraints that resolve to real numbers. A native view with a zero-sized frame lays its subviews out into nothing.
357
+
358
+ **The app crashes on launch, or the iOS build cannot find `RTNSelectSpec/Props.h`.**
359
+ `RCT_NEW_ARCH_ENABLED=1` was not set when you ran `pod install`. Set it, re-run `pod install`, and rebuild. There is no old-bridge fallback.
360
+
361
+ **An Android build fails on a missing method in `RTNSelectManagerInterface`.**
362
+ Update to 1.1.1 or later, where the `setTextColor` stub the generated interface expects was added.
363
+
364
+ **My invisible overlay does not respond to taps on iOS.**
365
+ Its `opacity` is `0` (or below `0.02`). See the overlay pattern section.
366
+
367
+ **Android ignores `mode` or `textColor`.**
368
+ Expected, they are iOS only. See Platform differences.
369
+
370
+ **Android fires `onValueChange` when I set `selectedIndex` from JS.**
371
+ Expected too, and documented in Platform differences. Guard your handler against a value that has not changed.
372
+
373
+ ## Contributing
374
+
375
+ Bug reports and pull requests are welcome. [`CONTRIBUTING.md`](./CONTRIBUTING.md) covers how to run the component against the demo screens, and what to include in a report so it can be reproduced. Release history is in [`CHANGELOG.md`](./CHANGELOG.md).
376
+
377
+ ## License
121
378
 
122
- **App crashes on launch**
123
- Ensure `RCT_NEW_ARCH_ENABLED=1` was present when you ran `pod install`. This library does not support the old React Native Bridge.
379
+ MIT, Wayan NEEL. See [LICENSE](./LICENSE).
File without changes
@@ -0,0 +1,2 @@
1
+ #Thu Aug 27 12:56:24 CEST 2026
2
+ gradle.version=9.2.0
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-native-select",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "react-native-native-select is a strictly native, performant Select component for React Native built exclusively for the New Architecture.",
5
5
  "main": "src/index.ts",
6
6
  "react-native": "src/index.ts",