@gravity-ui/dynamic-forms 1.2.0 → 1.4.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 (99) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +34 -5
  3. package/build/cjs/lib/core/components/Form/Controller.js +9 -3
  4. package/build/cjs/lib/core/components/Form/DynamicField.js +11 -4
  5. package/build/cjs/lib/core/components/Form/hooks/index.js +3 -0
  6. package/build/cjs/lib/core/components/Form/hooks/useControllerMirror.js +19 -0
  7. package/build/cjs/lib/core/components/Form/hooks/useDynamicFieldMirror.js +22 -0
  8. package/build/cjs/lib/core/components/Form/hooks/useField.js +1 -1
  9. package/build/cjs/lib/core/components/Form/hooks/useIntegrationFF.js +46 -0
  10. package/build/cjs/lib/core/components/Form/hooks/useSearchStore.js +5 -2
  11. package/build/cjs/lib/core/components/Form/hooks/useStore.js +3 -35
  12. package/build/cjs/lib/core/components/Form/types/index.js +2 -0
  13. package/build/cjs/lib/core/components/Form/types/mirror.js +2 -0
  14. package/build/cjs/lib/core/components/Form/types/store.js +2 -0
  15. package/build/cjs/lib/core/components/View/ViewController.js +2 -2
  16. package/build/cjs/lib/core/components/View/hooks/useComponents.js +1 -3
  17. package/build/cjs/lib/kit/components/Inputs/FileInput/FileInput.css +20 -0
  18. package/build/cjs/lib/kit/components/Inputs/FileInput/FileInput.js +50 -0
  19. package/build/cjs/lib/kit/components/Inputs/FileInput/index.js +4 -0
  20. package/build/cjs/lib/kit/components/Inputs/FileInput/utils.js +16 -0
  21. package/build/cjs/lib/kit/components/Inputs/TableArrayInput/TableArrayInput.js +3 -2
  22. package/build/cjs/lib/kit/components/Inputs/TextLink/TextLink.js +20 -0
  23. package/build/cjs/lib/kit/components/Inputs/TextLink/index.js +4 -0
  24. package/build/cjs/lib/kit/components/Inputs/index.js +2 -0
  25. package/build/cjs/lib/kit/components/Layouts/Row/Row.css +18 -10
  26. package/build/cjs/lib/kit/components/Layouts/Row/Row.js +4 -5
  27. package/build/cjs/lib/kit/components/Views/FileInputView/FileInputView.js +12 -0
  28. package/build/cjs/lib/kit/components/Views/FileInputView/index.js +4 -0
  29. package/build/cjs/lib/kit/components/Views/TextLinkView/TextLinkView.js +17 -0
  30. package/build/cjs/lib/kit/components/Views/TextLinkView/index.js +4 -0
  31. package/build/cjs/lib/kit/components/Views/index.js +2 -0
  32. package/build/cjs/lib/kit/constants/config.js +8 -0
  33. package/build/cjs/lib/kit/i18n/en.json +3 -1
  34. package/build/cjs/lib/kit/i18n/ru.json +3 -1
  35. package/build/cjs/lib/kit/utils/common.js +1 -26
  36. package/build/cjs/lib/kit/validators/validators.js +8 -8
  37. package/build/esm/lib/core/components/Form/Controller.js +9 -3
  38. package/build/esm/lib/core/components/Form/DynamicField.d.ts +2 -1
  39. package/build/esm/lib/core/components/Form/DynamicField.js +12 -5
  40. package/build/esm/lib/core/components/Form/hooks/index.d.ts +3 -0
  41. package/build/esm/lib/core/components/Form/hooks/index.js +3 -0
  42. package/build/esm/lib/core/components/Form/hooks/useControllerMirror.d.ts +2 -0
  43. package/build/esm/lib/core/components/Form/hooks/useControllerMirror.js +14 -0
  44. package/build/esm/lib/core/components/Form/hooks/useDynamicFieldMirror.d.ts +2 -0
  45. package/build/esm/lib/core/components/Form/hooks/useDynamicFieldMirror.js +17 -0
  46. package/build/esm/lib/core/components/Form/hooks/useField.d.ts +2 -2
  47. package/build/esm/lib/core/components/Form/hooks/useField.js +1 -1
  48. package/build/esm/lib/core/components/Form/hooks/useIntegrationFF.d.ts +2 -0
  49. package/build/esm/lib/core/components/Form/hooks/useIntegrationFF.js +41 -0
  50. package/build/esm/lib/core/components/Form/hooks/useSearchStore.d.ts +2 -4
  51. package/build/esm/lib/core/components/Form/hooks/useSearchStore.js +5 -2
  52. package/build/esm/lib/core/components/Form/hooks/useStore.d.ts +2 -8
  53. package/build/esm/lib/core/components/Form/hooks/useStore.js +5 -37
  54. package/build/esm/lib/core/components/Form/types/context.d.ts +2 -1
  55. package/build/esm/lib/core/components/Form/types/index.d.ts +2 -0
  56. package/build/esm/lib/core/components/Form/types/index.js +2 -0
  57. package/build/esm/lib/core/components/Form/types/mirror.d.ts +17 -0
  58. package/build/esm/lib/core/components/Form/types/mirror.js +1 -0
  59. package/build/esm/lib/core/components/Form/types/store.d.ts +7 -0
  60. package/build/esm/lib/core/components/Form/types/store.js +1 -0
  61. package/build/esm/lib/core/components/View/ViewController.js +2 -2
  62. package/build/esm/lib/core/components/View/hooks/useComponents.d.ts +2 -2
  63. package/build/esm/lib/core/components/View/hooks/useComponents.js +1 -3
  64. package/build/esm/lib/core/constants.d.ts +1 -0
  65. package/build/esm/lib/core/types/specs.d.ts +6 -1
  66. package/build/esm/lib/kit/components/Inputs/FileInput/FileInput.css +20 -0
  67. package/build/esm/lib/kit/components/Inputs/FileInput/FileInput.d.ts +4 -0
  68. package/build/esm/lib/kit/components/Inputs/FileInput/FileInput.js +46 -0
  69. package/build/esm/lib/kit/components/Inputs/FileInput/index.d.ts +1 -0
  70. package/build/esm/lib/kit/components/Inputs/FileInput/index.js +1 -0
  71. package/build/esm/lib/kit/components/Inputs/FileInput/utils.d.ts +2 -0
  72. package/build/esm/lib/kit/components/Inputs/FileInput/utils.js +12 -0
  73. package/build/esm/lib/kit/components/Inputs/TableArrayInput/TableArrayInput.js +3 -2
  74. package/build/esm/lib/kit/components/Inputs/TextLink/TextLink.d.ts +2 -0
  75. package/build/esm/lib/kit/components/Inputs/TextLink/TextLink.js +15 -0
  76. package/build/esm/lib/kit/components/Inputs/TextLink/index.d.ts +1 -0
  77. package/build/esm/lib/kit/components/Inputs/TextLink/index.js +1 -0
  78. package/build/esm/lib/kit/components/Inputs/index.d.ts +2 -0
  79. package/build/esm/lib/kit/components/Inputs/index.js +2 -0
  80. package/build/esm/lib/kit/components/Layouts/Row/Row.css +18 -10
  81. package/build/esm/lib/kit/components/Layouts/Row/Row.js +4 -5
  82. package/build/esm/lib/kit/components/Views/FileInputView/FileInputView.d.ts +3 -0
  83. package/build/esm/lib/kit/components/Views/FileInputView/FileInputView.js +7 -0
  84. package/build/esm/lib/kit/components/Views/FileInputView/index.d.ts +1 -0
  85. package/build/esm/lib/kit/components/Views/FileInputView/index.js +1 -0
  86. package/build/esm/lib/kit/components/Views/TextLinkView/TextLinkView.d.ts +2 -0
  87. package/build/esm/lib/kit/components/Views/TextLinkView/TextLinkView.js +12 -0
  88. package/build/esm/lib/kit/components/Views/TextLinkView/index.d.ts +1 -0
  89. package/build/esm/lib/kit/components/Views/TextLinkView/index.js +1 -0
  90. package/build/esm/lib/kit/components/Views/index.d.ts +2 -0
  91. package/build/esm/lib/kit/components/Views/index.js +2 -0
  92. package/build/esm/lib/kit/constants/config.js +9 -1
  93. package/build/esm/lib/kit/i18n/en.json +3 -1
  94. package/build/esm/lib/kit/i18n/ru.json +3 -1
  95. package/build/esm/lib/kit/utils/common.d.ts +0 -2
  96. package/build/esm/lib/kit/utils/common.js +0 -24
  97. package/build/esm/lib/kit/validators/validators.d.ts +2 -0
  98. package/build/esm/lib/kit/validators/validators.js +8 -8
  99. package/package.json +12 -4
