@snack-uikit/fields 0.14.2 → 0.15.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 (62) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +15 -14
  3. package/dist/components/FieldDecorator/Header.js +1 -1
  4. package/dist/components/FieldDecorator/styles.module.css +7 -1
  5. package/dist/components/FieldSelect/FieldSelect.d.ts +1 -7
  6. package/dist/components/FieldSelect/FieldSelect.js +9 -21
  7. package/dist/components/FieldSelect/FieldSelectMultiple.d.ts +17 -0
  8. package/dist/components/FieldSelect/FieldSelectMultiple.js +118 -0
  9. package/dist/components/FieldSelect/FieldSelectSingle.d.ts +9 -28
  10. package/dist/components/FieldSelect/FieldSelectSingle.js +69 -55
  11. package/dist/components/FieldSelect/hooks.d.ts +30 -0
  12. package/dist/components/FieldSelect/hooks.js +72 -0
  13. package/dist/components/FieldSelect/index.d.ts +2 -1
  14. package/dist/components/FieldSelect/index.js +1 -1
  15. package/dist/components/FieldSelect/styles.module.css +129 -27
  16. package/dist/components/FieldSelect/types.d.ts +42 -37
  17. package/dist/components/FieldSelect/utils.d.ts +19 -0
  18. package/dist/components/FieldSelect/utils.js +112 -0
  19. package/dist/helperComponents/FieldContainerPrivate/styles.module.css +30 -6
  20. package/package.json +5 -3
  21. package/src/components/FieldDecorator/Header.tsx +6 -1
  22. package/src/components/FieldDecorator/styles.module.scss +38 -30
  23. package/src/components/FieldSelect/FieldSelect.tsx +13 -21
  24. package/src/components/FieldSelect/FieldSelectMultiple.tsx +255 -0
  25. package/src/components/FieldSelect/FieldSelectSingle.tsx +159 -99
  26. package/src/components/FieldSelect/hooks.ts +125 -0
  27. package/src/components/FieldSelect/index.ts +12 -1
  28. package/src/components/FieldSelect/styles.module.scss +71 -31
  29. package/src/components/FieldSelect/types.ts +55 -40
  30. package/src/components/FieldSelect/utils.ts +163 -0
  31. package/src/helperComponents/FieldContainerPrivate/FieldContainerPrivate.tsx +2 -0
  32. package/src/helperComponents/FieldContainerPrivate/styles.module.scss +32 -11
  33. package/dist/components/FieldSelect/FieldSelectBase.d.ts +0 -31
  34. package/dist/components/FieldSelect/FieldSelectBase.js +0 -51
  35. package/dist/components/FieldSelect/FieldSelectMulti.d.ts +0 -37
  36. package/dist/components/FieldSelect/FieldSelectMulti.js +0 -89
  37. package/dist/components/FieldSelect/constants.d.ts +0 -7
  38. package/dist/components/FieldSelect/constants.js +0 -6
  39. package/dist/components/FieldSelect/helpers/getArrowIcon.d.ts +0 -8
  40. package/dist/components/FieldSelect/helpers/getArrowIcon.js +0 -8
  41. package/dist/components/FieldSelect/helpers/getDisplayedValue.d.ts +0 -10
  42. package/dist/components/FieldSelect/helpers/getDisplayedValue.js +0 -12
  43. package/dist/components/FieldSelect/helpers/index.d.ts +0 -2
  44. package/dist/components/FieldSelect/helpers/index.js +0 -2
  45. package/dist/components/FieldSelect/hooks/index.d.ts +0 -3
  46. package/dist/components/FieldSelect/hooks/index.js +0 -3
  47. package/dist/components/FieldSelect/hooks/useFilteredOptions.d.ts +0 -7
  48. package/dist/components/FieldSelect/hooks/useFilteredOptions.js +0 -6
  49. package/dist/components/FieldSelect/hooks/useList.d.ts +0 -37
  50. package/dist/components/FieldSelect/hooks/useList.js +0 -52
  51. package/dist/components/FieldSelect/hooks/useListNavigation.d.ts +0 -26
  52. package/dist/components/FieldSelect/hooks/useListNavigation.js +0 -48
  53. package/src/components/FieldSelect/FieldSelectBase.tsx +0 -222
  54. package/src/components/FieldSelect/FieldSelectMulti.tsx +0 -163
  55. package/src/components/FieldSelect/constants.ts +0 -9
  56. package/src/components/FieldSelect/helpers/getArrowIcon.ts +0 -9
  57. package/src/components/FieldSelect/helpers/getDisplayedValue.ts +0 -25
  58. package/src/components/FieldSelect/helpers/index.ts +0 -2
  59. package/src/components/FieldSelect/hooks/index.ts +0 -3
  60. package/src/components/FieldSelect/hooks/useFilteredOptions.ts +0 -23
  61. package/src/components/FieldSelect/hooks/useList.ts +0 -87
  62. package/src/components/FieldSelect/hooks/useListNavigation.ts +0 -81
