UNPKG

segmented-picker-aned

Version:

Selection picker wheel with multi-column support and optional native dependencies.

1,240 lines (1,108 loc) 34.1 kB
import React, { Component } from 'react'; import { StyleSheet, Dimensions, Platform, View, TouchableOpacity, Text, requireNativeComponent, UIManager, findNodeHandle, Modal, TouchableWithoutFeedback, FlatList } from 'react-native'; import { View as View$1 } from 'react-native-animatable'; import PropTypes from 'prop-types'; const defaultProps = { native: false, options: [], visible: false, defaultSelections: {}, size: 0.45, cancelText: 'Cancel', confirmText: 'Done', nativeTestID: undefined, cancelTextColor: '#666666', confirmTextColor: '#0A84FF', pickerItemTextColor: '#282828', toolbarBackgroundColor: '#FAFAF8', toolbarBorderColor: '#E7E7E7', selectionBackgroundColor: '#F8F8F8', selectionBorderColor: '#C9C9C9', backgroundColor: '#FFFFFF', onValueChange: () => {}, onCancel: () => {}, onConfirm: () => {} }; const propTypes = { // Core Props options: PropTypes.arrayOf(PropTypes.shape({ key: PropTypes.string.isRequired, items: PropTypes.arrayOf(PropTypes.shape({ label: PropTypes.string.isRequired, value: PropTypes.string.isRequired, key: PropTypes.string, testID: PropTypes.string })).isRequired, testID: PropTypes.string, flex: PropTypes.number })).isRequired, visible: PropTypes.bool, defaultSelections: PropTypes.objectOf((propValue, key, componentName, location, propName) => { const column = propValue[key]; return column && String(column) !== column ? new Error(`Invalid prop \`${propName}\` supplied to \`${componentName}\`.` + ' Must be in the format: `{column1: \'value\', column2: \'value\', ...}`') : null; }), size: (props, propName, componentName) => { const value = props[propName]; if (value === undefined) return null; return value < 0 || value > 1 ? new Error(`Invalid prop \`${propName}\` supplied to \`${componentName}\`.` + ' Must be a floating point between 0-1 representing the screen percentage to cover.' + ' The default value is `0.45` (eg 45%).') : null; }, confirmText: PropTypes.string, nativeTestID: PropTypes.string, // Styling confirmTextColor: PropTypes.string, pickerItemTextColor: PropTypes.string, toolbarBackgroundColor: PropTypes.string, toolbarBorderColor: PropTypes.string, selectionBackgroundColor: PropTypes.string, selectionBorderColor: PropTypes.string, backgroundColor: PropTypes.string, // Events onValueChange: PropTypes.func, onCancel: PropTypes.func, onConfirm: PropTypes.func }; /** * Time in milliseconds for the list to fade in and out when displayed. */ const ANIMATION_TIME = 300; /** * Fixed sizing for list items and other UI elements. */ const GUTTER_WIDTH = 18; const GUTTER_HEIGHT = 5; const ITEM_HEIGHTS = { ios: 46, default: 50 }; const TEXT_CORRECTION = 2; /** * Constants used for automatically generated Test ID's (used for E2E testing). */ const TEST_IDS = { PICKER: 'SEGMENTED_PICKER', CONFIRM_BUTTON: 'SEGMENTED_PICKER_CONFIRM', CLOSE_AREA: 'SEGMENTED_PICKER_CLOSE_AREA' }; /** * Measurement and internal lifecycle tracking states. */ const TRACKING = { FLAT_LIST_REF: 'FLAT_LIST_REF_', LAST_SCROLL_OFFSET: 'LAST_SCROLL_OFFSET_', SCROLL_DIRECTION: 'SCROLL_DIRECTION_', IS_DRAGGING: 'IS_DRAGGING_', IS_MOMENTUM_SCROLLING: 'IS_MOMENTUM_SCROLLING_', IS_DIRTY: 'IS_DIRTY_' }; const ITEM_HEIGHT = Platform.select(ITEM_HEIGHTS); var styles = StyleSheet.create({ modalContainer: { width: '100%', height: '100%', backgroundColor: 'rgba(0, 0, 0, 0.7)', flex: 1, flexDirection: 'column' }, closeableContainer: { width: '100%' }, pickerContainer: { width: '100%', flex: 1, flexDirection: 'column', alignItems: 'flex-start' }, selectableArea: { flex: 1, alignSelf: 'stretch' }, pickerColumns: { flex: 1, flexDirection: 'row', paddingTop: GUTTER_HEIGHT, paddingRight: 0, paddingBottom: GUTTER_HEIGHT, paddingLeft: GUTTER_WIDTH }, pickerColumn: { flex: 1, marginRight: 12, position: 'relative' }, pickerList: { width: '100%', height: 'auto' }, pickerItem: { width: '100%', height: ITEM_HEIGHT, justifyContent: 'center' }, pickerItemText: { fontSize: 15, paddingTop: 5, paddingRight: 0, paddingBottom: TEXT_CORRECTION + 5, paddingLeft: 0, textAlign: 'center' }, nativePickerContainer: { width: Dimensions.get('window').width - GUTTER_WIDTH * 2, height: '100%', marginLeft: GUTTER_WIDTH }, nativePicker: { width: '100%', height: '100%' } }); var styles$1 = StyleSheet.create({ toolbarContainer: { width: '100%', height: 42, borderBottomWidth: 1, flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', alignSelf: 'flex-start' }, toolbarConfirmContainer: { height: '100%', paddingLeft: 30, justifyContent: 'center' }, toolbarCancelContainer: { height: '100%', paddingLeft: 30, justifyContent: 'center' }, toolbarText: { fontWeight: 'bold', fontSize: 15, paddingTop: 0, paddingRight: GUTTER_WIDTH, paddingBottom: TEXT_CORRECTION, paddingLeft: 0 } }); /** * Top action bar that displays above the picker modal which allows a user to confirm * their selections and close the modal. */ var Toolbar = (({ cancelText, cancelTextColor, confirmText, confirmTextColor, toolbarBackground, toolbarBorderColor, onConfirm, onCancel }) => /*#__PURE__*/ React.createElement(View, { style: [styles$1.toolbarContainer, { backgroundColor: toolbarBackground, borderBottomColor: toolbarBorderColor }] }, /*#__PURE__*/ React.createElement(TouchableOpacity, { activeOpacity: 0.4, onPress: onCancel, testID: TEST_IDS.CONFIRM_BUTTON }, /*#__PURE__*/ React.createElement(View, { style: styles$1.toolbarCancelContainer }, /*#__PURE__*/ React.createElement(Text, { style: [styles$1.toolbarText, { color: cancelTextColor }] }, cancelText))), /*#__PURE__*/ React.createElement(TouchableOpacity, { activeOpacity: 0.4, onPress: onConfirm, testID: TEST_IDS.CONFIRM_BUTTON }, /*#__PURE__*/ React.createElement(View, { style: styles$1.toolbarConfirmContainer }, /*#__PURE__*/ React.createElement(Text, { style: [styles$1.toolbarText, { color: confirmTextColor }] }, confirmText))))); const ITEM_HEIGHT$1 = Platform.select(ITEM_HEIGHTS); var styles$2 = StyleSheet.create({ selectionMarkerContainer: { width: '100%', height: '100%', position: 'absolute', top: 0, left: 0, flex: 1, alignItems: 'center', justifyContent: 'center', paddingLeft: GUTTER_WIDTH, paddingRight: GUTTER_WIDTH }, // Eliminates border rendering inconsistencies between iOS & Android selectionMarkerBorder: { width: '100%', height: Platform.select({ ios: 0.6, android: 0.7 }) }, selectionMarker: { width: '100%', height: ITEM_HEIGHT$1 } }); /** * Horizontal bar used to indicate the current picker selections. */ var SelectionMarker = (({ backgroundColor, borderColor }) => /*#__PURE__*/ React.createElement(View, { style: styles$2.selectionMarkerContainer }, /*#__PURE__*/ React.createElement(View, { style: [styles$2.selectionMarkerBorder, { backgroundColor: borderColor }] }), /*#__PURE__*/ React.createElement(View, { style: [styles$2.selectionMarker, { backgroundColor }] }), /*#__PURE__*/ React.createElement(View, { style: [styles$2.selectionMarkerBorder, { backgroundColor: borderColor }] }))); var UIPicker = requireNativeComponent('UIPicker'); /** * This class is utilised by the main Segmented Picker component as a fast synchronous * cache alternative to the regular React component state mechanism. */ class Cache { constructor(initialState = {}) { /** * Attempts to safely fetch the cached value for the specified key. * @param {string} key * @return {StoreItem | null} */ this.get = key => { if (key in this.store) { return this.store[key]; } return null; }; /** * Creates (or replaces) a value in the cache for the specified key. * @param {string} key * @param {StoreItem} value * @return {void} */ this.set = (key, value) => { this.store[key] = value; }; /** * Completely resets the cache to a blank state. * @return {void} */ this.purge = () => { this.store = {}; }; this.store = Object.assign({}, initialState); } /** * Returns the entire store value without any modification. * @return {Store} */ get current() { return this.store; } } /** * This class is used to store promises against an incrementing integer so that the * promise can be resolved from outside the context of the original block. * * An example of this is when React needs to request asynchronous data from a Native * UI Component. Communication to UI components across the React Native bridge is * limited to 1-way, so the value must be emitted afterwards using an event which is * not immediately available to the original JavaScript method. So to get around this * limitation, we empower the React event subscription to resolve the promise. */ class PromiseFactory { constructor() { this.promises = new Map(); this.nextPromiseId = 0; /** * Add a promise to the internal store and receive it's unique `id`. * @param {PromiseExecutor} e * @return {number} Unique `id` for the added promise. */ this.add = e => { const id = Number(this.nextPromiseId); this.promises.set(id, e); this.nextPromiseId += 1; return id; }; /** * Get a promise by it's `id`. * @param {number} id * @return {PromiseExecutor | undefined} */ this.get = id => this.promises.get(id); /** * Completely deletes a promise from the factory using it's unique `id`. * @param {number} id * @return {boolean} */ this.delete = id => this.promises.delete(id); } } /** * Methods to control and observe the native iOS `UIPickerView`. */ class UIPickerManager { constructor() { this.ref = React.createRef(); this.promiseFactory = new PromiseFactory(); /** * Programmatically select an index in the picker. * @param {number} index: List index of the picker item to select. * @param {string} columnKey: Unique key of the column to select. * @param {boolean = true} animated: Should the selection "snap" or animate smoothly into place? * @return {void} */ this.selectIndex = (index, column, animated = true) => { if (this.ref.current) { UIManager.dispatchViewManagerCommand(findNodeHandle(this.ref.current), UIManager.UIPicker.Commands.selectIndex, [index, column, animated]); } }; /** * Returns the current picker item selections as the appear on the user's screen. * @return {Promise<Selections>} */ this.getCurrentSelections = () => { if (!this.ref.current) { return Promise.resolve({}); } const promise = new Promise((resolve, reject) => { const pid = this.promiseFactory.add({ resolve, reject }); UIManager.dispatchViewManagerCommand(findNodeHandle(this.ref.current), UIManager.UIPicker.Commands.emitSelections, [pid]); }); return promise; }; /** * Ingests emitted selections from the native module and resolves the original promise * from `getCurrentSelections` using it's stored resolver in the Promise Factory. * @param {UIPickerSelectionsEvent} * @return {void} */ this.ingestSelections = ({ nativeEvent: { selections, pid } }) => { const promise = this.promiseFactory.get(pid); if (promise) { promise.resolve(selections); this.promiseFactory.delete(pid); } }; } get reactRef() { return this.ref; } } const ITEM_HEIGHT$2 = Platform.select(ITEM_HEIGHTS); const { FLAT_LIST_REF, LAST_SCROLL_OFFSET, SCROLL_DIRECTION, IS_DRAGGING, IS_MOMENTUM_SCROLLING, IS_DIRTY } = TRACKING; class SegmentedPicker extends Component { constructor(props) { super(props); this.cache = new Cache(); // Used as an internal synchronous state (fast) this.uiPickerManager = new UIPickerManager(); this.selectionChanges = {}; this.modalContainerRef = React.createRef(); this.pickerContainerRef = React.createRef(); /** * Make the picker visible on the screen. * External Usage: `ref.current.show()` * @return {Promise<void>} */ this.show = () => { this.setState({ visible: true }); return new Promise(resolve => setTimeout(resolve, ANIMATION_TIME)); }; /** * Hide the picker from the screen. * External Usage: `ref.current.hide()` * @return {Promise<void>} */ this.hide = async () => new Promise(async resolve => { var _a; if (Platform.OS === 'ios') { this.setState({ visible: false }, async () => { await new Promise(done => setTimeout(done, ANIMATION_TIME)); this.cache.purge(); resolve(); }); } else { await ((_a = this.modalContainerRef.current) === null || _a === void 0 ? void 0 : _a.fadeOut(ANIMATION_TIME)); this.setState({ visible: false }, () => { this.cache.purge(); resolve(); }); } }); /** * Selects a specific picker item `label` in the picklist and focuses it. * External Usage: `ref.current.selectLabel()` * @param {string} label * @param {string} column * @param {boolean = true} animated * @param {boolean = true} emitEvent: Specify whether to call the `onValueChange` event. * @param {boolean = false} zeroFallback: Select the first list item if not found. * @return {void} */ this.selectLabel = (label, column, animated = true, emitEvent = true, zeroFallback = false) => { const index = this.findItemIndexByLabel(label, column); if (index !== -1) { this.selectIndex(index, column, animated, emitEvent); } else if (this.columnItems(column).length > 0 && zeroFallback) { this.selectIndex(0, column, animated, emitEvent); } }; /** * Selects a specific picker item `value` in the picklist and focuses it. * External Usage: `ref.current.selectValue()` * @param {string} value * @param {string} column * @param {boolean = true} animated * @param {boolean = true} emitEvent: Specify whether to call the `onValueChange` event. * @param {boolean = false} zeroFallback: Select the first list item if not found. * @return {void} */ this.selectValue = (value, column, animated = true, emitEvent = true, zeroFallback = false) => { const index = this.findItemIndexByValue(value, column); if (index !== -1) { this.selectIndex(index, column, animated, emitEvent); } else if (this.columnItems(column).length > 0 && zeroFallback) { this.selectIndex(0, column, animated, emitEvent); } }; /** * Selects a specific label in the picklist and focuses it using it's list index. * External Usage: `ref.current.selectLabel()` * @param {number} index * @param {string} column * @param {boolean = true} animated * @param {boolean = true} emitEvent: Specify whether to call the `onValueChange` event. * @return {void} */ this.selectIndex = (index, column, animated = true, emitEvent = true) => { if (this.isNative()) { this.uiPickerManager.selectIndex(index, column, animated); return; } const { onValueChange } = this.props; const list = this.cache.get(`${FLAT_LIST_REF}${column}`); if (!list) { return; } list.scrollToIndex({ index, animated }); const items = this.columnItems(column); if (!this.selectionChanges[column] || this.selectionChanges[column] && this.selectionChanges[column] !== items[index].value) { this.selectionChanges = Object.assign(Object.assign({}, this.selectionChanges), { [column]: items[index].value }); if (emitEvent) { onValueChange({ column, value: items[index].value }); } } }; /** * Returns the current picklist selections as they appear on the UI. * External Usage: `await ref.current.getCurrentSelections()` * @return {Promise<Selections>} {column1: 'value', column2: 'value', ...} */ this.getCurrentSelections = async () => { if (this.isNative()) { const nativeSelections = await this.uiPickerManager.getCurrentSelections(); return nativeSelections; } const { options } = this.props; return Promise.resolve(options.reduce((columns, column) => { var _a; const lastOffset = this.cache.get(`${LAST_SCROLL_OFFSET}${column.key}`); const index = this.nearestOptionIndex(lastOffset || 0, column.key); const items = this.columnItems(column.key); return Object.assign(Object.assign({}, columns), { [column.key]: (_a = items[index]) === null || _a === void 0 ? void 0 : _a.value }); }, {})); }; /** * @private * Should the picker be powered by a native module, or with plain JavaScript? * Currently only available as an opt-in option for iOS devices. * @return {boolean} */ this.isNative = () => this.props.native && Platform.OS === 'ios'; /** * Filters the `options` prop for a specific column `key`. * @param {string} key * @return {PickerColumn} */ this.getColumn = key => this.props.options.filter(c => c.key === key)[0]; /** * Returns the picker list items for a specific column `key`. * @param {string} key * @return {Array<PickerItem>} */ this.columnItems = key => { var _a; return ((_a = this.getColumn(key)) === null || _a === void 0 ? void 0 : _a.items) || []; }; /** * @private * @param {string} label * @param {string} column * @return {number} */ this.findItemIndexByLabel = (label, column) => { const items = this.columnItems(column); return items.findIndex(item => item.label === label); }; /** * @private * @param {string} value * @param {string} column * @return {number} */ this.findItemIndexByValue = (value, column) => { const items = this.columnItems(column); return items.findIndex(item => item.value === value); }; /** * @private * Focuses the default picklist selections. * @return {void} */ this.setDefaultSelections = () => { const { options, defaultSelections } = this.props; const dirty = this.cache.get(IS_DIRTY); if (!dirty) { setTimeout(() => { // User defined default selections Object.keys(defaultSelections).forEach(column => this.selectValue(defaultSelections[column], column, false, false, true)); // Set all other selections to index 0 options.filter(column => !Object.keys(defaultSelections).includes(column.key) && this.columnItems(column.key).length > 0).forEach(column => this.selectIndex(0, column.key, false, false)); }, 0); } }; /** * @private * @param {string} column * @param {object} ref: The column's rendered FlatList component. * @return {void} */ this.setFlatListRef = (column, ref) => { if (ref) { this.cache.set(`${FLAT_LIST_REF}${column}`, ref); this.setDefaultSelections(); } }; /** * @private * @return {void} */ this.measurePickersHeight = event => { const { height } = event.nativeEvent.layout; this.setState({ pickersHeight: height }); }; /** * @private * Calculates the padding top and bottom for the pickers so that the first and * last list items are centered in the marker when fully scrolled up or down. * @return {number} */ this.pickersVerticalPadding = () => { const { pickersHeight } = this.state; return (pickersHeight - ITEM_HEIGHT$2 - GUTTER_HEIGHT * 2) / 2; }; /** * @private * Determines the index of the nearest option in the list based off the specified Y * scroll offset. * @param {number} offsetY: The scroll view content offset from react native (should * always be a positive integer). * @param {string} column * @return {number} */ this.nearestOptionIndex = (offsetY, column) => { const scrollDirection = this.cache.get(`${SCROLL_DIRECTION}${column}`) || 1; const rounding = scrollDirection === 0 ? 'floor' : 'ceil'; const adjustedOffsetY = scrollDirection === 0 ? offsetY / ITEM_HEIGHT$2 + 0.35 : offsetY / ITEM_HEIGHT$2 - 0.35; let nearestArrayMember = Math[rounding](adjustedOffsetY) || 0; // Safety checks making sure we don't return an out of range index const columnSize = this.columnItems(column).length; if (Math.sign(nearestArrayMember) === -1) { nearestArrayMember = 0; } else if (nearestArrayMember > columnSize - 1) { nearestArrayMember = columnSize - 1; } return nearestArrayMember; }; /** * @private * Calculates the current scroll direction based off the last and current Y offsets. * @param {NativeSyntheticEvent<NativeScrollEvent>} event: Event details from React Native. * @param {string} column * @return {void} */ this.onScroll = (event, column) => { if (!event.nativeEvent) return; const { y } = event.nativeEvent.contentOffset; const lastScrollOffset = this.cache.get(`${LAST_SCROLL_OFFSET}${column}`); if (lastScrollOffset !== null && lastScrollOffset < y) { this.cache.set(`${SCROLL_DIRECTION}${column}`, 1); // Down } else { this.cache.set(`${SCROLL_DIRECTION}${column}`, 0); // Up } this.cache.set(`${LAST_SCROLL_OFFSET}${column}`, y); }; /** * @private * @param {string} column * @return {void} */ this.onScrollBeginDrag = column => { this.cache.set(`${IS_DRAGGING}${column}`, true); const dirty = this.cache.get(IS_DIRTY); if (!dirty) { this.cache.set(IS_DIRTY, true); } }; /** * @private * @param {NativeSyntheticEvent<NativeScrollEvent>} event: Event details from React Native. * @param {string} column * @return {void} */ this.onScrollEndDrag = (event, column) => { this.cache.set(`${IS_DRAGGING}${column}`, false); if (Platform.OS === 'ios' && !this.cache.get(`${IS_MOMENTUM_SCROLLING}${column}`)) { // Not required on Android because all scrolls exit as momentum scrolls, // so the below method has already been called prior to this event. // Timeout is to temporarily allow raising fingers. this.selectIndexFromScrollPosition(event, column, 280); } }; /** * @private * @param {object} event: Event details from React Native. * @param {string} column * @return {void} */ this.onMomentumScrollBegin = (event, column) => { this.cache.set(`${IS_MOMENTUM_SCROLLING}${column}`, true); }; /** * @private * @param {NativeSyntheticEvent<NativeScrollEvent>} event: Event details from React Native. * @param {string} column * @return {void} */ this.onMomentumScrollEnd = (event, column) => { this.cache.set(`${IS_MOMENTUM_SCROLLING}${column}`, false); if (Platform.OS === 'ios') { event.persist(); this.selectIndexFromScrollPosition(event, column, 50); } else { this.selectIndexFromScrollPosition(event, column); } }; /** * @private * Scrolls to the nearest index based off a y offset from the FlatList. * @param {NativeSyntheticEvent<NativeScrollEvent>} event: Event details from React Native. * @param {string} column * @param {number?} delay * @return {void} */ this.selectIndexFromScrollPosition = (event, column, delay = 0) => { if (!event.nativeEvent) return; const { y } = event.nativeEvent.contentOffset; const nearestOptionIndex = this.nearestOptionIndex(y, column); setTimeout(() => { const isDragging = this.cache.get(`${IS_DRAGGING}${column}`); const isMomentumScrolling = this.cache.get(`${IS_MOMENTUM_SCROLLING}${column}`); if (!isDragging && !isMomentumScrolling) { this.selectIndex(nearestOptionIndex, column); } }, delay); }; /** * @private * This method is called when the picker is closed unexpectedly without pressing the * "Done" button in the top right hand corner. * @return {Promise<void>} */ this.onCancel = async () => { const selections = Object.assign({}, (await this.getCurrentSelections())); if (this.props.visible !== true) { await this.hide(); } this.props.onCancel(selections); }; /** * @private * This method is called when the right action button (default: "Done") is tapped. * It calls the `onConfirm` method and hides the picker. * @return {Promise<void>} */ this.onConfirm = async () => { const selections = Object.assign({}, (await this.getCurrentSelections())); if (this.props.visible !== true) { await this.hide(); } this.props.onConfirm(selections); }; /** * @private * Used by the FlatList to render picklist items. * @return {ReactElement} */ this.renderPickerItem = ({ item: { label, column, key, testID }, index }) => { const { pickerItemTextColor } = this.props; return ( /*#__PURE__*/ React.createElement(View, { style: styles.pickerItem }, /*#__PURE__*/ React.createElement(TouchableOpacity, { activeOpacity: 0.7, onPress: () => this.selectIndex(index, column), testID: testID || key }, /*#__PURE__*/ React.createElement(Text, { numberOfLines: 1, style: [styles.pickerItemText, { color: pickerItemTextColor }] }, label))) ); }; /** * @private * Forwards value changes onto the client from the Native iOS UIPicker when it is in use * over the default JavaScript picker implementation. * @param {UIPickerValueChangeEvent} * @return {void} */ this.uiPickerValueChange = ({ nativeEvent: { column, value } }) => { const { onValueChange } = this.props; onValueChange({ column, value }); }; this.state = { visible: false, pickersHeight: 0 }; } /** * Used in rare circumstances where this component is mounted with the `visible` * prop set to true. We must animate the picker in immediately. */ componentDidMount() { if (this.props.visible === true) { this.show(); } } /** * Animates in-and-out when toggling picker visibility with the `visible` prop. */ componentDidUpdate(prevProps) { const { visible: visibleProp } = this.props; const { visible: visibleState } = this.state; if (visibleProp === true && prevProps.visible !== true && visibleState !== true) { this.show(); } if (visibleProp === false && prevProps.visible === true) { this.hide(); } } render() { const { visible } = this.state; const { nativeTestID, options, defaultSelections, size, cancelText, cancelTextColor, confirmText, confirmTextColor, pickerItemTextColor, toolbarBackgroundColor, toolbarBorderColor, selectionBackgroundColor, selectionBorderColor, backgroundColor } = this.props; return ( /*#__PURE__*/ React.createElement(Modal, { visible: visible, animationType: Platform.select({ ios: 'fade', default: 'none' }), transparent: true, onRequestClose: this.onCancel }, /*#__PURE__*/ React.createElement(View$1, { useNativeDriver: true, animation: "fadeIn", easing: "ease-out-cubic", duration: ANIMATION_TIME, ref: this.modalContainerRef, style: styles.modalContainer, testID: TEST_IDS.PICKER }, /*#__PURE__*/ React.createElement(TouchableWithoutFeedback, { onPress: this.onCancel, testID: TEST_IDS.CLOSE_AREA }, /*#__PURE__*/ React.createElement(View, { style: [styles.closeableContainer, { height: `${100 - size * 100}%` }] })), /*#__PURE__*/ React.createElement(View$1, { useNativeDriver: true, animation: { from: { opacity: 0, translateY: 250 }, to: { opacity: 1, translateY: 0 } }, easing: "ease-out-quint", delay: 100, duration: ANIMATION_TIME, ref: this.pickerContainerRef, style: [styles.pickerContainer, { height: `${size * 100}%`, backgroundColor }] }, /*#__PURE__*/ React.createElement(Toolbar, { cancelText: cancelText, cancelTextColor: cancelTextColor, confirmText: confirmText, confirmTextColor: confirmTextColor, toolbarBackground: toolbarBackgroundColor, toolbarBorderColor: toolbarBorderColor, onConfirm: this.onConfirm, onCancel: this.onCancel }), /*#__PURE__*/ React.createElement(View, { style: styles.selectableArea }, this.isNative() && /*#__PURE__*/ React.createElement(View, { style: styles.nativePickerContainer }, /*#__PURE__*/ React.createElement(UIPicker, { ref: this.uiPickerManager.reactRef, nativeTestID: nativeTestID, style: styles.nativePicker, options: SegmentedPicker.ApplyPickerOptionDefaults(options), defaultSelections: defaultSelections, onValueChange: this.uiPickerValueChange, onEmitSelections: this.uiPickerManager.ingestSelections, theme: { itemHeight: ITEM_HEIGHT$2, selectionBackgroundColor, selectionBorderColor, pickerItemTextColor } })), !this.isNative() && /*#__PURE__*/ React.createElement(React.Fragment, null, /*#__PURE__*/ React.createElement(SelectionMarker, { backgroundColor: selectionBackgroundColor, borderColor: selectionBorderColor }), /*#__PURE__*/ React.createElement(View, { style: styles.pickerColumns, onLayout: this.measurePickersHeight }, SegmentedPicker.ApplyPickerOptionDefaults(options).map(({ key: column, testID: columnTestID, flex }) => /*#__PURE__*/ React.createElement(View, { style: [styles.pickerColumn, { flex }], key: `${column}` }, /*#__PURE__*/ React.createElement(View, { style: styles.pickerList }, /*#__PURE__*/ React.createElement(FlatList, { data: this.columnItems(column).map(({ label, value, key, testID }) => ({ label, value, column, testID, key: `${column}_${key || value || label}` })), renderItem: this.renderPickerItem, keyExtractor: item => item.key, initialNumToRender: 40, getItemLayout: (data, index) => ({ length: ITEM_HEIGHT$2, offset: ITEM_HEIGHT$2 * index, index }), contentContainerStyle: { paddingTop: this.pickersVerticalPadding(), paddingBottom: this.pickersVerticalPadding() }, showsVerticalScrollIndicator: false, ref: ref => this.setFlatListRef(column, ref), onScroll: event => this.onScroll(event, column), onScrollBeginDrag: () => this.onScrollBeginDrag(column), onScrollEndDrag: event => this.onScrollEndDrag(event, column), onMomentumScrollBegin: event => this.onMomentumScrollBegin(event, column), onMomentumScrollEnd: event => this.onMomentumScrollEnd(event, column), scrollEventThrottle: 32, decelerationRate: Platform.select({ ios: 1, android: undefined }), testID: `${columnTestID}` })))))))))) ); } } SegmentedPicker.propTypes = propTypes; SegmentedPicker.defaultProps = defaultProps; /** * @static * Decorates the `options` prop with necessary defaults for missing values. * @param options {PickerOptions} * @return {PickerOptions} */ SegmentedPicker.ApplyPickerOptionDefaults = options => options.map(column => Object.assign(Object.assign({}, column), { flex: column.flex || 1 })); export default SegmentedPicker; export { ANIMATION_TIME, GUTTER_HEIGHT, GUTTER_WIDTH, ITEM_HEIGHTS, TEST_IDS, TEXT_CORRECTION, TRACKING }; //# sourceMappingURL=bundle.js.map