@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.
- package/CHANGELOG.md +12 -0
- package/README.md +2 -0
- package/dist/components/FieldSelect/FieldSelectMultiple.d.ts +1 -0
- package/dist/components/FieldSelect/FieldSelectMultiple.js +10 -6
- package/dist/components/FieldSelect/FieldSelectSingle.d.ts +1 -0
- package/dist/components/FieldSelect/FieldSelectSingle.js +15 -9
- package/dist/components/FieldSelect/hooks.d.ts +5 -0
- package/dist/components/FieldSelect/hooks.js +25 -4
- package/dist/components/FieldSelect/legacy/index.d.ts +0 -1
- package/dist/components/FieldSelect/legacy/index.js +0 -1
- package/dist/components/FieldSelect/types.d.ts +2 -0
- package/dist/components/FieldSelect/utils/checkisSearchUnavailable.d.ts +7 -0
- package/dist/components/FieldSelect/utils/checkisSearchUnavailable.js +3 -0
- package/dist/components/FieldSelect/utils/getValueByPath.d.ts +1 -0
- package/dist/components/FieldSelect/utils/getValueByPath.js +17 -0
- package/dist/components/FieldSelect/utils/index.d.ts +2 -0
- package/dist/components/FieldSelect/utils/index.js +2 -0
- package/package.json +2 -2
- package/src/components/FieldSelect/FieldSelectMultiple.tsx +10 -5
- package/src/components/FieldSelect/FieldSelectSingle.tsx +13 -9
- package/src/components/FieldSelect/hooks.ts +41 -2
- package/src/components/FieldSelect/legacy/index.ts +0 -1
- package/src/components/FieldSelect/types.ts +3 -0
- package/src/components/FieldSelect/utils/checkisSearchUnavailable.ts +9 -0
- package/src/components/FieldSelect/utils/getValueByPath.ts +22 -0
- package/src/components/FieldSelect/utils/index.ts +2 -0
- package/dist/components/FieldSelect/legacy/hooks.d.ts +0 -5
- package/dist/components/FieldSelect/legacy/hooks.js +0 -19
- 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
|
|
118
|
-
const
|
|
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
|
-
|
|
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), {
|
|
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
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
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
|
+
}
|
|
@@ -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 @@
|
|
|
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
|
+
}
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
6
|
"title": "Fields",
|
|
7
|
-
"version": "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": "
|
|
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
|
|
162
|
-
const
|
|
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
|
-
|
|
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
|
|
179
|
-
const
|
|
180
|
-
autocomplete
|
|
181
|
-
|
|
182
|
-
|
|
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
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -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
|
-
}
|