@gravity-ui/dynamic-forms 1.1.1 → 1.3.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 (101) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/build/cjs/lib/core/components/Form/Controller.js +10 -2
  3. package/build/cjs/lib/core/components/Form/DynamicField.js +23 -7
  4. package/build/cjs/lib/core/components/Form/hooks/index.js +7 -0
  5. package/build/cjs/lib/core/components/Form/hooks/useComponents.js +2 -2
  6. package/build/cjs/lib/core/components/Form/hooks/useControllerMirror.js +19 -0
  7. package/build/cjs/lib/core/components/Form/hooks/useCreateSearchContext.js +9 -0
  8. package/build/cjs/lib/core/components/Form/hooks/useDynamicFieldMirror.js +22 -0
  9. package/build/cjs/lib/core/components/Form/hooks/useField.js +7 -7
  10. package/build/cjs/lib/core/components/Form/hooks/useIntegrationFF.js +46 -0
  11. package/build/cjs/lib/core/components/Form/hooks/useSearch/index.js +4 -0
  12. package/build/cjs/lib/core/components/Form/hooks/useSearch/useSearch.css +9 -0
  13. package/build/cjs/lib/core/components/Form/hooks/useSearch/useSearch.js +22 -0
  14. package/build/cjs/lib/core/components/Form/hooks/useSearchContext.js +12 -0
  15. package/build/cjs/lib/core/components/Form/hooks/useSearchStore.js +41 -0
  16. package/build/cjs/lib/core/components/Form/hooks/useStore.js +6 -38
  17. package/build/cjs/lib/core/components/Form/hooks/useValidate.js +2 -2
  18. package/build/cjs/lib/core/components/Form/index.js +1 -1
  19. package/build/cjs/lib/core/components/Form/types/index.js +3 -0
  20. package/build/cjs/lib/core/components/Form/types/mirror.js +2 -0
  21. package/build/cjs/lib/core/components/Form/types/search.js +2 -0
  22. package/build/cjs/lib/core/components/Form/types/store.js +2 -0
  23. package/build/cjs/lib/core/components/Form/{helpers.js → utils/common.js} +2 -2
  24. package/build/cjs/lib/core/components/Form/utils/index.js +5 -0
  25. package/build/cjs/lib/core/components/Form/utils/search.js +19 -0
  26. package/build/cjs/lib/kit/components/Inputs/TableArrayInput/TableArrayInput.css +10 -0
  27. package/build/cjs/lib/kit/components/Inputs/TableArrayInput/TableArrayInput.js +20 -9
  28. package/build/cjs/lib/kit/components/Inputs/TextLink/TextLink.js +20 -0
  29. package/build/cjs/lib/kit/components/Inputs/TextLink/index.js +4 -0
  30. package/build/cjs/lib/kit/components/Inputs/index.js +1 -0
  31. package/build/cjs/lib/kit/components/Layouts/Row/Row.css +18 -10
  32. package/build/cjs/lib/kit/components/Layouts/Row/Row.js +4 -5
  33. package/build/cjs/lib/kit/components/Views/TextLinkView/TextLinkView.js +17 -0
  34. package/build/cjs/lib/kit/components/Views/TextLinkView/index.js +4 -0
  35. package/build/cjs/lib/kit/components/Views/index.js +1 -0
  36. package/build/cjs/lib/kit/constants/config.js +4 -0
  37. package/build/cjs/lib/kit/validators/validators.js +1 -2
  38. package/build/esm/lib/core/components/Form/Controller.js +11 -3
  39. package/build/esm/lib/core/components/Form/DynamicField.d.ts +3 -1
  40. package/build/esm/lib/core/components/Form/DynamicField.js +23 -7
  41. package/build/esm/lib/core/components/Form/hooks/index.d.ts +7 -0
  42. package/build/esm/lib/core/components/Form/hooks/index.js +7 -0
  43. package/build/esm/lib/core/components/Form/hooks/useComponents.js +1 -1
  44. package/build/esm/lib/core/components/Form/hooks/useControllerMirror.d.ts +2 -0
  45. package/build/esm/lib/core/components/Form/hooks/useControllerMirror.js +14 -0
  46. package/build/esm/lib/core/components/Form/hooks/useCreateSearchContext.d.ts +3 -0
  47. package/build/esm/lib/core/components/Form/hooks/useCreateSearchContext.js +4 -0
  48. package/build/esm/lib/core/components/Form/hooks/useDynamicFieldMirror.d.ts +2 -0
  49. package/build/esm/lib/core/components/Form/hooks/useDynamicFieldMirror.js +17 -0
  50. package/build/esm/lib/core/components/Form/hooks/useField.d.ts +2 -2
  51. package/build/esm/lib/core/components/Form/hooks/useField.js +2 -2
  52. package/build/esm/lib/core/components/Form/hooks/useIntegrationFF.d.ts +2 -0
  53. package/build/esm/lib/core/components/Form/hooks/useIntegrationFF.js +41 -0
  54. package/build/esm/lib/core/components/Form/hooks/useSearch/index.d.ts +1 -0
  55. package/build/esm/lib/core/components/Form/hooks/useSearch/index.js +1 -0
  56. package/build/esm/lib/core/components/Form/hooks/useSearch/useSearch.css +9 -0
  57. package/build/esm/lib/core/components/Form/hooks/useSearch/useSearch.d.ts +4 -0
  58. package/build/esm/lib/core/components/Form/hooks/useSearch/useSearch.js +18 -0
  59. package/build/esm/lib/core/components/Form/hooks/useSearchContext.d.ts +1 -0
  60. package/build/esm/lib/core/components/Form/hooks/useSearchContext.js +7 -0
  61. package/build/esm/lib/core/components/Form/hooks/useSearchStore.d.ts +6 -0
  62. package/build/esm/lib/core/components/Form/hooks/useSearchStore.js +36 -0
  63. package/build/esm/lib/core/components/Form/hooks/useStore.d.ts +2 -8
  64. package/build/esm/lib/core/components/Form/hooks/useStore.js +5 -37
  65. package/build/esm/lib/core/components/Form/hooks/useValidate.js +1 -1
  66. package/build/esm/lib/core/components/Form/index.d.ts +1 -1
  67. package/build/esm/lib/core/components/Form/index.js +1 -1
  68. package/build/esm/lib/core/components/Form/types/context.d.ts +2 -1
  69. package/build/esm/lib/core/components/Form/types/index.d.ts +3 -0
  70. package/build/esm/lib/core/components/Form/types/index.js +3 -0
  71. package/build/esm/lib/core/components/Form/types/mirror.d.ts +17 -0
  72. package/build/esm/lib/core/components/Form/types/mirror.js +1 -0
  73. package/build/esm/lib/core/components/Form/types/search.d.ts +8 -0
  74. package/build/esm/lib/core/components/Form/types/search.js +1 -0
  75. package/build/esm/lib/core/components/Form/types/store.d.ts +7 -0
  76. package/build/esm/lib/core/components/Form/types/store.js +1 -0
  77. package/build/esm/lib/core/components/Form/{helpers.d.ts → utils/common.d.ts} +1 -1
  78. package/build/esm/lib/core/components/Form/{helpers.js → utils/common.js} +2 -2
  79. package/build/esm/lib/core/components/Form/utils/index.d.ts +2 -0
  80. package/build/esm/lib/core/components/Form/utils/index.js +2 -0
  81. package/build/esm/lib/core/components/Form/utils/search.d.ts +3 -0
  82. package/build/esm/lib/core/components/Form/utils/search.js +14 -0
  83. package/build/esm/lib/kit/components/Inputs/TableArrayInput/TableArrayInput.css +10 -0
  84. package/build/esm/lib/kit/components/Inputs/TableArrayInput/TableArrayInput.js +20 -9
  85. package/build/esm/lib/kit/components/Inputs/TextLink/TextLink.d.ts +2 -0
  86. package/build/esm/lib/kit/components/Inputs/TextLink/TextLink.js +15 -0
  87. package/build/esm/lib/kit/components/Inputs/TextLink/index.d.ts +1 -0
  88. package/build/esm/lib/kit/components/Inputs/TextLink/index.js +1 -0
  89. package/build/esm/lib/kit/components/Inputs/index.d.ts +1 -0
  90. package/build/esm/lib/kit/components/Inputs/index.js +1 -0
  91. package/build/esm/lib/kit/components/Layouts/Row/Row.css +18 -10
  92. package/build/esm/lib/kit/components/Layouts/Row/Row.js +4 -5
  93. package/build/esm/lib/kit/components/Views/TextLinkView/TextLinkView.d.ts +2 -0
  94. package/build/esm/lib/kit/components/Views/TextLinkView/TextLinkView.js +12 -0
  95. package/build/esm/lib/kit/components/Views/TextLinkView/index.d.ts +1 -0
  96. package/build/esm/lib/kit/components/Views/TextLinkView/index.js +1 -0
  97. package/build/esm/lib/kit/components/Views/index.d.ts +1 -0
  98. package/build/esm/lib/kit/components/Views/index.js +1 -0
  99. package/build/esm/lib/kit/constants/config.js +5 -1
  100. package/build/esm/lib/kit/validators/validators.js +1 -2
  101. package/package.json +12 -4
