@cloud-ru/uikit-product-mobile-fields 0.11.24

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.
Files changed (65) hide show
  1. package/CHANGELOG.md +1300 -0
  2. package/LICENSE +201 -0
  3. package/README.md +8 -0
  4. package/package.json +66 -0
  5. package/src/components/AdaptiveField/AdaptiveField.tsx +88 -0
  6. package/src/components/AdaptiveField/index.ts +1 -0
  7. package/src/components/MobileFieldDate/MobileFieldDate.tsx +375 -0
  8. package/src/components/MobileFieldDate/constants.ts +33 -0
  9. package/src/components/MobileFieldDate/index.ts +2 -0
  10. package/src/components/MobileFieldDate/styles.module.scss +75 -0
  11. package/src/components/MobileFieldDate/types.ts +6 -0
  12. package/src/components/MobileFieldDate/utils.ts +49 -0
  13. package/src/components/MobileFieldSelect/MobileFieldSelect.tsx +18 -0
  14. package/src/components/MobileFieldSelect/MobileFieldSelectMultiple.tsx +331 -0
  15. package/src/components/MobileFieldSelect/MobileFieldSelectSingle.tsx +300 -0
  16. package/src/components/MobileFieldSelect/hooks.tsx +195 -0
  17. package/src/components/MobileFieldSelect/index.ts +13 -0
  18. package/src/components/MobileFieldSelect/legacy/components/Items/hooks.tsx +53 -0
  19. package/src/components/MobileFieldSelect/legacy/components/index.ts +1 -0
  20. package/src/components/MobileFieldSelect/legacy/hooks.ts +38 -0
  21. package/src/components/MobileFieldSelect/legacy/index.ts +3 -0
  22. package/src/components/MobileFieldSelect/legacy/utils.ts +176 -0
  23. package/src/components/MobileFieldSelect/styles.module.scss +176 -0
  24. package/src/components/MobileFieldSelect/types.ts +156 -0
  25. package/src/components/MobileFieldSelect/utils/extractFieldDecoratorProps.ts +35 -0
  26. package/src/components/MobileFieldSelect/utils/extractListProps.ts +30 -0
  27. package/src/components/MobileFieldSelect/utils/getArrowIcon.ts +15 -0
  28. package/src/components/MobileFieldSelect/utils/index.ts +6 -0
  29. package/src/components/MobileFieldSelect/utils/options.tsx +88 -0
  30. package/src/components/MobileFieldSelect/utils/typeGuards.ts +38 -0
  31. package/src/components/MobileFieldSelect/utils/updateItems.ts +121 -0
  32. package/src/components/index.ts +3 -0
  33. package/src/constants/allFields.ts +11 -0
  34. package/src/constants/dateFields.ts +127 -0
  35. package/src/constants/index.ts +2 -0
  36. package/src/helperComponents/ButtonCopyValue/ButtonCopyValue.tsx +79 -0
  37. package/src/helperComponents/ButtonCopyValue/helpers.tsx +19 -0
  38. package/src/helperComponents/ButtonCopyValue/index.ts +1 -0
  39. package/src/helperComponents/ButtonCopyValue/styles.module.scss +5 -0
  40. package/src/helperComponents/FieldContainerPrivate/FieldContainerPrivate.tsx +79 -0
  41. package/src/helperComponents/FieldContainerPrivate/index.ts +1 -0
  42. package/src/helperComponents/FieldContainerPrivate/styles.module.scss +131 -0
  43. package/src/helperComponents/ItemContent/ItemContent.tsx +37 -0
  44. package/src/helperComponents/ItemContent/index.ts +1 -0
  45. package/src/helperComponents/ItemContent/styles.module.scss +80 -0
  46. package/src/helperComponents/index.ts +3 -0
  47. package/src/hooks/dateHandlers/index.ts +3 -0
  48. package/src/hooks/dateHandlers/useDateField.ts +275 -0
  49. package/src/hooks/dateHandlers/useDateFieldHelpersForMode.ts +145 -0
  50. package/src/hooks/dateHandlers/useFocusHandlers.ts +46 -0
  51. package/src/hooks/dateHandlers/useHandlers.ts +15 -0
  52. package/src/hooks/index.ts +5 -0
  53. package/src/hooks/styles.module.scss +17 -0
  54. package/src/hooks/useCopyButton.tsx +47 -0
  55. package/src/hooks/usePostfix.tsx +21 -0
  56. package/src/hooks/usePrefix.tsx +21 -0
  57. package/src/hooks/useValueControl.ts +15 -0
  58. package/src/index.ts +3 -0
  59. package/src/styles.module.scss +55 -0
  60. package/src/types/allFields.ts +9 -0
  61. package/src/types/dateFields.ts +14 -0
  62. package/src/types/index.ts +2 -0
  63. package/src/utils/adaptiveField.tsx +19 -0
  64. package/src/utils/dateFields.ts +75 -0
  65. package/src/utils/getValidationState.ts +6 -0
