@gravity-ui/dynamic-forms 3.0.0 → 3.1.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 (29) hide show
  1. package/build/cjs/lib/core/components/Form/Controller.js +2 -1
  2. package/build/cjs/lib/core/components/Form/DynamicField.js +3 -2
  3. package/build/cjs/lib/core/components/Form/hooks/useField.js +20 -2
  4. package/build/cjs/lib/core/components/Form/hooks/useStore.js +6 -6
  5. package/build/cjs/lib/core/components/View/DynamicView.js +1 -2
  6. package/build/cjs/lib/kit/components/Inputs/ArrayBase/ArrayBase.css +3 -0
  7. package/build/cjs/lib/kit/components/Inputs/Text/Text.js +16 -31
  8. package/build/cjs/lib/kit/components/Layouts/Transparent/Transparent.css +3 -1
  9. package/build/cjs/lib/kit/components/ViewLayouts/ViewAccordeon/ViewAccordeon.js +2 -1
  10. package/build/cjs/lib/kit/components/ViewLayouts/ViewCardAccordeon.js +2 -1
  11. package/build/esm/lib/core/components/Form/Controller.js +2 -1
  12. package/build/esm/lib/core/components/Form/DynamicField.d.ts +2 -1
  13. package/build/esm/lib/core/components/Form/DynamicField.js +3 -2
  14. package/build/esm/lib/core/components/Form/hooks/useField.d.ts +3 -2
  15. package/build/esm/lib/core/components/Form/hooks/useField.js +20 -2
  16. package/build/esm/lib/core/components/Form/hooks/useStore.js +7 -7
  17. package/build/esm/lib/core/components/Form/types/context.d.ts +2 -1
  18. package/build/esm/lib/core/components/View/DynamicView.d.ts +2 -2
  19. package/build/esm/lib/core/components/View/DynamicView.js +1 -2
  20. package/build/esm/lib/core/components/View/types/context.d.ts +2 -2
  21. package/build/esm/lib/kit/components/Inputs/ArrayBase/ArrayBase.css +3 -0
  22. package/build/esm/lib/kit/components/Inputs/Text/Text.d.ts +1 -2
  23. package/build/esm/lib/kit/components/Inputs/Text/Text.js +17 -33
  24. package/build/esm/lib/kit/components/Layouts/Transparent/Transparent.css +3 -1
  25. package/build/esm/lib/kit/components/ViewLayouts/ViewAccordeon/ViewAccordeon.js +2 -1
  26. package/build/esm/lib/kit/components/ViewLayouts/ViewCardAccordeon.js +2 -1
  27. package/package.json +6 -6
  28. package/build/cjs/lib/kit/components/Inputs/Text/Text.css +0 -6
  29. package/build/esm/lib/kit/components/Inputs/Text/Text.css +0 -6
@@ -5,7 +5,7 @@ const tslib_1 = require("tslib");
5
5
  const lodash_1 = tslib_1.__importDefault(require("lodash"));
6
6
  const hooks_1 = require("./hooks");
7
7
  const Controller = ({ spec, name, value, parentOnChange, parentOnUnmount, }) => {
8
- const { tools, __mirror } = (0, hooks_1.useDynamicFormsCtx)();
8
+ const { tools, externalErrors, __mirror } = (0, hooks_1.useDynamicFormsCtx)();
9
9
  const { inputEntity, Layout } = (0, hooks_1.useComponents)(spec);
10
10
  const render = (0, hooks_1.useRender)({ name, spec, inputEntity, Layout });
11
11
  const validate = (0, hooks_1.useValidate)(spec);
@@ -18,6 +18,7 @@ const Controller = ({ spec, name, value, parentOnChange, parentOnUnmount, }) =>
18
18
  tools,
19
19
  parentOnChange,
20
20
  parentOnUnmount,
21
+ externalErrors,
21
22
  });
22
23
  const withSearch = (0, hooks_1.useSearch)(spec, renderProps.input.value, name);