package/CHANGELOG.md CHANGED
@@ -3,6 +3,22 @@
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.15.0 (2024-02-23)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+ * **FF-4221:** fix remove disabled & checked items by backspace ([4f0b945](https://github.com/cloud-ru-tech/snack-uikit/commit/4f0b945f4b1878923e5231298474393d6ad412d5))
12
+
13
+
14
+ ### BREAKING CHANGES
15
+
16
+
17
+ * **FF-4221:** replace list package; FieldMultiple select as TagSelect ([10333e9](https://github.com/cloud-ru-tech/snack-uikit/commit/10333e9ca00cb445f7617148ee82c2acfea678e6))
18
+
19
+
20
+
21
+
6
22
  ## 0.14.2 (2024-02-22)
7
23
 
8
24
 
package/README.md CHANGED
@@ -302,23 +302,15 @@ const [isOpen, setIsOpen] = useState(false);
302
302
  ### Props
303
303
  | name | type | default value | description |
304
304
  |------|------|---------------|-------------|
305
- | options* | `Option[]` | - | Массив опций выпадающего списка |
306
- | selectionMode | "single" \| "multi" | single | |
307
- | open | `boolean` | - | Открыт ли выпадающий список |
308
- | onOpenChange | `(value: boolean) => void` | - | Колбек открытия выпадающего списка |
309
- | searchable | `boolean` | - | Можно ли искать опции внутри списка |
310
- | showCopyButton | `boolean` | - | Отображение кнопки Копировать для поля (актуально только для `readonly = true`) |
311
- | showClearButton | `boolean` | true | Отображение кнопки очистки поля |
312
- | prefixIcon | `ReactElement<any, string \| JSXElementConstructor<any>>` | - | Иконка-префикс для поля |
313
- | noDataText | `string` | - | Текст отсутствия доступных значений |
314
- | locale | `Locale` | - | Текущая локаль |
305
+ | options* | `OptionProps[]` | - | |
315
306
  | disabled | `boolean` | false | Является ли поле деактивированным |
316
- | readonly | `boolean` | false | Является ли поле доступным только для чтения |
307
+ | readonly | `boolean` | false false | Является ли поле доступным только для чтения |
317
308
  | id | `string` | - | Значение html-атрибута id |
318
309
  | name | `string` | - | Значение html-атрибута name |
319
310
  | placeholder | `string` | - | Значение плейсхолдера |
320
311
  | onFocus | `FocusEventHandler<HTMLInputElement>` | - | Колбек обработки получения фокуса |
321
312
  | onBlur | `FocusEventHandler<HTMLInputElement>` | - | Колбек обработки потери фокуса |
313
+ | onKeyDown | `KeyboardEventHandler<HTMLInputElement>` | - | Колбек обработки нажатия клавиши клавиатуры |
322
314
  | className | `string` | - | CSS-класс |
323
315
  | label | `string` | - | Лейбл |
324
316
  | labelTooltip | `string` | - | Всплывающая подсказка лейбла |
@@ -328,11 +320,20 @@ const [isOpen, setIsOpen] = useState(false);
328
320
  | hint | `string` | - | Подсказка внизу |
329
321
  | validationState | enum ValidationState: `"default"`, `"error"`, `"warning"`, `"success"` | - | Состояние валидации |
330
322
  | showHintIcon | `boolean` | - | Отображать иконку подсказки |
331
- | value | `string \| string[]` | - | Выбранное значение: <br> - одно для single mode <br> - массив для multi mode |
332
- | onChange | `((value: string) => void) \| ((value: string[]) => void)` | - | Колбек смены значения |
323
+ | loading | `boolean` | - | |
324
+ | value | `SelectionSingleValueType \| SelectionSingleValueType[]` | - | Controlled состояние |
325
+ | onChange | `((value: any) => void) \| ((value: any) => void)` | - | Controlled обработчик измения состояния |
326
+ | defaultValue | `SelectionSingleValueType \| SelectionSingleValueType[]` | - | Начальное состояние |
327
+ | searchable | `boolean` | - | |
328
+ | showCopyButton | `boolean` | - | Отображение кнопки Копировать для поля (актуально только для `readonly = true`) |
329
+ | showClearButton | `boolean` | true | Отображение кнопки очистки поля |
330
+ | prefixIcon | `ReactElement<any, string \| JSXElementConstructor<any>>` | - | Иконка-префикс для поля |
331
+ | footer | `ReactNode` | - | |
332
+ | search | `SearchState` | - | |
333
+ | autocomplete | `boolean` | - | |
334
+ | selection | "single" \| "multiple" | - | |
333
335
  | 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 |
334
336
  | key | `Key` | - | |
335
- | getSelectedItemsText | `(amount: number) => string` | - | Колбек формирования текста |
336
337
  ## FieldStepper
337
338
  ### Props
338
339
  | name | type | default value | description |
@@ -4,5 +4,5 @@ import { Tooltip } from '@snack-uikit/tooltip';
4
4
  import { TruncateString } from '@snack-uikit/truncate-string';
5
5
  import styles from './styles.module.css';
6
6
  export function Header({ label = '', labelTooltip, labelFor, size, required = false, labelTooltipPlacement = 'top', }) {
7
- return (_jsx("span", { className: styles.header, "data-size": size, children: label && (_jsxs("span", { className: styles.labelLayout, children: [_jsx("label", { className: styles.label, htmlFor: labelFor, "data-test-id": 'field-decorator__label', children: _jsx(TruncateString, { text: label }) }), required && _jsx("span", { "data-test-id": 'field-decorator__required-sign', children: "*" }), labelTooltip && (_jsx(Tooltip, { tip: labelTooltip, placement: labelTooltipPlacement, "data-test-id": 'field-decorator__label-tooltip', children: _jsx(QuestionSVG, { size: 16, className: styles.icon, "data-test-id": 'field-decorator__label-tooltip-trigger' }) }))] })) }));
7
+ return (_jsx("span", { className: styles.header, "data-size": size, children: label && (_jsxs("span", { className: styles.labelLayout, children: [_jsx("label", { className: styles.label, htmlFor: labelFor, "data-test-id": 'field-decorator__label', children: _jsx(TruncateString, { text: label }) }), required && _jsx("span", { "data-test-id": 'field-decorator__required-sign', children: "*" }), labelTooltip && (_jsx(Tooltip, { tip: labelTooltip, placement: labelTooltipPlacement, "data-test-id": 'field-decorator__label-tooltip', triggerClassName: styles.labelTooltipTrigger, children: _jsx(QuestionSVG, { size: 16, className: styles.icon, "data-test-id": 'field-decorator__label-tooltip-trigger' }) }))] })) }));
8
8
  }
@@ -56,8 +56,8 @@
56
56
  }
57
57
 
58
58
  .footer{
59
+ gap:var(--space-fields-hint-container-gap, 4px);
59
60
  display:flex;
60
- gap:var(--dimension-050m, 4px);
61
61
  justify-content:space-between;
62
62
  box-sizing:border-box;
63
63
  }
@@ -178,4 +178,10 @@
178
178
  }
179
179
  .counterCurrentValue[data-limit-exceeded][data-validation=success]{
180
180
  color:var(--sys-green-text-light, #67ba6e);
181
+ }
182
+
183
+ .labelTooltipTrigger{
184
+ display:flex;
185
+ align-items:center;
186
+ height:100%;
181
187
  }
@@ -1,9 +1,3 @@
1
1
  /// <reference types="react" />
2
- import { SELECTION_MODE } from './constants';
3
- import { FieldSelectMultiProps, FieldSelectSingleProps } from './types';
4
- export type FieldSelectProps = ({
5
- selectionMode?: typeof SELECTION_MODE.Single;
6
- } & FieldSelectSingleProps) | ({
7
- selectionMode: typeof SELECTION_MODE.Multi;
8
- } & FieldSelectMultiProps);
2
+ import { FieldSelectProps } from './types';
9
3
  export declare const FieldSelect: import("react").ForwardRefExoticComponent<FieldSelectProps & import("react").RefAttributes<HTMLInputElement>>;
@@ -1,26 +1,14 @@
1
- var __rest = (this && this.__rest) || function (s, e) {
2
- var t = {};
3
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
- t[p] = s[p];
5
- if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
- for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
- if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
- t[p[i]] = s[p[i]];
9
- }
10
- return t;
11
- };
12
1
  import { jsx as _jsx } from "react/jsx-runtime";
13
2
  import { forwardRef } from 'react';
14
- import { SELECTION_MODE } from './constants';
15
- import { FieldSelectMulti } from './FieldSelectMulti';
3
+ import { FieldSelectMultiple } from './FieldSelectMultiple';
16
4
  import { FieldSelectSingle } from './FieldSelectSingle';
17
- export const FieldSelect = forwardRef((_a, ref) => {
18
- var { selectionMode = SELECTION_MODE.Single } = _a, props = __rest(_a, ["selectionMode"]);
19
- switch (selectionMode) {
20
- case SELECTION_MODE.Multi:
21
- return _jsx(FieldSelectMulti, Object.assign({}, props, { ref: ref }));
22
- case SELECTION_MODE.Single:
23
- default:
24
- return _jsx(FieldSelectSingle, Object.assign({}, props, { ref: ref }));
5
+ import { isFieldSelectMultipleProps, isFieldSelectSingleProps } from './utils';
6
+ export const FieldSelect = forwardRef((props, ref) => {
7
+ if (isFieldSelectMultipleProps(props)) {
8
+ return _jsx(FieldSelectMultiple, Object.assign({}, props, { ref: ref }));
25
9
  }
10
+ if (isFieldSelectSingleProps(props)) {
11
+ return _jsx(FieldSelectSingle, Object.assign({}, props, { ref: ref }));
12
+ }
13
+ return null;
26
14
  });
@@ -0,0 +1,17 @@
1
+ /// <reference types="react" />
2
+ export declare const FieldSelectMultiple: import("react").ForwardRefExoticComponent<import("./types").InputProps & import("./types").WrapperProps & {
3
+ options: import("./types").OptionProps[];
4
+ loading?: boolean | undefined;
5
+ } & Omit<import("@snack-uikit/list").SelectionMultipleState, "mode"> & {
6
+ 'data-test-id'?: string | undefined;
7
+ } & import("react").AriaAttributes & {
8
+ options: import("./types").OptionProps[];
9
+ searchable?: boolean | undefined;
10
+ showCopyButton?: boolean | undefined;
11
+ showClearButton?: boolean | undefined;
12
+ readonly?: boolean | undefined;
13
+ prefixIcon?: import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>> | undefined;
14
+ footer?: import("react").ReactNode;
15
+ search?: import("./types").SearchState | undefined;
16
+ autocomplete?: boolean | undefined;
17
+ } & import("react").RefAttributes<HTMLInputElement>>;
@@ -0,0 +1,118 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
12
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
13
+ import cn from 'classnames';
14
+ import mergeRefs from 'merge-refs';
15
+ import { forwardRef, useMemo, useRef, useState } from 'react';
16
+ import { useUncontrolledProp } from 'uncontrollable';
17
+ import { InputPrivate } from '@snack-uikit/input-private';
18
+ import { Droplist, useFuzzySearch } from '@snack-uikit/list';
19
+ import { Tag } from '@snack-uikit/tag';
20
+ import { extractSupportProps } from '@snack-uikit/utils';
21
+ import { FieldContainerPrivate } from '../../helperComponents';
22
+ import { FieldDecorator } from '../FieldDecorator';
23
+ import { useButtons, useHandleDeleteItem, useHandleOnKeyDown, useSearchInput } from './hooks';
24
+ import styles from './styles.module.css';
25
+ import { extractSelectedMultipleOptions, getArrowIcon, transformOptionsToItems } from './utils';
26
+ const BASE_MIN_WIDTH = 4;
27
+ export const FieldSelectMultiple = forwardRef((_a, ref) => {
28
+ var _b;
29
+ 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 } = _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"]);
30
+ const localRef = useRef(null);
31
+ const inputPlugRef = useRef(null);
32
+ const contentRef = useRef(null);
33
+ const [open, setOpen] = useState(false);
34
+ const items = useMemo(() => transformOptionsToItems(options), [options]);
35
+ const [value, setValue] = useUncontrolledProp(valueProp, defaultValue, onChangeProp);
36
+ const selectedOption = useMemo(() => {
37
+ const notSortSelectedOption = extractSelectedMultipleOptions(options, value);
38
+ if (notSortSelectedOption) {
39
+ return notSortSelectedOption.sort((a, b) => {
40
+ if (b.disabled && !a.disabled) {
41
+ return 1;
42
+ }
43
+ if (a.disabled && !b.disabled) {
44
+ return -1;
45
+ }
46
+ return 0;
47
+ });
48
+ }
49
+ }, [options, value]);
50
+ const { inputValue, onInputValueChange, prevInputValue } = useSearchInput(Object.assign(Object.assign({}, search), { defaultValue: String(selectedOption !== null && selectedOption !== void 0 ? selectedOption : '') }));
51
+ const onClear = () => {
52
+ var _a;
53
+ setValue(undefined);
54
+ onInputValueChange('');
55
+ (_a = localRef.current) === null || _a === void 0 ? void 0 : _a.focus();
56
+ setOpen(true);
57
+ };
58
+ const { ArrowIcon, arrowIconSize } = getArrowIcon({ size, open });
59
+ const { buttons, inputKeyDownNavigationHandler, buttonsRefs } = useButtons({
60
+ readonly,
61
+ size,
62
+ showClearButton: showClearButton && Boolean(value),
63
+ showCopyButton,
64
+ inputRef: localRef,
65
+ onClear,
66
+ valueToCopy: String((_b = selectedOption === null || selectedOption === void 0 ? void 0 : selectedOption.map(option => option.option).join(', ')) !== null && _b !== void 0 ? _b : ''),
67
+ });
68
+ const commonHandleOnKeyDown = useHandleOnKeyDown({
69
+ inputKeyDownNavigationHandler,
70
+ onInputKeyDownProp,
71
+ setOpen,
72
+ });
73
+ const handleItemDelete = useHandleDeleteItem(setValue);
74
+ const handleOnKeyDown = (onKeyDown) => (e) => {
75
+ if (e.code === 'Backspace' && inputValue === '') {
76
+ if ((selectedOption === null || selectedOption === void 0 ? void 0 : selectedOption.length) && !selectedOption.slice(-1)[0].disabled) {
77
+ handleItemDelete(selectedOption.pop())();
78
+ }
79
+ }
80
+ if (!open && prevInputValue.current !== inputValue) {
81
+ setOpen(true);
82
+ }
83
+ commonHandleOnKeyDown(onKeyDown)(e);
84
+ };
85
+ const handleOpenChange = (open) => {
86
+ if (!readonly && !disabled && !buttonsRefs.includes(document.activeElement)) {
87
+ setOpen(open);
88
+ if (!open) {
89
+ prevInputValue.current = inputValue;
90
+ }
91
+ if (open) {
92
+ prevInputValue.current = '';
93
+ }
94
+ }
95
+ };
96
+ const handleBlur = (e) => {
97
+ var _a;
98
+ if (!open) {
99
+ onInputValueChange('');
100
+ }
101
+ (_a = rest === null || rest === void 0 ? void 0 : rest.onBlur) === null || _a === void 0 ? void 0 : _a.call(rest, e);
102
+ };
103
+ const fuzzySearch = useFuzzySearch(items);
104
+ const result = autocomplete ? items : fuzzySearch(prevInputValue.current !== inputValue ? inputValue : '');
105
+ return (_jsx(FieldDecorator, Object.assign({}, extractSupportProps(rest), { 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: {
106
+ mode: 'multiple',
107
+ value: value,
108
+ onChange: setValue,
109
+ }, size: size, open: !disabled && !readonly && open, onOpenChange: handleOpenChange, loading: loading, children: ({ onKeyDown }) => {
110
+ var _a, _b, _c, _d;
111
+ 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 &&
112
+ selectedOption.map(option => (_jsx(Tag, { size: size === 'l' ? 's' : 'xs', tabIndex: -1, label: String(option.option), onDelete: !option.disabled ? handleItemDelete(option) : undefined }, option.value))), _jsx("div", { className: styles.inputWrapper, style: {
113
+ minWidth: value
114
+ ? 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)
115
+ : '100%',
116
+ }, 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 })] }) }));
117
+ } }) })));
118
+ });
@@ -1,36 +1,17 @@
1
1
  /// <reference types="react" />
