@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.
Files changed (33) hide show
  1. package/components/AsyncAutocomplete/AsyncAutocomplete.d.ts +1 -0
  2. package/components/AsyncAutocomplete/types.d.ts +5 -0
  3. package/components/AsyncAutocomplete/useLogic/useLogic.d.ts +1 -1
  4. package/components/AsyncAutocomplete/useLogic/useLogic.js +13 -4
  5. package/components/useAsyncOptions/faker.d.ts +3 -0
  6. package/components/useAsyncOptions/faker.js +3 -0
  7. package/components/useAsyncOptions/index.d.ts +1 -0
  8. package/components/useAsyncOptions/index.js +1 -0
  9. package/components/useAsyncOptions/public.d.ts +1 -0
  10. package/components/useAsyncOptions/public.js +1 -0
  11. package/components/useAsyncOptions/types.d.ts +7 -0
  12. package/components/useAsyncOptions/types.js +1 -0
  13. package/components/useAsyncOptions/useAsyncOptions.d.ts +8 -1
  14. package/components/useAsyncOptions/useAsyncOptions.js +22 -3
  15. package/node/components/AsyncAutocomplete/AsyncAutocomplete.d.ts +1 -0
  16. package/node/components/AsyncAutocomplete/types.d.ts +5 -0
  17. package/node/components/AsyncAutocomplete/useLogic/useLogic.d.ts +1 -1
  18. package/node/components/AsyncAutocomplete/useLogic/useLogic.js +13 -4
  19. package/node/components/useAsyncOptions/faker.d.ts +3 -0
  20. package/node/components/useAsyncOptions/faker.js +9 -0
  21. package/node/components/useAsyncOptions/index.d.ts +1 -0
  22. package/node/components/useAsyncOptions/index.js +1 -0
  23. package/node/components/useAsyncOptions/public.d.ts +1 -0
  24. package/node/components/useAsyncOptions/public.js +1 -0
  25. package/node/components/useAsyncOptions/types.d.ts +7 -0
  26. package/node/components/useAsyncOptions/types.js +2 -0
  27. package/node/components/useAsyncOptions/useAsyncOptions.d.ts +8 -1
  28. package/node/components/useAsyncOptions/useAsyncOptions.js +22 -3
  29. package/package.json +1 -1
  30. package/components/AsyncAutocomplete/faker.d.ts +0 -2
  31. package/components/AsyncAutocomplete/faker.js +0 -38
  32. package/node/components/AsyncAutocomplete/faker.d.ts +0 -2
  33. 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 emptyOptionsText = options.length === 0 ? noOptionsText : DEFAULT_EMPTY_OPTIONS_TEXT;
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: emptyOptionsText,
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,3 @@
1
+ export const makeFetchOptions = (options) => vi.fn().mockResolvedValue({ options });
2
+ export const makeEmptyFetchOptions = () => vi.fn().mockResolvedValue({ options: [] });
3
+ export const makeErrorFetchOptions = (error) => vi.fn().mockRejectedValue(error);
@@ -1 +1,2 @@
1
1
  export * from './useAsyncOptions';
2
+ export * from './types';
@@ -1 +1,2 @@
1
1
  export * from './useAsyncOptions';
2
+ export * from './types';
@@ -1 +1,2 @@
1
+ export * from './types';
1
2
  export * from './useAsyncOptions';
@@ -1 +1,2 @@
1
+ export * from './types';
1
2
  export * from './useAsyncOptions';
@@ -0,0 +1,7 @@
1
+ export type ShouldFetchResult = {
2
+ isAllow: true;
3
+ } | {
4
+ isAllow: false;
5
+ reason: string;
6
+ };
7
+ export type ShouldFetch = (inputValue: string) => ShouldFetchResult;
@@ -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 onInputChange = (search, reason) => {
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 || minSymbolsToFetch === 0) {
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 emptyOptionsText = options.length === 0 ? noOptionsText : constants_1.DEFAULT_EMPTY_OPTIONS_TEXT;
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: emptyOptionsText,
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;
@@ -1 +1,2 @@
1
1
  export * from './useAsyncOptions';
2
+ export * from './types';
@@ -15,3 +15,4 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./useAsyncOptions"), exports);
18
+ __exportStar(require("./types"), exports);
@@ -1 +1,2 @@
1
+ export * from './types';
1
2
  export * from './useAsyncOptions';
@@ -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);
@@ -0,0 +1,7 @@
1
+ export type ShouldFetchResult = {
2
+ isAllow: true;
3
+ } | {
4
+ isAllow: false;
5
+ reason: string;
6
+ };
7
+ export type ShouldFetch = (inputValue: string) => ShouldFetchResult;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -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 onInputChange = (search, reason) => {
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 || minSymbolsToFetch === 0) {
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@astral/ui",
3
- "version": "4.93.0",
3
+ "version": "4.94.0",
4
4
  "browser": "./index.js",
5
5
  "main": "./node/index.js",
6
6
  "dependencies": {
@@ -1,2 +0,0 @@
1
- import { type AxiosRequestConfig } from 'axios';
2
- export declare const getFakeUsers: (search: string, options?: AxiosRequestConfig) => Promise<any>;
@@ -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,2 +0,0 @@
1
- import { type AxiosRequestConfig } from 'axios';
2
- export declare const getFakeUsers: (search: string, options?: AxiosRequestConfig) => Promise<any>;
@@ -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;