@snack-uikit/fields 0.53.2-preview-0a70077b.0 → 0.54.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +28 -130
  3. package/dist/cjs/components/FieldSecure/FieldSecure.d.ts +1 -1
  4. package/dist/cjs/components/FieldSelect/FieldSelectMultiple.d.ts +3 -1
  5. package/dist/cjs/components/FieldSelect/FieldSelectMultiple.js +28 -7
  6. package/dist/cjs/components/FieldSelect/FieldSelectSingle.d.ts +3 -1
  7. package/dist/cjs/components/FieldSelect/FieldSelectSingle.js +25 -12
  8. package/dist/cjs/components/FieldSelect/{hooks.d.ts → hooks/common.d.ts} +1 -1
  9. package/dist/cjs/components/FieldSelect/{hooks.js → hooks/common.js} +4 -5
  10. package/dist/cjs/components/FieldSelect/hooks/customOption.d.ts +26 -0
  11. package/dist/cjs/components/FieldSelect/hooks/customOption.js +69 -0
  12. package/dist/cjs/components/FieldSelect/hooks/index.d.ts +2 -0
  13. package/dist/cjs/components/FieldSelect/hooks/index.js +26 -0
  14. package/dist/cjs/components/FieldSelect/index.d.ts +1 -1
  15. package/dist/cjs/components/FieldSelect/types.d.ts +20 -3
  16. package/dist/cjs/components/FieldSelect/utils/customOption.d.ts +4 -0
  17. package/dist/cjs/components/FieldSelect/utils/customOption.js +21 -0
  18. package/dist/cjs/components/FieldSelect/utils/index.d.ts +2 -0
  19. package/dist/cjs/components/FieldSelect/utils/index.js +3 -1
  20. package/dist/cjs/components/FieldText/FieldText.d.ts +1 -1
  21. package/dist/cjs/components/FieldTextArea/FieldTextArea.d.ts +1 -1
  22. package/dist/cjs/components/index.d.ts +0 -1
  23. package/dist/cjs/components/index.js +0 -1
  24. package/dist/cjs/helperComponents/ButtonFieldList/ButtonFieldList.d.ts +1 -1
  25. package/dist/esm/components/FieldSecure/FieldSecure.d.ts +1 -1
  26. package/dist/esm/components/FieldSelect/FieldSelectMultiple.d.ts +3 -1
  27. package/dist/esm/components/FieldSelect/FieldSelectMultiple.js +27 -9
  28. package/dist/esm/components/FieldSelect/FieldSelectSingle.d.ts +3 -1
  29. package/dist/esm/components/FieldSelect/FieldSelectSingle.js +24 -14
  30. package/dist/esm/components/FieldSelect/{hooks.d.ts → hooks/common.d.ts} +1 -1
  31. package/dist/esm/components/FieldSelect/{hooks.js → hooks/common.js} +3 -4
  32. package/dist/esm/components/FieldSelect/hooks/customOption.d.ts +26 -0
  33. package/dist/esm/components/FieldSelect/hooks/customOption.js +38 -0
  34. package/dist/esm/components/FieldSelect/hooks/index.d.ts +2 -0
  35. package/dist/esm/components/FieldSelect/hooks/index.js +2 -0
  36. package/dist/esm/components/FieldSelect/index.d.ts +1 -1
  37. package/dist/esm/components/FieldSelect/types.d.ts +20 -3
  38. package/dist/esm/components/FieldSelect/utils/customOption.d.ts +4 -0
  39. package/dist/esm/components/FieldSelect/utils/customOption.js +13 -0
  40. package/dist/esm/components/FieldSelect/utils/index.d.ts +2 -0
  41. package/dist/esm/components/FieldSelect/utils/index.js +2 -0
  42. package/dist/esm/components/FieldText/FieldText.d.ts +1 -1
  43. package/dist/esm/components/FieldTextArea/FieldTextArea.d.ts +1 -1
  44. package/dist/esm/components/index.d.ts +0 -1
  45. package/dist/esm/components/index.js +0 -1
  46. package/dist/esm/helperComponents/ButtonFieldList/ButtonFieldList.d.ts +1 -1
  47. package/package.json +2 -2
  48. package/src/components/FieldSelect/FieldSelectMultiple.tsx +55 -10
  49. package/src/components/FieldSelect/FieldSelectSingle.tsx +33 -15
  50. package/src/components/FieldSelect/{hooks.ts → hooks/common.ts} +4 -5
  51. package/src/components/FieldSelect/hooks/customOption.ts +90 -0
  52. package/src/components/FieldSelect/hooks/index.ts +2 -0
  53. package/src/components/FieldSelect/index.ts +2 -0
  54. package/src/components/FieldSelect/types.ts +24 -3
  55. package/src/components/FieldSelect/utils/customOption.ts +23 -0
  56. package/src/components/FieldSelect/utils/index.ts +2 -0
  57. package/src/components/index.ts +0 -1
  58. package/dist/cjs/components/FieldAdd/FieldAdd.d.ts +0 -52
  59. package/dist/cjs/components/FieldAdd/FieldAdd.js +0 -216
  60. package/dist/cjs/components/FieldAdd/index.d.ts +0 -2
  61. package/dist/cjs/components/FieldAdd/index.js +0 -13
  62. package/dist/cjs/components/FieldAdd/styles.module.css +0 -51
  63. package/dist/esm/components/FieldAdd/FieldAdd.d.ts +0 -52
  64. package/dist/esm/components/FieldAdd/FieldAdd.js +0 -31
  65. package/dist/esm/components/FieldAdd/index.d.ts +0 -2
  66. package/dist/esm/components/FieldAdd/index.js +0 -1
  67. package/dist/esm/components/FieldAdd/styles.module.css +0 -51
  68. package/src/components/FieldAdd/FieldAdd.tsx +0 -215
  69. package/src/components/FieldAdd/index.ts +0 -3
  70. package/src/components/FieldAdd/styles.module.scss +0 -60
