@gravity-ui/dynamic-forms 1.3.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 (45) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +34 -5
  3. package/build/cjs/lib/core/components/View/ViewController.js +2 -2
  4. package/build/cjs/lib/core/components/View/hooks/useComponents.js +1 -3
  5. package/build/cjs/lib/kit/components/Inputs/FileInput/FileInput.css +20 -0
  6. package/build/cjs/lib/kit/components/Inputs/FileInput/FileInput.js +50 -0
  7. package/build/cjs/lib/kit/components/Inputs/FileInput/index.js +4 -0
  8. package/build/cjs/lib/kit/components/Inputs/FileInput/utils.js +16 -0
  9. package/build/cjs/lib/kit/components/Inputs/index.js +1 -0
  10. package/build/cjs/lib/kit/components/Views/FileInputView/FileInputView.js +12 -0
  11. package/build/cjs/lib/kit/components/Views/FileInputView/index.js +4 -0
  12. package/build/cjs/lib/kit/components/Views/index.js +1 -0
  13. package/build/cjs/lib/kit/constants/config.js +4 -0
  14. package/build/cjs/lib/kit/i18n/en.json +3 -1
  15. package/build/cjs/lib/kit/i18n/ru.json +3 -1
  16. package/build/cjs/lib/kit/utils/common.js +1 -26
  17. package/build/cjs/lib/kit/validators/validators.js +7 -6
  18. package/build/esm/lib/core/components/View/ViewController.js +2 -2
  19. package/build/esm/lib/core/components/View/hooks/useComponents.d.ts +2 -2
  20. package/build/esm/lib/core/components/View/hooks/useComponents.js +1 -3
  21. package/build/esm/lib/core/constants.d.ts +1 -0
  22. package/build/esm/lib/core/types/specs.d.ts +6 -1
  23. package/build/esm/lib/kit/components/Inputs/FileInput/FileInput.css +20 -0
  24. package/build/esm/lib/kit/components/Inputs/FileInput/FileInput.d.ts +4 -0
  25. package/build/esm/lib/kit/components/Inputs/FileInput/FileInput.js +46 -0
  26. package/build/esm/lib/kit/components/Inputs/FileInput/index.d.ts +1 -0
  27. package/build/esm/lib/kit/components/Inputs/FileInput/index.js +1 -0
  28. package/build/esm/lib/kit/components/Inputs/FileInput/utils.d.ts +2 -0
  29. package/build/esm/lib/kit/components/Inputs/FileInput/utils.js +12 -0
  30. package/build/esm/lib/kit/components/Inputs/index.d.ts +1 -0
  31. package/build/esm/lib/kit/components/Inputs/index.js +1 -0
  32. package/build/esm/lib/kit/components/Views/FileInputView/FileInputView.d.ts +3 -0
  33. package/build/esm/lib/kit/components/Views/FileInputView/FileInputView.js +7 -0
  34. package/build/esm/lib/kit/components/Views/FileInputView/index.d.ts +1 -0
  35. package/build/esm/lib/kit/components/Views/FileInputView/index.js +1 -0
  36. package/build/esm/lib/kit/components/Views/index.d.ts +1 -0
  37. package/build/esm/lib/kit/components/Views/index.js +1 -0
  38. package/build/esm/lib/kit/constants/config.js +5 -1
  39. package/build/esm/lib/kit/i18n/en.json +3 -1
  40. package/build/esm/lib/kit/i18n/ru.json +3 -1
  41. package/build/esm/lib/kit/utils/common.d.ts +0 -2
  42. package/build/esm/lib/kit/utils/common.js +0 -24
  43. package/build/esm/lib/kit/validators/validators.d.ts +2 -0
  44. package/build/esm/lib/kit/validators/validators.js +7 -6
  45. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.4.0](https://github.com/gravity-ui/dynamic-forms/compare/v1.3.0...v1.4.0) (2023-04-11)
4
+
5
+
6
+ ### Features
7
+
8
+ * **FileInput:** added new input file input ([#24](https://github.com/gravity-ui/dynamic-forms/issues/24)) ([1553318](https://github.com/gravity-ui/dynamic-forms/commit/155331839e50ab4192b69ba32992e37b972378c4))
9
+
3
10
  ## [1.3.0](https://github.com/gravity-ui/dynamic-forms/compare/v1.2.0...v1.3.0) (2023-03-31)
4
11
 
5
12
 
package/README.md CHANGED
@@ -1,10 +1,6 @@
1
1
  # @gravity-ui/dynamic-forms · [![npm package](https://img.shields.io/npm/v/@gravity-ui/dynamic-forms)](https://www.npmjs.com/package/@gravity-ui/dynamic-forms) [![CI](https://img.shields.io/github/actions/workflow/status/gravity-ui/dynamic-forms/.github/workflows/ci.yml?label=CI&logo=github)](https://github.com/gravity-ui/dynamic-forms/actions/workflows/ci.yml?query=branch:main) [![storybook](https://img.shields.io/badge/Storybook-deployed-ff4685)](https://preview.gravity-ui.com/dynamic-forms/)
2
2
 
3
- This is a template for typical package.
4
-
5
- 1. Create a new repository and use this repository as a template.
6
- 2. Replace `dynamic-forms` through the whole repository with your name.
7
- 3. Overwrite other things at your desire.
3
+ The JSON Schema-based library for rendering forms and form values.
8
4
 
9
5
  ## Install
10
6
 
@@ -13,3 +9,36 @@ npm install --save-dev @gravity-ui/dynamic-forms
13
9
  ```
14
10
 
15
11
  ## Usage
12
+
13
+ ```jsx
14
+ import {DynamicField, Spec, dynamicConfig} from '@gravity-ui/dynamic-forms';
15
+
16
+ // To embed in a final-form
17
+ <DynamicField name={name} spec={spec} config={config} />;
18
+
19
+ import {DynamicView, dynamicViewConfig} from '@gravity-ui/dynamic-forms';
20
+
21
+ // To get an overview of the values
22
+ <DynamicView value={value} spec={spec} config={dynamicViewConfig} />;
23
+ ```
24
+
25
+ ### I18N
26
+
27
+ Certain components include text tokens (words and phrases) that are available in two languages: `en` (the default) and `ru`. To set the language, use the `configure` function:
28
+
29
+ ```js
30
+ // index.js
31
+
32
+ import {configure, Lang} from '@gravity-ui/dynamic-forms';
33
+
34
+ configure({lang: Lang.Ru});
35
+ ```
36
+
37
+ ## Development
38
+
39
+ To start the development server with storybook execute the following command:
40
+
41
+ ```shell
42
+ npm ci
43
+ npm run dev
44
+ ```
@@ -5,8 +5,8 @@ const tslib_1 = require("tslib");
5
5
  const react_1 = tslib_1.__importDefault(require("react"));
6
6
  const hooks_1 = require("./hooks");
7
7
  const ViewController = ({ spec, name, }) => {
8
- const { value, Link } = (0, hooks_1.useDynamicFormsCtx)();
9
- const { viewEntity, Layout } = (0, hooks_1.useComponents)(spec);
8
+ const { config, value, Link } = (0, hooks_1.useDynamicFormsCtx)();
9
+ const { viewEntity, Layout } = (0, hooks_1.useComponents)(spec, config);
10
10
  const render = (0, hooks_1.useRender)({ name, value, spec, viewEntity, Layout, Link });
11
11
  return react_1.default.createElement(react_1.default.Fragment, null, render);
12
12
  };
@@ -7,10 +7,8 @@ const lodash_1 = tslib_1.__importDefault(require("lodash"));
7
7
  const react_is_1 = require("react-is");
8
8
  const helpers_1 = require("../../../helpers");
9
9
  const helpers_2 = require("../helpers");
10
- const _1 = require("./");
11
- const useComponents = (spec) => {
10
+ const useComponents = (spec, config) => {
12
11
  var _a, _b;
13
- const { config } = (0, _1.useDynamicFormsCtx)();
14
12
  const { views, layouts } = react_1.default.useMemo(() => {
15
13
  if ((0, helpers_2.isCorrectViewConfig)(config) && (0, helpers_1.isCorrectSpec)(spec)) {
16
14
  return config[spec.type];
@@ -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,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FileInput = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const react_1 = tslib_1.__importDefault(require("react"));
6
+ const icons_1 = require("@gravity-ui/icons");
7
+ const uikit_1 = require("@gravity-ui/uikit");
8
+ const i18n_1 = tslib_1.__importDefault(require("../../../../kit/i18n"));
9
+ const utils_1 = require("../../../utils");
10
+ const utils_2 = require("./utils");
11
+ const b = (0, utils_1.block)('file-input');
12
+ const FileInput = ({ input, spec }) => {
13
+ var _a, _b;
14
+ const { value, onChange } = input;
15
+ const inputRef = react_1.default.useRef(null);
16
+ const [fileName, setFileName] = react_1.default.useState('');
17
+ const handleClick = react_1.default.useCallback(() => {
18
+ var _a;
19
+ (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.click();
20
+ }, []);
21
+ const handleDownload = react_1.default.useCallback(async (file) => { var _a; return await (0, utils_2.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]);
22
+ const handleReset = react_1.default.useCallback(() => {
23
+ setFileName('');
24
+ onChange('');
25
+ }, [onChange]);
26
+ const handleInputChange = react_1.default.useCallback(async (event) => {
27
+ const file = event.target.files;
28
+ if (file && file.length > 0) {
29
+ setFileName(file[0].name);
30
+ const data = (await handleDownload(file[0]));
31
+ onChange(data);
32
+ }
33
+ }, [handleDownload, onChange]);
34
+ const fileNameContent = react_1.default.useMemo(() => {
35
+ if (value) {
36
+ if (fileName) {
37
+ return react_1.default.createElement(react_1.default.Fragment, null, fileName);
38
+ }
39
+ return (react_1.default.createElement(uikit_1.Label, { size: "m", theme: "info" }, (0, i18n_1.default)('label-data_loaded')));
40
+ }
41
+ return null;
42
+ }, [fileName, value]);
43
+ return (react_1.default.createElement("div", { className: b() },
44
+ react_1.default.createElement(uikit_1.Button, { disabled: spec.viewSpec.disabled, onClick: handleClick }, (0, i18n_1.default)('button-upload_file')),
45
+ react_1.default.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 }),
46
+ react_1.default.createElement("span", { className: b('file-name') }, fileNameContent),
47
+ value ? (react_1.default.createElement(uikit_1.Button, { view: "flat", onClick: handleReset, disabled: spec.viewSpec.disabled },
48
+ react_1.default.createElement(uikit_1.Icon, { data: icons_1.Xmark, size: 16 }))) : null));
49
+ };
50
+ exports.FileInput = FileInput;
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ tslib_1.__exportStar(require("./FileInput"), exports);
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readFile = void 0;
4
+ function readFile(file, readAsMethod = 'readAsBinaryString') {
5
+ return new Promise((resolve, reject) => {
6
+ const reader = new FileReader();
7
+ if (typeof reader[readAsMethod] !== 'function') {
8
+ reject(new Error(`Unknown parameter: ${readAsMethod}`));
9
+ return;
10
+ }
11
+ reader.addEventListener('load', () => resolve(reader.result));
12
+ reader.addEventListener('error', () => reject(reader.error));
13
+ reader[readAsMethod](file);
14
+ });
15
+ }
16
+ exports.readFile = readFile;
@@ -4,6 +4,7 @@ const tslib_1 = require("tslib");
4
4
  tslib_1.__exportStar(require("./ArrayBase"), exports);
5
5
  tslib_1.__exportStar(require("./CardOneOf"), exports);
6
6
  tslib_1.__exportStar(require("./Checkbox"), exports);
7
+ tslib_1.__exportStar(require("./FileInput"), exports);
7
8
  tslib_1.__exportStar(require("./MultiSelect"), exports);
8
9
  tslib_1.__exportStar(require("./ObjectBase"), exports);
9
10
  tslib_1.__exportStar(require("./OneOf"), exports);
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FileInputView = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const react_1 = tslib_1.__importDefault(require("react"));
6
+ const i18n_1 = tslib_1.__importDefault(require("../../../../kit/i18n"));
7
+ const components_1 = require("../../../components");
8
+ const FileInputView = ({ value, spec }) => {
9
+ var _a;
10
+ return (react_1.default.createElement(components_1.LongValue, { value: ((_a = spec.viewSpec.fileInput) === null || _a === void 0 ? void 0 : _a.ignoreText) ? (0, i18n_1.default)('label-data_loaded') : value }));
11
+ };
12
+ exports.FileInputView = FileInputView;
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ tslib_1.__exportStar(require("./FileInputView"), exports);
@@ -4,6 +4,7 @@ const tslib_1 = require("tslib");
4
4
  tslib_1.__exportStar(require("./ArrayBaseView"), exports);
5
5
  tslib_1.__exportStar(require("./BaseView"), exports);
6
6
  tslib_1.__exportStar(require("./CardOneOfView"), exports);
7
+ tslib_1.__exportStar(require("./FileInputView"), exports);
7
8
  tslib_1.__exportStar(require("./MonacoInputView"), exports);
8
9
  tslib_1.__exportStar(require("./MultiSelectView"), exports);
9
10
  tslib_1.__exportStar(require("./NumberWithScaleView"), exports);
@@ -84,6 +84,7 @@ exports.dynamicConfig = {
84
84
  textarea: { Component: components_1.TextArea },
85
85
  select: { Component: components_1.Select },
86
86
  base: { Component: components_1.Text },
87
+ file_input: { Component: components_1.FileInput },
87
88
  number_with_scale: { Component: components_1.NumberWithScale },
88
89
  monaco_input: { Component: components_1.MonacoInput },
89
90
  text_content: { Component: components_1.TextContent, independent: true },
@@ -177,6 +178,7 @@ exports.dynamicCardConfig = {
177
178
  textarea: { Component: components_1.TextArea },
178
179
  select: { Component: components_1.Select },
179
180
  base: { Component: components_1.Text },
181
+ file_input: { Component: components_1.FileInput },
180
182
  number_with_scale: { Component: components_1.NumberWithScale },
181
183
  monaco_input: { Component: components_1.MonacoInputCard },
182
184
  text_content: { Component: components_1.TextContent, independent: true },
@@ -261,6 +263,7 @@ exports.dynamicViewConfig = {
261
263
  textarea: { Component: components_1.TextAreaView },
262
264
  select: { Component: components_1.BaseView },
263
265
  base: { Component: components_1.BaseView },
266
+ file_input: { Component: components_1.FileInputView },
264
267
  number_with_scale: { Component: components_1.NumberWithScaleView },
265
268
  monaco_input: { Component: components_1.MonacoView },
266
269
  text_content: undefined,
@@ -337,6 +340,7 @@ exports.dynamicViewCardConfig = {
337
340
  textarea: { Component: components_1.TextAreaView },
338
341
  select: { Component: components_1.BaseView },
339
342
  base: { Component: components_1.BaseView },
343
+ file_input: { Component: components_1.FileInputView },
340
344
  number_with_scale: { Component: components_1.NumberWithScaleView },
341
345
  monaco_input: { Component: components_1.MonacoViewCard },
342
346
  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,36 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isCorrectSizeParams = exports.prepareSpec = exports.isNotEmptyValue = exports.getChildError = void 0;
3
+ exports.isCorrectSizeParams = exports.prepareSpec = exports.isNotEmptyValue = void 0;
4
4
  const tslib_1 = require("tslib");
5
- const final_form_1 = require("final-form");
6
5
  const lodash_1 = tslib_1.__importDefault(require("lodash"));
7
6
  const core_1 = require("../../core");
8
7
  const helpers_1 = require("../validators/helpers");
9
8
  const bigIntMath_1 = require("./bigIntMath");
10
- const getChildError = (name, errors, head) => {
11
- const error = lodash_1.default.get(errors, name);
12
- if (!head && (error === null || error === void 0 ? void 0 : error[final_form_1.ARRAY_ERROR])) {
13
- return true;
14
- }
15
- else if (lodash_1.default.isArray(error)) {
16
- return error.some((itemError, idx) => {
17
- if (lodash_1.default.isArray(itemError) || lodash_1.default.isObjectLike(itemError)) {
18
- return (0, exports.getChildError)(`${name}[${idx}]`, errors);
19
- }
20
- return Boolean(itemError);
21
- });
22
- }
23
- else if (lodash_1.default.isObjectLike(error)) {
24
- return Object.keys(error).some((key) => {
25
- if (lodash_1.default.isArray(error[key]) || lodash_1.default.isObjectLike(error[key])) {
26
- return (0, exports.getChildError)(`${name}.${key}`, errors);
27
- }
28
- return Boolean(error[key]);
29
- });
30
- }
31
- return false;
32
- };
33
- exports.getChildError = getChildError;
34
9
  const isNotEmptyValue = (value, spec) => {
35
10
  var _a;
36
11
  if (lodash_1.default.isNil(value)) {
@@ -37,7 +37,7 @@ const getBooleanValidator = (params = {}) => {
37
37
  };
38
38
  exports.getBooleanValidator = getBooleanValidator;
39
39
  const getNumberValidator = (params = {}) => {
40
- const { ignoreRequiredCheck, ignoreSpaceStartCheck, ignoreSpaceEndCheck, ignoreNumberCheck, ignoreMaximumCheck, ignoreMinimumCheck, ignoreIntCheck, } = params;
40
+ const { ignoreRequiredCheck, ignoreSpaceStartCheck, ignoreSpaceEndCheck, ignoreNumberCheck, ignoreMaximumCheck, ignoreMinimumCheck, ignoreIntCheck, ignoreDotEnd, ignoreZeroStart, } = params;
41
41
  return (spec, value = '') => {
42
42
  const stringValue = String(value);
43
43
  if (!ignoreRequiredCheck && spec.required && !stringValue.length) {
@@ -50,16 +50,17 @@ const getNumberValidator = (params = {}) => {
50
50
  if (!ignoreSpaceEndCheck && !stringValue[stringValue.length - 1].trim()) {
51
51
  return validators_1.ErrorMessages.SPACE_END;
52
52
  }
53
- if (stringValue[stringValue.length - 1] === '.') {
53
+ if (!ignoreDotEnd && stringValue[stringValue.length - 1] === '.') {
54
54
  return validators_1.ErrorMessages.DOT_END;
55
55
  }
56
56
  if (!ignoreNumberCheck && !(0, helpers_1.isFloat)(stringValue)) {
57
57
  return validators_1.ErrorMessages.NUMBER;
58
58
  }
59
- if ((stringValue.length > 1 && stringValue[0] === '0' && stringValue[1] !== '.') ||
60
- (stringValue.length > 2 &&
61
- stringValue.substring(0, 2) === '-0' &&
62
- stringValue[2] !== '.')) {
59
+ if (!ignoreZeroStart &&
60
+ ((stringValue.length > 1 && stringValue[0] === '0' && stringValue[1] !== '.') ||
61
+ (stringValue.length > 2 &&
62
+ stringValue.substring(0, 2) === '-0' &&
63
+ stringValue[2] !== '.'))) {
63
64
  return validators_1.ErrorMessages.ZERO_START;
64
65
  }
65
66
  }
@@ -1,8 +1,8 @@
1
1
  import React from 'react';
2
2
  import { useComponents, useDynamicFormsCtx, useRender } from './hooks';
3
3
  export const ViewController = ({ spec, name, }) => {
4
- const { value, Link } = useDynamicFormsCtx();
5
- const { viewEntity, Layout } = useComponents(spec);
4
+ const { config, value, Link } = useDynamicFormsCtx();
5
+ const { viewEntity, Layout } = useComponents(spec, config);
6
6
  const render = useRender({ name, value, spec, viewEntity, Layout, Link });
7
7
  return React.createElement(React.Fragment, null, render);
8
8
  };
@@ -1,6 +1,6 @@
1
1
  import { FormValue, Spec } from '../../../types';
2
- import { IndependentViewEntity, ViewEntity, ViewLayoutType } from '../types';
3
- export declare const useComponents: <Value extends FormValue, SpecType extends Spec>(spec: SpecType) => {
2
+ import { DynamicViewConfig, IndependentViewEntity, ViewEntity, ViewLayoutType } from '../types';
3
+ export declare const useComponents: <Value extends FormValue, SpecType extends Spec>(spec: SpecType, config: DynamicViewConfig) => {
4
4
  viewEntity: ViewEntity<Value, SpecType> | IndependentViewEntity<Value, SpecType> | undefined;
5
5
  Layout: ViewLayoutType<Value, SpecType> | undefined;
6
6
  };
@@ -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
+ }
@@ -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';
@@ -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';
@@ -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';
@@ -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';
@@ -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';
@@ -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, TextLink, TextLinkView, 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: {
@@ -81,6 +81,7 @@ export const dynamicConfig = {
81
81
  textarea: { Component: TextArea },
82
82
  select: { Component: Select },
83
83
  base: { Component: Text },
84
+ file_input: { Component: FileInput },
84
85
  number_with_scale: { Component: NumberWithScale },
85
86
  monaco_input: { Component: MonacoInput },
86
87
  text_content: { Component: TextContent, independent: true },
@@ -174,6 +175,7 @@ export const dynamicCardConfig = {
174
175
  textarea: { Component: TextArea },
175
176
  select: { Component: Select },
176
177
  base: { Component: Text },
178
+ file_input: { Component: FileInput },
177
179
  number_with_scale: { Component: NumberWithScale },
178
180
  monaco_input: { Component: MonacoInputCard },
179
181
  text_content: { Component: TextContent, independent: true },
@@ -258,6 +260,7 @@ export const dynamicViewConfig = {
258
260
  textarea: { Component: TextAreaView },
259
261
  select: { Component: BaseView },
260
262
  base: { Component: BaseView },
263
+ file_input: { Component: FileInputView },
261
264
  number_with_scale: { Component: NumberWithScaleView },
262
265
  monaco_input: { Component: MonacoView },
263
266
  text_content: undefined,
@@ -334,6 +337,7 @@ export const dynamicViewCardConfig = {
334
337
  textarea: { Component: TextAreaView },
335
338
  select: { Component: BaseView },
336
339
  base: { Component: BaseView },
340
+ file_input: { Component: FileInputView },
337
341
  number_with_scale: { Component: NumberWithScaleView },
338
342
  monaco_input: { Component: MonacoViewCard },
339
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gravity-ui/dynamic-forms",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "",
5
5
  "license": "MIT",
6
6
  "main": "build/cjs/index.js",