@snack-uikit/fields 0.18.2 → 0.18.4-preview-5d3667ec.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +4 -3
- package/dist/components/FieldSelect/FieldSelectMultiple.d.ts +2 -1
- package/dist/components/FieldSelect/FieldSelectSingle.d.ts +2 -1
- package/dist/components/FieldSelect/styles.module.css +8 -18
- package/dist/components/FieldSelect/types.d.ts +4 -3
- package/dist/components/FieldSelect/utils/extractListProps.d.ts +1 -1
- package/dist/components/FieldSelect/utils/extractListProps.js +3 -1
- package/dist/components/FieldSlider/FieldSlider.js +63 -19
- package/dist/components/FieldSlider/helpers/getClosestMark.d.ts +2 -2
- package/dist/components/FieldSlider/helpers/getClosestMark.js +3 -3
- package/dist/components/FieldSlider/helpers/index.d.ts +1 -0
- package/dist/components/FieldSlider/helpers/index.js +1 -0
- package/dist/components/FieldSlider/helpers/isMarkObject.d.ts +8 -0
- package/dist/components/FieldSlider/helpers/isMarkObject.js +3 -0
- package/package.json +6 -6
- package/src/components/FieldSelect/styles.module.scss +5 -3
- package/src/components/FieldSelect/types.ts +5 -3
- package/src/components/FieldSelect/utils/extractListProps.ts +4 -0
- package/src/components/FieldSlider/FieldSlider.tsx +93 -19
- package/src/components/FieldSlider/helpers/getClosestMark.ts +7 -3
- package/src/components/FieldSlider/helpers/index.ts +1 -0
- package/src/components/FieldSlider/helpers/isMarkObject.ts +13 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,18 @@
|
|
|
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.18.3 (2024-04-22)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* **FF-4649:** container width of multiselect ([ca5702d](https://github.com/cloud-ru-tech/snack-uikit/commit/ca5702dbb4ae2574b5b312c926663cc67bbe694c))
|
|
12
|
+
* **FF-4649:** pass props footer and widthStrategy for Droplist ([c4a78cb](https://github.com/cloud-ru-tech/snack-uikit/commit/c4a78cbaddd6b8e6718b7a10614e56b5d618332e))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
6
18
|
## 0.18.2 (2024-04-22)
|
|
7
19
|
|
|
8
20
|
### Only dependencies have been changed
|
package/README.md
CHANGED
|
@@ -340,19 +340,20 @@ const [isOpen, setIsOpen] = useState(false);
|
|
|
340
340
|
| showClearButton | `boolean` | true | Отображение кнопки очистки поля |
|
|
341
341
|
| prefixIcon | `ReactElement<any, string \| JSXElementConstructor<any>>` | - | Иконка-префикс для поля |
|
|
342
342
|
| footer | `ReactNode` | - | |
|
|
343
|
+
| widthStrategy | enum PopoverWidthStrategy: `"auto"`, `"gte"`, `"eq"` | - | |
|
|
343
344
|
| search | `SearchState` | - | |
|
|
344
345
|
| autocomplete | `boolean` | - | |
|
|
345
346
|
| addOptionByEnter | `boolean` | - | |
|
|
346
347
|
| open | `boolean` | - | |
|
|
347
348
|
| onOpenChange | `(open: boolean) => void` | - | |
|
|
348
349
|
| selectedOptionFormatter | `SelectedOptionFormatter` | - | |
|
|
350
|
+
| pinTop | `ItemProps[]` | - | Элементы списка, закрепленные сверху |
|
|
351
|
+
| pinBottom | `ItemProps[]` | - | Элементы списка, закрепленные снизу |
|
|
352
|
+
| dataFiltered | `boolean` | - | |
|
|
349
353
|
| dataError | `boolean` | - | |
|
|
350
354
|
| noDataState | `EmptyStateProps` | - | Экран при отстутствии данных |
|
|
351
355
|
| noResultsState | `EmptyStateProps` | - | Экран при отстутствии результатов поиска или фильтров |
|
|
352
356
|
| errorDataState | `EmptyStateProps` | - | Экран при ошибке запроса |
|
|
353
|
-
| pinTop | `ItemProps[]` | - | Элементы списка, закрепленные сверху |
|
|
354
|
-
| pinBottom | `ItemProps[]` | - | Элементы списка, закрепленные снизу |
|
|
355
|
-
| dataFiltered | `boolean` | - | |
|
|
356
357
|
| selection | "single" \| "multiple" | - | |
|
|
357
358
|
| 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 |
|
|
358
359
|
| key | `Key` | - | |
|
|
@@ -15,10 +15,11 @@ export declare const FieldSelectMultiple: import("react").ForwardRefExoticCompon
|
|
|
15
15
|
readonly?: boolean | undefined;
|
|
16
16
|
prefixIcon?: import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>> | undefined;
|
|
17
17
|
footer?: import("react").ReactNode;
|
|
18
|
+
widthStrategy?: import("@snack-uikit/popover-private/dist/types").PopoverWidthStrategy | undefined;
|
|
18
19
|
search?: import("./types").SearchState | undefined;
|
|
19
20
|
autocomplete?: boolean | undefined;
|
|
20
21
|
addOptionByEnter?: boolean | undefined;
|
|
21
22
|
open?: boolean | undefined;
|
|
22
23
|
onOpenChange?(open: boolean): void;
|
|
23
24
|
selectedOptionFormatter?: SelectedOptionFormatter | undefined;
|
|
24
|
-
} & Pick<import("@snack-uikit/list").
|
|
25
|
+
} & Pick<import("@snack-uikit/list").DroplistProps, "pinTop" | "pinBottom" | "dataFiltered" | "dataError" | "noDataState" | "noResultsState" | "errorDataState">, "showCopyButton"> & import("react").RefAttributes<HTMLInputElement>>;
|
|
@@ -13,10 +13,11 @@ export declare const FieldSelectSingle: import("react").ForwardRefExoticComponen
|
|
|
13
13
|
readonly?: boolean | undefined;
|
|
14
14
|
prefixIcon?: import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>> | undefined;
|
|
15
15
|
footer?: import("react").ReactNode;
|
|
16
|
+
widthStrategy?: import("@snack-uikit/popover-private/dist/types").PopoverWidthStrategy | undefined;
|
|
16
17
|
search?: import("./types").SearchState | undefined;
|
|
17
18
|
autocomplete?: boolean | undefined;
|
|
18
19
|
addOptionByEnter?: boolean | undefined;
|
|
19
20
|
open?: boolean | undefined;
|
|
20
21
|
onOpenChange?(open: boolean): void;
|
|
21
22
|
selectedOptionFormatter?: SelectedOptionFormatter | undefined;
|
|
22
|
-
} & Pick<import("@snack-uikit/list").
|
|
23
|
+
} & Pick<import("@snack-uikit/list").DroplistProps, "pinTop" | "pinBottom" | "dataFiltered" | "dataError" | "noDataState" | "noResultsState" | "errorDataState"> & import("react").RefAttributes<HTMLInputElement>>;
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
.contentWrapper{
|
|
7
|
+
position:relative;
|
|
8
|
+
overflow:hidden;
|
|
7
9
|
display:flex;
|
|
8
10
|
flex-wrap:wrap;
|
|
9
11
|
width:100%;
|
|
@@ -71,14 +73,10 @@
|
|
|
71
73
|
}
|
|
72
74
|
.container[data-size=s][data-variant=single-line-container] .displayValue{
|
|
73
75
|
width:calc(100% - (
|
|
74
|
-
var(--space-fields-single-line-container-s-right, 6px) +
|
|
75
|
-
var(--space-fields-single-line-container-s-gap, 4px) +
|
|
76
|
-
calc(var(var(--space-fields-postfix-gap, 4px)) + var(--size-icon-container-xs, 16px) * 2)
|
|
76
|
+
var(--space-fields-single-line-container-s-right, 6px) + var(--space-fields-single-line-container-s-gap, 4px) + calc(var(var(--space-fields-postfix-gap, 4px)) + var(--size-icon-container-xs, 16px) * 2)
|
|
77
77
|
));
|
|
78
78
|
margin-right:calc(
|
|
79
|
-
var(--space-fields-single-line-container-s-right, 6px) +
|
|
80
|
-
var(--space-fields-single-line-container-s-gap, 4px) +
|
|
81
|
-
calc(var(var(--space-fields-postfix-gap, 4px)) + var(--size-icon-container-xs, 16px) * 2)
|
|
79
|
+
var(--space-fields-single-line-container-s-right, 6px) + var(--space-fields-single-line-container-s-gap, 4px) + calc(var(var(--space-fields-postfix-gap, 4px)) + var(--size-icon-container-xs, 16px) * 2)
|
|
82
80
|
);
|
|
83
81
|
padding-left:var(--space-fields-single-line-container-s-left, 6px);
|
|
84
82
|
border-radius:var(--radius-fields-s, 12px);
|
|
@@ -98,14 +96,10 @@
|
|
|
98
96
|
}
|
|
99
97
|
.container[data-size=m][data-variant=single-line-container] .displayValue{
|
|
100
98
|
width:calc(100% - (
|
|
101
|
-
var(--space-fields-single-line-container-m-right, 8px) +
|
|
102
|
-
var(--space-fields-single-line-container-m-gap, 4px) +
|
|
103
|
-
calc(var(var(--space-fields-postfix-gap, 4px)) + var(--size-icon-container-s, 24px) * 2)
|
|
99
|
+
var(--space-fields-single-line-container-m-right, 8px) + var(--space-fields-single-line-container-m-gap, 4px) + calc(var(var(--space-fields-postfix-gap, 4px)) + var(--size-icon-container-s, 24px) * 2)
|
|
104
100
|
));
|
|
105
101
|
margin-right:calc(
|
|
106
|
-
var(--space-fields-single-line-container-m-right, 8px) +
|
|
107
|
-
var(--space-fields-single-line-container-m-gap, 4px) +
|
|
108
|
-
calc(var(var(--space-fields-postfix-gap, 4px)) + var(--size-icon-container-s, 24px) * 2)
|
|
102
|
+
var(--space-fields-single-line-container-m-right, 8px) + var(--space-fields-single-line-container-m-gap, 4px) + calc(var(var(--space-fields-postfix-gap, 4px)) + var(--size-icon-container-s, 24px) * 2)
|
|
109
103
|
);
|
|
110
104
|
padding-left:var(--space-fields-single-line-container-m-left, 8px);
|
|
111
105
|
border-radius:var(--radius-fields-m, 14px);
|
|
@@ -125,14 +119,10 @@
|
|
|
125
119
|
}
|
|
126
120
|
.container[data-size=l][data-variant=single-line-container] .displayValue{
|
|
127
121
|
width:calc(100% - (
|
|
128
|
-
var(--space-fields-single-line-container-l-right, 10px) +
|
|
129
|
-
var(--space-fields-single-line-container-l-gap, 8px) +
|
|
130
|
-
calc(var(var(--space-fields-postfix-gap, 4px)) + var(--size-icon-container-s, 24px) * 2)
|
|
122
|
+
var(--space-fields-single-line-container-l-right, 10px) + var(--space-fields-single-line-container-l-gap, 8px) + calc(var(var(--space-fields-postfix-gap, 4px)) + var(--size-icon-container-s, 24px) * 2)
|
|
131
123
|
));
|
|
132
124
|
margin-right:calc(
|
|
133
|
-
var(--space-fields-single-line-container-l-right, 10px) +
|
|
134
|
-
var(--space-fields-single-line-container-l-gap, 8px) +
|
|
135
|
-
calc(var(var(--space-fields-postfix-gap, 4px)) + var(--size-icon-container-s, 24px) * 2)
|
|
125
|
+
var(--space-fields-single-line-container-l-right, 10px) + var(--space-fields-single-line-container-l-gap, 8px) + calc(var(var(--space-fields-postfix-gap, 4px)) + var(--size-icon-container-s, 24px) * 2)
|
|
136
126
|
);
|
|
137
127
|
padding-left:var(--space-fields-single-line-container-l-left, 10px);
|
|
138
128
|
border-radius:var(--radius-fields-l, 16px);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ReactElement } from 'react';
|
|
2
2
|
import { InputPrivateProps } from '@snack-uikit/input-private';
|
|
3
|
-
import { AccordionItemProps, BaseItemProps,
|
|
3
|
+
import { AccordionItemProps, BaseItemProps, DroplistProps, GroupItemProps, NextListItemProps, SelectionMultipleState, SelectionSingleState } from '@snack-uikit/list';
|
|
4
4
|
import { TagProps } from '@snack-uikit/tag';
|
|
5
5
|
import { WithSupportProps } from '@snack-uikit/utils';
|
|
6
6
|
import { FieldDecoratorProps } from '../FieldDecorator';
|
|
@@ -51,14 +51,15 @@ type FiledSelectCommonProps = WithSupportProps<{
|
|
|
51
51
|
readonly?: boolean;
|
|
52
52
|
/** Иконка-префикс для поля */
|
|
53
53
|
prefixIcon?: ReactElement;
|
|
54
|
-
footer?:
|
|
54
|
+
footer?: DroplistProps['footer'];
|
|
55
|
+
widthStrategy?: DroplistProps['widthStrategy'];
|
|
55
56
|
search?: SearchState;
|
|
56
57
|
autocomplete?: boolean;
|
|
57
58
|
addOptionByEnter?: boolean;
|
|
58
59
|
open?: boolean;
|
|
59
60
|
onOpenChange?(open: boolean): void;
|
|
60
61
|
selectedOptionFormatter?: SelectedOptionFormatter;
|
|
61
|
-
}> & Pick<
|
|
62
|
+
}> & Pick<DroplistProps, 'dataError' | 'noDataState' | 'noResultsState' | 'errorDataState' | 'pinTop' | 'pinBottom' | 'dataFiltered'>;
|
|
62
63
|
export type FieldSelectSingleProps = FieldSelectPrivateProps & Omit<SelectionSingleState, 'mode'> & WrapperProps & FiledSelectCommonProps;
|
|
63
64
|
export type FieldSelectMultipleProps = FieldSelectPrivateProps & {
|
|
64
65
|
removeByBackspace?: boolean;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { DroplistProps } from '@snack-uikit/list';
|
|
2
2
|
import { FieldSelectProps } from '../types';
|
|
3
|
-
export declare function extractListProps({ dataError, noDataState, noResultsState, errorDataState, pinTop, pinBottom, dataFiltered, loading, }: Partial<FieldSelectProps>): Partial<DroplistProps>;
|
|
3
|
+
export declare function extractListProps({ dataError, noDataState, noResultsState, errorDataState, pinTop, pinBottom, dataFiltered, loading, footer, widthStrategy, }: Partial<FieldSelectProps>): Partial<DroplistProps>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export function extractListProps({ dataError, noDataState, noResultsState, errorDataState, pinTop, pinBottom, dataFiltered, loading, }) {
|
|
1
|
+
export function extractListProps({ dataError, noDataState, noResultsState, errorDataState, pinTop, pinBottom, dataFiltered, loading, footer, widthStrategy, }) {
|
|
2
2
|
return {
|
|
3
3
|
dataError,
|
|
4
4
|
noDataState,
|
|
@@ -8,6 +8,8 @@ export function extractListProps({ dataError, noDataState, noResultsState, error
|
|
|
8
8
|
pinBottom,
|
|
9
9
|
dataFiltered,
|
|
10
10
|
loading,
|
|
11
|
+
footer,
|
|
12
|
+
widthStrategy,
|
|
11
13
|
trigger: 'clickAndFocusVisible',
|
|
12
14
|
placement: 'bottom',
|
|
13
15
|
'data-test-id': 'field-select__list',
|
|
@@ -11,7 +11,7 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
11
11
|
};
|
|
12
12
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
13
13
|
import mergeRefs from 'merge-refs';
|
|
14
|
-
import { forwardRef, useEffect, useRef, useState } from 'react';
|
|
14
|
+
import { forwardRef, useCallback, useEffect, useMemo, useRef, useState, } from 'react';
|
|
15
15
|
import { InputPrivate, SIZE } from '@snack-uikit/input-private';
|
|
16
16
|
import { Slider } from '@snack-uikit/slider';
|
|
17
17
|
import { extractSupportProps } from '@snack-uikit/utils';
|
|
@@ -19,7 +19,7 @@ import { CONTAINER_VARIANT, VALIDATION_STATE } from '../../constants';
|
|
|
19
19
|
import { FieldContainerPrivate } from '../../helperComponents';
|
|
20
20
|
import { useValueControl } from '../../hooks';
|
|
21
21
|
import { FieldDecorator } from '../FieldDecorator';
|
|
22
|
-
import { generateAllowedValues, getClosestMark, getTextFieldValue } from './helpers';
|
|
22
|
+
import { generateAllowedValues, getClosestMark, getTextFieldValue, isMarkObject } from './helpers';
|
|
23
23
|
import styles from './styles.module.css';
|
|
24
24
|
const getDefaultValue = (range, min, max, value) => {
|
|
25
25
|
if (range) {
|
|
@@ -32,6 +32,14 @@ const getDefaultValue = (range, min, max, value) => {
|
|
|
32
32
|
};
|
|
33
33
|
export const FieldSlider = forwardRef((_a, ref) => {
|
|
34
34
|
var { id, name, min, max, step, marks, showScaleBar = true, value: valueProp, range = false, disabled = false, readonly = false, onChange: onChangeProp, onFocus, onBlur, className, label, labelTooltip, labelTooltipPlacement, required, hint, showHintIcon, size = SIZE.S, postfixIcon, textInputFormatter } = _a, rest = __rest(_a, ["id", "name", "min", "max", "step", "marks", "showScaleBar", "value", "range", "disabled", "readonly", "onChange", "onFocus", "onBlur", "className", "label", "labelTooltip", "labelTooltipPlacement", "required", "hint", "showHintIcon", "size", "postfixIcon", "textInputFormatter"]);
|
|
35
|
+
const getMarkValue = useCallback((key) => {
|
|
36
|
+
const mark = marks[key];
|
|
37
|
+
if (isMarkObject(mark)) {
|
|
38
|
+
return mark.label;
|
|
39
|
+
}
|
|
40
|
+
return mark;
|
|
41
|
+
}, [marks]);
|
|
42
|
+
const hasMarksEqualToValues = useMemo(() => Object.keys(marks).every(key => key === getMarkValue(key)), [getMarkValue, marks]);
|
|
35
43
|
const [value = getDefaultValue(range, min, max, valueProp), onChange] = useValueControl({
|
|
36
44
|
value: valueProp,
|
|
37
45
|
defaultValue: getDefaultValue(range, min, max, valueProp),
|
|
@@ -40,46 +48,82 @@ export const FieldSlider = forwardRef((_a, ref) => {
|
|
|
40
48
|
const [textFieldInputValue, setTextFieldInputValue] = useState(getTextFieldValue(value, textInputFormatter));
|
|
41
49
|
const localRef = useRef(null);
|
|
42
50
|
const onTextFieldChange = (textFieldValue) => {
|
|
43
|
-
const numValue =
|
|
44
|
-
if (Number.isNaN(numValue)) {
|
|
51
|
+
const numValue = parseInt(textFieldValue);
|
|
52
|
+
if (textFieldValue && Number.isNaN(numValue)) {
|
|
45
53
|
return;
|
|
46
54
|
}
|
|
47
55
|
setTextFieldInputValue(textFieldValue);
|
|
48
56
|
};
|
|
49
|
-
const
|
|
50
|
-
const
|
|
51
|
-
|
|
57
|
+
const handleNonEqualMarksSliderChange = (textFieldNumValue) => {
|
|
58
|
+
const handleChange = (key) => {
|
|
59
|
+
setTextFieldInputValue(String(getMarkValue(key)));
|
|
60
|
+
onChange(Number(key));
|
|
61
|
+
};
|
|
62
|
+
const allowedValues = Object.keys(marks).map(key => ({
|
|
63
|
+
key,
|
|
64
|
+
value: parseInt(String(getMarkValue(key))),
|
|
65
|
+
}));
|
|
66
|
+
const suitableEntry = allowedValues.find(entry => entry.value === textFieldNumValue);
|
|
67
|
+
if (suitableEntry) {
|
|
68
|
+
handleChange(suitableEntry.key);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const actualMin = parseInt(String(getMarkValue(min)));
|
|
72
|
+
const actualMax = parseInt(String(getMarkValue(max)));
|
|
73
|
+
if (textFieldNumValue < actualMin) {
|
|
74
|
+
handleChange(min);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (textFieldNumValue > actualMax) {
|
|
78
|
+
handleChange(max);
|
|
52
79
|
return;
|
|
53
80
|
}
|
|
81
|
+
const { mark } = getClosestMark(textFieldNumValue, allowedValues, mark => mark.value);
|
|
82
|
+
handleChange(mark.key);
|
|
83
|
+
};
|
|
84
|
+
const handleEqualMarksSliderChange = (textFieldNumValue) => {
|
|
85
|
+
const handleChange = (value) => {
|
|
86
|
+
setTextFieldInputValue(String(value));
|
|
87
|
+
onChange(value);
|
|
88
|
+
};
|
|
54
89
|
if (textFieldNumValue < min) {
|
|
55
|
-
|
|
56
|
-
onChange(min);
|
|
90
|
+
handleChange(min);
|
|
57
91
|
return;
|
|
58
92
|
}
|
|
59
93
|
if (textFieldNumValue > max) {
|
|
60
|
-
|
|
61
|
-
onChange(max);
|
|
94
|
+
handleChange(max);
|
|
62
95
|
return;
|
|
63
96
|
}
|
|
64
97
|
if (step === null) {
|
|
65
98
|
const allowedValues = Object.keys(marks).map(Number);
|
|
66
99
|
if (allowedValues.includes(textFieldNumValue)) {
|
|
67
|
-
|
|
100
|
+
setTextFieldInputValue(String(textFieldNumValue));
|
|
101
|
+
handleChange(textFieldNumValue);
|
|
68
102
|
return;
|
|
69
103
|
}
|
|
70
|
-
const { mark } = getClosestMark(textFieldNumValue, allowedValues);
|
|
71
|
-
|
|
72
|
-
onChange(mark);
|
|
104
|
+
const { mark } = getClosestMark(textFieldNumValue, allowedValues, mark => mark);
|
|
105
|
+
handleChange(mark);
|
|
73
106
|
return;
|
|
74
107
|
}
|
|
75
108
|
const allowedValues = generateAllowedValues(min, max, step);
|
|
76
109
|
if (allowedValues.includes(textFieldNumValue)) {
|
|
77
|
-
|
|
110
|
+
handleChange(textFieldNumValue);
|
|
78
111
|
return;
|
|
79
112
|
}
|
|
80
|
-
const { mark } = getClosestMark(textFieldNumValue, allowedValues);
|
|
81
|
-
|
|
82
|
-
|
|
113
|
+
const { mark } = getClosestMark(textFieldNumValue, allowedValues, mark => mark);
|
|
114
|
+
handleChange(mark);
|
|
115
|
+
};
|
|
116
|
+
const handleTextValueChange = () => {
|
|
117
|
+
const textFieldNumValue = parseInt(textFieldInputValue);
|
|
118
|
+
if (Number.isNaN(textFieldNumValue)) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (hasMarksEqualToValues) {
|
|
122
|
+
handleEqualMarksSliderChange(textFieldNumValue);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
handleNonEqualMarksSliderChange(textFieldNumValue);
|
|
126
|
+
}
|
|
83
127
|
};
|
|
84
128
|
const onTextFieldBlur = (e) => {
|
|
85
129
|
onBlur === null || onBlur === void 0 ? void 0 : onBlur(e);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const getDiff = (value, mark) => Math.abs(mark - value);
|
|
2
|
-
export const getClosestMark = (value, marks) => marks.reduce((accResult, mark) => {
|
|
3
|
-
const diff = getDiff(value, mark);
|
|
2
|
+
export const getClosestMark = (value, marks, getMarkValue) => marks.reduce((accResult, mark) => {
|
|
3
|
+
const diff = getDiff(value, getMarkValue(mark));
|
|
4
4
|
if (diff < accResult.lowestDiff) {
|
|
5
5
|
return {
|
|
6
6
|
lowestDiff: diff,
|
|
@@ -9,6 +9,6 @@ export const getClosestMark = (value, marks) => marks.reduce((accResult, mark) =
|
|
|
9
9
|
}
|
|
10
10
|
return accResult;
|
|
11
11
|
}, {
|
|
12
|
-
lowestDiff: getDiff(value, marks[0]),
|
|
12
|
+
lowestDiff: getDiff(value, getMarkValue(marks[0])),
|
|
13
13
|
mark: marks[0],
|
|
14
14
|
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { SliderProps as SliderComponentProps } from '@snack-uikit/slider';
|
|
3
|
+
type Marks = NonNullable<SliderComponentProps['marks']>;
|
|
4
|
+
type MarkObject = {
|
|
5
|
+
label: ReactNode;
|
|
6
|
+
};
|
|
7
|
+
export declare function isMarkObject(mark: Marks[keyof Marks]): mark is MarkObject;
|
|
8
|
+
export {};
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
6
|
"title": "Fields",
|
|
7
|
-
"version": "0.18.
|
|
7
|
+
"version": "0.18.4-preview-5d3667ec.0",
|
|
8
8
|
"sideEffects": [
|
|
9
9
|
"*.css",
|
|
10
10
|
"*.woff",
|
|
@@ -34,13 +34,13 @@
|
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@snack-uikit/button": "0.17.0",
|
|
36
36
|
"@snack-uikit/calendar": "0.7.6",
|
|
37
|
-
"@snack-uikit/droplist": "0.13.
|
|
37
|
+
"@snack-uikit/droplist": "0.13.15",
|
|
38
38
|
"@snack-uikit/icons": "0.20.1",
|
|
39
39
|
"@snack-uikit/input-private": "3.1.1",
|
|
40
|
-
"@snack-uikit/list": "0.11.0",
|
|
40
|
+
"@snack-uikit/list": "0.11.1-preview-5d3667ec.0",
|
|
41
41
|
"@snack-uikit/scroll": "0.5.2",
|
|
42
|
-
"@snack-uikit/slider": "0.1.
|
|
43
|
-
"@snack-uikit/tag": "0.
|
|
42
|
+
"@snack-uikit/slider": "0.1.8-preview-5d3667ec.0",
|
|
43
|
+
"@snack-uikit/tag": "0.9.0",
|
|
44
44
|
"@snack-uikit/tooltip": "0.13.1",
|
|
45
45
|
"@snack-uikit/truncate-string": "0.4.12",
|
|
46
46
|
"@snack-uikit/utils": "3.2.0",
|
|
@@ -56,5 +56,5 @@
|
|
|
56
56
|
"peerDependencies": {
|
|
57
57
|
"@snack-uikit/locale": "*"
|
|
58
58
|
},
|
|
59
|
-
"gitHead": "
|
|
59
|
+
"gitHead": "311bf31807d9b8cc975e80caf14ec35108256337"
|
|
60
60
|
}
|
|
@@ -18,8 +18,12 @@ $base-min-width: 4px;
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
.contentWrapper {
|
|
21
|
+
position: relative;
|
|
22
|
+
|
|
23
|
+
overflow: hidden;
|
|
21
24
|
display: flex;
|
|
22
25
|
flex-wrap: wrap;
|
|
26
|
+
|
|
23
27
|
width: 100%;
|
|
24
28
|
}
|
|
25
29
|
|
|
@@ -59,9 +63,7 @@ $base-min-width: 4px;
|
|
|
59
63
|
$button-width: simple-var($icons-sizes, $size);
|
|
60
64
|
$postfix-width: calc(var(#{$space-fields-postfix-gap}) + $button-width * 2);
|
|
61
65
|
$margin-right: calc(
|
|
62
|
-
#{simple-var($fields, $containerVariant, $size, 'padding-right')} +
|
|
63
|
-
#{simple-var($fields, $containerVariant, $size, 'gap')} +
|
|
64
|
-
#{$postfix-width}
|
|
66
|
+
#{simple-var($fields, $containerVariant, $size, 'padding-right')} + #{simple-var($fields, $containerVariant, $size, 'gap')} + #{$postfix-width}
|
|
65
67
|
);
|
|
66
68
|
|
|
67
69
|
width: calc(100% - $margin-right);
|
|
@@ -4,8 +4,8 @@ import { InputPrivateProps } from '@snack-uikit/input-private';
|
|
|
4
4
|
import {
|
|
5
5
|
AccordionItemProps,
|
|
6
6
|
BaseItemProps,
|
|
7
|
+
DroplistProps,
|
|
7
8
|
GroupItemProps,
|
|
8
|
-
ListProps,
|
|
9
9
|
NextListItemProps,
|
|
10
10
|
SelectionMultipleState,
|
|
11
11
|
SelectionSingleState,
|
|
@@ -82,7 +82,9 @@ type FiledSelectCommonProps = WithSupportProps<{
|
|
|
82
82
|
/** Иконка-префикс для поля */
|
|
83
83
|
prefixIcon?: ReactElement;
|
|
84
84
|
|
|
85
|
-
footer?:
|
|
85
|
+
footer?: DroplistProps['footer'];
|
|
86
|
+
|
|
87
|
+
widthStrategy?: DroplistProps['widthStrategy'];
|
|
86
88
|
|
|
87
89
|
search?: SearchState;
|
|
88
90
|
|
|
@@ -97,7 +99,7 @@ type FiledSelectCommonProps = WithSupportProps<{
|
|
|
97
99
|
selectedOptionFormatter?: SelectedOptionFormatter;
|
|
98
100
|
}> &
|
|
99
101
|
Pick<
|
|
100
|
-
|
|
102
|
+
DroplistProps,
|
|
101
103
|
'dataError' | 'noDataState' | 'noResultsState' | 'errorDataState' | 'pinTop' | 'pinBottom' | 'dataFiltered'
|
|
102
104
|
>;
|
|
103
105
|
|
|
@@ -11,6 +11,8 @@ export function extractListProps({
|
|
|
11
11
|
pinBottom,
|
|
12
12
|
dataFiltered,
|
|
13
13
|
loading,
|
|
14
|
+
footer,
|
|
15
|
+
widthStrategy,
|
|
14
16
|
}: Partial<FieldSelectProps>): Partial<DroplistProps> {
|
|
15
17
|
return {
|
|
16
18
|
dataError,
|
|
@@ -21,6 +23,8 @@ export function extractListProps({
|
|
|
21
23
|
pinBottom,
|
|
22
24
|
dataFiltered,
|
|
23
25
|
loading,
|
|
26
|
+
footer,
|
|
27
|
+
widthStrategy,
|
|
24
28
|
trigger: 'clickAndFocusVisible',
|
|
25
29
|
placement: 'bottom',
|
|
26
30
|
'data-test-id': 'field-select__list',
|
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import mergeRefs from 'merge-refs';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
FocusEvent,
|
|
4
|
+
forwardRef,
|
|
5
|
+
KeyboardEvent,
|
|
6
|
+
ReactElement,
|
|
7
|
+
useCallback,
|
|
8
|
+
useEffect,
|
|
9
|
+
useMemo,
|
|
10
|
+
useRef,
|
|
11
|
+
useState,
|
|
12
|
+
} from 'react';
|
|
3
13
|
|
|
4
14
|
import { InputPrivate, InputPrivateProps, SIZE } from '@snack-uikit/input-private';
|
|
5
15
|
import { Slider, SliderProps as SliderComponentProps } from '@snack-uikit/slider';
|
|
@@ -9,7 +19,7 @@ import { CONTAINER_VARIANT, VALIDATION_STATE } from '../../constants';
|
|
|
9
19
|
import { FieldContainerPrivate } from '../../helperComponents';
|
|
10
20
|
import { useValueControl } from '../../hooks';
|
|
11
21
|
import { FieldDecorator, FieldDecoratorProps } from '../FieldDecorator';
|
|
12
|
-
import { generateAllowedValues, getClosestMark, getTextFieldValue } from './helpers';
|
|
22
|
+
import { generateAllowedValues, getClosestMark, getTextFieldValue, isMarkObject } from './helpers';
|
|
13
23
|
import styles from './styles.module.scss';
|
|
14
24
|
import { TextInputFormatter } from './types';
|
|
15
25
|
|
|
@@ -81,6 +91,24 @@ export const FieldSlider = forwardRef<HTMLInputElement, FieldSliderProps>(
|
|
|
81
91
|
},
|
|
82
92
|
ref,
|
|
83
93
|
) => {
|
|
94
|
+
const getMarkValue = useCallback(
|
|
95
|
+
(key: keyof SliderProps['marks']) => {
|
|
96
|
+
const mark = marks[key];
|
|
97
|
+
|
|
98
|
+
if (isMarkObject(mark)) {
|
|
99
|
+
return mark.label;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return mark;
|
|
103
|
+
},
|
|
104
|
+
[marks],
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
const hasMarksEqualToValues = useMemo(
|
|
108
|
+
() => Object.keys(marks).every(key => key === getMarkValue(key)),
|
|
109
|
+
[getMarkValue, marks],
|
|
110
|
+
);
|
|
111
|
+
|
|
84
112
|
const [value = getDefaultValue(range, min, max, valueProp), onChange] = useValueControl<number | number[]>({
|
|
85
113
|
value: valueProp,
|
|
86
114
|
defaultValue: getDefaultValue(range, min, max, valueProp),
|
|
@@ -93,54 +121,100 @@ export const FieldSlider = forwardRef<HTMLInputElement, FieldSliderProps>(
|
|
|
93
121
|
const localRef = useRef<HTMLInputElement>(null);
|
|
94
122
|
|
|
95
123
|
const onTextFieldChange = (textFieldValue: string) => {
|
|
96
|
-
const numValue =
|
|
97
|
-
|
|
124
|
+
const numValue = parseInt(textFieldValue);
|
|
125
|
+
|
|
126
|
+
if (textFieldValue && Number.isNaN(numValue)) {
|
|
98
127
|
return;
|
|
99
128
|
}
|
|
100
129
|
|
|
101
130
|
setTextFieldInputValue(textFieldValue);
|
|
102
131
|
};
|
|
103
132
|
|
|
104
|
-
const
|
|
105
|
-
const
|
|
106
|
-
|
|
133
|
+
const handleNonEqualMarksSliderChange = (textFieldNumValue: number) => {
|
|
134
|
+
const handleChange = (key: string | number) => {
|
|
135
|
+
setTextFieldInputValue(String(getMarkValue(key)));
|
|
136
|
+
onChange(Number(key));
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const allowedValues = Object.keys(marks).map(key => ({
|
|
140
|
+
key,
|
|
141
|
+
value: parseInt(String(getMarkValue(key))),
|
|
142
|
+
}));
|
|
143
|
+
const suitableEntry = allowedValues.find(entry => entry.value === textFieldNumValue);
|
|
144
|
+
|
|
145
|
+
if (suitableEntry) {
|
|
146
|
+
handleChange(suitableEntry.key);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const actualMin = parseInt(String(getMarkValue(min)));
|
|
151
|
+
const actualMax = parseInt(String(getMarkValue(max)));
|
|
152
|
+
|
|
153
|
+
if (textFieldNumValue < actualMin) {
|
|
154
|
+
handleChange(min);
|
|
107
155
|
return;
|
|
108
156
|
}
|
|
109
157
|
|
|
158
|
+
if (textFieldNumValue > actualMax) {
|
|
159
|
+
handleChange(max);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const { mark } = getClosestMark(textFieldNumValue, allowedValues, mark => mark.value);
|
|
164
|
+
handleChange(mark.key);
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const handleEqualMarksSliderChange = (textFieldNumValue: number) => {
|
|
168
|
+
const handleChange = (value: number) => {
|
|
169
|
+
setTextFieldInputValue(String(value));
|
|
170
|
+
onChange(value);
|
|
171
|
+
};
|
|
172
|
+
|
|
110
173
|
if (textFieldNumValue < min) {
|
|
111
|
-
|
|
112
|
-
onChange(min);
|
|
174
|
+
handleChange(min);
|
|
113
175
|
return;
|
|
114
176
|
}
|
|
115
177
|
|
|
116
178
|
if (textFieldNumValue > max) {
|
|
117
|
-
|
|
118
|
-
onChange(max);
|
|
179
|
+
handleChange(max);
|
|
119
180
|
return;
|
|
120
181
|
}
|
|
121
182
|
|
|
122
183
|
if (step === null) {
|
|
123
184
|
const allowedValues = Object.keys(marks).map(Number);
|
|
124
185
|
if (allowedValues.includes(textFieldNumValue)) {
|
|
125
|
-
|
|
186
|
+
setTextFieldInputValue(String(textFieldNumValue));
|
|
187
|
+
handleChange(textFieldNumValue);
|
|
126
188
|
return;
|
|
127
189
|
}
|
|
128
190
|
|
|
129
|
-
const { mark } = getClosestMark(textFieldNumValue, allowedValues);
|
|
130
|
-
|
|
131
|
-
onChange(mark);
|
|
191
|
+
const { mark } = getClosestMark(textFieldNumValue, allowedValues, mark => mark);
|
|
192
|
+
handleChange(mark);
|
|
132
193
|
return;
|
|
133
194
|
}
|
|
134
195
|
|
|
135
196
|
const allowedValues = generateAllowedValues(min, max, step);
|
|
136
197
|
if (allowedValues.includes(textFieldNumValue)) {
|
|
137
|
-
|
|
198
|
+
handleChange(textFieldNumValue);
|
|
138
199
|
return;
|
|
139
200
|
}
|
|
140
201
|
|
|
141
|
-
const { mark } = getClosestMark(textFieldNumValue, allowedValues);
|
|
142
|
-
|
|
143
|
-
|
|
202
|
+
const { mark } = getClosestMark(textFieldNumValue, allowedValues, mark => mark);
|
|
203
|
+
handleChange(mark);
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const handleTextValueChange = () => {
|
|
207
|
+
const textFieldNumValue = parseInt(textFieldInputValue);
|
|
208
|
+
|
|
209
|
+
if (Number.isNaN(textFieldNumValue)) {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (hasMarksEqualToValues) {
|
|
214
|
+
handleEqualMarksSliderChange(textFieldNumValue);
|
|
215
|
+
} else {
|
|
216
|
+
handleNonEqualMarksSliderChange(textFieldNumValue);
|
|
217
|
+
}
|
|
144
218
|
};
|
|
145
219
|
|
|
146
220
|
const onTextFieldBlur = (e: FocusEvent<HTMLInputElement, Element>) => {
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
const getDiff = (value: number, mark: number): number => Math.abs(mark - value);
|
|
2
2
|
|
|
3
|
-
export const getClosestMark = (
|
|
3
|
+
export const getClosestMark = <T>(
|
|
4
|
+
value: number,
|
|
5
|
+
marks: T[],
|
|
6
|
+
getMarkValue: (value: T) => number,
|
|
7
|
+
): { lowestDiff: number; mark: T } =>
|
|
4
8
|
marks.reduce(
|
|
5
9
|
(accResult, mark) => {
|
|
6
|
-
const diff = getDiff(value, mark);
|
|
10
|
+
const diff = getDiff(value, getMarkValue(mark));
|
|
7
11
|
if (diff < accResult.lowestDiff) {
|
|
8
12
|
return {
|
|
9
13
|
lowestDiff: diff,
|
|
@@ -14,7 +18,7 @@ export const getClosestMark = (value: number, marks: number[]): { lowestDiff: nu
|
|
|
14
18
|
return accResult;
|
|
15
19
|
},
|
|
16
20
|
{
|
|
17
|
-
lowestDiff: getDiff(value, marks[0]),
|
|
21
|
+
lowestDiff: getDiff(value, getMarkValue(marks[0])),
|
|
18
22
|
mark: marks[0],
|
|
19
23
|
},
|
|
20
24
|
);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
import { SliderProps as SliderComponentProps } from '@snack-uikit/slider';
|
|
4
|
+
|
|
5
|
+
type Marks = NonNullable<SliderComponentProps['marks']>;
|
|
6
|
+
|
|
7
|
+
type MarkObject = {
|
|
8
|
+
label: ReactNode;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export function isMarkObject(mark: Marks[keyof Marks]): mark is MarkObject {
|
|
12
|
+
return Boolean(mark && typeof mark === 'object' && 'label' in mark);
|
|
13
|
+
}
|