UNPKG

@monchilin/react-native-dropdown

Version:
651 lines (572 loc) 19 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _react = _interopRequireWildcard(require("react")); var _reactNative = require("react-native"); var _Modal = _interopRequireDefault(require("./Modal")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; } function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } const truth = () => true; const isNumber = o => typeof o === 'number'; function id(v) { return v; } const TOUCHABLE_ELEMENTS = ['TouchableHighlight', 'TouchableOpacity', 'TouchableWithoutFeedback', 'TouchableNativeFeedback']; // TODO react native for web not support useWindowDimensions() const { width: windowWidth, height: windowHeight } = _reactNative.Dimensions.get('window'); const useEffectWithSkipFirst = (callback, deps) => { const isFirstRun = (0, _react.useRef)(true); (0, _react.useEffect)(() => { if (isFirstRun.current) { isFirstRun.current = false; return; } return callback(); // eslint-disable-next-line react-hooks/exhaustive-deps }, deps); }; const useAnimation = ({ visible, transitionHide, transitionShow, meta }) => { const [state, setState] = (0, _react.useState)(false); const anim = (0, _react.useRef)(new _reactNative.Animated.Value(90)).current; const [style, setStyle] = (0, _react.useState)({}); useEffectWithSkipFirst(() => { if (visible) { const transitionShowConfig = transitions[transitionShow]; const interpolate = anim.interpolate(transitionShowConfig.interpolate(meta)); anim.setValue(transitionShowConfig.initialValue); setState(true); switch (transitionShow) { case 'flipUp': setStyle({ transform: [{ rotateX: interpolate }] }); break; case 'scaleIn': setStyle({ transform: [{ scaleX: interpolate, scaleY: 1 }] }); break; case 'fadeIn': setStyle({ opacity: interpolate }); break; case 'slideUp': setStyle({ height: interpolate }); break; default: setStyle({}); } // @ts-ignore _reactNative.Animated[transitionShowConfig.animationType](anim, transitionShowConfig.config).start(); } else { const transitionHideConfig = transitions[transitionHide]; const interpolate = anim.interpolate(transitionHideConfig.interpolate(meta)); anim.setValue(transitionHideConfig.initialValue); switch (transitionHide) { case 'flipDown': setStyle({ transform: [{ rotateX: interpolate }] }); break; case 'scaleOut': setStyle({ transform: [{ scaleX: interpolate, scaleY: 1 }] }); break; case 'fadeOut': setStyle({ opacity: interpolate }); break; case 'slideDown': setStyle({ height: interpolate }); break; default: setStyle({}); } // @ts-ignore _reactNative.Animated[transitionHideConfig.animationType](anim, transitionHideConfig.config).start(() => { setState(false); }); } }, [visible]); return { visible: state, style }; }; const transitions = { flipUp: { config: { toValue: 0, friction: 10, tension: 20, useNativeDriver: true }, interpolate: () => ({ inputRange: [0, 180], outputRange: ['0deg', '180deg'] }), initialValue: 90, animationType: 'spring' }, flipDown: { config: { toValue: 90, friction: 10, tension: 40, useNativeDriver: true }, interpolate: () => ({ inputRange: [0, 90], outputRange: ['0deg', '90deg'] }), initialValue: 0, animationType: 'spring' }, scaleIn: { config: { toValue: 1, friction: 10, tension: 10, useNativeDriver: true }, interpolate: () => ({ inputRange: [0, 1], outputRange: [0, 1] }), initialValue: 0, animationType: 'spring' }, scaleOut: { config: { toValue: 0, friction: 10, tension: 10, useNativeDriver: true }, interpolate: () => ({ inputRange: [0, 1], outputRange: [0, 1] }), initialValue: 1, animationType: 'spring' }, fadeIn: { config: { toValue: 1, duration: 500, useNativeDriver: true }, interpolate: () => ({ inputRange: [0, 1], outputRange: [0, 1] }), initialValue: 0, animationType: 'timing' }, fadeOut: { config: { toValue: 0, duration: 500, useNativeDriver: true }, interpolate: () => ({ inputRange: [0, 1], outputRange: [0, 1] }), initialValue: 1, animationType: 'timing' }, slideUp: { config: { toValue: 100, useNativeDriver: false }, interpolate: meta => ({ inputRange: [0, 100], delay: 800, easing: _reactNative.Easing.in, outputRange: [0, meta.dropdownHeight] }), initialValue: 0, animationType: 'timing' }, slideDown: { config: { toValue: 0, useNativeDriver: false }, interpolate: meta => ({ inputRange: [0, 100], delay: 800, easing: _reactNative.Easing.sin, outputRange: [0, meta.dropdownHeight] }), initialValue: 100, animationType: 'timing' } }; const usePosition = ({ heightSourceStyle, widthSourceStyle }) => { const height = (0, _react.useMemo)(() => { const style = _reactNative.StyleSheet.flatten(heightSourceStyle.find(item => _reactNative.StyleSheet.flatten(item).height)); const _height = (style === null || style === void 0 ? void 0 : style.height) ? style.height.toString() : '-1'; return Number.parseFloat(_height); }, [heightSourceStyle]); const width = (0, _react.useMemo)(() => { const style = _reactNative.StyleSheet.flatten(widthSourceStyle.find(item => _reactNative.StyleSheet.flatten(item).width)); const _width = (style === null || style === void 0 ? void 0 : style.width) ? style.width.toString() : '-1'; return Number.parseFloat(_width); }, [widthSourceStyle]); return { height, width }; }; const SplitLine = () => { return /*#__PURE__*/_react.default.createElement(_reactNative.View, { style: styles.splitLint }); }; function Component({ defaultIndex, defaultLabel = 'Please select', index, onSelect = truth, dataSource, disabled = false, loading = false, animated = true, transitionShow = 'flipUp', transitionHide = 'flipDown', scrollEnabled = true, keyExtractor = (_, itemIndex) => itemIndex.toString(), adjustFrame = id, renderItem, renderSeparator = SplitLine, showSeparator = true, renderLabel, onDropdownWillShow = truth, onDropdownWillHide = truth, rootContainerStyle = {}, rootContainerProps = {}, labelContainerStyle = {}, labelContainerDisabledStyle = {}, labelContainerProps = {}, labelStyle = {}, labelDisabledStyle = {}, labelProps = {}, modalProps = {}, dropdownStyle = {}, dropdownProps = {}, itemTouchableProps = {}, itemLabelStyle = {}, itemLabelProps = {}, itemLabelHighlightStyle = {}, children }, ref) { const _button = (0, _react.useRef)(null); const _buttonFrame = (0, _react.useRef)({ x: 0, y: 0, w: 0, h: 0 }); const [dropdownVisible, setDropdownVisible] = (0, _react.useState)(false); const [selectedIndex, setSelectedIndex] = (0, _react.useState)(() => { return isNumber(index) ? index : isNumber(defaultIndex) ? defaultIndex : -1; }); const dropDownSize = usePosition({ heightSourceStyle: [rootContainerStyle, dropdownStyle, styles.dropdown], widthSourceStyle: [rootContainerStyle, dropdownStyle, rootContainerStyle] }); const { style: dropdownAnimatedStyle, visible: _dropdownVisibleForAnimation } = useAnimation({ visible: dropdownVisible, transitionHide, transitionShow, meta: { dropdownHeight: dropDownSize.height, dropdownWidth: dropDownSize.width } }); const labelText = (0, _react.useMemo)(() => { return renderLabel ? renderLabel(dataSource[selectedIndex], selectedIndex) : dataSource[selectedIndex] ? dataSource[selectedIndex] : defaultLabel; }, [dataSource, defaultLabel, renderLabel, selectedIndex]); useEffectWithSkipFirst(() => { if (isNumber(index)) { setSelectedIndex(index); } else { setSelectedIndex(-1); } }, [index]); const hide = (0, _react.useCallback)(() => { setDropdownVisible(false); }, []); const show = (0, _react.useCallback)(() => { setDropdownVisible(true); }, []); const select = (0, _react.useCallback)(newIndex => { setSelectedIndex(newIndex); onSelect(newIndex); }, [onSelect]); (0, _react.useImperativeHandle)(ref, () => ({ select, hide, show })); const onLayout = () => { var _button$current; if (!((_button$current = _button.current) === null || _button$current === void 0 ? void 0 : _button$current.measure)) { return; } _button.current.measure((_fx, _fy, width, height, px, py) => { _buttonFrame.current = { x: px, y: py, w: width, h: height }; }); }; const _onRequestClose = () => { if (onDropdownWillHide(dataSource[selectedIndex], selectedIndex) !== false) { hide(); } }; const _onModalPress = () => { if (onDropdownWillHide(dataSource[selectedIndex], selectedIndex) !== false) { hide(); } }; const _onLabelPress = () => { if (onDropdownWillShow(dataSource[selectedIndex], selectedIndex) !== false) { show(); } }; const _onPressModalItem = (newIndex, item) => { // 除非是 false 否则更新 index if (onSelect(newIndex, item) !== false) { setSelectedIndex(newIndex); } if (onDropdownWillHide(dataSource[newIndex], newIndex) !== false) { setDropdownVisible(false); } }; const getDropdownHorizontalBorderWidthFromStyle = (0, _react.useCallback)(() => { var _style$borderWidth, _style$borderLeftWidt, _style$borderRightWid; const style = _reactNative.StyleSheet.flatten([dropdownStyle, rootContainerStyle, styles.dropdown]); const borderWidth = (_style$borderWidth = style.borderWidth) !== null && _style$borderWidth !== void 0 ? _style$borderWidth : 0; const borderLeftWidth = (_style$borderLeftWidt = style.borderLeftWidth) !== null && _style$borderLeftWidt !== void 0 ? _style$borderLeftWidt : 0; const borderRightWidth = (_style$borderRightWid = style.borderRightWidth) !== null && _style$borderRightWid !== void 0 ? _style$borderRightWid : 0; if (!borderLeftWidth && !borderRightWidth) { return borderWidth * 2; } return borderLeftWidth + borderRightWidth; }, [rootContainerStyle, dropdownStyle]); const _calcPosition = () => { // 首先根据 style 的对象获取 dropdown 容器的高度 const dropdownHeight = dropDownSize.height; // x: 按钮的 x 点(相对于屏幕左上角) // y: 按钮的 y 点(相对于屏幕顶点) // w: 按钮的 width // h: 按钮的 height const { x, y, w, h } = _buttonFrame.current; // 距离底部的空间 const buttonSpace = windowHeight - y - h; // 距离右边的空间 const rightSpace = windowWidth - x; // 如果距离底部的空间大于等于 dropdown 的高度 或者 底部空间 const showInBottom = buttonSpace >= dropdownHeight || buttonSpace >= y; const showInLeft = rightSpace >= x; const positionStyle = { height: dropdownHeight, top: showInBottom ? y + h : Math.max(0, y - dropdownHeight) }; if (showInLeft) { positionStyle.left = x; } else { const dropdownWidth = dropDownSize.width; if (dropdownWidth !== -1) { positionStyle.width = dropdownWidth; } positionStyle.right = rightSpace - w; } return adjustFrame(positionStyle); }; const _renderLoading = () => { return /*#__PURE__*/_react.default.createElement(_reactNative.ActivityIndicator, { size: "small" }); }; const _renderLabel = () => { return /*#__PURE__*/_react.default.createElement(_reactNative.TouchableOpacity, _extends({ ref: _button, disabled: disabled, onPress: _onLabelPress, style: [labelContainerStyle, disabled && styles.labelContainerDisabled, disabled && labelContainerDisabledStyle] }, labelContainerProps), children !== null && children !== void 0 ? children : /*#__PURE__*/_react.default.createElement(_reactNative.Text, _extends({ style: [styles.label, labelStyle, disabled && styles.labelDisabled, disabled && labelDisabledStyle], numberOfLines: 1 }, labelProps), labelText)); }; const _renderItem = ({ index: newIndex, item }) => { const highlighted = newIndex === selectedIndex; const row = renderItem ? renderItem(item, newIndex, highlighted) : /*#__PURE__*/_react.default.createElement(_reactNative.Text, _extends({ style: [styles.dropdownTextStyle, itemLabelStyle, highlighted && styles.highlightedRowText, highlighted && itemLabelHighlightStyle] }, itemLabelProps), item); const preservedProps = { onPress: () => _onPressModalItem(newIndex, item), ...itemTouchableProps }; if (TOUCHABLE_ELEMENTS.find(name => name === row.type.displayName)) { const props = { ...row.props, onPress: preservedProps.onPress }; const { children: realChildren } = row.props; switch (row.type.displayName) { case 'TouchableHighlight': { return /*#__PURE__*/_react.default.createElement(_reactNative.TouchableHighlight, props, realChildren); } case 'TouchableOpacity': { return /*#__PURE__*/_react.default.createElement(_reactNative.TouchableOpacity, props, realChildren); } case 'TouchableWithoutFeedback': { return /*#__PURE__*/_react.default.createElement(_reactNative.TouchableWithoutFeedback, props, realChildren); } // TODO react native web not support TouchableNativeFeedback case 'TouchableNativeFeedback': { console.warn('react native web not support TouchableNativeFeedback'); return /*#__PURE__*/_react.default.createElement(_reactNative.TouchableNativeFeedback, props, row); } default: break; } } return /*#__PURE__*/_react.default.createElement(_reactNative.TouchableHighlight, preservedProps, row); }; const _renderDropdown = () => { const dropdownWidth = dropDownSize.width !== -1 ? dropDownSize.width - getDropdownHorizontalBorderWidthFromStyle() : undefined; return /*#__PURE__*/_react.default.createElement(_reactNative.FlatList, _extends({ scrollEnabled: scrollEnabled, style: [dropdownStyle, { width: dropdownWidth, height: dropDownSize.height }], data: dataSource, renderItem: _renderItem, keyExtractor: keyExtractor, ItemSeparatorComponent: showSeparator ? renderSeparator : null, automaticallyAdjustContentInsets: false, showsVerticalScrollIndicator: false }, dropdownProps)); }; const _renderModel = () => { const frameStyle = _calcPosition(); return /*#__PURE__*/_react.default.createElement(_Modal.default, _extends({ animated: animated, animationType: 'none', visible: _dropdownVisibleForAnimation, transparent: true, onRequestClose: _onRequestClose, supportedOrientations: ['portrait', 'portrait-upside-down', 'landscape', 'landscape-left', 'landscape-right'] }, modalProps), /*#__PURE__*/_react.default.createElement(_reactNative.TouchableWithoutFeedback, { disabled: !dropdownVisible, onPress: _onModalPress }, /*#__PURE__*/_react.default.createElement(_reactNative.View, { style: styles.modal }, /*#__PURE__*/_react.default.createElement(_reactNative.Animated.View, { style: [styles.dropdown, frameStyle, animated && dropdownAnimatedStyle] }, loading ? _renderLoading() : _renderDropdown())))); }; return /*#__PURE__*/_react.default.createElement(_reactNative.View, _extends({ onLayout: onLayout, style: rootContainerStyle }, rootContainerProps), _renderLabel(), _renderModel()); } var _default = /*#__PURE__*/_react.default.forwardRef(Component); exports.default = _default; const styles = _reactNative.StyleSheet.create({ label: { fontSize: 12 }, modal: { flexGrow: 1 }, dropdown: { height: (33 + _reactNative.StyleSheet.hairlineWidth) * 4, position: 'absolute', borderWidth: _reactNative.StyleSheet.hairlineWidth, borderColor: 'lightgray', borderRadius: 2, backgroundColor: '#ffffff', justifyContent: 'center' }, loading: { alignSelf: 'center' }, dropdownTextStyle: { paddingHorizontal: 6, paddingVertical: 10, fontSize: 11, color: 'rgba(0,0,0,.65)', backgroundColor: 'white', textAlignVertical: 'center' }, highlightedRowText: { color: 'black' }, highlightedRow: { backgroundColor: '#f5f5f5' }, labelDisabled: { color: 'rgba(0,0,0,.25)' }, labelContainerDisabled: { backgroundColor: '#fff' }, splitLint: { backgroundColor: 'rgb(217, 217, 217)', height: 1, width: '100%' } }); //# sourceMappingURL=Dropdown.js.map