23
24
  (0, hooks_1.useControllerMirror)(name, {
@@ -9,7 +9,7 @@ const helpers_1 = require("../../helpers");
9
9
  const Controller_1 = require("./Controller");
10
10
  const hooks_1 = require("./hooks");
11
11
  const utils_1 = require("./utils");
12
- const DynamicField = ({ name, spec, config, Monaco, generateRandomValue, search, withoutInsertFFDebounce, __mirror, }) => {
12
+ const DynamicField = ({ name, spec, config, Monaco, generateRandomValue, search, withoutInsertFFDebounce, errors: externalErrors, __mirror, }) => {
13
13
  const DynamicFormsCtx = (0, hooks_1.useCreateContext)();
14
14
  const SearchContext = (0, hooks_1.useCreateSearchContext)();
15
15
  const { tools, store } = (0, hooks_1.useStore)(name);
@@ -20,8 +20,9 @@ const DynamicField = ({ name, spec, config, Monaco, generateRandomValue, search,
20
20
  Monaco: (0, react_is_1.isValidElementType)(Monaco) ? Monaco : undefined,
21
21
  generateRandomValue,
22
22
  tools,
23
+ externalErrors,
23
24
  __mirror,
24
- }), [tools, config, Monaco, __mirror, generateRandomValue]);
25
+ }), [tools, config, Monaco, __mirror, generateRandomValue, externalErrors]);
25
26
  const searchContext = react_1.default.useMemo(() => ({
26
27
  setField,
27
28
  removeField,
@@ -7,7 +7,7 @@ const lodash_1 = tslib_1.__importDefault(require("lodash"));
7
7
  const helpers_1 = require("../../../helpers");
8
8
  const constants_1 = require("../constants");
9
9
  const utils_1 = require("../utils");
10
- const useField = ({ name, spec, initialValue, value: externalValue, validate: propsValidate, tools, parentOnChange, parentOnUnmount: externalParentOnUnmount, }) => {
10
+ const useField = ({ name, spec, initialValue, value: externalValue, validate: propsValidate, tools, parentOnChange, parentOnUnmount: externalParentOnUnmount, externalErrors, }) => {
11
11
  const firstRenderRef = react_1.default.useRef(true);
12
12
  const validate = react_1.default.useCallback((value) => propsValidate === null || propsValidate === void 0 ? void 0 : propsValidate((0, utils_1.transformArrOut)(value)), [propsValidate]);
13
13
  const [state, setState] = react_1.default.useState(() => {
@@ -27,7 +27,13 @@ const useField = ({ name, spec, initialValue, value: externalValue, validate: pr
27
27
  }
28
28
  }
29
29
  }
30
- const error = validate === null || validate === void 0 ? void 0 : validate(value);
30
+ let externalError = lodash_1.default.get(externalErrors, name);
31
+ if (!(lodash_1.default.isString(externalError) ||
32
+ lodash_1.default.isBoolean(externalError) ||
33
+ lodash_1.default.isUndefined(externalError))) {
34
+ externalError = undefined;
35
+ }
36
+ const error = (validate === null || validate === void 0 ? void 0 : validate(value)) || externalError;
31
37
  const dirty = !lodash_1.default.isEqual(value, initialValue);
32
38
  return {
33
39
  active: false,
@@ -138,6 +144,18 @@ const useField = ({ name, spec, initialValue, value: externalValue, validate: pr
138
144
  (parentOnChange ? parentOnChange : tools.onChange)(name, state.value, Object.assign(Object.assign({}, state.childErrors), { [name]: state.error }));
139
145
  }
140
146
  }, [state.value]);
147
+ react_1.default.useEffect(() => {
148
+ const externalError = lodash_1.default.get(externalErrors, name);
149
+ if (!firstRenderRef.current &&
150
+ (lodash_1.default.isString(externalError) ||
151
+ lodash_1.default.isBoolean(externalError) ||
152
+ lodash_1.default.isUndefined(externalError)) &&
153
+ state.error !== externalError &&
154
+ !(state.error && !externalError)) {
155
+ setState(Object.assign(Object.assign({}, state), { error: externalError }));
156
+ (parentOnChange ? parentOnChange : tools.onChange)(name, state.value, Object.assign(Object.assign({}, state.childErrors), { [name]: externalError }));
157
+ }
158
+ }, [externalErrors]);
141
159
  react_1.default.useEffect(() => {
142
160
  firstRenderRef.current = false;
143
161
  return () => {
@@ -7,14 +7,14 @@ const lodash_1 = tslib_1.__importDefault(require("lodash"));
7
7
  const react_final_form_1 = require("react-final-form");
8
8
  const utils_1 = require("../utils");
9
9
  const useStore = (name) => {
10
- const form = (0, react_final_form_1.useForm)();
10
+ const formState = (0, react_final_form_1.useFormState)();
11
11
  const firstRenderRef = react_1.default.useRef(true);
12
12
  const [store, setStore] = react_1.default.useState(() => {
13
13
  const values = (0, utils_1.transformArrIn)({
14
- [name]: lodash_1.default.get(form.getState().values, name),
14
+ [name]: lodash_1.default.get(formState.values, name),
15
15
  });
16
16
  const initialValue = (0, utils_1.transformArrIn)({
17
- [name]: lodash_1.default.get(form.getState().initialValues, name),
17
+ [name]: lodash_1.default.get(formState.initialValues, name),
18
18
  });
19
19
  return {
20
20
  name,
@@ -23,7 +23,7 @@ const useStore = (name) => {
23
23
  errors: {},
24
24
  };
25
25
  });
26
- const submitFailed = form.getState().submitFailed;
26
+ const submitFailed = formState.submitFailed;
27
27
  const tools = react_1.default.useMemo(() => ({
28
28
  initialValue: store.initialValue,
29
29
  onChange: (name, value, errors) => setStore((store) => (Object.assign(Object.assign({}, store), { values: lodash_1.default.set(Object.assign({}, store.values), name, lodash_1.default.clone(value)), errors: errors || {} }))),
@@ -33,10 +33,10 @@ const useStore = (name) => {
33
33
  react_1.default.useEffect(() => {
34
34
  if (!firstRenderRef.current) {
35
35
  const values = (0, utils_1.transformArrIn)({
36
- [name]: lodash_1.default.get(form.getState().values, name),
36
+ [name]: lodash_1.default.get(formState.values, name),
37
37
  });
38
38
  const initialValue = (0, utils_1.transformArrIn)({
39
- [name]: lodash_1.default.get(form.getState().initialValues, name),
39
+ [name]: lodash_1.default.get(formState.initialValues, name),
40
40
  });
41
41
  setStore({
42
42
  name: name,
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DynamicView = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const react_1 = tslib_1.__importDefault(require("react"));
6
- const lodash_1 = tslib_1.__importDefault(require("lodash"));
7
6
  const react_is_1 = require("react-is");
8
7
  const helpers_1 = require("../../helpers");
9
8
  const ViewController_1 = require("./ViewController");
@@ -12,7 +11,7 @@ const hooks_1 = require("./hooks");
12
11
  const DynamicView = ({ value, spec, config, Link, Monaco }) => {
13
12
  const DynamicFormsCtx = (0, hooks_1.useCreateContext)();
14
13
  const context = react_1.default.useMemo(() => ({ config, value, Link, Monaco: (0, react_is_1.isValidElementType)(Monaco) ? Monaco : undefined }), [config, value, Link, Monaco]);
15
- if (lodash_1.default.isObjectLike(value) && (0, helpers_1.isCorrectSpec)(spec) && (0, helpers_2.isCorrectViewConfig)(config)) {
14
+ if ((0, helpers_1.isCorrectSpec)(spec) && (0, helpers_2.isCorrectViewConfig)(config)) {
16
15
  return (react_1.default.createElement(DynamicFormsCtx.Provider, { value: context },
17
16
  react_1.default.createElement(ViewController_1.ViewController, { spec: spec, name: "" })));
18
17
  }
@@ -2,6 +2,9 @@
2
2
  display: flex;
3
3
  align-items: flex-end;
4
4
  }
5
+ .df-array-base_add-button-right .df-transparent {
6
+ align-items: flex-end;
7
+ }
5
8
  .df-array-base__items-wrapper_add-button-down {
6
9
  margin-bottom: 15px;
7
10
  }
@@ -3,38 +3,23 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Text = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const react_1 = tslib_1.__importDefault(require("react"));
6
- const icons_1 = require("@gravity-ui/icons");
6
+ const components_1 = require("@gravity-ui/components");
7
7
  const uikit_1 = require("@gravity-ui/uikit");
8
8
  const lodash_1 = tslib_1.__importDefault(require("lodash"));
9
- const utils_1 = require("../../../utils");
10
- const b = (0, utils_1.block)('text');
11
- const Text = ({ name, input, spec }) => {
12
- const { value, onBlur, onChange, onFocus } = input;
13
- const [hideValue, setHideValue] = react_1.default.useState(spec.viewSpec.type === 'password');
14
- const handleChange = react_1.default.useCallback((value) => {
15
- onChange(value);
16
- }, [onChange, spec]);
17
- const type = react_1.default.useMemo(() => {
18
- if (spec.viewSpec.type === 'password') {
19
- return 'password';
20
- }
21
- return 'text';
22
- }, [spec.viewSpec.type]);
23
- const additionalRightContent = react_1.default.useMemo(() => {
24
- if (type === 'password') {
25
- const onClick = () => {
26
- setHideValue((hideValue) => !hideValue);
27
- };
28
- return (react_1.default.createElement("div", { className: b() },
29
- input.value ? (react_1.default.createElement(uikit_1.CopyToClipboard, { text: String(value), timeout: 500 }, (state) => (react_1.default.createElement(uikit_1.Button, { view: "flat-secondary", className: b('button'), size: "s" },
30
- react_1.default.createElement(uikit_1.Icon, { size: 14, data: state === uikit_1.CopyToClipboardStatus.Pending
31
- ? icons_1.Copy
32
- : icons_1.CopyCheck }))))) : null,
33
- react_1.default.createElement(uikit_1.Button, { view: "flat-secondary", onClick: onClick, className: b('button'), size: "s" },
34
- react_1.default.createElement(uikit_1.Icon, { data: hideValue ? icons_1.Eye : icons_1.EyeSlash, size: 14 }))));
35
- }
36
- return undefined;
37
- }, [hideValue, input.value, type, value]);
38
- return (react_1.default.createElement(uikit_1.TextInput, { type: hideValue ? 'password' : 'text', value: lodash_1.default.isNil(value) ? '' : `${value}`, hasClear: true, onBlur: onBlur, onFocus: onFocus, onUpdate: handleChange, disabled: spec.viewSpec.disabled, placeholder: spec.viewSpec.placeholder, autoComplete: type === 'password' ? 'new-password' : undefined, qa: name, rightContent: additionalRightContent }));
9
+ const Text = ({ name, input: { value, onBlur, onChange, onFocus }, spec, }) => {
10
+ const props = {
11
+ value: lodash_1.default.isNil(value) ? '' : `${value}`,
12
+ hasClear: true,
13
+ onBlur: onBlur,
14
+ onFocus: onFocus,
15
+ onUpdate: onChange,
16
+ disabled: spec.viewSpec.disabled,
17
+ placeholder: spec.viewSpec.placeholder,
18
+ qa: name,
19
+ };
20
+ if (spec.viewSpec.type === 'password') {
21
+ return (react_1.default.createElement(components_1.PasswordInput, Object.assign({}, props, { autoComplete: "new-password", showCopyButton: true, showRevealButton: true })));
22
+ }
23
+ return react_1.default.createElement(uikit_1.TextInput, Object.assign({}, props, { type: "text" }));
39
24
  };
40
25
  exports.Text = Text;
@@ -1,7 +1,6 @@
1
1
  .df-transparent {
2
2
  display: flex;
3
3
  margin-bottom: 15px;
4
- align-items: flex-end;
5
4
  }
6
5
  .df-transparent:last-child {
7
6
  margin-bottom: 0;
@@ -12,6 +11,9 @@
12
11
  .df-transparent_without-max-width {
13
12
  max-width: unset;
14
13
  }
14
+ .df-transparent_without-max-width > .df-error-wrapper {
15
+ width: auto;
16
+ }
15
17
  .df-transparent__remove-button {
16
18
  margin-left: 5px;
17
19
  }
@@ -3,10 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ViewAccordeon = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const react_1 = tslib_1.__importDefault(require("react"));
6
+ const lodash_1 = tslib_1.__importDefault(require("lodash"));
6
7
  const utils_1 = require("../../../utils");
7
8
  const SimpleVerticalAccordeon_1 = require("../../SimpleVerticalAccordeon");
8
9
  const ViewAccordeon = ({ name, value, spec, children, }) => {
9
- const [open, setOpen] = react_1.default.useState(true);
10
+ const [open, setOpen] = react_1.default.useState(lodash_1.default.isBoolean(spec.viewSpec.layoutOpen) ? spec.viewSpec.layoutOpen : true);
10
11
  if (!(0, utils_1.isNotEmptyValue)(value, spec)) {
11
12
  return null;
12
13
  }
@@ -3,10 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ViewCardAccordeon = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const react_1 = tslib_1.__importDefault(require("react"));
6
+ const lodash_1 = tslib_1.__importDefault(require("lodash"));
6
7
  const __1 = require("../");
7
8
  const utils_1 = require("../../utils");
8
9
  const ViewCardAccordeon = ({ name, value, spec, children, }) => {
9
- const [open, setOpen] = react_1.default.useState(true);
10
+ const [open, setOpen] = react_1.default.useState(lodash_1.default.isBoolean(spec.viewSpec.layoutOpen) ? spec.viewSpec.layoutOpen : true);
10
11
  const onToggle = react_1.default.useCallback(() => setOpen((f) => !f), [setOpen]);
11
12
  if (!(0, utils_1.isNotEmptyValue)(value, spec)) {
12
13
  return null;
@@ -1,7 +1,7 @@
1
1
  import _ from 'lodash';
2
2
  import { useComponents, useControllerMirror, useDynamicFormsCtx, useField, useRender, useSearch, useValidate, } from './hooks';
3
3
  export const Controller = ({ spec, name, value, parentOnChange, parentOnUnmount, }) => {
4
- const { tools, __mirror } = useDynamicFormsCtx();
4
+ const { tools, externalErrors, __mirror } = useDynamicFormsCtx();
5
5
  const { inputEntity, Layout } = useComponents(spec);
6
6
  const render = useRender({ name, spec, inputEntity, Layout });
7
7
  const validate = useValidate(spec);
@@ -14,6 +14,7 @@ export const Controller = ({ spec, name, value, parentOnChange, parentOnUnmount,
14
14
  tools,
15
15
  parentOnChange,
16
16
  parentOnUnmount,
17
+ externalErrors,
17
18
  });
18
19
  const withSearch = useSearch(spec, renderProps.input.value, name);
19
20
  useControllerMirror(name, {
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  import type { MonacoEditorProps } from 'react-monaco-editor/lib/types';
3
3
  import { Spec, StringSpec } from '../../types';
4
- import { DynamicFormConfig, FieldValue, WonderMirror } from './types';
4
+ import { BaseValidateError, DynamicFormConfig, FieldValue, WonderMirror } from './types';
5
5
  export interface DynamicFieldProps {
6
6
  name: string;
7
7
  spec: Spec;
@@ -10,6 +10,7 @@ export interface DynamicFieldProps {
10
10
  search?: string | ((spec: Spec, input: FieldValue, name: string) => boolean);
11
11
  generateRandomValue?: (spec: StringSpec) => string;
12
12
  withoutInsertFFDebounce?: boolean;
13
+ errors?: Record<string, BaseValidateError>;
13
14
  __mirror?: WonderMirror;
14
15
  }
15
16
  export declare const DynamicField: React.FC<DynamicFieldProps>;
@@ -5,7 +5,7 @@ import { isCorrectSpec } from '../../helpers';
5
5
  import { Controller } from './Controller';
6
6
  import { useCreateContext, useCreateSearchContext, useDynamicFieldMirror, useIntegrationFF, useSearchStore, useStore, } from './hooks';
7
7
  import { getDefaultSearchFunction, isCorrectConfig } from './utils';
8
- export const DynamicField = ({ name, spec, config, Monaco, generateRandomValue, search, withoutInsertFFDebounce, __mirror, }) => {
8
+ export const DynamicField = ({ name, spec, config, Monaco, generateRandomValue, search, withoutInsertFFDebounce, errors: externalErrors, __mirror, }) => {
9
9
  const DynamicFormsCtx = useCreateContext();
10
10
  const SearchContext = useCreateSearchContext();
11
11
  const { tools, store } = useStore(name);
@@ -16,8 +16,9 @@ export const DynamicField = ({ name, spec, config, Monaco, generateRandomValue,
16
16
  Monaco: isValidElementType(Monaco) ? Monaco : undefined,
17
17
  generateRandomValue,
18
18
  tools,
19
+ externalErrors,
19
20
  __mirror,
20
- }), [tools, config, Monaco, __mirror, generateRandomValue]);
21
+ }), [tools, config, Monaco, __mirror, generateRandomValue, externalErrors]);
21
22
  const searchContext = React.useMemo(() => ({
22
23
  setField,
23
24
  removeField,
@@ -1,5 +1,5 @@
1
1
  import { Spec } from '../../../types';
2
- import { DynamicFormsContext, FieldRenderProps, FieldValue, ValidateError } from '../types';
2
+ import { BaseValidateError, DynamicFormsContext, FieldRenderProps, FieldValue, ValidateError } from '../types';
3
3
  export interface UseFieldProps<Value extends FieldValue, SpecType extends Spec> {
4
4
  name: string;
5
5
  spec: SpecType;
@@ -9,5 +9,6 @@ export interface UseFieldProps<Value extends FieldValue, SpecType extends Spec>
9
9
  tools: DynamicFormsContext['tools'];
10
10
  parentOnChange: ((childName: string, childValue: FieldValue, childErrors: Record<string, ValidateError>) => void) | null;
11
11
  parentOnUnmount: ((childName: string) => void) | null;
12
+ externalErrors?: Record<string, BaseValidateError>;
12
13
  }
13
- export declare const useField: <Value extends FieldValue, SpecType extends Spec>({ name, spec, initialValue, value: externalValue, validate: propsValidate, tools, parentOnChange, parentOnUnmount: externalParentOnUnmount, }: UseFieldProps<Value, SpecType>) => FieldRenderProps<Value>;
14
+ export declare const useField: <Value extends FieldValue, SpecType extends Spec>({ name, spec, initialValue, value: externalValue, validate: propsValidate, tools, parentOnChange, parentOnUnmount: externalParentOnUnmount, externalErrors, }: UseFieldProps<Value, SpecType>) => FieldRenderProps<Value>;
@@ -3,7 +3,7 @@ import _ from 'lodash';
3
3
  import { isArraySpec, isNumberSpec, isObjectSpec } from '../../../helpers';
4
4
  import { OBJECT_ARRAY_CNT, OBJECT_ARRAY_FLAG } from '../constants';
5
5
  import { isArrayItem, transformArrIn, transformArrOut } from '../utils';
6
- export const useField = ({ name, spec, initialValue, value: externalValue, validate: propsValidate, tools, parentOnChange, parentOnUnmount: externalParentOnUnmount, }) => {
6
+ export const useField = ({ name, spec, initialValue, value: externalValue, validate: propsValidate, tools, parentOnChange, parentOnUnmount: externalParentOnUnmount, externalErrors, }) => {
7
7
  const firstRenderRef = React.useRef(true);
8
8
  const validate = React.useCallback((value) => propsValidate === null || propsValidate === void 0 ? void 0 : propsValidate(transformArrOut(value)), [propsValidate]);
9
9
  const [state, setState] = React.useState(() => {
@@ -23,7 +23,13 @@ export const useField = ({ name, spec, initialValue, value: externalValue, valid
23
23
  }
24
24
  }
25
25
  }
26
- const error = validate === null || validate === void 0 ? void 0 : validate(value);
26
+ let externalError = _.get(externalErrors, name);
27
+ if (!(_.isString(externalError) ||
28
+ _.isBoolean(externalError) ||
29
+ _.isUndefined(externalError))) {
30
+ externalError = undefined;
31
+ }
32
+ const error = (validate === null || validate === void 0 ? void 0 : validate(value)) || externalError;
27
33
  const dirty = !_.isEqual(value, initialValue);
28
34
  return {
29
35
  active: false,
@@ -134,6 +140,18 @@ export const useField = ({ name, spec, initialValue, value: externalValue, valid
134
140
  (parentOnChange ? parentOnChange : tools.onChange)(name, state.value, Object.assign(Object.assign({}, state.childErrors), { [name]: state.error }));
135
141
  }
136
142
  }, [state.value]);
143
+ React.useEffect(() => {
144
+ const externalError = _.get(externalErrors, name);
145
+ if (!firstRenderRef.current &&
146
+ (_.isString(externalError) ||
147
+ _.isBoolean(externalError) ||
148
+ _.isUndefined(externalError)) &&
149
+ state.error !== externalError &&
150
+ !(state.error && !externalError)) {
151
+ setState(Object.assign(Object.assign({}, state), { error: externalError }));
152
+ (parentOnChange ? parentOnChange : tools.onChange)(name, state.value, Object.assign(Object.assign({}, state.childErrors), { [name]: externalError }));
153
+ }
154
+ }, [externalErrors]);
137
155
  React.useEffect(() => {
138
156
  firstRenderRef.current = false;
139
157
  return () => {
@@ -1,16 +1,16 @@
1
1
  import React from 'react';
2
2
  import _ from 'lodash';
3
- import { useForm } from 'react-final-form';
3
+ import { useFormState } from 'react-final-form';
4
4
  import { transformArrIn } from '../utils';
5
5
  export const useStore = (name) => {
6
- const form = useForm();
6
+ const formState = useFormState();
7
7
  const firstRenderRef = React.useRef(true);
8
8
  const [store, setStore] = React.useState(() => {
9
9
  const values = transformArrIn({
10
- [name]: _.get(form.getState().values, name),
10
+ [name]: _.get(formState.values, name),
11
11
  });
12
12
  const initialValue = transformArrIn({
13
- [name]: _.get(form.getState().initialValues, name),
13
+ [name]: _.get(formState.initialValues, name),
14
14
  });
15
15
  return {
16
16
  name,
@@ -19,7 +19,7 @@ export const useStore = (name) => {
19
19
  errors: {},
20
20
  };
21
21
  });
22
- const submitFailed = form.getState().submitFailed;
22
+ const submitFailed = formState.submitFailed;
23
23
  const tools = React.useMemo(() => ({
24
24
  initialValue: store.initialValue,
25
25
  onChange: (name, value, errors) => setStore((store) => (Object.assign(Object.assign({}, store), { values: _.set(Object.assign({}, store.values), name, _.clone(value)), errors: errors || {} }))),
@@ -29,10 +29,10 @@ export const useStore = (name) => {
29
29
  React.useEffect(() => {
30
30
  if (!firstRenderRef.current) {
31
31
  const values = transformArrIn({
32
- [name]: _.get(form.getState().values, name),
32
+ [name]: _.get(formState.values, name),
33
33
  });
34
34
  const initialValue = transformArrIn({
35
- [name]: _.get(form.getState().initialValues, name),
35
+ [name]: _.get(formState.initialValues, name),
36
36
  });
37
37
  setStore({
38
38
  name: name,
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  import type { MonacoEditorProps } from 'react-monaco-editor/lib/types';
3
3
  import { StringSpec } from '../../../types';
4
- import { DynamicFormConfig, FieldValue, ValidateError, WonderMirror } from './';
4
+ import { BaseValidateError, DynamicFormConfig, FieldValue, ValidateError, WonderMirror } from './';
5
5
  export interface DynamicFormsContext {
6
6
  config: DynamicFormConfig;
7
7
  Monaco?: React.ComponentType<MonacoEditorProps>;
@@ -12,5 +12,6 @@ export interface DynamicFormsContext {
12
12
  onUnmount: (name: string) => void;
13
13
  submitFailed: boolean;
14
14
  };
15
+ externalErrors?: Record<string, BaseValidateError>;
15
16
  __mirror?: WonderMirror;
16
17
  }
@@ -1,9 +1,9 @@
1
1
  import React from 'react';
2
2
  import type { MonacoEditorProps } from 'react-monaco-editor/lib/types';
3
- import { AnyObject, FormValue, Spec } from '../../types';
3
+ import { FormValue, Spec } from '../../types';
4
4
  import { DynamicViewConfig } from './types';
5
5
  export interface DynamicViewProps {
6
- value: AnyObject;
6
+ value: FormValue;
7
7
  spec: Spec;
8
8
  config: DynamicViewConfig;
9
9
  Link?: React.ComponentType<{
@@ -1,5 +1,4 @@
1
1
  import React from 'react';
2
- import _ from 'lodash';
3
2
  import { isValidElementType } from 'react-is';
4
3
  import { isCorrectSpec } from '../../helpers';
5
4
  import { ViewController } from './ViewController';
@@ -8,7 +7,7 @@ import { useCreateContext } from './hooks';
8
7
  export const DynamicView = ({ value, spec, config, Link, Monaco }) => {
9
8
  const DynamicFormsCtx = useCreateContext();
10
9
  const context = React.useMemo(() => ({ config, value, Link, Monaco: isValidElementType(Monaco) ? Monaco : undefined }), [config, value, Link, Monaco]);
11
- if (_.isObjectLike(value) && isCorrectSpec(spec) && isCorrectViewConfig(config)) {
10
+ if (isCorrectSpec(spec) && isCorrectViewConfig(config)) {
12
11
  return (React.createElement(DynamicFormsCtx.Provider, { value: context },
13
12
  React.createElement(ViewController, { spec: spec, name: "" })));
14
13
  }
@@ -1,10 +1,10 @@
1
1
  import React from 'react';
2
2
  import type { MonacoEditorProps } from 'react-monaco-editor/lib/types';
3
- import { AnyObject, FormValue, Spec } from '../../../types';
3
+ import { FormValue, Spec } from '../../../types';
4
4
  import { DynamicViewConfig } from './';
5
5
  export interface DynamicViewContext {
6
6
  config: DynamicViewConfig;
7
- value: AnyObject;
7
+ value: FormValue;
8
8
  Link?: React.ComponentType<{
9
9
  value: FormValue;
10
10
  link: Spec['viewSpec']['link'];
@@ -2,6 +2,9 @@
2
2
  display: flex;
3
3
  align-items: flex-end;
4
4
  }
5
+ .df-array-base_add-button-right .df-transparent {
6
+ align-items: flex-end;
7
+ }
5
8
  .df-array-base__items-wrapper_add-button-down {
6
9
  margin-bottom: 15px;
7
10
  }
@@ -1,3 +1,2 @@
1
1
  import { NumberInputProps, StringInputProps } from '../../../../core';
2
- import './Text.css';
3
- export declare const Text: <T extends NumberInputProps | StringInputProps>({ name, input, spec }: T) => JSX.Element;
2
+ export declare const Text: <T extends NumberInputProps | StringInputProps>({ name, input: { value, onBlur, onChange, onFocus }, spec, }: T) => JSX.Element;
@@ -1,36 +1,20 @@
1
1
  import React from 'react';
2
- import { Copy, CopyCheck, Eye, EyeSlash } from '@gravity-ui/icons';
3
- import { Button, CopyToClipboard, CopyToClipboardStatus, Icon, TextInput } from '@gravity-ui/uikit';
2
+ import { PasswordInput } from '@gravity-ui/components';
3
+ import { TextInput } from '@gravity-ui/uikit';
4
4
  import _ from 'lodash';
5
- import { block } from '../../../utils';
6
- import './Text.css';
7
- const b = block('text');
8
- export const Text = ({ name, input, spec }) => {
9
- const { value, onBlur, onChange, onFocus } = input;
10
- const [hideValue, setHideValue] = React.useState(spec.viewSpec.type === 'password');
11
- const handleChange = React.useCallback((value) => {
12
- onChange(value);
13
- }, [onChange, spec]);
14
- const type = React.useMemo(() => {
15
- if (spec.viewSpec.type === 'password') {
16
- return 'password';
17
- }
18
- return 'text';
19
- }, [spec.viewSpec.type]);
20
- const additionalRightContent = React.useMemo(() => {
21
- if (type === 'password') {
22
- const onClick = () => {
23
- setHideValue((hideValue) => !hideValue);
24
- };
25
- return (React.createElement("div", { className: b() },
26
- input.value ? (React.createElement(CopyToClipboard, { text: String(value), timeout: 500 }, (state) => (React.createElement(Button, { view: "flat-secondary", className: b('button'), size: "s" },
27
- React.createElement(Icon, { size: 14, data: state === CopyToClipboardStatus.Pending
28
- ? Copy
29
- : CopyCheck }))))) : null,
30
- React.createElement(Button, { view: "flat-secondary", onClick: onClick, className: b('button'), size: "s" },
31
- React.createElement(Icon, { data: hideValue ? Eye : EyeSlash, size: 14 }))));
32
- }
33
- return undefined;
34
- }, [hideValue, input.value, type, value]);
35
- return (React.createElement(TextInput, { type: hideValue ? 'password' : 'text', value: _.isNil(value) ? '' : `${value}`, hasClear: true, onBlur: onBlur, onFocus: onFocus, onUpdate: handleChange, disabled: spec.viewSpec.disabled, placeholder: spec.viewSpec.placeholder, autoComplete: type === 'password' ? 'new-password' : undefined, qa: name, rightContent: additionalRightContent }));
5
+ export const Text = ({ name, input: { value, onBlur, onChange, onFocus }, spec, }) => {
6
+ const props = {
7
+ value: _.isNil(value) ? '' : `${value}`,
8
+ hasClear: true,
9
+ onBlur: onBlur,
10
+ onFocus: onFocus,
11
+ onUpdate: onChange,
12
+ disabled: spec.viewSpec.disabled,
13
+ placeholder: spec.viewSpec.placeholder,
14
+ qa: name,
15
+ };
16
+ if (spec.viewSpec.type === 'password') {
17
+ return (React.createElement(PasswordInput, Object.assign({}, props, { autoComplete: "new-password", showCopyButton: true, showRevealButton: true })));
18
+ }
19
+ return React.createElement(TextInput, Object.assign({}, props, { type: "text" }));
36
20
  };
@@ -1,7 +1,6 @@
1
1
  .df-transparent {
2
2
  display: flex;
3
3
  margin-bottom: 15px;
4
- align-items: flex-end;
5
4
  }
6
5
  .df-transparent:last-child {
7
6
  margin-bottom: 0;
@@ -12,6 +11,9 @@
12
11
  .df-transparent_without-max-width {
13
12
  max-width: unset;
14
13
  }
14
+ .df-transparent_without-max-width > .df-error-wrapper {
15
+ width: auto;
16
+ }
15
17
  .df-transparent__remove-button {
16
18
  margin-left: 5px;
17
19
  }
@@ -1,8 +1,9 @@
1
1
  import React from 'react';
2
+ import _ from 'lodash';
2
3
  import { isNotEmptyValue } from '../../../utils';
3
4
  import { SimpleVerticalAccordeon } from '../../SimpleVerticalAccordeon';
4
5
  export const ViewAccordeon = ({ name, value, spec, children, }) => {
5
- const [open, setOpen] = React.useState(true);
6
+ const [open, setOpen] = React.useState(_.isBoolean(spec.viewSpec.layoutOpen) ? spec.viewSpec.layoutOpen : true);
6
7
  if (!isNotEmptyValue(value, spec)) {
7
8
  return null;
8
9
  }
@@ -1,8 +1,9 @@
1
1
  import React from 'react';
2
+ import _ from 'lodash';
2
3
  import { Card } from '../';
3
4
  import { isNotEmptyValue } from '../../utils';
4
5
  export const ViewCardAccordeon = ({ name, value, spec, children, }) => {
5
- const [open, setOpen] = React.useState(true);
6
+ const [open, setOpen] = React.useState(_.isBoolean(spec.viewSpec.layoutOpen) ? spec.viewSpec.layoutOpen : true);
6
7
  const onToggle = React.useCallback(() => setOpen((f) => !f), [setOpen]);
7
8
  if (!isNotEmptyValue(value, spec)) {
8
9
  return null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gravity-ui/dynamic-forms",
3
- "version": "3.0.0",
3
+ "version": "3.1.0",
4
4
  "description": "",
5
5
  "license": "MIT",
6
6
  "main": "build/cjs/index.js",
@@ -38,9 +38,9 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "@bem-react/classname": "^1.6.0",
41
- "@gravity-ui/components": "^2.4.0",
41
+ "@gravity-ui/components": "^2.8.0",
42
42
  "@gravity-ui/i18n": "^1.1.0",
43
- "@gravity-ui/icons": "^2.5.0",
43
+ "@gravity-ui/icons": "^2.8.1",
44
44
  "lodash": "^4.17.20"
45
45
  },
46
46
  "devDependencies": {
@@ -48,11 +48,11 @@
48
48
  "@babel/preset-typescript": "^7.18.6",
49
49
  "@commitlint/cli": "^17.0.0",
50
50
  "@commitlint/config-conventional": "^17.0.0",
51
- "@gravity-ui/eslint-config": "^2.1.1",
52
- "@gravity-ui/prettier-config": "^1.0.1",
51
+ "@gravity-ui/eslint-config": "^2.2.0",
52
+ "@gravity-ui/prettier-config": "^1.1.0",
53
53
  "@gravity-ui/stylelint-config": "^2.0.0",
54
54
  "@gravity-ui/tsconfig": "^1.0.0",
55
- "@gravity-ui/uikit": "^5.8.0",
55
+ "@gravity-ui/uikit": "^5.19.1",
56
56
  "@storybook/addon-essentials": "^7.0.27",
57
57
  "@storybook/preset-scss": "^1.0.3",
58
58
  "@storybook/react": "^7.0.27",
@@ -1,6 +0,0 @@
1
- .df-text {
2
- display: flex;
3
- }
4
- .df-text__button {
5
- --yc-button-background-color-hover: transparent;
6
- }
@@ -1,6 +0,0 @@
1
- .df-text {
2
- display: flex;
3
- }
4
- .df-text__button {
5
- --yc-button-background-color-hover: transparent;
6
- }