@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.
- package/CHANGELOG.md +7 -0
- package/README.md +34 -5
- package/build/cjs/lib/core/components/View/ViewController.js +2 -2
- package/build/cjs/lib/core/components/View/hooks/useComponents.js +1 -3
- package/build/cjs/lib/kit/components/Inputs/FileInput/FileInput.css +20 -0
- package/build/cjs/lib/kit/components/Inputs/FileInput/FileInput.js +50 -0
- package/build/cjs/lib/kit/components/Inputs/FileInput/index.js +4 -0
- package/build/cjs/lib/kit/components/Inputs/FileInput/utils.js +16 -0
- package/build/cjs/lib/kit/components/Inputs/index.js +1 -0
- package/build/cjs/lib/kit/components/Views/FileInputView/FileInputView.js +12 -0
- package/build/cjs/lib/kit/components/Views/FileInputView/index.js +4 -0
- package/build/cjs/lib/kit/components/Views/index.js +1 -0
- package/build/cjs/lib/kit/constants/config.js +4 -0
- package/build/cjs/lib/kit/i18n/en.json +3 -1
- package/build/cjs/lib/kit/i18n/ru.json +3 -1
- package/build/cjs/lib/kit/utils/common.js +1 -26
- package/build/cjs/lib/kit/validators/validators.js +7 -6
- package/build/esm/lib/core/components/View/ViewController.js +2 -2
- package/build/esm/lib/core/components/View/hooks/useComponents.d.ts +2 -2
- package/build/esm/lib/core/components/View/hooks/useComponents.js +1 -3
- package/build/esm/lib/core/constants.d.ts +1 -0
- package/build/esm/lib/core/types/specs.d.ts +6 -1
- package/build/esm/lib/kit/components/Inputs/FileInput/FileInput.css +20 -0
- package/build/esm/lib/kit/components/Inputs/FileInput/FileInput.d.ts +4 -0
- package/build/esm/lib/kit/components/Inputs/FileInput/FileInput.js +46 -0
- package/build/esm/lib/kit/components/Inputs/FileInput/index.d.ts +1 -0
- package/build/esm/lib/kit/components/Inputs/FileInput/index.js +1 -0
- package/build/esm/lib/kit/components/Inputs/FileInput/utils.d.ts +2 -0
- package/build/esm/lib/kit/components/Inputs/FileInput/utils.js +12 -0
- package/build/esm/lib/kit/components/Inputs/index.d.ts +1 -0
- package/build/esm/lib/kit/components/Inputs/index.js +1 -0
- package/build/esm/lib/kit/components/Views/FileInputView/FileInputView.d.ts +3 -0
- package/build/esm/lib/kit/components/Views/FileInputView/FileInputView.js +7 -0
- package/build/esm/lib/kit/components/Views/FileInputView/index.d.ts +1 -0
- package/build/esm/lib/kit/components/Views/FileInputView/index.js +1 -0
- package/build/esm/lib/kit/components/Views/index.d.ts +1 -0
- package/build/esm/lib/kit/components/Views/index.js +1 -0
- package/build/esm/lib/kit/constants/config.js +5 -1
- package/build/esm/lib/kit/i18n/en.json +3 -1
- package/build/esm/lib/kit/i18n/ru.json +3 -1
- package/build/esm/lib/kit/utils/common.d.ts +0 -2
- package/build/esm/lib/kit/utils/common.js +0 -24
- package/build/esm/lib/kit/validators/validators.d.ts +2 -0
- package/build/esm/lib/kit/validators/validators.js +7 -6
- 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 · [](https://www.npmjs.com/package/@gravity-ui/dynamic-forms) [](https://github.com/gravity-ui/dynamic-forms/actions/workflows/ci.yml?query=branch:main) [](https://preview.gravity-ui.com/dynamic-forms/)
|
|
2
2
|
|
|
3
|
-
|
|
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
|
|
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,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;
|
|
@@ -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 =
|
|
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 (
|
|
60
|
-
(stringValue.length >
|
|
61
|
-
stringValue.
|
|
62
|
-
|
|
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
|
-
|
|
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];
|
|
@@ -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,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,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
|
+
}
|
|
@@ -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,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 (
|
|
54
|
-
(stringValue.length >
|
|
55
|
-
stringValue.
|
|
56
|
-
|
|
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
|
}
|