@map-colonies/react-components 3.7.3 → 3.7.4
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/CHANGELOG.md +8 -0
- package/dist/autocomplete/autocomplete.css +25 -0
- package/dist/autocomplete/autocomplete.d.ts +34 -0
- package/dist/autocomplete/autocomplete.d.ts.map +1 -0
- package/dist/autocomplete/autocomplete.js +467 -0
- package/dist/autocomplete/autocomplete.js.map +1 -0
- package/dist/autocomplete/index.d.ts +2 -0
- package/dist/autocomplete/index.d.ts.map +1 -0
- package/dist/autocomplete/index.js +5 -0
- package/dist/autocomplete/index.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +5 -2
- package/src/lib/autocomplete/autocomplete.css +25 -0
- package/src/lib/autocomplete/autocomplete.stories.tsx +101 -0
- package/src/lib/autocomplete/autocomplete.tsx +683 -0
- package/src/lib/autocomplete/get-input-selection.d.ts +1 -0
- package/src/lib/autocomplete/index.ts +1 -0
- package/src/lib/index.ts +1 -0
|
@@ -0,0 +1,683 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
import React, {
|
|
3
|
+
useState,
|
|
4
|
+
useRef,
|
|
5
|
+
useEffect,
|
|
6
|
+
ChangeEvent,
|
|
7
|
+
KeyboardEvent,
|
|
8
|
+
} from 'react';
|
|
9
|
+
import getCaretCoordinates from 'textarea-caret';
|
|
10
|
+
import getInputSelection, { setCaretPosition } from 'get-input-selection';
|
|
11
|
+
|
|
12
|
+
import './autocomplete.css';
|
|
13
|
+
|
|
14
|
+
const KEY_UP = 38;
|
|
15
|
+
const KEY_DOWN = 40;
|
|
16
|
+
const KEY_RETURN = 13;
|
|
17
|
+
const KEY_ENTER = 14;
|
|
18
|
+
const KEY_ESCAPE = 27;
|
|
19
|
+
const KEY_TAB = 9;
|
|
20
|
+
|
|
21
|
+
const OPTION_LIST_Y_OFFSET = 10;
|
|
22
|
+
const OPTION_LIST_MIN_WIDTH = 100;
|
|
23
|
+
|
|
24
|
+
export interface IAutocompleteProps {
|
|
25
|
+
Component: React.ReactElement;
|
|
26
|
+
ComponentProps: Record<string, unknown>;
|
|
27
|
+
defaultValue: string;
|
|
28
|
+
disabled: boolean;
|
|
29
|
+
maxOptions: number;
|
|
30
|
+
onBlur: () => void;
|
|
31
|
+
onChange: (val: string) => void;
|
|
32
|
+
onKeyDown: (evt: any) => void;
|
|
33
|
+
onRequestOptions: (val: string) => void;
|
|
34
|
+
onSelect: (val: string) => void;
|
|
35
|
+
changeOnSelect: (trigger: string, slug: string) => string;
|
|
36
|
+
options: string[];
|
|
37
|
+
regex: string;
|
|
38
|
+
matchAny: boolean;
|
|
39
|
+
minChars: number;
|
|
40
|
+
requestOnlyIfNoOptions: boolean;
|
|
41
|
+
spaceRemovers: string[];
|
|
42
|
+
spacer: string;
|
|
43
|
+
trigger: string | string[];
|
|
44
|
+
value: string | undefined;
|
|
45
|
+
offsetX: number;
|
|
46
|
+
offsetY: number;
|
|
47
|
+
passThroughEnter: boolean;
|
|
48
|
+
mode: 'autocomplete' | 'assist';
|
|
49
|
+
direction: 'ltr' | 'rtl';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const Autocomplete: React.FC<IAutocompleteProps> = (props) => {
|
|
53
|
+
const [recentValue, setRecentValue] = useState(props.defaultValue);
|
|
54
|
+
const [enableSpaceRemovers, setEnableSpaceRemovers] = useState(false);
|
|
55
|
+
|
|
56
|
+
const [state, setState] = useState({
|
|
57
|
+
helperVisible: false,
|
|
58
|
+
left: 0,
|
|
59
|
+
right: 0,
|
|
60
|
+
trigger: '',
|
|
61
|
+
matchLength: 0,
|
|
62
|
+
matchStart: 0,
|
|
63
|
+
options: [],
|
|
64
|
+
selection: 0,
|
|
65
|
+
top: 0,
|
|
66
|
+
value: '',
|
|
67
|
+
caret: 0,
|
|
68
|
+
width: 'unset',
|
|
69
|
+
});
|
|
70
|
+
const refInput = useRef(null);
|
|
71
|
+
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
window.addEventListener('resize', handleResize);
|
|
74
|
+
return (): void => {
|
|
75
|
+
try {
|
|
76
|
+
window.removeEventListener('resize', handleResize);
|
|
77
|
+
} catch (e) {
|
|
78
|
+
console.log('WINDOW "resize" remove listener failed', e);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}, []);
|
|
82
|
+
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
const { options } = props;
|
|
85
|
+
const { caret } = state;
|
|
86
|
+
|
|
87
|
+
// if (options.length !== prevProps.options.length) {
|
|
88
|
+
updateHelper(recentValue, caret, options);
|
|
89
|
+
// }
|
|
90
|
+
}, [props]);
|
|
91
|
+
|
|
92
|
+
const getMatch = (
|
|
93
|
+
str: string,
|
|
94
|
+
caret: number,
|
|
95
|
+
providedOptions: string[]
|
|
96
|
+
): Record<string, unknown> => {
|
|
97
|
+
const { trigger, matchAny, regex } = props;
|
|
98
|
+
const re = new RegExp(regex);
|
|
99
|
+
|
|
100
|
+
let triggers = trigger;
|
|
101
|
+
if (!Array.isArray(triggers)) {
|
|
102
|
+
triggers = new Array(trigger) as string[];
|
|
103
|
+
}
|
|
104
|
+
triggers.sort();
|
|
105
|
+
|
|
106
|
+
const providedOptionsObject: Record<string, unknown> = {};
|
|
107
|
+
if (Array.isArray(providedOptions)) {
|
|
108
|
+
triggers.forEach((triggerStr) => {
|
|
109
|
+
providedOptionsObject[triggerStr] = providedOptions;
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const triggersMatch = arrayTriggerMatch(triggers, re);
|
|
114
|
+
let slugData: Record<string, unknown> = {};
|
|
115
|
+
|
|
116
|
+
for (
|
|
117
|
+
let triggersIndex = 0;
|
|
118
|
+
triggersIndex < triggersMatch.length;
|
|
119
|
+
triggersIndex++
|
|
120
|
+
) {
|
|
121
|
+
const { triggerStr, triggerMatch, triggerLength } = triggersMatch[
|
|
122
|
+
triggersIndex
|
|
123
|
+
];
|
|
124
|
+
|
|
125
|
+
for (let i = caret - 1; i >= 0; --i) {
|
|
126
|
+
const substr = str.substring(i, caret);
|
|
127
|
+
const match = substr.match(re);
|
|
128
|
+
let matchStart = -1;
|
|
129
|
+
|
|
130
|
+
if (triggerLength > 0) {
|
|
131
|
+
const triggerIdx = triggerMatch ? i : i - triggerLength + 1;
|
|
132
|
+
|
|
133
|
+
if (triggerIdx < 0) {
|
|
134
|
+
// out of input
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (isTrigger(triggerStr, str, triggerIdx)) {
|
|
139
|
+
matchStart = triggerIdx + triggerLength;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (!match && matchStart < 0) {
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
} else {
|
|
146
|
+
if (match && i > 0) {
|
|
147
|
+
// find first non-matching character or begin of input
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
matchStart = i === 0 && match ? 0 : i + 1;
|
|
151
|
+
|
|
152
|
+
if (caret - matchStart === 0) {
|
|
153
|
+
// matched slug is empty
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (matchStart >= 0) {
|
|
159
|
+
const triggerOptions = providedOptionsObject[triggerStr] as string[];
|
|
160
|
+
if (!Array.isArray(triggerOptions)) {
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const matchedSlug = str.substring(matchStart, caret);
|
|
165
|
+
|
|
166
|
+
const options = triggerOptions.filter((slug) => {
|
|
167
|
+
const idx = slug.toLowerCase().indexOf(matchedSlug.toLowerCase());
|
|
168
|
+
return idx !== -1 && (matchAny || idx === 0);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
const currTrigger = triggerStr;
|
|
172
|
+
const matchLength = matchedSlug.length;
|
|
173
|
+
|
|
174
|
+
if (slugData === {}) {
|
|
175
|
+
slugData = {
|
|
176
|
+
trigger: currTrigger,
|
|
177
|
+
matchStart,
|
|
178
|
+
matchLength,
|
|
179
|
+
options,
|
|
180
|
+
};
|
|
181
|
+
} else {
|
|
182
|
+
slugData = {
|
|
183
|
+
...slugData,
|
|
184
|
+
trigger: currTrigger,
|
|
185
|
+
matchStart,
|
|
186
|
+
matchLength,
|
|
187
|
+
options,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return slugData;
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const arrayTriggerMatch = (triggers: string[], re: RegExp) => {
|
|
198
|
+
const triggersMatch = triggers.map((trigger) => ({
|
|
199
|
+
triggerStr: trigger,
|
|
200
|
+
triggerMatch: trigger.match(re),
|
|
201
|
+
triggerLength: trigger.length,
|
|
202
|
+
}));
|
|
203
|
+
|
|
204
|
+
return triggersMatch;
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const isTrigger = (trigger: string, str: string, i: number): boolean => {
|
|
208
|
+
if (!trigger || !trigger.length) {
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (str.substr(i, trigger.length) === trigger) {
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return false;
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
|
220
|
+
const { onChange, options, spaceRemovers, spacer, value } = props;
|
|
221
|
+
|
|
222
|
+
const old = recentValue;
|
|
223
|
+
const str = e.target.value;
|
|
224
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|
225
|
+
const caret = (getInputSelection(getInputElement(e.target)) as {
|
|
226
|
+
start: number;
|
|
227
|
+
end: number;
|
|
228
|
+
}).end;
|
|
229
|
+
|
|
230
|
+
if (!str.length) {
|
|
231
|
+
updateState({
|
|
232
|
+
...state,
|
|
233
|
+
helperVisible: false,
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
setRecentValue(str);
|
|
238
|
+
|
|
239
|
+
updateState({
|
|
240
|
+
...state,
|
|
241
|
+
caret,
|
|
242
|
+
value: e.target.value,
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
if (!str.length || !caret) {
|
|
246
|
+
return onChange(e.target.value);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// '@wonderjenny ,|' -> '@wonderjenny, |'
|
|
250
|
+
if (
|
|
251
|
+
enableSpaceRemovers &&
|
|
252
|
+
spaceRemovers.length &&
|
|
253
|
+
str.length > 2 &&
|
|
254
|
+
spacer.length
|
|
255
|
+
) {
|
|
256
|
+
for (let i = 0; i < Math.max(old.length, str.length); ++i) {
|
|
257
|
+
if (old[i] !== str[i]) {
|
|
258
|
+
if (
|
|
259
|
+
i >= 2 &&
|
|
260
|
+
str[i - 1] === spacer &&
|
|
261
|
+
spaceRemovers.indexOf(str[i - 2]) === -1 &&
|
|
262
|
+
spaceRemovers.indexOf(str[i]) !== -1 &&
|
|
263
|
+
getMatch(str.substring(0, i - 2), caret - 3, options) !== {}
|
|
264
|
+
) {
|
|
265
|
+
const newValue = `${str.slice(0, i - 1)}${str.slice(
|
|
266
|
+
i,
|
|
267
|
+
i + 1
|
|
268
|
+
)}${str.slice(i - 1, i)}${str.slice(i + 1)}`;
|
|
269
|
+
|
|
270
|
+
updateCaretPosition(i + 1);
|
|
271
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
272
|
+
if (refInput.current !== null) {
|
|
273
|
+
((refInput.current as unknown) as Record<
|
|
274
|
+
string,
|
|
275
|
+
unknown
|
|
276
|
+
>).value = newValue;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (!value) {
|
|
280
|
+
updateState({
|
|
281
|
+
...state,
|
|
282
|
+
value: newValue,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return onChange(newValue);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
break;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
setEnableSpaceRemovers(false);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
updateHelper(str, caret, options);
|
|
297
|
+
|
|
298
|
+
// if (!value) {
|
|
299
|
+
// updateState({
|
|
300
|
+
// ...state,
|
|
301
|
+
// value: e.target.value
|
|
302
|
+
// });
|
|
303
|
+
// }
|
|
304
|
+
|
|
305
|
+
return onChange(e.target.value);
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
const getInputElement = (
|
|
309
|
+
parent: HTMLElement | undefined
|
|
310
|
+
): HTMLElement | undefined => {
|
|
311
|
+
if (parent !== undefined) {
|
|
312
|
+
if (parent.children.length > 0) {
|
|
313
|
+
const innerTextAreas = parent.getElementsByTagName('textarea');
|
|
314
|
+
if (innerTextAreas.length > 0) {
|
|
315
|
+
return innerTextAreas[0];
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const innerInputs = parent.getElementsByTagName('input');
|
|
319
|
+
if (innerInputs.length > 0) {
|
|
320
|
+
return innerInputs[0];
|
|
321
|
+
}
|
|
322
|
+
} else {
|
|
323
|
+
return parent;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return undefined;
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
331
|
+
const { helperVisible, options, selection } = state;
|
|
332
|
+
const { onKeyDown, passThroughEnter } = props;
|
|
333
|
+
|
|
334
|
+
if (helperVisible) {
|
|
335
|
+
switch (event.keyCode) {
|
|
336
|
+
case KEY_ESCAPE:
|
|
337
|
+
event.preventDefault();
|
|
338
|
+
resetHelper();
|
|
339
|
+
break;
|
|
340
|
+
case KEY_UP:
|
|
341
|
+
event.preventDefault();
|
|
342
|
+
updateState({
|
|
343
|
+
...state,
|
|
344
|
+
selection: (options.length + selection - 1) % options.length,
|
|
345
|
+
});
|
|
346
|
+
break;
|
|
347
|
+
case KEY_DOWN:
|
|
348
|
+
event.preventDefault();
|
|
349
|
+
updateState({
|
|
350
|
+
...state,
|
|
351
|
+
selection: (selection + 1) % options.length,
|
|
352
|
+
});
|
|
353
|
+
break;
|
|
354
|
+
case KEY_ENTER:
|
|
355
|
+
case KEY_RETURN:
|
|
356
|
+
if (!passThroughEnter) {
|
|
357
|
+
event.preventDefault();
|
|
358
|
+
}
|
|
359
|
+
handleSelection(selection);
|
|
360
|
+
break;
|
|
361
|
+
case KEY_TAB:
|
|
362
|
+
handleSelection(selection);
|
|
363
|
+
break;
|
|
364
|
+
default:
|
|
365
|
+
onKeyDown(event);
|
|
366
|
+
break;
|
|
367
|
+
}
|
|
368
|
+
} else {
|
|
369
|
+
onKeyDown(event);
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
const handleResize = () => {
|
|
374
|
+
updateState({
|
|
375
|
+
...state,
|
|
376
|
+
helperVisible: false,
|
|
377
|
+
});
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
const handleSelection = (idx: number) => {
|
|
381
|
+
const { spacer, onSelect, changeOnSelect } = props;
|
|
382
|
+
const { matchStart, matchLength, options, trigger } = state;
|
|
383
|
+
|
|
384
|
+
const slug = options[idx];
|
|
385
|
+
const value = recentValue;
|
|
386
|
+
const part1 = value.substring(0, matchStart - trigger.length);
|
|
387
|
+
const part2 = value.substring(matchStart + matchLength);
|
|
388
|
+
|
|
389
|
+
const event = { target: refInput.current };
|
|
390
|
+
const changedStr = changeOnSelect(trigger, slug);
|
|
391
|
+
|
|
392
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
393
|
+
if (event.target !== null) {
|
|
394
|
+
((event.target as unknown) as Record<
|
|
395
|
+
string,
|
|
396
|
+
unknown
|
|
397
|
+
>).value = `${part1}${changedStr}${spacer}${part2}`;
|
|
398
|
+
}
|
|
399
|
+
handleChange(event as any);
|
|
400
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
401
|
+
if (event.target !== null) {
|
|
402
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
403
|
+
onSelect((event.target as any).value);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
resetHelper();
|
|
407
|
+
|
|
408
|
+
updateCaretPosition(part1.length + changedStr.length + 1);
|
|
409
|
+
|
|
410
|
+
setEnableSpaceRemovers(true);
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
const updateCaretPosition = (caret: number) => {
|
|
414
|
+
updateState({
|
|
415
|
+
...state,
|
|
416
|
+
caret,
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
const input = getInputElement((refInput.current as unknown) as HTMLElement);
|
|
420
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|
421
|
+
setCaretPosition(input, caret);
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
const updateState = (val: any) => {
|
|
425
|
+
setTimeout(() => {
|
|
426
|
+
setState({
|
|
427
|
+
...val,
|
|
428
|
+
});
|
|
429
|
+
}, 0);
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
const updateHelper = (str: string, caret: number, options: string[]) => {
|
|
433
|
+
const parent = (refInput.current as unknown) as HTMLElement;
|
|
434
|
+
if (refInput.current === null) {
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const input = getInputElement(parent);
|
|
439
|
+
if (input === undefined) {
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
const parentRect = parent.getBoundingClientRect();
|
|
443
|
+
|
|
444
|
+
const slug = getMatch(str, caret, options) as any;
|
|
445
|
+
|
|
446
|
+
if (Object.keys(slug).length > 0 && input !== null) {
|
|
447
|
+
const caretPos = getCaretCoordinates(input, caret);
|
|
448
|
+
const rect = input.getBoundingClientRect();
|
|
449
|
+
const {
|
|
450
|
+
minChars,
|
|
451
|
+
onRequestOptions,
|
|
452
|
+
requestOnlyIfNoOptions,
|
|
453
|
+
mode,
|
|
454
|
+
} = props;
|
|
455
|
+
|
|
456
|
+
let top,
|
|
457
|
+
left,
|
|
458
|
+
right,
|
|
459
|
+
width = 'unset';
|
|
460
|
+
if (mode === 'assist') {
|
|
461
|
+
top =
|
|
462
|
+
parent === input
|
|
463
|
+
? caretPos.top + input.offsetTop
|
|
464
|
+
: caretPos.top + parentRect.top;
|
|
465
|
+
left = Math.min(
|
|
466
|
+
caretPos.left + input.offsetLeft - OPTION_LIST_Y_OFFSET,
|
|
467
|
+
input.offsetLeft + rect.width - OPTION_LIST_MIN_WIDTH
|
|
468
|
+
);
|
|
469
|
+
} else {
|
|
470
|
+
top = parentRect.top + parent.offsetHeight;
|
|
471
|
+
left = parent.offsetLeft;
|
|
472
|
+
right = parent.offsetLeft - parent.offsetWidth;
|
|
473
|
+
width = `${parentRect.width}px`;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
if (
|
|
477
|
+
slug.matchLength >= minChars &&
|
|
478
|
+
(slug.options.length > 1 ||
|
|
479
|
+
(slug.options.length === 1 &&
|
|
480
|
+
slug.options[0].length !== slug.matchLength))
|
|
481
|
+
) {
|
|
482
|
+
updateState({
|
|
483
|
+
...state,
|
|
484
|
+
value: str,
|
|
485
|
+
helperVisible: true,
|
|
486
|
+
top,
|
|
487
|
+
left,
|
|
488
|
+
right,
|
|
489
|
+
width,
|
|
490
|
+
...slug,
|
|
491
|
+
});
|
|
492
|
+
} else {
|
|
493
|
+
if (!requestOnlyIfNoOptions || !slug.options.length) {
|
|
494
|
+
onRequestOptions(str.substr(slug.matchStart, slug.matchLength));
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
resetHelper();
|
|
498
|
+
}
|
|
499
|
+
} else {
|
|
500
|
+
resetHelper();
|
|
501
|
+
}
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
const resetHelper = () => {
|
|
505
|
+
const timeDelay = 100;
|
|
506
|
+
setTimeout(() => {
|
|
507
|
+
updateState({
|
|
508
|
+
...state,
|
|
509
|
+
helperVisible: false,
|
|
510
|
+
selection: 0,
|
|
511
|
+
});
|
|
512
|
+
}, timeDelay);
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
const renderAutocompleteList = () => {
|
|
516
|
+
const {
|
|
517
|
+
helperVisible,
|
|
518
|
+
left,
|
|
519
|
+
right,
|
|
520
|
+
matchStart,
|
|
521
|
+
matchLength,
|
|
522
|
+
options,
|
|
523
|
+
selection,
|
|
524
|
+
top,
|
|
525
|
+
value,
|
|
526
|
+
width,
|
|
527
|
+
} = state;
|
|
528
|
+
|
|
529
|
+
if (!helperVisible) {
|
|
530
|
+
return null;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
const { maxOptions, offsetX, offsetY, direction } = props;
|
|
534
|
+
|
|
535
|
+
if (options.length === 0) {
|
|
536
|
+
return null;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
if (selection >= options.length) {
|
|
540
|
+
updateState({
|
|
541
|
+
...state,
|
|
542
|
+
selection: 0,
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
return null;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
const optionNumber = maxOptions === 0 ? options.length : maxOptions;
|
|
549
|
+
|
|
550
|
+
const helperOptions = options
|
|
551
|
+
.slice(0, optionNumber)
|
|
552
|
+
.map((val: string, idx) => {
|
|
553
|
+
const highlightStart = val
|
|
554
|
+
.toLowerCase()
|
|
555
|
+
.indexOf(value.substr(matchStart, matchLength).toLowerCase());
|
|
556
|
+
|
|
557
|
+
return (
|
|
558
|
+
<li
|
|
559
|
+
className={idx === selection ? 'active' : undefined}
|
|
560
|
+
key={val}
|
|
561
|
+
onClick={() => {
|
|
562
|
+
handleSelection(idx);
|
|
563
|
+
}}
|
|
564
|
+
onMouseEnter={() => {
|
|
565
|
+
updateState({
|
|
566
|
+
...state,
|
|
567
|
+
selection: idx,
|
|
568
|
+
});
|
|
569
|
+
}}
|
|
570
|
+
>
|
|
571
|
+
{val.slice(0, highlightStart)}
|
|
572
|
+
<strong>{val.substr(highlightStart, matchLength)}</strong>
|
|
573
|
+
{val.slice(highlightStart + matchLength)}
|
|
574
|
+
</li>
|
|
575
|
+
);
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
const horizontalPosition =
|
|
579
|
+
direction === 'ltr'
|
|
580
|
+
? { left: left + offsetX }
|
|
581
|
+
: { right: right + offsetX };
|
|
582
|
+
return (
|
|
583
|
+
<ul
|
|
584
|
+
className="react-autocomplete-input"
|
|
585
|
+
style={{ ...horizontalPosition, top: top + offsetY, width }}
|
|
586
|
+
>
|
|
587
|
+
{helperOptions}
|
|
588
|
+
</ul>
|
|
589
|
+
);
|
|
590
|
+
};
|
|
591
|
+
|
|
592
|
+
const {
|
|
593
|
+
Component,
|
|
594
|
+
ComponentProps,
|
|
595
|
+
defaultValue,
|
|
596
|
+
disabled,
|
|
597
|
+
onBlur,
|
|
598
|
+
value,
|
|
599
|
+
...rest
|
|
600
|
+
} = props;
|
|
601
|
+
|
|
602
|
+
const stateValue = recentValue; //state.value;
|
|
603
|
+
|
|
604
|
+
const propagated: any = Object.assign({}, rest);
|
|
605
|
+
Object.keys(props).forEach((k) => {
|
|
606
|
+
delete (propagated as Record<string, unknown>)[k];
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
let val = '';
|
|
610
|
+
|
|
611
|
+
if (stateValue) {
|
|
612
|
+
val = stateValue;
|
|
613
|
+
} else if (defaultValue) {
|
|
614
|
+
val = defaultValue;
|
|
615
|
+
} else if (value !== undefined) {
|
|
616
|
+
val = value;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
return (
|
|
620
|
+
<>
|
|
621
|
+
{React.cloneElement(Component, {
|
|
622
|
+
disabled: disabled,
|
|
623
|
+
onBlur: onBlur,
|
|
624
|
+
onChange: handleChange,
|
|
625
|
+
onKeyDown: handleKeyDown,
|
|
626
|
+
ref: refInput,
|
|
627
|
+
value: val,
|
|
628
|
+
...propagated,
|
|
629
|
+
...ComponentProps,
|
|
630
|
+
})}
|
|
631
|
+
{renderAutocompleteList()}
|
|
632
|
+
</>
|
|
633
|
+
);
|
|
634
|
+
};
|
|
635
|
+
|
|
636
|
+
const defaultProps: IAutocompleteProps = {
|
|
637
|
+
Component: <textarea />,
|
|
638
|
+
ComponentProps: {},
|
|
639
|
+
defaultValue: '',
|
|
640
|
+
disabled: false,
|
|
641
|
+
maxOptions: 6,
|
|
642
|
+
onBlur: () => {},
|
|
643
|
+
onChange: (val: string) => {},
|
|
644
|
+
onKeyDown: (evt: any) => {},
|
|
645
|
+
onRequestOptions: (val: string) => {},
|
|
646
|
+
onSelect: (val: string) => {},
|
|
647
|
+
changeOnSelect: (trigger: string, slug: string): string =>
|
|
648
|
+
`${trigger}${slug}`,
|
|
649
|
+
options: [],
|
|
650
|
+
regex: '^[A-Za-z0-9\\-_]+$',
|
|
651
|
+
matchAny: false,
|
|
652
|
+
minChars: 0,
|
|
653
|
+
requestOnlyIfNoOptions: true,
|
|
654
|
+
spaceRemovers: [',', '.', '!', '?'],
|
|
655
|
+
spacer: ' ',
|
|
656
|
+
trigger: '@',
|
|
657
|
+
offsetX: 0,
|
|
658
|
+
offsetY: 0,
|
|
659
|
+
value: undefined,
|
|
660
|
+
passThroughEnter: false,
|
|
661
|
+
mode: 'assist',
|
|
662
|
+
direction: 'ltr',
|
|
663
|
+
};
|
|
664
|
+
|
|
665
|
+
interface IAutocompleteOptionalProps extends Partial<IAutocompleteProps> {}
|
|
666
|
+
export const AutocompleteWithDefauls: React.FC<IAutocompleteOptionalProps> = (
|
|
667
|
+
props
|
|
668
|
+
) => {
|
|
669
|
+
let compProps = {
|
|
670
|
+
...defaultProps,
|
|
671
|
+
...props,
|
|
672
|
+
};
|
|
673
|
+
if (props.mode === 'autocomplete') {
|
|
674
|
+
compProps = {
|
|
675
|
+
...compProps,
|
|
676
|
+
trigger: '',
|
|
677
|
+
regex: '.',
|
|
678
|
+
spacer: '',
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
return <Autocomplete {...compProps} />;
|
|
682
|
+
};
|
|
683
|
+
export default AutocompleteWithDefauls;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
declare module 'get-input-selection';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { AutocompleteWithDefauls as Autocomplete } from './autocomplete';
|
package/src/lib/index.ts
CHANGED