@@ -3,10 +3,8 @@ import _ from 'lodash';
3
3
  import { isValidElementType } from 'react-is';
4
4
  import { isCorrectSpec } from '../../../helpers';
5
5
  import { isCorrectViewConfig } from '../helpers';
6
- import { useDynamicFormsCtx } from './';
7
- export const useComponents = (spec) => {
6
+ export const useComponents = (spec, config) => {
8
7
  var _a, _b;
9
- const { config } = useDynamicFormsCtx();
10
8
  const { views, layouts } = React.useMemo(() => {
11
9
  if (isCorrectViewConfig(config) && isCorrectSpec(spec)) {
12
10
  return config[spec.type];
@@ -5,3 +5,4 @@ export declare enum SpecTypes {
5
5
  Object = "object",
6
6
  String = "string"
7
7
  }
8
+ export type ReadAsMethod = 'readAsArrayBuffer' | 'readAsBinaryString' | 'readAsDataURL' | 'readAsText';
@@ -1,5 +1,5 @@
1
1
  import { LabelProps } from '@gravity-ui/uikit';
2
- import { SpecTypes } from '../constants';
2
+ import { ReadAsMethod, SpecTypes } from '../constants';
3
3
  import { ArrayValue, ObjectValue } from './';
4
4
  export interface ArraySpec<LinkType = any> {
5
5
  defaultValue?: ArrayValue;
@@ -114,6 +114,11 @@ export interface StringSpec<LinkType = any> {
114
114
  hideValues?: string[];
115
115
  placeholder?: string;
116
116
  themeLabel?: LabelProps['theme'];
117
+ fileInput?: {
118
+ accept?: string;
119
+ readAsMethod?: ReadAsMethod;
120
+ ignoreText?: boolean;
121
+ };
117
122
  };
118
123
  }
119
124
  export type Spec = ArraySpec | BooleanSpec | NumberSpec | ObjectSpec | StringSpec;
@@ -0,0 +1,20 @@
1
+ .df-file-input {
2
+ display: flex;
3
+ }
4
+ .df-file-input__input {
5
+ opacity: 0;
6
+ position: absolute;
7
+ clip: rect(0 0 0 0);
8
+ width: 1px;
9
+ height: 1px;
10
+ margin: -1px;
11
+ }
12
+ .df-file-input__file-name {
13
+ display: block;
14
+ margin: auto 10px;
15
+ max-width: 160px;
16
+ text-overflow: ellipsis;
17
+ overflow: hidden;
18
+ white-space: nowrap;
19
+ color: var(--yc-color-text-secondary);
20
+ }
@@ -0,0 +1,4 @@
1
+ import React from 'react';
2
+ import { StringInputProps } from '../../../../core';
3
+ import './FileInput.css';
4
+ export declare const FileInput: React.FC<StringInputProps>;
@@ -0,0 +1,46 @@
1
+ import React from 'react';
2
+ import { Xmark } from '@gravity-ui/icons';
3
+ import { Button, Icon, Label } from '@gravity-ui/uikit';
4
+ import i18n from '../../../../kit/i18n';
5
+ import { block } from '../../../utils';
6
+ import { readFile } from './utils';
7
+ import './FileInput.css';
8
+ const b = block('file-input');
9
+ export const FileInput = ({ input, spec }) => {
10
+ var _a, _b;
11
+ const { value, onChange } = input;
12
+ const inputRef = React.useRef(null);
13
+ const [fileName, setFileName] = React.useState('');
14
+ const handleClick = React.useCallback(() => {
15
+ var _a;
16
+ (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.click();
17
+ }, []);
18
+ const handleDownload = React.useCallback(async (file) => { var _a; return await readFile(file, (_a = spec.viewSpec.fileInput) === null || _a === void 0 ? void 0 : _a.readAsMethod); }, [(_a = spec.viewSpec.fileInput) === null || _a === void 0 ? void 0 : _a.readAsMethod]);
19
+ const handleReset = React.useCallback(() => {
20
+ setFileName('');
21
+ onChange('');
22
+ }, [onChange]);
23
+ const handleInputChange = React.useCallback(async (event) => {
24
+ const file = event.target.files;
25
+ if (file && file.length > 0) {
26
+ setFileName(file[0].name);
27
+ const data = (await handleDownload(file[0]));
28
+ onChange(data);
29
+ }
30
+ }, [handleDownload, onChange]);
31
+ const fileNameContent = React.useMemo(() => {
32
+ if (value) {
33
+ if (fileName) {
34
+ return React.createElement(React.Fragment, null, fileName);
35
+ }
36
+ return (React.createElement(Label, { size: "m", theme: "info" }, i18n('label-data_loaded')));
37
+ }
38
+ return null;
39
+ }, [fileName, value]);
40
+ return (React.createElement("div", { className: b() },
41
+ React.createElement(Button, { disabled: spec.viewSpec.disabled, onClick: handleClick }, i18n('button-upload_file')),
42
+ React.createElement("input", { type: "file", ref: inputRef, autoComplete: "off", disabled: spec.viewSpec.disabled, onChange: handleInputChange, className: b('input'), tabIndex: -1, accept: (_b = spec.viewSpec.fileInput) === null || _b === void 0 ? void 0 : _b.accept }),
43
+ React.createElement("span", { className: b('file-name') }, fileNameContent),
44
+ value ? (React.createElement(Button, { view: "flat", onClick: handleReset, disabled: spec.viewSpec.disabled },
45
+ React.createElement(Icon, { data: Xmark, size: 16 }))) : null));
46
+ };
@@ -0,0 +1 @@
1
+ export * from './FileInput';
@@ -0,0 +1 @@
1
+ export * from './FileInput';
@@ -0,0 +1,2 @@
1
+ import { ReadAsMethod } from '../../../../core';
2
+ export declare function readFile(file: Blob, readAsMethod?: ReadAsMethod): Promise<string | ArrayBuffer | null>;
@@ -0,0 +1,12 @@
1
+ export function readFile(file, readAsMethod = 'readAsBinaryString') {
2
+ return new Promise((resolve, reject) => {
3
+ const reader = new FileReader();
4
+ if (typeof reader[readAsMethod] !== 'function') {
5
+ reject(new Error(`Unknown parameter: ${readAsMethod}`));
6
+ return;
7
+ }
8
+ reader.addEventListener('load', () => resolve(reader.result));
9
+ reader.addEventListener('error', () => reject(reader.error));
10
+ reader[readAsMethod](file);
11
+ });
12
+ }
@@ -65,9 +65,10 @@ export const TableArrayInput = ({ spec, name, arrayInput, input }) => {
65
65
  return [idxColumn, ...columns, removeColumn];
66
66
  }, [name, spec, onItemRemove, parentOnChange, parentOnUnmount, input.value]);
67
67
  const getRowClassNames = React.useCallback(({ key }) => {
68
- const searchResult = isHiddenField(`${name}.<${key}>`);
68
+ var _a;
69
+ const searchResult = (_a = spec.viewSpec.table) === null || _a === void 0 ? void 0 : _a.every(({ property }) => isHiddenField(`${name}.<${key}>.${property}`));
69
70
  return [b('row', { hidden: searchResult })];
70
- }, [isHiddenField, name]);
71
+ }, [isHiddenField, name, spec.viewSpec.table]);
71
72
  if (!columns) {
72
73
  return null;
73
74
  }
@@ -0,0 +1,2 @@
1
+ import { ObjectIndependentInput } from '../../../../core';
2
+ export declare const TextLink: ObjectIndependentInput;
@@ -0,0 +1,15 @@
1
+ import React from 'react';
2
+ import _ from 'lodash';
3
+ import { Controller, isStringSpec, } from '../../../../core';
4
+ const TEXT_LINK_PROPERTY_NAME = 'text';
5
+ export const TextLink = ({ spec, input, name }) => {
6
+ var _a;
7
+ const parentOnChange = React.useCallback((childName, childValue, childErrors) => input.onChange((currentValue) => _.set(Object.assign({}, currentValue), childName.split(`${name}.`).join(''), childValue), childErrors), [input.onChange, input.name]);
8
+ const parentOnUnmount = React.useCallback((childName) => input.onChange((currentValue) => currentValue, { [childName]: false }), [input.onChange]);
9
+ const specProperties = Object.assign({}, spec.properties);
10
+ if (!specProperties[TEXT_LINK_PROPERTY_NAME] ||
11
+ !isStringSpec(specProperties[TEXT_LINK_PROPERTY_NAME])) {
12
+ return null;
13
+ }
14
+ return (React.createElement(Controller, { initialValue: (_a = input.value) === null || _a === void 0 ? void 0 : _a[TEXT_LINK_PROPERTY_NAME], spec: specProperties[TEXT_LINK_PROPERTY_NAME], name: `${name}.${TEXT_LINK_PROPERTY_NAME}`, key: `${name}.${TEXT_LINK_PROPERTY_NAME}`, parentOnChange: parentOnChange, parentOnUnmount: parentOnUnmount }));
15
+ };
@@ -0,0 +1 @@
1
+ export * from './TextLink';
@@ -0,0 +1 @@
1
+ export * from './TextLink';
@@ -1,6 +1,7 @@
1
1
  export * from './ArrayBase';
2
2
  export * from './CardOneOf';
3
3
  export * from './Checkbox';
4
+ export * from './FileInput';
4
5
  export * from './MultiSelect';
5
6
  export * from './ObjectBase';
6
7
  export * from './OneOf';
@@ -11,5 +12,6 @@ export * from './TableArrayInput';
11
12
  export * from './Text';
12
13
  export * from './TextArea';
13
14
  export * from './TextContent';
15
+ export * from './TextLink';
14
16
  export * from './NumberWithScale';
15
17
  export * from './MonacoInput';
@@ -1,6 +1,7 @@
1
1
  export * from './ArrayBase';
2
2
  export * from './CardOneOf';
3
3
  export * from './Checkbox';
4
+ export * from './FileInput';
4
5
  export * from './MultiSelect';
5
6
  export * from './ObjectBase';
6
7
  export * from './OneOf';
@@ -11,5 +12,6 @@ export * from './TableArrayInput';
11
12
  export * from './Text';
12
13
  export * from './TextArea';
13
14
  export * from './TextContent';
15
+ export * from './TextLink';
14
16
  export * from './NumberWithScale';
15
17
  export * from './MonacoInput';
@@ -13,13 +13,16 @@
13
13
  }
14
14
  .df-row__left {
15
15
  width: 180px;
16
+ min-height: 28px;
16
17
  display: flex;
18
+ margin-bottom: auto;
17
19
  flex-direction: column;
18
20
  flex-shrink: 0;
19
21
  }
20
22
  .df-row__left-inner {
21
- min-height: 28px;
22
- display: flex;
23
+ display: inline;
24
+ margin-top: auto;
25
+ margin-bottom: auto;
23
26
  }
24
27
  .df-row__left::after {
25
28
  content: "";
@@ -27,18 +30,24 @@
27
30
  flex-shrink: 1;
28
31
  }
29
32
  .df-row__title {
30
- align-self: center;
33
+ word-break: break-word;
34
+ margin-right: 3px;
35
+ }
36
+ .df-row__title_required::after {
37
+ content: "*";
38
+ color: var(--yc-color-text-danger);
31
39
  }
32
40
  .df-row__note {
33
- height: 28px;
34
- display: flex;
35
- align-items: center;
36
- margin-left: 5px;
41
+ padding-right: 16px;
42
+ }
43
+ .df-row__note-inner {
44
+ position: absolute;
45
+ margin-top: 1px;
37
46
  }
38
- .df-row__note .yc-help-popover {
47
+ .df-row__note-inner .yc-help-popover {
39
48
  display: flex;
40
49
  }
41
- .df-row__note .yc-help-popover > span {
50
+ .df-row__note-inner .yc-help-popover > span {
42
51
  display: flex;
43
52
  }
44
53
  .df-row__right {
@@ -59,6 +68,5 @@
59
68
  margin-left: 5px;
60
69
  }
61
70
  .df-row__required-mark {
62
- margin-left: 2px;
63
71
  color: var(--yc-color-text-danger);
64
72
  }
@@ -11,11 +11,10 @@ const RowBase = ({ name, spec, input, meta, verboseDescription, children, }) =>
11
11
  return (React.createElement("div", { className: b({ 'extra-width': isArraySpec(spec) || arrayItem }) },
12
12
  React.createElement("div", { className: b('left') },
13
13
  React.createElement("div", { className: b('left-inner') },
14
- React.createElement("div", { className: b('title') },
15
- spec.viewSpec.layoutTitle,
16
- spec.required && React.createElement("span", { className: b('required-mark') }, "*")),
17
- !verboseDescription && spec.viewSpec.layoutDescription ? (React.createElement("div", { className: b('note') },
18
- React.createElement(HelpPopover, { htmlContent: spec.viewSpec.layoutDescription, placement: ['bottom', 'top'] }))) : null)),
14
+ React.createElement("span", { className: b('title', { required: spec.required }) }, spec.viewSpec.layoutTitle),
15
+ !verboseDescription && spec.viewSpec.layoutDescription ? (React.createElement("span", { className: b('note') },
16
+ React.createElement("span", { className: b('note-inner') },
17
+ React.createElement(HelpPopover, { htmlContent: spec.viewSpec.layoutDescription, placement: ['bottom', 'top'] })))) : null)),
19
18
  React.createElement("div", { className: b('right') },
20
19
  React.createElement("div", { className: b('right-inner') },
21
20
  React.createElement(ErrorWrapper, { name: name, meta: meta, withoutChildErrorStyles: isArraySpec(spec) || isObjectSpec(spec) }, children),
@@ -0,0 +1,3 @@
1
+ import React from 'react';
2
+ import { StringViewProps } from '../../../../core';
3
+ export declare const FileInputView: React.FC<StringViewProps>;
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ import i18n from '../../../../kit/i18n';
3
+ import { LongValue } from '../../../components';
4
+ export const FileInputView = ({ value, spec }) => {
5
+ var _a;
6
+ return (React.createElement(LongValue, { value: ((_a = spec.viewSpec.fileInput) === null || _a === void 0 ? void 0 : _a.ignoreText) ? i18n('label-data_loaded') : value }));
7
+ };
@@ -0,0 +1 @@
1
+ export * from './FileInputView';
@@ -0,0 +1 @@
1
+ export * from './FileInputView';
@@ -0,0 +1,2 @@
1
+ import { ObjectIndependentView } from '../../../../core';
2
+ export declare const TextLinkView: ObjectIndependentView;
@@ -0,0 +1,12 @@
1
+ import React from 'react';
2
+ import { ViewController } from '../../../../core';
3
+ const TEXT_LINK_PROPERTY_NAME = 'text';
4
+ export const TextLinkView = ({ value, spec, name }) => {
5
+ const specProperties = spec.properties;
6
+ const preparedSpec = React.useMemo(() => {
7
+ var _a;
8
+ return specProperties && specProperties[TEXT_LINK_PROPERTY_NAME]
9
+ ? Object.assign(Object.assign({}, specProperties[TEXT_LINK_PROPERTY_NAME]), { viewSpec: Object.assign(Object.assign({}, (_a = specProperties[TEXT_LINK_PROPERTY_NAME]) === null || _a === void 0 ? void 0 : _a.viewSpec), { link: value === null || value === void 0 ? void 0 : value.link }) }) : undefined;
10
+ }, [specProperties, value === null || value === void 0 ? void 0 : value.link]);
11
+ return preparedSpec ? (React.createElement(ViewController, { spec: preparedSpec, name: `${name}.${TEXT_LINK_PROPERTY_NAME}` })) : null;
12
+ };
@@ -0,0 +1 @@
1
+ export * from './TextLinkView';
@@ -0,0 +1 @@
1
+ export * from './TextLinkView';
@@ -1,6 +1,7 @@
1
1
  export * from './ArrayBaseView';
2
2
  export * from './BaseView';
3
3
  export * from './CardOneOfView';
4
+ export * from './FileInputView';
4
5
  export * from './MonacoInputView';
5
6
  export * from './MultiSelectView';
6
7
  export * from './NumberWithScaleView';
@@ -9,3 +10,4 @@ export * from './OneOfCardView';
9
10
  export * from './OneOfView';
10
11
  export * from './TableArrayView';
11
12
  export * from './TextAreaView';
13
+ export * from './TextLinkView';
@@ -1,6 +1,7 @@
1
1
  export * from './ArrayBaseView';
2
2
  export * from './BaseView';
3
3
  export * from './CardOneOfView';
4
+ export * from './FileInputView';
4
5
  export * from './MonacoInputView';
5
6
  export * from './MultiSelectView';
6
7
  export * from './NumberWithScaleView';
@@ -9,3 +10,4 @@ export * from './OneOfCardView';
9
10
  export * from './OneOfView';
10
11
  export * from './TableArrayView';
11
12
  export * from './TextAreaView';
13
+ export * from './TextLinkView';
@@ -1,4 +1,4 @@
1
- import { Accordeon, AccordeonCardLayout, ArrayBase, ArrayBaseView, BaseView, CardAccordeon, CardOneOf, CardOneOfView, CardSection, Checkbox, Group, Group2, MonacoInput, MonacoInputCard, MonacoView, MonacoViewCard, MultiSelect, MultiSelectView, NumberWithScale, NumberWithScaleView, ObjectBase, ObjectBaseView, OneOf, OneOfCard, OneOfCardView, OneOfView, Row, Row2, RowVerbose, Secret, Section, Section2, SectionCard, SectionCard2, SectionWithSubtitle, SectionWithSubtitle2, Select, TableArrayInput, TableArrayView, TableCell, Text, TextArea, TextAreaView, TextContent, Transparent, ViewAccordeon, ViewAccordeonCard, ViewCardAccordeon, ViewCardSection, ViewGroup, ViewGroup2, ViewRow, ViewRow2, ViewSection, ViewSection2, ViewSectionCard, ViewSectionCard2, ViewTableCell, ViewTransparent, } from '../components';
1
+ import { Accordeon, AccordeonCardLayout, ArrayBase, ArrayBaseView, BaseView, CardAccordeon, CardOneOf, CardOneOfView, CardSection, Checkbox, FileInput, FileInputView, Group, Group2, MonacoInput, MonacoInputCard, MonacoView, MonacoViewCard, MultiSelect, MultiSelectView, NumberWithScale, NumberWithScaleView, ObjectBase, ObjectBaseView, OneOf, OneOfCard, OneOfCardView, OneOfView, Row, Row2, RowVerbose, Secret, Section, Section2, SectionCard, SectionCard2, SectionWithSubtitle, SectionWithSubtitle2, Select, TableArrayInput, TableArrayView, TableCell, Text, TextArea, TextAreaView, TextContent, TextLink, TextLinkView, Transparent, ViewAccordeon, ViewAccordeonCard, ViewCardAccordeon, ViewCardSection, ViewGroup, ViewGroup2, ViewRow, ViewRow2, ViewSection, ViewSection2, ViewSectionCard, ViewSectionCard2, ViewTableCell, ViewTransparent, } from '../components';
2
2
  import { getArrayValidator, getBooleanValidator, getNumberValidator, getObjectValidator, getStringValidator, } from '../validators';
3
3
  export const dynamicConfig = {
4
4
  array: {
@@ -57,6 +57,7 @@ export const dynamicConfig = {
57
57
  card_oneof: { Component: CardOneOf, independent: true },
58
58
  secret: { Component: Secret, independent: true },
59
59
  base: { Component: ObjectBase, independent: true },
60
+ text_link: { Component: TextLink, independent: true },
60
61
  },
61
62
  layouts: {
62
63
  row: Row,
@@ -80,6 +81,7 @@ export const dynamicConfig = {
80
81
  textarea: { Component: TextArea },
81
82
  select: { Component: Select },
82
83
  base: { Component: Text },
84
+ file_input: { Component: FileInput },
83
85
  number_with_scale: { Component: NumberWithScale },
84
86
  monaco_input: { Component: MonacoInput },
85
87
  text_content: { Component: TextContent, independent: true },
@@ -152,6 +154,7 @@ export const dynamicCardConfig = {
152
154
  oneof: { Component: OneOfCard, independent: true },
153
155
  secret: { Component: Secret, independent: true },
154
156
  base: { Component: ObjectBase, independent: true },
157
+ text_link: { Component: TextLink, independent: true },
155
158
  },
156
159
  layouts: {
157
160
  row: Row2,
@@ -172,6 +175,7 @@ export const dynamicCardConfig = {
172
175
  textarea: { Component: TextArea },
173
176
  select: { Component: Select },
174
177
  base: { Component: Text },
178
+ file_input: { Component: FileInput },
175
179
  number_with_scale: { Component: NumberWithScale },
176
180
  monaco_input: { Component: MonacoInputCard },
177
181
  text_content: { Component: TextContent, independent: true },
@@ -236,6 +240,7 @@ export const dynamicViewConfig = {
236
240
  card_oneof: { Component: CardOneOfView, independent: true },
237
241
  secret: undefined,
238
242
  base: { Component: ObjectBaseView, independent: true },
243
+ text_link: { Component: TextLinkView, independent: true },
239
244
  },
240
245
  layouts: {
241
246
  row: ViewRow,
@@ -255,6 +260,7 @@ export const dynamicViewConfig = {
255
260
  textarea: { Component: TextAreaView },
256
261
  select: { Component: BaseView },
257
262
  base: { Component: BaseView },
263
+ file_input: { Component: FileInputView },
258
264
  number_with_scale: { Component: NumberWithScaleView },
259
265
  monaco_input: { Component: MonacoView },
260
266
  text_content: undefined,
@@ -313,6 +319,7 @@ export const dynamicViewCardConfig = {
313
319
  oneof: { Component: OneOfCardView, independent: true },
314
320
  secret: undefined,
315
321
  base: { Component: ObjectBaseView, independent: true },
322
+ text_link: { Component: TextLinkView, independent: true },
316
323
  },
317
324
  layouts: {
318
325
  row: ViewRow2,
@@ -330,6 +337,7 @@ export const dynamicViewCardConfig = {
330
337
  textarea: { Component: TextAreaView },
331
338
  select: { Component: BaseView },
332
339
  base: { Component: BaseView },
340
+ file_input: { Component: FileInputView },
333
341
  number_with_scale: { Component: NumberWithScaleView },
334
342
  monaco_input: { Component: MonacoViewCard },
335
343
  text_content: undefined,
@@ -42,5 +42,7 @@
42
42
  "label_error-zero-start": "Value must not start with a zero",
43
43
  "label_error-dot-end": "Value must not end with a dot",
44
44
  "label_delete": "Delete",
45
- "button_cancel": "Close"
45
+ "button_cancel": "Close",
46
+ "button-upload_file": "Upload file",
47
+ "label-data_loaded": "Data uploaded"
46
48
  }
@@ -42,5 +42,7 @@
42
42
  "label_error-zero-start": "Значение не должно начинаться с нуля",
43
43
  "label_error-dot-end": "Значение не должно заканчиваться точкой",
44
44
  "label_delete": "Удалить",
45
- "button_cancel": "Закрыть"
45
+ "button_cancel": "Закрыть",
46
+ "button-upload_file": "Загрузить файл",
47
+ "label-data_loaded": "Данные загружены"
46
48
  }
@@ -1,6 +1,4 @@
1
- import { ValidationErrors } from 'final-form';
2
1
  import { FormValue, Spec, StringSpec } from '../../core';
3
- export declare const getChildError: (name: string, errors?: ValidationErrors, head?: boolean) => boolean;
4
2
  export declare const isNotEmptyValue: (value: FormValue | undefined, spec: Spec | undefined) => boolean;
5
3
  export declare const prepareSpec: <Type extends Spec>(spec: Type, parseJsonDefaultValue?: boolean) => Type;
6
4
  export declare const isCorrectSizeParams: (spec: StringSpec) => boolean;
@@ -1,31 +1,7 @@
1
- import { ARRAY_ERROR } from 'final-form';
2
1
  import _ from 'lodash';
3
2
  import { isArraySpec, isObjectSpec, isStringSpec, } from '../../core';
4
3
  import { isFloat } from '../validators/helpers';
5
4
  import { divide } from './bigIntMath';
6
- export const getChildError = (name, errors, head) => {
7
- const error = _.get(errors, name);
8
- if (!head && (error === null || error === void 0 ? void 0 : error[ARRAY_ERROR])) {
9
- return true;
10
- }
11
- else if (_.isArray(error)) {
12
- return error.some((itemError, idx) => {
13
- if (_.isArray(itemError) || _.isObjectLike(itemError)) {
14
- return getChildError(`${name}[${idx}]`, errors);
15
- }
16
- return Boolean(itemError);
17
- });
18
- }
19
- else if (_.isObjectLike(error)) {
20
- return Object.keys(error).some((key) => {
21
- if (_.isArray(error[key]) || _.isObjectLike(error[key])) {
22
- return getChildError(`${name}.${key}`, errors);
23
- }
24
- return Boolean(error[key]);
25
- });
26
- }
27
- return false;
28
- };
29
5
  export const isNotEmptyValue = (value, spec) => {
30
6
  var _a;
31
7
  if (_.isNil(value)) {
@@ -17,6 +17,8 @@ export interface GetNumberValidatorParams {
17
17
  ignoreMaximumCheck?: boolean;
18
18
  ignoreMinimumCheck?: boolean;
19
19
  ignoreIntCheck?: boolean;
20
+ ignoreDotEnd?: boolean;
21
+ ignoreZeroStart?: boolean;
20
22
  }
21
23
  export declare const getNumberValidator: (params?: GetNumberValidatorParams) => (spec: NumberSpec, value?: string | number) => string | false;
22
24
  export interface GetObjectValidatorParams {
@@ -31,7 +31,7 @@ export const getBooleanValidator = (params = {}) => {
31
31
  };
32
32
  };
33
33
  export const getNumberValidator = (params = {}) => {
34
- const { ignoreRequiredCheck, ignoreSpaceStartCheck, ignoreSpaceEndCheck, ignoreNumberCheck, ignoreMaximumCheck, ignoreMinimumCheck, ignoreIntCheck, } = params;
34
+ const { ignoreRequiredCheck, ignoreSpaceStartCheck, ignoreSpaceEndCheck, ignoreNumberCheck, ignoreMaximumCheck, ignoreMinimumCheck, ignoreIntCheck, ignoreDotEnd, ignoreZeroStart, } = params;
35
35
  return (spec, value = '') => {
36
36
  const stringValue = String(value);
37
37
  if (!ignoreRequiredCheck && spec.required && !stringValue.length) {
@@ -44,16 +44,17 @@ export const getNumberValidator = (params = {}) => {
44
44
  if (!ignoreSpaceEndCheck && !stringValue[stringValue.length - 1].trim()) {
45
45
  return ErrorMessages.SPACE_END;
46
46
  }
47
- if (stringValue[stringValue.length - 1] === '.') {
47
+ if (!ignoreDotEnd && stringValue[stringValue.length - 1] === '.') {
48
48
  return ErrorMessages.DOT_END;
49
49
  }
50
50
  if (!ignoreNumberCheck && !isFloat(stringValue)) {
51
51
  return ErrorMessages.NUMBER;
52
52
  }
53
- if ((stringValue.length > 1 && stringValue[0] === '0' && stringValue[1] !== '.') ||
54
- (stringValue.length > 2 &&
55
- stringValue.substring(0, 2) === '-0' &&
56
- stringValue[2] !== '.')) {
53
+ if (!ignoreZeroStart &&
54
+ ((stringValue.length > 1 && stringValue[0] === '0' && stringValue[1] !== '.') ||
55
+ (stringValue.length > 2 &&
56
+ stringValue.substring(0, 2) === '-0' &&
57
+ stringValue[2] !== '.'))) {
57
58
  return ErrorMessages.ZERO_START;
58
59
  }
59
60
  }
@@ -65,8 +66,7 @@ export const getNumberValidator = (params = {}) => {
65
66
  }
66
67
  if (!ignoreMinimumCheck &&
67
68
  _.isNumber(spec.minimum) &&
68
- stringValue.length &&
69
- spec.minimum > Number(stringValue)) {
69
+ ((stringValue.length && spec.minimum > Number(stringValue)) || !stringValue.length)) {
70
70
  return ErrorMessages.minNumber(spec.minimum);
71
71
  }
72
72
  if (_.isString(spec.format) && stringValue.length) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gravity-ui/dynamic-forms",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "description": "",
5
5
  "license": "MIT",
6
6
  "main": "build/cjs/index.js",
@@ -28,7 +28,8 @@
28
28
  "lint:js": "eslint src --quiet",
29
29
  "lint:prettier": "prettier --check 'src/**/*.{js,jsx,ts,tsx,css,scss}'",
30
30
  "lint:styles": "stylelint 'src/**/*.scss'",
31
- "typecheck": "tsc -p src --noEmit",
31
+ "test": "jest",
32
+ "typecheck": "tsc --noEmit",
32
33
  "build": "gulp",
33
34
  "build-storybook": "build-storybook",
34
35
  "dev": "start-storybook -p 6006",
@@ -42,10 +43,10 @@
42
43
  "lodash": "^4.17.20"
43
44
  },
44
45
  "devDependencies": {
45
- "@commitlint/cli": "^17.0.0",
46
- "@commitlint/config-conventional": "^17.0.0",
47
46
  "@babel/preset-env": "^7.20.2",
48
47
  "@babel/preset-typescript": "^7.18.6",
48
+ "@commitlint/cli": "^17.0.0",
49
+ "@commitlint/config-conventional": "^17.0.0",
49
50
  "@gravity-ui/eslint-config": "^2.0.0",
50
51
  "@gravity-ui/prettier-config": "^1.0.1",
51
52
  "@gravity-ui/stylelint-config": "^2.0.0",
@@ -54,6 +55,9 @@
54
55
  "@storybook/addon-essentials": "^6.5.16",
55
56
  "@storybook/preset-scss": "^1.0.3",
56
57
  "@storybook/react": "^6.5.16",
58
+ "@testing-library/jest-dom": "^5.16.5",
59
+ "@testing-library/react": "^14.0.0",
60
+ "@types/jest": "^29.5.0",
57
61
  "@types/lodash": "^4.14.180",
58
62
  "@types/react": "^18.0.28",
59
63
  "@types/react-dom": "^18.0.11",
@@ -67,6 +71,9 @@
67
71
  "gulp-replace": "^1.1.4",
68
72
  "gulp-typescript": "^6.0.0-alpha.1",
69
73
  "husky": "^7.0.4",
74
+ "jest": "^29.5.0",
75
+ "jest-environment-jsdom": "^29.5.0",
76
+ "jest-transform-css": "^6.0.1",
70
77
  "monaco-editor": "^0.30.1",
71
78
  "monaco-editor-webpack-plugin": "^6.0.0",
72
79
  "npm-run-all": "^4.1.5",
@@ -83,6 +90,7 @@
83
90
  "style-loader": "^2.0.0",
84
91
  "stylelint": "^14.15.0",
85
92
  "stylelint-scss": "^4.2.0",
93
+ "ts-jest": "^29.0.5",
86
94
  "typescript": "^4.9.5"
87
95
  },
88
96
  "peerDependencies": {