@@ -1,10 +1,4 @@
1
- import { FieldObjectValue, FieldValue, ValidateError } from '../types';
2
- export interface DynamicFieldStore {
3
- name: string;
4
- initialValue: FieldObjectValue;
5
- values: FieldObjectValue;
6
- errors: Record<string, ValidateError>;
7
- }
1
+ import { DynamicFieldStore, FieldObjectValue, FieldValue, ValidateError } from '../types';
8
2
  export declare const useStore: (name: string) => {
9
3
  tools: {
10
4
  initialValue: FieldObjectValue;
@@ -12,5 +6,5 @@ export declare const useStore: (name: string) => {
12
6
  onUnmount: (name: string) => void;
13
7
  submitFailed: boolean;
14
8
  };
15
- watcher: JSX.Element;
9
+ store: DynamicFieldStore;
16
10
  };
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  import _ from 'lodash';
3
- import { Field as FinalFormField, useForm } from 'react-final-form';
4
- import { transformArrIn, transformArrOut } from '../helpers';
3
+ import { useForm } from 'react-final-form';
4
+ import { transformArrIn } from '../utils';
5
5
  export const useStore = (name) => {
6
6
  const form = useForm();
7
7
  const firstRenderRef = React.useRef(true);
@@ -17,44 +17,12 @@ export const useStore = (name) => {
17
17
  };
18
18
  });