@@ -0,0 +1,13 @@
1
+ export const getCustomOptionTriggerByCode = (code) => {
2
+ switch (code) {
3
+ case 'Enter':
4
+ return 'enter';
5
+ case 'Space':
6
+ return 'space';
7
+ case 'Comma':
8
+ return 'comma';
9
+ default:
10
+ return undefined;
11
+ }
12
+ };
13
+ export const shouldHandleCustomOptionTrigger = (trigger, availableTriggers) => (trigger ? availableTriggers.includes(trigger) : false);
@@ -5,3 +5,5 @@ export * from './updateItems';
5
5
  export * from './getArrowIcon';
6
6
  export * from './getValueByPath';
7
7
  export * from './checkisSearchUnavailable';
8
+ export * from './customOption';
9
+ export * from './filterItemsByFlattenIds';
@@ -5,3 +5,5 @@ export * from './updateItems';
5
5
  export * from './getArrowIcon';
6
6
  export * from './getValueByPath';
7
7
  export * from './checkisSearchUnavailable';
8
+ export * from './customOption';
9
+ export * from './filterItemsByFlattenIds';
@@ -32,5 +32,5 @@ type FieldTextOwnProps = {
32
32
  export type FieldTextProps = WithSupportProps<FieldTextOwnProps & InputProps & WrapperProps>;
33
33
  export declare const FieldText: import("react").ForwardRefExoticComponent<{
34
34
  'data-test-id'?: string;
35
- } & import("react").AriaAttributes & FieldTextOwnProps & Pick<Partial<InputPrivateProps>, "onChange" | "value"> & Pick<Required<InputPrivateProps>, "inputMode"> & Pick<InputPrivateProps, "id" | "name" | "onPaste" | "onFocus" | "onBlur" | "onKeyDown" | "disabled" | "placeholder" | "readonly" | "autoFocus" | "autoComplete" | "maxLength" | "spellCheck" | "pattern"> & WrapperProps & import("react").RefAttributes<HTMLInputElement>>;
35
+ } & import("react").AriaAttributes & FieldTextOwnProps & Pick<Partial<InputPrivateProps>, "onChange" | "value"> & Pick<Required<InputPrivateProps>, "inputMode"> & Pick<InputPrivateProps, "id" | "name" | "onPaste" | "onFocus" | "onBlur" | "onKeyDown" | "disabled" | "readonly" | "placeholder" | "autoFocus" | "autoComplete" | "maxLength" | "spellCheck" | "pattern"> & WrapperProps & import("react").RefAttributes<HTMLInputElement>>;
36
36
  export {};
