UNPKG

react-native-autocomplete-dropdown

Version:

Dropdown Item picker with search and autocomplete (typeahead) functionality for react native

500 lines (496 loc) 17.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var _exportNames = { AutocompleteDropdown: true, AutocompleteDropdownContextProvider: true }; exports.AutocompleteDropdown = void 0; Object.defineProperty(exports, "AutocompleteDropdownContextProvider", { enumerable: true, get: function () { return _AutocompleteDropdownContext.AutocompleteDropdownContextProvider; } }); var _react = _interopRequireWildcard(require("react")); var _lodash = _interopRequireDefault(require("lodash.debounce")); var _reactNative = require("react-native"); var _reactNativeSizeMatters = require("react-native-size-matters"); var _Dropdown = require("./Dropdown"); var _NothingFound = require("./NothingFound"); var _RightButton = require("./RightButton"); var _ScrollViewListItem = require("./ScrollViewListItem"); var _AutocompleteDropdownContext = require("./AutocompleteDropdownContext"); var _diacriticless = _interopRequireDefault(require("./diacriticless")); var _theme = require("./theme"); var _jsxRuntime = require("react/jsx-runtime"); var _types = require("./types"); Object.keys(_types).forEach(function (key) { if (key === "default" || key === "__esModule") return; if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return; if (key in exports && exports[key] === _types[key]) return; Object.defineProperty(exports, key, { enumerable: true, get: function () { return _types[key]; } }); }); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); } const AutocompleteDropdown = exports.AutocompleteDropdown = /*#__PURE__*/(0, _react.memo)(props => { const { dataSet: dataSetProp, initialValue: initialValueProp, clearOnFocus = true, caseSensitive = false, ignoreAccents = true, trimSearchText = true, editable = true, enableLoadingIndicator = true, matchFrom, inputHeight = (0, _reactNativeSizeMatters.moderateScale)(40, 0.2), suggestionsListMaxHeight = (0, _reactNativeSizeMatters.moderateScale)(200, 0.2), // bottomOffset = 0, direction: directionProp, controller, onSelectItem: onSelectItemProp, onOpenSuggestionsList: onOpenSuggestionsListProp, useFilter, renderItem: customRenderItem, EmptyResultComponent, emptyResultText, onClear, onChangeText: onTextChange, debounce: debounceDelay = 0, onChevronPress: onChevronPressProp, onFocus: onFocusProp, onBlur: onBlurProp, onSubmit: onSubmitProp, closeOnSubmit, loading: loadingProp, LeftComponent, textInputProps, showChevron, showClear, rightButtonsContainerStyle, ChevronIconComponent, ClearIconComponent, RightIconComponent, onRightIconComponentPress, containerStyle, inputContainerStyle, suggestionsListTextStyle, ref } = props; const InputComponent = props.InputComponent || _reactNative.TextInput; const inputRef = (0, _react.useRef)(null); const containerRef = (0, _react.useRef)(null); const [searchText, setSearchText] = (0, _react.useState)(''); const [inputValue, setInputValue] = (0, _react.useState)(''); const [loading, setLoading] = (0, _react.useState)(loadingProp); const [selectedItem, setSelectedItem] = (0, _react.useState)(null); const [isOpened, setIsOpened] = (0, _react.useState)(false); const initialDataSetRef = (0, _react.useRef)(dataSetProp); const initialValueRef = (0, _react.useRef)(initialValueProp); const [dataSet, setDataSet] = (0, _react.useState)(dataSetProp); const matchFromStart = matchFrom === 'start' ? true : false; const { content, setContent, activeInputContainerRef, activeControllerRef, direction = directionProp, setDirection, controllerRefs } = (0, _react.useContext)(_AutocompleteDropdownContext.AutocompleteDropdownContext); const systemTheme = (0, _reactNative.useColorScheme)(); const themeName = props.theme || systemTheme || 'light'; const styles = (0, _react.useMemo)(() => getStyles(themeName), [themeName]); (0, _react.useEffect)(() => { setLoading(loadingProp); }, [loadingProp]); const calculateDirection = (0, _react.useCallback)(async ({ waitForKeyboard }) => { const [, positionY] = await new Promise(resolve => containerRef.current?.measureInWindow((...rect) => resolve(rect))); return new Promise(resolve => { setTimeout(() => { const kbHeight = _reactNative.Keyboard.metrics?.()?.height || 0; const screenHeight = _reactNative.Dimensions.get('window').height; setDirection((screenHeight - kbHeight) / 2 > positionY ? 'down' : 'up'); resolve(); }, waitForKeyboard ? _reactNative.Platform.select({ ios: 600, android: 250, default: 1 }) : 1 // wait for keyboard to show ); }); }, [setDirection]); const onClearPress = (0, _react.useCallback)(() => { setSearchText(''); setInputValue(''); setSelectedItem(null); setIsOpened(false); inputRef.current?.blur(); if (typeof onClear === 'function') { onClear(); } }, [onClear]); /** methods */ const close = (0, _react.useCallback)(() => { setIsOpened(false); setContent(undefined); }, [setContent]); const blur = (0, _react.useCallback)(() => { inputRef.current?.blur(); }, []); const focus = (0, _react.useCallback)(() => { inputRef.current?.focus(); }, []); const open = (0, _react.useCallback)(async () => { if (directionProp) { setDirection(directionProp); } else { await calculateDirection({ waitForKeyboard: !!inputRef.current?.isFocused() }); } setTimeout(() => { setIsOpened(true); }, 0); }, [calculateDirection, directionProp, setDirection]); const toggle = (0, _react.useCallback)(() => { isOpened ? close() : open(); }, [close, isOpened, open]); const clear = (0, _react.useCallback)(() => { onClearPress(); }, [onClearPress]); (0, _react.useEffect)(() => { if (ref) { if (typeof ref === 'function') { ref(inputRef.current); } else { ref.current = inputRef.current; } } }, [ref]); /** Set initial value */ (0, _react.useEffect)(() => { const initialDataSet = initialDataSetRef.current; const initialValue = initialValueRef.current; let initialValueItem; if (typeof initialValue === 'string') { initialValueItem = initialDataSet?.find(el => el.id === initialValue); } else if (typeof initialValue === 'object' && initialValue.id) { initialValueItem = initialDataSet?.find(el => el.id === initialValue?.id); if (!initialValueItem) { // set the item as it is if it's not in the list initialValueItem = initialValue; } } if (initialValueItem) { setSelectedItem(initialValueItem); } }, []); (0, _react.useEffect)(() => { return () => { setContent(undefined); setIsOpened(false); }; }, [setContent]); const setInputText = (0, _react.useCallback)(text => { setSearchText(text); }, []); const setItem = (0, _react.useCallback)(item => { setSelectedItem(item); }, []); (0, _react.useEffect)(() => { if (activeControllerRef?.current) { controllerRefs?.current.push(activeControllerRef?.current); } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const closeAll = (0, _react.useCallback)(() => { controllerRefs?.current.forEach(c => { c?.blur?.(); c?.close?.(); }); }, [controllerRefs]); /** expose controller methods */ (0, _react.useEffect)(() => { const methods = activeControllerRef ? { close, blur, focus, open, toggle, clear, setInputText, setItem } : null; if (activeControllerRef) { activeControllerRef.current = methods; } if (typeof controller === 'function') { controller(methods); } else if (controller) { controller.current = methods; } }, [blur, focus, clear, close, controller, activeControllerRef, open, setInputText, setItem, toggle]); (0, _react.useEffect)(() => { if (selectedItem) { setInputValue(selectedItem.title ?? ''); } else { setInputValue(''); } }, [selectedItem]); (0, _react.useEffect)(() => { setInputValue(searchText); }, [searchText]); (0, _react.useEffect)(() => { if (typeof onSelectItemProp === 'function') { onSelectItemProp(selectedItem); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedItem]); (0, _react.useEffect)(() => { if (typeof onOpenSuggestionsListProp === 'function') { onOpenSuggestionsListProp(isOpened); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [isOpened]); (0, _react.useEffect)(() => { // renew state on close if (!isOpened && selectedItem && !loading && !inputRef.current?.isFocused()) { setInputValue(selectedItem.title || ''); } }, [isOpened, loading, searchText, selectedItem]); const _onSelectItem = (0, _react.useCallback)(item => { setSelectedItem(item); inputRef.current?.blur(); setIsOpened(false); }, []); (0, _react.useEffect)(() => { initialDataSetRef.current = dataSetProp; setDataSet(dataSetProp); }, [dataSetProp]); (0, _react.useEffect)(() => { const initialDataSet = initialDataSetRef.current; if (!searchText?.length) { setDataSet(initialDataSet); return; } if (!Array.isArray(initialDataSet) || useFilter === false) { return; } let findWhat = caseSensitive ? searchText : searchText.toLowerCase(); if (ignoreAccents) { findWhat = (0, _diacriticless.default)(findWhat); } if (trimSearchText) { findWhat = findWhat.trim(); } const newSet = initialDataSet.filter(item => { const titleStr = item.title || ''; const title = caseSensitive ? titleStr : titleStr.toLowerCase(); const findWhere = ignoreAccents ? (0, _diacriticless.default)(title) : title; if (matchFromStart) { return typeof item.title === 'string' && findWhere.startsWith(findWhat); } else { return typeof item.title === 'string' && findWhere.indexOf(findWhat) !== -1; } }); setDataSet(newSet); }, [ignoreAccents, matchFromStart, caseSensitive, searchText, trimSearchText, useFilter]); const renderItem = (0, _react.useCallback)(({ item }) => { if (typeof customRenderItem === 'function') { const EL = customRenderItem(item, searchText); return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, { onPress: () => _onSelectItem(item), children: EL }); } return /*#__PURE__*/(0, _jsxRuntime.jsx)(_ScrollViewListItem.ScrollViewListItem, { title: item.title || '', highlight: searchText, style: suggestionsListTextStyle, theme: themeName, onPress: () => _onSelectItem(item), ignoreAccents: ignoreAccents }, item.id); }, [_onSelectItem, customRenderItem, ignoreAccents, searchText, suggestionsListTextStyle, themeName]); const ListEmptyComponent = (0, _react.useMemo)(() => { return EmptyResultComponent ?? /*#__PURE__*/(0, _jsxRuntime.jsx)(_NothingFound.NothingFound, { emptyResultText: emptyResultText }); }, [EmptyResultComponent, emptyResultText]); const debouncedEvent = (0, _react.useMemo)(() => (0, _lodash.default)(text => { if (typeof onTextChange === 'function') { onTextChange(text); } setLoading(false); }, debounceDelay), [debounceDelay, onTextChange]); const onChangeText = (0, _react.useCallback)(text => { setSearchText(text); setInputValue(text); setLoading(true); debouncedEvent(text); }, [debouncedEvent]); const onChevronPress = (0, _react.useCallback)(() => { toggle(); _reactNative.Keyboard.dismiss(); if (typeof onChevronPressProp === 'function') { onChevronPressProp(); } }, [onChevronPressProp, toggle]); const onFocus = (0, _react.useCallback)(e => { if (clearOnFocus) { setSearchText(''); setInputValue(''); } if (typeof onFocusProp === 'function') { onFocusProp(e); } open(); }, [clearOnFocus, onFocusProp, open]); const onBlur = (0, _react.useCallback)(e => { if (typeof onBlurProp === 'function') { onBlurProp(e); } }, [onBlurProp]); const onSubmit = (0, _react.useCallback)(e => { inputRef.current?.blur(); if (closeOnSubmit) { close(); } if (typeof onSubmitProp === 'function') { onSubmitProp(e); } }, [close, closeOnSubmit, onSubmitProp]); const onPressOut = (0, _react.useCallback)(e => { closeAll(); if (editable) { inputRef?.current?.focus(); } else { toggle(); } }, [closeAll, editable, toggle]); (0, _react.useEffect)(() => { if (!content && !inputRef.current?.isFocused() || loading) { const db = (0, _lodash.default)(() => { setIsOpened(false); }, 100); db(); return () => { db.cancel(); }; } }, [content, loading]); (0, _react.useEffect)(() => { // searchTextRef if (searchText && inputRef.current?.isFocused() && !loading) { setIsOpened(true); } }, [loading, searchText]); (0, _react.useEffect)(() => { if (isOpened && Array.isArray(dataSet)) { if (activeInputContainerRef) { activeInputContainerRef.current = containerRef.current; } setContent(/*#__PURE__*/(0, _jsxRuntime.jsx)(_Dropdown.Dropdown, { dataSet: dataSet, direction: direction, flatListProps: props.flatListProps, inputHeight: inputHeight, suggestionsListContainerStyle: props.suggestionsListContainerStyle, suggestionsListMaxHeight: suggestionsListMaxHeight, renderItem: renderItem, ItemSeparatorComponent: props.ItemSeparatorComponent, ListEmptyComponent: ListEmptyComponent, theme: themeName })); } else { setContent(undefined); } }, [ListEmptyComponent, activeInputContainerRef, dataSet, direction, inputHeight, isOpened, props.suggestionsListContainerStyle, props.flatListProps, props.ItemSeparatorComponent, renderItem, setContent, suggestionsListMaxHeight, themeName]); return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, { onStartShouldSetResponder: () => true, onTouchEnd: e => { e.stopPropagation(); }, style: [styles.container, containerStyle], children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { ref: containerRef, onLayout: _ => {} // it's necessary use onLayout here for Androd (bug?) , style: [styles.inputContainerStyle, inputContainerStyle], children: [LeftComponent, /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, { style: styles.pressable, pointerEvents: _reactNative.Platform.select({ ios: 'box-only', default: 'auto' }), onPressOut: onPressOut, children: /*#__PURE__*/(0, _jsxRuntime.jsx)(InputComponent, { ref: inputRef, value: inputValue, onChangeText: onChangeText, autoCorrect: false, editable: editable, onBlur: onBlur, onFocus: onFocus, onSubmitEditing: onSubmit, placeholderTextColor: _theme.theme[themeName].inputPlaceholderColor, ...textInputProps, style: [styles.input, { height: inputHeight }, (textInputProps ?? {}).style] }) }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_RightButton.RightButton, { isOpened: isOpened, inputHeight: inputHeight, onClearPress: onClearPress, onChevronPress: onChevronPress, showChevron: showChevron ?? true, showClear: showClear ?? (!!searchText || !!selectedItem), enableLoadingIndicator: enableLoadingIndicator, loading: loading, buttonsContainerStyle: rightButtonsContainerStyle, ChevronIconComponent: ChevronIconComponent, ClearIconComponent: ClearIconComponent, RightIconComponent: RightIconComponent, onRightIconComponentPress: onRightIconComponentPress })] }) }); }); const getStyles = (themeName = 'light') => _reactNativeSizeMatters.ScaledSheet.create({ container: { marginVertical: 2 }, inputContainerStyle: { display: 'flex', flexDirection: 'row', backgroundColor: _theme.theme[themeName].inputBackgroundColor, borderRadius: 5, overflow: 'hidden' }, input: { flexGrow: 1, flexShrink: 1, overflow: 'hidden', paddingHorizontal: 13, fontSize: 16, color: _theme.theme[themeName].inputTextColor }, pressable: { flexGrow: 1, flexShrink: 1 } }); //# sourceMappingURL=index.js.map