19
19
  const submitFailed = form.getState().submitFailed;
20
- const watcher = React.useMemo(() => {
21
- const props = {
22
- name: store.name,
23
- render: () => null,
24
- subscription: {},
25
- validate: () => {
26
- const asyncErrors = [];
27
- let error;
28
- _.values(store.errors).forEach((err) => {
29
- if (err) {
30
- if (_.isFunction(err === null || err === void 0 ? void 0 : err.then)) {
31
- asyncErrors.push(err);
32
- }
33
- else {
34
- error = err;
35
- }
36
- }
37
- });
38
- if (asyncErrors.length) {
39
- return Promise.all(asyncErrors).then((r) => r[0]);
40
- }
41
- return error;
42
- },
43
- };
44
- return React.createElement(FinalFormField, Object.assign({}, props));
45
- }, [store.name, store.errors]);
46
20
  const tools = React.useMemo(() => ({
47
21
  initialValue: store.initialValue,
48
- onChange: (name, value, errors) => setStore((store) => (Object.assign(Object.assign({}, store), { values: _.set(Object.assign({}, store.values), name, value), errors: errors || {} }))),
49
- onUnmount: (name) => setStore((store) => (Object.assign(Object.assign({}, store), { errors: _.omit(store.errors, Object.keys(store.errors).filter((key) => key.startsWith(name))) }))),
22
+ onChange: (name, value, errors) => setStore((store) => (Object.assign(Object.assign({}, store), { values: _.set(Object.assign({}, store.values), name, _.clone(value)), errors: errors || {} }))),
23
+ onUnmount: (name) => setStore((store) => (Object.assign(Object.assign({}, store), { values: _.omit(store.values, name), errors: _.omit(store.errors, Object.keys(store.errors).filter((key) => key.startsWith(name))) }))),
50
24
  submitFailed,
51
25
  }), [store.initialValue, setStore, submitFailed]);
