@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 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
- }, "showCopyButton"> & import("react").RefAttributes<HTMLInputElement>>;
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, useState } from 'react';
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 { extractSelectedMultipleOptions, getArrowIcon, transformOptionsToItems } from './utils';
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 { id, name, placeholder, size = 's', options, value: valueProp, defaultValue, onChange: onChangeProp, loading, disabled = false, readonly = false, searchable = true, showClearButton = true, onKeyDown: onInputKeyDownProp, label, labelTooltip, labelTooltipPlacement, required = false, hint, showHintIcon, validationState = 'default', footer, search, autocomplete = false, prefixIcon, error } = _a, rest = __rest(_a, ["id", "name", "placeholder", "size", "options", "value", "defaultValue", "onChange", "loading", "disabled", "readonly", "searchable", "showClearButton", "onKeyDown", "label", "labelTooltip", "labelTooltipPlacement", "required", "hint", "showHintIcon", "validationState", "footer", "search", "autocomplete", "prefixIcon", "error"]);
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] = useState(false);
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 selectedOption = useMemo(() => {
40
- const notSortSelectedOption = extractSelectedMultipleOptions(options, value);
41
- if (notSortSelectedOption) {
42
- return notSortSelectedOption.sort((a, b) => {
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
- }, [options, value]);
53
- const { inputValue, onInputValueChange, prevInputValue } = useSearchInput(Object.assign(Object.assign({}, search), { defaultValue: String(selectedOption !== null && selectedOption !== void 0 ? selectedOption : '') }));
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
- setValue(undefined);
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 && Boolean(value),
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 ((selectedOption === null || selectedOption === void 0 ? void 0 : selectedOption.length) && !selectedOption.slice(-1)[0].disabled) {
79
- handleItemDelete(selectedOption.pop())();
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
- prevInputValue.current = inputValue;
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
- prevInputValue.current = '';
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(items);
106
- const result = autocomplete ? items : fuzzySearch(prevInputValue.current !== inputValue ? inputValue : '');
107
- return (_jsx(FieldDecorator, Object.assign({}, extractSupportProps(rest), { error: error, required: required, readonly: readonly, label: label, labelTooltip: labelTooltip, labelTooltipPlacement: labelTooltipPlacement, labelFor: id, hint: hint, disabled: disabled, showHintIcon: showHintIcon, size: size, validationState: validationState, children: _jsx(Droplist, { trigger: 'clickAndFocusVisible', placement: 'bottom', "data-test-id": 'field-select__list', items: result, triggerElemRef: localRef, scroll: true, marker: true, footer: footer, selection: {
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, loading: loading, children: ({ onKeyDown }) => {
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: [selectedOption &&
114
- selectedOption.map(option => (_jsx(Tag, { size: size === 'l' ? 's' : 'xs', tabIndex: -1, label: String(option.option), onDelete: !option.disabled && !disabled ? handleItemDelete(option) : undefined }, option.value))), _jsx("div", { className: styles.inputWrapper, style: {
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: !selectedOption ? 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 })] }) }));
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
- } & import("react").RefAttributes<HTMLInputElement>>;
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, useState } from 'react';
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 { extractSelectedOptions, getArrowIcon, transformOptionsToItems } from './utils';
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, loading, disabled = false, readonly = false, searchable = true, showCopyButton = true, showClearButton = true, onKeyDown: onInputKeyDownProp, label, labelTooltip, labelTooltipPlacement, required = false, hint, showHintIcon, validationState = 'default', footer, search, autocomplete = false, prefixIcon, error } = _a, rest = __rest(_a, ["id", "name", "placeholder", "size", "options", "value", "defaultValue", "onChange", "loading", "disabled", "readonly", "searchable", "showCopyButton", "showClearButton", "onKeyDown", "label", "labelTooltip", "labelTooltipPlacement", "required", "hint", "showHintIcon", "validationState", "footer", "search", "autocomplete", "prefixIcon", "error"]);
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] = useState(false);
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 selectedOption = useMemo(() => extractSelectedOptions(options, value), [options, value]);
36
- const { inputValue, onInputValueChange, prevInputValue } = useSearchInput(Object.assign(Object.assign({}, search), { defaultValue: String(selectedOption !== null && selectedOption !== void 0 ? selectedOption : '') }));
37
- useEffect(() => {
38
- var _a;
39
- !open && onInputValueChange(String((_a = selectedOption === null || selectedOption === void 0 ? void 0 : selectedOption.option) !== null && _a !== void 0 ? _a : ''));
40
- }, [onInputValueChange, open, selectedOption]);
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
- onInputValueChange(String((_a = selectedOption === null || selectedOption === void 0 ? void 0 : selectedOption.option) !== null && _a !== void 0 ? _a : ''));
44
- prevInputValue.current = String((_b = selectedOption === null || selectedOption === void 0 ? void 0 : selectedOption.option) !== null && _b !== void 0 ? _b : '');
45
- }, [prevInputValue, onInputValueChange, selectedOption]);
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: String((_b = selectedOption === null || selectedOption === void 0 ? void 0 : selectedOption.option) !== null && _b !== void 0 ? _b : ''),
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(items);
88
- const result = autocomplete ? items : fuzzySearch(prevInputValue.current !== inputValue ? inputValue : '');
89
- return (_jsx(FieldDecorator, Object.assign({}, extractSupportProps(rest), { error: error, required: required, readonly: readonly, label: label, labelTooltip: labelTooltip, labelTooltipPlacement: labelTooltipPlacement, labelFor: id, hint: hint, disabled: disabled, showHintIcon: showHintIcon, size: size, validationState: validationState, children: _jsx(Droplist, { trigger: 'clickAndFocusVisible', placement: 'bottom', "data-test-id": 'field-select__list', items: result, triggerElemRef: localRef, scroll: true, marker: true, footer: footer, selection: {
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, loading: loading, 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) }), _jsxs("div", { className: styles.postfix, children: [buttons, _jsx(ArrowIcon, { size: arrowIconSize, className: styles.arrowIcon })] })] })) }) })));
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 { OptionProps, SearchState } from './types';
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): (option?: OptionProps) => () => void;
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/dist/utils';
5
- import { useCopyButton } from '../../hooks';
6
- import { isAccordionOptionProps, isBaseOptionProps, isNextListOptionProps, transformOptionsToItems } from './utils';
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] = useUncontrolledProp(value, defaultValue !== null && defaultValue !== void 0 ? defaultValue : '', onChange);
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((option) => () => {
60
- if (!option) {
58
+ return useCallback((item) => () => {
59
+ var _a;
60
+ if (!item) {
61
61
  return;
62
62
  }
63
- if (isAccordionOptionProps(option) || isNextListOptionProps(option)) {
64
- const removeIds = extractChildIds({ items: transformOptionsToItems(option.options) }).concat(option.value);
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(option)) {
69
- setValue((value) => value === null || value === void 0 ? void 0 : value.filter(v => v !== option.value));
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 & Omit<SelectionMultipleState, 'mode'> & Omit<FiledSelectCommonProps, 'showCopyButton'>;
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 { AccordionOptionProps, BaseOptionProps, FieldSelectMultipleProps, FieldSelectSingleProps, GroupOptionProps, NestListOptionProps, OptionProps, OptionWithoutGroup } from './types';
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 extractSelectedOptions(options: OptionProps[], value: string | number | undefined): OptionWithoutGroup | undefined;
10
- export declare function extractSelectedMultipleOptions(options: OptionProps[], value?: (string | number | undefined)[]): OptionWithoutGroup[] | undefined;
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>;