@momo-kits/auto-complete 0.75.1-beta.8 → 0.77.2-beta.3

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, ListItemProps, SuggestItem} from './types';
5
+ import styles from './styles';
6
+
7
+ const AutoComplete: FC<AutoCompleteProps> = ({
8
+ data = [],
9
+ query = '',
10
+ onPressItem,
11
+ maxItemShow = 5,
12
+ }) => {
13
+ const [filteredData, setFilteredData] = useState<(string | SuggestItem)[]>(
14
+ [],
15
+ );
16
+ const [haveSelected, setHaveSelected] = useState(false);
17
+
18
+ useEffect(() => {
19
+ setHaveSelected(false);
20
+ onSearch(query);
21
+ return () => {};
22
+ }, [query]);
23
+
24
+ const removeDiacritics = (str: string) => {
25
+ return str.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
26
+ };
27
+
28
+ const sanitizeInput = (str: string) => {
29
+ return str.replace(/[\\/*+?().{}[\]^$|]/g, '');
30
+ };
31
+
32
+ const onSearch = (query: string) => {
33
+ const sanitizedText = sanitizeInput(query);
34
+ const normalizedText = removeDiacritics(sanitizedText.trim().toLowerCase());
35
+ const regex = new RegExp(normalizedText, 'i');
36
+
37
+ const filtered: (string | SuggestItem)[] = data
38
+ .filter((item: string | SuggestItem) => {
39
+ if (typeof item === 'string') {
40
+ return regex.test(removeDiacritics(item.toLowerCase()));
41
+ } else {
42
+ return regex.test(removeDiacritics(item.title.toLowerCase()));
43
+ }
44
+ })
45
+ .slice(0, maxItemShow);
46
+ setFilteredData(filtered);
47
+ };
48
+
49
+ const onSelected = (title: string, value: null | string | undefined) => {
50
+ setHaveSelected(true);
51
+ onPressItem?.(title, value);
52
+ };
53
+
54
+ const _renderItem = ({item}: {item: string | SuggestItem}) => {
55
+ const title = typeof item === 'string' ? item : item.title;
56
+ const value = typeof item === 'string' ? null : item.value;
57
+
58
+ return (
59
+ <TouchableOpacity
60
+ onPress={() => onSelected(title, value)}
61
+ style={styles.item}>
62
+ <Text numberOfLines={1} typography={'description_default'}>
63
+ {title}
64
+ </Text>
65
+ {!!value && (
66
+ <Text numberOfLines={1} typography={'description_default'}>
67
+ {value}
68
+ </Text>
69
+ )}
70
+ </TouchableOpacity>
71
+ );
72
+ };
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 default AutoComplete;
package/package.json CHANGED
@@ -1,16 +1,15 @@
1
1
  {
2
2
  "name": "@momo-kits/auto-complete",
3
- "version": "0.75.1-beta.8",
3
+ "version": "0.77.2-beta.03",
4
4
  "private": false,
5
- "main": "index.js",
6
- "dependencies": {},
5
+ "main": "index.tsx",
7
6
  "peerDependencies": {
7
+ "@momo-kits/foundation": "latest",
8
8
  "react": "16.9.0",
9
9
  "react-native": ">=0.55",
10
- "@momo-kits/core": ">=0.0.4-beta",
11
- "lodash": "^4.17.15",
12
10
  "prop-types": "^15.7.2"
13
11
  },
14
12
  "devDependencies": {},
15
- "license": "MoMo"
13
+ "license": "MoMo",
14
+ "dependencies": {}
16
15
  }
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,16 @@
1
+ import {ReactElement} from 'react';
2
+
3
+ export type SuggestItem = {
4
+ title: string;
5
+ value?: string;
6
+ };
7
+
8
+ export type AutoCompleteProps = {
9
+ data: Array<string | SuggestItem>;
10
+ query: string;
11
+ maxItemShow?: number;
12
+ renderItem?: (data: string) => ReactElement;
13
+ onPressItem?: (item: string, value: null | string | undefined) => void;
14
+ };
15
+
16
+ export type ListItemProps = {item: string; index: number};
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
- };