@@ -0,0 +1,331 @@
1
+ import cn from 'classnames';
2
+ import mergeRefs from 'merge-refs';
3
+ import {
4
+ FocusEvent,
5
+ forwardRef,
6
+ ForwardRefExoticComponent,
7
+ KeyboardEvent,
8
+ KeyboardEventHandler,
9
+ PropsWithoutRef,
10
+ RefAttributes,
11
+ useMemo,
12
+ useRef,
13
+ useState,
14
+ } from 'react';
15
+
16
+ import { useLocale } from '@cloud-ru/uikit-product-locale';
17
+ import { MobileModalCustom } from '@cloud-ru/uikit-product-mobile-modal';
18
+ import { ButtonFilled, ButtonFunction } from '@snack-uikit/button';
19
+ import { FieldDecorator } from '@snack-uikit/fields';
20
+ import { InputPrivate } from '@snack-uikit/input-private';
21
+ import { ItemId, kindFlattenItems, List, ListProps, SelectionSingleValueType } from '@snack-uikit/list';
22
+ import { Tag } from '@snack-uikit/tag';
23
+ import { extractSupportProps, isBrowser, useLayoutEffect, useValueControl } from '@snack-uikit/utils';
24
+
25
+ import { FieldContainerPrivate, ItemContent, ItemContentProps } from '../../helperComponents';
26
+ import { usePostfix, usePrefix } from '../../hooks';
27
+ import { getValidationState } from '../../utils/getValidationState';
28
+ import { useButtons, useHandleDeleteItem, useHandleOnKeyDown, useSearchInput } from './hooks';
29
+ import { useSearch } from './legacy';
30
+ import styles from './styles.module.scss';
31
+ import { ItemWithId, MobileFieldSelectMultipleProps, SelectedOptionFormatter } from './types';
32
+ import { extractFieldDecoratorProps, extractListProps, getArrowIcon, updateMultipleItems } from './utils';
33
+
34
+ const defaultSelectedOptionFormatter: SelectedOptionFormatter = item =>
35
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
36
+ // @ts-ignore
37
+ item?.content.option || '';
38
+
39
+ export const MobileFieldSelectMultiple: ForwardRefExoticComponent<
40
+ PropsWithoutRef<MobileFieldSelectMultipleProps> & RefAttributes<HTMLInputElement>
41
+ > = forwardRef<HTMLInputElement, MobileFieldSelectMultipleProps>(
42
+ (
43
+ {
44
+ id,
45
+ name,
46
+ placeholder,
47
+ size = 's',
48
+ options,
49
+ virtualized,
50
+ value: valueProp,
51
+ defaultValue,
52
+ onChange: onChangeProp,
53
+ disabled = false,
54
+ readonly = false,
55
+ searchable: searchableProp = true,
56
+ showClearButton = true,
57
+ onKeyDown: onInputKeyDownProp,
58
+ validationState = 'default',
59
+ search,
60
+ enableFuzzySearch = true,
61
+ autocomplete = false,
62
+ autoFocus,
63
+ prefixIcon,
64
+ prefix,
65
+ postfix,
66
+ removeByBackspace = false,
67
+ addOptionByEnter = false,
68
+ open: openProp,
69
+ onOpenChange,
70
+ selectedOptionFormatter = defaultSelectedOptionFormatter,
71
+ ...rest
72
+ },
73
+ ref,
74
+ ) => {
75
+ const { t } = useLocale('MobileFields');
76
+
77
+ const localRef = useRef<HTMLInputElement>(null);
78
+ const contentRef = useRef<HTMLDivElement>(null);
79
+
80
+ const [open = false, setOpen] = useValueControl<boolean>({ value: openProp, onChange: onOpenChange });
81
+
82
+ const [value, setValue] = useValueControl<SelectionSingleValueType[]>({
83
+ value: valueProp,
84
+ defaultValue,
85
+ onChange: onChangeProp,
86
+ });
87
+
88
+ const [{ selectedItems, items = [] }, setItems] = useState<{
89
+ selectedItems?: ItemWithId[];
90
+ items: ListProps['items'];
91
+ }>(() => updateMultipleItems({ options, value, currentItems: [], selectedItems: undefined }));
92
+
93
+ const { flattenItems } = useMemo(() => kindFlattenItems({ items }), [items]);
94
+
95
+ const searchable =
96
+ (searchableProp && Object.values(flattenItems).length > 5) || autocomplete || Boolean(addOptionByEnter);
97
+
98
+ const { inputValue, setInputValue, prevInputValue, updateInputValue } = useSearchInput({
99
+ ...search,
100
+ defaultValue: '',
101
+ selectedOptionFormatter,
102
+ });
103
+
104
+ const prefixSettings = usePrefix({ prefix, disabled });
105
+ const postfixSettings = usePostfix({ postfix, disabled });
106
+
107
+ useLayoutEffect(() => {
108
+ setItems(({ selectedItems }) => updateMultipleItems({ options, value, selectedItems }));
109
+ }, [options, value]);
110
+
111
+ const onClear = () => {
112
+ setValue(selectedItems?.filter(item => item.disabled).map(item => item.id) as ItemId[]);
113
+ localRef.current?.focus();
114
+
115
+ if (rest.required) {
116
+ setOpen(true);
117
+ }
118
+ };
119
+
120
+ const { ArrowIcon, arrowIconSize } = getArrowIcon({ size, open });
121
+
122
+ const { postfixButtons, inputKeyDownNavigationHandler, buttonsRefs } = useButtons({
123
+ readonly,
124
+ size,
125
+ showClearButton:
126
+ showClearButton && !disabled && !readonly && Boolean(selectedItems?.find(item => !item.disabled)),
127
+ showCopyButton: false,
128
+ inputRef: localRef,
129
+ onClear,
130
+ });
131
+
132
+ const commonHandleOnKeyDown = useHandleOnKeyDown({
133
+ inputKeyDownNavigationHandler,
134
+ onInputKeyDownProp,
135
+ setOpen,
136
+ });
137
+
138
+ const handleItemDelete = useHandleDeleteItem(setValue);
139
+ const handleOnKeyDown = (onKeyDown?: KeyboardEventHandler<HTMLElement>) => (e: KeyboardEvent<HTMLInputElement>) => {
140
+ if (removeByBackspace && e.code === 'Backspace' && inputValue === '') {
141
+ if (selectedItems?.length && !selectedItems.slice(-1)[0].disabled) {
142
+ handleItemDelete(selectedItems.pop() as ListProps['items'][0])();
143
+ }
144
+ }
145
+
146
+ if (e.code === 'Enter') {
147
+ e.stopPropagation();
148
+ e.preventDefault();
149
+ }
150
+
151
+ if (addOptionByEnter && e.code === 'Enter' && inputValue !== '') {
152
+ if (!(value ?? []).includes(inputValue)) {
153
+ setValue((value: SelectionSingleValueType[]) => (value ?? []).concat(inputValue));
154
+ updateInputValue();
155
+ }
156
+ }
157
+
158
+ if (!open && prevInputValue.current !== inputValue) {
159
+ setOpen(true);
160
+ }
161
+
162
+ commonHandleOnKeyDown(onKeyDown)(e);
163
+ };
164
+
165
+ const handleOpenChange = (open: boolean) => {
166
+ if (!readonly && !disabled && isBrowser() && !buttonsRefs.includes(document.activeElement)) {
167
+ setOpen(open);
168
+
169
+ if (!open) {
170
+ updateInputValue();
171
+ }
172
+ }
173
+ };
174
+
175
+ const handleApplyChange = () => {
176
+ onChangeProp?.(selectedItems?.map(item => item.id));
177
+ handleOpenChange(false);
178
+ setInputValue('');
179
+ };
180
+
181
+ const handleBlur = (e: FocusEvent<HTMLInputElement>) => {
182
+ if (!open && !buttonsRefs.filter(Boolean).includes(e.relatedTarget)) {
183
+ updateInputValue();
184
+
185
+ rest?.onBlur?.(e);
186
+ }
187
+ };
188
+
189
+ const searcher = useSearch(items, enableFuzzySearch);
190
+ const result = autocomplete || !searchable || prevInputValue.current === inputValue ? items : searcher(inputValue);
191
+
192
+ const fieldValidationState = getValidationState({ validationState, error: rest.error });
193
+
194
+ const listJsx = (
195
+ <div className={styles.listWrapper}>
196
+ <List
197
+ {...extractListProps(rest)}
198
+ items={result}
199
+ size='l'
200
+ scroll
201
+ virtualized={virtualized}
202
+ search={
203
+ searchable
204
+ ? {
205
+ value: inputValue,
206
+ onChange: setInputValue,
207
+ onKeyDown: handleOnKeyDown(),
208
+ }
209
+ : undefined
210
+ }
211
+ contentRender={({ content, ...rest }) => {
212
+ if (typeof content !== 'function') {
213
+ return <ItemContent {...(content as ItemContentProps)} {...rest} />;
214
+ }
215
+
216
+ return content;
217
+ }}
218
+ selection={{
219
+ mode: 'multiple',
220
+ value: value,
221
+ onChange: value => {
222
+ setValue(value);
223
+ },
224
+ }}
225
+ />
226
+ </div>
227
+ );
228
+
229
+ return (
230
+ <>
231
+ <FieldDecorator
232
+ {...extractSupportProps(rest)}
233
+ {...extractFieldDecoratorProps(rest)}
234
+ labelFor={id}
235
+ size={size}
236
+ validationState={fieldValidationState}
237
+ >
238
+ <FieldContainerPrivate
239
+ className={cn(styles.container, styles.tagContainer)}
240
+ validationState={fieldValidationState}
241
+ disabled={disabled}
242
+ readonly={readonly}
243
+ focused={open}
244
+ variant='single-line-container'
245
+ inputRef={localRef}
246
+ size={size}
247
+ prefix={
248
+ (prefixIcon || prefixSettings.show) && (
249
+ <>
250
+ {prefixIcon}
251
+ {prefixSettings.show && prefixSettings.render({ key: prefixSettings.id })}
252
+ </>
253
+ )
254
+ }
255
+ onClick={() => {
256
+ handleOpenChange(true);
257
+ }}
258
+ >
259
+ <>
260
+ <div className={styles.contentWrapper} ref={contentRef}>
261
+ {selectedItems &&
262
+ selectedItems.map(option => (
263
+ <Tag
264
+ size={size === 'l' ? 's' : 'xs'}
265
+ tabIndex={-1}
266
+ label={selectedOptionFormatter(option)}
267
+ key={option.id}
268
+ appearance={option.appearance ?? 'neutral'}
269
+ />
270
+ ))}
271
+
272
+ <div className={styles.inputWrapper}>
273
+ <InputPrivate
274
+ id={id}
275
+ name={name}
276
+ type='text'
277
+ disabled={disabled}
278
+ placeholder={!selectedItems || !selectedItems.length ? placeholder : undefined}
279
+ ref={mergeRefs(ref, localRef)}
280
+ onChange={undefined}
281
+ value=''
282
+ inputMode='none'
283
+ data-test-id='field-select__input'
284
+ onBlur={handleBlur}
285
+ className={styles.readonlyCursor}
286
+ autoFocus={autoFocus}
287
+ />
288
+ </div>
289
+ </div>
290
+
291
+ <div className={styles.postfix}>
292
+ {postfixButtons}
293
+ {postfixSettings.show && postfixSettings.render({ key: postfixSettings.id })}
294
+ <ArrowIcon size={arrowIconSize} className={styles.arrowIcon} />
295
+ </div>
296
+ </>
297
+ </FieldContainerPrivate>
298
+ </FieldDecorator>
299
+
300
+ <MobileModalCustom open={open} onClose={() => handleOpenChange(false)} size={searchable ? 'full' : 'auto'}>
301
+ {rest.label && <MobileModalCustom.Header title={rest.label} />}
302
+
303
+ {searchable ? listJsx : <MobileModalCustom.Body className={styles.bodyNoPadding} content={listJsx} />}
304
+
305
+ <MobileModalCustom.Footer
306
+ actions={
307
+ <div className={styles.footer}>
308
+ <div className={styles.footerTopLine}>
309
+ <span className={styles.counter}>{`${t('selectedN')}${selectedItems?.length || 0}`}</span>
310
+ <ButtonFunction
311
+ size='m'
312
+ label={t('resetAll')}
313
+ onClick={onClear}
314
+ data-test-id='field-select__reset-all-button'
315
+ />
316
+ </div>
317
+ <ButtonFilled
318
+ fullWidth
319
+ label={t('select')}
320
+ data-test-id='field-select__approve-button'
321
+ onClick={handleApplyChange}
322
+ size='l'
323
+ />
324
+ </div>
325
+ }
326
+ />
327
+ </MobileModalCustom>
328
+ </>
329
+ );
330
+ },
331
+ );
@@ -0,0 +1,300 @@
1
+ import mergeRefs from 'merge-refs';
2
+ import {
3
+ FocusEvent,
4
+ forwardRef,
5
+ ForwardRefExoticComponent,
6
+ KeyboardEvent,
7
+ KeyboardEventHandler,
8
+ PropsWithoutRef,
9
+ RefAttributes,
10
+ useCallback,
11
+ useEffect,
12
+ useMemo,
13
+ useRef,
14
+ useState,
15
+ } from 'react';
16
+
17
+ import { MobileModalCustom } from '@cloud-ru/uikit-product-mobile-modal';
18
+ import { FieldDecorator } from '@snack-uikit/fields';
19
+ import { InputPrivate } from '@snack-uikit/input-private';
20
+ import { kindFlattenItems, List, ListProps, SelectionSingleValueType } from '@snack-uikit/list';
21
+ import { extractSupportProps, isBrowser, useLayoutEffect, useValueControl } from '@snack-uikit/utils';
22
+
23
+ import { FieldContainerPrivate, ItemContent, ItemContentProps } from '../../helperComponents';
24
+ import { usePostfix, usePrefix } from '../../hooks';
25
+ import { getValidationState } from '../../utils/getValidationState';
26
+ import { useButtons, useHandleOnKeyDown, useSearchInput } from './hooks';
27
+ import { useSearch } from './legacy';
28
+ import styles from './styles.module.scss';
29
+ import { ItemWithId, MobileFieldSelectSingleProps, SelectedOptionFormatter } from './types';
30
+ import { extractFieldDecoratorProps, extractListProps, getArrowIcon, updateItems } from './utils';
31
+
32
+ const defaultSelectedOptionFormatter: SelectedOptionFormatter = item =>
33
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
34
+ // @ts-expect-error
35
+ item?.content.option || '';
36
+
37
+ export const MobileFieldSelectSingle: ForwardRefExoticComponent<
38
+ PropsWithoutRef<MobileFieldSelectSingleProps> & RefAttributes<HTMLInputElement>
39
+ > = forwardRef<HTMLInputElement, MobileFieldSelectSingleProps>(
40
+ (
41
+ {
42
+ id,
43
+ name,
44
+ placeholder,
45
+ size = 's',
46
+ options,
47
+ virtualized,
48
+ value: valueProp,
49
+ defaultValue,
50
+ onChange: onChangeProp,
51
+ disabled = false,
52
+ readonly = false,
53
+ searchable: searchableProp = true,
54
+ showCopyButton = true,
55
+ showClearButton = true,
56
+ onKeyDown: onInputKeyDownProp,
57
+ required = false,
58
+ validationState = 'default',
59
+ search,
60
+ enableFuzzySearch = true,
61
+ autocomplete = false,
62
+ autoFocus,
63
+ prefixIcon,
64
+ prefix,
65
+ postfix,
66
+ addOptionByEnter = false,
67
+ open: openProp,
68
+ onOpenChange,
69
+ selectedOptionFormatter = defaultSelectedOptionFormatter,
70
+ ...rest
71
+ },
72
+ ref,
73
+ ) => {
74
+ const localRef = useRef<HTMLInputElement>(null);
75
+
76
+ const [open = false, setOpen] = useValueControl<boolean>({ value: openProp, onChange: onOpenChange });
77
+ const [value, setValue] = useValueControl<SelectionSingleValueType>({
78
+ value: valueProp,
79
+ defaultValue,
80
+ onChange: onChangeProp,
81
+ });
82
+
83
+ const [{ selectedItem, items = [] }, setItems] = useState<{
84
+ selectedItem?: ItemWithId;
85
+ items: ListProps['items'];
86
+ }>(() => updateItems({ options, value, currentItems: [], selectedItem: undefined }));
87
+
88
+ const { inputValue, setInputValue, prevInputValue } = useSearchInput({
89
+ ...search,
90
+ selectedOptionFormatter,
91
+ });
92
+
93
+ const prevSelectedItem = useRef<ItemWithId | undefined>(selectedItem);
94
+
95
+ const prefixSettings = usePrefix({ prefix, disabled });
96
+ const postfixSettings = usePostfix({ postfix, disabled });
97
+
98
+ useLayoutEffect(() => {
99
+ setItems(({ selectedItem }) => updateItems({ options, value, selectedItem }));
100
+ }, [options, value]);
101
+
102
+ const { flattenItems } = useMemo(() => kindFlattenItems({ items }), [items]);
103
+
104
+ const searchable =
105
+ (searchableProp && Object.values(flattenItems).length > 5) || autocomplete || Boolean(addOptionByEnter);
106
+
107
+ useEffect(() => {
108
+ if (
109
+ prevSelectedItem.current &&
110
+ prevSelectedItem.current.id === selectedItem?.id &&
111
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
112
+ // @ts-expect-error
113
+ prevSelectedItem.current.content.option === selectedItem?.content.option
114
+ ) {
115
+ return;
116
+ }
117
+
118
+ prevSelectedItem.current = selectedItem;
119
+ setInputValue('');
120
+
121
+ // eslint-disable-next-line react-hooks/exhaustive-deps
122
+ }, [selectedItem, prevSelectedItem]);
123
+
124
+ const onClear = useCallback(() => {
125
+ setValue(undefined);
126
+ localRef.current?.focus();
127
+ if (required) {
128
+ setOpen(true);
129
+ }
130
+ }, [required, setOpen, setValue]);
131
+
132
+ const { ArrowIcon, arrowIconSize } = getArrowIcon({ size, open });
133
+
134
+ const { postfixButtons, inputKeyDownNavigationHandler, buttonsRefs } = useButtons({
135
+ readonly,
136
+ size,
137
+ showClearButton: showClearButton && !disabled && !readonly && value !== undefined,
138
+ showCopyButton,
139
+ inputRef: localRef,
140
+ onClear,
141
+ valueToCopy: selectedOptionFormatter(selectedItem),
142
+ });
143
+
144
+ const handleBlur = (e: FocusEvent<HTMLInputElement>) => {
145
+ if (!open && !buttonsRefs.filter(Boolean).includes(e.relatedTarget)) {
146
+ setInputValue('');
147
+
148
+ rest?.onBlur?.(e);
149
+ }
150
+ };
151
+
152
+ const commonHandleOnKeyDown = useHandleOnKeyDown({
153
+ inputKeyDownNavigationHandler,
154
+ onInputKeyDownProp,
155
+ setOpen,
156
+ });
157
+
158
+ const handleSelectionChange = useCallback(
159
+ (newValue?: SelectionSingleValueType) => {
160
+ localRef.current?.focus();
161
+ setOpen(false);
162
+ if (newValue !== undefined) {
163
+ setValue(newValue);
164
+ }
165
+ },
166
+ [setOpen, setValue],
167
+ );
168
+
169
+ const handleOnKeyDown = (onKeyDown?: KeyboardEventHandler<HTMLElement>) => (e: KeyboardEvent<HTMLInputElement>) => {
170
+ if (!open && prevInputValue.current !== inputValue) {
171
+ setOpen(true);
172
+ }
173
+
174
+ if (e.code === 'Enter') {
175
+ e.stopPropagation();
176
+ e.preventDefault();
177
+ }
178
+
179
+ if (addOptionByEnter && e.code === 'Enter' && inputValue !== '') {
180
+ handleSelectionChange(inputValue);
181
+ }
182
+
183
+ commonHandleOnKeyDown(onKeyDown)(e);
184
+ };
185
+
186
+ const handleOpenChange = (open: boolean) => {
187
+ if (!readonly && !disabled && isBrowser() && !buttonsRefs.includes(document.activeElement)) {
188
+ setOpen(open);
189
+
190
+ if (!open) {
191
+ setInputValue('');
192
+ }
193
+ }
194
+ };
195
+
196
+ const searcher = useSearch(items, enableFuzzySearch);
197
+ const result =
198
+ autocomplete || !searchable || selectedOptionFormatter(selectedItem) === inputValue
199
+ ? items
200
+ : searcher(inputValue);
201
+
202
+ const fieldValidationState = getValidationState({ validationState, error: rest.error });
203
+
204
+ const listJsx = (
205
+ <div className={styles.listWrapper}>
206
+ <List
207
+ {...extractListProps(rest)}
208
+ size='l'
209
+ virtualized={virtualized}
210
+ items={result}
211
+ contentRender={({ content, ...rest }) => {
212
+ if (typeof content !== 'function') {
213
+ return <ItemContent {...(content as ItemContentProps)} {...rest} />;
214
+ }
215
+
216
+ return content;
217
+ }}
218
+ scroll
219
+ search={
220
+ searchable
221
+ ? {
222
+ value: inputValue,
223
+ onChange: setInputValue,
224
+ onKeyDown: handleOnKeyDown(),
225
+ }
226
+ : undefined
227
+ }
228
+ selection={{
229
+ mode: 'single',
230
+ value: value,
231
+ onChange: handleSelectionChange,
232
+ }}
233
+ />
234
+ </div>
235
+ );
236
+
237
+ return (
238
+ <>
239
+ <FieldDecorator
240
+ {...extractSupportProps(rest)}
241
+ {...extractFieldDecoratorProps(rest)}
242
+ validationState={fieldValidationState}
243
+ required={required}
244
+ readonly={readonly}
245
+ labelFor={id}
246
+ disabled={disabled}
247
+ size={size}
248
+ >
249
+ <FieldContainerPrivate
250
+ className={styles.container}
251
+ validationState={fieldValidationState}
252
+ disabled={disabled}
253
+ readonly={readonly}
254
+ focused={open}
255
+ variant={'single-line-container'}
256
+ inputRef={localRef}
257
+ size={size}
258
+ prefix={
259
+ (prefixIcon || prefixSettings.show) && (
260
+ <>
261
+ {prefixIcon}
262
+ {prefixSettings.show && prefixSettings.render({ key: prefixSettings.id })}
263
+ </>
264
+ )
265
+ }
266
+ onClick={() => handleOpenChange(true)}
267
+ >
268
+ <InputPrivate
269
+ id={id}
270
+ name={name}
271
+ type='text'
272
+ disabled={disabled}
273
+ placeholder={placeholder}
274
+ ref={mergeRefs(ref, localRef)}
275
+ onChange={undefined}
276
+ value={selectedOptionFormatter(selectedItem)}
277
+ inputMode='none'
278
+ data-test-id='field-select__input'
279
+ onBlur={handleBlur}
280
+ className={styles.readonlyCursor}
281
+ autoFocus={autoFocus}
282
+ />
283
+
284
+ <div className={styles.postfix}>
285
+ {postfixButtons}
286
+ {postfixSettings.show && postfixSettings.render({ key: postfixSettings.id })}
287
+ <ArrowIcon size={arrowIconSize} className={styles.arrowIcon} />
288
+ </div>
289
+ </FieldContainerPrivate>
290
+ </FieldDecorator>
291
+
292
+ <MobileModalCustom open={open} onClose={() => handleOpenChange(false)} size={searchable ? 'full' : 'auto'}>
293
+ {rest.label && <MobileModalCustom.Header title={rest.label} />}
294
+
295
+ {searchable ? listJsx : <MobileModalCustom.Body className={styles.bodyNoPadding} content={listJsx} />}
296
+ </MobileModalCustom>
297
+ </>
298
+ );
299
+ },
300
+ );