2
- import { Option } from './types';
3
- export declare const FieldSelectSingle: import("react").ForwardRefExoticComponent<{
2
+ export declare const FieldSelectSingle: import("react").ForwardRefExoticComponent<import("./types").InputProps & import("./types").WrapperProps & {
3
+ options: import("./types").OptionProps[];
4
+ loading?: boolean | undefined;
5
+ } & Omit<import("@snack-uikit/list").SelectionSingleState, "mode"> & {
4
6
  'data-test-id'?: string | undefined;
5
7
  } & import("react").AriaAttributes & {
6
- options: Option[];
7
- open?: boolean | undefined;
8
- onOpenChange?(value: boolean): void;
8
+ options: import("./types").OptionProps[];
9
9
  searchable?: boolean | undefined;
10
10
  showCopyButton?: boolean | undefined;
11
11
  showClearButton?: boolean | undefined;
12
- prefixIcon?: import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>> | undefined;
13
- noDataText?: string | undefined;
14
- locale?: Intl.Locale | undefined;
15
- } & {
16
- disabled?: boolean | undefined;
17
12
  readonly?: boolean | undefined;
18
- name?: string | undefined;
19
- placeholder?: string | undefined;
20
- id?: string | undefined;
21
- onFocus?: import("react").FocusEventHandler<HTMLInputElement> | undefined;
22
- onBlur?: import("react").FocusEventHandler<HTMLInputElement> | undefined;
23
- } & {
24
- showHintIcon?: boolean | undefined;
25
- validationState?: import("../../types").ValidationState | undefined;
26
- hint?: string | undefined;
27
- size?: import("@snack-uikit/input-private").Size | undefined;
28
- label?: string | undefined;
29
- className?: string | undefined;
30
- labelTooltip?: string | undefined;
31
- required?: boolean | undefined;
32
- labelTooltipPlacement?: import("@snack-uikit/popover-private/dist/types").Placement | undefined;
33
- } & {
34
- value?: string | undefined;
35
- onChange?(value: string): void;
13
+ prefixIcon?: import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>> | undefined;
14
+ footer?: import("react").ReactNode;
15
+ search?: import("./types").SearchState | undefined;
16
+ autocomplete?: boolean | undefined;
36
17
  } & import("react").RefAttributes<HTMLInputElement>>;
@@ -9,68 +9,82 @@ var __rest = (this && this.__rest) || function (s, e) {
9
9
  }
10
10
  return t;
11
11
  };
