@momo-kits/auto-complete 0.73.3-beta.5 → 0.74.2-react-native.2

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.
package/index.tsx ADDED
@@ -0,0 +1,87 @@
1
+ import React, {FC, useEffect, useState} from 'react';
2
+ import {FlatList, TouchableOpacity} from 'react-native';
3
+ import {Divider, Shadow, Text} from '@momo-kits/foundation';
4
+ import {AutoCompleteProps, SuggestItem} from './types';
5
+ import styles from './styles';
6
+
7
+ const AutoComplete: FC<AutoCompleteProps> = ({
8
+ data = [],
9
+ query = '',
10
+ onPressItem,
11
+ maxItemShow = 5,
12
+ searchKey = 'title',
13
+ }) => {
14
+ const [haveSelected, setHaveSelected] = useState(false);
15
+
16
+ useEffect(() => {
17
+ setHaveSelected(false);
18
+ onSearch(query);
19
+ return () => {};
20
+ }, [query]);
21
+
22
+ const removeDiacritics = (str: string) => {
23
+ return str.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
24
+ };
25
+
26
+ const sanitizeInput = (str: string) => {
27
+ return str.replace(/[\\/*+?().{}[\]^$|]/g, '');
28
+ };
29
+
30
+ const onSearch = (query: string) => {
31
+ const sanitizedText = sanitizeInput(query);
32
+ const normalizedText = removeDiacritics(sanitizedText.trim().toLowerCase());
33
+ const regex = new RegExp(normalizedText, 'i');
34
+
35
+ const filtered: (string | SuggestItem)[] = data
36
+ .filter((item: string | SuggestItem) => {
37
+ if (typeof item === 'string') {
38
+ return regex.test(removeDiacritics(item.toLowerCase()));
39
+ } else {
40
+ return regex.test(removeDiacritics(item?.[searchKey].toLowerCase()));
41
+ }
42
+ })
43
+ .slice(0, maxItemShow);
44
+ return filtered;
45
+ };
46
+
47
+ const onSelected = (title: string, value: null | string | undefined) => {
48
+ setHaveSelected(true);
49
+ onPressItem?.(title, value);
50
+ };
51
+
52
+ const _renderItem = ({item}: {item: string | SuggestItem}) => {
53
+ const title = typeof item === 'string' ? item : item.title;
54
+ const value = typeof item === 'string' ? null : item.value;
55
+
56
+ return (
57
+ <TouchableOpacity
58
+ onPress={() => onSelected(title, value)}
59
+ style={styles.item}>
60
+ <Text numberOfLines={1} typography={'body_default_regular'}>
61
+ {title}
62
+ </Text>
63
+ {!!value && (
64
+ <Text numberOfLines={1} typography={'body_default_regular'}>
65
+ {value}
66
+ </Text>
67
+ )}
68
+ </TouchableOpacity>
69
+ );
70
+ };
71
+
72
+ const filteredData = onSearch(query);
73
+
74
+ if (filteredData.length === 0 || query.length === 0 || haveSelected)
75
+ return null;
76
+ return (
77
+ <FlatList
78
+ keyExtractor={(item, index) => index.toString()}
79
+ ItemSeparatorComponent={() => <Divider />}
80
+ style={[styles.flatList, Shadow.Light]}
81
+ data={filteredData}
82
+ renderItem={_renderItem}
83
+ />
84
+ );
85
+ };
86
+
87
+ export {AutoComplete};
package/package.json CHANGED
@@ -1,16 +1,17 @@
1
1
  {
2
2
  "name": "@momo-kits/auto-complete",
3
- "version": "0.73.3-beta.5",
3
+ "version": "0.74.2-react-native.2",
4
4
  "private": false,
5
- "main": "index.js",
6
- "dependencies": {},
5
+ "main": "index.tsx",
7
6
  "peerDependencies": {
8
- "react": "16.9.0",
9
- "react-native": ">=0.55",
10
- "@momo-kits/core": ">=0.0.4-beta",
11
- "lodash": "^4.17.15",
12
- "prop-types": "^15.7.2"
7
+ "@momo-kits/foundation": "latest",
8
+ "react": "*",
9
+ "react-native": "*",
10
+ "prop-types": "15.7.2"
13
11
  },
14
- "devDependencies": {},
15
- "license": "MoMo"
12
+ "devDependencies": {
13
+ "@momo-platform/versions": "4.1.11"
14
+ },
15
+ "license": "MoMo",
16
+ "dependencies": {}
16
17
  }
package/publish.sh CHANGED
@@ -25,5 +25,4 @@ npm publish --tag beta --access=public
25
25
  cd ..
26
26
  rm -rf dist
27
27
 
28
-
29
- ##curl -X POST -H 'Content-Type: application/json' 'https://chat.googleapis.com/v1/spaces/AAAAbP8987c/messages?key=AIzaSyDdI0hCZtE6vySjMm-WEfRq3CPzqKqqsHI&token=UGSFRvk_oYb9uGsAgs31bVvMm6jDkmD8zihGm3eyaQA%3D&threadKey=JoaXTEYaNNkl' -d '{"text": "@momo-kits/auto-complete new version release: '*"$VERSION"*' https://www.npmjs.com/package/@momo-kits/auto-complete"}'
28
+ ##curl -X POST -H 'Content-Type: application/json' 'https://chat.googleapis.com/v1/spaces/AAAAbP8987c/messages?key=AIzaSyDdI0hCZtE6vySjMm-WEfRq3CPzqKqqsHI&token=UGSFRvk_oYb9uGsAgs31bVvMm6jDkmD8zihGm3eyaQA%3D&threadKey=JoaXTEYaNNkl' -d '{"text": "@momo-kits/bank new version release: '*"$VERSION"*' `https://www.npmjs.com/package/@momo-kits/auto-complete`"}'
package/styles.ts ADDED
@@ -0,0 +1,15 @@
1
+ import {StyleSheet} from 'react-native';
2
+ import {Radius, Spacing} from '@momo-kits/foundation';
3
+
4
+ export default StyleSheet.create({
5
+ flatList: {
6
+ backgroundColor: 'white',
7
+ borderRadius: Radius.S,
8
+ paddingHorizontal: Spacing.M,
9
+ },
10
+ item: {
11
+ paddingVertical: Spacing.M,
12
+ flexDirection: 'row',
13
+ justifyContent: 'space-between',
14
+ },
15
+ });
package/types.ts ADDED
@@ -0,0 +1,60 @@
1
+ import {ReactElement} from 'react';
2
+
3
+ /**
4
+ * Represents a suggested item in the autocomplete list. Each item has a title for display
5
+ * and a value that represents the underlying data.
6
+ */
7
+ export type SuggestItem = {
8
+ title: string; // The display text for a suggestion item.
9
+ value: string; // The underlying value or identifier for this item.
10
+ };
11
+
12
+ /**
13
+ * Props for configuring the AutoComplete component.
14
+ */
15
+ export type AutoCompleteProps = {
16
+ /**
17
+ * An array of strings or `SuggestItem` objects that will be used to create the
18
+ * autocomplete dropdown list.
19
+ */
20
+ data: Array<string | SuggestItem>;
21
+
22
+ /**
23
+ * The current text in the autocomplete input field. This is used to determine
24
+ * which suggestions to display.
25
+ */
26
+ query: string;
27
+
28
+ /**
29
+ * Optional. The maximum number of items to display in the dropdown list. If not
30
+ * provided, all matching items will be shown.
31
+ */
32
+ maxItemShow?: number;
33
+
34
+ /**
35
+ * Optional. A function to render the UI of each item in the dropdown list.
36
+ * This is useful for adding custom styling or formatting to the displayed items.
37
+ */
38
+ renderItem?: (data: string) => ReactElement;
39
+
40
+ /**
41
+ * A function that is called when a user selects an item from the autocomplete list.
42
+ * It receives two arguments: the display text of the selected item and its corresponding value.
43
+ */
44
+ onPressItem?: (item: string, value: null | string | undefined) => void;
45
+
46
+ /**
47
+ * Indicates whether the 'title' or 'value' of a `SuggestItem` should be used for
48
+ * searching. This affects which items are filtered into the dropdown list as the
49
+ * user types in the input field.
50
+ */
51
+ searchKey: 'title' | 'value';
52
+ };
53
+
54
+ /**
55
+ * Props for representing individual items in the autocomplete suggestions list.
56
+ */
57
+ export type ListItemProps = {
58
+ item: string; // The content or data of the list item, to be rendered in the list.
59
+ index: number; // Represents the position of this item within the list.
60
+ };
package/AutoComplete.js DELETED
@@ -1,395 +0,0 @@
1
- /* eslint-disable no-extra-boolean-cast */
2
- /* eslint-disable no-param-reassign */
3
- import React, { Component } from 'react';
4
- import {
5
- findNodeHandle,
6
- StyleSheet,
7
- UIManager,
8
- View,
9
- Platform,
10
- } from 'react-native';
11
- import { debounce, get } from 'lodash';
12
- import PropTypes from 'prop-types';
13
- import {
14
- ValueUtil,
15
- NumberUtils,
16
- Colors,
17
- Text,
18
- RNGestureHandler,
19
- } from '@momo-kits/core';
20
-
21
- const { TouchableOpacity } = RNGestureHandler;
22
- export default class AutoComplete extends Component {
23
- constructor(props) {
24
- super(props);
25
- this.hashmapRefs = {};
26
- this.hashmapPosition = {};
27
- this.hashmapInputValue = {};
28
- this.selectedItem = null;
29
- this.childrenWithProps = null;
30
- }
31
-
32
- /**
33
- * func measure all component have keyAutoComplete to get it's position
34
- */
35
- measure() {
36
- try {
37
- Object.keys(this.hashmapRefs)?.forEach((key) => {
38
- const node = findNodeHandle(this.hashmapRefs[key]);
39
- if (node) {
40
- if (Platform.OS === 'android') {
41
- UIManager.measureLayoutRelativeToParent(
42
- node,
43
- (e) => {
44
- console.error(e);
45
- },
46
- (x, y, width, height) => {
47
- this.hashmapPosition[key] = {
48
- x,
49
- y: y + height,
50
- width,
51
- };
52
- },
53
- );
54
- } else {
55
- UIManager.measure(node, (x, y, width, height) => {
56
- this.hashmapPosition[key] = {
57
- x,
58
- y: y + height,
59
- width,
60
- };
61
- });
62
- }
63
- }
64
- });
65
- } catch (e) {
66
- console.log(`try catch :: ${e}`);
67
- }
68
- }
69
-
70
- componentDidMount() {
71
- // setTimeout to fix async
72
- setTimeout(() => {
73
- this.measure();
74
- }, 500);
75
- }
76
-
77
- getValueByKey = (key, value) => {
78
- const splitKey = key.split('-');
79
- return splitKey.length > 1
80
- ? splitKey.reduce(
81
- (result, item, index) =>
82
- (result =
83
- result +
84
- value[item] +
85
- (index === splitKey.length - 1
86
- ? ''
87
- : !!value[item]
88
- ? ' '
89
- : '')),
90
- '',
91
- )
92
- : key === 'phone'
93
- ? NumberUtils.formatPhoneNumberVN(value[key])
94
- : value[key];
95
- };
96
-
97
- /**
98
- * loop all child components
99
- * @param {"Children"} components ;
100
- */
101
- cloneChildren(components) {
102
- if (components) {
103
- return React.Children.map(components, (child) => {
104
- if (!child?.props) return child;
105
- if (
106
- child?.props?.children &&
107
- React.Children.count(child?.props?.children) > 0 &&
108
- child.type.name !== Text
109
- ) {
110
- // component have children -> clone it and all it's children
111
- return React.cloneElement(child, {
112
- children: this.cloneChildren(child.props.children),
113
- });
114
- }
115
-
116
- const { onChangeText, keyAutoComplete, onFocus, onEndEditing } =
117
- child.props;
118
- if (keyAutoComplete) {
119
- // Update props when component have keyAutoComplete
120
- if (this.selectedItem) {
121
- this.hashmapInputValue[keyAutoComplete] =
122
- this.getValueByKey(
123
- keyAutoComplete,
124
- this.selectedItem,
125
- ); // this.selectedItem[keyAutoComplete];
126
- return React.cloneElement(child, {
127
- ref: (view) =>
128
- (this.hashmapRefs[keyAutoComplete] = view),
129
- onChangeText: (text) =>
130
- this.changeTextHandle(
131
- child,
132
- text,
133
- onChangeText,
134
- ),
135
- onFocus: (e) => this.focusHandle(e, child, onFocus),
136
- // value: this.getValueByKey(keyAutoComplete, this.selectedItem),
137
- onEndEditing: (e) =>
138
- this.endFocusHandle(e, onEndEditing),
139
- });
140
- }
141
- return React.cloneElement(child, {
142
- ref: (view) =>
143
- (this.hashmapRefs[keyAutoComplete] = view),
144
- onChangeText: (text) =>
145
- this.changeTextHandle(child, text, onChangeText),
146
- onFocus: (e) => this.focusHandle(e, child, onFocus),
147
- onEndEditing: (e) =>
148
- this.endFocusHandle(e, onEndEditing),
149
- });
150
- }
151
- return child;
152
- });
153
- }
154
-
155
- return components;
156
- }
157
-
158
- render() {
159
- const { style = {} } = this.props;
160
- const suggest = get(this.state, 'suggest', {});
161
- const childrenWithProps = this.cloneChildren(
162
- get(this.props, 'children', null),
163
- );
164
- return (
165
- <View style={[{ zIndex: 1 }, style]}>
166
- {childrenWithProps}
167
- {this.renderSuggest(suggest)}
168
- </View>
169
- );
170
- }
171
-
172
- querySearch = (child, text) => {
173
- const keyAutoComplete = get(child.props, 'keyAutoComplete', '');
174
- const isShowAutoComplete = get(child.props, 'isShowAutoComplete', true);
175
- const { data } = this.props;
176
- const dataOutput =
177
- data && data.length > 0 && this.filter(data, keyAutoComplete, text);
178
- if (this.hashmapRefs[keyAutoComplete]) {
179
- this.setState({
180
- suggest: {
181
- data: dataOutput,
182
- position: this.hashmapPosition[keyAutoComplete],
183
- isShowAutoComplete,
184
- },
185
- });
186
- }
187
- };
188
-
189
- changeTextHandle = (child, text, onChangeText) => {
190
- this.querySearch(child, text);
191
- if (onChangeText && typeof onChangeText === 'function') {
192
- onChangeText(text);
193
- }
194
- };
195
-
196
- focusHandle = (e, child, onFocus) => {
197
- let text = '';
198
- if (child && typeof child.getText === 'function') {
199
- text = child.getText();
200
- }
201
-
202
- this.querySearch(child, text);
203
-
204
- if (onFocus && typeof onFocus === 'function') {
205
- onFocus(e);
206
- }
207
- };
208
-
209
- endFocusHandle = (e, onEndEditing) => {
210
- if (this.isShowingSuggest()) {
211
- this.hideSuggest();
212
- }
213
- if (onEndEditing && typeof onEndEditing === 'function') {
214
- onEndEditing(e);
215
- }
216
- };
217
-
218
- filter = (data, key, query) => {
219
- if (!data || !key) {
220
- return null;
221
- }
222
- if (query === '') {
223
- return data;
224
- }
225
-
226
- return data.filter((item) => {
227
- const valueStr = item ? this.getValueByKey(key, item) : '';
228
- const valueStrFormated = ValueUtil.removeAlias(valueStr)
229
- .toLowerCase()
230
- .trim()
231
- .replace(/\s/g, '');
232
- const queryFormated = ValueUtil.removeAlias(query)
233
- .toLowerCase()
234
- .trim()
235
- .replace(/\s/g, '');
236
- return valueStrFormated.indexOf(queryFormated) !== -1;
237
- });
238
- };
239
-
240
- renderSuggest(suggest) {
241
- const { numSuggest } = this.props;
242
- if (
243
- suggest &&
244
- suggest.data &&
245
- suggest.data.length > 0 &&
246
- suggest.isShowAutoComplete
247
- ) {
248
- const { x = 0, y = 0, width = 0 } = suggest.position || {};
249
- const sliceData = suggest.data.slice(0, numSuggest);
250
-
251
- return (
252
- <View
253
- style={[
254
- styles.containerSuggest,
255
- {
256
- left: x,
257
- top: y,
258
- width,
259
- },
260
- ]}>
261
- {sliceData.map((item, index) => (
262
- <View key={index.toString()}>
263
- {this.renderItem({ item, index })}
264
- {index !== sliceData.length - 1 &&
265
- this.renderSeparator()}
266
- </View>
267
- ))}
268
- </View>
269
- );
270
- }
271
- return null;
272
- }
273
-
274
- renderItem = ({ item, index }) => {
275
- const { renderSuggestItem } = this.props;
276
- return (
277
- <TouchableOpacity onPress={() => this.onPressItemSuggest(item)}>
278
- {renderSuggestItem && typeof renderSuggestItem === 'function'
279
- ? renderSuggestItem({ item, index })
280
- : this.renderSuggestItemDefault({ item, index })}
281
- </TouchableOpacity>
282
- );
283
- };
284
-
285
- renderSeparator = () => <View style={styles.separator} />;
286
-
287
- renderSuggestItemDefault = ({ item }) => {
288
- const { title, value } = item;
289
- return (
290
- <View style={[styles.viewSuggest]}>
291
- <Text.Title>{title}</Text.Title>
292
- <Text.Title>{value}</Text.Title>
293
- </View>
294
- );
295
- };
296
-
297
- onPressItemSuggest = (item) => {
298
- this.selectedItem = item;
299
- // Loop hashRef to update value of child
300
- Object.keys(this.hashmapRefs).forEach((key) => {
301
- if (
302
- this.hashmapRefs[key].setText &&
303
- typeof this.hashmapRefs[key].setText === 'function'
304
- )
305
- this.hashmapRefs[key].setText(
306
- this.getValueByKey(key, this.selectedItem),
307
- );
308
- if (
309
- this.hashmapRefs[key].setValue &&
310
- typeof this.hashmapRefs[key].setValue === 'function'
311
- )
312
- this.hashmapRefs[key].setValue(
313
- this.getValueByKey(key, this.selectedItem),
314
- );
315
- });
316
-
317
- const { onSelected } = this.props;
318
- this.setState(
319
- {
320
- suggest: {},
321
- },
322
- () => {
323
- if (onSelected && typeof onSelected === 'function') {
324
- onSelected(item);
325
- }
326
- this.selectedItem = null;
327
- },
328
- );
329
- };
330
-
331
- isShowingSuggest = () => {
332
- const { suggest } = this.state;
333
- return suggest && suggest.data && suggest.data.length > 0;
334
- };
335
-
336
- hideSuggest = () => {
337
- this.setState(
338
- {
339
- suggest: {},
340
- },
341
- () => {
342
- this.selectedItem = null;
343
- },
344
- );
345
- };
346
- }
347
-
348
- const styles = StyleSheet.create({
349
- viewSuggest: {
350
- flexDirection: 'row',
351
- justifyContent: 'space-between',
352
- alignItems: 'center',
353
- paddingVertical: 5,
354
- },
355
- containerSuggest: {
356
- backgroundColor: 'white',
357
- paddingVertical: 10,
358
- paddingHorizontal: 12,
359
- position: 'absolute',
360
- maxHeight: 240,
361
- borderColor: '#DADADA',
362
- borderRadius: 4,
363
- borderWidth: 1,
364
- shadowColor: '#000000',
365
- shadowOffset: {
366
- width: 0,
367
- height: 1,
368
- },
369
- shadowRadius: 1,
370
- elevation: 2,
371
- shadowOpacity: 0.5,
372
- },
373
- separator: {
374
- height: 1,
375
- width: '100%',
376
- backgroundColor: Colors.placeholder,
377
- marginVertical: 10,
378
- },
379
- });
380
-
381
- AutoComplete.propTypes = {
382
- style: PropTypes.oneOfType([
383
- PropTypes.array,
384
- PropTypes.object,
385
- PropTypes.number,
386
- ]),
387
- data: PropTypes.arrayOf(PropTypes.object).isRequired,
388
- renderSuggestItem: PropTypes.func,
389
- onSelected: PropTypes.func.isRequired,
390
- numSuggest: PropTypes.number,
391
- };
392
-
393
- AutoComplete.defaultProps = {
394
- numSuggest: 2,
395
- };
package/index.js DELETED
@@ -1,5 +0,0 @@
1
- import AutoComplete from './AutoComplete';
2
-
3
- export {
4
- AutoComplete
5
- };