@@ -30,5 +30,5 @@ type FieldTextAreaOwnProps = {
30
30
  export type FieldTextAreaProps = WithSupportProps<FieldTextAreaOwnProps & InputProps & WrapperProps>;
31
31
  export declare const FieldTextArea: import("react").ForwardRefExoticComponent<{
32
32
  'data-test-id'?: string;
33
- } & import("react").AriaAttributes & FieldTextAreaOwnProps & Pick<Partial<TextAreaProps>, "value"> & Pick<TextAreaProps, "id" | "name" | "onFocus" | "onBlur" | "onKeyDown" | "disabled" | "placeholder" | "readonly" | "autoFocus" | "inputMode" | "maxLength" | "spellCheck"> & WrapperProps & import("react").RefAttributes<HTMLTextAreaElement>>;
33
+ } & import("react").AriaAttributes & FieldTextAreaOwnProps & Pick<Partial<TextAreaProps>, "value"> & Pick<TextAreaProps, "id" | "name" | "onFocus" | "onBlur" | "onKeyDown" | "disabled" | "readonly" | "placeholder" | "autoFocus" | "inputMode" | "maxLength" | "spellCheck"> & WrapperProps & import("react").RefAttributes<HTMLTextAreaElement>>;
34
34
  export {};
@@ -1,4 +1,3 @@
1
- export * from './FieldAdd';
2
1
  export * from './FieldColor';
3
2
  export * from './FieldDate';
4
3
  export * from './FieldDecorator';
@@ -1,4 +1,3 @@
1
- export * from './FieldAdd';
2
1
  export * from './FieldColor';
3
2
  export * from './FieldDate';
4
3
  export * from './FieldDecorator';
@@ -1,4 +1,4 @@
1
1
  import { ButtonFieldProps } from '../ButtonField';
2
- export declare const ButtonFieldList: import("react").ForwardRefExoticComponent<Omit<ButtonFieldProps, "hasArrow" | "arrowOpen"> & Pick<import("@snack-uikit/list").DroplistProps, "search" | "items" | "scroll" | "open" | "onOpenChange"> & {
2
+ export declare const ButtonFieldList: import("react").ForwardRefExoticComponent<Omit<ButtonFieldProps, "hasArrow" | "arrowOpen"> & Pick<import("@snack-uikit/list").DroplistProps, "search" | "open" | "onOpenChange" | "items" | "scroll"> & {
3
3
  selection?: Omit<import("@snack-uikit/list").SelectionSingleState, "mode">;
4
4
  } & import("react").RefAttributes<HTMLButtonElement>>;
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "access": "public"
5
5
  },
6
6
  "title": "Fields",
7
- "version": "0.53.2-preview-0a70077b.0",
7
+ "version": "0.54.0",
8
8
  "sideEffects": [
9
9
  "*.css",
10
10
  "*.woff",
@@ -66,5 +66,5 @@
66
66
  "peerDependencies": {
67
67
  "@snack-uikit/locale": "*"
68
68
  },
69
- "gitHead": "7e1ec2d145e6d393555ff3c97b4e693f98dd5408"
69
+ "gitHead": "91f67a065fb1a5b9b57c9c03374c752e3acf3a0f"
70
70
  }
@@ -1,6 +1,15 @@
1
1
  import cn from 'classnames';
2
2
  import mergeRefs from 'merge-refs';
3
- import { FocusEvent, forwardRef, KeyboardEvent, KeyboardEventHandler, useEffect, useRef, useState } from 'react';
3
+ import {
4
+ FocusEvent,
5
+ forwardRef,
6
+ KeyboardEvent,
7
+ KeyboardEventHandler,
8
+ useCallback,
9
+ useEffect,
10
+ useRef,
11
+ useState,
12
+ } from 'react';
4
13
 
5
14
  import { InputPrivate } from '@snack-uikit/input-private';
6
15
  import { BaseItemProps, Droplist, ItemProps, SelectionSingleValueType } from '@snack-uikit/list';
@@ -12,10 +21,24 @@ import { usePostfix, usePrefix, useValueControl } from '../../hooks';
12
21
  import { getValidationState } from '../../utils/getValidationState';
13
22
  import { FieldDecorator } from '../FieldDecorator';
14
23
  import { extractFieldDecoratorProps } from '../FieldDecorator/utils';
15
- import { useButtons, useHandleDeleteItem, useHandleOnKeyDown, useSearch, useSearchInput } from './hooks';
24
+ import {
25
+ useButtons,
26
+ useFieldSelectMultipleCustomOption,
27
+ useHandleDeleteItem,
28
+ useHandleOnKeyDown,
29
+ useSearch,
30
+ useSearchInput,
31
+ } from './hooks';
16
32
  import styles from './styles.module.scss';
17
33
  import { FieldSelectMultipleProps, ItemWithId, SelectedOptionFormatter } from './types';
18
- import { checkisSearchUnavailable, extractListProps, getArrowIcon, updateMultipleItems } from './utils';
34
+ import {
35
+ checkisSearchUnavailable,
36
+ extractListProps,
37
+ getArrowIcon,
38
+ getCustomOptionTriggerByCode,
39
+ shouldHandleCustomOptionTrigger,
40
+ updateMultipleItems,
41
+ } from './utils';
19
42
 
