Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
194 changes: 95 additions & 99 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,14 +158,15 @@ export default class RNPickerSelect extends PureComponent {
}).concat(this.props.items);
const itemsChanged = !isEqual(prevState.items, items);

// update selectedItem if value prop is defined and differs from currently selected item
// update selectedItem if value or itemKey prop is defined and differs from currently selected item
const { selectedItem, idx } = RNPickerSelect.getSelectedItem({
items,
key: this.props.itemKey,
value: this.props.value,
});
const selectedItemChanged =
!isEqual(this.props.value, undefined) && !isEqual(prevState.selectedItem, selectedItem);
(!isEqual(this.props.value, undefined) && !isEqual(prevState.selectedItem, selectedItem)) ||
(!isEqual(this.props.itemKey, prevProps.itemKey));

if (itemsChanged || selectedItemChanged) {
this.props.onValueChange(selectedItem.value, idx);
Expand Down Expand Up @@ -328,8 +329,7 @@ export default class RNPickerSelect extends PureComponent {
<View
style={[
defaultStyles.chevron,
this.isDarkTheme() ? defaultStyles.chevronDark : {},
this.isDarkTheme() ? style.chevronDark : style.chevron,
style.chevron,
defaultStyles.chevronUp,
style.chevronUp,
onUpArrow ? [defaultStyles.chevronActive, style.chevronActive] : {},
Expand All @@ -343,8 +343,7 @@ export default class RNPickerSelect extends PureComponent {
<View
style={[
defaultStyles.chevron,
this.isDarkTheme() ? defaultStyles.chevronDark : {},
this.isDarkTheme() ? style.chevronDark : style.chevron,
style.chevron,
defaultStyles.chevronDown,
style.chevronDown,
onDownArrow ? [defaultStyles.chevronActive, style.chevronActive] : {},
Expand All @@ -358,28 +357,41 @@ export default class RNPickerSelect extends PureComponent {
this.togglePicker(true, onDonePress, true);
}}
onPressIn={() => {
this.setState({ doneDepressed: true });
this.setState({
doneDepressed: true,
});
}}
onPressOut={() => {
this.setState({ doneDepressed: false });
}}
hitSlop={{
top: 4,
right: 4,
bottom: 4,
left: 4,
this.setState({
doneDepressed: false,
});
}}
hitSlop={{ top: 4, right: 4, bottom: 4, left: 4 }}
{...touchableDoneProps}
>
<View testID="needed_for_touchable">
<View
accessible
accessibilityRole="button"
accessibilityLabel={doneText}
style={[
defaultStyles.done,
this.isDarkTheme() ? defaultStyles.doneDark : {},
this.isDarkTheme() ? style.doneDark : style.done,
doneDepressed
? [defaultStyles.doneDepressed, style.doneDepressed]
: {},
]}
>
<Text
testID="done_text"
allowFontScaling={false}
adjustsFontSizeToFit={false}
style={[
defaultStyles.done,
this.isDarkTheme() ? defaultStyles.doneDark : {},
this.isDarkTheme() ? style.doneDark : style.done,
doneDepressed ? [defaultStyles.doneDepressed, style.doneDepressed] : {},
defaultStyles.doneText,
this.isDarkTheme() ? defaultStyles.doneTextDark : {},
this.isDarkTheme() ? style.doneTextDark : style.doneText,
doneDepressed
? [defaultStyles.doneTextDepressed, style.doneTextDepressed]
: {},
]}
>
{doneText}
Expand All @@ -391,30 +403,32 @@ export default class RNPickerSelect extends PureComponent {
}

renderIcon() {
const { style, Icon } = this.props;
const { Icon } = this.props;

if (!Icon) {
return null;
if (Icon) {
return <Icon testID="icon_ios" />;
}

return (
<View testID="icon_container" style={[defaultStyles.iconContainer, style.iconContainer]}>
<Icon testID="icon" />
</View>
);
return null;
}

renderTextInputOrChildren() {
const { children, style, textInputProps } = this.props;
const { children, style, textInputProps, fixAndroidTouchableBug } = this.props;
const { selectedItem } = this.state;

const containerStyle =
Platform.OS === 'ios' ? style.inputIOSContainer : style.inputAndroidContainer;
const containerStyle = Platform.OS === 'android' && fixAndroidTouchableBug
? { height: 0, width: 0, flex: 1 }
: {};

if (children) {
return (
<View pointerEvents="box-only" style={containerStyle}>
{children}
{React.Children.map(children, (child) =>
React.cloneElement(child, {
pointerEvents: 'none',
style: [child.props.style, this.getPlaceholderStyle()],
})
)}
</View>
);
}
Expand All @@ -423,11 +437,8 @@ export default class RNPickerSelect extends PureComponent {
<View pointerEvents="box-only" style={containerStyle}>
<TextInput
testID="text_input"
style={[
Platform.OS === 'ios' ? style.inputIOS : style.inputAndroid,
this.getPlaceholderStyle(),
]}
value={selectedItem.inputLabel ? selectedItem.inputLabel : selectedItem.label}
style={[style.inputIOS, this.getPlaceholderStyle()]}
value={selectedItem.inputLabel || selectedItem.label}
ref={this.setInputRef}
editable={false}
{...textInputProps}
Expand All @@ -438,7 +449,7 @@ export default class RNPickerSelect extends PureComponent {
}

renderIOS() {
const { style, modalProps, pickerProps, touchableWrapperProps } = this.props;
const { doneText, modalProps, pickerProps, style } = this.props;
const { animationType, orientation, selectedItem, showPicker } = this.state;

return (
Expand All @@ -449,39 +460,44 @@ export default class RNPickerSelect extends PureComponent {
this.togglePicker(true);
}}
activeOpacity={1}
{...touchableWrapperProps}
>
{this.renderTextInputOrChildren()}
</TouchableOpacity>

<Modal
testID="ios_modal"
visible={showPicker}
transparent
animationType={animationType}
supportedOrientations={['portrait', 'landscape']}
onDismiss={() => {
this.togglePicker(true, undefined, true);
}}
onRequestClose={() => {
this.togglePicker(true, undefined, true);
}}
onOrientationChange={this.onOrientationChange}
{...modalProps}
>
<TouchableOpacity
style={[defaultStyles.modalViewTop, style.modalViewTop]}
testID="ios_modal_top"
onPress={() => {
this.togglePicker(true);
this.togglePicker(true, undefined, true);
}}
/>
{this.renderInputAccessoryView()}
<View
style={[
defaultStyles.modalViewBottom,
this.isDarkTheme() ? defaultStyles.modalViewBottomDark : {},
{ height: orientation === 'portrait' ? 215 : 162 },
this.isDarkTheme() ? style.modalViewBottomDark : style.modalViewBottom,
]}
>
<Picker
testID="ios_picker"
onValueChange={this.onValueChange}
selectedValue={selectedItem.value}
onValueChange={this.onValueChange}
prompt={doneText}
{...pickerProps}
>
{this.renderPickerItems()}
Expand All @@ -493,67 +509,43 @@ export default class RNPickerSelect extends PureComponent {
}

renderAndroidHeadless() {
const {
disabled,
Icon,
style,
pickerProps,
onOpen,
touchableWrapperProps,
fixAndroidTouchableBug,
} = this.props;
const { pickerProps, style, disabled } = this.props;
const { selectedItem } = this.state;

const Component = fixAndroidTouchableBug ? View : TouchableOpacity;
return (
<Component
testID="android_touchable_wrapper"
onPress={onOpen}
activeOpacity={1}
{...touchableWrapperProps}
>
<View style={style.headlessAndroidContainer}>
{this.renderTextInputOrChildren()}
<Picker
style={[
Icon ? { backgroundColor: 'transparent' } : {}, // to hide native icon
defaultStyles.headlessAndroidPicker,
style.headlessAndroidPicker,
]}
testID="android_picker_headless"
enabled={!disabled}
onValueChange={this.onValueChange}
selectedValue={selectedItem.value}
{...pickerProps}
>
{this.renderPickerItems()}
</Picker>
</View>
</Component>
<View style={style.headlessAndroidContainer}>
{this.renderTextInputOrChildren()}

<Picker
style={{ position: 'absolute', top: 0, width: '100%', height: '100%' }}
testID="android_picker_headless"
selectedValue={selectedItem.value}
onValueChange={this.onValueChange}
enabled={!disabled}
{...pickerProps}
>
{this.renderPickerItems()}
</Picker>
</View>
);
}

renderAndroidNativePickerStyle() {
const { disabled, Icon, style, pickerProps } = this.props;
const { pickerProps, style, disabled } = this.props;
const { selectedItem } = this.state;

return (
<View style={[defaultStyles.viewContainer, style.viewContainer]}>
<Picker
style={[
Icon ? { backgroundColor: 'transparent' } : {}, // to hide native icon
style.inputAndroid,
this.getPlaceholderStyle(),
]}
style={[defaultStyles.inputAndroid, style.inputAndroid]}
testID="android_picker"
enabled={!disabled}
onValueChange={this.onValueChange}
selectedValue={selectedItem.value}
onValueChange={this.onValueChange}
enabled={!disabled}
{...pickerProps}
>
{this.renderPickerItems()}
</Picker>
{this.renderIcon()}
</View>
);
}
Expand All @@ -564,23 +556,27 @@ export default class RNPickerSelect extends PureComponent {

return (
<View style={[defaultStyles.viewContainer, style.viewContainer]}>
<Picker
style={[style.inputWeb]}
testID="web_picker"
enabled={!disabled}
onValueChange={this.onValueChange}
selectedValue={selectedItem.value}
<select
disabled={disabled}
onChange={(e) => {
this.onValueChange(e.target.value, e.target.selectedIndex);
}}
style={[defaultStyles.inputWeb, style.inputWeb]}
value={selectedItem.value}
{...pickerProps}
>
{this.renderPickerItems()}
</Picker>
{this.renderIcon()}
{this.renderPickerItems().map((item) => (
<option key={item.key} value={item.props.value}>
{item.props.label}
</option>
))}
</select>
</View>
);
}

render() {
const { children, useNativeAndroidPickerStyle } = this.props;
const { useNativeAndroidPickerStyle, fixAndroidTouchableBug } = this.props;

if (Platform.OS === 'ios') {
return this.renderIOS();
Expand All @@ -590,11 +586,11 @@ export default class RNPickerSelect extends PureComponent {
return this.renderWeb();
}

if (children || !useNativeAndroidPickerStyle) {
return this.renderAndroidHeadless();
if (useNativeAndroidPickerStyle) {
return this.renderAndroidNativePickerStyle();
}

return this.renderAndroidNativePickerStyle();
return this.renderAndroidHeadless();
}
}

Expand Down