12
- import { jsx as _jsx } from "react/jsx-runtime";
13
- import { forwardRef, useCallback, useEffect, useMemo, useState } from 'react';
12
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
13
+ import mergeRefs from 'merge-refs';
14
+ import { forwardRef, useEffect, useMemo, useRef, useState } from 'react';
14
15
  import { useUncontrolledProp } from 'uncontrollable';
15
- import { selectAll } from '@snack-uikit/input-private';
16
- import { DEFAULT_LOCALE, EMPTY_OPTION, SELECTION_MODE } from './constants';
17
- import { FieldSelectBase } from './FieldSelectBase';
18
- import { getDisplayedValue } from './helpers';
19
- import { useList } from './hooks';
16
+ import { InputPrivate } from '@snack-uikit/input-private';
17
+ import { Droplist, useFuzzySearch } from '@snack-uikit/list';
18
+ import { extractSupportProps } from '@snack-uikit/utils';
19
+ import { FieldContainerPrivate } from '../../helperComponents';
20
+ import { FieldDecorator } from '../FieldDecorator';
21
+ import { useButtons, useHandleOnKeyDown, useSearchInput } from './hooks';
22
+ import styles from './styles.module.css';
23
+ import { extractSelectedOptions, getArrowIcon, transformOptionsToItems } from './utils';
20
24
  export const FieldSelectSingle = forwardRef((_a, ref) => {
21
- var { value: valueProp, onChange, options, disabled = false, readonly = false, searchable = true, required = false, locale = DEFAULT_LOCALE, open, onOpenChange, showCopyButton: showCopyButtonProp = true, showClearButton: showClearButtonProp = true } = _a, rest = __rest(_a, ["value", "onChange", "options", "disabled", "readonly", "searchable", "required", "locale", "open", "onOpenChange", "showCopyButton", "showClearButton"]);
22
- const selectionMode = SELECTION_MODE.Single;
23
- const [value, setValue] = useUncontrolledProp(valueProp, '', onChange);
24
- const selected = useMemo(() => { var _a; return (_a = options.find(option => option.value === value)) !== null && _a !== void 0 ? _a : EMPTY_OPTION; }, [options, value]);
25
- const displayedValue = getDisplayedValue({ selectionMode, selected });
26
- const [inputValue, setInputValue] = useState(selected.label);
27
- const showAdditionalButton = Boolean(value && !disabled);
28
- const isChecked = useCallback((option) => selected.value === option.value, [selected.value]);
29
- const { isOpen, setIsOpen, localRef, extendedOptions, onInputKeyDown, onInputValueChange, onButtonKeyDown, clearButtonRef, copyButtonRef, showClearButton, showCopyButton, onDroplistFocusLeave, firstDroplistItemRefCallback, } = useList({
30
- open,
31
- onOpenChange,
32
- disabled,
33
- readonly,
34
- inputValue,
35
- setInputValue,
36
- searchable,
37
- options,
38
- isChecked,
39
- showCopyButton: showCopyButtonProp,
40
- showClearButton: showClearButtonProp,
41
- showAdditionalButton,
42
- });
43
- const handleOpenChange = (isOpen) => {
44
- if (isOpen) {
45
- searchable && selectAll(localRef.current);
46
- }
47
- else {
48
- setInputValue(displayedValue);
49
- }
50
- setIsOpen(isOpen);
51
- };
52
- const handleClear = () => {
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 } = _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"]);
27
+ const localRef = useRef(null);
28
+ const [open, setOpen] = useState(false);
29
+ const [value, setValue] = useUncontrolledProp(valueProp, defaultValue, onChangeProp);
30
+ const items = useMemo(() => transformOptionsToItems(options), [options]);
31
+ const selectedOption = useMemo(() => extractSelectedOptions(options, value), [options, value]);
32
+ const { inputValue, onInputValueChange, prevInputValue } = useSearchInput(Object.assign(Object.assign({}, search), { defaultValue: String(selectedOption !== null && selectedOption !== void 0 ? selectedOption : '') }));
33
+ useEffect(() => {
34
+ var _a;
35
+ !open && onInputValueChange(String((_a = selectedOption === null || selectedOption === void 0 ? void 0 : selectedOption.option) !== null && _a !== void 0 ? _a : ''));
36
+ }, [onInputValueChange, open, selectedOption]);
37
+ useEffect(() => {
53
38
  var _a, _b;
39
+ onInputValueChange(String((_a = selectedOption === null || selectedOption === void 0 ? void 0 : selectedOption.option) !== null && _a !== void 0 ? _a : ''));
40
+ prevInputValue.current = String((_b = selectedOption === null || selectedOption === void 0 ? void 0 : selectedOption.option) !== null && _b !== void 0 ? _b : '');
41
+ }, [prevInputValue, onInputValueChange, selectedOption]);
42
+ const onClear = () => {
43
+ var _a;
54
44
  setValue('');
55
- setInputValue('');
56
- if (required) {
57
- (_a = localRef.current) === null || _a === void 0 ? void 0 : _a.focus();
58
- setIsOpen(true);
59
- }
60
- else {
61
- (_b = localRef.current) === null || _b === void 0 ? void 0 : _b.blur();
62
- setIsOpen(false);
45
+ onInputValueChange('');
46
+ (_a = localRef.current) === null || _a === void 0 ? void 0 : _a.focus();
47
+ setOpen(true);
48
+ };
49
+ const { ArrowIcon, arrowIconSize } = getArrowIcon({ size, open });
50
+ const { buttons, inputKeyDownNavigationHandler, buttonsRefs } = useButtons({
51
+ readonly,
52
+ size,
53
+ showClearButton: showClearButton && Boolean(value),
54
+ showCopyButton,
55
+ inputRef: localRef,
56
+ onClear,
57
+ valueToCopy: String((_b = selectedOption === null || selectedOption === void 0 ? void 0 : selectedOption.option) !== null && _b !== void 0 ? _b : ''),
58
+ });
59
+ const commonHandleOnKeyDown = useHandleOnKeyDown({
60
+ inputKeyDownNavigationHandler,
61
+ onInputKeyDownProp,
62
+ setOpen,
63
+ });
64
+ const handleOnKeyDown = (onKeyDown) => (e) => {
65
+ if (!open && prevInputValue.current !== inputValue) {
66
+ setOpen(true);
63
67
  }
68
+ commonHandleOnKeyDown(onKeyDown)(e);
64
69
  };
65
- const handleChange = (option) => () => {
70
+ const handleSelectionChange = (newValue) => {
66
71
  var _a;
67
- setValue(option.value);
68
- setInputValue(option.label);
69
- setIsOpen(false);
72
+ setValue(newValue);
70
73
  (_a = localRef.current) === null || _a === void 0 ? void 0 : _a.focus();
74
+ if (newValue) {
75
+ setOpen(false);
76
+ }
71
77
  };
72
- useEffect(() => {
73
- setInputValue(selected.label);
74
- }, [selected]);
75
- return (_jsx(FieldSelectBase, Object.assign({}, rest, { ref: ref, localRef: localRef, selectionMode: selectionMode, options: extendedOptions, selected: selected, disabled: disabled, readonly: readonly, required: required, searchable: searchable, onChange: handleChange, onClear: handleClear, valueToCopy: displayedValue, inputValue: searchable ? inputValue : displayedValue, onInputValueChange: onInputValueChange, onInputKeyDown: onInputKeyDown, clearButtonRef: clearButtonRef, copyButtonRef: copyButtonRef, onButtonKeyDown: onButtonKeyDown, open: isOpen, onOpenChange: handleOpenChange, locale: locale, showCopyButton: showCopyButton, showClearButton: showClearButton, onDroplistFocusLeave: onDroplistFocusLeave, firstDroplistItemRefCallback: firstDroplistItemRefCallback })));
78
+ const handleOpenChange = (open) => {
79
+ if (!readonly && !disabled && !buttonsRefs.includes(document.activeElement)) {
80
+ setOpen(open);
81
+ }
82
+ };
83
+ const fuzzySearch = useFuzzySearch(items);
84
+ const result = autocomplete ? items : fuzzySearch(prevInputValue.current !== inputValue ? inputValue : '');
85
+ return (_jsx(FieldDecorator, Object.assign({}, extractSupportProps(rest), { 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: {
86
+ mode: 'single',
87
+ value: value,
88
+ onChange: handleSelectionChange,
89
+ }, 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 })] })] })) }) })));
76
90
  });
