@snack-uikit/fields 0.25.3-preview-4ab4d581.0 → 0.26.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 (29) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +2 -0
  3. package/dist/components/FieldSelect/FieldSelectMultiple.d.ts +1 -0
  4. package/dist/components/FieldSelect/FieldSelectMultiple.js +10 -6
  5. package/dist/components/FieldSelect/FieldSelectSingle.d.ts +1 -0
  6. package/dist/components/FieldSelect/FieldSelectSingle.js +15 -9
  7. package/dist/components/FieldSelect/hooks.d.ts +5 -0
  8. package/dist/components/FieldSelect/hooks.js +25 -4
  9. package/dist/components/FieldSelect/legacy/index.d.ts +0 -1
  10. package/dist/components/FieldSelect/legacy/index.js +0 -1
  11. package/dist/components/FieldSelect/types.d.ts +2 -0
  12. package/dist/components/FieldSelect/utils/checkisSearchUnavailable.d.ts +7 -0
  13. package/dist/components/FieldSelect/utils/checkisSearchUnavailable.js +3 -0
  14. package/dist/components/FieldSelect/utils/getValueByPath.d.ts +1 -0
  15. package/dist/components/FieldSelect/utils/getValueByPath.js +17 -0
  16. package/dist/components/FieldSelect/utils/index.d.ts +2 -0
  17. package/dist/components/FieldSelect/utils/index.js +2 -0
  18. package/package.json +2 -2
  19. package/src/components/FieldSelect/FieldSelectMultiple.tsx +10 -5
  20. package/src/components/FieldSelect/FieldSelectSingle.tsx +13 -9
  21. package/src/components/FieldSelect/hooks.ts +41 -2
  22. package/src/components/FieldSelect/legacy/index.ts +0 -1
  23. package/src/components/FieldSelect/types.ts +3 -0
  24. package/src/components/FieldSelect/utils/checkisSearchUnavailable.ts +9 -0
  25. package/src/components/FieldSelect/utils/getValueByPath.ts +22 -0
  26. package/src/components/FieldSelect/utils/index.ts +2 -0
  27. package/dist/components/FieldSelect/legacy/hooks.d.ts +0 -5
  28. package/dist/components/FieldSelect/legacy/hooks.js +0 -19
  29. package/src/components/FieldSelect/legacy/hooks.ts +0 -28
