@snack-uikit/fields 0.18.3 → 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.
@@ -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 = Number(textFieldValue);
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 handleTextValueChange = () => {
50
- const textFieldNumValue = Number(textFieldInputValue);
51
- if (Number.isNaN(textFieldNumValue)) {
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
- setTextFieldInputValue(String(min));
56
- onChange(min);
90
+ handleChange(min);
57
91
  return;
58
92
  }
59
93
  if (textFieldNumValue > max) {
60
- setTextFieldInputValue(String(max));
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
- onChange(textFieldNumValue);
100
+ setTextFieldInputValue(String(textFieldNumValue));
101
+ handleChange(textFieldNumValue);
68
102
  return;
69
103
  }
70
- const { mark } = getClosestMark(textFieldNumValue, allowedValues);
71
- setTextFieldInputValue(String(mark));
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
- onChange(textFieldNumValue);
110
+ handleChange(textFieldNumValue);
78
111
  return;
79
112
  }
80
- const { mark } = getClosestMark(textFieldNumValue, allowedValues);
81
- setTextFieldInputValue(String(mark));
82
- onChange(mark);
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,4 +1,4 @@
1
- export declare const getClosestMark: (value: number, marks: number[]) => {
1
+ export declare const getClosestMark: <T>(value: number, marks: T[], getMarkValue: (value: T) => number) => {
2
2
  lowestDiff: number;
3
- mark: number;
3
+ mark: T;
4
4
  };
@@ -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
  });
@@ -1,3 +1,4 @@
1
1
  export * from './getTextFieldValue';
2
2
  export * from './getClosestMark';
3
3
  export * from './generateAllowedValues';
4
+ export * from './isMarkObject';
@@ -1,3 +1,4 @@
1
1
  export * from './getTextFieldValue';
2
2
  export * from './getClosestMark';
3
3
  export * from './generateAllowedValues';
4
+ export * from './isMarkObject';
@@ -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 {};
@@ -0,0 +1,3 @@
1
+ export function isMarkObject(mark) {
2
+ return Boolean(mark && typeof mark === 'object' && 'label' in mark);
3
+ }
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "access": "public"
5
5
  },
6
6
  "title": "Fields",
7
- "version": "0.18.3",
7
+ "version": "0.18.4-preview-5d3667ec.0",
8
8
  "sideEffects": [
9
9
  "*.css",
10
10
  "*.woff",
@@ -37,9 +37,9 @@
37
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.7",
42
+ "@snack-uikit/slider": "0.1.8-preview-5d3667ec.0",
43
43
  "@snack-uikit/tag": "0.9.0",
44
44
  "@snack-uikit/tooltip": "0.13.1",
45
45
  "@snack-uikit/truncate-string": "0.4.12",
@@ -56,5 +56,5 @@
56
56
  "peerDependencies": {
57
57
  "@snack-uikit/locale": "*"
58
58
  },
59
- "gitHead": "f6b48b7373bda024626822d7c17b05d6ddbf20b2"
59
+ "gitHead": "311bf31807d9b8cc975e80caf14ec35108256337"
60
60
  }
@@ -1,5 +1,15 @@
1
1
  import mergeRefs from 'merge-refs';
2
- import { FocusEvent, forwardRef, KeyboardEvent, ReactElement, useEffect, useRef, useState } from 'react';
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 = Number(textFieldValue);
97
- if (Number.isNaN(numValue)) {
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 handleTextValueChange = () => {
105
- const textFieldNumValue = Number(textFieldInputValue);
106
- if (Number.isNaN(textFieldNumValue)) {
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
- setTextFieldInputValue(String(min));
112
- onChange(min);
174
+ handleChange(min);
113
175
  return;
114
176
  }
115
177
 
116
178
  if (textFieldNumValue > max) {
117
- setTextFieldInputValue(String(max));
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
- onChange(textFieldNumValue);
186
+ setTextFieldInputValue(String(textFieldNumValue));
187
+ handleChange(textFieldNumValue);
126
188
  return;
127
189
  }
128
190
 
129
- const { mark } = getClosestMark(textFieldNumValue, allowedValues);
130
- setTextFieldInputValue(String(mark));
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
- onChange(textFieldNumValue);
198
+ handleChange(textFieldNumValue);
138
199
  return;
139
200
  }
140
201
 
141
- const { mark } = getClosestMark(textFieldNumValue, allowedValues);
142
- setTextFieldInputValue(String(mark));
143
- onChange(mark);
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 = (value: number, marks: number[]): { lowestDiff: number; mark: number } =>
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
  );
@@ -1,3 +1,4 @@
1
1
  export * from './getTextFieldValue';
2
2
  export * from './getClosestMark';
3
3
  export * from './generateAllowedValues';
4
+ export * from './isMarkObject';
@@ -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
+ }