@gravity-ui/dynamic-forms 5.14.0 → 5.16.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 (26) hide show
  1. package/build/cjs/lib/kit/components/Inputs/TextContent/TextContent.css +3 -1
  2. package/build/cjs/lib/kit/components/Inputs/TextContent/TextContent.js +8 -3
  3. package/build/cjs/lib/kit/components/Inputs/TextContent/utils.js +5 -1
  4. package/build/cjs/lib/kit/components/Views/TextContentView.js +4 -3
  5. package/build/cjs/lib/kit/constants/config.js +1 -0
  6. package/build/cjs/lib/kit/i18n/en.json +2 -0
  7. package/build/cjs/lib/kit/i18n/ru.json +2 -0
  8. package/build/cjs/lib/kit/validators/helpers.js +70 -1
  9. package/build/cjs/lib/kit/validators/messages.js +6 -0
  10. package/build/cjs/lib/kit/validators/validators.js +68 -1
  11. package/build/esm/lib/core/types/specs.d.ts +5 -0
  12. package/build/esm/lib/kit/components/Inputs/TextContent/TextContent.css +3 -1
  13. package/build/esm/lib/kit/components/Inputs/TextContent/TextContent.js +9 -4
  14. package/build/esm/lib/kit/components/Inputs/TextContent/utils.d.ts +2 -0
  15. package/build/esm/lib/kit/components/Inputs/TextContent/utils.js +3 -0
  16. package/build/esm/lib/kit/components/Views/TextContentView.js +4 -3
  17. package/build/esm/lib/kit/constants/config.js +2 -1
  18. package/build/esm/lib/kit/i18n/en.json +2 -0
  19. package/build/esm/lib/kit/i18n/ru.json +2 -0
  20. package/build/esm/lib/kit/validators/helpers.d.ts +5 -0
  21. package/build/esm/lib/kit/validators/helpers.js +68 -0
  22. package/build/esm/lib/kit/validators/messages.js +6 -0
  23. package/build/esm/lib/kit/validators/types.d.ts +2 -0
  24. package/build/esm/lib/kit/validators/validators.d.ts +4 -1
  25. package/build/esm/lib/kit/validators/validators.js +67 -1
  26. package/package.json +1 -1
@@ -26,9 +26,11 @@
26
26
  white-space: initial;
27
27
  line-height: var(--_--height);
28
28
  }