package/CHANGELOG.md CHANGED
@@ -3,6 +3,18 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # 0.26.0 (2024-09-17)
7
+
8
+
9
+ ### Features
10
+
11
+ * **FF-00:** add autocomplete to FieldText ([76c21fa](https://github.com/cloud-ru-tech/snack-uikit/commit/76c21fa78b8177c4025bdace62fd1fbd66526726))
12
+ * **PDS-750:** Added simple search to field select ([dda75ec](https://github.com/cloud-ru-tech/snack-uikit/commit/dda75ecdffd9b6fa90adad40c90a842357aee78a))
13
+
14
+
15
+
16
+
17
+
6
18
  ## 0.25.2 (2024-09-06)
7
19
 
8
20
  ### Only dependencies have been changed
package/README.md CHANGED
@@ -158,6 +158,7 @@ const [isOpen, setIsOpen] = useState(false);
158
158
  size='s'
159
159
  validationState='error'
160
160
  prefixIcon={<PlaceholderSVG />}
161
+ enableFuzzySearch={true}
161
162
  />;
162
163
  ```
163
164
  ## Особенности работы FieldStepper-a
@@ -379,6 +380,7 @@ FieldStepper в основном предназначен для работы с
379
380
  | autocomplete | `boolean` | - | |
380
381
  | addOptionByEnter | `boolean` | - | |
381
382
  | open | `boolean` | - | |
383
+ | enableFuzzySearch | `boolean` | - | Включить нечеткий поиск |
382
384
  | onOpenChange | `(open: boolean) => void` | - | |
383
385
  | selectedOptionFormatter | `SelectedOptionFormatter` | - | |
384
386
  | untouchableScrollbars | `boolean` | - | Отключает возможность взаимодействовать со скролбарами мышью. |
@@ -21,6 +21,7 @@ export declare const FieldSelectMultiple: import("react").ForwardRefExoticCompon
21
21
  autocomplete?: boolean;
22
22
  addOptionByEnter?: boolean;
23
23
  open?: boolean;
24
+ enableFuzzySearch?: boolean;
24
25
  onOpenChange?(open: boolean): void;
25
26
  selectedOptionFormatter?: SelectedOptionFormatter;
26
27
  } & Pick<import("@snack-uikit/list").DroplistProps, "untouchableScrollbars" | "dataError" | "dataFiltered" | "noDataState" | "noResultsState" | "errorDataState">, "showCopyButton"> & import("react").RefAttributes<HTMLInputElement>>;
@@ -22,10 +22,9 @@ import { useValueControl } from '../../hooks';
22
22
  import { getValidationState } from '../../utils/getValidationState';
23
23
  import { FieldDecorator } from '../FieldDecorator';
24
24
  import { extractFieldDecoratorProps } from '../FieldDecorator/utils';
25
- import { useButtons, useHandleDeleteItem, useHandleOnKeyDown, useSearchInput } from './hooks';
26
- import { useFuzzySearch } from './legacy';
25
+ import { useButtons, useHandleDeleteItem, useHandleOnKeyDown, useSearch, useSearchInput } from './hooks';
27
26
  import styles from './styles.module.css';
28
- import { extractListProps, getArrowIcon, updateMultipleItems } from './utils';
27
+ import { checkisSearchUnavailable, extractListProps, getArrowIcon, updateMultipleItems } from './utils';
29
28
  const BASE_MIN_WIDTH = 4;
30
29
  const defaultSelectedOptionFormatter = item =>
31
30
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
@@ -33,7 +32,7 @@ const defaultSelectedOptionFormatter = item =>
33
32
  (item === null || item === void 0 ? void 0 : item.content.option) || '';
34
33
  export const FieldSelectMultiple = forwardRef((props, ref) => {
35
34
  var _a;
36
- const { id, name, placeholder, size = 's', options, value: valueProp, defaultValue, onChange: onChangeProp, disabled = false, readonly = false, searchable = true, showClearButton = true, onKeyDown: onInputKeyDownProp, validationState = 'default', search, autocomplete = false, prefixIcon, removeByBackspace = false, addOptionByEnter = false, untouchableScrollbars = false, open: openProp, onOpenChange, selectedOptionFormatter = defaultSelectedOptionFormatter } = props, rest = __rest(props, ["id", "name", "placeholder", "size", "options", "value", "defaultValue", "onChange", "disabled", "readonly", "searchable", "showClearButton", "onKeyDown", "validationState", "search", "autocomplete", "prefixIcon", "removeByBackspace", "addOptionByEnter", "untouchableScrollbars", "open", "onOpenChange", "selectedOptionFormatter"]);
35
+ const { id, name, placeholder, size = 's', options, value: valueProp, defaultValue, onChange: onChangeProp, disabled = false, readonly = false, searchable = true, showClearButton = true, onKeyDown: onInputKeyDownProp, validationState = 'default', search, autocomplete = false, prefixIcon, removeByBackspace = false, addOptionByEnter = false, untouchableScrollbars = false, open: openProp, enableFuzzySearch = true, onOpenChange, selectedOptionFormatter = defaultSelectedOptionFormatter } = props, rest = __rest(props, ["id", "name", "placeholder", "size", "options", "value", "defaultValue", "onChange", "disabled", "readonly", "searchable", "showClearButton", "onKeyDown", "validationState", "search", "autocomplete", "prefixIcon", "removeByBackspace", "addOptionByEnter", "untouchableScrollbars", "open", "enableFuzzySearch", "onOpenChange", "selectedOptionFormatter"]);
37
36
  const localRef = useRef(null);
38
37
  const inputPlugRef = useRef(null);
39
38
  const contentRef = useRef(null);
@@ -114,8 +113,13 @@ export const FieldSelectMultiple = forwardRef((props, ref) => {
114
113
  (_a = rest === null || rest === void 0 ? void 0 : rest.onBlur) === null || _a === void 0 ? void 0 : _a.call(rest, e);
115
114
  }
116
115
  };
117
- const fuzzySearch = useFuzzySearch(items);
118
- const result = autocomplete || !searchable || prevInputValue.current === inputValue ? items : fuzzySearch(inputValue);
116
+ const searchFunction = useSearch(items, enableFuzzySearch);
117
+ const isSearchUnavailable = checkisSearchUnavailable({
118
+ autocomplete,
119
+ searchable,
120
+ isSameValue: prevInputValue.current === inputValue,
121
+ });
122
+ const result = isSearchUnavailable ? items : searchFunction(inputValue);
119
123
  const fieldValidationState = getValidationState({ validationState, error: rest.error });
120
124
  return (_jsx(FieldDecorator, Object.assign({}, extractSupportProps(rest), extractFieldDecoratorProps(props), { validationState: fieldValidationState, children: _jsx(Droplist, Object.assign({}, extractListProps(props), { items: result, triggerElemRef: localRef, trigger: 'click', selection: {
121
125
  mode: 'multiple',
@@ -19,6 +19,7 @@ export declare const FieldSelectSingle: import("react").ForwardRefExoticComponen
19
19
  autocomplete?: boolean;
20
20
  addOptionByEnter?: boolean;
21
21
  open?: boolean;
22
+ enableFuzzySearch?: boolean;
22
23
  onOpenChange?(open: boolean): void;
23
24
  selectedOptionFormatter?: SelectedOptionFormatter;
24
25
  } & Pick<import("@snack-uikit/list").DroplistProps, "untouchableScrollbars" | "dataError" | "dataFiltered" | "noDataState" | "noResultsState" | "errorDataState"> & import("react").RefAttributes<HTMLInputElement>>;
@@ -21,16 +21,16 @@ import { useValueControl } from '../../hooks';
21
21
  import { getValidationState } from '../../utils/getValidationState';
22
22
  import { FieldDecorator } from '../FieldDecorator';
23
23
  import { extractFieldDecoratorProps } from '../FieldDecorator/utils';
24
- import { useButtons, useHandleOnKeyDown, useSearchInput } from './hooks';
25
- import { useFuzzySearch } from './legacy';
24
+ import { useButtons, useHandleOnKeyDown, useSearch, useSearchInput } from './hooks';
26
25
  import styles from './styles.module.css';
27
- import { extractListProps, getArrowIcon, updateItems } from './utils';
26
+ import { checkisSearchUnavailable, extractListProps, getArrowIcon, updateItems } from './utils';
28
27
  const defaultSelectedOptionFormatter = item =>
29
28
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
30
29
  // @ts-expect-error
31
30
  (item === null || item === void 0 ? void 0 : item.content.option) || '';
32
31
  export const FieldSelectSingle = forwardRef((props, ref) => {
33
- const { id, name, placeholder, size = 's', options, value: valueProp, defaultValue, onChange: onChangeProp, disabled = false, readonly = false, searchable = true, showCopyButton = true, showClearButton = true, onKeyDown: onInputKeyDownProp, required = false, validationState = 'default', search, autocomplete = false, prefixIcon, addOptionByEnter = false, untouchableScrollbars = false, open: openProp, onOpenChange, selectedOptionFormatter = defaultSelectedOptionFormatter } = props, rest = __rest(props, ["id", "name", "placeholder", "size", "options", "value", "defaultValue", "onChange", "disabled", "readonly", "searchable", "showCopyButton", "showClearButton", "onKeyDown", "required", "validationState", "search", "autocomplete", "prefixIcon", "addOptionByEnter", "untouchableScrollbars", "open", "onOpenChange", "selectedOptionFormatter"]);
32
+ var _a;
33
+ const { id, name, placeholder, size = 's', options, value: valueProp, defaultValue, onChange: onChangeProp, disabled = false, readonly = false, searchable = true, showCopyButton = true, showClearButton = true, onKeyDown: onInputKeyDownProp, required = false, validationState = 'default', search, autocomplete = false, prefixIcon, addOptionByEnter = false, untouchableScrollbars = false, open: openProp, onOpenChange, selectedOptionFormatter = defaultSelectedOptionFormatter, enableFuzzySearch = true } = props, rest = __rest(props, ["id", "name", "placeholder", "size", "options", "value", "defaultValue", "onChange", "disabled", "readonly", "searchable", "showCopyButton", "showClearButton", "onKeyDown", "required", "validationState", "search", "autocomplete", "prefixIcon", "addOptionByEnter", "untouchableScrollbars", "open", "onOpenChange", "selectedOptionFormatter", "enableFuzzySearch"]);
34
34
  const localRef = useRef(null);
35
35
  const [open = false, setOpen] = useValueControl({ value: openProp, onChange: onOpenChange });
36
36
  const [value, setValue] = useValueControl({
@@ -39,7 +39,10 @@ export const FieldSelectSingle = forwardRef((props, ref) => {
39
39
  onChange: onChangeProp,
40
40
  });
41
41
  const [{ selectedItem, items = [] }, setItems] = useState(() => updateItems({ options, value, currentItems: [], selectedItem: undefined }));
42
- const { inputValue, setInputValue, prevInputValue, updateInputValue } = useSearchInput(Object.assign(Object.assign({}, search), { defaultValue: selectedOptionFormatter(selectedItem), selectedOptionFormatter }));
42
+ const { inputValue, setInputValue, prevInputValue, updateInputValue } = useSearchInput(Object.assign(Object.assign({}, search), {
43
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
44
+ // @ts-expect-error
45
+ defaultValue: (_a = selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.content.option) !== null && _a !== void 0 ? _a : '', selectedOptionFormatter }));
43
46
  const prevSelectedItem = useRef(selectedItem);
44
47
  useLayoutEffect(() => {
45
48
  setItems(({ selectedItem }) => updateItems({ options, value, selectedItem }));
@@ -115,10 +118,13 @@ export const FieldSelectSingle = forwardRef((props, ref) => {
115
118
  }
116
119
  }
117
120
  };
118
- const fuzzySearch = useFuzzySearch(items);
119
- const result = autocomplete || !searchable || selectedOptionFormatter(selectedItem) === inputValue
120
- ? items
121
- : fuzzySearch(inputValue);
121
+ const filterFunction = useSearch(items, enableFuzzySearch);
122
+ const isSearchUnavailable = checkisSearchUnavailable({
123
+ autocomplete,
124
+ searchable,
125
+ isSameValue: selectedOptionFormatter(selectedItem) === inputValue,
126
+ });
127
+ const result = isSearchUnavailable ? items : filterFunction(inputValue);
122
128
  const fieldValidationState = getValidationState({ validationState, error: rest.error });
123
129
  return (_jsx(FieldDecorator, Object.assign({}, extractSupportProps(rest), extractFieldDecoratorProps(props), { validationState: fieldValidationState, children: _jsx(Droplist, Object.assign({}, extractListProps(props), { items: result, selection: {
124
130
  mode: 'single',
@@ -1,5 +1,6 @@
1
1
  import { KeyboardEvent, KeyboardEventHandler, MouseEvent, RefObject } from 'react';
2
2
  import { Handler } from 'uncontrollable';
3
+ import { ItemProps } from '@snack-uikit/list';
3
4
  import { ItemWithId, SearchState, SelectedOptionFormatter } from './types';
4
5
  type UseHandleOnKeyDownProps = {
5
6
  inputKeyDownNavigationHandler: KeyboardEventHandler<HTMLInputElement>;
@@ -31,4 +32,8 @@ export declare function useSearchInput({ value, onChange, defaultValue, selected
31
32
  updateInputValue: (selectedItem?: ItemWithId) => void;
32
33
  };
33
34
  export declare function useHandleDeleteItem(setValue: Handler): (item?: ItemWithId) => (e?: MouseEvent<HTMLButtonElement>) => void;
35
+ /**
36
+ * Четкий и нечеткий поиск среди айтемов по полям 'content.option', 'content.caption', 'content.description', 'label'
37
+ */
38
+ export declare function useSearch(items: ItemProps[], enableFuzzySearch?: boolean): (search: string) => import("@snack-uikit/list/dist/components/Items").Item[] | import("@snack-uikit/list/dist/components/Items").FlattenItem[];
34
39
  export {};
@@ -1,11 +1,10 @@
1
+ import FuzzySearch from 'fuzzy-search';
1
2
  import { useCallback, useMemo, useRef } from 'react';
2
3
  import { useButtonNavigation, useClearButton } from '@snack-uikit/input-private';
3
- import {
4
- // extractChildIds,
5
- isAccordionItemProps, isNextListItemProps, } from '@snack-uikit/list';
4
+ import { isAccordionItemProps, isNextListItemProps, kindFlattenItems, } from '@snack-uikit/list';
6
5
  import { useCopyButton, useValueControl } from '../../hooks';
7
6
  import { extractChildIds } from './legacy';
8
- import { isBaseOptionProps } from './utils';
7
+ import { getValueByPath, isBaseOptionProps } from './utils';
9
8
  export function useHandleOnKeyDown({ setOpen, inputKeyDownNavigationHandler, onInputKeyDownProp, }) {
10
9
  return useCallback((onKeyDown) => (e) => {
11
10
  if (e.code === 'Space') {
@@ -84,3 +83,25 @@ export function useHandleDeleteItem(setValue) {
84
83
  }
85
84
  }, [setValue]);
86
85
  }
86
+ const DEFAULT_MIN_SEARCH_INPUT_LENGTH = 1;
87
+ const COMMON_FIELDS_TO_SEARCH = ['content.option', 'content.caption', 'content.description'];
88
+ /**
89
+ * Четкий и нечеткий поиск среди айтемов по полям 'content.option', 'content.caption', 'content.description', 'label'
90
+ */
91
+ export function useSearch(items, enableFuzzySearch = true) {
92
+ const flattenItems = useMemo(() => {
93
+ const { flattenItems } = kindFlattenItems({ items });
94
+ return Object.values(flattenItems);
95
+ }, [items]);
96
+ const filterFunction = useCallback((search) => {
97
+ if (!enableFuzzySearch) {
98
+ return flattenItems.filter(item => [...COMMON_FIELDS_TO_SEARCH, 'label'].some(key => {
99
+ const value = getValueByPath(item, key);
100
+ return value.toLowerCase().includes(search.toLowerCase());
101
+ }));
102
+ }
103
+ const searcher = new FuzzySearch(flattenItems, COMMON_FIELDS_TO_SEARCH, {});
104
+ return searcher.search(search);
105
+ }, [enableFuzzySearch, flattenItems]);
106
+ return useCallback((search) => (search.length >= DEFAULT_MIN_SEARCH_INPUT_LENGTH ? filterFunction(search) : items), [filterFunction, items]);
107
+ }
@@ -1,3 +1,2 @@
1
1
  export * from './components';
2
- export { useFuzzySearch } from './hooks';
3
2
  export { extractChildIds, flattenItems } from './utils';
@@ -1,3 +1,2 @@
1
1
  export * from './components';
2
- export { useFuzzySearch } from './hooks';
3
2
  export { extractChildIds, flattenItems } from './utils';
@@ -60,6 +60,8 @@ type FiledSelectCommonProps = WithSupportProps<{
60
60
  autocomplete?: boolean;
61
61
  addOptionByEnter?: boolean;
62
62
  open?: boolean;
63
+ /** Включить нечеткий поиск */
64
+ enableFuzzySearch?: boolean;
63
65
  onOpenChange?(open: boolean): void;
64
66
  selectedOptionFormatter?: SelectedOptionFormatter;
65
67
  }> & Pick<DroplistProps, 'dataError' | 'noDataState' | 'noResultsState' | 'errorDataState' | 'dataFiltered' | 'untouchableScrollbars'>;
@@ -0,0 +1,7 @@
1
+ type CheckIsSearchUnavailableParams = {
2
+ autocomplete: boolean;
3
+ searchable: boolean;
4
+ isSameValue: boolean;
5
+ };
6
+ export declare function checkisSearchUnavailable({ autocomplete, searchable, isSameValue }: CheckIsSearchUnavailableParams): boolean;
7
+ export {};
@@ -0,0 +1,3 @@
1
+ export function checkisSearchUnavailable({ autocomplete, searchable, isSameValue }) {
2
+ return autocomplete || !searchable || isSameValue;
3
+ }
@@ -0,0 +1 @@
1
+ export declare function getValueByPath(item: Record<string, unknown>, path: string): string;
@@ -0,0 +1,17 @@
1
+ export function getValueByPath(item, path) {
2
+ const keys = path.split('.');
3
+ const firstFieldName = keys[0];
4
+ if (!item[firstFieldName]) {
5
+ return '';
6
+ }
7
+ if (keys.length > 1) {
8
+ return getValueByPath(item[firstFieldName], keys.slice(1, keys.length).join('.'));
9
+ }
10
+ if (path in item) {
11
+ const value = item[path];
12
+ if (typeof value === 'string' || typeof value === 'number') {
13
+ return value.toString() || '';
14
+ }
15
+ }
16
+ return '';
17
+ }
@@ -3,3 +3,5 @@ export * from './extractListProps';
3
3
  export * from './options';
4
4
  export * from './updateItems';
5
5
  export * from './getArrowIcon';
6
+ export * from './getValueByPath';
7
+ export * from './checkisSearchUnavailable';
@@ -3,3 +3,5 @@ export * from './extractListProps';
3
3
  export * from './options';
4
4
  export * from './updateItems';
5
5
  export * from './getArrowIcon';
6
+ export * from './getValueByPath';
7
+ export * from './checkisSearchUnavailable';
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "access": "public"
5
5
  },
6
6
  "title": "Fields",
7
- "version": "0.25.3-preview-4ab4d581.0",
7
+ "version": "0.26.0",
8
8
  "sideEffects": [
9
9
  "*.css",
10
10
  "*.woff",
@@ -59,5 +59,5 @@
59
59
  "peerDependencies": {
60
60
  "@snack-uikit/locale": "*"
61
61
  },
62
- "gitHead": "0d52e9b583e9a990834cd94ec67277bdd27651c1"
62
+ "gitHead": "b5dbc02c777773140b597e9c3268a9424f8608d8"
63
63
  }
@@ -12,11 +12,10 @@ import { useValueControl } from '../../hooks';
12
12
  import { getValidationState } from '../../utils/getValidationState';
13
13
  import { FieldDecorator } from '../FieldDecorator';
14
14
  import { extractFieldDecoratorProps } from '../FieldDecorator/utils';
15
- import { useButtons, useHandleDeleteItem, useHandleOnKeyDown, useSearchInput } from './hooks';
16
- import { useFuzzySearch } from './legacy';
15
+ import { useButtons, useHandleDeleteItem, useHandleOnKeyDown, useSearch, useSearchInput } from './hooks';
17
16
  import styles from './styles.module.scss';
18
17
  import { FieldSelectMultipleProps, ItemWithId, SelectedOptionFormatter } from './types';
19
- import { extractListProps, getArrowIcon, updateMultipleItems } from './utils';
18
+ import { checkisSearchUnavailable, extractListProps, getArrowIcon, updateMultipleItems } from './utils';
20
19
 
21
20
  const BASE_MIN_WIDTH = 4;
22
21
 
@@ -48,6 +47,7 @@ export const FieldSelectMultiple = forwardRef<HTMLInputElement, FieldSelectMulti
48
47
  addOptionByEnter = false,
49
48
  untouchableScrollbars = false,
50
49
  open: openProp,
50
+ enableFuzzySearch = true,
51
51
  onOpenChange,
52
52
  selectedOptionFormatter = defaultSelectedOptionFormatter,
53
53
  ...rest
@@ -158,8 +158,13 @@ export const FieldSelectMultiple = forwardRef<HTMLInputElement, FieldSelectMulti
158
158
  }
159
159
  };
160
160
 
161
- const fuzzySearch = useFuzzySearch(items);
162
- const result = autocomplete || !searchable || prevInputValue.current === inputValue ? items : fuzzySearch(inputValue);
161
+ const searchFunction = useSearch(items, enableFuzzySearch);
162
+ const isSearchUnavailable = checkisSearchUnavailable({
163
+ autocomplete,
164
+ searchable,
165
+ isSameValue: prevInputValue.current === inputValue,
166
+ });
167
+ const result = isSearchUnavailable ? items : searchFunction(inputValue);
163
168
 
164
169
  const fieldValidationState = getValidationState({ validationState, error: rest.error });
165
170
 
@@ -21,11 +21,10 @@ import { useValueControl } from '../../hooks';
21
21
  import { getValidationState } from '../../utils/getValidationState';
22
22
  import { FieldDecorator } from '../FieldDecorator';
23
23
  import { extractFieldDecoratorProps } from '../FieldDecorator/utils';
24
- import { useButtons, useHandleOnKeyDown, useSearchInput } from './hooks';
25
- import { useFuzzySearch } from './legacy';
24
+ import { useButtons, useHandleOnKeyDown, useSearch, useSearchInput } from './hooks';
26
25
  import styles from './styles.module.scss';
27
26
  import { FieldSelectSingleProps, ItemWithId, SelectedOptionFormatter } from './types';
28
- import { extractListProps, getArrowIcon, updateItems } from './utils';
27
+ import { checkisSearchUnavailable, extractListProps, getArrowIcon, updateItems } from './utils';
29
28
 
30
29
  const defaultSelectedOptionFormatter: SelectedOptionFormatter = item =>
31
30
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
@@ -58,6 +57,7 @@ export const FieldSelectSingle = forwardRef<HTMLInputElement, FieldSelectSingleP
58
57
  open: openProp,
59
58
  onOpenChange,
60
59
  selectedOptionFormatter = defaultSelectedOptionFormatter,
60
+ enableFuzzySearch = true,
61
61
  ...rest
62
62
  } = props;
63
63
  const localRef = useRef<HTMLInputElement>(null);
@@ -75,7 +75,9 @@ export const FieldSelectSingle = forwardRef<HTMLInputElement, FieldSelectSingleP
75
75
 
76
76
  const { inputValue, setInputValue, prevInputValue, updateInputValue } = useSearchInput({
77
77
  ...search,
78
- defaultValue: selectedOptionFormatter(selectedItem),
78
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
79
+ // @ts-expect-error
80
+ defaultValue: selectedItem?.content.option ?? '',
79
81
  selectedOptionFormatter,
80
82
  });
81
83
 
@@ -175,11 +177,13 @@ export const FieldSelectSingle = forwardRef<HTMLInputElement, FieldSelectSingleP
175
177
  }
176
178
  };
177
179
 
178
- const fuzzySearch = useFuzzySearch(items);
179
- const result =
180
- autocomplete || !searchable || selectedOptionFormatter(selectedItem) === inputValue
181
- ? items
182
- : fuzzySearch(inputValue);
180
+ const filterFunction = useSearch(items, enableFuzzySearch);
181
+ const isSearchUnavailable = checkisSearchUnavailable({
182
+ autocomplete,
183
+ searchable,
184
+ isSameValue: selectedOptionFormatter(selectedItem) === inputValue,
185
+ });
186
+ const result = isSearchUnavailable ? items : filterFunction(inputValue);
183
187
 
184
188
  const fieldValidationState = getValidationState({ validationState, error: rest.error });
185
189
 
@@ -1,18 +1,20 @@
1
+ import FuzzySearch from 'fuzzy-search';
1
2
  import { KeyboardEvent, KeyboardEventHandler, MouseEvent, RefObject, useCallback, useMemo, useRef } from 'react';
2
3
  import { Handler } from 'uncontrollable';
3
4
 
4
5
  import { useButtonNavigation, useClearButton } from '@snack-uikit/input-private';
5
6
  import {
6
- // extractChildIds,
7
7
  isAccordionItemProps,
8
8
  isNextListItemProps,
9
+ ItemProps,
10
+ kindFlattenItems,
9
11
  SelectionSingleValueType,
10
12
  } from '@snack-uikit/list';
11
13
 
12
14
  import { useCopyButton, useValueControl } from '../../hooks';
13
15
  import { extractChildIds } from './legacy';
14
16
  import { ItemWithId, SearchState, SelectedOptionFormatter } from './types';
15
- import { isBaseOptionProps } from './utils';
17
+ import { getValueByPath, isBaseOptionProps } from './utils';
16
18
 
17
19
  type UseHandleOnKeyDownProps = {
18
20
  inputKeyDownNavigationHandler: KeyboardEventHandler<HTMLInputElement>;
@@ -156,3 +158,40 @@ export function useHandleDeleteItem(setValue: Handler) {
156
158
  [setValue],
157
159
  );
158
160
  }
161
+
162
+ const DEFAULT_MIN_SEARCH_INPUT_LENGTH = 1;
163
+ const COMMON_FIELDS_TO_SEARCH = ['content.option', 'content.caption', 'content.description'];
164
+
165
+ /**
166
+ * Четкий и нечеткий поиск среди айтемов по полям 'content.option', 'content.caption', 'content.description', 'label'
167
+ */
168
+ export function useSearch(items: ItemProps[], enableFuzzySearch: boolean = true) {
169
+ const flattenItems = useMemo(() => {
170
+ const { flattenItems } = kindFlattenItems({ items });
171
+
172
+ return Object.values(flattenItems);
173
+ }, [items]);
174
+
175
+ const filterFunction = useCallback(
176
+ (search: string) => {
177
+ if (!enableFuzzySearch) {
178
+ return flattenItems.filter(item =>
179
+ [...COMMON_FIELDS_TO_SEARCH, 'label'].some(key => {
180
+ const value = getValueByPath(item, key);
181
+
182
+ return value.toLowerCase().includes(search.toLowerCase());
183
+ }),
184
+ );
185
+ }
186
+
187
+ const searcher = new FuzzySearch(flattenItems, COMMON_FIELDS_TO_SEARCH, {});
188
+ return searcher.search(search);
189
+ },
190
+ [enableFuzzySearch, flattenItems],
191
+ );
192
+
193
+ return useCallback(
194
+ (search: string) => (search.length >= DEFAULT_MIN_SEARCH_INPUT_LENGTH ? filterFunction(search) : items),
195
+ [filterFunction, items],
196
+ );
197
+ }
@@ -1,3 +1,2 @@
1
1
  export * from './components';
2
- export { useFuzzySearch } from './hooks';
3
2
  export { extractChildIds, flattenItems } from './utils';
@@ -116,6 +116,9 @@ type FiledSelectCommonProps = WithSupportProps<{
116
116
 
117
117
  open?: boolean;
118
118
 
119
+ /** Включить нечеткий поиск */
120
+ enableFuzzySearch?: boolean;
121
+
119
122
  onOpenChange?(open: boolean): void;
120
123
 
121
124
  selectedOptionFormatter?: SelectedOptionFormatter;
@@ -0,0 +1,9 @@
1
+ type CheckIsSearchUnavailableParams = {
2
+ autocomplete: boolean;
3
+ searchable: boolean;
4
+ isSameValue: boolean;
5
+ };
6
+
7
+ export function checkisSearchUnavailable({ autocomplete, searchable, isSameValue }: CheckIsSearchUnavailableParams) {
8
+ return autocomplete || !searchable || isSameValue;
9
+ }
@@ -0,0 +1,22 @@
1
+ export function getValueByPath(item: Record<string, unknown>, path: string): string {
2
+ const keys = path.split('.');
3
+ const firstFieldName = keys[0];
4
+
5
+ if (!item[firstFieldName]) {
6
+ return '';
7
+ }
8
+
9
+ if (keys.length > 1) {
10
+ return getValueByPath(item[firstFieldName] as Record<string, unknown>, keys.slice(1, keys.length).join('.'));
11
+ }
12
+
13
+ if (path in item) {
14
+ const value = item[path];
15
+
16
+ if (typeof value === 'string' || typeof value === 'number') {
17
+ return value.toString() || '';
18
+ }
19
+ }
20
+
21
+ return '';
22
+ }
@@ -3,3 +3,5 @@ export * from './extractListProps';
3
3
  export * from './options';
4
4
  export * from './updateItems';
5
5
  export * from './getArrowIcon';
6
+ export * from './getValueByPath';
7
+ export * from './checkisSearchUnavailable';
@@ -1,5 +0,0 @@
1
- import { ItemProps } from '@snack-uikit/list';
2
- /**
3
- * Нечеткий поиск среди айтемов по полям 'content.option', 'content.caption', 'content.description', 'label'
4
- */
5
- export declare function useFuzzySearch(items: ItemProps[], minSearchInputLength?: number): (search: string) => import("@snack-uikit/list/dist/components/Items").Item[] | import("@snack-uikit/list/dist/components/Items").FlattenItem[];
@@ -1,19 +0,0 @@
1
- import FuzzySearch from 'fuzzy-search';
2
- import { useCallback, useMemo } from 'react';
3
- import { kindFlattenItems } from '@snack-uikit/list';
4
- const DEFAULT_MIN_SEARCH_INPUT_LENGTH = 1;
5
- /**
6
- * Нечеткий поиск среди айтемов по полям 'content.option', 'content.caption', 'content.description', 'label'
7
- */
8
- export function useFuzzySearch(items, minSearchInputLength) {
9
- const flattenItems = useMemo(() => {
10
- const { flattenItems } = kindFlattenItems({ items });
11
- return Object.values(flattenItems);
12
- }, [items]);
13
- return useCallback((search) => {
14
- const searcher = new FuzzySearch(flattenItems, ['content.option', 'content.caption', 'content.description'], {});
15
- return search.length >= (minSearchInputLength !== null && minSearchInputLength !== void 0 ? minSearchInputLength : DEFAULT_MIN_SEARCH_INPUT_LENGTH)
16
- ? searcher.search(search)
17
- : items;
18
- }, [flattenItems, items, minSearchInputLength]);
19
- }
@@ -1,28 +0,0 @@
1
- import FuzzySearch from 'fuzzy-search';
2
- import { useCallback, useMemo } from 'react';
3
-
4
- import { ItemProps, kindFlattenItems } from '@snack-uikit/list';
5
-
6
- const DEFAULT_MIN_SEARCH_INPUT_LENGTH = 1;
7
-
8
- /**
9
- * Нечеткий поиск среди айтемов по полям 'content.option', 'content.caption', 'content.description', 'label'
10
- */
11
- export function useFuzzySearch(items: ItemProps[], minSearchInputLength?: number) {
12
- const flattenItems = useMemo(() => {
13
- const { flattenItems } = kindFlattenItems({ items });
14
-
15
- return Object.values(flattenItems);
16
- }, [items]);
17
-
18
- return useCallback(
19
- (search: string) => {
20
- const searcher = new FuzzySearch(flattenItems, ['content.option', 'content.caption', 'content.description'], {});
21
-
22
- return search.length >= (minSearchInputLength ?? DEFAULT_MIN_SEARCH_INPUT_LENGTH)
23
- ? searcher.search(search)
24
- : items;
25
- },
26
- [flattenItems, items, minSearchInputLength],
27
- );
28
- }