@astral/ui 4.93.0 → 4.94.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/components/AsyncAutocomplete/AsyncAutocomplete.d.ts +1 -0
- package/components/AsyncAutocomplete/types.d.ts +5 -0
- package/components/AsyncAutocomplete/useLogic/useLogic.d.ts +1 -1
- package/components/AsyncAutocomplete/useLogic/useLogic.js +13 -4
- package/components/useAsyncOptions/faker.d.ts +3 -0
- package/components/useAsyncOptions/faker.js +3 -0
- package/components/useAsyncOptions/index.d.ts +1 -0
- package/components/useAsyncOptions/index.js +1 -0
- package/components/useAsyncOptions/public.d.ts +1 -0
- package/components/useAsyncOptions/public.js +1 -0
- package/components/useAsyncOptions/types.d.ts +7 -0
- package/components/useAsyncOptions/types.js +1 -0
- package/components/useAsyncOptions/useAsyncOptions.d.ts +8 -1
- package/components/useAsyncOptions/useAsyncOptions.js +22 -3
- package/node/components/AsyncAutocomplete/AsyncAutocomplete.d.ts +1 -0
- package/node/components/AsyncAutocomplete/types.d.ts +5 -0
- package/node/components/AsyncAutocomplete/useLogic/useLogic.d.ts +1 -1
- package/node/components/AsyncAutocomplete/useLogic/useLogic.js +13 -4
- package/node/components/useAsyncOptions/faker.d.ts +3 -0
- package/node/components/useAsyncOptions/faker.js +9 -0
- package/node/components/useAsyncOptions/index.d.ts +1 -0
- package/node/components/useAsyncOptions/index.js +1 -0
- package/node/components/useAsyncOptions/public.d.ts +1 -0
- package/node/components/useAsyncOptions/public.js +1 -0
- package/node/components/useAsyncOptions/types.d.ts +7 -0
- package/node/components/useAsyncOptions/types.js +2 -0
- package/node/components/useAsyncOptions/useAsyncOptions.d.ts +8 -1
- package/node/components/useAsyncOptions/useAsyncOptions.js +22 -3
- package/package.json +1 -1
- package/components/AsyncAutocomplete/faker.d.ts +0 -2
- package/components/AsyncAutocomplete/faker.js +0 -38
- package/node/components/AsyncAutocomplete/faker.d.ts +0 -2
- package/node/components/AsyncAutocomplete/faker.js +0 -45
|
@@ -9,5 +9,6 @@ export declare const AsyncAutocomplete: <TOptions, TMultiple extends boolean = f
|
|
|
9
9
|
fetchDelayMs?: number | undefined;
|
|
10
10
|
fetchOptions: (search: string, signal?: AbortSignal | undefined) => Promise<TOptions[]>;
|
|
11
11
|
minSymbolsToFetch?: number | undefined;
|
|
12
|
+
shouldFetch?: import("../useAsyncOptions").ShouldFetch | undefined;
|
|
12
13
|
onDataLoadError?: ((error: Error) => void) | undefined;
|
|
13
14
|
} & import("react").RefAttributes<unknown>) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | null;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type AutocompleteProps } from '../Autocomplete';
|
|
2
|
+
import { type ShouldFetch } from '../useAsyncOptions';
|
|
2
3
|
export type AsyncAutocompleteProps<TOptions, TMultiple extends boolean = false, TDisableClearable extends boolean = false, TFreeSolo extends boolean = false> = Omit<AutocompleteProps<TOptions, TMultiple, TDisableClearable, TFreeSolo>, 'inputValue' | 'loading' | 'disabled' | 'fullWidth' | 'error' | 'required' | 'success' | 'options' | 'filterOptions'> & {
|
|
3
4
|
/**
|
|
4
5
|
* Если `true`, будет показан индикатор загрузки
|
|
@@ -59,6 +60,10 @@ export type AsyncAutocompleteProps<TOptions, TMultiple extends boolean = false,
|
|
|
59
60
|
* @default 0
|
|
60
61
|
*/
|
|
61
62
|
minSymbolsToFetch?: number;
|
|
63
|
+
/**
|
|
64
|
+
* Функция, задающая правила валидации запроса
|
|
65
|
+
*/
|
|
66
|
+
shouldFetch?: ShouldFetch;
|
|
62
67
|
/**
|
|
63
68
|
* Функция для обработки ошибок загрузки данных
|
|
64
69
|
*/
|
|
@@ -2,7 +2,7 @@ import type { AutocompleteInputChangeReason } from '@mui/material/Autocomplete';
|
|
|
2
2
|
import { type SyntheticEvent } from 'react';
|
|
3
3
|
import { type AsyncAutocompleteProps } from '../types';
|
|
4
4
|
type UseLogicParams<TOptions, TMultiple extends boolean, TDisableClearable extends boolean, TFreeSolo extends boolean> = AsyncAutocompleteProps<TOptions, TMultiple, TDisableClearable, TFreeSolo>;
|
|
5
|
-
export declare const useLogic: <TOptions, TMultiple extends boolean, TDisableClearable extends boolean, TFreeSolo extends boolean>({ fetchOptions, fetchDelayMs, minSymbolsToFetch, defaultValue, getOptionLabel, noOptionsText, onOpen, onInputChange, onDataLoadError, isLoadedDataError, isDisabled, isFullWidth, isSuccess, isError, isLoading, isRequired, ...restProps }: UseLogicParams<TOptions, TMultiple, TDisableClearable, TFreeSolo>) => {
|
|
5
|
+
export declare const useLogic: <TOptions, TMultiple extends boolean, TDisableClearable extends boolean, TFreeSolo extends boolean>({ fetchOptions, fetchDelayMs, minSymbolsToFetch, shouldFetch, defaultValue, getOptionLabel, noOptionsText, onOpen, onInputChange, onDataLoadError, isLoadedDataError, isDisabled, isFullWidth, isSuccess, isError, isLoading, isRequired, ...restProps }: UseLogicParams<TOptions, TMultiple, TDisableClearable, TFreeSolo>) => {
|
|
6
6
|
autocompleteProps: {
|
|
7
7
|
classes?: Partial<import("@mui/material/Autocomplete").AutocompleteClasses> | undefined;
|
|
8
8
|
defaultChecked?: boolean | undefined;
|
|
@@ -3,11 +3,12 @@ import { DEFAULT_DELAY_MS, DEFAULT_EMPTY_OPTIONS_TEXT, DEFAULT_MIN_SEARCH_CHARAC
|
|
|
3
3
|
/* Стандартный фильтр. Необходим для правильного отображения опций, при асинхронном поиске
|
|
4
4
|
* см. https://track.astral.ru/soft/browse/UIKIT-2823 */
|
|
5
5
|
const filterOptions = (options) => options;
|
|
6
|
-
export const useLogic = ({ fetchOptions, fetchDelayMs = DEFAULT_DELAY_MS, minSymbolsToFetch = DEFAULT_MIN_SEARCH_CHARACTERS, defaultValue, getOptionLabel, noOptionsText = DEFAULT_NO_OPTIONS_TEXT, onOpen, onInputChange, onDataLoadError, isLoadedDataError, isDisabled, isFullWidth, isSuccess, isError, isLoading, isRequired, ...restProps }) => {
|
|
7
|
-
const { options, onInputChange: propsOnInputChange, onOpen: propsOnOpen, isLoadingData, isLoadingError, } = useAsyncOptions({
|
|
6
|
+
export const useLogic = ({ fetchOptions, fetchDelayMs = DEFAULT_DELAY_MS, minSymbolsToFetch = DEFAULT_MIN_SEARCH_CHARACTERS, shouldFetch, defaultValue, getOptionLabel, noOptionsText = DEFAULT_NO_OPTIONS_TEXT, onOpen, onInputChange, onDataLoadError, isLoadedDataError, isDisabled, isFullWidth, isSuccess, isError, isLoading, isRequired, ...restProps }) => {
|
|
7
|
+
const { options, onInputChange: propsOnInputChange, onOpen: propsOnOpen, isLoadingData, isLoadingError, isAllow, hintText, } = useAsyncOptions({
|
|
8
8
|
fetchOptions,
|
|
9
9
|
fetchDelayMs,
|
|
10
10
|
minSymbolsToFetch,
|
|
11
|
+
shouldFetch,
|
|
11
12
|
onDataLoadError,
|
|
12
13
|
});
|
|
13
14
|
const handleInputChange = (event, search, reason) => {
|
|
@@ -21,7 +22,15 @@ export const useLogic = ({ fetchOptions, fetchDelayMs = DEFAULT_DELAY_MS, minSym
|
|
|
21
22
|
propsOnOpen(defaultSearch, defaultValue);
|
|
22
23
|
onOpen?.(event);
|
|
23
24
|
};
|
|
24
|
-
const
|
|
25
|
+
const getEmptyOptionsText = () => {
|
|
26
|
+
if (isAllow) {
|
|
27
|
+
return noOptionsText;
|
|
28
|
+
}
|
|
29
|
+
if (hintText !== '') {
|
|
30
|
+
return hintText;
|
|
31
|
+
}
|
|
32
|
+
return DEFAULT_EMPTY_OPTIONS_TEXT;
|
|
33
|
+
};
|
|
25
34
|
return {
|
|
26
35
|
autocompleteProps: {
|
|
27
36
|
options,
|
|
@@ -32,7 +41,7 @@ export const useLogic = ({ fetchOptions, fetchDelayMs = DEFAULT_DELAY_MS, minSym
|
|
|
32
41
|
required: isRequired,
|
|
33
42
|
success: isSuccess,
|
|
34
43
|
onOpen: handleOpen,
|
|
35
|
-
noOptionsText:
|
|
44
|
+
noOptionsText: getEmptyOptionsText(),
|
|
36
45
|
onInputChange: handleInputChange,
|
|
37
46
|
isLoadedDataError: isLoadedDataError ?? isLoadingError,
|
|
38
47
|
getOptionLabel,
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare const makeFetchOptions: (options: string[]) => import("@vitest/spy").Mock<import("@vitest/spy").Procedure>;
|
|
2
|
+
export declare const makeEmptyFetchOptions: () => import("@vitest/spy").Mock<import("@vitest/spy").Procedure>;
|
|
3
|
+
export declare const makeErrorFetchOptions: (error: Error) => import("@vitest/spy").Mock<import("@vitest/spy").Procedure>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AutocompleteInputChangeReason } from '@mui/material/Autocomplete';
|
|
2
|
+
import { type ShouldFetch } from './types';
|
|
2
3
|
type FetchResult<TOptions> = {
|
|
3
4
|
options: TOptions[];
|
|
4
5
|
meta?: {
|
|
@@ -37,6 +38,10 @@ type UseAsyncOptionsParams<TOptions> = {
|
|
|
37
38
|
* Минимальное количество символов для выполнения запроса
|
|
38
39
|
*/
|
|
39
40
|
minSymbolsToFetch: number;
|
|
41
|
+
/**
|
|
42
|
+
* Функция, задающая правила валидации запроса
|
|
43
|
+
*/
|
|
44
|
+
shouldFetch?: ShouldFetch;
|
|
40
45
|
/**
|
|
41
46
|
* Задержка для debounce
|
|
42
47
|
*/
|
|
@@ -57,7 +62,7 @@ type UseAsyncOptionsParams<TOptions> = {
|
|
|
57
62
|
* Хук для асинхронной загрузки опций в компонентах с поиском.
|
|
58
63
|
* Управляет задержкой, отменой запросов и состояниями загрузки.
|
|
59
64
|
*/
|
|
60
|
-
export declare const useAsyncOptions: <TOptions>({ fetchOptions, fetchDelayMs, minSymbolsToFetch, onDataLoadError, isSmoothLoading, }: UseAsyncOptionsParams<TOptions>) => {
|
|
65
|
+
export declare const useAsyncOptions: <TOptions>({ fetchOptions, fetchDelayMs, minSymbolsToFetch, shouldFetch, onDataLoadError, isSmoothLoading, }: UseAsyncOptionsParams<TOptions>) => {
|
|
61
66
|
options: TOptions[];
|
|
62
67
|
meta: {
|
|
63
68
|
isAllDataLoaded: boolean;
|
|
@@ -65,6 +70,8 @@ export declare const useAsyncOptions: <TOptions>({ fetchOptions, fetchDelayMs, m
|
|
|
65
70
|
isLoadingData: boolean;
|
|
66
71
|
onRetry: (search: string) => void;
|
|
67
72
|
isLoadingError: boolean;
|
|
73
|
+
isAllow: boolean;
|
|
74
|
+
hintText: string;
|
|
68
75
|
debouncedFetchOptions: import("debounce").DebouncedFunction<(search: string) => void>;
|
|
69
76
|
onInputChange: (search: string, reason?: AutocompleteInputChangeReason) => void;
|
|
70
77
|
onOpen: (defaultSearch?: string, defaultValue?: TOptions | null | undefined, isAlwaysTriggerOnOpen?: boolean) => void;
|
|
@@ -5,12 +5,14 @@ import { useLoadingState } from '../useLoadingState';
|
|
|
5
5
|
* Хук для асинхронной загрузки опций в компонентах с поиском.
|
|
6
6
|
* Управляет задержкой, отменой запросов и состояниями загрузки.
|
|
7
7
|
*/
|
|
8
|
-
export const useAsyncOptions = ({ fetchOptions, fetchDelayMs, minSymbolsToFetch, onDataLoadError, isSmoothLoading, }) => {
|
|
8
|
+
export const useAsyncOptions = ({ fetchOptions, fetchDelayMs, minSymbolsToFetch, shouldFetch, onDataLoadError, isSmoothLoading, }) => {
|
|
9
9
|
const [isLoadingData, setIsLoadingData] = useState(false);
|
|
10
10
|
const [isFirstOpen, setIsFirstOpen] = useState(true);
|
|
11
11
|
const [isLoadingError, setIsLoadingError] = useState(false);
|
|
12
12
|
const abortControllerRef = useRef(null);
|
|
13
13
|
const { isShowLoader } = useLoadingState(isLoadingData);
|
|
14
|
+
const [isAllow, setIsAllow] = useState(true);
|
|
15
|
+
const [hintText, setHintText] = useState('');
|
|
14
16
|
const [options, setOptions] = useState({
|
|
15
17
|
options: [],
|
|
16
18
|
});
|
|
@@ -39,17 +41,32 @@ export const useAsyncOptions = ({ fetchOptions, fetchDelayMs, minSymbolsToFetch,
|
|
|
39
41
|
abortControllerRef.current = null;
|
|
40
42
|
});
|
|
41
43
|
}, fetchDelayMs), [fetchOptions, fetchDelayMs]);
|
|
42
|
-
const
|
|
44
|
+
const isAllowedSearch = (search) => {
|
|
43
45
|
if ((!search.length && minSymbolsToFetch > 0) ||
|
|
44
46
|
search.length < minSymbolsToFetch) {
|
|
47
|
+
setHintText('');
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
const shouldFetchResult = shouldFetch?.(search);
|
|
51
|
+
if (shouldFetchResult && !shouldFetchResult.isAllow) {
|
|
52
|
+
setHintText(search.length !== 0 ? shouldFetchResult.reason : '');
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
return true;
|
|
56
|
+
};
|
|
57
|
+
const onInputChange = (search, reason) => {
|
|
58
|
+
if (!isAllowedSearch(search)) {
|
|
45
59
|
if (debouncedFetchOptions.isPending) {
|
|
46
60
|
debouncedFetchOptions.clear();
|
|
47
61
|
}
|
|
48
62
|
setIsLoadingData(false);
|
|
49
63
|
setOptions({ options: [] });
|
|
64
|
+
setIsAllow(false);
|
|
50
65
|
return;
|
|
51
66
|
}
|
|
52
67
|
if (!isFirstOpen) {
|
|
68
|
+
setIsAllow(true);
|
|
69
|
+
setHintText('');
|
|
53
70
|
if (reason === 'input') {
|
|
54
71
|
setIsLoadingData(true);
|
|
55
72
|
}
|
|
@@ -59,7 +76,7 @@ export const useAsyncOptions = ({ fetchOptions, fetchDelayMs, minSymbolsToFetch,
|
|
|
59
76
|
const onOpen = (defaultSearch = '', defaultValue, isAlwaysTriggerOnOpen = false) => {
|
|
60
77
|
if (isFirstOpen || isAlwaysTriggerOnOpen) {
|
|
61
78
|
setIsFirstOpen(false);
|
|
62
|
-
if (defaultValue ||
|
|
79
|
+
if (defaultValue || isAllowedSearch(defaultSearch)) {
|
|
63
80
|
setIsLoadingData(true);
|
|
64
81
|
debouncedFetchOptions(defaultSearch);
|
|
65
82
|
}
|
|
@@ -78,6 +95,8 @@ export const useAsyncOptions = ({ fetchOptions, fetchDelayMs, minSymbolsToFetch,
|
|
|
78
95
|
isLoadingData: isSmoothLoading ? isShowLoader : isLoadingData,
|
|
79
96
|
onRetry,
|
|
80
97
|
isLoadingError,
|
|
98
|
+
isAllow,
|
|
99
|
+
hintText,
|
|
81
100
|
debouncedFetchOptions,
|
|
82
101
|
onInputChange,
|
|
83
102
|
onOpen,
|
|
@@ -9,5 +9,6 @@ export declare const AsyncAutocomplete: <TOptions, TMultiple extends boolean = f
|
|
|
9
9
|
fetchDelayMs?: number | undefined;
|
|
10
10
|
fetchOptions: (search: string, signal?: AbortSignal | undefined) => Promise<TOptions[]>;
|
|
11
11
|
minSymbolsToFetch?: number | undefined;
|
|
12
|
+
shouldFetch?: import("../useAsyncOptions").ShouldFetch | undefined;
|
|
12
13
|
onDataLoadError?: ((error: Error) => void) | undefined;
|
|
13
14
|
} & import("react").RefAttributes<unknown>) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | null;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type AutocompleteProps } from '../Autocomplete';
|
|
2
|
+
import { type ShouldFetch } from '../useAsyncOptions';
|
|
2
3
|
export type AsyncAutocompleteProps<TOptions, TMultiple extends boolean = false, TDisableClearable extends boolean = false, TFreeSolo extends boolean = false> = Omit<AutocompleteProps<TOptions, TMultiple, TDisableClearable, TFreeSolo>, 'inputValue' | 'loading' | 'disabled' | 'fullWidth' | 'error' | 'required' | 'success' | 'options' | 'filterOptions'> & {
|
|
3
4
|
/**
|
|
4
5
|
* Если `true`, будет показан индикатор загрузки
|
|
@@ -59,6 +60,10 @@ export type AsyncAutocompleteProps<TOptions, TMultiple extends boolean = false,
|
|
|
59
60
|
* @default 0
|
|
60
61
|
*/
|
|
61
62
|
minSymbolsToFetch?: number;
|
|
63
|
+
/**
|
|
64
|
+
* Функция, задающая правила валидации запроса
|
|
65
|
+
*/
|
|
66
|
+
shouldFetch?: ShouldFetch;
|
|
62
67
|
/**
|
|
63
68
|
* Функция для обработки ошибок загрузки данных
|
|
64
69
|
*/
|
|
@@ -2,7 +2,7 @@ import type { AutocompleteInputChangeReason } from '@mui/material/Autocomplete';
|
|
|
2
2
|
import { type SyntheticEvent } from 'react';
|
|
3
3
|
import { type AsyncAutocompleteProps } from '../types';
|
|
4
4
|
type UseLogicParams<TOptions, TMultiple extends boolean, TDisableClearable extends boolean, TFreeSolo extends boolean> = AsyncAutocompleteProps<TOptions, TMultiple, TDisableClearable, TFreeSolo>;
|
|
5
|
-
export declare const useLogic: <TOptions, TMultiple extends boolean, TDisableClearable extends boolean, TFreeSolo extends boolean>({ fetchOptions, fetchDelayMs, minSymbolsToFetch, defaultValue, getOptionLabel, noOptionsText, onOpen, onInputChange, onDataLoadError, isLoadedDataError, isDisabled, isFullWidth, isSuccess, isError, isLoading, isRequired, ...restProps }: UseLogicParams<TOptions, TMultiple, TDisableClearable, TFreeSolo>) => {
|
|
5
|
+
export declare const useLogic: <TOptions, TMultiple extends boolean, TDisableClearable extends boolean, TFreeSolo extends boolean>({ fetchOptions, fetchDelayMs, minSymbolsToFetch, shouldFetch, defaultValue, getOptionLabel, noOptionsText, onOpen, onInputChange, onDataLoadError, isLoadedDataError, isDisabled, isFullWidth, isSuccess, isError, isLoading, isRequired, ...restProps }: UseLogicParams<TOptions, TMultiple, TDisableClearable, TFreeSolo>) => {
|
|
6
6
|
autocompleteProps: {
|
|
7
7
|
classes?: Partial<import("@mui/material/Autocomplete").AutocompleteClasses> | undefined;
|
|
8
8
|
defaultChecked?: boolean | undefined;
|
|
@@ -6,11 +6,12 @@ const constants_1 = require("../constants");
|
|
|
6
6
|
/* Стандартный фильтр. Необходим для правильного отображения опций, при асинхронном поиске
|
|
7
7
|
* см. https://track.astral.ru/soft/browse/UIKIT-2823 */
|
|
8
8
|
const filterOptions = (options) => options;
|
|
9
|
-
const useLogic = ({ fetchOptions, fetchDelayMs = constants_1.DEFAULT_DELAY_MS, minSymbolsToFetch = constants_1.DEFAULT_MIN_SEARCH_CHARACTERS, defaultValue, getOptionLabel, noOptionsText = constants_1.DEFAULT_NO_OPTIONS_TEXT, onOpen, onInputChange, onDataLoadError, isLoadedDataError, isDisabled, isFullWidth, isSuccess, isError, isLoading, isRequired, ...restProps }) => {
|
|
10
|
-
const { options, onInputChange: propsOnInputChange, onOpen: propsOnOpen, isLoadingData, isLoadingError, } = (0, useAsyncOptions_1.useAsyncOptions)({
|
|
9
|
+
const useLogic = ({ fetchOptions, fetchDelayMs = constants_1.DEFAULT_DELAY_MS, minSymbolsToFetch = constants_1.DEFAULT_MIN_SEARCH_CHARACTERS, shouldFetch, defaultValue, getOptionLabel, noOptionsText = constants_1.DEFAULT_NO_OPTIONS_TEXT, onOpen, onInputChange, onDataLoadError, isLoadedDataError, isDisabled, isFullWidth, isSuccess, isError, isLoading, isRequired, ...restProps }) => {
|
|
10
|
+
const { options, onInputChange: propsOnInputChange, onOpen: propsOnOpen, isLoadingData, isLoadingError, isAllow, hintText, } = (0, useAsyncOptions_1.useAsyncOptions)({
|
|
11
11
|
fetchOptions,
|
|
12
12
|
fetchDelayMs,
|
|
13
13
|
minSymbolsToFetch,
|
|
14
|
+
shouldFetch,
|
|
14
15
|
onDataLoadError,
|
|
15
16
|
});
|
|
16
17
|
const handleInputChange = (event, search, reason) => {
|
|
@@ -24,7 +25,15 @@ const useLogic = ({ fetchOptions, fetchDelayMs = constants_1.DEFAULT_DELAY_MS, m
|
|
|
24
25
|
propsOnOpen(defaultSearch, defaultValue);
|
|
25
26
|
onOpen?.(event);
|
|
26
27
|
};
|
|
27
|
-
const
|
|
28
|
+
const getEmptyOptionsText = () => {
|
|
29
|
+
if (isAllow) {
|
|
30
|
+
return noOptionsText;
|
|
31
|
+
}
|
|
32
|
+
if (hintText !== '') {
|
|
33
|
+
return hintText;
|
|
34
|
+
}
|
|
35
|
+
return constants_1.DEFAULT_EMPTY_OPTIONS_TEXT;
|
|
36
|
+
};
|
|
28
37
|
return {
|
|
29
38
|
autocompleteProps: {
|
|
30
39
|
options,
|
|
@@ -35,7 +44,7 @@ const useLogic = ({ fetchOptions, fetchDelayMs = constants_1.DEFAULT_DELAY_MS, m
|
|
|
35
44
|
required: isRequired,
|
|
36
45
|
success: isSuccess,
|
|
37
46
|
onOpen: handleOpen,
|
|
38
|
-
noOptionsText:
|
|
47
|
+
noOptionsText: getEmptyOptionsText(),
|
|
39
48
|
onInputChange: handleInputChange,
|
|
40
49
|
isLoadedDataError: isLoadedDataError ?? isLoadingError,
|
|
41
50
|
getOptionLabel,
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare const makeFetchOptions: (options: string[]) => import("@vitest/spy").Mock<import("@vitest/spy").Procedure>;
|
|
2
|
+
export declare const makeEmptyFetchOptions: () => import("@vitest/spy").Mock<import("@vitest/spy").Procedure>;
|
|
3
|
+
export declare const makeErrorFetchOptions: (error: Error) => import("@vitest/spy").Mock<import("@vitest/spy").Procedure>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.makeErrorFetchOptions = exports.makeEmptyFetchOptions = exports.makeFetchOptions = void 0;
|
|
4
|
+
const makeFetchOptions = (options) => vi.fn().mockResolvedValue({ options });
|
|
5
|
+
exports.makeFetchOptions = makeFetchOptions;
|
|
6
|
+
const makeEmptyFetchOptions = () => vi.fn().mockResolvedValue({ options: [] });
|
|
7
|
+
exports.makeEmptyFetchOptions = makeEmptyFetchOptions;
|
|
8
|
+
const makeErrorFetchOptions = (error) => vi.fn().mockRejectedValue(error);
|
|
9
|
+
exports.makeErrorFetchOptions = makeErrorFetchOptions;
|
|
@@ -14,4 +14,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./types"), exports);
|
|
17
18
|
__exportStar(require("./useAsyncOptions"), exports);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AutocompleteInputChangeReason } from '@mui/material/Autocomplete';
|
|
2
|
+
import { type ShouldFetch } from './types';
|
|
2
3
|
type FetchResult<TOptions> = {
|
|
3
4
|
options: TOptions[];
|
|
4
5
|
meta?: {
|
|
@@ -37,6 +38,10 @@ type UseAsyncOptionsParams<TOptions> = {
|
|
|
37
38
|
* Минимальное количество символов для выполнения запроса
|
|
38
39
|
*/
|
|
39
40
|
minSymbolsToFetch: number;
|
|
41
|
+
/**
|
|
42
|
+
* Функция, задающая правила валидации запроса
|
|
43
|
+
*/
|
|
44
|
+
shouldFetch?: ShouldFetch;
|
|
40
45
|
/**
|
|
41
46
|
* Задержка для debounce
|
|
42
47
|
*/
|
|
@@ -57,7 +62,7 @@ type UseAsyncOptionsParams<TOptions> = {
|
|
|
57
62
|
* Хук для асинхронной загрузки опций в компонентах с поиском.
|
|
58
63
|
* Управляет задержкой, отменой запросов и состояниями загрузки.
|
|
59
64
|
*/
|
|
60
|
-
export declare const useAsyncOptions: <TOptions>({ fetchOptions, fetchDelayMs, minSymbolsToFetch, onDataLoadError, isSmoothLoading, }: UseAsyncOptionsParams<TOptions>) => {
|
|
65
|
+
export declare const useAsyncOptions: <TOptions>({ fetchOptions, fetchDelayMs, minSymbolsToFetch, shouldFetch, onDataLoadError, isSmoothLoading, }: UseAsyncOptionsParams<TOptions>) => {
|
|
61
66
|
options: TOptions[];
|
|
62
67
|
meta: {
|
|
63
68
|
isAllDataLoaded: boolean;
|
|
@@ -65,6 +70,8 @@ export declare const useAsyncOptions: <TOptions>({ fetchOptions, fetchDelayMs, m
|
|
|
65
70
|
isLoadingData: boolean;
|
|
66
71
|
onRetry: (search: string) => void;
|
|
67
72
|
isLoadingError: boolean;
|
|
73
|
+
isAllow: boolean;
|
|
74
|
+
hintText: string;
|
|
68
75
|
debouncedFetchOptions: import("debounce").DebouncedFunction<(search: string) => void>;
|
|
69
76
|
onInputChange: (search: string, reason?: AutocompleteInputChangeReason) => void;
|
|
70
77
|
onOpen: (defaultSearch?: string, defaultValue?: TOptions | null | undefined, isAlwaysTriggerOnOpen?: boolean) => void;
|
|
@@ -8,12 +8,14 @@ const useLoadingState_1 = require("../useLoadingState");
|
|
|
8
8
|
* Хук для асинхронной загрузки опций в компонентах с поиском.
|
|
9
9
|
* Управляет задержкой, отменой запросов и состояниями загрузки.
|
|
10
10
|
*/
|
|
11
|
-
const useAsyncOptions = ({ fetchOptions, fetchDelayMs, minSymbolsToFetch, onDataLoadError, isSmoothLoading, }) => {
|
|
11
|
+
const useAsyncOptions = ({ fetchOptions, fetchDelayMs, minSymbolsToFetch, shouldFetch, onDataLoadError, isSmoothLoading, }) => {
|
|
12
12
|
const [isLoadingData, setIsLoadingData] = (0, react_1.useState)(false);
|
|
13
13
|
const [isFirstOpen, setIsFirstOpen] = (0, react_1.useState)(true);
|
|
14
14
|
const [isLoadingError, setIsLoadingError] = (0, react_1.useState)(false);
|
|
15
15
|
const abortControllerRef = (0, react_1.useRef)(null);
|
|
16
16
|
const { isShowLoader } = (0, useLoadingState_1.useLoadingState)(isLoadingData);
|
|
17
|
+
const [isAllow, setIsAllow] = (0, react_1.useState)(true);
|
|
18
|
+
const [hintText, setHintText] = (0, react_1.useState)('');
|
|
17
19
|
const [options, setOptions] = (0, react_1.useState)({
|
|
18
20
|
options: [],
|
|
19
21
|
});
|
|
@@ -42,17 +44,32 @@ const useAsyncOptions = ({ fetchOptions, fetchDelayMs, minSymbolsToFetch, onData
|
|
|
42
44
|
abortControllerRef.current = null;
|
|
43
45
|
});
|
|
44
46
|
}, fetchDelayMs), [fetchOptions, fetchDelayMs]);
|
|
45
|
-
const
|
|
47
|
+
const isAllowedSearch = (search) => {
|
|
46
48
|
if ((!search.length && minSymbolsToFetch > 0) ||
|
|
47
49
|
search.length < minSymbolsToFetch) {
|
|
50
|
+
setHintText('');
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
const shouldFetchResult = shouldFetch?.(search);
|
|
54
|
+
if (shouldFetchResult && !shouldFetchResult.isAllow) {
|
|
55
|
+
setHintText(search.length !== 0 ? shouldFetchResult.reason : '');
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
return true;
|
|
59
|
+
};
|
|
60
|
+
const onInputChange = (search, reason) => {
|
|
61
|
+
if (!isAllowedSearch(search)) {
|
|
48
62
|
if (debouncedFetchOptions.isPending) {
|
|
49
63
|
debouncedFetchOptions.clear();
|
|
50
64
|
}
|
|
51
65
|
setIsLoadingData(false);
|
|
52
66
|
setOptions({ options: [] });
|
|
67
|
+
setIsAllow(false);
|
|
53
68
|
return;
|
|
54
69
|
}
|
|
55
70
|
if (!isFirstOpen) {
|
|
71
|
+
setIsAllow(true);
|
|
72
|
+
setHintText('');
|
|
56
73
|
if (reason === 'input') {
|
|
57
74
|
setIsLoadingData(true);
|
|
58
75
|
}
|
|
@@ -62,7 +79,7 @@ const useAsyncOptions = ({ fetchOptions, fetchDelayMs, minSymbolsToFetch, onData
|
|
|
62
79
|
const onOpen = (defaultSearch = '', defaultValue, isAlwaysTriggerOnOpen = false) => {
|
|
63
80
|
if (isFirstOpen || isAlwaysTriggerOnOpen) {
|
|
64
81
|
setIsFirstOpen(false);
|
|
65
|
-
if (defaultValue ||
|
|
82
|
+
if (defaultValue || isAllowedSearch(defaultSearch)) {
|
|
66
83
|
setIsLoadingData(true);
|
|
67
84
|
debouncedFetchOptions(defaultSearch);
|
|
68
85
|
}
|
|
@@ -81,6 +98,8 @@ const useAsyncOptions = ({ fetchOptions, fetchDelayMs, minSymbolsToFetch, onData
|
|
|
81
98
|
isLoadingData: isSmoothLoading ? isShowLoader : isLoadingData,
|
|
82
99
|
onRetry,
|
|
83
100
|
isLoadingError,
|
|
101
|
+
isAllow,
|
|
102
|
+
hintText,
|
|
84
103
|
debouncedFetchOptions,
|
|
85
104
|
onInputChange,
|
|
86
105
|
onOpen,
|
package/package.json
CHANGED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { faker } from '@ui/dev/faker';
|
|
2
|
-
import axios from 'axios';
|
|
3
|
-
import AxiosMockAdapter from 'axios-mock-adapter';
|
|
4
|
-
const getRandomDelay = () => Math.random() * 100 + 100; // 200-500ms
|
|
5
|
-
const axiosInstance = axios.create();
|
|
6
|
-
const mock = new AxiosMockAdapter(axiosInstance, {
|
|
7
|
-
delayResponse: getRandomDelay(),
|
|
8
|
-
});
|
|
9
|
-
const generateFakeUsers = (count) => {
|
|
10
|
-
return Array.from({ length: count - 1 }).map(() => ({
|
|
11
|
-
id: faker.string.uuid(),
|
|
12
|
-
name: faker.person.fullName(),
|
|
13
|
-
}));
|
|
14
|
-
};
|
|
15
|
-
const FAKE_USERS = [
|
|
16
|
-
{
|
|
17
|
-
id: '1',
|
|
18
|
-
name: 'Иван Третьяков',
|
|
19
|
-
},
|
|
20
|
-
...generateFakeUsers(30),
|
|
21
|
-
];
|
|
22
|
-
mock.onGet('/api/users').reply((config) => {
|
|
23
|
-
const search = new URLSearchParams(config.params).get('name_like') || '';
|
|
24
|
-
const users = FAKE_USERS.filter((user) => user.name.toLowerCase().includes(search.toLowerCase()));
|
|
25
|
-
return [200, users];
|
|
26
|
-
});
|
|
27
|
-
mock.onGet('/api/users/id').reply((config) => {
|
|
28
|
-
const userId = new URLSearchParams(config.params).get('id') || '';
|
|
29
|
-
const user = FAKE_USERS.find((fakeUser) => fakeUser.id === userId);
|
|
30
|
-
return [200, user];
|
|
31
|
-
});
|
|
32
|
-
export const getFakeUsers = async (search, options) => {
|
|
33
|
-
const response = await axiosInstance.get('api/users', {
|
|
34
|
-
...options,
|
|
35
|
-
params: { ...options?.params, name_like: search },
|
|
36
|
-
});
|
|
37
|
-
return response.data;
|
|
38
|
-
};
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.getFakeUsers = void 0;
|
|
7
|
-
const faker_1 = require("@ui/dev/faker");
|
|
8
|
-
const axios_1 = __importDefault(require("axios"));
|
|
9
|
-
const axios_mock_adapter_1 = __importDefault(require("axios-mock-adapter"));
|
|
10
|
-
const getRandomDelay = () => Math.random() * 100 + 100; // 200-500ms
|
|
11
|
-
const axiosInstance = axios_1.default.create();
|
|
12
|
-
const mock = new axios_mock_adapter_1.default(axiosInstance, {
|
|
13
|
-
delayResponse: getRandomDelay(),
|
|
14
|
-
});
|
|
15
|
-
const generateFakeUsers = (count) => {
|
|
16
|
-
return Array.from({ length: count - 1 }).map(() => ({
|
|
17
|
-
id: faker_1.faker.string.uuid(),
|
|
18
|
-
name: faker_1.faker.person.fullName(),
|
|
19
|
-
}));
|
|
20
|
-
};
|
|
21
|
-
const FAKE_USERS = [
|
|
22
|
-
{
|
|
23
|
-
id: '1',
|
|
24
|
-
name: 'Иван Третьяков',
|
|
25
|
-
},
|
|
26
|
-
...generateFakeUsers(30),
|
|
27
|
-
];
|
|
28
|
-
mock.onGet('/api/users').reply((config) => {
|
|
29
|
-
const search = new URLSearchParams(config.params).get('name_like') || '';
|
|
30
|
-
const users = FAKE_USERS.filter((user) => user.name.toLowerCase().includes(search.toLowerCase()));
|
|
31
|
-
return [200, users];
|
|
32
|
-
});
|
|
33
|
-
mock.onGet('/api/users/id').reply((config) => {
|
|
34
|
-
const userId = new URLSearchParams(config.params).get('id') || '';
|
|
35
|
-
const user = FAKE_USERS.find((fakeUser) => fakeUser.id === userId);
|
|
36
|
-
return [200, user];
|
|
37
|
-
});
|
|
38
|
-
const getFakeUsers = async (search, options) => {
|
|
39
|
-
const response = await axiosInstance.get('api/users', {
|
|
40
|
-
...options,
|
|
41
|
-
params: { ...options?.params, name_like: search },
|
|
42
|
-
});
|
|
43
|
-
return response.data;
|
|
44
|
-
};
|
|
45
|
-
exports.getFakeUsers = getFakeUsers;
|