29
- .df-text-content__icon {
29
+ .df-text-content__icon, .df-text-content__alert-icon {
30
30
  display: flex;
31
31
  align-items: center;
32
+ }
33
+ .df-text-content__icon {
32
34
  margin-right: var(--df-text-content-icon-margin-right, var(--g-spacing-1));
33
35
  }
34
36
  .df-text-content__wrapper {
@@ -14,18 +14,23 @@ const TextContentComponent = ({ spec, value, Layout, }) => {
14
14
  var _a;
15
15
  const { textContentParams, layoutDescription } = spec.viewSpec;
16
16
  const text = react_1.default.useMemo(() => ((textContentParams === null || textContentParams === void 0 ? void 0 : textContentParams.text) ? textContentParams === null || textContentParams === void 0 ? void 0 : textContentParams.text : layoutDescription), [layoutDescription, textContentParams === null || textContentParams === void 0 ? void 0 : textContentParams.text]);
17
+ const iconName = textContentParams === null || textContentParams === void 0 ? void 0 : textContentParams.icon;
18
+ const alertView = textContentParams === null || textContentParams === void 0 ? void 0 : textContentParams.viewAlert;
19
+ const hasIconParam = typeof iconName === 'string';
20
+ const IconComponent = react_1.default.useMemo(() => (iconName ? (0, utils_2.loadIcon)(iconName) : undefined), [iconName]);
21
+ const iconLib = react_1.default.useMemo(() => (IconComponent ? react_1.default.createElement(LazyLoader_1.LazyLoader, { component: IconComponent }) : null), [IconComponent]);
22
+ const alertIcon = react_1.default.useMemo(() => (iconLib ? react_1.default.createElement("span", { className: b('alert-icon') }, iconLib) : null), [iconLib]);
17
23
  if (!text) {
18
24
  return null;
19
25
  }
20
- const iconLib = (textContentParams === null || textContentParams === void 0 ? void 0 : textContentParams.icon) ? (react_1.default.createElement(LazyLoader_1.LazyLoader, { component: (0, utils_2.loadIcon)(textContentParams === null || textContentParams === void 0 ? void 0 : textContentParams.icon) })) : undefined;
21
26
  let content = react_1.default.createElement("span", { dangerouslySetInnerHTML: { __html: text } });
22
27
  if (textContentParams === null || textContentParams === void 0 ? void 0 : textContentParams.themeAlert) {
23
28
  const titleAlert = (textContentParams === null || textContentParams === void 0 ? void 0 : textContentParams.titleAlert) || !(0, lodash_1.isEmpty)(textContentParams === null || textContentParams === void 0 ? void 0 : textContentParams.titleAlert)
24
29
  ? textContentParams.titleAlert
25
30
  : undefined;
26
- content = (react_1.default.createElement(uikit_1.Alert, { icon: iconLib, message: content,
31
+ content = (react_1.default.createElement(uikit_1.Alert, Object.assign({}, (alertIcon || hasIconParam ? { icon: alertIcon } : {}), { message: content,
27
32
  // If the title is an empty line, then you need to explicitly write undefined, otherwise there will be an additional indent
28
- title: titleAlert, theme: textContentParams === null || textContentParams === void 0 ? void 0 : textContentParams.themeAlert, view: textContentParams === null || textContentParams === void 0 ? void 0 : textContentParams.viewAlert }));
33
+ title: titleAlert, theme: textContentParams === null || textContentParams === void 0 ? void 0 : textContentParams.themeAlert, view: (0, utils_2.isAlertView)(alertView) ? alertView : undefined })));
29
34
  }
30
35
  else if (textContentParams === null || textContentParams === void 0 ? void 0 : textContentParams.themeLabel) {
31
36
  content = (react_1.default.createElement(uikit_1.Label, { size: "m", theme: textContentParams.themeLabel, className: b(), value: value, icon: iconLib }, content));
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.loadIcon = void 0;
3
+ exports.isAlertView = exports.loadIcon = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const react_1 = tslib_1.__importDefault(require("react"));
6
6
  const loadIcon = (name) => {
@@ -20,3 +20,7 @@ const loadIcon = (name) => {
20
20
  return Icon;
21
21
  };
22
22
  exports.loadIcon = loadIcon;
23
+ const isAlertView = (view) => {
24
+ return view === 'filled' || view === 'outlined';
25
+ };
26
+ exports.isAlertView = isAlertView;
@@ -5,16 +5,17 @@ const tslib_1 = require("tslib");
5
5
  const react_1 = tslib_1.__importDefault(require("react"));
6
6
  const Inputs_1 = require("../Inputs");
7
7
  const TextContentView = ({ name, spec, Layout, value }) => {
8
+ var _a;
9
+ const layoutValue = ((_a = spec.viewSpec.textContentParams) === null || _a === void 0 ? void 0 : _a.text) || spec.viewSpec.layoutDescription || value;
8
10
  const WrappedLayout = react_1.default.useMemo(() => {
9
11
  if (Layout) {
10
12
  const Component = (props) => {
11
- const VALUE_STUB = 'if u see this, please create issue about it here: https://github.com/gravity-ui/dynamic-forms/issues/new';
12
- return react_1.default.createElement(Layout, Object.assign({ name: name, value: VALUE_STUB }, props));
13
+ return react_1.default.createElement(Layout, Object.assign({ name: name, value: layoutValue }, props));
13
14
  };
14
15
  return Component;
15
16
  }
16
17
  return undefined;
17
- }, [Layout, name]);
18
+ }, [Layout, layoutValue, name]);
18
19
  return react_1.default.createElement(Inputs_1.TextContentComponent, { spec: spec, value: value, Layout: WrappedLayout });
19
20
  };
20
21
  exports.TextContentView = TextContentView;
@@ -123,6 +123,7 @@ exports.dynamicConfig = {
123
123
  validators: {
124
124
  base: (0, validators_1.getStringValidator)(),
125
125
  number: (0, validators_1.getNumberValidator)(),
126
+ number_with_scale: (0, validators_1.getNumberWithScaleValidator)(),
126
127
  },
127
128
  },
128
129
  };
@@ -37,6 +37,8 @@
37
37
  ],
38
38
  "label_error-min-number": "Value must be an integer no less than {{count}}",
39
39
  "label_error-max-number": "Value must not be greater than {{count}}",
40
+ "label_error-min-number-with-scale": "Value must be no less than {{count}} {{scale}}",
41
+ "label_error-max-number-with-scale": "Value must not be greater than {{count}} {{scale}}",
40
42
  "label_error-space-end": "Value must not end with a space",
41
43
  "label_error-space-start": "Value must not start with a space",
42
44
  "label_error-zero-start": "Value must not start with a zero",
@@ -37,6 +37,8 @@
37
37
  ],
38
38
  "label_error-min-number": "Значение должно быть числом не меньше {{count}}",
39
39
  "label_error-max-number": "Значение должно быть числом не больше {{count}}",
40
+ "label_error-min-number-with-scale": "Значение должно быть не меньше {{count}} {{scale}}",
41
+ "label_error-max-number-with-scale": "Значение должно быть не больше {{count}} {{scale}}",
40
42
  "label_error-space-end": "Значение не должно заканчиваться пробелом",
41
43
  "label_error-space-start": "Значение не должно начинаться с пробела",