52
- const change = React.useCallback(_.debounce((value) => {
53
- form.change(store.name, _.get(transformArrOut(value), store.name));
54
- }, 100), [form.change, store.name]);
55
- React.useEffect(() => {
56
- change(store.values);
57
- }, [store.values]);
58
26
  React.useEffect(() => {
59
27
  if (!firstRenderRef.current) {
60
28
  const initialValue = transformArrIn({
@@ -71,5 +39,5 @@ export const useStore = (name) => {
71
39
  React.useEffect(() => {
72
40
  firstRenderRef.current = false;
73
41
  }, []);
74
- return { tools, watcher };
42
+ return { tools, store };
75
43
  };
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  import _ from 'lodash';
3
3
  import { isCorrectSpec } from '../../../helpers';
4
- import { isCorrectConfig } from '../helpers';
4
+ import { isCorrectConfig } from '../utils';
5
5
  import { useDynamicFormsCtx } from './';
6
6
  export const useValidate = (spec) => {
7
7
  const { config } = useDynamicFormsCtx();
@@ -1,5 +1,5 @@
1
1
  export * from './constants';
2
2
  export * from './Controller';
3
3
  export * from './DynamicField';
4
- export * from './helpers';
5
4
  export * from './types';
5
+ export * from './utils';
@@ -1,5 +1,5 @@
1
1
  export * from './constants';
2
2
  export * from './Controller';
3
3
  export * from './DynamicField';
4
- export * from './helpers';
5
4
  export * from './types';
5
+ export * from './utils';
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import type { MonacoEditorProps } from 'react-monaco-editor/lib/types';
3
- import { DynamicFormConfig, FieldObjectValue, FieldValue, ValidateError } from './';
3
+ import { DynamicFormConfig, FieldObjectValue, FieldValue, ValidateError, WonderMirror } from './';
4
4
  export interface DynamicFormsContext {
5
5
  config: DynamicFormConfig;
6
6
  Monaco?: React.ComponentType<MonacoEditorProps>;
@@ -10,4 +10,5 @@ export interface DynamicFormsContext {
10
10
  onUnmount: (name: string) => void;
11
11
  submitFailed: boolean;
12
12
  };
13
+ __mirror?: WonderMirror;
13
14
  }
@@ -5,8 +5,11 @@ export * from './context';
5
5
  export * from './field';
6
6
  export * from './input';
7
7
  export * from './layout';
8
+ export * from './mirror';
8
9
  export * from './number';
9
10
  export * from './object';
11
+ export * from './store';
10
12
  export * from './string';
11
13
  export * from './validators';
12
14
  export * from './value';
15
+ export * from './search';
@@ -5,8 +5,11 @@ export * from './context';
5
5
  export * from './field';
6
6
  export * from './input';
7
7
  export * from './layout';
8
+ export * from './mirror';
8
9
  export * from './number';
9
10
  export * from './object';
11
+ export * from './store';
10
12
  export * from './string';
11
13
  export * from './validators';
12
14
  export * from './value';
15
+ export * from './search';
@@ -0,0 +1,17 @@
1
+ import { useComponents, useField, useIntegrationFF, useRender, useSearch, useSearchStore, useStore, useValidate } from '../hooks';
2
+ export interface ControllerMirror {
3
+ useComponents?: ReturnType<typeof useComponents>;
4
+ useRender?: ReturnType<typeof useRender>;
5
+ useValidate?: ReturnType<typeof useValidate>;
6
+ useField?: ReturnType<typeof useField>;
7
+ useSearch?: ReturnType<typeof useSearch>;
8
+ }
9
+ export interface DynamicFieldMirror {
10
+ useStore?: ReturnType<typeof useStore>;
11
+ useIntegrationFF?: ReturnType<typeof useIntegrationFF>;
12
+ useSearchStore?: ReturnType<typeof useSearchStore>;
13
+ }
14
+ export interface WonderMirror {
15
+ field: DynamicFieldMirror;
16
+ controller: Record<string, ControllerMirror | undefined>;
17
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,8 @@
1
+ import { Spec } from '../../../types';
2
+ import { FieldValue } from './value';
3
+ export interface SearchContext {
4
+ setField: (name: string, search: boolean) => void;
5
+ removeField: (name: string) => void;
6
+ isHiddenField: (name: string) => boolean;
7
+ searchFunction: (spec: Spec, value: FieldValue, name: string) => boolean;
8
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,7 @@
1
+ import { FieldObjectValue, ValidateError } from '../types';
2
+ export interface DynamicFieldStore {
3
+ name: string;
4
+ initialValue: FieldObjectValue;
5
+ values: FieldObjectValue;
6
+ errors: Record<string, ValidateError>;
7
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,4 +1,4 @@
1
- import { FormValue } from '../../types';
1
+ import { FormValue } from '../../../types';
2
2
  export declare const isCorrectConfig: (candidate: any) => boolean;
3
3
  export declare const transformArrIn: <Type extends FormValue, ReturnType_1 extends FormValue = Type>(value: Type) => ReturnType_1;
4
4
  export declare const transformArrOut: <Type extends FormValue, ReturnType_1 extends FormValue = Type>(value: Type) => ReturnType_1;
@@ -1,6 +1,6 @@
1
1
  import _ from 'lodash';
2
- import { SpecTypes } from '../../constants';
3
- import { OBJECT_ARRAY_CNT, OBJECT_ARRAY_FLAG, REMOVED_ITEM } from './constants';
2
+ import { SpecTypes } from '../../../constants';
3
+ import { OBJECT_ARRAY_CNT, OBJECT_ARRAY_FLAG, REMOVED_ITEM } from '../constants';
4
4
  export const isCorrectConfig = (candidate) => Object.values(SpecTypes).every((type) => _.isObjectLike(candidate) &&
5
5
  _.isObjectLike(candidate[type]) &&
6
6
  _.isObjectLike(candidate[type].inputs) &&
@@ -0,0 +1,2 @@
1
+ export * from './common';
2
+ export * from './search';
@@ -0,0 +1,2 @@
1
+ export * from './common';
2
+ export * from './search';
@@ -0,0 +1,3 @@
1
+ import { Spec } from '../../../types';
2
+ export declare const getParentName: (name: string) => string | undefined;
3
+ export declare const getDefaultSearchFunction: (search?: string) => (spec: Spec) => boolean;
@@ -0,0 +1,14 @@
1
+ export const getParentName = (name) => {
2
+ const index = name.lastIndexOf('.');
3
+ if (index !== -1) {
4
+ return name.substring(0, index);
5
+ }
6
+ return undefined;
7
+ };
8
+ export const getDefaultSearchFunction = (search) => (spec) => {
9
+ var _a;
10
+ if (search) {
11
+ return Boolean((_a = spec.viewSpec.layoutTitle) === null || _a === void 0 ? void 0 : _a.toLowerCase().includes(search.trim().toLowerCase()));
12
+ }
13
+ return true;
14
+ };
@@ -1,6 +1,16 @@
1
1
  .df-table-array__table {
2
2
  margin-bottom: 10px;
3
3
  }
4
+ .df-table-array__table .yc-table__cell {
5
+ border-bottom: 0px transparent;
6
+ }
7
+ .df-table-array__row .yc-table__cell {
8
+ border-bottom: 0px transparent;
9
+ border-top: 1px solid var(--yc-color-line-generic);
10
+ }
11
+ .df-table-array__row_hidden {
12
+ display: none;
13
+ }
4
14
  .df-table-array__cell .yc-text-input,
5
15
  .df-table-array__cell .yc-select-control,
6
16
  .df-table-array__cell .yc-select,
@@ -3,16 +3,21 @@ import { Plus, Xmark } from '@gravity-ui/icons';
3
3
  import { Button, Icon, Table } from '@gravity-ui/uikit';
4
4
  import _ from 'lodash';
5
5
  import { Controller, OBJECT_ARRAY_CNT, OBJECT_ARRAY_FLAG, REMOVED_ITEM, isArraySpec, isBooleanSpec, isObjectSpec, transformArrIn, } from '../../../../core';
6
+ import { useSearchContext } from '../../../../core/components/Form/hooks';
6
7
  import { block } from '../../../utils';
7
8
  import './TableArrayInput.css';
8
9
  const b = block('table-array');
9
10
  export const TableArrayInput = ({ spec, name, arrayInput, input }) => {
11
+ const { isHiddenField } = useSearchContext();
10
12
  const keys = React.useMemo(() => Object.keys(arrayInput.value || {})
11
13
  .filter((k) => k !== OBJECT_ARRAY_FLAG &&
12
14
  k !== OBJECT_ARRAY_CNT &&
13
15
  arrayInput.value[k] !== REMOVED_ITEM)
14
16
  .map((k) => k.split('<').join('').split('>').join(''))
15
- .sort((a, b) => Number(a) - Number(b)), [arrayInput.value]);
17
+ .sort((a, b) => Number(a) - Number(b))
18
+ .map((key) => ({
19
+ key,
20
+ })), [arrayInput.value]);
16
21
  const onItemAdd = React.useCallback(() => {
17
22
  arrayInput.onItemAdd({});
18
23
  }, [arrayInput.onItemAdd]);
@@ -30,39 +35,45 @@ export const TableArrayInput = ({ spec, name, arrayInput, input }) => {
30
35
  id: 'idx',
31
36
  name: '',
32
37
  sticky: 'left',
33
- template: (key, idx) => (React.createElement("div", { className: b('idx'), key: `idx-${key}` }, idx + 1)),
38
+ template: ({ key }, idx) => (React.createElement("div", { className: b('idx'), key: `idx-${key}` }, idx + 1)),
34
39
  };
35
40
  const removeColumn = {
36
41
  id: 'remove',
37
42
  name: '',
38
43
  sticky: 'right',
39
- template: (key) => (React.createElement(Button, { view: "flat", onClick: () => onItemRemove(key), key: `remove-${key}` },
44
+ template: ({ key }) => (React.createElement(Button, { view: "flat", onClick: () => onItemRemove(key), key: `remove-${key}` },
40
45
  React.createElement(Icon, { data: Xmark, size: 16 }))),
41
46
  };
42
47
  const columns = table.map(({ property, label }) => ({
43
48
  id: property,
44
49
  name: label,
45
- template: (key) => {
50
+ template: ({ key, }, idx) => {
46
51
  var _a, _b, _c;
47
52
  const entitySpec = (_a = items === null || items === void 0 ? void 0 : items.properties) === null || _a === void 0 ? void 0 : _a[property];
48
53
  if (!entitySpec) {
49
54
  return null;
50
55
  }
56
+ const preparedEntitySpec = Object.assign(Object.assign({}, entitySpec), { viewSpec: Object.assign(Object.assign({}, entitySpec.viewSpec), { layoutTitle: table.map(({ label }) => label).join(` ${idx + 1} `) + ` ${idx + 1}` }) });
51
57
  return (React.createElement("div", { className: b('cell', {
52
- bool: isBooleanSpec(entitySpec),
53
- arr: isArraySpec(entitySpec),
54
- obj: isObjectSpec(entitySpec),
58
+ bool: isBooleanSpec(preparedEntitySpec),
59
+ arr: isArraySpec(preparedEntitySpec),
60
+ obj: isObjectSpec(preparedEntitySpec),
55
61
  }), key: `${name}.<${key}>.${property}` },
56
- React.createElement(Controller, { initialValue: (_c = (_b = input.value) === null || _b === void 0 ? void 0 : _b[`<${key}>`]) === null || _c === void 0 ? void 0 : _c[property], spec: entitySpec, name: `${name}.<${key}>.${property}`, parentOnChange: parentOnChange, parentOnUnmount: parentOnUnmount })));
62
+ React.createElement(Controller, { initialValue: (_c = (_b = input.value) === null || _b === void 0 ? void 0 : _b[`<${key}>`]) === null || _c === void 0 ? void 0 : _c[property], spec: preparedEntitySpec, name: `${name}.<${key}>.${property}`, parentOnChange: parentOnChange, parentOnUnmount: parentOnUnmount })));
57
63
  },
58
64
  }));
59
65
  return [idxColumn, ...columns, removeColumn];
60
66
  }, [name, spec, onItemRemove, parentOnChange, parentOnUnmount, input.value]);
67
+ const getRowClassNames = React.useCallback(({ key }) => {
68
+ var _a;
69
+ const searchResult = (_a = spec.viewSpec.table) === null || _a === void 0 ? void 0 : _a.every(({ property }) => isHiddenField(`${name}.<${key}>.${property}`));
70
+ return [b('row', { hidden: searchResult })];
71
+ }, [isHiddenField, name, spec.viewSpec.table]);
61
72
  if (!columns) {
62
73
  return null;
63
74
  }
64
75
  return (React.createElement("div", { className: b() },
65
- keys.length ? (React.createElement(Table, { className: b('table'), data: keys, columns: columns, getRowId: (_, idx) => `${name}-${idx}`, verticalAlign: "top" })) : null,
76
+ keys.length ? (React.createElement(Table, { className: b('table'), data: keys, columns: columns, getRowId: (_, idx) => `${name}-${idx}`, verticalAlign: "top", getRowClassNames: getRowClassNames })) : null,
66
77
  !arrayInput.value && spec.defaultValue ? (React.createElement(Button, { onClick: () => input.onChange(transformArrIn(spec.defaultValue)), disabled: spec.viewSpec.disabled },
67
78
  React.createElement(Icon, { data: Plus, size: 14 }),
68
79
  spec.viewSpec.layoutTitle || null)) : (React.createElement(Button, { onClick: onItemAdd, disabled: spec.viewSpec.disabled },
@@ -0,0 +1,2 @@
1
+ import { ObjectIndependentInput } from '../../../../core';
2
+ export declare const TextLink: ObjectIndependentInput;
@@ -0,0 +1,15 @@
1
+ import React from 'react';
2
+ import _ from 'lodash';
3
+ import { Controller, isStringSpec, } from '../../../../core';
4
+ const TEXT_LINK_PROPERTY_NAME = 'text';
5
+ export const TextLink = ({ spec, input, name }) => {
6
+ var _a;
7
+ const parentOnChange = React.useCallback((childName, childValue, childErrors) => input.onChange((currentValue) => _.set(Object.assign({}, currentValue), childName.split(`${name}.`).join(''), childValue), childErrors), [input.onChange, input.name]);
8
+ const parentOnUnmount = React.useCallback((childName) => input.onChange((currentValue) => currentValue, { [childName]: false }), [input.onChange]);
9
+ const specProperties = Object.assign({}, spec.properties);
10
+ if (!specProperties[TEXT_LINK_PROPERTY_NAME] ||
11
+ !isStringSpec(specProperties[TEXT_LINK_PROPERTY_NAME])) {
12
+ return null;
13
+ }
14
+ return (React.createElement(Controller, { initialValue: (_a = input.value) === null || _a === void 0 ? void 0 : _a[TEXT_LINK_PROPERTY_NAME], spec: specProperties[TEXT_LINK_PROPERTY_NAME], name: `${name}.${TEXT_LINK_PROPERTY_NAME}`, key: `${name}.${TEXT_LINK_PROPERTY_NAME}`, parentOnChange: parentOnChange, parentOnUnmount: parentOnUnmount }));
15
+ };
@@ -0,0 +1 @@
1
+ export * from './TextLink';
@@ -0,0 +1 @@
1
+ export * from './TextLink';
@@ -11,5 +11,6 @@ export * from './TableArrayInput';
11
11
  export * from './Text';
12
12
  export * from './TextArea';
13
13
  export * from './TextContent';
14
+ export * from './TextLink';
14
15
  export * from './NumberWithScale';
15
16
  export * from './MonacoInput';
@@ -11,5 +11,6 @@ export * from './TableArrayInput';
11
11
  export * from './Text';
12
12
  export * from './TextArea';
13
13
  export * from './TextContent';
14
+ export * from './TextLink';
14
15
  export * from './NumberWithScale';
15
16
  export * from './MonacoInput';
@@ -13,13 +13,16 @@
13
13
  }
14
14
  .df-row__left {
15
15
  width: 180px;
16
+ min-height: 28px;
16
17
  display: flex;
18
+ margin-bottom: auto;
17
19
  flex-direction: column;
18
20
  flex-shrink: 0;
19
21
  }
20
22
  .df-row__left-inner {
21
- min-height: 28px;
22
- display: flex;
23
+ display: inline;
24
+ margin-top: auto;
25
+ margin-bottom: auto;
23
26
  }
24
27
  .df-row__left::after {
25
28
  content: "";
@@ -27,18 +30,24 @@
27
30
  flex-shrink: 1;
28
31
  }
29
32
  .df-row__title {
30
- align-self: center;
33
+ word-break: break-word;
34
+ margin-right: 3px;
35
+ }
36
+ .df-row__title_required::after {
37
+ content: "*";
38
+ color: var(--yc-color-text-danger);
31
39
  }
32
40
  .df-row__note {
33
- height: 28px;
34
- display: flex;
35
- align-items: center;
36
- margin-left: 5px;
41
+ padding-right: 16px;
42
+ }
43
+ .df-row__note-inner {
44
+ position: absolute;
45
+ margin-top: 1px;
37
46
  }
38
- .df-row__note .yc-help-popover {
47
+ .df-row__note-inner .yc-help-popover {
39
48
  display: flex;
40
49
  }
41
- .df-row__note .yc-help-popover > span {
50
+ .df-row__note-inner .yc-help-popover > span {
42
51
  display: flex;
43
52
  }
44
53
  .df-row__right {
@@ -59,6 +68,5 @@
59
68
  margin-left: 5px;
60
69
  }
61
70
  .df-row__required-mark {
62
- margin-left: 2px;
63
71
  color: var(--yc-color-text-danger);
64
72
  }
@@ -11,11 +11,10 @@ const RowBase = ({ name, spec, input, meta, verboseDescription, children, }) =>
11
11
  return (React.createElement("div", { className: b({ 'extra-width': isArraySpec(spec) || arrayItem }) },
12
12
  React.createElement("div", { className: b('left') },
13
13
  React.createElement("div", { className: b('left-inner') },
14
- React.createElement("div", { className: b('title') },
15
- spec.viewSpec.layoutTitle,
16
- spec.required && React.createElement("span", { className: b('required-mark') }, "*")),
17
- !verboseDescription && spec.viewSpec.layoutDescription ? (React.createElement("div", { className: b('note') },
18
- React.createElement(HelpPopover, { htmlContent: spec.viewSpec.layoutDescription, placement: ['bottom', 'top'] }))) : null)),
14
+ React.createElement("span", { className: b('title', { required: spec.required }) }, spec.viewSpec.layoutTitle),
15
+ !verboseDescription && spec.viewSpec.layoutDescription ? (React.createElement("span", { className: b('note') },
16
+ React.createElement("span", { className: b('note-inner') },
17
+ React.createElement(HelpPopover, { htmlContent: spec.viewSpec.layoutDescription, placement: ['bottom', 'top'] })))) : null)),
19
18
  React.createElement("div", { className: b('right') },
20
19
  React.createElement("div", { className: b('right-inner') },
21
20
  React.createElement(ErrorWrapper, { name: name, meta: meta, withoutChildErrorStyles: isArraySpec(spec) || isObjectSpec(spec) }, children),
@@ -0,0 +1,2 @@
1
+ import { ObjectIndependentView } from '../../../../core';
2
+ export declare const TextLinkView: ObjectIndependentView;
@@ -0,0 +1,12 @@
1
+ import React from 'react';
2
+ import { ViewController } from '../../../../core';
3
+ const TEXT_LINK_PROPERTY_NAME = 'text';
4
+ export const TextLinkView = ({ value, spec, name }) => {
5
+ const specProperties = spec.properties;
6
+ const preparedSpec = React.useMemo(() => {
7
+ var _a;
8
+ return specProperties && specProperties[TEXT_LINK_PROPERTY_NAME]
9
+ ? Object.assign(Object.assign({}, specProperties[TEXT_LINK_PROPERTY_NAME]), { viewSpec: Object.assign(Object.assign({}, (_a = specProperties[TEXT_LINK_PROPERTY_NAME]) === null || _a === void 0 ? void 0 : _a.viewSpec), { link: value === null || value === void 0 ? void 0 : value.link }) }) : undefined;
10
+ }, [specProperties, value === null || value === void 0 ? void 0 : value.link]);
11
+ return preparedSpec ? (React.createElement(ViewController, { spec: preparedSpec, name: `${name}.${TEXT_LINK_PROPERTY_NAME}` })) : null;
12
+ };
@@ -0,0 +1 @@
1
+ export * from './TextLinkView';
@@ -0,0 +1 @@
1
+ export * from './TextLinkView';
@@ -9,3 +9,4 @@ export * from './OneOfCardView';
9
9
  export * from './OneOfView';
10
10
  export * from './TableArrayView';
11
11
  export * from './TextAreaView';
12
+ export * from './TextLinkView';
@@ -9,3 +9,4 @@ export * from './OneOfCardView';
9
9
  export * from './OneOfView';
10
10
  export * from './TableArrayView';
11
11
  export * from './TextAreaView';
12
+ export * from './TextLinkView';
@@ -1,4 +1,4 @@
1
- import { Accordeon, AccordeonCardLayout, ArrayBase, ArrayBaseView, BaseView, CardAccordeon, CardOneOf, CardOneOfView, CardSection, Checkbox, Group, Group2, MonacoInput, MonacoInputCard, MonacoView, MonacoViewCard, MultiSelect, MultiSelectView, NumberWithScale, NumberWithScaleView, ObjectBase, ObjectBaseView, OneOf, OneOfCard, OneOfCardView, OneOfView, Row, Row2, RowVerbose, Secret, Section, Section2, SectionCard, SectionCard2, SectionWithSubtitle, SectionWithSubtitle2, Select, TableArrayInput, TableArrayView, TableCell, Text, TextArea, TextAreaView, TextContent, Transparent, ViewAccordeon, ViewAccordeonCard, ViewCardAccordeon, ViewCardSection, ViewGroup, ViewGroup2, ViewRow, ViewRow2, ViewSection, ViewSection2, ViewSectionCard, ViewSectionCard2, ViewTableCell, ViewTransparent, } from '../components';
1
+ import { Accordeon, AccordeonCardLayout, ArrayBase, ArrayBaseView, BaseView, CardAccordeon, CardOneOf, CardOneOfView, CardSection, Checkbox, Group, Group2, MonacoInput, MonacoInputCard, MonacoView, MonacoViewCard, MultiSelect, MultiSelectView, NumberWithScale, NumberWithScaleView, ObjectBase, ObjectBaseView, OneOf, OneOfCard, OneOfCardView, OneOfView, Row, Row2, RowVerbose, Secret, Section, Section2, SectionCard, SectionCard2, SectionWithSubtitle, SectionWithSubtitle2, Select, TableArrayInput, TableArrayView, TableCell, Text, TextArea, TextAreaView, TextContent, TextLink, TextLinkView, Transparent, ViewAccordeon, ViewAccordeonCard, ViewCardAccordeon, ViewCardSection, ViewGroup, ViewGroup2, ViewRow, ViewRow2, ViewSection, ViewSection2, ViewSectionCard, ViewSectionCard2, ViewTableCell, ViewTransparent, } from '../components';
2
2
  import { getArrayValidator, getBooleanValidator, getNumberValidator, getObjectValidator, getStringValidator, } from '../validators';
3
3
  export const dynamicConfig = {
4
4
  array: {
@@ -57,6 +57,7 @@ export const dynamicConfig = {
57
57
  card_oneof: { Component: CardOneOf, independent: true },
58
58
  secret: { Component: Secret, independent: true },
59
59
  base: { Component: ObjectBase, independent: true },
60
+ text_link: { Component: TextLink, independent: true },
60
61
  },
61
62
  layouts: {
62
63
  row: Row,
@@ -152,6 +153,7 @@ export const dynamicCardConfig = {
152
153
  oneof: { Component: OneOfCard, independent: true },
153
154
  secret: { Component: Secret, independent: true },
154
155
  base: { Component: ObjectBase, independent: true },
156
+ text_link: { Component: TextLink, independent: true },
155
157
  },
156
158
  layouts: {
157
159
  row: Row2,
@@ -236,6 +238,7 @@ export const dynamicViewConfig = {
236
238
  card_oneof: { Component: CardOneOfView, independent: true },
237
239
  secret: undefined,
238
240
  base: { Component: ObjectBaseView, independent: true },
241
+ text_link: { Component: TextLinkView, independent: true },
239
242
  },
240
243
  layouts: {
241
244
  row: ViewRow,
@@ -313,6 +316,7 @@ export const dynamicViewCardConfig = {
313
316
  oneof: { Component: OneOfCardView, independent: true },
314
317
  secret: undefined,
315
318
  base: { Component: ObjectBaseView, independent: true },
319
+ text_link: { Component: TextLinkView, independent: true },
316
320
  },
317
321
  layouts: {
318
322
  row: ViewRow2,
@@ -65,8 +65,7 @@ export const getNumberValidator = (params = {}) => {
65
65
  }
66
66
  if (!ignoreMinimumCheck &&
67
67
  _.isNumber(spec.minimum) &&
68
- stringValue.length &&
69
- spec.minimum > Number(stringValue)) {
68
+ ((stringValue.length && spec.minimum > Number(stringValue)) || !stringValue.length)) {
70
69
  return ErrorMessages.minNumber(spec.minimum);
71
70
  }
72
71
  if (_.isString(spec.format) && stringValue.length) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gravity-ui/dynamic-forms",
3
- "version": "1.1.1",
3
+ "version": "1.3.0",
4
4
  "description": "",
5
5
  "license": "MIT",
6
6
  "main": "build/cjs/index.js",
@@ -28,7 +28,8 @@
28
28
  "lint:js": "eslint src --quiet",
29
29
  "lint:prettier": "prettier --check 'src/**/*.{js,jsx,ts,tsx,css,scss}'",
30
30
  "lint:styles": "stylelint 'src/**/*.scss'",
31
- "typecheck": "tsc -p src --noEmit",
31
+ "test": "jest",
32
+ "typecheck": "tsc --noEmit",
32
33
  "build": "gulp",
33
34
  "build-storybook": "build-storybook",
34
35
  "dev": "start-storybook -p 6006",
@@ -42,10 +43,10 @@
42
43
  "lodash": "^4.17.20"
43
44
  },
44
45
  "devDependencies": {
45
- "@commitlint/cli": "^17.0.0",
46
- "@commitlint/config-conventional": "^17.0.0",
47
46
  "@babel/preset-env": "^7.20.2",
48
47
  "@babel/preset-typescript": "^7.18.6",
48
+ "@commitlint/cli": "^17.0.0",
49
+ "@commitlint/config-conventional": "^17.0.0",
49
50
  "@gravity-ui/eslint-config": "^2.0.0",
50
51
  "@gravity-ui/prettier-config": "^1.0.1",
51
52
  "@gravity-ui/stylelint-config": "^2.0.0",
@@ -54,6 +55,9 @@
54
55
  "@storybook/addon-essentials": "^6.5.16",
55
56
  "@storybook/preset-scss": "^1.0.3",
56
57
  "@storybook/react": "^6.5.16",
58
+ "@testing-library/jest-dom": "^5.16.5",
59
+ "@testing-library/react": "^14.0.0",
60
+ "@types/jest": "^29.5.0",
57
61
  "@types/lodash": "^4.14.180",
58
62
  "@types/react": "^18.0.28",
59
63
  "@types/react-dom": "^18.0.11",
@@ -67,6 +71,9 @@
67
71
  "gulp-replace": "^1.1.4",
68
72
  "gulp-typescript": "^6.0.0-alpha.1",
69
73
  "husky": "^7.0.4",
74
+ "jest": "^29.5.0",
75
+ "jest-environment-jsdom": "^29.5.0",
76
+ "jest-transform-css": "^6.0.1",
70
77
  "monaco-editor": "^0.30.1",
71
78
  "monaco-editor-webpack-plugin": "^6.0.0",
72
79
  "npm-run-all": "^4.1.5",
@@ -83,6 +90,7 @@
83
90
  "style-loader": "^2.0.0",
84
91
  "stylelint": "^14.15.0",
85
92
  "stylelint-scss": "^4.2.0",
93
+ "ts-jest": "^29.0.5",
86
94
  "typescript": "^4.9.5"
87
95
  },
88
96
  "peerDependencies": {