@riebel/react-native-multiple-select 0.6.0

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.
@@ -0,0 +1,46 @@
1
+ ## Issue summary
2
+ Add a short description of issue, preferably, a sentence.
3
+
4
+
5
+ ### Library versions
6
+
7
+ <!--
8
+ List the version of react-native and multi-select in use as below
9
+
10
+ react-native: 0.48.1
11
+ react-native-multiple-select: 0.2.1
12
+ -->
13
+
14
+
15
+ ### Steps to Reproduce
16
+
17
+ (Write your steps here:)
18
+
19
+ 1.
20
+ 2.
21
+ 3.
22
+
23
+ ### Expected Behavior
24
+
25
+ <!--
26
+ How did you expect your project to behave?
27
+ It’s fine if you’re not sure your understanding is correct.
28
+ Just write down what you thought would happen.
29
+ -->
30
+
31
+ (Write what you thought would happen.)
32
+
33
+ ### Actual Behavior
34
+
35
+ <!--
36
+ Did something go wrong?
37
+ Is something broken, or not behaving as you expected?
38
+ Describe this section in detail, and attach screenshots if possible.
39
+ Don't just say "it doesn't work"!
40
+ -->
41
+
42
+ (Write what happened. Add screenshots!)
43
+
44
+ ### Reproducible Code
45
+
46
+ (Paste exact code snippet and instructions to reproduce the issue.)
package/.prettierrc ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "semi": false,
3
+ "trailingComma": "none",
4
+ "singleQuote": true,
5
+ "printWidth": 90,
6
+ "tabWidth": 2,
7
+ "bracketSpacing": true,
8
+ "arrowParens": "always"
9
+ }
@@ -0,0 +1,9 @@
1
+ [<img alt="enieber" src="https://avatars3.githubusercontent.com/u/7907068?v=4&s=117" width="117">](https://github.com/enieber)[<img alt="arslbbt" src="https://avatars0.githubusercontent.com/u/12788132?v=4&s=117" width="117">](https://github.com/arslbbt)[<img alt="ziyafenn" src="https://avatars0.githubusercontent.com/u/314302?v=4&s=117" width="117">](https://github.com/ziyafenn)[<img alt="SushilShrestha" src="https://avatars2.githubusercontent.com/u/3480695?v=4&s=117" width="117">](https://github.com/SushilShrestha)[<img alt="remeryAGS" src="https://avatars0.githubusercontent.com/u/4663476?v=4&s=117" width="117">](https://github.com/remeryAGS)[<img alt="MartinCamen" src="https://avatars3.githubusercontent.com/u/8720813?v=4&s=117" width="117">](https://github.com/MartinCamen)
2
+
3
+ [<img alt="mikaello" src="https://avatars3.githubusercontent.com/u/2505178?v=4&s=117" width="117">](https://github.com/mikaello)[<img alt="pwoltman" src="https://avatars3.githubusercontent.com/u/1881769?v=4&s=117" width="117">](https://github.com/pwoltman)[<img alt="easyhrworld" src="https://avatars3.githubusercontent.com/u/22884806?v=4&s=117" width="117">](https://github.com/easyhrworld)[<img alt="creedmangrum" src="https://avatars0.githubusercontent.com/u/16233247?v=4&s=117" width="117">](https://github.com/creedmangrum)[<img alt="donedgardo" src="https://avatars2.githubusercontent.com/u/2483536?v=4&s=117" width="117">](https://github.com/donedgardo)[<img alt="toystars" src="https://avatars0.githubusercontent.com/u/16062709?v=4&s=117" width="117">](https://github.com/toystars)
4
+
5
+ [<img alt="augustoalegon" src="https://avatars3.githubusercontent.com/u/14319083?s=117&v=4" width="117">](https://github.com/augustoalegon)[<img alt="riebel" src="https://avatars.githubusercontent.com/riebel?s=117&v=4" width="117">](https://github.com/riebel)
6
+
7
+
8
+
9
+
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2017 Mustapha Babatunde
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,273 @@
1
+ # react-native-multiple-select
2
+
3
+ [![npm](https://img.shields.io/npm/v/react-native-multiple-select.svg)](https://www.npmjs.com/package/react-native-multiple-select) [![Downloads](https://img.shields.io/npm/dt/react-native-multiple-select.svg)](https://www.npmjs.com/package/react-native-multiple-select) [![Licence](https://img.shields.io/npm/l/react-native-multiple-select.svg)](https://www.npmjs.com/package/react-native-multiple-select)
4
+
5
+ > Simple multi-select component for React Native, written in TypeScript.
6
+
7
+ ![multiple](https://user-images.githubusercontent.com/16062709/30819847-0907dd1e-a218-11e7-9980-e70b2d8e7953.gif) ![single](https://user-images.githubusercontent.com/16062709/30819849-095d6144-a218-11e7-85b9-4e2b96f9ead9.gif)
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ npm install react-native-multiple-select
13
+ ```
14
+
15
+ or with yarn:
16
+
17
+ ```bash
18
+ yarn add react-native-multiple-select
19
+ ```
20
+
21
+ ### Peer dependencies
22
+
23
+ - `react` >= 16.8.0
24
+ - `react-native` >= 0.60.0
25
+
26
+ ### Icon library
27
+
28
+ This component does **not** bundle an icon library. You provide your own via the `iconComponent` prop. The component you pass must accept `name`, `size?`, `color?`, `style?`, and `onPress?` props.
29
+
30
+ Icon names default to **MaterialCommunityIcons** names. Use the `iconNames` prop to remap them for other icon sets:
31
+
32
+ | Key | Default | Purpose |
33
+ |-----|---------|---------|
34
+ | `search` | `magnify` | Search input icon |
35
+ | `close` | `close-circle` | Tag remove icon |
36
+ | `check` | `check` | Selected item checkmark |
37
+ | `arrowDown` | `menu-down` | Dropdown indicator |
38
+ | `arrowRight` | `menu-right` | Dropdown indicator (hideSubmitButton) |
39
+ | `arrowLeft` | `arrow-left` | Back / close dropdown |
40
+
41
+ **MaterialCommunityIcons** (defaults work out of the box):
42
+
43
+ ```tsx
44
+ import { MaterialDesignIcons } from '@react-native-vector-icons/material-design-icons'
45
+ // or
46
+ import MaterialCommunityIcons from '@expo/vector-icons/MaterialCommunityIcons'
47
+
48
+ <MultiSelect iconComponent={MaterialCommunityIcons} items={items} ... />
49
+ ```
50
+
51
+ **Ionicons:**
52
+
53
+ ```tsx
54
+ import Ionicons from '@expo/vector-icons/Ionicons'
55
+
56
+ <MultiSelect
57
+ iconComponent={Ionicons}
58
+ iconNames={{
59
+ search: 'search',
60
+ close: 'close-circle',
61
+ check: 'checkmark',
62
+ arrowDown: 'chevron-down',
63
+ arrowRight: 'chevron-forward',
64
+ arrowLeft: 'arrow-back'
65
+ }}
66
+ items={items}
67
+ ...
68
+ />
69
+ ```
70
+
71
+ **FontAwesome 6:**
72
+
73
+ ```tsx
74
+ import FontAwesome6 from '@expo/vector-icons/FontAwesome6'
75
+
76
+ <MultiSelect
77
+ iconComponent={FontAwesome6}
78
+ iconNames={{
79
+ search: 'magnifying-glass',
80
+ close: 'circle-xmark',
81
+ check: 'check',
82
+ arrowDown: 'chevron-down',
83
+ arrowRight: 'chevron-right',
84
+ arrowLeft: 'arrow-left'
85
+ }}
86
+ items={items}
87
+ ...
88
+ />
89
+ ```
90
+
91
+ ## Usage
92
+
93
+ ```tsx
94
+ import React, { useRef, useState } from 'react'
95
+ import { View } from 'react-native'
96
+ import MultiSelect from 'react-native-multiple-select'
97
+ import type { MultiSelectRef } from 'react-native-multiple-select'
98
+ import MaterialCommunityIcons from '@expo/vector-icons/MaterialCommunityIcons'
99
+
100
+ const items = [
101
+ { id: '92iijs7yta', name: 'Ondo' },
102
+ { id: 'a0s0a8ssbsd', name: 'Ogun' },
103
+ { id: '16hbajsabsd', name: 'Calabar' },
104
+ { id: 'nahs75a5sg', name: 'Lagos' },
105
+ { id: '667atsas', name: 'Maiduguri' },
106
+ { id: 'hsyasajs', name: 'Anambra' },
107
+ { id: 'djsjudksjd', name: 'Benue' },
108
+ { id: 'sdhyaysdj', name: 'Kaduna' },
109
+ { id: 'suudydjsjd', name: 'Abuja' }
110
+ ]
111
+
112
+ const MultiSelectExample = () => {
113
+ const [selectedItems, setSelectedItems] = useState<string[]>([])
114
+ const multiSelectRef = useRef<MultiSelectRef>(null)
115
+
116
+ return (
117
+ <View style={{ flex: 1 }}>
118
+ <MultiSelect
119
+ hideTags
120
+ items={items}
121
+ iconComponent={MaterialCommunityIcons}
122
+ uniqueKey="id"
123
+ ref={multiSelectRef}
124
+ onSelectedItemsChange={setSelectedItems}
125
+ selectedItems={selectedItems}
126
+ selectText="Pick Items"
127
+ searchInputPlaceholderText="Search Items..."
128
+ onChangeInput={(text) => console.log(text)}
129
+ tagRemoveIconColor="#CCC"
130
+ tagBorderColor="#CCC"
131
+ tagTextColor="#CCC"
132
+ selectedItemTextColor="#CCC"
133
+ selectedItemIconColor="#CCC"
134
+ itemTextColor="#000"
135
+ displayKey="name"
136
+ searchInputStyle={{ color: '#CCC' }}
137
+ submitButtonColor="#CCC"
138
+ submitButtonText="Submit"
139
+ />
140
+ <View>
141
+ {multiSelectRef.current?.getSelectedItemsExt(selectedItems)}
142
+ </View>
143
+ </View>
144
+ )
145
+ }
146
+ ```
147
+
148
+ ## Props
149
+
150
+ ### Required
151
+
152
+ | Prop | Type | Purpose |
153
+ |------|------|---------|
154
+ | `items` | `MultiSelectItem[]` | Array of objects to display. Each object must contain a name and unique identifier |
155
+ | `iconComponent` | `IconComponentType` | Icon component to render icons (e.g. `MaterialCommunityIcons`) |
156
+ | `onSelectedItemsChange` | `(items: string[]) => void` | Called when selection changes |
157
+
158
+ ### Optional
159
+
160
+ | Prop | Type | Default | Purpose |
161
+ |------|------|---------|---------|
162
+ | `iconNames` | `Partial<IconNames>` | MaterialCommunityIcons defaults | Override icon names for other icon libraries (see above) |
163
+ | `single` | `boolean` | `false` | Toggle between single and multi select mode |
164
+ | `selectedItems` | `string[]` | `[]` | Array of selected item keys |
165
+ | `uniqueKey` | `string` | `'_id'` | Key used to uniquely identify each item |
166
+ | `displayKey` | `string` | `'name'` | Key used to display item label |
167
+ | `selectText` | `string` | `'Select'` | Text displayed on the main component |
168
+ | `selectedText` | `string` | `'selected'` | Text appended to selection count |
169
+ | `searchInputPlaceholderText` | `string` | `'Search'` | Placeholder for the search input |
170
+ | `searchIcon` | `ReactNode` | magnify icon | Custom search icon element |
171
+ | `noItemsText` | `string` | `'No items to display.'` | Text shown when no items match |
172
+ | `filterMethod` | `'partial' \| 'full'` | `'partial'` | Search matching strategy |
173
+ | `canAddItems` | `boolean` | `false` | Allow users to add new items via the search input |
174
+ | `removeSelected` | `boolean` | `false` | Hide already-selected items from the dropdown |
175
+ | `hideTags` | `boolean` | `false` | Hide tokenized selected items below the selector |
176
+ | `hideSubmitButton` | `boolean` | `false` | Hide the submit button in the dropdown |
177
+ | `hideDropdown` | `boolean` | `false` | Hide dropdown cancel, show back arrow instead |
178
+ | `fixedHeight` | `boolean` | `false` | Use fixed height with scroll instead of auto height |
179
+ | `fontSize` | `number` | `14` | Font size for the dropdown label |
180
+ | `itemFontSize` | `number` | `16` | Font size for each item in the dropdown |
181
+ | `fontFamily` | `string` | `''` | Custom font family for the component |
182
+ | `altFontFamily` | `string` | `''` | Font family for the placeholder text |
183
+ | `itemFontFamily` | `string` | `''` | Font family for non-selected items |
184
+ | `selectedItemFontFamily` | `string` | `''` | Font family for selected items |
185
+ | `textColor` | `string` | `'#525966'` | Color for the dropdown label |
186
+ | `itemTextColor` | `string` | `'#525966'` | Text color for non-selected items |
187
+ | `selectedItemTextColor` | `string` | `'#00A5FF'` | Text color for selected items |
188
+ | `selectedItemIconColor` | `string` | `'#00A5FF'` | Check icon color for selected items |
189
+ | `tagBorderColor` | `string` | `'#00A5FF'` | Border color for selected item tags |
190
+ | `tagTextColor` | `string` | `'#00A5FF'` | Text color for selected item tags |
191
+ | `tagRemoveIconColor` | `string` | `'#C62828'` | Color for the tag remove icon |
192
+ | `submitButtonColor` | `string` | `'#CCC'` | Background color for the submit button |
193
+ | `submitButtonText` | `string` | `'Submit'` | Text on the submit button |
194
+ | `searchInputStyle` | `StyleProp<TextStyle>` | | Style for the search input |
195
+ | `tagContainerStyle` | `StyleProp<ViewStyle>` | | Style for tag containers |
196
+ | `styleMainWrapper` | `StyleProp<ViewStyle>` | | Style for the outermost wrapper |
197
+ | `styleDropdownMenu` | `StyleProp<ViewStyle>` | | Style for the dropdown menu view |
198
+ | `styleDropdownMenuSubsection` | `StyleProp<ViewStyle>` | | Style for the inner dropdown view |
199
+ | `styleInputGroup` | `StyleProp<ViewStyle>` | | Style for the search input group |
200
+ | `styleItemsContainer` | `StyleProp<ViewStyle>` | | Style for the items container |
201
+ | `styleListContainer` | `StyleProp<ViewStyle>` | | Style for the list container |
202
+ | `styleRowList` | `StyleProp<ViewStyle>` | | Style for each item row |
203
+ | `styleSelectorContainer` | `StyleProp<ViewStyle>` | | Style for the open selector container |
204
+ | `styleTextDropdown` | `StyleProp<TextStyle>` | | Style for dropdown text |
205
+ | `styleTextDropdownSelected` | `StyleProp<TextStyle>` | | Style for dropdown text when items are selected |
206
+ | `styleTextTag` | `StyleProp<TextStyle>` | | Style for tag text |
207
+ | `styleIndicator` | `StyleProp<ViewStyle>` | | Style for the dropdown indicator icon |
208
+ | `textInputProps` | `TextInputProps` | | Additional props for the TextInput |
209
+ | `flatListProps` | `Partial<FlatListProps>` | | Additional props for the FlatList |
210
+
211
+ ### Callbacks
212
+
213
+ | Prop | Type | Purpose |
214
+ |------|------|---------|
215
+ | `onAddItem` | `(newItems: MultiSelectItem[]) => void` | Called when a new item is added |
216
+ | `onChangeInput` | `(text: string) => void` | Called when the search input changes |
217
+ | `onClearSelector` | `() => void` | Called when the back button is pressed |
218
+ | `onToggleList` | `() => void` | Called when the selector is toggled |
219
+
220
+ ## Notes
221
+
222
+ - **Displaying tags elsewhere:** Use a ref to call `getSelectedItemsExt(selectedItems)` and render tags in a different part of your view.
223
+
224
+ - **Disabled items:** Set `disabled: true` on an item object to render it in gray and make it non-interactive.
225
+
226
+ - **Single mode:** `selectedItems` should still be passed as an array. The selected item is returned as a single-element array.
227
+
228
+ - **Display key:** By default the component uses the `name` key to display items. Use `displayKey` to change this.
229
+
230
+ - **Filter methods:**
231
+ - `partial`: "University of New" matches "University of New York" but also "University of Columbia" and "New England Tech" (individual word matches)
232
+ - `full`: "University of New" only matches items containing the full substring "University of New"
233
+
234
+ ## TypeScript
235
+
236
+ This package is written in TypeScript and ships type declarations. Exported types:
237
+
238
+ ```tsx
239
+ import type {
240
+ MultiSelectProps,
241
+ MultiSelectItem,
242
+ MultiSelectRef,
243
+ IconProps,
244
+ IconComponentType,
245
+ IconNames
246
+ } from 'react-native-multiple-select'
247
+ ```
248
+
249
+ ## Contributing
250
+
251
+ Contributions are **welcome** and will be fully **credited**.
252
+
253
+ Contributions are accepted via Pull Requests on [Github](https://github.com/toystars/react-native-multiple-select).
254
+
255
+ ### Pull Requests
256
+
257
+ - **Document changes in behaviour** - Keep the README and relevant docs up-to-date.
258
+ - **Follow SemVer** - We follow [SemVer v2.0.0](http://semver.org/).
259
+ - **Create feature branches** - Don't ask us to pull from your master branch.
260
+ - **One pull request per feature** - If you want to do more than one thing, send multiple pull requests.
261
+ - **Squash commits** - Make sure each commit in your PR is meaningful.
262
+
263
+ ## Issues
264
+
265
+ Check [issues](https://github.com/toystars/react-native-multiple-select/issues) for current issues.
266
+
267
+ ## Contributors
268
+
269
+ See [CONTRIBUTORS](CONTRIBUTORS.md)
270
+
271
+ ## License
272
+
273
+ The MIT License (MIT). See [LICENSE](LICENSE) for more information.
@@ -0,0 +1,3 @@
1
+ module.exports = {
2
+ presets: ['@react-native/babel-preset']
3
+ }
@@ -0,0 +1,9 @@
1
+ /*!
2
+ * react-native-multi-select
3
+ * Copyright(c) 2017 Mustapha Babatunde Oluwaleke
4
+ * MIT Licensed
5
+ */
6
+ import React from 'react';
7
+ import type { MultiSelectProps, MultiSelectRef } from './types';
8
+ declare const MultiSelect: React.ForwardRefExoticComponent<MultiSelectProps & React.RefAttributes<MultiSelectRef>>;
9
+ export default MultiSelect;
@@ -0,0 +1,278 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /*!
3
+ * react-native-multi-select
4
+ * Copyright(c) 2017 Mustapha Babatunde Oluwaleke
5
+ * MIT Licensed
6
+ */
7
+ import React, { useState, useCallback, useImperativeHandle, forwardRef } from 'react';
8
+ import { Text, View, TextInput, TouchableWithoutFeedback, TouchableOpacity, FlatList, UIManager } from 'react-native';
9
+ import styles, { colorPack, selectorViewStyle } from './styles';
10
+ // set UIManager LayoutAnimationEnabledExperimental
11
+ if (UIManager.setLayoutAnimationEnabledExperimental) {
12
+ UIManager.setLayoutAnimationEnabledExperimental(true);
13
+ }
14
+ const getDisplayValue = (item, key) => {
15
+ const val = item[key];
16
+ if (val == null)
17
+ return '';
18
+ if (typeof val === 'string')
19
+ return val;
20
+ if (typeof val === 'number' || typeof val === 'boolean')
21
+ return String(val);
22
+ return '';
23
+ };
24
+ const MultiSelect = forwardRef(({ single = false, selectedItems = [], items, iconComponent: Icon, iconNames, uniqueKey = '_id', displayKey = 'name', tagBorderColor = colorPack.primary, tagTextColor = colorPack.primary, tagRemoveIconColor = colorPack.danger, tagContainerStyle, fontFamily = '', selectedItemFontFamily = '', selectedItemTextColor = colorPack.primary, selectedItemIconColor = colorPack.primary, itemFontFamily = '', itemTextColor = colorPack.textPrimary, itemFontSize = 16, searchIcon, searchInputPlaceholderText = 'Search', searchInputStyle = { color: colorPack.textPrimary }, selectText = 'Select', selectedText = 'selected', altFontFamily = '', fontSize = 14, textColor = colorPack.textPrimary, fixedHeight = false, hideTags = false, hideSubmitButton = false, hideDropdown = false, submitButtonColor = '#CCC', submitButtonText = 'Submit', canAddItems = false, removeSelected = false, noItemsText = 'No items to display.', filterMethod = 'partial', onSelectedItemsChange, onAddItem, onChangeInput, onClearSelector: onClearSelectorProp, onToggleList, textInputProps, flatListProps, styleDropdownMenu, styleDropdownMenuSubsection, styleInputGroup, styleItemsContainer, styleListContainer, styleMainWrapper, styleRowList, styleSelectorContainer, styleTextDropdown, styleTextDropdownSelected, styleTextTag, styleIndicator }, ref) => {
25
+ const [selector, setSelector] = useState(false);
26
+ const [searchTerm, setSearchTerm] = useState('');
27
+ const names = React.useMemo(() => ({
28
+ search: iconNames?.search ?? 'magnify',
29
+ close: iconNames?.close ?? 'close-circle',
30
+ check: iconNames?.check ?? 'check',
31
+ arrowDown: iconNames?.arrowDown ?? 'menu-down',
32
+ arrowRight: iconNames?.arrowRight ?? 'menu-right',
33
+ arrowLeft: iconNames?.arrowLeft ?? 'arrow-left'
34
+ }), [iconNames]);
35
+ const resolvedSearchIcon = searchIcon ?? (_jsx(Icon, { name: names.search, size: 20, color: colorPack.placeholderTextColor, style: styles.searchIconMargin }));
36
+ const getItemKey = useCallback((item) => getDisplayValue(item, uniqueKey), [uniqueKey]);
37
+ const findItem = useCallback((itemKey) => items.find((singleItem) => singleItem[uniqueKey] === itemKey) ?? {}, [items, uniqueKey]);
38
+ const itemSelected = useCallback((item) => selectedItems.indexOf(getItemKey(item)) !== -1, [selectedItems, getItemKey]);
39
+ const handleChangeInput = useCallback((value) => {
40
+ onChangeInput?.(value);
41
+ setSearchTerm(value);
42
+ }, [onChangeInput]);
43
+ const getSelectLabel = useCallback(() => {
44
+ if (selectedItems.length === 0) {
45
+ return selectText;
46
+ }
47
+ if (single) {
48
+ const foundItem = findItem(selectedItems[0]);
49
+ return getDisplayValue(foundItem, displayKey) || selectText;
50
+ }
51
+ return `${selectText} (${String(selectedItems.length)} ${selectedText})`;
52
+ }, [selectedItems, selectText, single, findItem, displayKey, selectedText]);
53
+ const clearSearchTerm = useCallback(() => {
54
+ setSearchTerm('');
55
+ }, []);
56
+ const toggleSelector = useCallback(() => {
57
+ setSelector((prev) => !prev);
58
+ onToggleList?.();
59
+ }, [onToggleList]);
60
+ const submitSelection = useCallback(() => {
61
+ toggleSelector();
62
+ clearSearchTerm();
63
+ }, [toggleSelector, clearSearchTerm]);
64
+ const clearSelectorCallback = useCallback(() => {
65
+ setSelector(false);
66
+ onClearSelectorProp?.();
67
+ }, [onClearSelectorProp]);
68
+ const removeItem = useCallback((item) => {
69
+ const key = getItemKey(item);
70
+ const newItems = selectedItems.filter((singleItem) => key !== singleItem);
71
+ onSelectedItemsChange(newItems);
72
+ }, [selectedItems, getItemKey, onSelectedItemsChange]);
73
+ const toggleItem = useCallback((item) => {
74
+ const key = getItemKey(item);
75
+ if (single) {
76
+ submitSelection();
77
+ onSelectedItemsChange([key]);
78
+ }
79
+ else {
80
+ const isSelected = itemSelected(item);
81
+ const newItems = isSelected
82
+ ? selectedItems.filter((singleItem) => key !== singleItem)
83
+ : [...selectedItems, key];
84
+ onSelectedItemsChange(newItems);
85
+ }
86
+ }, [
87
+ single,
88
+ getItemKey,
89
+ selectedItems,
90
+ onSelectedItemsChange,
91
+ itemSelected,
92
+ submitSelection
93
+ ]);
94
+ const addItem = useCallback(() => {
95
+ const newItemName = searchTerm;
96
+ if (newItemName) {
97
+ const newItemId = newItemName
98
+ .split(' ')
99
+ .filter((word) => word.length)
100
+ .join('-');
101
+ const newItems = [...items, { [uniqueKey]: newItemId, name: newItemName }];
102
+ const newSelectedItems = [...selectedItems, newItemId];
103
+ onAddItem?.(newItems);
104
+ onSelectedItemsChange(newSelectedItems);
105
+ clearSearchTerm();
106
+ }
107
+ }, [
108
+ searchTerm,
109
+ items,
110
+ uniqueKey,
111
+ selectedItems,
112
+ onAddItem,
113
+ onSelectedItemsChange,
114
+ clearSearchTerm
115
+ ]);
116
+ const itemStyle = useCallback((item) => {
117
+ const isSelected = itemSelected(item);
118
+ const ff = {};
119
+ if (isSelected && selectedItemFontFamily) {
120
+ ff.fontFamily = selectedItemFontFamily;
121
+ }
122
+ else if (!isSelected && itemFontFamily) {
123
+ ff.fontFamily = itemFontFamily;
124
+ }
125
+ const color = isSelected
126
+ ? { color: selectedItemTextColor }
127
+ : { color: itemTextColor };
128
+ return { ...ff, ...color, fontSize: itemFontSize };
129
+ }, [
130
+ itemSelected,
131
+ selectedItemFontFamily,
132
+ itemFontFamily,
133
+ selectedItemTextColor,
134
+ itemTextColor,
135
+ itemFontSize
136
+ ]);
137
+ const displaySelectedItems = useCallback((optionalSelectedItems) => {
138
+ const actualSelectedItems = optionalSelectedItems ?? selectedItems;
139
+ return actualSelectedItems.map((singleSelectedItem) => {
140
+ const item = findItem(singleSelectedItem);
141
+ const label = getDisplayValue(item, displayKey);
142
+ if (!label)
143
+ return null;
144
+ return (_jsxs(View, { style: [
145
+ styles.selectedItem,
146
+ styles.selectedItemLayout,
147
+ {
148
+ width: label.length * 8 + 60,
149
+ borderColor: tagBorderColor
150
+ },
151
+ tagContainerStyle ?? {}
152
+ ], children: [_jsx(Text, { style: [
153
+ styles.tagLabel,
154
+ { color: tagTextColor },
155
+ styleTextTag ?? {},
156
+ fontFamily ? { fontFamily } : {}
157
+ ], numberOfLines: 1, children: label }), _jsx(TouchableOpacity, { onPress: () => {
158
+ removeItem(item);
159
+ }, children: _jsx(Icon, { name: names.close, style: [styles.tagRemoveIcon, { color: tagRemoveIconColor }] }) })] }, getItemKey(item)));
160
+ });
161
+ }, [
162
+ Icon,
163
+ names,
164
+ selectedItems,
165
+ findItem,
166
+ displayKey,
167
+ getItemKey,
168
+ tagBorderColor,
169
+ tagTextColor,
170
+ tagRemoveIconColor,
171
+ tagContainerStyle,
172
+ styleTextTag,
173
+ fontFamily,
174
+ removeItem
175
+ ]);
176
+ useImperativeHandle(ref, () => ({
177
+ getSelectedItemsExt: (optionalItems) => (_jsx(View, { style: styles.rowWrap, children: displaySelectedItems(optionalItems) }))
178
+ }), [displaySelectedItems]);
179
+ const filterItemsPartial = useCallback((term) => {
180
+ const parts = term.trim().split(/[ \-:]+/);
181
+ const regex = new RegExp(`(${parts.join('|')})`, 'ig');
182
+ return items.filter((item) => regex.test(getDisplayValue(item, displayKey)));
183
+ }, [items, displayKey]);
184
+ const filterItemsFull = useCallback((term) => {
185
+ const lower = term.trim().toLowerCase();
186
+ return items.filter((item) => getDisplayValue(item, displayKey).toLowerCase().indexOf(lower) >= 0);
187
+ }, [items, displayKey]);
188
+ const filterItems = useCallback((term) => {
189
+ if (filterMethod === 'full')
190
+ return filterItemsFull(term);
191
+ return filterItemsPartial(term);
192
+ }, [filterMethod, filterItemsFull, filterItemsPartial]);
193
+ const getRow = useCallback((item) => (_jsx(TouchableOpacity, { disabled: Boolean(item.disabled), onPress: () => {
194
+ toggleItem(item);
195
+ }, style: [styleRowList, styles.rowPadding], children: _jsx(View, { children: _jsxs(View, { style: styles.rowAlignCenter, children: [_jsx(Text, { style: [
196
+ styles.rowItemText,
197
+ itemStyle(item),
198
+ item.disabled ? styles.disabledText : {}
199
+ ], children: getDisplayValue(item, displayKey) }), itemSelected(item) ? (_jsx(Icon, { name: names.check, style: [styles.checkIcon, { color: selectedItemIconColor }] })) : null] }) }) })), [
200
+ Icon,
201
+ names,
202
+ toggleItem,
203
+ styleRowList,
204
+ itemStyle,
205
+ displayKey,
206
+ itemSelected,
207
+ selectedItemIconColor
208
+ ]);
209
+ const getRowNew = useCallback((item) => (_jsx(TouchableOpacity, { disabled: Boolean(item.disabled), onPress: () => {
210
+ addItem();
211
+ }, style: styles.rowPadding, children: _jsx(View, { children: _jsx(View, { style: styles.rowAlignCenter, children: _jsxs(Text, { style: [
212
+ styles.rowItemText,
213
+ itemStyle(item),
214
+ item.disabled ? styles.disabledText : {}
215
+ ], children: ["Add ", getDisplayValue(item, 'name'), " (tap or press return)"] }) }) }) })), [addItem, itemStyle]);
216
+ const renderItems = useCallback(() => {
217
+ let renderList = searchTerm ? filterItems(searchTerm) : items;
218
+ if (removeSelected) {
219
+ renderList = renderList.filter((item) => !selectedItems.includes(getItemKey(item)));
220
+ }
221
+ let itemList = null;
222
+ let searchTermMatch = false;
223
+ if (renderList.length) {
224
+ itemList = (_jsx(FlatList, { data: renderList, extraData: selectedItems, keyExtractor: (_item, index) => index.toString(), renderItem: (rowData) => getRow(rowData.item), ...flatListProps, nestedScrollEnabled: true }));
225
+ searchTermMatch = renderList.some((item) => item.name === searchTerm);
226
+ }
227
+ else if (!canAddItems) {
228
+ itemList = (_jsx(View, { style: styles.noItemsRow, children: _jsx(Text, { style: [styles.noItemsText, fontFamily ? { fontFamily } : {}], children: noItemsText }) }));
229
+ }
230
+ let addItemRow = null;
231
+ if (canAddItems && !searchTermMatch && searchTerm.length) {
232
+ addItemRow = getRowNew({ name: searchTerm });
233
+ }
234
+ return (_jsxs(View, { style: styleListContainer, children: [itemList, addItemRow] }));
235
+ }, [
236
+ searchTerm,
237
+ filterItems,
238
+ items,
239
+ removeSelected,
240
+ selectedItems,
241
+ getItemKey,
242
+ getRow,
243
+ flatListProps,
244
+ canAddItems,
245
+ fontFamily,
246
+ noItemsText,
247
+ getRowNew,
248
+ styleListContainer
249
+ ]);
250
+ return (_jsx(View, { style: styleMainWrapper, children: selector ? (_jsxs(View, { style: [selectorViewStyle(fixedHeight), styleSelectorContainer], children: [_jsxs(View, { style: [styles.inputGroup, styleInputGroup], children: [resolvedSearchIcon, _jsx(TextInput, { autoFocus: true, onChangeText: handleChangeInput, onSubmitEditing: addItem, placeholder: searchInputPlaceholderText, placeholderTextColor: colorPack.placeholderTextColor, underlineColorAndroid: "transparent", style: [searchInputStyle, styles.searchInputFlex], value: searchTerm, ...textInputProps }), hideSubmitButton && (_jsx(TouchableOpacity, { onPress: submitSelection, children: _jsx(Icon, { name: names.arrowDown, style: [styles.indicator, styles.indicatorPadded, styleIndicator] }) })), !hideDropdown && (_jsx(Icon, { name: names.arrowLeft, size: 20, onPress: clearSelectorCallback, color: colorPack.placeholderTextColor, style: styles.backArrowMargin }))] }), _jsxs(View, { style: styles.selectorContent, children: [_jsx(View, { style: styleItemsContainer, children: renderItems() }), !single && !hideSubmitButton && (_jsx(TouchableOpacity, { onPress: submitSelection, style: [styles.button, { backgroundColor: submitButtonColor }], children: _jsx(Text, { style: [styles.buttonText, fontFamily ? { fontFamily } : {}], children: submitButtonText }) }))] })] })) : (_jsxs(View, { children: [_jsx(View, { style: [styles.dropdownView, styleDropdownMenu], children: _jsx(View, { style: [
251
+ styles.subSection,
252
+ styles.subSectionPadded,
253
+ styleDropdownMenuSubsection
254
+ ], children: _jsx(TouchableWithoutFeedback, { onPress: toggleSelector, children: _jsxs(View, { style: styles.dropdownTouchable, children: [_jsx(Text, { style: selectedItems.length === 0
255
+ ? [
256
+ styles.dropdownLabelBase,
257
+ {
258
+ fontSize,
259
+ color: textColor || colorPack.placeholderTextColor
260
+ },
261
+ styleTextDropdown,
262
+ altFontFamily
263
+ ? { fontFamily: altFontFamily }
264
+ : fontFamily
265
+ ? { fontFamily }
266
+ : {}
267
+ ]
268
+ : [
269
+ styles.dropdownLabelBase,
270
+ {
271
+ fontSize,
272
+ color: textColor || colorPack.placeholderTextColor
273
+ },
274
+ styleTextDropdownSelected
275
+ ], numberOfLines: 1, children: getSelectLabel() }), _jsx(Icon, { name: hideSubmitButton ? names.arrowRight : names.arrowDown, style: [styles.indicator, styleIndicator] })] }) }) }) }), !single && !hideTags && selectedItems.length ? (_jsx(View, { style: styles.rowWrap, children: displaySelectedItems() })) : null] })) }));
276
+ });
277
+ MultiSelect.displayName = 'MultiSelect';
278
+ export default MultiSelect;
@@ -0,0 +1,7 @@
1
+ /*!
2
+ * react-native-multi-select
3
+ * Copyright(c) 2017 Mustapha Babatunde Oluwaleke
4
+ * MIT Licensed
5
+ */
6
+ export { default } from './MultiSelect';
7
+ export type { MultiSelectProps, MultiSelectItem, MultiSelectRef, IconProps, IconComponentType, IconNames } from './types';