@cloud-ru/uikit-product-mobile-fields 2.0.15 → 2.0.16-preview-b1a595e8.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.
@@ -39,7 +39,7 @@ const defaultSelectedOptionFormatter = item =>
39
39
  // @ts-ignore
40
40
  (item === null || item === void 0 ? void 0 : item.content.option) || '';
41
41
  exports.MobileFieldSelectMultiple = (0, react_1.forwardRef)((_a, ref) => {
42
- var { id, name, placeholder, size = 's', options, virtualized, value: valueProp, defaultValue, onChange: onChangeProp, disabled = false, readonly = false, searchable: searchableProp = true, showClearButton = true, onKeyDown: onInputKeyDownProp, validationState = 'default', search, enableFuzzySearch = true, autocomplete = false, autoFocus, prefixIcon, prefix, postfix, removeByBackspace = false, addOptionByEnter = false, open: openProp, onOpenChange, selectedOptionFormatter = defaultSelectedOptionFormatter } = _a, rest = __rest(_a, ["id", "name", "placeholder", "size", "options", "virtualized", "value", "defaultValue", "onChange", "disabled", "readonly", "searchable", "showClearButton", "onKeyDown", "validationState", "search", "enableFuzzySearch", "autocomplete", "autoFocus", "prefixIcon", "prefix", "postfix", "removeByBackspace", "addOptionByEnter", "open", "onOpenChange", "selectedOptionFormatter"]);
42
+ var { id, name, placeholder, size = 's', options, virtualized, value: valueProp, defaultValue, onChange: onChangeProp, disabled = false, readonly = false, searchable: searchableProp = true, showClearButton = true, onKeyDown: onInputKeyDownProp, validationState = 'default', search, enableFuzzySearch = true, autocomplete = false, autoFocus, prefixIcon, prefix, postfix, removeByBackspace = false, addOptionByEnter = false, addCustomOptionTriggers, open: openProp, onOpenChange, selectedOptionFormatter = defaultSelectedOptionFormatter } = _a, rest = __rest(_a, ["id", "name", "placeholder", "size", "options", "virtualized", "value", "defaultValue", "onChange", "disabled", "readonly", "searchable", "showClearButton", "onKeyDown", "validationState", "search", "enableFuzzySearch", "autocomplete", "autoFocus", "prefixIcon", "prefix", "postfix", "removeByBackspace", "addOptionByEnter", "addCustomOptionTriggers", "open", "onOpenChange", "selectedOptionFormatter"]);
43
43
  const { t } = (0, uikit_product_locale_1.useLocale)('MobileFields');
44
44
  const localRef = (0, react_1.useRef)(null);
45
45
  const contentRef = (0, react_1.useRef)(null);
@@ -53,6 +53,14 @@ exports.MobileFieldSelectMultiple = (0, react_1.forwardRef)((_a, ref) => {
53
53
  const { flattenItems } = (0, react_1.useMemo)(() => (0, list_1.kindFlattenItems)({ items }), [items]);
54
54
  const searchable = (searchableProp && Object.values(flattenItems).length > 5) || autocomplete || Boolean(addOptionByEnter);
55
55
  const { inputValue, setInputValue, prevInputValue, updateInputValue } = (0, hooks_2.useSearchInput)(Object.assign(Object.assign({}, search), { defaultValue: '', selectedOptionFormatter }));
56
+ const { resolvedAddCustomOptionTriggers, tryCommitCustomOptionFromInput } = (0, fields_1.useFieldSelectMultipleCustomOption)({
57
+ addCustomOptionTriggers,
58
+ addOptionByEnter,
59
+ inputValue,
60
+ value,
61
+ setValue,
62
+ updateInputValue,
63
+ });
56
64
  const prefixSettings = (0, hooks_1.usePrefix)({ prefix, disabled });
57
65
  const postfixSettings = (0, hooks_1.usePostfix)({ postfix, disabled });
58
66
  (0, utils_1.useLayoutEffect)(() => {
@@ -90,11 +98,14 @@ exports.MobileFieldSelectMultiple = (0, react_1.forwardRef)((_a, ref) => {
90
98
  if (e.code === 'Enter') {
91
99
  e.stopPropagation();
92
100
  e.preventDefault();
101
+ tryCommitCustomOptionFromInput('enter');
93
102
  }
94
- if (addOptionByEnter && e.code === 'Enter' && inputValue !== '') {
95
- if (!(value !== null && value !== void 0 ? value : []).includes(inputValue)) {
96
- setValue((value) => (value !== null && value !== void 0 ? value : []).concat(inputValue));
97
- updateInputValue();
103
+ else {
104
+ const customOptionTrigger = (0, fields_1.getCustomOptionTriggerByCode)(e.code);
105
+ if ((0, fields_1.shouldHandleCustomOptionTrigger)(customOptionTrigger, resolvedAddCustomOptionTriggers)) {
106
+ e.stopPropagation();
107
+ e.preventDefault();
108
+ tryCommitCustomOptionFromInput(customOptionTrigger);
98
109
  }
99
110
  }
100
111
  if (!open && prevInputValue.current !== inputValue) {
@@ -122,6 +133,9 @@ exports.MobileFieldSelectMultiple = (0, react_1.forwardRef)((_a, ref) => {
122
133
  (_a = rest === null || rest === void 0 ? void 0 : rest.onBlur) === null || _a === void 0 ? void 0 : _a.call(rest, e);
123
134
  }
124
135
  };
136
+ const handleSearchBlur = () => {
137
+ tryCommitCustomOptionFromInput('blur');
138
+ };
125
139
  const searcher = (0, legacy_1.useSearch)(items, enableFuzzySearch);
126
140
  const result = autocomplete || !searchable || prevInputValue.current === inputValue ? items : searcher(inputValue);
127
141
  const fieldValidationState = (0, getValidationState_1.getValidationState)({ validationState, error: rest.error });
@@ -130,6 +144,7 @@ exports.MobileFieldSelectMultiple = (0, react_1.forwardRef)((_a, ref) => {
130
144
  value: inputValue,
131
145
  onChange: setInputValue,
132
146
  onKeyDown: handleOnKeyDown(),
147
+ onBlur: handleSearchBlur,
133
148
  }
134
149
  : undefined, contentRender: (_a) => {
135
150
  var { content } = _a, rest = __rest(_a, ["content"]);
@@ -35,7 +35,7 @@ const defaultSelectedOptionFormatter = item =>
35
35
  // @ts-expect-error
36
36
  (item === null || item === void 0 ? void 0 : item.content.option) || '';
37
37
  exports.MobileFieldSelectSingle = (0, react_1.forwardRef)((_a, ref) => {
38
- var { id, name, placeholder, size = 's', options, virtualized, value: valueProp, defaultValue, onChange: onChangeProp, disabled = false, readonly = false, searchable: searchableProp = true, showCopyButton = true, showClearButton = true, onKeyDown: onInputKeyDownProp, required = false, validationState = 'default', search, enableFuzzySearch = true, autocomplete = false, autoFocus, prefixIcon, prefix, postfix, addOptionByEnter = false, open: openProp, onOpenChange, selectedOptionFormatter = defaultSelectedOptionFormatter } = _a, rest = __rest(_a, ["id", "name", "placeholder", "size", "options", "virtualized", "value", "defaultValue", "onChange", "disabled", "readonly", "searchable", "showCopyButton", "showClearButton", "onKeyDown", "required", "validationState", "search", "enableFuzzySearch", "autocomplete", "autoFocus", "prefixIcon", "prefix", "postfix", "addOptionByEnter", "open", "onOpenChange", "selectedOptionFormatter"]);
38
+ var { id, name, placeholder, size = 's', options, virtualized, value: valueProp, defaultValue, onChange: onChangeProp, disabled = false, readonly = false, searchable: searchableProp = true, showCopyButton = true, showClearButton = true, onKeyDown: onInputKeyDownProp, required = false, validationState = 'default', search, enableFuzzySearch = true, autocomplete = false, autoFocus, prefixIcon, prefix, postfix, addOptionByEnter = false, addCustomOptionTriggers, open: openProp, onOpenChange, selectedOptionFormatter = defaultSelectedOptionFormatter } = _a, rest = __rest(_a, ["id", "name", "placeholder", "size", "options", "virtualized", "value", "defaultValue", "onChange", "disabled", "readonly", "searchable", "showCopyButton", "showClearButton", "onKeyDown", "required", "validationState", "search", "enableFuzzySearch", "autocomplete", "autoFocus", "prefixIcon", "prefix", "postfix", "addOptionByEnter", "addCustomOptionTriggers", "open", "onOpenChange", "selectedOptionFormatter"]);
39
39
  const localRef = (0, react_1.useRef)(null);
40
40
  const [open = false, setOpen] = (0, utils_1.useValueControl)({ value: openProp, onChange: onOpenChange });
41
41
  const [value, setValue] = (0, utils_1.useValueControl)({
@@ -83,6 +83,20 @@ exports.MobileFieldSelectSingle = (0, react_1.forwardRef)((_a, ref) => {
83
83
  onClear,
84
84
  valueToCopy: selectedOptionFormatter(selectedItem),
85
85
  });
86
+ const handleSelectionChange = (0, react_1.useCallback)((newValue) => {
87
+ var _a;
88
+ (_a = localRef.current) === null || _a === void 0 ? void 0 : _a.focus();
89
+ setOpen(false);
90
+ if (newValue !== undefined) {
91
+ setValue(newValue);
92
+ }
93
+ }, [setOpen, setValue]);
94
+ const { resolvedAddCustomOptionTriggers, tryCommitCustomOptionFromInput } = (0, fields_1.useFieldSelectSingleCustomOption)({
95
+ addCustomOptionTriggers,
96
+ addOptionByEnter,
97
+ inputValue,
98
+ handleSelectionChange,
99
+ });
86
100
  const handleBlur = (e) => {
87
101
  var _a;
88
102
  if (!open && !buttonsRefs.filter(Boolean).includes(e.relatedTarget)) {
@@ -90,19 +104,15 @@ exports.MobileFieldSelectSingle = (0, react_1.forwardRef)((_a, ref) => {
90
104
  (_a = rest === null || rest === void 0 ? void 0 : rest.onBlur) === null || _a === void 0 ? void 0 : _a.call(rest, e);
91
105
  }
92
106
  };
107
+ const handleSearchBlur = () => {
108
+ tryCommitCustomOptionFromInput('blur');
109
+ setInputValue('');
110
+ };
93
111
  const commonHandleOnKeyDown = (0, hooks_2.useHandleOnKeyDown)({
94
112
  inputKeyDownNavigationHandler,
95
113
  onInputKeyDownProp,
96
114
  setOpen,
97
115
  });
98
- const handleSelectionChange = (0, react_1.useCallback)((newValue) => {
99
- var _a;
100
- (_a = localRef.current) === null || _a === void 0 ? void 0 : _a.focus();
101
- setOpen(false);
102
- if (newValue !== undefined) {
103
- setValue(newValue);
104
- }
105
- }, [setOpen, setValue]);
106
116
  const handleOnKeyDown = (onKeyDown) => (e) => {
107
117
  if (!open && prevInputValue.current !== inputValue) {
108
118
  setOpen(true);
@@ -110,16 +120,14 @@ exports.MobileFieldSelectSingle = (0, react_1.forwardRef)((_a, ref) => {
110
120
  if (e.code === 'Enter') {
111
121
  e.stopPropagation();
112
122
  e.preventDefault();
113
- }
114
- if (addOptionByEnter && e.code === 'Enter' && inputValue !== '') {
115
- handleSelectionChange(inputValue);
123
+ tryCommitCustomOptionFromInput('enter');
116
124
  }
117
125
  commonHandleOnKeyDown(onKeyDown)(e);
118
126
  };
119
127
  const handleOpenChange = (open) => {
120
128
  if (!readonly && !disabled && (0, utils_1.isBrowser)() && !buttonsRefs.includes(document.activeElement)) {
121
129
  setOpen(open);
122
- if (!open) {
130
+ if (!open && !resolvedAddCustomOptionTriggers.includes('blur')) {
123
131
  setInputValue('');
124
132
  }
125
133
  }
@@ -140,6 +148,7 @@ exports.MobileFieldSelectSingle = (0, react_1.forwardRef)((_a, ref) => {
140
148
  value: inputValue,
141
149
  onChange: setInputValue,
142
150
  onKeyDown: handleOnKeyDown(),
151
+ onBlur: handleSearchBlur,
143
152
  }
144
153
  : undefined, selection: {
145
154
  mode: 'single',
@@ -1,5 +1,5 @@
1
1
  import { ReactElement, ReactNode } from 'react';
2
- import { FieldDecoratorProps } from '@snack-uikit/fields';
2
+ import { FieldDecoratorProps, FieldSelectMultipleProps, FieldSelectProps, FieldSelectSingleProps } from '@snack-uikit/fields';
3
3
  import { InputPrivateProps } from '@snack-uikit/input-private';
4
4
  import { AccordionItemProps, BaseItemProps, DroplistProps, GroupItemProps, ItemContentProps, NextListItemProps, SelectionMultipleState, SelectionSingleState } from '@snack-uikit/list';
5
5
  import { TagProps } from '@snack-uikit/tag';
@@ -71,15 +71,14 @@ type FiledSelectCommonProps = WithSupportProps<{
71
71
  */
72
72
  enableFuzzySearch?: boolean;
73
73
  autocomplete?: boolean;
74
- addOptionByEnter?: boolean;
75
74
  open?: boolean;
76
75
  onOpenChange?(open: boolean): void;
77
76
  selectedOptionFormatter?: SelectedOptionFormatter;
78
- }> & Pick<DroplistProps, 'dataError' | 'noDataState' | 'noResultsState' | 'errorDataState' | 'dataFiltered'>;
79
- export type MobileFieldSelectSingleProps = FieldSelectPrivateProps & Omit<SelectionSingleState, 'mode'> & WrapperProps & FiledSelectCommonProps;
77
+ }> & Pick<FieldSelectProps, 'addOptionByEnter'> & Pick<DroplistProps, 'dataError' | 'noDataState' | 'noResultsState' | 'errorDataState' | 'dataFiltered'>;
78
+ export type MobileFieldSelectSingleProps = FieldSelectPrivateProps & Omit<SelectionSingleState, 'mode'> & WrapperProps & FiledSelectCommonProps & Pick<FieldSelectSingleProps, 'addCustomOptionTriggers'>;
80
79
  export type MobileFieldSelectMultipleProps = FieldSelectPrivateProps & {
81
80
  removeByBackspace?: boolean;
82
- } & Omit<SelectionMultipleState, 'mode'> & Omit<FiledSelectCommonProps, 'showCopyButton'>;
81
+ } & Omit<SelectionMultipleState, 'mode'> & Omit<FiledSelectCommonProps, 'showCopyButton'> & Pick<FieldSelectMultipleProps, 'addCustomOptionTriggers'>;
83
82
  export type MobileFieldSelectProps = (MobileFieldSelectSingleProps & {
84
83
  selection?: 'single';
85
84
  }) | (MobileFieldSelectMultipleProps & {
@@ -16,7 +16,7 @@ import { forwardRef, useMemo, useRef, useState, } from 'react';
16
16
  import { useLocale } from '@cloud-ru/uikit-product-locale';
17
17
  import { MobileModalCustom } from '@cloud-ru/uikit-product-mobile-modal';
18
18
  import { ButtonFilled, ButtonFunction } from '@snack-uikit/button';
19
- import { FieldDecorator } from '@snack-uikit/fields';
19
+ import { FieldDecorator, getCustomOptionTriggerByCode, shouldHandleCustomOptionTrigger, useFieldSelectMultipleCustomOption, } from '@snack-uikit/fields';
20
20
  import { InputPrivate } from '@snack-uikit/input-private';
21
21
  import { kindFlattenItems, List } from '@snack-uikit/list';
22
22
  import { Tag } from '@snack-uikit/tag';
@@ -33,7 +33,7 @@ const defaultSelectedOptionFormatter = item =>
33
33
  // @ts-ignore
34
34
  (item === null || item === void 0 ? void 0 : item.content.option) || '';
35
35
  export const MobileFieldSelectMultiple = forwardRef((_a, ref) => {
36
- var { id, name, placeholder, size = 's', options, virtualized, value: valueProp, defaultValue, onChange: onChangeProp, disabled = false, readonly = false, searchable: searchableProp = true, showClearButton = true, onKeyDown: onInputKeyDownProp, validationState = 'default', search, enableFuzzySearch = true, autocomplete = false, autoFocus, prefixIcon, prefix, postfix, removeByBackspace = false, addOptionByEnter = false, open: openProp, onOpenChange, selectedOptionFormatter = defaultSelectedOptionFormatter } = _a, rest = __rest(_a, ["id", "name", "placeholder", "size", "options", "virtualized", "value", "defaultValue", "onChange", "disabled", "readonly", "searchable", "showClearButton", "onKeyDown", "validationState", "search", "enableFuzzySearch", "autocomplete", "autoFocus", "prefixIcon", "prefix", "postfix", "removeByBackspace", "addOptionByEnter", "open", "onOpenChange", "selectedOptionFormatter"]);
36
+ var { id, name, placeholder, size = 's', options, virtualized, value: valueProp, defaultValue, onChange: onChangeProp, disabled = false, readonly = false, searchable: searchableProp = true, showClearButton = true, onKeyDown: onInputKeyDownProp, validationState = 'default', search, enableFuzzySearch = true, autocomplete = false, autoFocus, prefixIcon, prefix, postfix, removeByBackspace = false, addOptionByEnter = false, addCustomOptionTriggers, open: openProp, onOpenChange, selectedOptionFormatter = defaultSelectedOptionFormatter } = _a, rest = __rest(_a, ["id", "name", "placeholder", "size", "options", "virtualized", "value", "defaultValue", "onChange", "disabled", "readonly", "searchable", "showClearButton", "onKeyDown", "validationState", "search", "enableFuzzySearch", "autocomplete", "autoFocus", "prefixIcon", "prefix", "postfix", "removeByBackspace", "addOptionByEnter", "addCustomOptionTriggers", "open", "onOpenChange", "selectedOptionFormatter"]);
37
37
  const { t } = useLocale('MobileFields');
38
38
  const localRef = useRef(null);
39
39
  const contentRef = useRef(null);
@@ -47,6 +47,14 @@ export const MobileFieldSelectMultiple = forwardRef((_a, ref) => {
47
47
  const { flattenItems } = useMemo(() => kindFlattenItems({ items }), [items]);
48
48
  const searchable = (searchableProp && Object.values(flattenItems).length > 5) || autocomplete || Boolean(addOptionByEnter);
49
49
  const { inputValue, setInputValue, prevInputValue, updateInputValue } = useSearchInput(Object.assign(Object.assign({}, search), { defaultValue: '', selectedOptionFormatter }));
50
+ const { resolvedAddCustomOptionTriggers, tryCommitCustomOptionFromInput } = useFieldSelectMultipleCustomOption({
51
+ addCustomOptionTriggers,
52
+ addOptionByEnter,
53
+ inputValue,
54
+ value,
55
+ setValue,
56
+ updateInputValue,
57
+ });
50
58
  const prefixSettings = usePrefix({ prefix, disabled });
51
59
  const postfixSettings = usePostfix({ postfix, disabled });
52
60
  useLayoutEffect(() => {
@@ -84,11 +92,14 @@ export const MobileFieldSelectMultiple = forwardRef((_a, ref) => {
84
92
  if (e.code === 'Enter') {
85
93
  e.stopPropagation();
86
94
  e.preventDefault();
95
+ tryCommitCustomOptionFromInput('enter');
87
96
  }
88
- if (addOptionByEnter && e.code === 'Enter' && inputValue !== '') {
89
- if (!(value !== null && value !== void 0 ? value : []).includes(inputValue)) {
90
- setValue((value) => (value !== null && value !== void 0 ? value : []).concat(inputValue));
91
- updateInputValue();
97
+ else {
98
+ const customOptionTrigger = getCustomOptionTriggerByCode(e.code);
99
+ if (shouldHandleCustomOptionTrigger(customOptionTrigger, resolvedAddCustomOptionTriggers)) {
100
+ e.stopPropagation();
101
+ e.preventDefault();
102
+ tryCommitCustomOptionFromInput(customOptionTrigger);
92
103
  }
93
104
  }
94
105
  if (!open && prevInputValue.current !== inputValue) {
@@ -116,6 +127,9 @@ export const MobileFieldSelectMultiple = forwardRef((_a, ref) => {
116
127
  (_a = rest === null || rest === void 0 ? void 0 : rest.onBlur) === null || _a === void 0 ? void 0 : _a.call(rest, e);
117
128
  }
118
129
  };
130
+ const handleSearchBlur = () => {
131
+ tryCommitCustomOptionFromInput('blur');
132
+ };
119
133
  const searcher = useSearch(items, enableFuzzySearch);
120
134
  const result = autocomplete || !searchable || prevInputValue.current === inputValue ? items : searcher(inputValue);
121
135
  const fieldValidationState = getValidationState({ validationState, error: rest.error });
@@ -124,6 +138,7 @@ export const MobileFieldSelectMultiple = forwardRef((_a, ref) => {
124
138
  value: inputValue,
125
139
  onChange: setInputValue,
126
140
  onKeyDown: handleOnKeyDown(),
141
+ onBlur: handleSearchBlur,
127
142
  }
128
143
  : undefined, contentRender: (_a) => {
129
144
  var { content } = _a, rest = __rest(_a, ["content"]);
@@ -13,7 +13,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
13
13
  import mergeRefs from 'merge-refs';
14
14
  import { forwardRef, useCallback, useEffect, useMemo, useRef, useState, } from 'react';
15
15
  import { MobileModalCustom } from '@cloud-ru/uikit-product-mobile-modal';
16
- import { FieldDecorator } from '@snack-uikit/fields';
16
+ import { FieldDecorator, useFieldSelectSingleCustomOption } from '@snack-uikit/fields';
17
17
  import { InputPrivate } from '@snack-uikit/input-private';
18
18
  import { kindFlattenItems, List } from '@snack-uikit/list';
19
19
  import { extractSupportProps, isBrowser, useLayoutEffect, useValueControl } from '@snack-uikit/utils';
@@ -29,7 +29,7 @@ const defaultSelectedOptionFormatter = item =>
29
29
  // @ts-expect-error
30
30
  (item === null || item === void 0 ? void 0 : item.content.option) || '';
31
31
  export const MobileFieldSelectSingle = forwardRef((_a, ref) => {
32
- var { id, name, placeholder, size = 's', options, virtualized, value: valueProp, defaultValue, onChange: onChangeProp, disabled = false, readonly = false, searchable: searchableProp = true, showCopyButton = true, showClearButton = true, onKeyDown: onInputKeyDownProp, required = false, validationState = 'default', search, enableFuzzySearch = true, autocomplete = false, autoFocus, prefixIcon, prefix, postfix, addOptionByEnter = false, open: openProp, onOpenChange, selectedOptionFormatter = defaultSelectedOptionFormatter } = _a, rest = __rest(_a, ["id", "name", "placeholder", "size", "options", "virtualized", "value", "defaultValue", "onChange", "disabled", "readonly", "searchable", "showCopyButton", "showClearButton", "onKeyDown", "required", "validationState", "search", "enableFuzzySearch", "autocomplete", "autoFocus", "prefixIcon", "prefix", "postfix", "addOptionByEnter", "open", "onOpenChange", "selectedOptionFormatter"]);
32
+ var { id, name, placeholder, size = 's', options, virtualized, value: valueProp, defaultValue, onChange: onChangeProp, disabled = false, readonly = false, searchable: searchableProp = true, showCopyButton = true, showClearButton = true, onKeyDown: onInputKeyDownProp, required = false, validationState = 'default', search, enableFuzzySearch = true, autocomplete = false, autoFocus, prefixIcon, prefix, postfix, addOptionByEnter = false, addCustomOptionTriggers, open: openProp, onOpenChange, selectedOptionFormatter = defaultSelectedOptionFormatter } = _a, rest = __rest(_a, ["id", "name", "placeholder", "size", "options", "virtualized", "value", "defaultValue", "onChange", "disabled", "readonly", "searchable", "showCopyButton", "showClearButton", "onKeyDown", "required", "validationState", "search", "enableFuzzySearch", "autocomplete", "autoFocus", "prefixIcon", "prefix", "postfix", "addOptionByEnter", "addCustomOptionTriggers", "open", "onOpenChange", "selectedOptionFormatter"]);
33
33
  const localRef = useRef(null);
34
34
  const [open = false, setOpen] = useValueControl({ value: openProp, onChange: onOpenChange });
35
35
  const [value, setValue] = useValueControl({
@@ -77,6 +77,20 @@ export const MobileFieldSelectSingle = forwardRef((_a, ref) => {
77
77
  onClear,
78
78
  valueToCopy: selectedOptionFormatter(selectedItem),
79
79
  });
80
+ const handleSelectionChange = useCallback((newValue) => {
81
+ var _a;
82
+ (_a = localRef.current) === null || _a === void 0 ? void 0 : _a.focus();
83
+ setOpen(false);
84
+ if (newValue !== undefined) {
85
+ setValue(newValue);
86
+ }
87
+ }, [setOpen, setValue]);
88
+ const { resolvedAddCustomOptionTriggers, tryCommitCustomOptionFromInput } = useFieldSelectSingleCustomOption({
89
+ addCustomOptionTriggers,
90
+ addOptionByEnter,
91
+ inputValue,
92
+ handleSelectionChange,
93
+ });
80
94
  const handleBlur = (e) => {
81
95
  var _a;
82
96
  if (!open && !buttonsRefs.filter(Boolean).includes(e.relatedTarget)) {
@@ -84,19 +98,15 @@ export const MobileFieldSelectSingle = forwardRef((_a, ref) => {
84
98
  (_a = rest === null || rest === void 0 ? void 0 : rest.onBlur) === null || _a === void 0 ? void 0 : _a.call(rest, e);
85
99
  }
86
100
  };
101
+ const handleSearchBlur = () => {
102
+ tryCommitCustomOptionFromInput('blur');
103
+ setInputValue('');
104
+ };
87
105
  const commonHandleOnKeyDown = useHandleOnKeyDown({
88
106
  inputKeyDownNavigationHandler,
89
107
  onInputKeyDownProp,
90
108
  setOpen,
91
109
  });
92
- const handleSelectionChange = useCallback((newValue) => {
93
- var _a;
94
- (_a = localRef.current) === null || _a === void 0 ? void 0 : _a.focus();
95
- setOpen(false);
96
- if (newValue !== undefined) {
97
- setValue(newValue);
98
- }
99
- }, [setOpen, setValue]);
100
110
  const handleOnKeyDown = (onKeyDown) => (e) => {
101
111
  if (!open && prevInputValue.current !== inputValue) {
102
112
  setOpen(true);
@@ -104,16 +114,14 @@ export const MobileFieldSelectSingle = forwardRef((_a, ref) => {
104
114
  if (e.code === 'Enter') {
105
115
  e.stopPropagation();
106
116
  e.preventDefault();
107
- }
108
- if (addOptionByEnter && e.code === 'Enter' && inputValue !== '') {
109
- handleSelectionChange(inputValue);
117
+ tryCommitCustomOptionFromInput('enter');
110
118
  }
111
119
  commonHandleOnKeyDown(onKeyDown)(e);
112
120
  };
113
121
  const handleOpenChange = (open) => {
114
122
  if (!readonly && !disabled && isBrowser() && !buttonsRefs.includes(document.activeElement)) {
115
123
  setOpen(open);
116
- if (!open) {
124
+ if (!open && !resolvedAddCustomOptionTriggers.includes('blur')) {
117
125
  setInputValue('');
118
126
  }
119
127
  }
@@ -134,6 +142,7 @@ export const MobileFieldSelectSingle = forwardRef((_a, ref) => {
134
142
  value: inputValue,
135
143
  onChange: setInputValue,
136
144
  onKeyDown: handleOnKeyDown(),
145
+ onBlur: handleSearchBlur,
137
146
  }
138
147
  : undefined, selection: {
139
148
  mode: 'single',
@@ -1,5 +1,5 @@
1
1
  import { ReactElement, ReactNode } from 'react';
2
- import { FieldDecoratorProps } from '@snack-uikit/fields';
2
+ import { FieldDecoratorProps, FieldSelectMultipleProps, FieldSelectProps, FieldSelectSingleProps } from '@snack-uikit/fields';
3
3
  import { InputPrivateProps } from '@snack-uikit/input-private';
4
4
  import { AccordionItemProps, BaseItemProps, DroplistProps, GroupItemProps, ItemContentProps, NextListItemProps, SelectionMultipleState, SelectionSingleState } from '@snack-uikit/list';
5
5
  import { TagProps } from '@snack-uikit/tag';
@@ -71,15 +71,14 @@ type FiledSelectCommonProps = WithSupportProps<{
71
71
  */
72
72
  enableFuzzySearch?: boolean;
73
73
  autocomplete?: boolean;
74
- addOptionByEnter?: boolean;
75
74
  open?: boolean;
76
75
  onOpenChange?(open: boolean): void;
77
76
  selectedOptionFormatter?: SelectedOptionFormatter;
78
- }> & Pick<DroplistProps, 'dataError' | 'noDataState' | 'noResultsState' | 'errorDataState' | 'dataFiltered'>;
79
- export type MobileFieldSelectSingleProps = FieldSelectPrivateProps & Omit<SelectionSingleState, 'mode'> & WrapperProps & FiledSelectCommonProps;
77
+ }> & Pick<FieldSelectProps, 'addOptionByEnter'> & Pick<DroplistProps, 'dataError' | 'noDataState' | 'noResultsState' | 'errorDataState' | 'dataFiltered'>;
78
+ export type MobileFieldSelectSingleProps = FieldSelectPrivateProps & Omit<SelectionSingleState, 'mode'> & WrapperProps & FiledSelectCommonProps & Pick<FieldSelectSingleProps, 'addCustomOptionTriggers'>;
80
79
  export type MobileFieldSelectMultipleProps = FieldSelectPrivateProps & {
81
80
  removeByBackspace?: boolean;
82
- } & Omit<SelectionMultipleState, 'mode'> & Omit<FiledSelectCommonProps, 'showCopyButton'>;
81
+ } & Omit<SelectionMultipleState, 'mode'> & Omit<FiledSelectCommonProps, 'showCopyButton'> & Pick<FieldSelectMultipleProps, 'addCustomOptionTriggers'>;
83
82
  export type MobileFieldSelectProps = (MobileFieldSelectSingleProps & {
84
83
  selection?: 'single';
85
84
  }) | (MobileFieldSelectMultipleProps & {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cloud-ru/uikit-product-mobile-fields",
3
3
  "title": "Mobile Fields",
4
- "version": "2.0.15",
4
+ "version": "2.0.16-preview-b1a595e8.0",
5
5
  "sideEffects": [
6
6
  "*.css",
7
7
  "*.woff",
@@ -42,9 +42,9 @@
42
42
  "@cloud-ru/uikit-product-utils": "9.1.0",
43
43
  "@snack-uikit/button": "0.19.15",
44
44
  "@snack-uikit/calendar": "0.13.18",
45
- "@snack-uikit/fields": "0.53.0",
45
+ "@snack-uikit/fields": "0.54.1-preview-1078c3b3.0",
46
46
  "@snack-uikit/input-private": "4.8.8",
47
- "@snack-uikit/list": "0.32.17",
47
+ "@snack-uikit/list": "0.32.18-preview-1078c3b3.0",
48
48
  "@snack-uikit/scroll": "0.10.8",
49
49
  "@snack-uikit/tag": "0.15.18",
50
50
  "@snack-uikit/utils": "3.10.1",
@@ -62,5 +62,5 @@
62
62
  "peerDependencies": {
63
63
  "@cloud-ru/uikit-product-locale": "*"
64
64
  },
65
- "gitHead": "7ab3fb759acc8f2f742b85ac3fefdc02540c58f6"
65
+ "gitHead": "3a64809a3d108ed6eaf9059976fcdcc7eaeb7c7f"
66
66
  }
@@ -16,7 +16,12 @@ import {
16
16
  import { useLocale } from '@cloud-ru/uikit-product-locale';
17
17
  import { MobileModalCustom } from '@cloud-ru/uikit-product-mobile-modal';
18
18
  import { ButtonFilled, ButtonFunction } from '@snack-uikit/button';
19
- import { FieldDecorator } from '@snack-uikit/fields';
19
+ import {
20
+ FieldDecorator,
21
+ getCustomOptionTriggerByCode,
22
+ shouldHandleCustomOptionTrigger,
23
+ useFieldSelectMultipleCustomOption,
24
+ } from '@snack-uikit/fields';
20
25
  import { InputPrivate } from '@snack-uikit/input-private';
21
26
  import { ItemId, kindFlattenItems, List, ListProps, SelectionSingleValueType } from '@snack-uikit/list';
22
27
  import { Tag } from '@snack-uikit/tag';
@@ -65,6 +70,7 @@ export const MobileFieldSelectMultiple: ForwardRefExoticComponent<
65
70
  postfix,
66
71
  removeByBackspace = false,
67
72
  addOptionByEnter = false,
73
+ addCustomOptionTriggers,
68
74
  open: openProp,
69
75
  onOpenChange,
70
76
  selectedOptionFormatter = defaultSelectedOptionFormatter,
@@ -101,6 +107,15 @@ export const MobileFieldSelectMultiple: ForwardRefExoticComponent<
101
107
  selectedOptionFormatter,
102
108
  });
103
109
 
110
+ const { resolvedAddCustomOptionTriggers, tryCommitCustomOptionFromInput } = useFieldSelectMultipleCustomOption({
111
+ addCustomOptionTriggers,
112
+ addOptionByEnter,
113
+ inputValue,
114
+ value,
115
+ setValue,
116
+ updateInputValue,
117
+ });
118
+
104
119
  const prefixSettings = usePrefix({ prefix, disabled });
105
120
  const postfixSettings = usePostfix({ postfix, disabled });
106
121
 
@@ -136,6 +151,7 @@ export const MobileFieldSelectMultiple: ForwardRefExoticComponent<
136
151
  });
137
152
 
138
153
  const handleItemDelete = useHandleDeleteItem(setValue);
154
+
139
155
  const handleOnKeyDown = (onKeyDown?: KeyboardEventHandler<HTMLElement>) => (e: KeyboardEvent<HTMLInputElement>) => {
140
156
  if (removeByBackspace && e.code === 'Backspace' && inputValue === '') {
141
157
  if (selectedItems?.length && !selectedItems.slice(-1)[0].disabled) {
@@ -146,12 +162,14 @@ export const MobileFieldSelectMultiple: ForwardRefExoticComponent<
146
162
  if (e.code === 'Enter') {
147
163
  e.stopPropagation();
148
164
  e.preventDefault();
149
- }
150
-
151
- if (addOptionByEnter && e.code === 'Enter' && inputValue !== '') {
152
- if (!(value ?? []).includes(inputValue)) {
153
- setValue((value: SelectionSingleValueType[]) => (value ?? []).concat(inputValue));
154
- updateInputValue();
165
+ tryCommitCustomOptionFromInput('enter');
166
+ } else {
167
+ const customOptionTrigger = getCustomOptionTriggerByCode(e.code);
168
+
169
+ if (shouldHandleCustomOptionTrigger(customOptionTrigger, resolvedAddCustomOptionTriggers)) {
170
+ e.stopPropagation();
171
+ e.preventDefault();
172
+ tryCommitCustomOptionFromInput(customOptionTrigger);
155
173
  }
156
174
  }
157
175
 
@@ -186,6 +204,10 @@ export const MobileFieldSelectMultiple: ForwardRefExoticComponent<
186
204
  }
187
205
  };
188
206
 
207
+ const handleSearchBlur = () => {
208
+ tryCommitCustomOptionFromInput('blur');
209
+ };
210
+
189
211
  const searcher = useSearch(items, enableFuzzySearch);
190
212
  const result = autocomplete || !searchable || prevInputValue.current === inputValue ? items : searcher(inputValue);
191
213
 
@@ -205,6 +227,7 @@ export const MobileFieldSelectMultiple: ForwardRefExoticComponent<
205
227
  value: inputValue,
206
228
  onChange: setInputValue,
207
229
  onKeyDown: handleOnKeyDown(),
230
+ onBlur: handleSearchBlur,
208
231
  }
209
232
  : undefined
210
233
  }
@@ -15,7 +15,7 @@ import {
15
15
  } from 'react';
16
16
 
17
17
  import { MobileModalCustom } from '@cloud-ru/uikit-product-mobile-modal';
18
- import { FieldDecorator } from '@snack-uikit/fields';
18
+ import { FieldDecorator, useFieldSelectSingleCustomOption } from '@snack-uikit/fields';
19
19
  import { InputPrivate } from '@snack-uikit/input-private';
20
20
  import { kindFlattenItems, List, ListProps, SelectionSingleValueType } from '@snack-uikit/list';
21
21
  import { extractSupportProps, isBrowser, useLayoutEffect, useValueControl } from '@snack-uikit/utils';
@@ -64,6 +64,7 @@ export const MobileFieldSelectSingle: ForwardRefExoticComponent<
64
64
  prefix,
65
65
  postfix,
66
66
  addOptionByEnter = false,
67
+ addCustomOptionTriggers,
67
68
  open: openProp,
68
69
  onOpenChange,
69
70
  selectedOptionFormatter = defaultSelectedOptionFormatter,
@@ -141,6 +142,24 @@ export const MobileFieldSelectSingle: ForwardRefExoticComponent<
141
142
  valueToCopy: selectedOptionFormatter(selectedItem),
142
143
  });
143
144
 
145
+ const handleSelectionChange = useCallback(
146
+ (newValue?: SelectionSingleValueType) => {
147
+ localRef.current?.focus();
148
+ setOpen(false);
149
+ if (newValue !== undefined) {
150
+ setValue(newValue);
151
+ }
152
+ },
153
+ [setOpen, setValue],
154
+ );
155
+
156
+ const { resolvedAddCustomOptionTriggers, tryCommitCustomOptionFromInput } = useFieldSelectSingleCustomOption({
157
+ addCustomOptionTriggers,
158
+ addOptionByEnter,
159
+ inputValue,
160
+ handleSelectionChange,
161
+ });
162
+
144
163
  const handleBlur = (e: FocusEvent<HTMLInputElement>) => {
145
164
  if (!open && !buttonsRefs.filter(Boolean).includes(e.relatedTarget)) {
146
165
  setInputValue('');
@@ -149,23 +168,17 @@ export const MobileFieldSelectSingle: ForwardRefExoticComponent<
149
168
  }
150
169
  };
151
170
 
171
+ const handleSearchBlur = () => {
172
+ tryCommitCustomOptionFromInput('blur');
173
+ setInputValue('');
174
+ };
175
+
152
176
  const commonHandleOnKeyDown = useHandleOnKeyDown({
153
177
  inputKeyDownNavigationHandler,
154
178
  onInputKeyDownProp,
155
179
  setOpen,
156
180
  });
157
181
 
158
- const handleSelectionChange = useCallback(
159
- (newValue?: SelectionSingleValueType) => {
160
- localRef.current?.focus();
161
- setOpen(false);
162
- if (newValue !== undefined) {
163
- setValue(newValue);
164
- }
165
- },
166
- [setOpen, setValue],
167
- );
168
-
169
182
  const handleOnKeyDown = (onKeyDown?: KeyboardEventHandler<HTMLElement>) => (e: KeyboardEvent<HTMLInputElement>) => {
170
183
  if (!open && prevInputValue.current !== inputValue) {
171
184
  setOpen(true);
@@ -174,10 +187,7 @@ export const MobileFieldSelectSingle: ForwardRefExoticComponent<
174
187
  if (e.code === 'Enter') {
175
188
  e.stopPropagation();
176
189
  e.preventDefault();
177
- }
178
-
179
- if (addOptionByEnter && e.code === 'Enter' && inputValue !== '') {
180
- handleSelectionChange(inputValue);
190
+ tryCommitCustomOptionFromInput('enter');
181
191
  }
182
192
 
183
193
  commonHandleOnKeyDown(onKeyDown)(e);
@@ -187,7 +197,7 @@ export const MobileFieldSelectSingle: ForwardRefExoticComponent<
187
197
  if (!readonly && !disabled && isBrowser() && !buttonsRefs.includes(document.activeElement)) {
188
198
  setOpen(open);
189
199
 
190
- if (!open) {
200
+ if (!open && !resolvedAddCustomOptionTriggers.includes('blur')) {
191
201
  setInputValue('');
192
202
  }
193
203
  }
@@ -222,6 +232,7 @@ export const MobileFieldSelectSingle: ForwardRefExoticComponent<
222
232
  value: inputValue,
223
233
  onChange: setInputValue,
224
234
  onKeyDown: handleOnKeyDown(),
235
+ onBlur: handleSearchBlur,
225
236
  }
226
237
  : undefined
227
238
  }
@@ -1,6 +1,11 @@
1
1
  import { ReactElement, ReactNode } from 'react';
2
2
 
3
- import { FieldDecoratorProps } from '@snack-uikit/fields';
3
+ import {
4
+ FieldDecoratorProps,
5
+ FieldSelectMultipleProps,
6
+ FieldSelectProps,
7
+ FieldSelectSingleProps,
8
+ } from '@snack-uikit/fields';
4
9
  import { InputPrivateProps } from '@snack-uikit/input-private';
5
10
  import {
6
11
  AccordionItemProps,
@@ -122,25 +127,26 @@ type FiledSelectCommonProps = WithSupportProps<{
122
127
 
123
128
  autocomplete?: boolean;
124
129
 
125
- addOptionByEnter?: boolean;
126
-
127
130
  open?: boolean;
128
131
 
129
132
  onOpenChange?(open: boolean): void;
130
133
 
131
134
  selectedOptionFormatter?: SelectedOptionFormatter;
132
135
  }> &
136
+ Pick<FieldSelectProps, 'addOptionByEnter'> &
133
137
  Pick<DroplistProps, 'dataError' | 'noDataState' | 'noResultsState' | 'errorDataState' | 'dataFiltered'>;
134
138
 
135
139
  export type MobileFieldSelectSingleProps = FieldSelectPrivateProps &
136
140
  Omit<SelectionSingleState, 'mode'> &
137
141
  WrapperProps &
138
- FiledSelectCommonProps;
142
+ FiledSelectCommonProps &
143
+ Pick<FieldSelectSingleProps, 'addCustomOptionTriggers'>;
139
144
 
140
145
  export type MobileFieldSelectMultipleProps = FieldSelectPrivateProps & {
141
146
  removeByBackspace?: boolean;
142
147
  } & Omit<SelectionMultipleState, 'mode'> &
143
- Omit<FiledSelectCommonProps, 'showCopyButton'>;
148
+ Omit<FiledSelectCommonProps, 'showCopyButton'> &
149
+ Pick<FieldSelectMultipleProps, 'addCustomOptionTriggers'>;
144
150
 
145
151
  export type MobileFieldSelectProps =
146
152
  | (MobileFieldSelectSingleProps & { selection?: 'single' })