@@ -0,0 +1,30 @@
1
+ import { KeyboardEvent, KeyboardEventHandler, RefObject } from 'react';
2
+ import { Handler } from 'uncontrollable';
3
+ import { OptionProps, SearchState } from './types';
4
+ type UseHandleOnKeyDownProps = {
5
+ inputKeyDownNavigationHandler: KeyboardEventHandler<HTMLInputElement>;
6
+ onInputKeyDownProp: KeyboardEventHandler<HTMLInputElement> | undefined;
7
+ setOpen(open: boolean): void;
8
+ };
9
+ export declare function useHandleOnKeyDown({ setOpen, inputKeyDownNavigationHandler, onInputKeyDownProp, }: UseHandleOnKeyDownProps): (onKeyDown?: KeyboardEventHandler<HTMLElement>) => (e: KeyboardEvent<HTMLInputElement>) => void;
10
+ type UseButtonsProps = {
11
+ readonly: boolean;
12
+ showClearButton: boolean;
13
+ showCopyButton: boolean;
14
+ size: 's' | 'm' | 'l';
15
+ onClear(): void;
16
+ inputRef: RefObject<HTMLInputElement>;
17
+ valueToCopy?: string;
18
+ };
19
+ export declare function useButtons({ readonly, showClearButton, showCopyButton, size, onClear, inputRef, valueToCopy, }: UseButtonsProps): {
20
+ buttons: import("react/jsx-runtime").JSX.Element;
21
+ inputKeyDownNavigationHandler: KeyboardEventHandler<HTMLInputElement>;
22
+ buttonsRefs: (Element | null)[];
23
+ };
24
+ export declare function useSearchInput({ value, onChange, defaultValue }: SearchState): {
25
+ inputValue: string;
26
+ onInputValueChange: Handler;
27
+ prevInputValue: import("react").MutableRefObject<string>;
28
+ };
29
+ export declare function useHandleDeleteItem(setValue: Handler): (option?: OptionProps) => () => void;
30
+ export {};
@@ -0,0 +1,72 @@
1
+ import { useCallback, useMemo, useRef } from 'react';
2
+ import { useUncontrolledProp } from 'uncontrollable';
3
+ 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';
7
+ export function useHandleOnKeyDown({ setOpen, inputKeyDownNavigationHandler, onInputKeyDownProp, }) {
8
+ return useCallback((onKeyDown) => (e) => {
9
+ if (e.code === 'Space') {
10
+ e.stopPropagation();
11
+ }
12
+ else {
13
+ onKeyDown === null || onKeyDown === void 0 ? void 0 : onKeyDown(e);
14
+ }
15
+ if (e.code === 'ArrowUp') {
16
+ setOpen(false);
17
+ }
18
+ if (['ArrowUp', 'ArrowDown'].includes(e.key)) {
19
+ e.preventDefault();
20
+ }
21
+ if (e.key === 'Tab') {
22
+ setOpen(false);
23
+ }
24
+ inputKeyDownNavigationHandler(e);
25
+ onInputKeyDownProp === null || onInputKeyDownProp === void 0 ? void 0 : onInputKeyDownProp(e);
26
+ }, [inputKeyDownNavigationHandler, onInputKeyDownProp, setOpen]);
27
+ }
28
+ export function useButtons({ readonly, showClearButton, showCopyButton, size, onClear, inputRef, valueToCopy = '', }) {
29
+ const clearButtonRef = useRef(null);
30
+ const copyButtonRef = useRef(null);
31
+ const buttonsRefs = [copyButtonRef.current, clearButtonRef.current];
32
+ const clearButtonSettings = useClearButton({
33
+ clearButtonRef,
34
+ showClearButton: !readonly && showClearButton,
35
+ size,
36
+ onClear,
37
+ });
38
+ const copyButtonSettings = useCopyButton({
39
+ copyButtonRef,
40
+ showCopyButton: readonly && showCopyButton,
41
+ size,
42
+ valueToCopy,
43
+ });
44
+ const { onInputKeyDown: inputKeyDownNavigationHandler, buttons } = useButtonNavigation({
45
+ inputRef,
46
+ buttons: useMemo(() => [clearButtonSettings, copyButtonSettings], [clearButtonSettings, copyButtonSettings]),
47
+ onButtonKeyDown: undefined,
48
+ readonly,
49
+ submitKeys: ['Enter', 'Space', 'Tab'],
50
+ });
51
+ return { buttons, inputKeyDownNavigationHandler, buttonsRefs };
52
+ }
53
+ export function useSearchInput({ value, onChange, defaultValue }) {
54
+ const [inputValue, onInputValueChange] = useUncontrolledProp(value, defaultValue !== null && defaultValue !== void 0 ? defaultValue : '', onChange);
55
+ const prevInputValue = useRef(inputValue);
56
+ return { inputValue, onInputValueChange, prevInputValue };
57
+ }
58
+ export function useHandleDeleteItem(setValue) {
59
+ return useCallback((option) => () => {
60
+ if (!option) {
61
+ return;
62
+ }
63
+ if (isAccordionOptionProps(option) || isNextListOptionProps(option)) {
64
+ const removeIds = extractChildIds({ items: transformOptionsToItems(option.options) }).concat(option.value);
65
+ setValue((value) => value === null || value === void 0 ? void 0 : value.filter(v => !removeIds.includes(v !== null && v !== void 0 ? v : '')));
66
+ return;
67
+ }
68
+ if (isBaseOptionProps(option)) {
69
+ setValue((value) => value === null || value === void 0 ? void 0 : value.filter(v => v !== option.value));
70
+ }
71
+ }, [setValue]);
72
+ }
@@ -1 +1,2 @@
1
- export * from './FieldSelect';
1
+ export { FieldSelect } from './FieldSelect';
2
+ export type { FieldSelectSingleProps, FieldSelectMultipleProps, FieldSelectProps, OptionProps, BaseOptionProps, AccordionOptionProps, NestListOptionProps, GroupOptionProps, } from './types';
@@ -1 +1 @@
1
- export * from './FieldSelect';
1
+ export { FieldSelect } from './FieldSelect';