20
43
  const BASE_MIN_WIDTH = 4;
21
44
 
@@ -47,6 +70,7 @@ export const FieldSelectMultiple = forwardRef<HTMLInputElement, FieldSelectMulti
47
70
  postfix,
48
71
  removeByBackspace = false,
49
72
  addOptionByEnter = false,
73
+ addCustomOptionTriggers,
50
74
  untouchableScrollbars = false,
51
75
  open: openProp,
52
76
  enableFuzzySearch = true,
@@ -80,6 +104,15 @@ export const FieldSelectMultiple = forwardRef<HTMLInputElement, FieldSelectMulti
80
104
  resetSearchOnOptionSelection,
81
105
  });
82
106
 
107
+ const { resolvedAddCustomOptionTriggers, tryCommitCustomOptionFromInput } = useFieldSelectMultipleCustomOption({
108
+ addCustomOptionTriggers,
109
+ addOptionByEnter,
110
+ inputValue,
111
+ value,
112
+ setValue,
113
+ updateInputValue,
114
+ });
115
+
83
116
  const prefixSettings = usePrefix({ prefix, disabled });
84
117
  const postfixSettings = usePostfix({ postfix, disabled });
85
118
 
@@ -114,6 +147,15 @@ export const FieldSelectMultiple = forwardRef<HTMLInputElement, FieldSelectMulti
114
147
  });
115
148
 
116
149
  const handleItemDelete = useHandleDeleteItem(setValue);
