@snack-uikit/fields 0.16.1 → 0.17.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 +18 -0
- package/README.md +11 -0
- package/dist/components/FieldDecorator/utils.d.ts +15 -0
- package/dist/components/FieldDecorator/utils.js +16 -0
- package/dist/components/FieldSelect/FieldSelectMultiple.d.ts +6 -1
- package/dist/components/FieldSelect/FieldSelectMultiple.js +56 -27
- package/dist/components/FieldSelect/FieldSelectSingle.d.ts +4 -1
- package/dist/components/FieldSelect/FieldSelectSingle.js +45 -20
- package/dist/components/FieldSelect/hooks.d.ts +2 -2
- package/dist/components/FieldSelect/hooks.js +11 -11
- package/dist/components/FieldSelect/types.d.ts +10 -3
- package/dist/components/FieldSelect/utils.d.ts +7 -4
- package/dist/components/FieldSelect/utils.js +59 -49
- package/dist/components/FieldSlider/helpers/generateAllowedValues.js +1 -1
- package/dist/hooks/useValueControl.js +1 -1
- package/package.json +5 -5
- package/src/components/FieldDecorator/utils.ts +31 -0
- package/src/components/FieldSelect/FieldSelectMultiple.tsx +90 -60
- package/src/components/FieldSelect/FieldSelectSingle.tsx +54 -39
- package/src/components/FieldSelect/hooks.ts +17 -13
- package/src/components/FieldSelect/types.ts +18 -4
- package/src/components/FieldSelect/utils.ts +89 -65
- package/src/components/FieldSlider/helpers/generateAllowedValues.ts +1 -1
- package/src/hooks/useValueControl.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,24 @@
|
|
|
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.17.0 (2024-02-27)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* **FF-4324:** fix infinite response in actocomplete mode ([ad19e0c](https://github.com/cloud-ru-tech/snack-uikit/commit/ad19e0cb6e91b4dda12bc34a3f55fe823f508602))
|
|
12
|
+
* **FF-4324:** fix value dec value as value === max ([a8bb974](https://github.com/cloud-ru-tech/snack-uikit/commit/a8bb9747f31864ea616fef6b9e2166047faceae7))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
* **FF-4323:** add not found items from value ([a924867](https://github.com/cloud-ru-tech/snack-uikit/commit/a92486740ce4de274ff7331fd9dbf67a9f1a39da))
|
|
18
|
+
* **FF-4324:** add appearance to tags view in muliple select ([1ce6b81](https://github.com/cloud-ru-tech/snack-uikit/commit/1ce6b817714bf6fb35e394338ab0083fa92ca44f))
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
6
24
|
## 0.16.1 (2024-02-27)
|
|
7
25
|
|
|
8
26
|
### Only dependencies have been changed
|
package/README.md
CHANGED
|
@@ -337,9 +337,20 @@ const [isOpen, setIsOpen] = useState(false);
|
|
|
337
337
|
| footer | `ReactNode` | - | |
|
|
338
338
|
| search | `SearchState` | - | |
|
|
339
339
|
| autocomplete | `boolean` | - | |
|
|
340
|
+
| addOptionByEnter | `boolean` | - | |
|
|
341
|
+
| open | `boolean` | - | |
|
|
342
|
+
| onOpenChange | `(open: boolean) => void` | - | |
|
|
343
|
+
| dataError | `boolean` | - | |
|
|
344
|
+
| noDataState | `EmptyStateProps` | - | Экран при отстутствии данных |
|
|
345
|
+
| noResultsState | `EmptyStateProps` | - | Экран при отстутствии результатов поиска или фильтров |
|
|
346
|
+
| errorDataState | `EmptyStateProps` | - | Экран при ошибке запроса |
|
|
347
|
+
| pinTop | `ItemProps[]` | - | Элементы списка, закрепленные сверху |
|
|
348
|
+
| pinBottom | `ItemProps[]` | - | Элементы списка, закрепленные снизу |
|
|
349
|
+
| dataFiltered | `boolean` | - | |
|
|
340
350
|
| selection | "single" \| "multiple" | - | |
|
|
341
351
|
| ref | `Ref<HTMLInputElement>` | - | Allows getting a ref to the component instance. Once the component unmounts, React will set `ref.current` to `null` (or call the ref with `null` if you passed a callback ref). @see https://react.dev/learn/referencing-values-with-refs#refs-and-the-dom |
|
|
342
352
|
| key | `Key` | - | |
|
|
353
|
+
| removeByBackspace | `boolean` | - | |
|
|
343
354
|
## FieldStepper
|
|
344
355
|
### Props
|
|
345
356
|
| name | type | default value | description |
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { FieldDecoratorProps } from './FieldDecorator';
|
|
2
|
+
export declare function extractFieldDecoratorProps<T extends Partial<FieldDecoratorProps>>({ error, required, readonly, label, labelTooltip, labelTooltipPlacement, labelFor, hint, disabled, showHintIcon, size, validationState, }: T): {
|
|
3
|
+
error: string | undefined;
|
|
4
|
+
required: boolean | undefined;
|
|
5
|
+
readonly: boolean | undefined;
|
|
6
|
+
label: string | undefined;
|
|
7
|
+
labelTooltip: string | undefined;
|
|
8
|
+
labelTooltipPlacement: import("@snack-uikit/popover-private/dist/types").Placement | undefined;
|
|
9
|
+
labelFor: string | undefined;
|
|
10
|
+
hint: string | undefined;
|
|
11
|
+
disabled: boolean | undefined;
|
|
12
|
+
showHintIcon: boolean | undefined;
|
|
13
|
+
size: import("@snack-uikit/input-private").Size | undefined;
|
|
14
|
+
validationState: import("../../types").ValidationState | undefined;
|
|
15
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function extractFieldDecoratorProps({ error, required, readonly, label, labelTooltip, labelTooltipPlacement, labelFor, hint, disabled, showHintIcon, size, validationState, }) {
|
|
2
|
+
return {
|
|
3
|
+
error,
|
|
4
|
+
required,
|
|
5
|
+
readonly,
|
|
6
|
+
label,
|
|
7
|
+
labelTooltip,
|
|
8
|
+
labelTooltipPlacement,
|
|
9
|
+
labelFor,
|
|
10
|
+
hint,
|
|
11
|
+
disabled,
|
|
12
|
+
showHintIcon,
|
|
13
|
+
size,
|
|
14
|
+
validationState,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
export declare const FieldSelectMultiple: import("react").ForwardRefExoticComponent<import("./types").InputProps & import("./types").WrapperProps & {
|
|
3
3
|
options: import("./types").OptionProps[];
|
|
4
4
|
loading?: boolean | undefined;
|
|
5
|
+
} & {
|
|
6
|
+
removeByBackspace?: boolean | undefined;
|
|
5
7
|
} & Omit<import("@snack-uikit/list").SelectionMultipleState, "mode"> & Omit<{
|
|
6
8
|
'data-test-id'?: string | undefined;
|
|
7
9
|
} & import("react").AriaAttributes & {
|
|
@@ -14,4 +16,7 @@ export declare const FieldSelectMultiple: import("react").ForwardRefExoticCompon
|
|
|
14
16
|
footer?: import("react").ReactNode;
|
|
15
17
|
search?: import("./types").SearchState | undefined;
|
|
16
18
|
autocomplete?: boolean | undefined;
|
|
17
|
-
|
|
19
|
+
addOptionByEnter?: boolean | undefined;
|
|
20
|
+
open?: boolean | undefined;
|
|
21
|
+
onOpenChange?(open: boolean): void;
|
|
22
|
+
} & Pick<import("@snack-uikit/list").ListProps, "dataError" | "noDataState" | "noResultsState" | "errorDataState" | "pinTop" | "pinBottom" | "dataFiltered">, "showCopyButton"> & import("react").RefAttributes<HTMLInputElement>>;
|
|
@@ -12,7 +12,7 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
12
12
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
13
13
|
import cn from 'classnames';
|
|
14
14
|
import mergeRefs from 'merge-refs';
|
|
15
|
-
import { forwardRef, useMemo, useRef
|
|
15
|
+
import { forwardRef, useMemo, useRef } from 'react';
|
|
16
16
|
import { InputPrivate } from '@snack-uikit/input-private';
|
|
17
17
|
import { Droplist, useFuzzySearch } from '@snack-uikit/list';
|
|
18
18
|
import { Tag } from '@snack-uikit/tag';
|
|
@@ -20,26 +20,30 @@ import { extractSupportProps } from '@snack-uikit/utils';
|
|
|
20
20
|
import { FieldContainerPrivate } from '../../helperComponents';
|
|
21
21
|
import { useValueControl } from '../../hooks';
|
|
22
22
|
import { FieldDecorator } from '../FieldDecorator';
|
|
23
|
+
import { extractFieldDecoratorProps } from '../FieldDecorator/utils';
|
|
23
24
|
import { useButtons, useHandleDeleteItem, useHandleOnKeyDown, useSearchInput } from './hooks';
|
|
24
25
|
import styles from './styles.module.css';
|
|
25
|
-
import {
|
|
26
|
+
import { extractListProps, findSelectedOptions, getArrowIcon, mapOptionToAppearance, transformOptionsToItems, } from './utils';
|
|
26
27
|
const BASE_MIN_WIDTH = 4;
|
|
27
28
|
export const FieldSelectMultiple = forwardRef((_a, ref) => {
|
|
28
|
-
var
|
|
29
|
+
var _b, _c, _d;
|
|
30
|
+
var { 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, open: openProp, onOpenChange } = _a, rest = __rest(_a, ["id", "name", "placeholder", "size", "options", "value", "defaultValue", "onChange", "disabled", "readonly", "searchable", "showClearButton", "onKeyDown", "validationState", "search", "autocomplete", "prefixIcon", "removeByBackspace", "addOptionByEnter", "open", "onOpenChange"]);
|
|
29
31
|
const localRef = useRef(null);
|
|
30
32
|
const inputPlugRef = useRef(null);
|
|
31
33
|
const contentRef = useRef(null);
|
|
32
|
-
const [open, setOpen] =
|
|
34
|
+
const [open = false, setOpen] = useValueControl({ value: openProp, onChange: onOpenChange });
|
|
33
35
|
const items = useMemo(() => transformOptionsToItems(options), [options]);
|
|
36
|
+
const mapItemsToTagAppearance = useMemo(() => mapOptionToAppearance(options), [options]);
|
|
34
37
|
const [value, setValue] = useValueControl({
|
|
35
38
|
value: valueProp,
|
|
36
39
|
defaultValue,
|
|
37
40
|
onChange: onChangeProp,
|
|
38
41
|
});
|
|
39
|
-
const
|
|
40
|
-
const notSortSelectedOption =
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
const { selected, itemsWithPlaceholder, disabledSelected } = useMemo(() => {
|
|
43
|
+
const [notSortSelectedOption, placeholder] = findSelectedOptions(items, value);
|
|
44
|
+
const selectedWithPlaceholder = notSortSelectedOption || placeholder ? (placeholder !== null && placeholder !== void 0 ? placeholder : []).concat(notSortSelectedOption !== null && notSortSelectedOption !== void 0 ? notSortSelectedOption : []) : undefined;
|
|
45
|
+
const selected = selectedWithPlaceholder
|
|
46
|
+
? selectedWithPlaceholder.sort((a, b) => {
|
|
43
47
|
if (b.disabled && !a.disabled) {
|
|
44
48
|
return 1;
|
|
45
49
|
}
|
|
@@ -47,13 +51,21 @@ export const FieldSelectMultiple = forwardRef((_a, ref) => {
|
|
|
47
51
|
return -1;
|
|
48
52
|
}
|
|
49
53
|
return 0;
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
+
})
|
|
55
|
+
: undefined;
|
|
56
|
+
const placeholderItems = placeholder ? placeholder : [];
|
|
57
|
+
const disabledSelected = selected === null || selected === void 0 ? void 0 : selected.filter((item) => item.disabled);
|
|
58
|
+
return {
|
|
59
|
+
selected,
|
|
60
|
+
disabledSelected,
|
|
61
|
+
itemsWithPlaceholder: placeholderItems.concat(items),
|
|
62
|
+
};
|
|
63
|
+
}, [items, value]);
|
|
64
|
+
const { inputValue, onInputValueChange, prevInputValue } = useSearchInput(Object.assign(Object.assign({}, search), { defaultValue: '' }));
|
|
54
65
|
const onClear = () => {
|
|
55
66
|
var _a;
|
|
56
|
-
|
|
67
|
+
const disabledValues = disabledSelected === null || disabledSelected === void 0 ? void 0 : disabledSelected.map(item => item.id);
|
|
68
|
+
setValue(disabledValues);
|
|
57
69
|
onInputValueChange('');
|
|
58
70
|
(_a = localRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
59
71
|
setOpen(true);
|
|
@@ -62,7 +74,7 @@ export const FieldSelectMultiple = forwardRef((_a, ref) => {
|
|
|
62
74
|
const { buttons, inputKeyDownNavigationHandler, buttonsRefs } = useButtons({
|
|
63
75
|
readonly,
|
|
64
76
|
size,
|
|
65
|
-
showClearButton: showClearButton &&
|
|
77
|
+
showClearButton: showClearButton && !disabled && !readonly && ((_b = value === null || value === void 0 ? void 0 : value.length) !== null && _b !== void 0 ? _b : 0) > ((_c = disabledSelected === null || disabledSelected === void 0 ? void 0 : disabledSelected.length) !== null && _c !== void 0 ? _c : 0),
|
|
66
78
|
showCopyButton: false,
|
|
67
79
|
inputRef: localRef,
|
|
68
80
|
onClear,
|
|
@@ -74,11 +86,20 @@ export const FieldSelectMultiple = forwardRef((_a, ref) => {
|
|
|
74
86
|
});
|
|
75
87
|
const handleItemDelete = useHandleDeleteItem(setValue);
|
|
76
88
|
const handleOnKeyDown = (onKeyDown) => (e) => {
|
|
77
|
-
if (e.code === 'Backspace' && inputValue === '') {
|
|
78
|
-
if ((
|
|
79
|
-
handleItemDelete(
|
|
89
|
+
if (removeByBackspace && e.code === 'Backspace' && inputValue === '') {
|
|
90
|
+
if ((selected === null || selected === void 0 ? void 0 : selected.length) && !selected.slice(-1)[0].disabled) {
|
|
91
|
+
handleItemDelete(selected.pop())();
|
|
80
92
|
}
|
|
81
93
|
}
|
|
94
|
+
if (e.code === 'Enter') {
|
|
95
|
+
e.stopPropagation();
|
|
96
|
+
e.preventDefault();
|
|
97
|
+
}
|
|
98
|
+
if (addOptionByEnter && e.code === 'Enter' && inputValue !== '') {
|
|
99
|
+
setValue((value) => (value !== null && value !== void 0 ? value : []).concat(inputValue));
|
|
100
|
+
onInputValueChange('');
|
|
101
|
+
prevInputValue.current = '';
|
|
102
|
+
}
|
|
82
103
|
if (!open && prevInputValue.current !== inputValue) {
|
|
83
104
|
setOpen(true);
|
|
84
105
|
}
|
|
@@ -88,10 +109,16 @@ export const FieldSelectMultiple = forwardRef((_a, ref) => {
|
|
|
88
109
|
if (!readonly && !disabled && !buttonsRefs.includes(document.activeElement)) {
|
|
89
110
|
setOpen(open);
|
|
90
111
|
if (!open) {
|
|
91
|
-
|
|
112
|
+
onInputValueChange('');
|
|
113
|
+
prevInputValue.current = '';
|
|
114
|
+
if (inputPlugRef.current) {
|
|
115
|
+
inputPlugRef.current.style.width = BASE_MIN_WIDTH + 'px';
|
|
116
|
+
}
|
|
92
117
|
}
|
|
93
118
|
if (open) {
|
|
94
|
-
|
|
119
|
+
if (inputPlugRef.current) {
|
|
120
|
+
inputPlugRef.current.style.width = 'unset';
|
|
121
|
+
}
|
|
95
122
|
}
|
|
96
123
|
}
|
|
97
124
|
};
|
|
@@ -102,19 +129,21 @@ export const FieldSelectMultiple = forwardRef((_a, ref) => {
|
|
|
102
129
|
}
|
|
103
130
|
(_a = rest === null || rest === void 0 ? void 0 : rest.onBlur) === null || _a === void 0 ? void 0 : _a.call(rest, e);
|
|
104
131
|
};
|
|
105
|
-
const fuzzySearch = useFuzzySearch(
|
|
106
|
-
const result = autocomplete
|
|
107
|
-
|
|
132
|
+
const fuzzySearch = useFuzzySearch(itemsWithPlaceholder);
|
|
133
|
+
const result = autocomplete || !searchable || prevInputValue.current === inputValue
|
|
134
|
+
? itemsWithPlaceholder
|
|
135
|
+
: fuzzySearch(inputValue);
|
|
136
|
+
return (_jsx(FieldDecorator, Object.assign({}, extractSupportProps(rest), extractFieldDecoratorProps(rest), { labelFor: id, size: size, validationState: validationState, children: _jsx(Droplist, Object.assign({}, extractListProps(rest), { items: result, triggerElemRef: localRef, selection: {
|
|
108
137
|
mode: 'multiple',
|
|
109
138
|
value: value,
|
|
110
139
|
onChange: setValue,
|
|
111
|
-
}, size: size, open: !disabled && !readonly && open, onOpenChange: handleOpenChange,
|
|
140
|
+
}, dataFiltered: (_d = rest.dataFiltered) !== null && _d !== void 0 ? _d : Boolean(inputValue.length), size: size, open: !disabled && !readonly && open, onOpenChange: handleOpenChange, children: ({ onKeyDown }) => {
|
|
112
141
|
var _a, _b, _c, _d;
|
|
113
|
-
return (_jsx(FieldContainerPrivate, { className: cn(styles.container, styles.tagContainer), validationState: validationState, disabled: disabled, readonly: readonly, focused: open, variant: 'single-line-container', inputRef: localRef, size: size, prefix: prefixIcon, children: _jsxs(_Fragment, { children: [_jsxs("div", { className: styles.contentWrapper, ref: contentRef, children: [
|
|
114
|
-
|
|
142
|
+
return (_jsx(FieldContainerPrivate, { className: cn(styles.container, styles.tagContainer), validationState: validationState, disabled: disabled, readonly: readonly, focused: open, variant: 'single-line-container', inputRef: localRef, size: size, prefix: prefixIcon, children: _jsxs(_Fragment, { children: [_jsxs("div", { className: styles.contentWrapper, ref: contentRef, children: [selected &&
|
|
143
|
+
selected.map(option => (_jsx(Tag, { size: size === 'l' ? 's' : 'xs', tabIndex: -1, label: String(option.content.option), appearance: option.id ? mapItemsToTagAppearance[option === null || option === void 0 ? void 0 : option.id] : 'neutral', onDelete: !option.disabled && !disabled && !readonly ? handleItemDelete(option) : undefined }, option.id))), _jsx("div", { className: styles.inputWrapper, style: {
|
|
115
144
|
minWidth: value
|
|
116
145
|
? Math.min((_b = (_a = contentRef.current) === null || _a === void 0 ? void 0 : _a.clientWidth) !== null && _b !== void 0 ? _b : BASE_MIN_WIDTH, (_d = (_c = inputPlugRef.current) === null || _c === void 0 ? void 0 : _c.clientWidth) !== null && _d !== void 0 ? _d : BASE_MIN_WIDTH)
|
|
117
146
|
: '100%',
|
|
118
|
-
}, children: _jsx(InputPrivate, { id: id, name: name, type: 'text', disabled: disabled, placeholder: !
|
|
119
|
-
} }) })));
|
|
147
|
+
}, children: _jsx(InputPrivate, { id: id, name: name, type: 'text', disabled: disabled, placeholder: !selected || !selected.length ? placeholder : undefined, ref: mergeRefs(ref, localRef), onChange: searchable ? onInputValueChange : undefined, value: searchable ? inputValue : '', readonly: !searchable || readonly, "data-test-id": 'field-select__input', onKeyDown: handleOnKeyDown(onKeyDown), onBlur: handleBlur, className: styles.input }) })] }), _jsxs("div", { className: styles.postfix, children: [buttons, _jsx(ArrowIcon, { size: arrowIconSize, className: styles.arrowIcon })] }), _jsx("span", { ref: inputPlugRef, className: styles.inputPlug, children: inputValue })] }) }));
|
|
148
|
+
} })) })));
|
|
120
149
|
});
|
|
@@ -14,4 +14,7 @@ export declare const FieldSelectSingle: import("react").ForwardRefExoticComponen
|
|
|
14
14
|
footer?: import("react").ReactNode;
|
|
15
15
|
search?: import("./types").SearchState | undefined;
|
|
16
16
|
autocomplete?: boolean | undefined;
|
|
17
|
-
|
|
17
|
+
addOptionByEnter?: boolean | undefined;
|
|
18
|
+
open?: boolean | undefined;
|
|
19
|
+
onOpenChange?(open: boolean): void;
|
|
20
|
+
} & Pick<import("@snack-uikit/list").ListProps, "dataError" | "noDataState" | "noResultsState" | "errorDataState" | "pinTop" | "pinBottom" | "dataFiltered"> & import("react").RefAttributes<HTMLInputElement>>;
|
|
@@ -11,38 +11,49 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
11
11
|
};
|
|
12
12
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
13
13
|
import mergeRefs from 'merge-refs';
|
|
14
|
-
import { forwardRef, useEffect, useMemo, useRef
|
|
14
|
+
import { forwardRef, useEffect, useMemo, useRef } from 'react';
|
|
15
15
|
import { InputPrivate } from '@snack-uikit/input-private';
|
|
16
16
|
import { Droplist, useFuzzySearch } from '@snack-uikit/list';
|
|
17
17
|
import { extractSupportProps } from '@snack-uikit/utils';
|
|
18
18
|
import { FieldContainerPrivate } from '../../helperComponents';
|
|
19
19
|
import { useValueControl } from '../../hooks';
|
|
20
20
|
import { FieldDecorator } from '../FieldDecorator';
|
|
21
|
+
import { extractFieldDecoratorProps } from '../FieldDecorator/utils';
|
|
21
22
|
import { useButtons, useHandleOnKeyDown, useSearchInput } from './hooks';
|
|
22
23
|
import styles from './styles.module.css';
|
|
23
|
-
import {
|
|
24
|
+
import { extractListProps, findSelectedOption, getArrowIcon, transformOptionsToItems } from './utils';
|
|
24
25
|
export const FieldSelectSingle = forwardRef((_a, ref) => {
|
|
25
|
-
var _b;
|
|
26
|
-
var { id, name, placeholder, size = 's', options, value: valueProp, defaultValue, onChange: onChangeProp,
|
|
26
|
+
var _b, _c;
|
|
27
|
+
var { 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, open: openProp, onOpenChange } = _a, rest = __rest(_a, ["id", "name", "placeholder", "size", "options", "value", "defaultValue", "onChange", "disabled", "readonly", "searchable", "showCopyButton", "showClearButton", "onKeyDown", "required", "validationState", "search", "autocomplete", "prefixIcon", "addOptionByEnter", "open", "onOpenChange"]);
|
|
27
28
|
const localRef = useRef(null);
|
|
28
|
-
const [open, setOpen] =
|
|
29
|
+
const [open = false, setOpen] = useValueControl({ value: openProp, onChange: onOpenChange });
|
|
29
30
|
const [value, setValue] = useValueControl({
|
|
30
31
|
value: valueProp,
|
|
31
32
|
defaultValue,
|
|
32
33
|
onChange: onChangeProp,
|
|
33
34
|
});
|
|
34
35
|
const items = useMemo(() => transformOptionsToItems(options), [options]);
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
const { selected, itemsWithPlaceholder } = useMemo(() => {
|
|
37
|
+
const [fonded, placeholder] = findSelectedOption(items, value);
|
|
38
|
+
return {
|
|
39
|
+
selected: fonded !== null && fonded !== void 0 ? fonded : placeholder,
|
|
40
|
+
itemsWithPlaceholder: (placeholder ? [placeholder] : []).concat(items),
|
|
41
|
+
};
|
|
42
|
+
}, [items, value]);
|
|
43
|
+
const { inputValue, onInputValueChange, prevInputValue } = useSearchInput(Object.assign(Object.assign({}, search), { defaultValue: (_b = selected === null || selected === void 0 ? void 0 : selected.content.option) !== null && _b !== void 0 ? _b : '' }));
|
|
41
44
|
useEffect(() => {
|
|
45
|
+
if ((selected === null || selected === void 0 ? void 0 : selected.content.option) && prevInputValue.current !== (selected === null || selected === void 0 ? void 0 : selected.content.option)) {
|
|
46
|
+
onInputValueChange(selected.content.option);
|
|
47
|
+
prevInputValue.current = selected === null || selected === void 0 ? void 0 : selected.content.option;
|
|
48
|
+
}
|
|
49
|
+
}, [onInputValueChange, selected === null || selected === void 0 ? void 0 : selected.content.option, prevInputValue]);
|
|
50
|
+
const handleBlur = (e) => {
|
|
42
51
|
var _a, _b;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
52
|
+
if (!open && (selected === null || selected === void 0 ? void 0 : selected.content.option) !== inputValue) {
|
|
53
|
+
onInputValueChange((_a = selected === null || selected === void 0 ? void 0 : selected.content.option) !== null && _a !== void 0 ? _a : '');
|
|
54
|
+
}
|
|
55
|
+
(_b = rest === null || rest === void 0 ? void 0 : rest.onBlur) === null || _b === void 0 ? void 0 : _b.call(rest, e);
|
|
56
|
+
};
|
|
46
57
|
const onClear = () => {
|
|
47
58
|
var _a;
|
|
48
59
|
setValue('');
|
|
@@ -54,11 +65,11 @@ export const FieldSelectSingle = forwardRef((_a, ref) => {
|
|
|
54
65
|
const { buttons, inputKeyDownNavigationHandler, buttonsRefs } = useButtons({
|
|
55
66
|
readonly,
|
|
56
67
|
size,
|
|
57
|
-
showClearButton: showClearButton && Boolean(value),
|
|
68
|
+
showClearButton: showClearButton && !disabled && !readonly && Boolean(value),
|
|
58
69
|
showCopyButton,
|
|
59
70
|
inputRef: localRef,
|
|
60
71
|
onClear,
|
|
61
|
-
valueToCopy:
|
|
72
|
+
valueToCopy: (_c = selected === null || selected === void 0 ? void 0 : selected.content.option) !== null && _c !== void 0 ? _c : '',
|
|
62
73
|
});
|
|
63
74
|
const commonHandleOnKeyDown = useHandleOnKeyDown({
|
|
64
75
|
inputKeyDownNavigationHandler,
|
|
@@ -69,6 +80,13 @@ export const FieldSelectSingle = forwardRef((_a, ref) => {
|
|
|
69
80
|
if (!open && prevInputValue.current !== inputValue) {
|
|
70
81
|
setOpen(true);
|
|
71
82
|
}
|
|
83
|
+
if (e.code === 'Enter') {
|
|
84
|
+
e.stopPropagation();
|
|
85
|
+
e.preventDefault();
|
|
86
|
+
}
|
|
87
|
+
if (addOptionByEnter && e.code === 'Enter' && inputValue !== '') {
|
|
88
|
+
setValue(inputValue);
|
|
89
|
+
}
|
|
72
90
|
commonHandleOnKeyDown(onKeyDown)(e);
|
|
73
91
|
};
|
|
74
92
|
const handleSelectionChange = (newValue) => {
|
|
@@ -80,15 +98,22 @@ export const FieldSelectSingle = forwardRef((_a, ref) => {
|
|
|
80
98
|
}
|
|
81
99
|
};
|
|
82
100
|
const handleOpenChange = (open) => {
|
|
101
|
+
var _a, _b;
|
|
83
102
|
if (!readonly && !disabled && !buttonsRefs.includes(document.activeElement)) {
|
|
84
103
|
setOpen(open);
|
|
104
|
+
if (!open) {
|
|
105
|
+
onInputValueChange((_a = selected === null || selected === void 0 ? void 0 : selected.content.option) !== null && _a !== void 0 ? _a : '');
|
|
106
|
+
prevInputValue.current = (_b = selected === null || selected === void 0 ? void 0 : selected.content.option) !== null && _b !== void 0 ? _b : '';
|
|
107
|
+
}
|
|
85
108
|
}
|
|
86
109
|
};
|
|
87
|
-
const fuzzySearch = useFuzzySearch(
|
|
88
|
-
const result = autocomplete
|
|
89
|
-
|
|
110
|
+
const fuzzySearch = useFuzzySearch(itemsWithPlaceholder);
|
|
111
|
+
const result = autocomplete || !searchable || prevInputValue.current === inputValue
|
|
112
|
+
? itemsWithPlaceholder
|
|
113
|
+
: fuzzySearch(inputValue);
|
|
114
|
+
return (_jsx(FieldDecorator, Object.assign({}, extractSupportProps(rest), extractFieldDecoratorProps(rest), { validationState: validationState, required: required, readonly: readonly, labelFor: id, disabled: disabled, size: size, children: _jsx(Droplist, Object.assign({}, extractListProps(rest), { items: result, selection: {
|
|
90
115
|
mode: 'single',
|
|
91
116
|
value: value,
|
|
92
117
|
onChange: handleSelectionChange,
|
|
93
|
-
}, size: size, open: open, onOpenChange: handleOpenChange,
|
|
118
|
+
}, size: size, open: open, onOpenChange: handleOpenChange, triggerElemRef: localRef, children: ({ onKeyDown }) => (_jsxs(FieldContainerPrivate, { className: styles.container, validationState: validationState, disabled: disabled, readonly: readonly, focused: open, variant: 'single-line-container', inputRef: localRef, size: size, prefix: prefixIcon, children: [_jsx(InputPrivate, { id: id, name: name, type: 'text', disabled: disabled, placeholder: placeholder, ref: mergeRefs(ref, localRef), onChange: searchable ? onInputValueChange : undefined, value: inputValue, readonly: !searchable || readonly, "data-test-id": 'field-select__input', onKeyDown: handleOnKeyDown(onKeyDown), onBlur: handleBlur }), _jsxs("div", { className: styles.postfix, children: [buttons, _jsx(ArrowIcon, { size: arrowIconSize, className: styles.arrowIcon })] })] })) })) })));
|
|
94
119
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { KeyboardEvent, KeyboardEventHandler, RefObject } from 'react';
|
|
2
2
|
import { Handler } from 'uncontrollable';
|
|
3
|
-
import {
|
|
3
|
+
import { ItemWithId, SearchState } from './types';
|
|
4
4
|
type UseHandleOnKeyDownProps = {
|
|
5
5
|
inputKeyDownNavigationHandler: KeyboardEventHandler<HTMLInputElement>;
|
|
6
6
|
onInputKeyDownProp: KeyboardEventHandler<HTMLInputElement> | undefined;
|
|
@@ -26,5 +26,5 @@ export declare function useSearchInput({ value, onChange, defaultValue }: Search
|
|
|
26
26
|
onInputValueChange: Handler;
|
|
27
27
|
prevInputValue: import("react").MutableRefObject<string>;
|
|
28
28
|
};
|
|
29
|
-
export declare function useHandleDeleteItem(setValue: Handler): (
|
|
29
|
+
export declare function useHandleDeleteItem(setValue: Handler): (item?: ItemWithId) => () => void;
|
|
30
30
|
export {};
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { useCallback, useMemo, useRef } from 'react';
|
|
2
|
-
import { useUncontrolledProp } from 'uncontrollable';
|
|
3
2
|
import { useButtonNavigation, useClearButton } from '@snack-uikit/input-private';
|
|
4
|
-
import { extractChildIds } from '@snack-uikit/list
|
|
5
|
-
import { useCopyButton } from '../../hooks';
|
|
6
|
-
import {
|
|
3
|
+
import { extractChildIds, isAccordionItemProps, isNextListItemProps, } from '@snack-uikit/list';
|
|
4
|
+
import { useCopyButton, useValueControl } from '../../hooks';
|
|
5
|
+
import { isBaseOptionProps } from './utils';
|
|
7
6
|
export function useHandleOnKeyDown({ setOpen, inputKeyDownNavigationHandler, onInputKeyDownProp, }) {
|
|
8
7
|
return useCallback((onKeyDown) => (e) => {
|
|
9
8
|
if (e.code === 'Space') {
|
|
@@ -51,22 +50,23 @@ export function useButtons({ readonly, showClearButton, showCopyButton, size, on
|
|
|
51
50
|
return { buttons, inputKeyDownNavigationHandler, buttonsRefs };
|
|
52
51
|
}
|
|
53
52
|
export function useSearchInput({ value, onChange, defaultValue }) {
|
|
54
|
-
const [inputValue, onInputValueChange] =
|
|
53
|
+
const [inputValue = '', onInputValueChange] = useValueControl({ value, onChange, defaultValue });
|
|
55
54
|
const prevInputValue = useRef(inputValue);
|
|
56
55
|
return { inputValue, onInputValueChange, prevInputValue };
|
|
57
56
|
}
|
|
58
57
|
export function useHandleDeleteItem(setValue) {
|
|
59
|
-
return useCallback((
|
|
60
|
-
|
|
58
|
+
return useCallback((item) => () => {
|
|
59
|
+
var _a;
|
|
60
|
+
if (!item) {
|
|
61
61
|
return;
|
|
62
62
|
}
|
|
63
|
-
if (
|
|
64
|
-
const removeIds = extractChildIds({ items:
|
|
63
|
+
if (isAccordionItemProps(item) || isNextListItemProps(item)) {
|
|
64
|
+
const removeIds = extractChildIds({ items: item.items }).concat((_a = item.id) !== null && _a !== void 0 ? _a : '');
|
|
65
65
|
setValue((value) => value === null || value === void 0 ? void 0 : value.filter(v => !removeIds.includes(v !== null && v !== void 0 ? v : '')));
|
|
66
66
|
return;
|
|
67
67
|
}
|
|
68
|
-
if (isBaseOptionProps(
|
|
69
|
-
setValue((value) => value === null || value === void 0 ? void 0 : value.filter(v => v !==
|
|
68
|
+
if (isBaseOptionProps(item)) {
|
|
69
|
+
setValue((value) => value === null || value === void 0 ? void 0 : value.filter(v => v !== item.id));
|
|
70
70
|
}
|
|
71
71
|
}, [setValue]);
|
|
72
72
|
}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { ReactElement } from 'react';
|
|
2
2
|
import { InputPrivateProps } from '@snack-uikit/input-private';
|
|
3
3
|
import { AccordionItemProps, BaseItemProps, GroupItemProps, ListProps, NextListItemProps, SelectionMultipleState, SelectionSingleState } from '@snack-uikit/list';
|
|
4
|
+
import { TagProps } from '@snack-uikit/tag';
|
|
4
5
|
import { WithSupportProps } from '@snack-uikit/utils';
|
|
5
6
|
import { FieldDecoratorProps } from '../FieldDecorator';
|
|
6
7
|
export type OptionProps = BaseOptionProps | AccordionOptionProps | GroupOptionProps | NestListOptionProps;
|
|
7
8
|
export type OptionWithoutGroup = BaseOptionProps | AccordionOptionProps | NestListOptionProps;
|
|
8
9
|
export type BaseOptionProps = Pick<BaseItemProps, 'beforeContent' | 'afterContent' | 'disabled'> & BaseItemProps['content'] & {
|
|
9
10
|
value: string | number;
|
|
10
|
-
}
|
|
11
|
+
} & Pick<TagProps, 'appearance'>;
|
|
11
12
|
export type AccordionOptionProps = Pick<AccordionItemProps, 'type'> & BaseOptionProps & {
|
|
12
13
|
options: OptionProps[];
|
|
13
14
|
};
|
|
@@ -48,12 +49,18 @@ type FiledSelectCommonProps = WithSupportProps<{
|
|
|
48
49
|
footer?: ListProps['footer'];
|
|
49
50
|
search?: SearchState;
|
|
50
51
|
autocomplete?: boolean;
|
|
51
|
-
|
|
52
|
+
addOptionByEnter?: boolean;
|
|
53
|
+
open?: boolean;
|
|
54
|
+
onOpenChange?(open: boolean): void;
|
|
55
|
+
}> & Pick<ListProps, 'dataError' | 'noDataState' | 'noResultsState' | 'errorDataState' | 'pinTop' | 'pinBottom' | 'dataFiltered'>;
|
|
52
56
|
export type FieldSelectSingleProps = FieldSelectPrivateProps & Omit<SelectionSingleState, 'mode'> & WrapperProps & FiledSelectCommonProps;
|
|
53
|
-
export type FieldSelectMultipleProps = FieldSelectPrivateProps &
|
|
57
|
+
export type FieldSelectMultipleProps = FieldSelectPrivateProps & {
|
|
58
|
+
removeByBackspace?: boolean;
|
|
59
|
+
} & Omit<SelectionMultipleState, 'mode'> & Omit<FiledSelectCommonProps, 'showCopyButton'>;
|
|
54
60
|
export type FieldSelectProps = (FieldSelectSingleProps & {
|
|
55
61
|
selection?: 'single';
|
|
56
62
|
}) | (FieldSelectMultipleProps & {
|
|
57
63
|
selection: 'multiple';
|
|
58
64
|
});
|
|
65
|
+
export type ItemWithId = BaseItemProps | AccordionItemProps | NextListItemProps;
|
|
59
66
|
export {};
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { Size } from '@snack-uikit/input-private';
|
|
2
|
-
import { ItemProps } from '@snack-uikit/list';
|
|
3
|
-
import {
|
|
2
|
+
import { DroplistProps, ItemProps, SelectionSingleValueType } from '@snack-uikit/list';
|
|
3
|
+
import { TagProps } from '@snack-uikit/tag';
|
|
4
|
+
import { AccordionOptionProps, BaseOptionProps, FieldSelectMultipleProps, FieldSelectProps, FieldSelectSingleProps, GroupOptionProps, ItemWithId, NestListOptionProps, OptionProps } from './types';
|
|
4
5
|
export declare function isBaseOptionProps(option: any): option is BaseOptionProps;
|
|
5
6
|
export declare function isAccordionOptionProps(option: any): option is AccordionOptionProps;
|
|
6
7
|
export declare function isNextListOptionProps(option: any): option is NestListOptionProps;
|
|
7
8
|
export declare function isGroupOptionProps(option: any): option is GroupOptionProps;
|
|
9
|
+
export declare function mapOptionToAppearance(options: OptionProps[]): Record<string | number, TagProps['appearance'] | undefined>;
|
|
8
10
|
export declare function transformOptionsToItems(options: OptionProps[]): ItemProps[];
|
|
9
|
-
export declare function
|
|
10
|
-
export declare function
|
|
11
|
+
export declare function findSelectedOption(items: ItemProps[], value: SelectionSingleValueType): [ItemWithId | undefined, ItemWithId | undefined];
|
|
12
|
+
export declare function findSelectedOptions(items: ItemProps[], value: SelectionSingleValueType[] | undefined): [ItemWithId[] | undefined, ItemWithId[] | undefined];
|
|
11
13
|
export declare function isFieldSelectMultipleProps(props: any): props is FieldSelectMultipleProps;
|
|
12
14
|
export declare function isFieldSelectSingleProps(props: any): props is FieldSelectSingleProps;
|
|
13
15
|
export declare function getArrowIcon({ size, open }: {
|
|
@@ -17,3 +19,4 @@ export declare function getArrowIcon({ size, open }: {
|
|
|
17
19
|
ArrowIcon: ({ size, ...props }: import("@snack-uikit/icons/dist/components/interface-icons/chevronUp").ISvgIconProps) => import("react/jsx-runtime").JSX.Element;
|
|
18
20
|
arrowIconSize: 16 | 24;
|
|
19
21
|
};
|
|
22
|
+
export declare function extractListProps({ dataError, noDataState, noResultsState, errorDataState, pinTop, pinBottom, dataFiltered, loading, }: Partial<FieldSelectProps>): Partial<DroplistProps>;
|