42
44
  "label_error-zero-start": "Значение не должно начинаться с нуля",
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isFloat = exports.isInt = void 0;
3
+ exports.getScaledLimit = exports.isFloat = exports.isInt = void 0;
4
+ const utils_1 = require("../utils");
4
5
  const isInt = (value) => {
5
6
  const regex = /^(?:[-+]?(?:0|[1-9]\d*))$/;
6
7
  return regex.test(value);
@@ -11,3 +12,71 @@ const isFloat = (value) => {
11
12
  return regex.test(value);
12
13
  };
13
14
  exports.isFloat = isFloat;
15
+ const scaleValue = (value, scaleFactor, defaultFactor) => {
16
+ if (defaultFactor === scaleFactor) {
17
+ return String(value);
18
+ }
19
+ const valueStr = String(value);
20
+ try {
21
+ if (BigInt(scaleFactor) > BigInt(defaultFactor)) {
22
+ const ratio = (0, utils_1.divide)(scaleFactor, defaultFactor);
23
+ return ratio ? (0, utils_1.divide)(valueStr, ratio, 6) : null;
24
+ }
25
+ const ratio = (0, utils_1.divide)(defaultFactor, scaleFactor);
26
+ return ratio ? (0, utils_1.multiply)(valueStr, ratio, 6) : null;
27
+ }
28
+ catch (_a) {
29
+ return null;
30
+ }
31
+ };
32
+ const isReadable = (numStr) => {
33
+ if (numStr === null) {
34
+ return false;
35
+ }
36
+ // Math.abs does not support BigInt
37
+ const intPart = numStr.split('.')[0].replace('-', '');
38
+ try {
39
+ return BigInt(intPart) >= BigInt(1);
40
+ }
41
+ catch (_a) {
42
+ return false;
43
+ }
44
+ };
45
+ const getMostAppropriateScale = (sizeParams, value) => {
46
+ const { defaultType, scale } = sizeParams;
47
+ const valueStr = String(value);
48
+ const defaultFactor = scale[defaultType].factor;
49
+ let bestType = defaultType;
50
+ Object.keys(scale).forEach((key) => {
51
+ if (BigInt(scale[key].factor) > BigInt(scale[bestType].factor)) {
52
+ const ratio = (0, utils_1.divide)(scale[key].factor, defaultFactor);
53
+ if (!ratio) {
54
+ return;
55
+ }
56
+ if (!isReadable((0, utils_1.divide)(valueStr, ratio, 2))) {
57
+ return;
58
+ }
59
+ bestType = key;
60
+ }
61
+ });
62
+ return bestType;
63
+ };
64
+ const getScaledLimit = (spec, limit) => {
65
+ var _a;
66
+ const sizeParams = (_a = spec.viewSpec) === null || _a === void 0 ? void 0 : _a.sizeParams;
67
+ if (!sizeParams) {
68
+ return undefined;
69
+ }
70
+ const mostAppropriateScale = getMostAppropriateScale(sizeParams, limit);
71
+ const scaleEntry = sizeParams.scale[mostAppropriateScale];
72
+ const defaultEntry = sizeParams.scale[sizeParams.defaultType];
73
+ if (!scaleEntry || !defaultEntry) {
74
+ return undefined;
75
+ }
76
+ const count = scaleValue(limit, scaleEntry.factor, defaultEntry.factor);
77
+ if (count === null) {
78
+ return undefined;
79
+ }
80
+ return { count, scaleTitle: scaleEntry.title };
81
+ };
82
+ exports.getScaledLimit = getScaledLimit;
@@ -27,6 +27,12 @@ const getErrorMessages = () => ({
27
27
  maxNumber(count) {
28
28
  return (0, i18n_1.default)('label_error-max-number', { count });
29
29
  },
30
+ minNumberWithScale(count, scale) {
31
+ return (0, i18n_1.default)('label_error-min-number-with-scale', { count, scale });
32
+ },
33
+ maxNumberWithScale(count, scale) {
34
+ return (0, i18n_1.default)('label_error-max-number-with-scale', { count, scale });
35
+ },
30
36
  SPACE_START: (0, i18n_1.default)('label_error-space-start'),
31
37
  SPACE_END: (0, i18n_1.default)('label_error-space-end'),
32
38
  DOT_END: (0, i18n_1.default)('label_error-dot-end'),
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getStringValidator = exports.getObjectValidator = exports.getNumberValidator = exports.getBooleanValidator = exports.getArrayValidator = void 0;
3
+ exports.getStringValidator = exports.getObjectValidator = exports.getNumberWithScaleValidator = exports.getNumberValidator = exports.getBooleanValidator = exports.getArrayValidator = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const isArray_1 = tslib_1.__importDefault(require("lodash/isArray"));
6
6
  const isNumber_1 = tslib_1.__importDefault(require("lodash/isNumber"));
@@ -101,6 +101,73 @@ const getNumberValidator = (params = {}) => {
101
101
  };
102
102
  };
103
103
  exports.getNumberValidator = getNumberValidator;
104
+ const getNumberWithScaleValidator = (params = {}) => {
105
+ const { ignoreRequiredCheck, ignoreSpaceStartCheck, ignoreSpaceEndCheck, ignoreNumberCheck, ignoreMaximumCheck, ignoreMinimumCheck, ignoreIntCheck, ignoreDotEnd, ignoreZeroStart, ignoreInvalidZeroFormat, ignoreZeroEnd, customErrorMessages, } = params;
106
+ // eslint-disable-next-line complexity
107
+ return (spec, value = '') => {
108
+ const errorMessages = Object.assign(Object.assign({}, validators_1.ErrorMessages), customErrorMessages);
109
+ const stringValue = String(value);
110
+ if (!ignoreRequiredCheck && spec.required && !stringValue.length) {
111
+ return errorMessages.REQUIRED;
112
+ }
113
+ if (stringValue.length) {
114
+ if (!ignoreSpaceStartCheck && !stringValue[0].trim()) {
115
+ return errorMessages.SPACE_START;
116
+ }
117
+ if (!ignoreSpaceEndCheck && !stringValue[stringValue.length - 1].trim()) {
118
+ return errorMessages.SPACE_END;
119
+ }
120
+ if (!ignoreDotEnd && stringValue[stringValue.length - 1] === '.') {
121
+ return errorMessages.DOT_END;
122
+ }
123
+ if (!ignoreNumberCheck && !(0, helpers_1.isFloat)(stringValue)) {
124
+ return errorMessages.NUMBER;
125
+ }
126
+ if (!ignoreZeroStart &&
127
+ ((stringValue.length > 1 && stringValue[0] === '0' && stringValue[1] !== '.') ||
128
+ (stringValue.length > 2 &&
129
+ stringValue.substring(0, 2) === '-0' &&
130
+ stringValue[2] !== '.'))) {
131
+ return errorMessages.ZERO_START;
132
+ }
133
+ if (!ignoreInvalidZeroFormat &&
134
+ stringValue.trim().length > 1 &&
135
+ Number(stringValue.trim()) === 0) {
136
+ return errorMessages.INVALID_ZERO_FORMAT;
137
+ }
138
+ if (!ignoreZeroEnd &&
139
+ !(0, helpers_1.isInt)(stringValue) &&
140
+ stringValue[stringValue.length - 1] === '0') {
141
+ return errorMessages.ZERO_END;
142
+ }
143
+ }
144
+ if (!ignoreMaximumCheck &&
145
+ (0, isNumber_1.default)(spec.maximum) &&
146
+ stringValue.length &&
147
+ Number(stringValue) > spec.maximum) {
148
+ const scaled = (0, helpers_1.getScaledLimit)(spec, spec.maximum);
149
+ return scaled
150
+ ? errorMessages.maxNumberWithScale(scaled.count, scaled.scaleTitle)
151
+ : errorMessages.maxNumber(spec.maximum);
152
+ }
153
+ if (!ignoreMinimumCheck &&
154
+ (0, isNumber_1.default)(spec.minimum) &&
155
+ stringValue.length &&
156
+ spec.minimum > Number(stringValue)) {
157
+ const scaled = (0, helpers_1.getScaledLimit)(spec, spec.minimum);
158
+ return scaled
159
+ ? errorMessages.minNumberWithScale(scaled.count, scaled.scaleTitle)
160
+ : errorMessages.minNumber(spec.minimum);
161
+ }
162
+ if ((0, isString_1.default)(spec.format) && stringValue.length) {
163
+ if (!ignoreIntCheck && spec.format === 'int64' && !(0, helpers_1.isInt)(stringValue)) {
164
+ return errorMessages.INT;
165
+ }
166
+ }
167
+ return false;
168
+ };
169
+ };
170
+ exports.getNumberWithScaleValidator = getNumberWithScaleValidator;
104
171
  const getObjectValidator = (params = {}) => {
105
172
  const { ignoreRequiredCheck, customErrorMessages } = params;
106
173
  return (spec, value) => {
@@ -186,4 +186,9 @@ export interface StringSpec<LinkType = any, InputComponentProps extends Record<s
186
186
  generateRandomValueButton?: boolean;
187
187
  };
188
188
  }
189
+ export interface NumberWithScaleSpec extends StringSpec {
190
+ minimum?: number;
191
+ maximum?: number;
192
+ format?: 'float' | 'int64';
193
+ }
189
194
  export type Spec<LinkType = any, InputComponentProps extends Record<string, any> | undefined = undefined, LayoutComponentProps extends Record<string, any> | undefined = undefined> = ArraySpec<LinkType, InputComponentProps, LayoutComponentProps> | BooleanSpec<LinkType, InputComponentProps, LayoutComponentProps> | NumberSpec<LinkType, InputComponentProps, LayoutComponentProps> | ObjectSpec<LinkType, InputComponentProps, LayoutComponentProps> | StringSpec<LinkType, InputComponentProps, LayoutComponentProps>;
@@ -26,9 +26,11 @@
26
26
  white-space: initial;
27
27
  line-height: var(--_--height);
28
28
  }
29
- .df-text-content__icon {
29
+ .df-text-content__icon, .df-text-content__alert-icon {
30
30
  display: flex;
31
31
  align-items: center;
32
+ }
33
+ .df-text-content__icon {
32
34
  margin-right: var(--df-text-content-icon-margin-right, var(--g-spacing-1));
33
35
  }
34
36
  .df-text-content__wrapper {
@@ -4,25 +4,30 @@ import { isEmpty } from 'lodash';
4
4
  import cloneDeep from 'lodash/cloneDeep';
5
5
  import { block } from '../../../utils';
6
6
  import { LazyLoader } from '../../LazyLoader';
7
- import { loadIcon } from './utils';
7
+ import { isAlertView, loadIcon } from './utils';
8
8
  import './TextContent.css';
9
9
  const b = block('text-content');
10
10
  export const TextContentComponent = ({ spec, value, Layout, }) => {
11
11
  var _a;
12
12
  const { textContentParams, layoutDescription } = spec.viewSpec;
13
13
  const text = React.useMemo(() => ((textContentParams === null || textContentParams === void 0 ? void 0 : textContentParams.text) ? textContentParams === null || textContentParams === void 0 ? void 0 : textContentParams.text : layoutDescription), [layoutDescription, textContentParams === null || textContentParams === void 0 ? void 0 : textContentParams.text]);
14
+ const iconName = textContentParams === null || textContentParams === void 0 ? void 0 : textContentParams.icon;
15
+ const alertView = textContentParams === null || textContentParams === void 0 ? void 0 : textContentParams.viewAlert;
16
+ const hasIconParam = typeof iconName === 'string';
17
+ const IconComponent = React.useMemo(() => (iconName ? loadIcon(iconName) : undefined), [iconName]);
18
+ const iconLib = React.useMemo(() => (IconComponent ? React.createElement(LazyLoader, { component: IconComponent }) : null), [IconComponent]);
19
+ const alertIcon = React.useMemo(() => (iconLib ? React.createElement("span", { className: b('alert-icon') }, iconLib) : null), [iconLib]);
14
20
  if (!text) {
15
21
  return null;
16
22
  }
17
- const iconLib = (textContentParams === null || textContentParams === void 0 ? void 0 : textContentParams.icon) ? (React.createElement(LazyLoader, { component: loadIcon(textContentParams === null || textContentParams === void 0 ? void 0 : textContentParams.icon) })) : undefined;
18
23
  let content = React.createElement("span", { dangerouslySetInnerHTML: { __html: text } });
19
24
  if (textContentParams === null || textContentParams === void 0 ? void 0 : textContentParams.themeAlert) {
20
25
  const titleAlert = (textContentParams === null || textContentParams === void 0 ? void 0 : textContentParams.titleAlert) || !isEmpty(textContentParams === null || textContentParams === void 0 ? void 0 : textContentParams.titleAlert)
21
26
  ? textContentParams.titleAlert
22
27
  : undefined;
23
- content = (React.createElement(Alert, { icon: iconLib, message: content,
28
+ content = (React.createElement(Alert, Object.assign({}, (alertIcon || hasIconParam ? { icon: alertIcon } : {}), { message: content,
24
29
  // If the title is an empty line, then you need to explicitly write undefined, otherwise there will be an additional indent
25
- title: titleAlert, theme: textContentParams === null || textContentParams === void 0 ? void 0 : textContentParams.themeAlert, view: textContentParams === null || textContentParams === void 0 ? void 0 : textContentParams.viewAlert }));
30
+ title: titleAlert, theme: textContentParams === null || textContentParams === void 0 ? void 0 : textContentParams.themeAlert, view: isAlertView(alertView) ? alertView : undefined })));
26
31
  }
27
32
  else if (textContentParams === null || textContentParams === void 0 ? void 0 : textContentParams.themeLabel) {
28
33
  content = (React.createElement(Label, { size: "m", theme: textContentParams.themeLabel, className: b(), value: value, icon: iconLib }, content));
@@ -1,2 +1,4 @@
1
1
  import React from 'react';
2
+ import type { AlertProps } from '@gravity-ui/uikit';
2
3
  export declare const loadIcon: (name: string) => React.LazyExoticComponent<React.ComponentType<any>>;
4
+ export declare const isAlertView: (view: unknown) => view is AlertProps["view"];
@@ -15,3 +15,6 @@ export const loadIcon = (name) => {
15
15
  });
16
16
  return Icon;
17
17
  };
18
+ export const isAlertView = (view) => {
19
+ return view === 'filled' || view === 'outlined';
20
+ };
@@ -1,15 +1,16 @@
1
1
  import React from 'react';
2
2
  import { TextContentComponent } from '../Inputs';
3
3
  export const TextContentView = ({ name, spec, Layout, value }) => {
4
+ var _a;
5
+ const layoutValue = ((_a = spec.viewSpec.textContentParams) === null || _a === void 0 ? void 0 : _a.text) || spec.viewSpec.layoutDescription || value;
4
6
  const WrappedLayout = React.useMemo(() => {
5
7
  if (Layout) {
6
8
  const Component = (props) => {
7
- const VALUE_STUB = 'if u see this, please create issue about it here: https://github.com/gravity-ui/dynamic-forms/issues/new';
8
- return React.createElement(Layout, Object.assign({ name: name, value: VALUE_STUB }, props));
9
+ return React.createElement(Layout, Object.assign({ name: name, value: layoutValue }, props));
9
10
  };
10
11
  return Component;
11
12
  }
12
13
  return undefined;
13
- }, [Layout, name]);
14
+ }, [Layout, layoutValue, name]);
14
15
  return React.createElement(TextContentComponent, { spec: spec, value: value, Layout: WrappedLayout });
15
16
  };
@@ -1,5 +1,5 @@
1
1
  import { Accordeon, AccordeonCardForm, ArrayBase, ArrayBaseView, BaseView, CardAccordeon, CardOneOf, CardOneOfView, CardSection, Checkbox, CheckboxGroup, CheckboxGroupView, Column, DateInput, DateView, FileInput, FileInputView, Group, Group2, MonacoInput, MonacoView, MultiOneOf, MultiOneOfFlat, MultiOneOfFlatView, MultiOneOfView, MultiSelect, MultiSelectView, NumInput, NumberWithScale, NumberWithScaleView, ObjectBase, ObjectBaseView, ObjectInline, ObjectInlineView, ObjectValueInput, ObjectValueInputView, OneOf, OneOfFlat, OneOfFlatView, OneOfView, RadioGroup, RangeInputPicker, RangeInputPickerNumber, RangeInputPickerNumberView, RangeInputPickerView, Row, RowVerbose, Secret, Section, Section2, Select, Switch, TableArrayInput, TableArrayView, TableCell, Text, TextArea, TextAreaView, TextContent, TextContentView, TextLink, TextLinkView, TimeRangeSelector, TimeRangeSelectorView, Transparent, ViewAccordeon, ViewAccordeonCard, ViewCardAccordeon, ViewCardSection, ViewColumn, ViewGroup, ViewGroup2, ViewRow, ViewSection, ViewSection2, ViewTableCell, ViewTransparent, } from '../components';
2
- import { getArrayValidator, getBooleanValidator, getNumberValidator, getObjectValidator, getStringValidator, } from '../validators';
2
+ import { getArrayValidator, getBooleanValidator, getNumberValidator, getNumberWithScaleValidator, getObjectValidator, getStringValidator, } from '../validators';
3
3
  export const dynamicConfig = {
4
4
  array: {
5
5
  inputs: {
@@ -120,6 +120,7 @@ export const dynamicConfig = {
120
120
  validators: {
121
121
  base: getStringValidator(),
122
122
  number: getNumberValidator(),
123
+ number_with_scale: getNumberWithScaleValidator(),
123
124
  },
124
125
  },
125
126
  };
@@ -37,6 +37,8 @@
37
37
  ],
38
38
  "label_error-min-number": "Value must be an integer no less than {{count}}",
39
39
  "label_error-max-number": "Value must not be greater than {{count}}",
40
+ "label_error-min-number-with-scale": "Value must be no less than {{count}} {{scale}}",
41
+ "label_error-max-number-with-scale": "Value must not be greater than {{count}} {{scale}}",
40
42
  "label_error-space-end": "Value must not end with a space",
41
43
  "label_error-space-start": "Value must not start with a space",
42
44
  "label_error-zero-start": "Value must not start with a zero",
@@ -37,6 +37,8 @@
37
37
  ],
38
38
  "label_error-min-number": "Значение должно быть числом не меньше {{count}}",
39
39
  "label_error-max-number": "Значение должно быть числом не больше {{count}}",
40
+ "label_error-min-number-with-scale": "Значение должно быть не меньше {{count}} {{scale}}",
41
+ "label_error-max-number-with-scale": "Значение должно быть не больше {{count}} {{scale}}",
40
42
  "label_error-space-end": "Значение не должно заканчиваться пробелом",
41
43
  "label_error-space-start": "Значение не должно начинаться с пробела",
42
44
  "label_error-zero-start": "Значение не должно начинаться с нуля",
@@ -1,2 +1,7 @@
1
+ import type { StringSpec } from 'src/lib/core';
1
2
  export declare const isInt: (value: string) => boolean;
2
3
  export declare const isFloat: (value: string) => boolean;
4
+ export declare const getScaledLimit: (spec: StringSpec, limit: number) => {
5
+ count: string;
6
+ scaleTitle: string;
7
+ } | undefined;
@@ -1,3 +1,4 @@
1
+ import { divide, multiply } from '../utils';
1
2
  export const isInt = (value) => {
2
3
  const regex = /^(?:[-+]?(?:0|[1-9]\d*))$/;
3
4
  return regex.test(value);
@@ -6,3 +7,70 @@ export const isFloat = (value) => {
6
7
  const regex = /^(?:[-+]?(?:\d+))?(?:\.\d*)?(?:[eE][+-]?(?:\d+))?$/;
7
8
  return regex.test(value);
8
9
  };
10
+ const scaleValue = (value, scaleFactor, defaultFactor) => {
11
+ if (defaultFactor === scaleFactor) {
12
+ return String(value);
13
+ }
14
+ const valueStr = String(value);
15
+ try {
16
+ if (BigInt(scaleFactor) > BigInt(defaultFactor)) {
17
+ const ratio = divide(scaleFactor, defaultFactor);
18
+ return ratio ? divide(valueStr, ratio, 6) : null;
19
+ }
20
+ const ratio = divide(defaultFactor, scaleFactor);
21
+ return ratio ? multiply(valueStr, ratio, 6) : null;
22
+ }
23
+ catch (_a) {
24
+ return null;
25
+ }
26
+ };
27
+ const isReadable = (numStr) => {
28
+ if (numStr === null) {
29
+ return false;
30
+ }
31
+ // Math.abs does not support BigInt
32
+ const intPart = numStr.split('.')[0].replace('-', '');
33
+ try {
34
+ return BigInt(intPart) >= BigInt(1);
35
+ }
36
+ catch (_a) {
37
+ return false;
38
+ }
39
+ };
40
+ const getMostAppropriateScale = (sizeParams, value) => {
41
+ const { defaultType, scale } = sizeParams;
42
+ const valueStr = String(value);
43
+ const defaultFactor = scale[defaultType].factor;
44
+ let bestType = defaultType;
45
+ Object.keys(scale).forEach((key) => {
46
+ if (BigInt(scale[key].factor) > BigInt(scale[bestType].factor)) {
47
+ const ratio = divide(scale[key].factor, defaultFactor);
48
+ if (!ratio) {
49
+ return;
50
+ }
51
+ if (!isReadable(divide(valueStr, ratio, 2))) {
52
+ return;
53
+ }
54
+ bestType = key;
55
+ }
56
+ });
57
+ return bestType;
58
+ };
59
+ export const getScaledLimit = (spec, limit) => {
60
+ var _a;
61
+ const sizeParams = (_a = spec.viewSpec) === null || _a === void 0 ? void 0 : _a.sizeParams;
62
+ if (!sizeParams) {
63
+ return undefined;
64
+ }
65
+ const mostAppropriateScale = getMostAppropriateScale(sizeParams, limit);
66
+ const scaleEntry = sizeParams.scale[mostAppropriateScale];
67
+ const defaultEntry = sizeParams.scale[sizeParams.defaultType];
68
+ if (!scaleEntry || !defaultEntry) {
69
+ return undefined;
70
+ }
71
+ const count = scaleValue(limit, scaleEntry.factor, defaultEntry.factor);
72
+ if (count === null) {
73
+ return undefined;
74
+ }
75
+ return { count, scaleTitle: scaleEntry.title };
76
+ };
@@ -23,6 +23,12 @@ const getErrorMessages = () => ({
23
23
  maxNumber(count) {
24
24
  return i18n('label_error-max-number', { count });
25
25
  },
26
+ minNumberWithScale(count, scale) {
27
+ return i18n('label_error-min-number-with-scale', { count, scale });
28
+ },
29
+ maxNumberWithScale(count, scale) {
30
+ return i18n('label_error-max-number-with-scale', { count, scale });
31
+ },
26
32
  SPACE_START: i18n('label_error-space-start'),
27
33
  SPACE_END: i18n('label_error-space-end'),
28
34
  DOT_END: i18n('label_error-dot-end'),
@@ -9,6 +9,8 @@ export interface ErrorMessagesType {
9
9
  maxLengthArr: (count: number | bigint) => string;
10
10
  minNumber: (count: number | bigint) => string;
11
11
  maxNumber: (count: number | bigint) => string;
12
+ minNumberWithScale: (count: number | bigint | string, scale: string) => string;
13
+ maxNumberWithScale: (count: number | bigint | string, scale: string) => string;
12
14
  SPACE_START: string;
13
15
  SPACE_END: string;
14
16
  DOT_END: string;
@@ -1,4 +1,4 @@
1
- import type { ArraySpec, ArrayValue, BooleanSpec, NumberSpec, ObjectSpec, ObjectValue, StringSpec } from '../../core';
1
+ import type { ArraySpec, ArrayValue, BooleanSpec, NumberSpec, NumberWithScaleSpec, ObjectSpec, ObjectValue, StringSpec } from '../../core';
2
2
  import type { ErrorMessagesType } from './types';
3
3
  interface CommonValidatorParams {
4
4
  ignoreRequiredCheck?: boolean;
@@ -25,6 +25,9 @@ export interface GetNumberValidatorParams extends CommonValidatorParams {
25
25
  ignoreZeroEnd?: boolean;
26
26
  }
27
27
  export declare const getNumberValidator: (params?: GetNumberValidatorParams) => (spec: NumberSpec, value?: string | number) => string | false;
28
+ export interface GetNumberWithScaleValidatorParams extends GetNumberValidatorParams {
29
+ }
30
+ export declare const getNumberWithScaleValidator: (params?: GetNumberWithScaleValidatorParams) => (spec: NumberWithScaleSpec, value?: string) => string | false;
28
31
  export interface GetObjectValidatorParams extends CommonValidatorParams {
29
32
  }
30
33
  export declare const getObjectValidator: (params?: GetObjectValidatorParams) => (spec: ObjectSpec, value?: ObjectValue) => string | false;
@@ -2,7 +2,7 @@ import isArray from 'lodash/isArray';
2
2
  import isNumber from 'lodash/isNumber';
3
3
  import isString from 'lodash/isString';
4
4
  import { ErrorMessages } from '../validators';
5
- import { isFloat, isInt } from './helpers';
5
+ import { getScaledLimit, isFloat, isInt } from './helpers';
6
6
  export const getArrayValidator = (params = {}) => {
7
7
  const { ignoreRequiredCheck, ignoreMaxLengthCheck, ignoreMinLengthCheck, customErrorMessages } = params;
8
8
  return (spec, value) => {
@@ -94,6 +94,72 @@ export const getNumberValidator = (params = {}) => {
94
94
  return false;
95
95
  };
96
96
  };
97
+ export const getNumberWithScaleValidator = (params = {}) => {
98
+ const { ignoreRequiredCheck, ignoreSpaceStartCheck, ignoreSpaceEndCheck, ignoreNumberCheck, ignoreMaximumCheck, ignoreMinimumCheck, ignoreIntCheck, ignoreDotEnd, ignoreZeroStart, ignoreInvalidZeroFormat, ignoreZeroEnd, customErrorMessages, } = params;
99
+ // eslint-disable-next-line complexity
100
+ return (spec, value = '') => {
101
+ const errorMessages = Object.assign(Object.assign({}, ErrorMessages), customErrorMessages);
102
+ const stringValue = String(value);
103
+ if (!ignoreRequiredCheck && spec.required && !stringValue.length) {
104
+ return errorMessages.REQUIRED;
105
+ }
106
+ if (stringValue.length) {
107
+ if (!ignoreSpaceStartCheck && !stringValue[0].trim()) {
108
+ return errorMessages.SPACE_START;
109
+ }
110
+ if (!ignoreSpaceEndCheck && !stringValue[stringValue.length - 1].trim()) {
111
+ return errorMessages.SPACE_END;
112
+ }
113
+ if (!ignoreDotEnd && stringValue[stringValue.length - 1] === '.') {
114
+ return errorMessages.DOT_END;
115
+ }
116
+ if (!ignoreNumberCheck && !isFloat(stringValue)) {
117
+ return errorMessages.NUMBER;
118
+ }
119
+ if (!ignoreZeroStart &&
120
+ ((stringValue.length > 1 && stringValue[0] === '0' && stringValue[1] !== '.') ||
121
+ (stringValue.length > 2 &&
122
+ stringValue.substring(0, 2) === '-0' &&
123
+ stringValue[2] !== '.'))) {
124
+ return errorMessages.ZERO_START;
125
+ }
126
+ if (!ignoreInvalidZeroFormat &&
127
+ stringValue.trim().length > 1 &&
128
+ Number(stringValue.trim()) === 0) {
129
+ return errorMessages.INVALID_ZERO_FORMAT;
130
+ }
131
+ if (!ignoreZeroEnd &&
132
+ !isInt(stringValue) &&
133
+ stringValue[stringValue.length - 1] === '0') {
134
+ return errorMessages.ZERO_END;
135
+ }
136
+ }
137
+ if (!ignoreMaximumCheck &&
138
+ isNumber(spec.maximum) &&
139
+ stringValue.length &&
140
+ Number(stringValue) > spec.maximum) {
141
+ const scaled = getScaledLimit(spec, spec.maximum);
142
+ return scaled
143
+ ? errorMessages.maxNumberWithScale(scaled.count, scaled.scaleTitle)
144
+ : errorMessages.maxNumber(spec.maximum);
145
+ }
146
+ if (!ignoreMinimumCheck &&
147
+ isNumber(spec.minimum) &&
148
+ stringValue.length &&
149
+ spec.minimum > Number(stringValue)) {
150
+ const scaled = getScaledLimit(spec, spec.minimum);
151
+ return scaled
152
+ ? errorMessages.minNumberWithScale(scaled.count, scaled.scaleTitle)
153
+ : errorMessages.minNumber(spec.minimum);
154
+ }
155
+ if (isString(spec.format) && stringValue.length) {
156
+ if (!ignoreIntCheck && spec.format === 'int64' && !isInt(stringValue)) {
157
+ return errorMessages.INT;
158
+ }
159
+ }
160
+ return false;
161
+ };
162
+ };
97
163
  export const getObjectValidator = (params = {}) => {
98
164
  const { ignoreRequiredCheck, customErrorMessages } = params;
99
165
  return (spec, value) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gravity-ui/dynamic-forms",
3
- "version": "5.14.0",
3
+ "version": "5.16.0",
4
4
  "description": "",
5
5
  "license": "MIT",
6
6
  "main": "build/cjs/index.js",