150
+ const handleTagDelete = useCallback(
151
+ (option: BaseItemProps) => () => {
152
+ const deleteItemHandler = handleItemDelete(option);
153
+ deleteItemHandler();
154
+ localRef.current?.focus();
155
+ },
156
+ [handleItemDelete],
157
+ );
158
+
117
159
  const handleOnKeyDown = (onKeyDown?: KeyboardEventHandler<HTMLElement>) => (e: KeyboardEvent<HTMLInputElement>) => {
118
160
  if (removeByBackspace && e.code === 'Backspace' && inputValue === '') {
119
161
  if (selectedItems?.length && !selectedItems.slice(-1)[0].disabled) {
@@ -124,12 +166,14 @@ export const FieldSelectMultiple = forwardRef<HTMLInputElement, FieldSelectMulti
124
166
  if (e.code === 'Enter') {
125
167
  e.stopPropagation();
126
168
  e.preventDefault();
127
- }
128
-
129
- if (addOptionByEnter && e.code === 'Enter' && inputValue !== '') {
130
- if (!(value ?? []).includes(inputValue)) {
131
- setValue((value: SelectionSingleValueType[]) => (value ?? []).concat(inputValue));
132
- updateInputValue();
169
+ tryCommitCustomOptionFromInput('enter');
170
+ } else {
171
+ const customOptionTrigger = getCustomOptionTriggerByCode(e.code);
172
+
173
+ if (shouldHandleCustomOptionTrigger(customOptionTrigger, resolvedAddCustomOptionTriggers)) {
174
+ e.stopPropagation();
175
+ e.preventDefault();
176
+ tryCommitCustomOptionFromInput(customOptionTrigger);
133
177
  }
134
178
  }
135
179
 
@@ -160,6 +204,7 @@ export const FieldSelectMultiple = forwardRef<HTMLInputElement, FieldSelectMulti
160
204
 
161
205
  const handleBlur = (e: FocusEvent<HTMLInputElement>) => {
162
206
  if (!open && !buttonsRefs.filter(Boolean).includes(e.relatedTarget)) {
207
+ tryCommitCustomOptionFromInput('blur');
163
208
  updateInputValue();
164
209
 
165
210
  rest?.onBlur?.(e);
@@ -251,7 +296,7 @@ export const FieldSelectMultiple = forwardRef<HTMLInputElement, FieldSelectMulti
251
296
  label={selectedOptionFormatter(option)}
252
297
  key={option.id}
253
298
  appearance={option.appearance ?? 'neutral'}
254
- onDelete={!option.disabled && !disabled && !readonly ? handleItemDelete(option) : undefined}
299
+ onDelete={!option.disabled && !disabled && !readonly ? handleTagDelete(option) : undefined}
255
300
  className={styles.tag}
256
301
  data-disabled={disabled || undefined}
257
302
  />
@@ -20,10 +20,16 @@ import { usePostfix, usePrefix, useValueControl } from '../../hooks';
20
20
  import { getValidationState } from '../../utils/getValidationState';
21
21
  import { FieldDecorator } from '../FieldDecorator';
22
22
  import { extractFieldDecoratorProps } from '../FieldDecorator/utils';
23
- import { useButtons, useHandleOnKeyDown, useSearch, useSearchInput } from './hooks';
23
+ import { useButtons, useFieldSelectSingleCustomOption, useHandleOnKeyDown, useSearch, useSearchInput } from './hooks';
24
24
  import styles from './styles.module.scss';
25
25
  import { FieldSelectSingleProps, ItemWithId, SelectedOptionFormatter } from './types';
26
- import { checkisSearchUnavailable, extractListProps, getArrowIcon, updateItems } from './utils';
26
+ import {
27
+ checkisSearchUnavailable,
28
+ extractListProps,
29
+ getArrowIcon,
30
+ shouldHandleCustomOptionTrigger,
31
+ updateItems,
32
+ } from './utils';
27
33
 
28
34
  const defaultSelectedOptionFormatter: SelectedOptionFormatter = item =>
29
35
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
@@ -54,6 +60,7 @@ export const FieldSelectSingle = forwardRef<HTMLInputElement, FieldSelectSingleP
54
60
  prefix,
55
61
  postfix,
56
62
  addOptionByEnter = false,
63
+ addCustomOptionTriggers: addCustomOptionTriggersProp,
57
64
  untouchableScrollbars = false,
58
65
  open: openProp,
59
66
  onOpenChange,
@@ -131,14 +138,6 @@ export const FieldSelectSingle = forwardRef<HTMLInputElement, FieldSelectSingleP
131
138
  valueToCopy: selectedOptionFormatter(selectedItem),
132
139
  });
133
140
 
134
- const handleBlur = (e: FocusEvent<HTMLInputElement>) => {
135
- if (!open && !buttonsRefs.filter(Boolean).includes(e.relatedTarget)) {
136
- updateInputValue(selectedItem);
137
-
138
- rest?.onBlur?.(e);
139
- }
140
- };
141
-
142
141
  const commonHandleOnKeyDown = useHandleOnKeyDown({
143
142
  inputKeyDownNavigationHandler,
144
143
  onInputKeyDownProp,
@@ -157,6 +156,28 @@ export const FieldSelectSingle = forwardRef<HTMLInputElement, FieldSelectSingleP
157
156
  [setOpen, setValue],
158
157
  );
159
158
 
159
+ const { resolvedAddCustomOptionTriggers, tryCommitCustomOptionFromInput } = useFieldSelectSingleCustomOption({
160
+ addCustomOptionTriggers: addCustomOptionTriggersProp,
161
+ addOptionByEnter,
162
+ inputValue,
163
+ handleSelectionChange,
164
+ });
165
+
166
+ const handleBlur = (e: FocusEvent<HTMLInputElement>) => {
167
+ if (!open && !buttonsRefs.filter(Boolean).includes(e.relatedTarget)) {
168
+ const commitOnBlur =
169
+ shouldHandleCustomOptionTrigger('blur', resolvedAddCustomOptionTriggers) && inputValue !== '';
170
+
171
+ if (commitOnBlur) {
172
+ tryCommitCustomOptionFromInput('blur');
173
+ } else {
174
+ updateInputValue(selectedItem);
175
+ }
176
+
177
+ rest?.onBlur?.(e);
178
+ }
179
+ };
180
+
160
181
  const handleOnKeyDown = (onKeyDown?: KeyboardEventHandler<HTMLElement>) => (e: KeyboardEvent<HTMLInputElement>) => {
161
182
  if (!open && prevInputValue.current !== inputValue) {
162
183
  setOpen(true);
@@ -165,10 +186,7 @@ export const FieldSelectSingle = forwardRef<HTMLInputElement, FieldSelectSingleP
165
186
  if (e.code === 'Enter') {
166
187
  e.stopPropagation();
167
188
  e.preventDefault();
168
- }
169
-
170
- if (addOptionByEnter && e.code === 'Enter' && inputValue !== '') {
171
- handleSelectionChange(inputValue);
189
+ tryCommitCustomOptionFromInput('enter');
172
190
  }
173
191
 
174
192
  commonHandleOnKeyDown(onKeyDown)(e);
@@ -178,7 +196,7 @@ export const FieldSelectSingle = forwardRef<HTMLInputElement, FieldSelectSingleP
178
196
  if (isBrowser() && !readonly && !disabled && !buttonsRefs.includes(document.activeElement)) {
179
197
  setOpen(open);
180
198
 
181
- if (!open) {
199
+ if (!open && !shouldHandleCustomOptionTrigger('blur', resolvedAddCustomOptionTriggers)) {
182
200
  updateInputValue(selectedItem);
183
201
  }
184
202
  }
@@ -11,11 +11,10 @@ import {
11
11
  SelectionSingleValueType,
12
12
  } from '@snack-uikit/list';
13
13
 
14
- import { useCopyButton, useValueControl } from '../../hooks';
15
- import { extractChildIds } from './legacy';
16
- import { ItemWithId, SearchState, SelectedOptionFormatter } from './types';
17
- import { getValueByPath, isBaseOptionProps } from './utils';
18
- import { filterItemsByFlattenIds } from './utils/filterItemsByFlattenIds';
14
+ import { useCopyButton, useValueControl } from '../../../hooks';
15
+ import { extractChildIds } from '../legacy';
16
+ import { ItemWithId, SearchState, SelectedOptionFormatter } from '../types';
17
+ import { filterItemsByFlattenIds, getValueByPath, isBaseOptionProps } from '../utils';
19
18
 
20
19
  type UseHandleOnKeyDownProps = {
21
20
  inputKeyDownNavigationHandler: KeyboardEventHandler<HTMLInputElement>;
@@ -0,0 +1,90 @@
1
+ import { useCallback, useMemo } from 'react';
2
+ import { Handler } from 'uncontrollable';
3
+
4
+ import { SelectionSingleValueType } from '@snack-uikit/list';
5
+
6
+ import { FieldSelectMultipleAddCustomOptionTrigger } from '../types';
7
+
8
+ type UseResolvedAddCustomOptionTriggersParams = {
9
+ addCustomOptionTriggers?: FieldSelectMultipleAddCustomOptionTrigger[];
10
+ addOptionByEnter: boolean;
11
+ };
12
+
13
+ function useResolvedAddCustomOptionTriggers({
14
+ addCustomOptionTriggers,
15
+ addOptionByEnter,
16
+ }: UseResolvedAddCustomOptionTriggersParams): FieldSelectMultipleAddCustomOptionTrigger[] {
17
+ return useMemo(() => {
18
+ if (addCustomOptionTriggers !== undefined) {
19
+ return addCustomOptionTriggers;
20
+ }
21
+
22
+ return addOptionByEnter ? ['enter'] : [];
23
+ }, [addCustomOptionTriggers, addOptionByEnter]);
24
+ }
25
+
26
+ type UseFieldSelectMultipleCustomOptionParams = UseResolvedAddCustomOptionTriggersParams & {
27
+ inputValue: string;
28
+ value: SelectionSingleValueType[] | undefined;
29
+ setValue: Handler;
30
+ updateInputValue: () => void;
31
+ };
32
+ export function useFieldSelectMultipleCustomOption({
33
+ addCustomOptionTriggers,
34
+ addOptionByEnter,
35
+ inputValue,
36
+ value,
37
+ setValue,
38
+ updateInputValue,
39
+ }: UseFieldSelectMultipleCustomOptionParams) {
40
+ const resolvedAddCustomOptionTriggers = useResolvedAddCustomOptionTriggers({
41
+ addCustomOptionTriggers,
42
+ addOptionByEnter,
43
+ });
44
+
45
+ const tryCommitCustomOptionFromInput = useCallback(
46
+ (trigger: FieldSelectMultipleAddCustomOptionTrigger) => {
47
+ if (!resolvedAddCustomOptionTriggers.includes(trigger) || inputValue === '') {
48
+ return;
49
+ }
50
+
51
+ if (!(value ?? []).includes(inputValue)) {
52
+ setValue((prev: SelectionSingleValueType[]) => (prev ?? []).concat(inputValue));
53
+ updateInputValue();
54
+ }
55
+ },
56
+ [resolvedAddCustomOptionTriggers, inputValue, value, setValue, updateInputValue],
57
+ );
58
+
59
+ return { resolvedAddCustomOptionTriggers, tryCommitCustomOptionFromInput };
60
+ }
61
+
62
+ type UseFieldSelectSingleCustomOptionParams = UseResolvedAddCustomOptionTriggersParams & {
63
+ inputValue: string;
64
+ handleSelectionChange: (value: SelectionSingleValueType) => void;
65
+ };
66
+
67
+ export function useFieldSelectSingleCustomOption({
68
+ addCustomOptionTriggers,
69
+ addOptionByEnter,
70
+ inputValue,
71
+ handleSelectionChange,
72
+ }: UseFieldSelectSingleCustomOptionParams) {
73
+ const resolvedAddCustomOptionTriggers = useResolvedAddCustomOptionTriggers({
74
+ addCustomOptionTriggers,
75
+ addOptionByEnter,
76
+ });
77
+
78
+ const tryCommitCustomOptionFromInput = useCallback(
79
+ (trigger: FieldSelectMultipleAddCustomOptionTrigger) => {
80
+ if (!resolvedAddCustomOptionTriggers.includes(trigger) || inputValue === '') {
81
+ return;
82
+ }
83
+
84
+ handleSelectionChange(inputValue);
85
+ },
86
+ [resolvedAddCustomOptionTriggers, inputValue, handleSelectionChange],
87
+ );
88
+
89
+ return { resolvedAddCustomOptionTriggers, tryCommitCustomOptionFromInput };
90
+ }
@@ -0,0 +1,2 @@
1
+ export * from './common';
2
+ export * from './customOption';
@@ -1,6 +1,8 @@
1
1
  export { FieldSelect } from './FieldSelect';
2
2
 
3
3
  export type {
4
+ FieldSelectMultipleAddCustomOptionTrigger,
5
+ FieldSelectSingleAddCustomOptionTrigger,
4
6
  FieldSelectSingleProps,
5
7
  FieldSelectMultipleProps,
6
8
  FieldSelectProps,
@@ -70,10 +70,16 @@ export type SearchState = {
70
70
  onChange?(value: string): void;
71
71
  };
72
72
 
73
+ /** События, по которым произвольное значение из строки поиска фиксируется в значении поля */
74
+ export type FieldSelectMultipleAddCustomOptionTrigger = 'enter' | 'blur' | 'space' | 'comma';
75
+ export type FieldSelectSingleAddCustomOptionTrigger = Extract<
76
+ FieldSelectMultipleAddCustomOptionTrigger,
77
+ 'enter' | 'blur'
78
+ >;
79
+
73
80
  export type FieldSelectPrivateProps = InputProps &
74
81
  WrapperProps & {
75
82
  options: OptionProps[];
76
- /** Отображать ли состояние загрузки данных в поле и списке */
77
83
  loading?: boolean;
78
84
  /** Произвольный префикс для поля */
79
85
  prefix?: ReactNode;
@@ -114,6 +120,9 @@ type FiledSelectCommonProps = WithSupportProps<{
114
120
 
115
121
  autocomplete?: boolean;
116
122
 
123
+ /**
124
+ * @deprecated Используйте `addCustomOptionTriggers`
125
+ */
117
126
  addOptionByEnter?: boolean;
118
127
 
119
128
  open?: boolean;
@@ -148,12 +157,24 @@ type FiledSelectCommonProps = WithSupportProps<{
148
157
  export type FieldSelectSingleProps = FieldSelectPrivateProps &
149
158
  Omit<SelectionSingleState, 'mode'> &
150
159
  WrapperProps &
151
- FiledSelectCommonProps;
160
+ FiledSelectCommonProps & {
161
+ /**
162
+ * Триггеры фиксации произвольного значения из строки поиска в режиме `single`.
163
+ * Если передан, имеет приоритет над устаревшим `addOptionByEnter`.
164
+ */
165
+ addCustomOptionTriggers?: FieldSelectSingleAddCustomOptionTrigger[];
166
+ };
152
167
 
153
168
  export type FieldSelectMultipleProps = FieldSelectPrivateProps & {
154
169
  removeByBackspace?: boolean;
155
170
  } & Omit<SelectionMultipleState, 'mode'> &
156
- Omit<FiledSelectCommonProps, 'showCopyButton' | 'onCopyButtonClick'>;
171
+ Omit<FiledSelectCommonProps, 'showCopyButton' | 'onCopyButtonClick'> & {
172
+ /**
173
+ * Триггеры фиксации произвольного значения из строки поиска в режиме `multiple`.
174
+ * Если передан, имеет приоритет над устаревшим `addOptionByEnter`.
175
+ */
176
+ addCustomOptionTriggers?: FieldSelectMultipleAddCustomOptionTrigger[];
177
+ };
157
178
 
158
179
  export type FieldSelectProps =
159
180
  | (FieldSelectSingleProps & { selection?: 'single' })
@@ -0,0 +1,23 @@
1
+ import { KeyboardEvent } from 'react';
2
+
3
+ import { FieldSelectMultipleAddCustomOptionTrigger } from '../types';
4
+
5
+ export const getCustomOptionTriggerByCode = (
6
+ code: KeyboardEvent<HTMLInputElement>['code'],
7
+ ): FieldSelectMultipleAddCustomOptionTrigger | undefined => {
8
+ switch (code) {
9
+ case 'Enter':
10
+ return 'enter';
11
+ case 'Space':
12
+ return 'space';
13
+ case 'Comma':
14
+ return 'comma';
15
+ default:
16
+ return undefined;
17
+ }
18
+ };
19
+
20
+ export const shouldHandleCustomOptionTrigger = (
21
+ trigger: FieldSelectMultipleAddCustomOptionTrigger | undefined,
22
+ availableTriggers: FieldSelectMultipleAddCustomOptionTrigger[],
23
+ ): trigger is FieldSelectMultipleAddCustomOptionTrigger => (trigger ? availableTriggers.includes(trigger) : false);
@@ -5,3 +5,5 @@ export * from './updateItems';
5
5
  export * from './getArrowIcon';
6
6
  export * from './getValueByPath';
7
7
  export * from './checkisSearchUnavailable';
8
+ export * from './customOption';
9
+ export * from './filterItemsByFlattenIds';
@@ -1,4 +1,3 @@
1
- export * from './FieldAdd';
2
1
  export * from './FieldColor';
3
2
  export * from './FieldDate';
4
3
  export * from './FieldDecorator';
@@ -1,52 +0,0 @@
1
- import { ButtonOutlineProps } from '@snack-uikit/button';
2
- import { TooltipProps } from '@snack-uikit/tooltip';
3
- import { FieldSelectSingleProps } from '../FieldSelect';
4
- export type FieldAddButtonProps = Omit<ButtonOutlineProps, 'appearance' | 'icon' | 'size' | 'label'> & {
5
- /** Текст действия для кнопки добавления; используется как fallback для `tooltip.tip` и `aria-label` */
6
- label?: string;
7
- /** Тултип кнопки добавления */
8
- tooltip?: Omit<TooltipProps, 'children'>;
9
- };
10
- export type FieldAddProps = FieldSelectSingleProps & {
11
- /** Настройки кнопки добавления справа от поля */
12
- addButton?: FieldAddButtonProps;
13
- /** Тултип над контролом выбора значения */
14
- controlTooltip?: Omit<TooltipProps, 'children'>;
15
- /** Показывать ли кнопку добавления; в `readonly` режиме кнопка скрывается независимо от значения пропса */
16
- showAddButton?: boolean;
17
- };
18
- export declare const FieldAdd: import("react").ForwardRefExoticComponent<import("../FieldSelect/types").InputProps & import("../FieldSelect/types").WrapperProps & {
19
- options: import("../FieldSelect").OptionProps[];
20
- loading?: boolean;
21
- prefix?: import("react").ReactNode;
22
- postfix?: import("react").ReactNode;
23
- } & Omit<import("@snack-uikit/list").SelectionSingleState, "mode"> & {
24
- 'data-test-id'?: string;
25
- } & import("react").AriaAttributes & {
26
- options: import("../FieldSelect").OptionProps[];
27
- pinTop?: import("../FieldSelect").OptionProps[];
28
- pinBottom?: import("../FieldSelect").OptionProps[];
29
- searchable?: boolean;
30
- showCopyButton?: boolean;
31
- onCopyButtonClick?(): void;
32
- showClearButton?: boolean;
33
- readonly?: boolean;
34
- prefixIcon?: import("react").ReactElement;
35
- footer?: import("@snack-uikit/list").DroplistProps["footer"];
36
- widthStrategy?: import("@snack-uikit/list").DroplistProps["widthStrategy"];
37
- search?: import("../FieldSelect/types").SearchState;
38
- autocomplete?: boolean;
39
- addOptionByEnter?: boolean;
40
- open?: boolean;
41
- enableFuzzySearch?: boolean;
42
- resetSearchOnOptionSelection?: boolean;
43
- onOpenChange?(open: boolean): void;
44
- selectedOptionFormatter?: import("../FieldSelect").SelectedOptionFormatter;
45
- } & Pick<import("@snack-uikit/list").DroplistProps, "onScroll" | "scrollRef" | "scrollContainerRef" | "untouchableScrollbars" | "dataError" | "dataFiltered" | "scrollToSelectedItem" | "virtualized" | "noDataState" | "noResultsState" | "errorDataState"> & {
46
- /** Настройки кнопки добавления справа от поля */
47
- addButton?: FieldAddButtonProps;
48
- /** Тултип над контролом выбора значения */
49
- controlTooltip?: Omit<TooltipProps, "children">;
50
- /** Показывать ли кнопку добавления; в `readonly` режиме кнопка скрывается независимо от значения пропса */
51
- showAddButton?: boolean;
52
- } & import("react").RefAttributes<HTMLInputElement>>;