@alfalab/core-components-input-autocomplete 10.2.3 → 11.0.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.
- package/Component.desktop.d.ts +3 -70
- package/Component.desktop.js +2 -1
- package/Component.mobile.d.ts +5 -95
- package/Component.mobile.js +42 -45
- package/Component.modal.mobile.d.ts +5 -88
- package/Component.modal.mobile.js +9 -62
- package/Component.responsive.d.ts +3 -46
- package/Component.responsive.js +9 -12
- package/autocomplete-field/Component.d.ts +3 -3
- package/autocomplete-field/Component.js +1 -1
- package/autocomplete-field/index.css +2 -2
- package/autocomplete-mobile-field/Component.d.ts +2 -2
- package/autocomplete-mobile-field/Component.js +6 -3
- package/autocomplete-mobile-field/index.css +10 -10
- package/cssm/Component.desktop.d.ts +3 -70
- package/cssm/Component.desktop.js +2 -1
- package/cssm/Component.mobile.d.ts +5 -95
- package/cssm/Component.mobile.js +39 -44
- package/cssm/Component.modal.mobile.d.ts +5 -88
- package/cssm/Component.modal.mobile.js +10 -63
- package/cssm/Component.responsive.d.ts +3 -46
- package/cssm/Component.responsive.js +9 -11
- package/cssm/autocomplete-field/Component.d.ts +3 -3
- package/cssm/autocomplete-mobile-field/Component.d.ts +2 -2
- package/cssm/autocomplete-mobile-field/Component.js +5 -2
- package/cssm/autocomplete-mobile-field/index.module.css +2 -2
- package/cssm/desktop/index.d.ts +2 -0
- package/cssm/{desktop.js → desktop/index.js} +4 -3
- package/cssm/index.d.ts +2 -2
- package/cssm/index.js +5 -4
- package/cssm/mobile/index.d.ts +3 -0
- package/cssm/{mobile.js → mobile/index.js} +7 -7
- package/cssm/mobile.module.css +3 -10
- package/cssm/types.d.ts +58 -0
- package/cssm/types.js +2 -0
- package/cssm/utils.d.ts +2 -0
- package/cssm/utils.js +9 -0
- package/desktop/index.d.ts +2 -0
- package/{desktop.js → desktop/index.js} +3 -2
- package/desktop/package.json +3 -0
- package/esm/Component.desktop.d.ts +3 -70
- package/esm/Component.desktop.js +2 -1
- package/esm/Component.mobile.d.ts +5 -95
- package/esm/Component.mobile.js +43 -46
- package/esm/Component.modal.mobile.d.ts +5 -88
- package/esm/Component.modal.mobile.js +11 -61
- package/esm/Component.responsive.d.ts +3 -46
- package/esm/Component.responsive.js +9 -12
- package/esm/autocomplete-field/Component.d.ts +3 -3
- package/esm/autocomplete-field/Component.js +1 -1
- package/esm/autocomplete-field/index.css +2 -2
- package/esm/autocomplete-mobile-field/Component.d.ts +2 -2
- package/esm/autocomplete-mobile-field/Component.js +6 -3
- package/esm/autocomplete-mobile-field/index.css +10 -10
- package/esm/desktop/index.d.ts +2 -0
- package/esm/{desktop.js → desktop/index.js} +3 -2
- package/esm/index.d.ts +2 -2
- package/esm/index.js +5 -5
- package/esm/mobile/index.d.ts +3 -0
- package/esm/mobile/index.js +14 -0
- package/esm/mobile.css +4 -11
- package/esm/types.d.ts +58 -0
- package/esm/types.js +1 -0
- package/esm/utils.d.ts +2 -0
- package/esm/utils.js +5 -0
- package/index.d.ts +2 -2
- package/index.js +5 -5
- package/mobile/index.d.ts +3 -0
- package/{mobile.js → mobile/index.js} +5 -6
- package/mobile/package.json +3 -0
- package/mobile.css +4 -11
- package/modern/Component.desktop.d.ts +3 -70
- package/modern/Component.desktop.js +2 -1
- package/modern/Component.mobile.d.ts +5 -95
- package/modern/Component.mobile.js +54 -56
- package/modern/Component.modal.mobile.d.ts +5 -88
- package/modern/Component.modal.mobile.js +10 -68
- package/modern/Component.responsive.d.ts +3 -46
- package/modern/Component.responsive.js +8 -11
- package/modern/autocomplete-field/Component.d.ts +3 -3
- package/modern/autocomplete-field/Component.js +1 -1
- package/modern/autocomplete-field/index.css +2 -2
- package/modern/autocomplete-mobile-field/Component.d.ts +2 -2
- package/modern/autocomplete-mobile-field/Component.js +5 -2
- package/modern/autocomplete-mobile-field/index.css +10 -10
- package/modern/desktop/index.d.ts +2 -0
- package/modern/{desktop.js → desktop/index.js} +3 -2
- package/modern/index.d.ts +2 -2
- package/modern/index.js +5 -5
- package/modern/mobile/index.d.ts +3 -0
- package/modern/mobile/index.js +13 -0
- package/modern/mobile.css +4 -11
- package/modern/types.d.ts +58 -0
- package/modern/types.js +1 -0
- package/modern/utils.d.ts +2 -0
- package/modern/utils.js +5 -0
- package/package.json +7 -6
- package/src/Component.desktop.tsx +6 -44
- package/src/Component.mobile.tsx +90 -188
- package/src/Component.modal.mobile.tsx +5 -223
- package/src/Component.responsive.tsx +10 -30
- package/src/autocomplete-field/Component.tsx +2 -2
- package/src/autocomplete-mobile-field/Component.tsx +5 -1
- package/src/desktop/index.ts +2 -0
- package/src/desktop/package.json +3 -0
- package/src/index.ts +2 -4
- package/src/mobile/index.ts +4 -0
- package/src/mobile/package.json +3 -0
- package/src/mobile.module.css +2 -11
- package/src/types.ts +77 -0
- package/src/utils.ts +3 -0
- package/types.d.ts +58 -0
- package/types.js +2 -0
- package/utils.d.ts +2 -0
- package/utils.js +9 -0
- package/Component-0f4b9bed.d.ts +0 -72
- package/Component-69921c40.d.ts +0 -184
- package/Component-89f0cb07.d.ts +0 -38
- package/Component-bdb4c6b9.d.ts +0 -12
- package/Component-c76d6398.d.ts +0 -5
- package/Component-ebda875c.d.ts +0 -12
- package/Component.desktop-ebda875c.d.ts +0 -6
- package/Component.mobile-f1f15074.d.ts +0 -34
- package/Component.mobile-f28cbba0.d.ts +0 -41
- package/Component.modal.mobile-0f4b9bed.d.ts +0 -60
- package/Context-bdb4c6b9.d.ts +0 -4
- package/ResponsiveContext-baf4875b.d.ts +0 -5
- package/consts-f777ba1a.d.ts +0 -2
- package/cssm/Component-0f4b9bed.d.ts +0 -72
- package/cssm/Component-69921c40.d.ts +0 -184
- package/cssm/Component-89f0cb07.d.ts +0 -38
- package/cssm/Component-bdb4c6b9.d.ts +0 -12
- package/cssm/Component-c76d6398.d.ts +0 -5
- package/cssm/Component-ebda875c.d.ts +0 -12
- package/cssm/Component.desktop-ebda875c.d.ts +0 -6
- package/cssm/Component.mobile-f1f15074.d.ts +0 -34
- package/cssm/Component.mobile-f28cbba0.d.ts +0 -41
- package/cssm/Component.modal.mobile-0f4b9bed.d.ts +0 -60
- package/cssm/Context-bdb4c6b9.d.ts +0 -4
- package/cssm/ResponsiveContext-baf4875b.d.ts +0 -5
- package/cssm/consts-f777ba1a.d.ts +0 -2
- package/cssm/desktop-69921c40.d.ts +0 -6
- package/cssm/desktop.d.ts +0 -2
- package/cssm/hook-8abfea97.d.ts +0 -90
- package/cssm/index-136acbb1.d.ts +0 -28
- package/cssm/index-72dda473.d.ts +0 -12
- package/cssm/index-bdb4c6b9.d.ts +0 -2
- package/cssm/index-c76d6398.d.ts +0 -185
- package/cssm/index-ebda875c.d.ts +0 -146
- package/cssm/index-f12ee135.d.ts +0 -52
- package/cssm/mobile-89f0cb07.d.ts +0 -6
- package/cssm/mobile-96988a65.d.ts +0 -6
- package/cssm/mobile-f28cbba0.d.ts +0 -5
- package/cssm/mobile.d.ts +0 -3
- package/cssm/shared-4cd3936b.d.ts +0 -65
- package/cssm/types-1b036d4b.d.ts +0 -13
- package/cssm/types-72dda473.d.ts +0 -276
- package/cssm/typings-0f4b9bed.d.ts +0 -576
- package/cssm/typings-89f0cb07.d.ts +0 -93
- package/cssm/typings-bdb4c6b9.d.ts +0 -52
- package/cssm/useSkeleton-ebda875c.d.ts +0 -12
- package/cssm/utils-0f4b9bed.d.ts +0 -41
- package/cssm/utils-1574ad8b.d.ts +0 -29
- package/desktop-69921c40.d.ts +0 -6
- package/desktop.d.ts +0 -2
- package/esm/Component-0f4b9bed.d.ts +0 -72
- package/esm/Component-69921c40.d.ts +0 -184
- package/esm/Component-89f0cb07.d.ts +0 -38
- package/esm/Component-bdb4c6b9.d.ts +0 -12
- package/esm/Component-c76d6398.d.ts +0 -5
- package/esm/Component-ebda875c.d.ts +0 -12
- package/esm/Component.desktop-ebda875c.d.ts +0 -6
- package/esm/Component.mobile-f1f15074.d.ts +0 -34
- package/esm/Component.mobile-f28cbba0.d.ts +0 -41
- package/esm/Component.modal.mobile-0f4b9bed.d.ts +0 -60
- package/esm/Context-bdb4c6b9.d.ts +0 -4
- package/esm/ResponsiveContext-baf4875b.d.ts +0 -5
- package/esm/consts-f777ba1a.d.ts +0 -2
- package/esm/desktop-69921c40.d.ts +0 -6
- package/esm/desktop.d.ts +0 -2
- package/esm/hook-8abfea97.d.ts +0 -90
- package/esm/index-136acbb1.d.ts +0 -28
- package/esm/index-72dda473.d.ts +0 -12
- package/esm/index-bdb4c6b9.d.ts +0 -2
- package/esm/index-c76d6398.d.ts +0 -185
- package/esm/index-ebda875c.d.ts +0 -146
- package/esm/index-f12ee135.d.ts +0 -52
- package/esm/mobile-89f0cb07.d.ts +0 -6
- package/esm/mobile-96988a65.d.ts +0 -6
- package/esm/mobile-f28cbba0.d.ts +0 -5
- package/esm/mobile.d.ts +0 -3
- package/esm/mobile.js +0 -15
- package/esm/mobile.module-c5b1b036.js +0 -4
- package/esm/shared-4cd3936b.d.ts +0 -65
- package/esm/types-1b036d4b.d.ts +0 -13
- package/esm/types-72dda473.d.ts +0 -276
- package/esm/typings-0f4b9bed.d.ts +0 -576
- package/esm/typings-89f0cb07.d.ts +0 -93
- package/esm/typings-bdb4c6b9.d.ts +0 -52
- package/esm/useSkeleton-ebda875c.d.ts +0 -12
- package/esm/utils-0f4b9bed.d.ts +0 -41
- package/esm/utils-1574ad8b.d.ts +0 -29
- package/hook-8abfea97.d.ts +0 -90
- package/index-136acbb1.d.ts +0 -28
- package/index-72dda473.d.ts +0 -12
- package/index-bdb4c6b9.d.ts +0 -2
- package/index-c76d6398.d.ts +0 -185
- package/index-ebda875c.d.ts +0 -146
- package/index-f12ee135.d.ts +0 -52
- package/mobile-89f0cb07.d.ts +0 -6
- package/mobile-96988a65.d.ts +0 -6
- package/mobile-f28cbba0.d.ts +0 -5
- package/mobile.d.ts +0 -3
- package/mobile.module-c08975ff.js +0 -6
- package/modern/Component-0f4b9bed.d.ts +0 -72
- package/modern/Component-69921c40.d.ts +0 -184
- package/modern/Component-89f0cb07.d.ts +0 -38
- package/modern/Component-bdb4c6b9.d.ts +0 -12
- package/modern/Component-c76d6398.d.ts +0 -5
- package/modern/Component-ebda875c.d.ts +0 -12
- package/modern/Component.desktop-ebda875c.d.ts +0 -6
- package/modern/Component.mobile-f1f15074.d.ts +0 -34
- package/modern/Component.mobile-f28cbba0.d.ts +0 -41
- package/modern/Component.modal.mobile-0f4b9bed.d.ts +0 -60
- package/modern/Context-bdb4c6b9.d.ts +0 -4
- package/modern/ResponsiveContext-baf4875b.d.ts +0 -5
- package/modern/consts-f777ba1a.d.ts +0 -2
- package/modern/desktop-69921c40.d.ts +0 -6
- package/modern/desktop.d.ts +0 -2
- package/modern/hook-8abfea97.d.ts +0 -90
- package/modern/index-136acbb1.d.ts +0 -28
- package/modern/index-72dda473.d.ts +0 -12
- package/modern/index-bdb4c6b9.d.ts +0 -2
- package/modern/index-c76d6398.d.ts +0 -185
- package/modern/index-ebda875c.d.ts +0 -146
- package/modern/index-f12ee135.d.ts +0 -52
- package/modern/mobile-89f0cb07.d.ts +0 -6
- package/modern/mobile-96988a65.d.ts +0 -6
- package/modern/mobile-f28cbba0.d.ts +0 -5
- package/modern/mobile.d.ts +0 -3
- package/modern/mobile.js +0 -14
- package/modern/mobile.module-1bc0502b.js +0 -4
- package/modern/shared-4cd3936b.d.ts +0 -65
- package/modern/types-1b036d4b.d.ts +0 -13
- package/modern/types-72dda473.d.ts +0 -276
- package/modern/typings-0f4b9bed.d.ts +0 -576
- package/modern/typings-89f0cb07.d.ts +0 -93
- package/modern/typings-bdb4c6b9.d.ts +0 -52
- package/modern/useSkeleton-ebda875c.d.ts +0 -12
- package/modern/utils-0f4b9bed.d.ts +0 -41
- package/modern/utils-1574ad8b.d.ts +0 -29
- package/shared-4cd3936b.d.ts +0 -65
- package/src/desktop.ts +0 -1
- package/src/mobile.ts +0 -5
- package/types-1b036d4b.d.ts +0 -13
- package/types-72dda473.d.ts +0 -276
- package/typings-0f4b9bed.d.ts +0 -576
- package/typings-89f0cb07.d.ts +0 -93
- package/typings-bdb4c6b9.d.ts +0 -52
- package/useSkeleton-ebda875c.d.ts +0 -12
- package/utils-0f4b9bed.d.ts +0 -41
- package/utils-1574ad8b.d.ts +0 -29
package/src/Component.mobile.tsx
CHANGED
|
@@ -1,140 +1,75 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { Ref, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import mergeRefs from 'react-merge-refs';
|
|
3
3
|
import cn from 'classnames';
|
|
4
4
|
import throttle from 'lodash.throttle';
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
import {
|
|
7
|
+
SelectMobile,
|
|
8
|
+
SelectMobileProps,
|
|
9
|
+
SelectModalMobile,
|
|
10
|
+
} from '@alfalab/core-components-select/mobile';
|
|
11
|
+
import {
|
|
12
|
+
AnyObject,
|
|
13
|
+
BottomSheetSelectMobileProps,
|
|
14
|
+
Footer,
|
|
15
|
+
ModalSelectMobileProps,
|
|
13
16
|
} from '@alfalab/core-components-select/shared';
|
|
14
17
|
|
|
15
18
|
import { AutocompleteMobileField } from './autocomplete-mobile-field';
|
|
19
|
+
import { InputAutocompleteMobileProps } from './types';
|
|
20
|
+
import { searchFilterStub } from './utils';
|
|
16
21
|
|
|
17
22
|
import styles from './mobile.module.css';
|
|
18
23
|
|
|
19
|
-
export type InputAutocompleteMobileProps = Omit<
|
|
20
|
-
BaseSelectProps,
|
|
21
|
-
| 'OptionsList'
|
|
22
|
-
| 'Checkmark'
|
|
23
|
-
| 'onScroll'
|
|
24
|
-
| 'nativeSelect'
|
|
25
|
-
| 'autocomplete'
|
|
26
|
-
| 'valueRenderer'
|
|
27
|
-
| 'searchProps'
|
|
28
|
-
| 'showSearch'
|
|
29
|
-
| 'Search'
|
|
30
|
-
> & {
|
|
31
|
-
/**
|
|
32
|
-
* Обработчик выбора
|
|
33
|
-
*/
|
|
34
|
-
onChange: (payload: string | BaseSelectChangePayload) => void;
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Обработчик ввода фильтра.
|
|
38
|
-
*/
|
|
39
|
-
onFilter: (event: ChangeEvent<HTMLInputElement>) => void;
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Значение поля ввода
|
|
43
|
-
*/
|
|
44
|
-
value?: string;
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Значение фильтра.
|
|
48
|
-
*/
|
|
49
|
-
filter?: string;
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Обработчик нажатия на кнопку "Отмена".
|
|
53
|
-
*/
|
|
54
|
-
onCancel?: () => void;
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Обработчик нажатия на крестик в инпуте фильтра.
|
|
58
|
-
*/
|
|
59
|
-
onClearFilter?: () => void;
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Дополнительные пропсы компонента BottomSheet
|
|
63
|
-
*/
|
|
64
|
-
bottomSheetProps?: AdditionalMobileProps['bottomSheetProps'];
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Дополнительные пропсы на слот под заголовком компонента BottomSheet
|
|
68
|
-
*/
|
|
69
|
-
bottomSheetHeaderAddonsProps?: Record<string, unknown>;
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Дополнительные пропсы на кнопку "продолжить"
|
|
73
|
-
*/
|
|
74
|
-
continueButtonProps?: ButtonMobileProps;
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Дополнительные пропсы на кнопку "отмена"
|
|
78
|
-
*/
|
|
79
|
-
cancelButtonProps?: ButtonMobileProps;
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Кастомный инпут
|
|
83
|
-
*/
|
|
84
|
-
Input?: ElementType;
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
const SELECTED: string[] = [];
|
|
88
|
-
|
|
89
24
|
export const InputAutocompleteMobile = React.forwardRef(
|
|
90
25
|
(
|
|
91
26
|
{
|
|
92
27
|
Input,
|
|
93
|
-
|
|
94
|
-
bottomSheetHeaderAddonsProps = {},
|
|
95
|
-
value = '',
|
|
96
|
-
filter = '',
|
|
28
|
+
value,
|
|
97
29
|
name,
|
|
98
30
|
Arrow = null,
|
|
99
31
|
label,
|
|
100
|
-
placeholder,
|
|
32
|
+
placeholder = '',
|
|
101
33
|
size = 's',
|
|
102
34
|
open: openProp,
|
|
103
|
-
|
|
104
|
-
onChange,
|
|
35
|
+
onInput,
|
|
105
36
|
onOpen,
|
|
106
|
-
onCancel,
|
|
107
|
-
onClearFilter,
|
|
108
|
-
continueButtonProps,
|
|
109
|
-
cancelButtonProps,
|
|
110
|
-
selected,
|
|
111
37
|
multiple,
|
|
38
|
+
inputProps,
|
|
39
|
+
isBottomSheet = true,
|
|
40
|
+
dataTestId,
|
|
41
|
+
transitionProps,
|
|
112
42
|
...restProps
|
|
113
43
|
}: InputAutocompleteMobileProps,
|
|
114
44
|
ref,
|
|
115
45
|
) => {
|
|
116
46
|
const [open, setOpen] = useState(false);
|
|
117
|
-
const
|
|
47
|
+
const frozenValue = useRef<string>('');
|
|
48
|
+
const searchInputRef = useRef<HTMLInputElement>(null);
|
|
118
49
|
const targetRef = useRef<HTMLDivElement>(null);
|
|
119
50
|
|
|
120
|
-
const
|
|
51
|
+
const restorePrevValue = () => onInput?.(null, { value: frozenValue.current });
|
|
52
|
+
|
|
53
|
+
const setModalVisibility = (isOpen: boolean) => {
|
|
121
54
|
if (openProp === undefined) {
|
|
122
55
|
setOpen(isOpen);
|
|
123
56
|
}
|
|
124
57
|
|
|
125
|
-
if (
|
|
126
|
-
|
|
58
|
+
if (isOpen) {
|
|
59
|
+
frozenValue.current = value || '';
|
|
127
60
|
}
|
|
61
|
+
|
|
62
|
+
onOpen?.({ open: isOpen, name });
|
|
128
63
|
};
|
|
129
64
|
|
|
130
65
|
const handleOpen: SelectMobileProps['onOpen'] = (payload) => {
|
|
131
|
-
|
|
66
|
+
setModalVisibility(Boolean(payload.open));
|
|
132
67
|
};
|
|
133
68
|
|
|
134
69
|
const handleOptionsListTouchMove = useMemo(
|
|
135
70
|
() =>
|
|
136
71
|
throttle(() => {
|
|
137
|
-
const input =
|
|
72
|
+
const input = searchInputRef.current;
|
|
138
73
|
|
|
139
74
|
if (input && document.activeElement === input) {
|
|
140
75
|
input.blur();
|
|
@@ -143,119 +78,86 @@ export const InputAutocompleteMobile = React.forwardRef(
|
|
|
143
78
|
[],
|
|
144
79
|
);
|
|
145
80
|
|
|
146
|
-
const handleApply = () =>
|
|
147
|
-
setBottomSheetVisibility(false);
|
|
148
|
-
onChange(filter);
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
const handleChange: SelectMobileProps['onChange'] = (payload) => {
|
|
152
|
-
onChange(payload);
|
|
153
|
-
|
|
154
|
-
if (multiple) {
|
|
155
|
-
// После выбора опции возвращаем фокус в поле ввода.
|
|
156
|
-
bottomSheetInputRef.current?.focus();
|
|
157
|
-
}
|
|
158
|
-
};
|
|
81
|
+
const handleApply = () => setModalVisibility(false);
|
|
159
82
|
|
|
160
83
|
const handleCancel = () => {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
if (onCancel) {
|
|
164
|
-
onCancel();
|
|
165
|
-
}
|
|
84
|
+
setModalVisibility(false);
|
|
85
|
+
restorePrevValue();
|
|
166
86
|
};
|
|
167
87
|
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
// Перед закрытием шторки снимаем фокус с инпута, чтобы предотвратить скачок шторки.
|
|
172
|
-
if (
|
|
173
|
-
event.relatedTarget === targetRef.current &&
|
|
174
|
-
input &&
|
|
175
|
-
input === document.activeElement
|
|
176
|
-
) {
|
|
177
|
-
input.blur();
|
|
178
|
-
}
|
|
88
|
+
const handleExiting = (node: HTMLElement) => {
|
|
89
|
+
targetRef.current?.focus();
|
|
90
|
+
transitionProps?.onExiting?.(node);
|
|
179
91
|
};
|
|
180
92
|
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
block={true}
|
|
198
|
-
view='primary'
|
|
199
|
-
size='m'
|
|
200
|
-
onClick={handleApply}
|
|
201
|
-
{...continueButtonProps}
|
|
202
|
-
>
|
|
203
|
-
Продолжить
|
|
204
|
-
</ButtonMobile>
|
|
205
|
-
</div>
|
|
206
|
-
),
|
|
207
|
-
title: label || placeholder,
|
|
208
|
-
bottomAddons: (
|
|
209
|
-
<Component
|
|
210
|
-
block={true}
|
|
211
|
-
clear={!!onClearFilter}
|
|
212
|
-
onClear={onClearFilter}
|
|
213
|
-
value={filter}
|
|
214
|
-
onInput={onFilter}
|
|
215
|
-
placeholder={placeholder}
|
|
216
|
-
onFocus={handleInputFocus}
|
|
217
|
-
{...bottomSheetHeaderAddonsProps}
|
|
218
|
-
className={cn(
|
|
219
|
-
styles.bottomAddonInput,
|
|
220
|
-
bottomSheetHeaderAddonsProps.className as string,
|
|
221
|
-
)}
|
|
222
|
-
ref={mergeRefs([
|
|
223
|
-
bottomSheetInputRef,
|
|
224
|
-
bottomSheetHeaderAddonsProps.ref as RefObject<HTMLInputElement>,
|
|
225
|
-
])}
|
|
226
|
-
/>
|
|
227
|
-
),
|
|
228
|
-
initialHeight: 'full',
|
|
229
|
-
...bottomSheetProps,
|
|
230
|
-
containerProps: {
|
|
231
|
-
onTouchMove: handleOptionsListTouchMove,
|
|
232
|
-
...bottomSheetProps.containerProps,
|
|
233
|
-
},
|
|
234
|
-
};
|
|
93
|
+
const isOpen = Boolean(open || openProp);
|
|
94
|
+
|
|
95
|
+
const Component = isBottomSheet ? SelectMobile : SelectModalMobile;
|
|
96
|
+
|
|
97
|
+
const componentProps:
|
|
98
|
+
| ModalSelectMobileProps['modalProps']
|
|
99
|
+
| BottomSheetSelectMobileProps['bottomSheetProps'] = {
|
|
100
|
+
title: label || placeholder,
|
|
101
|
+
onClose: restorePrevValue,
|
|
102
|
+
transitionProps: {
|
|
103
|
+
...transitionProps,
|
|
104
|
+
onExiting: handleExiting,
|
|
105
|
+
},
|
|
106
|
+
[isBottomSheet ? 'containerProps' : 'componentDivProps']: {
|
|
107
|
+
onTouchMove: handleOptionsListTouchMove,
|
|
108
|
+
},
|
|
235
109
|
};
|
|
236
110
|
|
|
237
111
|
return (
|
|
238
|
-
<
|
|
239
|
-
useWithApplyHook={false}
|
|
112
|
+
<Component
|
|
240
113
|
Field={AutocompleteMobileField}
|
|
241
114
|
{...restProps}
|
|
115
|
+
{...(isBottomSheet
|
|
116
|
+
? { bottomSheetProps: componentProps }
|
|
117
|
+
: { modalProps: componentProps })}
|
|
118
|
+
dataTestId={dataTestId}
|
|
119
|
+
useWithApplyHook={false}
|
|
120
|
+
showSearch={true}
|
|
121
|
+
searchProps={{
|
|
122
|
+
value,
|
|
123
|
+
filterFn: searchFilterStub,
|
|
124
|
+
componentProps: {
|
|
125
|
+
leftAddons: null,
|
|
126
|
+
placeholder,
|
|
127
|
+
...inputProps,
|
|
128
|
+
className: cn(styles.input, inputProps?.className),
|
|
129
|
+
clear: inputProps?.clear ?? false,
|
|
130
|
+
ref: mergeRefs([searchInputRef, inputProps?.ref as Ref<HTMLInputElement>]),
|
|
131
|
+
onChange: onInput,
|
|
132
|
+
},
|
|
133
|
+
}}
|
|
134
|
+
Search={Input}
|
|
242
135
|
ref={mergeRefs([targetRef, ref])}
|
|
243
|
-
|
|
244
|
-
open={Boolean(open || openProp)}
|
|
136
|
+
open={isOpen}
|
|
245
137
|
onOpen={handleOpen}
|
|
246
138
|
Arrow={Arrow}
|
|
247
|
-
onChange={handleChange}
|
|
248
139
|
placeholder={placeholder}
|
|
249
140
|
label={label}
|
|
250
141
|
size={size}
|
|
251
142
|
name={name}
|
|
252
143
|
multiple={multiple}
|
|
253
|
-
bottomSheetProps={getBottomSheetProps()}
|
|
254
144
|
optionsListProps={{
|
|
255
|
-
|
|
256
|
-
|
|
145
|
+
footer: (
|
|
146
|
+
<Footer
|
|
147
|
+
showClear={true}
|
|
148
|
+
handleClear={handleCancel}
|
|
149
|
+
handleApply={handleApply}
|
|
150
|
+
clearText='Отмена'
|
|
151
|
+
applyText='Продолжить'
|
|
152
|
+
dataTestId={dataTestId}
|
|
153
|
+
/>
|
|
154
|
+
),
|
|
155
|
+
...(restProps.optionsListProps as AnyObject),
|
|
156
|
+
}}
|
|
157
|
+
fieldProps={{
|
|
158
|
+
value: isOpen ? frozenValue.current : value,
|
|
159
|
+
...(restProps.fieldProps as AnyObject),
|
|
257
160
|
}}
|
|
258
|
-
fieldProps={{ value, ...(restProps.fieldProps as Record<string, unknown>) }}
|
|
259
161
|
/>
|
|
260
162
|
);
|
|
261
163
|
},
|
|
@@ -1,227 +1,9 @@
|
|
|
1
|
-
import React
|
|
2
|
-
import mergeRefs from 'react-merge-refs';
|
|
3
|
-
import cn from 'classnames';
|
|
4
|
-
import throttle from 'lodash.throttle';
|
|
1
|
+
import React from 'react';
|
|
5
2
|
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
SelectMobileProps,
|
|
10
|
-
SelectModalMobile,
|
|
11
|
-
SelectModalMobileProps,
|
|
12
|
-
} from '@alfalab/core-components-select/mobile';
|
|
13
|
-
import type { BaseSelectChangePayload } from '@alfalab/core-components-select/shared';
|
|
14
|
-
|
|
15
|
-
import { AutocompleteMobileField } from './autocomplete-mobile-field';
|
|
16
|
-
|
|
17
|
-
import styles from './mobile.module.css';
|
|
18
|
-
|
|
19
|
-
export type InputAutocompleteModalMobileProps = Omit<
|
|
20
|
-
SelectModalMobileProps,
|
|
21
|
-
'OptionsList' | 'Checkmark' | 'onScroll' | 'nativeSelect' | 'autocomplete' | 'valueRenderer'
|
|
22
|
-
> & {
|
|
23
|
-
/**
|
|
24
|
-
* Обработчик выбора
|
|
25
|
-
*/
|
|
26
|
-
onChange: (payload: string | BaseSelectChangePayload) => void;
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Обработчик ввода фильтра.
|
|
30
|
-
*/
|
|
31
|
-
onFilter: (event: ChangeEvent<HTMLInputElement>) => void;
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Значение поля ввода
|
|
35
|
-
*/
|
|
36
|
-
value?: string;
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Значение фильтра.
|
|
40
|
-
*/
|
|
41
|
-
filter?: string;
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Обработчик нажатия на кнопку "Отмена".
|
|
45
|
-
*/
|
|
46
|
-
onCancel?: () => void;
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Обработчик нажатия на крестик в инпуте фильтра.
|
|
50
|
-
*/
|
|
51
|
-
onClearFilter?: () => void;
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Дополнительные пропсы на слот под заголовком
|
|
55
|
-
*/
|
|
56
|
-
bottomAddonProps?: Record<string, unknown>;
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Дополнительные пропсы на кнопку "продолжить"
|
|
60
|
-
*/
|
|
61
|
-
continueButtonProps?: ButtonMobileProps;
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Дополнительные пропсы на кнопку "отмена"
|
|
65
|
-
*/
|
|
66
|
-
cancelButtonProps?: ButtonMobileProps;
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Кастомный инпут
|
|
70
|
-
*/
|
|
71
|
-
Input?: ElementType;
|
|
72
|
-
};
|
|
3
|
+
import { InputAutocompleteMobile } from './Component.mobile';
|
|
4
|
+
import { InputAutocompleteMobileProps } from './types';
|
|
73
5
|
|
|
74
6
|
export const InputAutocompleteModalMobile = React.forwardRef<
|
|
75
7
|
HTMLDivElement,
|
|
76
|
-
|
|
77
|
-
>(
|
|
78
|
-
(
|
|
79
|
-
{
|
|
80
|
-
open: openProp,
|
|
81
|
-
onOpen,
|
|
82
|
-
name,
|
|
83
|
-
size = 's',
|
|
84
|
-
Input = CoreInput,
|
|
85
|
-
Arrow = null,
|
|
86
|
-
onClearFilter,
|
|
87
|
-
filter = '',
|
|
88
|
-
onFilter,
|
|
89
|
-
placeholder,
|
|
90
|
-
bottomAddonProps,
|
|
91
|
-
multiple,
|
|
92
|
-
onChange,
|
|
93
|
-
onCancel,
|
|
94
|
-
value,
|
|
95
|
-
cancelButtonProps,
|
|
96
|
-
continueButtonProps,
|
|
97
|
-
modalProps,
|
|
98
|
-
modalHeaderProps,
|
|
99
|
-
modalFooterProps,
|
|
100
|
-
...restProps
|
|
101
|
-
}: InputAutocompleteModalMobileProps,
|
|
102
|
-
ref,
|
|
103
|
-
) => {
|
|
104
|
-
const [open, setOpen] = React.useState(false);
|
|
105
|
-
const targetRef = useRef<HTMLDivElement>(null);
|
|
106
|
-
const modalScrollableRef = useRef<HTMLDivElement>(null);
|
|
107
|
-
const bottomAddonRef = useRef<HTMLInputElement>(null);
|
|
108
|
-
|
|
109
|
-
useEffect(() => {
|
|
110
|
-
const modalContentNode = modalScrollableRef.current;
|
|
111
|
-
|
|
112
|
-
const handleListTouchMove = throttle(() => {
|
|
113
|
-
const input = bottomAddonRef.current;
|
|
114
|
-
|
|
115
|
-
if (input && document.activeElement === input) {
|
|
116
|
-
input.blur();
|
|
117
|
-
}
|
|
118
|
-
}, 300);
|
|
119
|
-
|
|
120
|
-
modalContentNode?.addEventListener('touchmove', handleListTouchMove);
|
|
121
|
-
|
|
122
|
-
return () => modalContentNode?.removeEventListener('touchmove', handleListTouchMove);
|
|
123
|
-
}, [open]);
|
|
124
|
-
|
|
125
|
-
const setOpenModalSelect = (isOpen: boolean) => {
|
|
126
|
-
if (openProp === undefined) {
|
|
127
|
-
setOpen(isOpen);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
onOpen?.({ open: isOpen, name });
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
const handleOpen: SelectMobileProps['onOpen'] = (payload) => {
|
|
134
|
-
setOpenModalSelect(Boolean(payload.open));
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
const handleApply = () => {
|
|
138
|
-
setOpenModalSelect(false);
|
|
139
|
-
onChange(filter);
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
const handleCancel = () => {
|
|
143
|
-
setOpenModalSelect(false);
|
|
144
|
-
onCancel?.();
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
const handleChange: SelectMobileProps['onChange'] = (payload) => {
|
|
148
|
-
onChange(payload);
|
|
149
|
-
|
|
150
|
-
if (multiple) {
|
|
151
|
-
// После выбора опции возвращаем фокус в поле ввода.
|
|
152
|
-
bottomAddonRef.current?.focus();
|
|
153
|
-
}
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
const renderBottomAddon = () => (
|
|
157
|
-
<Input
|
|
158
|
-
block={true}
|
|
159
|
-
clear={!!onClearFilter}
|
|
160
|
-
onClear={onClearFilter}
|
|
161
|
-
value={filter}
|
|
162
|
-
onInput={onFilter}
|
|
163
|
-
placeholder={placeholder}
|
|
164
|
-
{...bottomAddonProps}
|
|
165
|
-
className={cn(styles.bottomAddonInput, bottomAddonProps?.className as string)}
|
|
166
|
-
ref={mergeRefs([
|
|
167
|
-
bottomAddonRef,
|
|
168
|
-
bottomAddonProps?.ref as RefObject<HTMLInputElement>,
|
|
169
|
-
])}
|
|
170
|
-
/>
|
|
171
|
-
);
|
|
172
|
-
|
|
173
|
-
const renderFooter = () => (
|
|
174
|
-
<React.Fragment>
|
|
175
|
-
<ButtonMobile
|
|
176
|
-
block={true}
|
|
177
|
-
view='secondary'
|
|
178
|
-
size='m'
|
|
179
|
-
onClick={handleCancel}
|
|
180
|
-
{...cancelButtonProps}
|
|
181
|
-
>
|
|
182
|
-
Отмена
|
|
183
|
-
</ButtonMobile>
|
|
184
|
-
<ButtonMobile
|
|
185
|
-
block={true}
|
|
186
|
-
view='primary'
|
|
187
|
-
size='m'
|
|
188
|
-
onClick={handleApply}
|
|
189
|
-
{...continueButtonProps}
|
|
190
|
-
>
|
|
191
|
-
Продолжить
|
|
192
|
-
</ButtonMobile>
|
|
193
|
-
</React.Fragment>
|
|
194
|
-
);
|
|
195
|
-
|
|
196
|
-
return (
|
|
197
|
-
<SelectModalMobile
|
|
198
|
-
Field={AutocompleteMobileField}
|
|
199
|
-
useWithApplyHook={false}
|
|
200
|
-
{...restProps}
|
|
201
|
-
ref={mergeRefs([ref, targetRef])}
|
|
202
|
-
Arrow={Arrow}
|
|
203
|
-
onOpen={handleOpen}
|
|
204
|
-
onChange={handleChange}
|
|
205
|
-
multiple={multiple}
|
|
206
|
-
open={openProp ?? open}
|
|
207
|
-
size={size}
|
|
208
|
-
fieldProps={{ value, ...(restProps.fieldProps as Record<string, unknown>) }}
|
|
209
|
-
placeholder={placeholder}
|
|
210
|
-
name={name}
|
|
211
|
-
modalProps={{
|
|
212
|
-
...modalProps,
|
|
213
|
-
componentRef: modalScrollableRef,
|
|
214
|
-
}}
|
|
215
|
-
modalHeaderProps={{
|
|
216
|
-
...modalHeaderProps,
|
|
217
|
-
bottomAddons: renderBottomAddon(),
|
|
218
|
-
}}
|
|
219
|
-
modalFooterProps={{
|
|
220
|
-
sticky: true,
|
|
221
|
-
...modalFooterProps,
|
|
222
|
-
children: renderFooter(),
|
|
223
|
-
}}
|
|
224
|
-
/>
|
|
225
|
-
);
|
|
226
|
-
},
|
|
227
|
-
);
|
|
8
|
+
InputAutocompleteMobileProps
|
|
9
|
+
>((props, ref) => <InputAutocompleteMobile {...props} ref={ref} isBottomSheet={false} />);
|
|
@@ -1,40 +1,20 @@
|
|
|
1
1
|
import React, { forwardRef } from 'react';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
import { ButtonMobileProps } from '@alfalab/core-components-button/mobile';
|
|
5
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
6
|
-
import type { BaseSelectProps } from '@alfalab/core-components-select/shared';
|
|
7
|
-
import { useMedia } from '@alfalab/hooks';
|
|
3
|
+
import { useMatchMedia } from '@alfalab/core-components-mq';
|
|
8
4
|
|
|
9
|
-
import { InputAutocompleteDesktop
|
|
10
|
-
import { InputAutocompleteMobile
|
|
5
|
+
import { InputAutocompleteDesktop } from './Component.desktop';
|
|
6
|
+
import { InputAutocompleteMobile } from './Component.mobile';
|
|
7
|
+
import { InputAutocompleteProps } from './types';
|
|
11
8
|
|
|
12
|
-
export
|
|
13
|
-
InputAutocompleteMobileProps & {
|
|
14
|
-
/**
|
|
15
|
-
* Контрольная точка, с нее начинается desktop версия
|
|
16
|
-
* @default 1024
|
|
17
|
-
*/
|
|
18
|
-
breakpoint?: number;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
export type InputAutocompleteMedia = 'desktop' | 'mobile';
|
|
22
|
-
|
|
23
|
-
export const InputAutocompleteResponsive = forwardRef<
|
|
9
|
+
export const InputAutocomplete = forwardRef<
|
|
24
10
|
HTMLInputElement | HTMLDivElement,
|
|
25
|
-
|
|
26
|
-
>(({ breakpoint = 1024, ...restProps }, ref) => {
|
|
27
|
-
const [
|
|
28
|
-
[
|
|
29
|
-
['mobile', `(max-width: ${breakpoint - 1}px)`],
|
|
30
|
-
['desktop', `(min-width: ${breakpoint}px)`],
|
|
31
|
-
],
|
|
32
|
-
'desktop',
|
|
33
|
-
);
|
|
11
|
+
InputAutocompleteProps
|
|
12
|
+
>(({ breakpoint = 1024, defaultMatchMediaValue = true, mobileProps, ...restProps }, ref) => {
|
|
13
|
+
const [isDesktop] = useMatchMedia(`(min-width: ${breakpoint}px)`, defaultMatchMediaValue);
|
|
34
14
|
|
|
35
|
-
return
|
|
15
|
+
return isDesktop ? (
|
|
36
16
|
<InputAutocompleteDesktop {...restProps} ref={ref as React.Ref<HTMLInputElement>} />
|
|
37
17
|
) : (
|
|
38
|
-
<InputAutocompleteMobile {...restProps} ref={ref} />
|
|
18
|
+
<InputAutocompleteMobile {...restProps} {...mobileProps} ref={ref} />
|
|
39
19
|
);
|
|
40
20
|
});
|
|
@@ -5,12 +5,12 @@ import cn from 'classnames';
|
|
|
5
5
|
import { InputDesktop as DefaultInput } from '@alfalab/core-components-input/desktop';
|
|
6
6
|
import type { FieldProps } from '@alfalab/core-components-select/shared';
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import { InputAutocompleteCommonProps } from '../types';
|
|
9
9
|
|
|
10
10
|
import styles from './index.module.css';
|
|
11
11
|
|
|
12
12
|
export type AutocompleteFieldProps = FieldProps &
|
|
13
|
-
Pick<
|
|
13
|
+
Pick<InputAutocompleteCommonProps, 'Input' | 'inputProps' | 'value' | 'onInput' | 'readOnly'>;
|
|
14
14
|
|
|
15
15
|
export const AutocompleteField = ({
|
|
16
16
|
label,
|
|
@@ -53,6 +53,8 @@ export const AutocompleteMobileField = ({
|
|
|
53
53
|
const filled = Boolean(value);
|
|
54
54
|
const showPlaceholder = placeholder && !filled && labelView === 'outer';
|
|
55
55
|
|
|
56
|
+
const { tabIndex, ...restInnerProps } = innerProps;
|
|
57
|
+
|
|
56
58
|
return (
|
|
57
59
|
<div
|
|
58
60
|
className={styles.component}
|
|
@@ -73,8 +75,10 @@ export const AutocompleteMobileField = ({
|
|
|
73
75
|
labelView={labelView}
|
|
74
76
|
rightAddons={Arrow}
|
|
75
77
|
dataTestId={getDataTestId(dataTestId, 'form-control')}
|
|
78
|
+
// downshift устанавливает фокус на таргет поле после выбора опции, не даем ему это сделать пока открыт список, иначе поле поиска будет терять фокус
|
|
79
|
+
tabIndex={open ? undefined : tabIndex}
|
|
76
80
|
{...restProps}
|
|
77
|
-
{...
|
|
81
|
+
{...restInnerProps}
|
|
78
82
|
>
|
|
79
83
|
<div className={styles.contentWrapper}>
|
|
80
84
|
{showPlaceholder && <span className={styles.placeholder}>{placeholder}</span>}
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,2 @@
|
|
|
1
|
-
export {
|
|
2
|
-
|
|
3
|
-
InputAutocompleteResponsiveProps as InputAutocompleteProps,
|
|
4
|
-
} from './Component.responsive';
|
|
1
|
+
export { InputAutocomplete } from './Component.responsive';
|
|
2
|
+
export type { InputAutocompleteProps } from './types';
|