@gravity-ui/dynamic-forms 4.0.1 → 4.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/kit/components/Inputs/ObjectBase/ObjectBase.css +6 -0
  2. package/build/cjs/lib/kit/components/Inputs/ObjectBase/ObjectBase.js +7 -2
  3. package/build/cjs/lib/kit/components/Inputs/ObjectValueInput/ObjectValueInput.js +4 -4
  4. package/build/cjs/lib/kit/components/Views/ObjectBaseView/ObjectBaseView.css +6 -0
  5. package/build/cjs/lib/kit/components/Views/ObjectBaseView/ObjectBaseView.js +15 -8
  6. package/build/cjs/lib/kit/components/Views/ObjectValueInputView/ObjectValueInputView.js +4 -4
  7. package/build/cjs/lib/kit/components/Views/OneOfView/OneOfView.js +15 -4
  8. package/build/cjs/lib/kit/constants/common.js +2 -1
  9. package/build/cjs/lib/kit/hooks/useOneOf/useOneOf.css +5 -0
  10. package/build/cjs/lib/kit/hooks/useOneOf/useOneOf.js +30 -3
  11. package/build/cjs/lib/kit/utils/index.js +1 -0
  12. package/build/cjs/lib/kit/utils/objectKeys.js +7 -0
  13. package/build/esm/lib/core/types/specs.d.ts +3 -1
  14. package/build/esm/lib/kit/components/Inputs/ObjectBase/ObjectBase.css +6 -0
  15. package/build/esm/lib/kit/components/Inputs/ObjectBase/ObjectBase.js +8 -3
  16. package/build/esm/lib/kit/components/Inputs/ObjectValueInput/ObjectValueInput.js +1 -1
  17. package/build/esm/lib/kit/components/Views/ObjectBaseView/ObjectBaseView.css +6 -0
  18. package/build/esm/lib/kit/components/Views/ObjectBaseView/ObjectBaseView.js +15 -8
  19. package/build/esm/lib/kit/components/Views/ObjectValueInputView/ObjectValueInputView.js +1 -1
  20. package/build/esm/lib/kit/components/Views/OneOfView/OneOfView.js +16 -5
  21. package/build/esm/lib/kit/constants/common.d.ts +1 -0
  22. package/build/esm/lib/kit/constants/common.js +1 -0
  23. package/build/esm/lib/kit/hooks/useOneOf/useOneOf.css +5 -0
  24. package/build/esm/lib/kit/hooks/useOneOf/useOneOf.js +32 -5
  25. package/build/esm/lib/kit/utils/index.d.ts +1 -0
  26. package/build/esm/lib/kit/utils/index.js +1 -0
  27. package/build/esm/lib/kit/utils/objectKeys.d.ts +4 -0
  28. package/build/esm/lib/kit/utils/objectKeys.js +3 -0
  29. package/package.json +1 -1
@@ -8,4 +8,10 @@
8
8
  }
9
9
  .df-object-base__content_inline > .df-use-search:last-child {
10
10
  margin-right: 0;
11
+ }
12
+ .df-object-base__delimiter {
13
+ display: flex;
14
+ margin-right: 8px;
15
+ align-items: center;
16
+ white-space: nowrap;
11
17
  }
@@ -31,12 +31,17 @@ const ObjectBase = (_a) => {
31
31
  const specProperties = inline
32
32
  ? (0, utils_1.filterPropertiesForObjectInline)(spec.properties)
33
33
  : spec.properties;
34
- return (react_1.default.createElement("div", { className: b('content', { inline }) }, (spec.viewSpec.order || Object.keys(specProperties)).map((property) => {
34
+ const delimiter = spec.viewSpec.delimiter;
35
+ const orderProperties = spec.viewSpec.order || Object.keys(specProperties);
36
+ return (react_1.default.createElement("div", { className: b('content', { inline }) }, orderProperties.map((property) => {
35
37
  var _a;
36
- return specProperties[property] ? (react_1.default.createElement(core_1.Controller, { value: (_a = restProps.input.value) === null || _a === void 0 ? void 0 : _a[property], spec: specProperties[property], name: `${name ? name + '.' : ''}${property}`, parentOnChange: parentOnChange, parentOnUnmount: restProps.input.parentOnUnmount, key: `${name ? name + '.' : ''}${property}` })) : null;
38
+ return specProperties[property] ? (react_1.default.createElement(react_1.default.Fragment, { key: `${name ? name + '.' : ''}${property}` },
39
+ react_1.default.createElement(core_1.Controller, { value: (_a = restProps.input.value) === null || _a === void 0 ? void 0 : _a[property], spec: specProperties[property], name: `${name ? name + '.' : ''}${property}`, parentOnChange: parentOnChange, parentOnUnmount: restProps.input.parentOnUnmount }),
40
+ delimiter && delimiter[property] ? (react_1.default.createElement(uikit_1.Text, { className: b('delimiter') }, delimiter[property])) : null)) : null;
37
41
  })));
38
42
  }, [
39
43
  spec.properties,
44
+ spec.viewSpec.delimiter,
40
45
  spec.viewSpec.order,
41
46
  restProps.input.value,
42
47
  restProps.input.parentOnUnmount,
@@ -6,15 +6,15 @@ const react_1 = tslib_1.__importDefault(require("react"));
6
6
  const cloneDeep_1 = tslib_1.__importDefault(require("lodash/cloneDeep"));
7
7
  const set_1 = tslib_1.__importDefault(require("lodash/set"));
8
8
  const core_1 = require("../../../../core");
9
- const OBJECT_VALUE_PROPERTY_NAME = 'value';
9
+ const common_1 = require("../../../constants/common");
10
10
  const ObjectValueInput = (props) => {
11
11
  var _a;
12
12
  const { spec, input, name, Layout } = props;
13
13
  const parentOnChange = react_1.default.useCallback((childName, childValue, childErrors) => input.onChange((currentValue) => (0, set_1.default)(Object.assign({}, currentValue), childName.split(`${name}.`).join(''), childValue), childErrors), [input.onChange, input.name]);
14
14
  const childSpec = react_1.default.useMemo(() => {
15
15
  var _a;
16
- if ((_a = spec.properties) === null || _a === void 0 ? void 0 : _a[OBJECT_VALUE_PROPERTY_NAME]) {
17
- const childSpec = (0, cloneDeep_1.default)(spec.properties[OBJECT_VALUE_PROPERTY_NAME]);
16
+ if ((_a = spec.properties) === null || _a === void 0 ? void 0 : _a[common_1.OBJECT_VALUE_PROPERTY_NAME]) {
17
+ const childSpec = (0, cloneDeep_1.default)(spec.properties[common_1.OBJECT_VALUE_PROPERTY_NAME]);
18
18
  childSpec.viewSpec.layout = 'transparent';
19
19
  return childSpec;
20
20
  }
@@ -23,7 +23,7 @@ const ObjectValueInput = (props) => {
23
23
  if (!childSpec) {
24
24
  return null;
25
25
  }
26
- const content = (react_1.default.createElement(core_1.Controller, { value: (_a = input.value) === null || _a === void 0 ? void 0 : _a[OBJECT_VALUE_PROPERTY_NAME], spec: childSpec, name: `${name}.${OBJECT_VALUE_PROPERTY_NAME}`, key: `${name}.${OBJECT_VALUE_PROPERTY_NAME}`, parentOnChange: parentOnChange, parentOnUnmount: input.parentOnUnmount }));
26
+ const content = (react_1.default.createElement(core_1.Controller, { value: (_a = input.value) === null || _a === void 0 ? void 0 : _a[common_1.OBJECT_VALUE_PROPERTY_NAME], spec: childSpec, name: `${name}.${common_1.OBJECT_VALUE_PROPERTY_NAME}`, key: `${name}.${common_1.OBJECT_VALUE_PROPERTY_NAME}`, parentOnChange: parentOnChange, parentOnUnmount: input.parentOnUnmount }));
27
27
  if (Layout) {
28
28
  return react_1.default.createElement(Layout, Object.assign({}, props), content);
29
29
  }
@@ -8,4 +8,10 @@
8
8
  }
9
9
  .df-object-base-view__content_inline > div:last-child {
10
10
  margin-right: 0;
11
+ }
12
+ .df-object-base-view__delimiter {
13
+ display: flex;
14
+ margin-right: 8px;
15
+ align-items: center;
16
+ white-space: nowrap;
11
17
  }
@@ -4,19 +4,26 @@ exports.ObjectInlineView = exports.ObjectBaseView = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const react_1 = tslib_1.__importDefault(require("react"));
6
6
  const isObjectLike_1 = tslib_1.__importDefault(require("lodash/isObjectLike"));
7
+ const uikit_1 = require("@gravity-ui/uikit");
7
8
  const core_1 = require("../../../../core");
8
9
  const utils_1 = require("../../../utils");
9
10
  const b = (0, utils_1.block)('object-base-view');
10
11
  const ObjectBaseView = (_a) => {
11
12
  var { inline, spec, name, Layout } = _a, restProps = tslib_1.__rest(_a, ["inline", "spec", "name", "Layout"]);
12
- if (!spec.properties || !(0, isObjectLike_1.default)(spec.properties)) {
13
- return null;
14
- }
15
- const specProperties = inline
16
- ? (0, utils_1.filterPropertiesForObjectInline)(spec.properties)
17
- : spec.properties;
18
- const content = (react_1.default.createElement("div", { className: b('content', { inline }) }, (spec.viewSpec.order || Object.keys(specProperties)).map((property) => specProperties[property] ? (react_1.default.createElement(core_1.ViewController, { spec: specProperties[property], name: `${name ? name + '.' : ''}${property}`, key: `${name ? name + '.' : ''}${property}` })) : null)));
19
- if (!Layout) {
13
+ const content = react_1.default.useMemo(() => {
14
+ if (!spec.properties || !(0, isObjectLike_1.default)(spec.properties)) {
15
+ return null;
16
+ }
17
+ const specProperties = inline
18
+ ? (0, utils_1.filterPropertiesForObjectInline)(spec.properties)
19
+ : spec.properties;
20
+ const delimiter = spec.viewSpec.delimiter;
21
+ const orderProperties = spec.viewSpec.order || Object.keys(specProperties);
22
+ return (react_1.default.createElement("div", { className: b('content', { inline }) }, orderProperties.map((property) => specProperties[property] ? (react_1.default.createElement(react_1.default.Fragment, { key: `${name ? name + '.' : ''}${property}` },
23
+ react_1.default.createElement(core_1.ViewController, { spec: specProperties[property], name: `${name ? name + '.' : ''}${property}` }),
24
+ delimiter && delimiter[property] ? (react_1.default.createElement(uikit_1.Text, { className: b('delimiter') }, delimiter[property])) : null)) : null)));
25
+ }, [inline, name, spec.properties, spec.viewSpec.delimiter, spec.viewSpec.order]);
26
+ if (!Layout || !content) {
20
27
  return content;
21
28
  }
22
29
  return (react_1.default.createElement(Layout, Object.assign({ spec: spec, name: name }, restProps), content));
@@ -5,13 +5,13 @@ const tslib_1 = require("tslib");
5
5
  const react_1 = tslib_1.__importDefault(require("react"));
6
6
  const cloneDeep_1 = tslib_1.__importDefault(require("lodash/cloneDeep"));
7
7
  const core_1 = require("../../../../core");
8
- const OBJECT_VALUE_PROPERTY_NAME = 'value';
8
+ const common_1 = require("../../../constants/common");
9
9
  const ObjectValueInputView = (_a) => {
10
10
  var { spec, name, Layout } = _a, restProps = tslib_1.__rest(_a, ["spec", "name", "Layout"]);
11
11
  const childSpec = react_1.default.useMemo(() => {
12
12
  var _a;
13
- if ((_a = spec.properties) === null || _a === void 0 ? void 0 : _a[OBJECT_VALUE_PROPERTY_NAME]) {
14
- const childSpec = (0, cloneDeep_1.default)(spec.properties[OBJECT_VALUE_PROPERTY_NAME]);
13
+ if ((_a = spec.properties) === null || _a === void 0 ? void 0 : _a[common_1.OBJECT_VALUE_PROPERTY_NAME]) {
14
+ const childSpec = (0, cloneDeep_1.default)(spec.properties[common_1.OBJECT_VALUE_PROPERTY_NAME]);
15
15
  childSpec.viewSpec.layout = '';
16
16
  return childSpec;
17
17
  }
@@ -20,7 +20,7 @@ const ObjectValueInputView = (_a) => {
20
20
  if (!childSpec) {
21
21
  return null;
22
22
  }
23
- const content = (react_1.default.createElement(core_1.ViewController, { spec: childSpec, name: `${name ? name + '.' : ''}${OBJECT_VALUE_PROPERTY_NAME}` }));
23
+ const content = (react_1.default.createElement(core_1.ViewController, { spec: childSpec, name: `${name ? name + '.' : ''}${common_1.OBJECT_VALUE_PROPERTY_NAME}` }));
24
24
  if (Layout) {
25
25
  return (react_1.default.createElement(Layout, Object.assign({ spec: spec, name: name }, restProps), content));
26
26
  }
@@ -9,15 +9,26 @@ const core_1 = require("../../../../core");
9
9
  const utils_1 = require("../../../utils");
10
10
  const b = (0, utils_1.block)('oneof-view');
11
11
  const OneOfViewComponent = (props) => {
12
+ var _a, _b;
12
13
  const { value = {}, spec, Layout, name } = props;
13
14
  const specProperties = react_1.default.useMemo(() => ((0, isObjectLike_1.default)(spec.properties) ? spec.properties : {}), [spec.properties]);
15
+ const specBooleanMap = react_1.default.useMemo(() => { var _a; return (_a = spec.viewSpec.oneOfParams) === null || _a === void 0 ? void 0 : _a.booleanMap; }, [(_a = spec.viewSpec.oneOfParams) === null || _a === void 0 ? void 0 : _a.booleanMap]);
14
16
  const valueKey = react_1.default.useMemo(() => Object.keys(value)[0], [value]);
15
17
  const valueName = react_1.default.useMemo(() => {
16
- var _a, _b;
17
- return (((_a = spec.description) === null || _a === void 0 ? void 0 : _a[valueKey]) ||
18
- ((_b = specProperties[valueKey]) === null || _b === void 0 ? void 0 : _b.viewSpec.layoutTitle) ||
18
+ var _a, _b, _c;
19
+ if (((_a = spec.viewSpec.oneOfParams) === null || _a === void 0 ? void 0 : _a.toggler) === 'checkbox' && specBooleanMap) {
20
+ return (0, utils_1.objectKeys)(specBooleanMap).find((key) => specBooleanMap[key] === valueKey);
21
+ }
22
+ return (((_b = spec.description) === null || _b === void 0 ? void 0 : _b[valueKey]) ||
23
+ ((_c = specProperties[valueKey]) === null || _c === void 0 ? void 0 : _c.viewSpec.layoutTitle) ||
19
24
  valueKey);
20
- }, [valueKey, spec.description, specProperties]);
25
+ }, [
26
+ valueKey,
27
+ spec.description,
28
+ specProperties,
29
+ (_b = spec.viewSpec.oneOfParams) === null || _b === void 0 ? void 0 : _b.toggler,
30
+ specBooleanMap,
31
+ ]);
21
32
  const wrappedValue = react_1.default.useMemo(() => {
22
33
  if (Layout) {
23
34
  return (react_1.default.createElement(Layout, Object.assign({}, props, { value: valueName }),
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.COMMON_TITLE_MAX_WIDTH = exports.COMMON_POPOVER_PLACEMENT = void 0;
3
+ exports.OBJECT_VALUE_PROPERTY_NAME = exports.COMMON_TITLE_MAX_WIDTH = exports.COMMON_POPOVER_PLACEMENT = void 0;
4
4
  exports.COMMON_POPOVER_PLACEMENT = ['bottom', 'top'];
5
5
  exports.COMMON_TITLE_MAX_WIDTH = 533;
6
+ exports.OBJECT_VALUE_PROPERTY_NAME = 'value';
@@ -11,4 +11,9 @@
11
11
  }
12
12
  .df-use-oneof__card > :first-child {
13
13
  margin-right: 8px;
14
+ }
15
+ .df-use-oneof__checkbox {
16
+ height: 28px;
17
+ display: flex;
18
+ align-items: center;
14
19
  }
@@ -11,9 +11,10 @@ const utils_1 = require("../../utils");
11
11
  const b = (0, utils_1.block)('use-oneof');
12
12
  const MAX_TAB_TITLE_LENGTH = 20;
13
13
  const useOneOf = ({ props, onTogglerChange }) => {
14
- var _a;
14
+ var _a, _b;
15
15
  const { name, input, spec, Layout } = props;
16
16
  const specProperties = react_1.default.useMemo(() => ((0, isObjectLike_1.default)(spec.properties) ? spec.properties : {}), [spec.properties]);
17
+ const specBooleanMap = react_1.default.useMemo(() => { var _a; return (_a = spec.viewSpec.oneOfParams) === null || _a === void 0 ? void 0 : _a.booleanMap; }, [(_a = spec.viewSpec.oneOfParams) === null || _a === void 0 ? void 0 : _a.booleanMap]);
17
18
  const [oneOfValue, setOneOfValue] = react_1.default.useState(() => {
18
19
  let valueKeys;
19
20
  if ((0, isObjectLike_1.default)(input.value)) {
@@ -31,6 +32,20 @@ const useOneOf = ({ props, onTogglerChange }) => {
31
32
  onTogglerChange === null || onTogglerChange === void 0 ? void 0 : onTogglerChange(newValue);
32
33
  }
33
34
  }, [setOneOfValue, input.onChange, oneOfValue]);
35
+ const onCheckboxChange = react_1.default.useCallback((checked) => {
36
+ if (specBooleanMap) {
37
+ const value = String(checked);
38
+ const newValue = specBooleanMap[value];
39
+ onOneOfChange([newValue]);
40
+ }
41
+ }, [onOneOfChange, specBooleanMap]);
42
+ const checkboxValue = react_1.default.useMemo(() => {
43
+ if (specBooleanMap) {
44
+ const keyBooleanMap = (0, utils_1.objectKeys)(specBooleanMap).find((key) => specBooleanMap[key] === oneOfValue);
45
+ return keyBooleanMap === 'true';
46
+ }
47
+ return undefined;
48
+ }, [oneOfValue, specBooleanMap]);
34
49
  const options = react_1.default.useMemo(() => (spec.viewSpec.order || Object.keys(specProperties)).map((value) => {
35
50
  var _a, _b;
36
51
  const title = ((_a = spec.description) === null || _a === void 0 ? void 0 : _a[value]) ||
@@ -44,7 +59,7 @@ const useOneOf = ({ props, onTogglerChange }) => {
44
59
  };
45
60
  }), [spec.description, spec.viewSpec.order, specProperties]);
46
61
  const togglerType = react_1.default.useMemo(() => {
47
- var _a, _b, _c;
62
+ var _a, _b, _c, _d;
48
63
  if (((_a = spec.viewSpec.oneOfParams) === null || _a === void 0 ? void 0 : _a.toggler) === 'card' && options.length < 3) {
49
64
  return 'card';
50
65
  }
@@ -54,8 +69,13 @@ const useOneOf = ({ props, onTogglerChange }) => {
54
69
  (0, some_1.default)(options, ({ title }) => title.length > MAX_TAB_TITLE_LENGTH))) {
55
70
  return 'select';
56
71
  }
72
+ if (((_d = spec.viewSpec.oneOfParams) === null || _d === void 0 ? void 0 : _d.toggler) === 'checkbox' &&
73
+ options.length === 2 &&
74
+ specBooleanMap) {
75
+ return 'checkbox';
76
+ }
57
77
  return 'radio';
58
- }, [options, (_a = spec.viewSpec.oneOfParams) === null || _a === void 0 ? void 0 : _a.toggler]);
78
+ }, [options, (_b = spec.viewSpec.oneOfParams) === null || _b === void 0 ? void 0 : _b.toggler, specBooleanMap]);
59
79
  const togglerInput = react_1.default.useMemo(() => {
60
80
  if (togglerType === 'card') {
61
81
  return (react_1.default.createElement("div", { className: b('card') }, options.map(({ value }) => {
@@ -69,15 +89,22 @@ const useOneOf = ({ props, onTogglerChange }) => {
69
89
  if (togglerType === 'select') {
70
90
  return (react_1.default.createElement(uikit_1.Select, { width: "max", value: [oneOfValue], onUpdate: onOneOfChange, options: options, disabled: spec.viewSpec.disabled, filterable: options.length > 7, qa: name }));
71
91
  }
92
+ if (togglerType === 'checkbox') {
93
+ return (react_1.default.createElement("div", { className: b('checkbox') },
94
+ react_1.default.createElement(uikit_1.Checkbox, { checked: checkboxValue, onUpdate: onCheckboxChange, disabled: spec.viewSpec.disabled, qa: name })));
95
+ }
72
96
  return (react_1.default.createElement(uikit_1.RadioButton, { value: oneOfValue, onChange: (event) => onOneOfChange([event.target.value]), disabled: spec.viewSpec.disabled, qa: name }, options.map((option) => (react_1.default.createElement(uikit_1.RadioButton.Option, { key: option.value, value: option.value }, option.title)))));
73
97
  }, [
74
98
  togglerType,
75
99
  oneOfValue,
76
100
  spec.viewSpec.disabled,
101
+ spec.description,
77
102
  name,
78
103
  options,
79
104
  onOneOfChange,
80
105
  specProperties,
106
+ onCheckboxChange,
107
+ checkboxValue,
81
108
  ]);
82
109
  const toggler = react_1.default.useMemo(() => {
83
110
  if (Layout) {
@@ -5,3 +5,4 @@ tslib_1.__exportStar(require("./cn"), exports);
5
5
  tslib_1.__exportStar(require("./common"), exports);
6
6
  tslib_1.__exportStar(require("./bigIntMath"), exports);
7
7
  tslib_1.__exportStar(require("./objectInline"), exports);
8
+ tslib_1.__exportStar(require("./objectKeys"), exports);
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.objectKeys = void 0;
4
+ function objectKeys(obj) {
5
+ return Object.keys(obj);
6
+ }
7
+ exports.objectKeys = objectKeys;
@@ -95,12 +95,14 @@ export interface ObjectSpec<LinkType = any, InputComponentProps extends Record<s
95
95
  order?: string[];
96
96
  link?: LinkType;
97
97
  oneOfParams?: {
98
- toggler?: 'select' | 'radio' | 'card';
98
+ toggler?: 'select' | 'radio' | 'card' | 'checkbox';
99
+ booleanMap?: Record<'true' | 'false', string>;
99
100
  };
100
101
  placeholder?: string;
101
102
  hidden?: boolean;
102
103
  inputProps?: InputComponentProps;
103
104
  layoutProps?: LayoutComponentProps;
105
+ delimiter?: Record<string, string>;
104
106
  };
105
107
  }
106
108
  export interface StringSpec<LinkType = any, InputComponentProps extends Record<string, any> | undefined = undefined, LayoutComponentProps extends Record<string, any> | undefined = undefined> {
@@ -8,4 +8,10 @@
8
8
  }
9
9
  .df-object-base__content_inline > .df-use-search:last-child {
10
10
  margin-right: 0;
11
+ }
12
+ .df-object-base__delimiter {
13
+ display: flex;
14
+ margin-right: 8px;
15
+ align-items: center;
16
+ white-space: nowrap;
11
17
  }
@@ -1,7 +1,7 @@
1
1
  import { __rest } from "tslib";
2
2
  import React from 'react';
3
3
  import { Plus } from '@gravity-ui/icons';
4
- import { Button, Icon } from '@gravity-ui/uikit';
4
+ import { Button, Icon, Text } from '@gravity-ui/uikit';
5
5
  import isObjectLike from 'lodash/isObjectLike';
6
6
  import set from 'lodash/set';
7
7
  import { Controller, transformArrIn, } from '../../../../core';
@@ -29,12 +29,17 @@ export const ObjectBase = (_a) => {
29
29
  const specProperties = inline
30
30
  ? filterPropertiesForObjectInline(spec.properties)
31
31
  : spec.properties;
32
- return (React.createElement("div", { className: b('content', { inline }) }, (spec.viewSpec.order || Object.keys(specProperties)).map((property) => {
32
+ const delimiter = spec.viewSpec.delimiter;
33
+ const orderProperties = spec.viewSpec.order || Object.keys(specProperties);
34
+ return (React.createElement("div", { className: b('content', { inline }) }, orderProperties.map((property) => {
33
35
  var _a;
34
- return specProperties[property] ? (React.createElement(Controller, { value: (_a = restProps.input.value) === null || _a === void 0 ? void 0 : _a[property], spec: specProperties[property], name: `${name ? name + '.' : ''}${property}`, parentOnChange: parentOnChange, parentOnUnmount: restProps.input.parentOnUnmount, key: `${name ? name + '.' : ''}${property}` })) : null;
36
+ return specProperties[property] ? (React.createElement(React.Fragment, { key: `${name ? name + '.' : ''}${property}` },
37
+ React.createElement(Controller, { value: (_a = restProps.input.value) === null || _a === void 0 ? void 0 : _a[property], spec: specProperties[property], name: `${name ? name + '.' : ''}${property}`, parentOnChange: parentOnChange, parentOnUnmount: restProps.input.parentOnUnmount }),
38
+ delimiter && delimiter[property] ? (React.createElement(Text, { className: b('delimiter') }, delimiter[property])) : null)) : null;
35
39
  })));
36
40
  }, [
37
41
  spec.properties,
42
+ spec.viewSpec.delimiter,
38
43
  spec.viewSpec.order,
39
44
  restProps.input.value,
40
45
  restProps.input.parentOnUnmount,
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  import cloneDeep from 'lodash/cloneDeep';
3
3
  import set from 'lodash/set';
4
4
  import { Controller } from '../../../../core';
5
- const OBJECT_VALUE_PROPERTY_NAME = 'value';
5
+ import { OBJECT_VALUE_PROPERTY_NAME } from '../../../constants/common';
6
6
  export const ObjectValueInput = (props) => {
7
7
  var _a;
8
8
  const { spec, input, name, Layout } = props;
@@ -8,4 +8,10 @@
8
8
  }
9
9
  .df-object-base-view__content_inline > div:last-child {
10
10
  margin-right: 0;
11
+ }
12
+ .df-object-base-view__delimiter {
13
+ display: flex;
14
+ margin-right: 8px;
15
+ align-items: center;
16
+ white-space: nowrap;
11
17
  }
@@ -1,20 +1,27 @@
1
1
  import { __rest } from "tslib";
2
2
  import React from 'react';
3
3
  import isObjectLike from 'lodash/isObjectLike';
4
+ import { Text } from '@gravity-ui/uikit';
4
5
  import { ViewController } from '../../../../core';
5
6
  import { block, filterPropertiesForObjectInline } from '../../../utils';
6
7
  import './ObjectBaseView.css';
7
8
  const b = block('object-base-view');
8
9
  export const ObjectBaseView = (_a) => {
9
10
  var { inline, spec, name, Layout } = _a, restProps = __rest(_a, ["inline", "spec", "name", "Layout"]);
10
- if (!spec.properties || !isObjectLike(spec.properties)) {
11
- return null;
12
- }
13
- const specProperties = inline
14
- ? filterPropertiesForObjectInline(spec.properties)
15
- : spec.properties;
16
- const content = (React.createElement("div", { className: b('content', { inline }) }, (spec.viewSpec.order || Object.keys(specProperties)).map((property) => specProperties[property] ? (React.createElement(ViewController, { spec: specProperties[property], name: `${name ? name + '.' : ''}${property}`, key: `${name ? name + '.' : ''}${property}` })) : null)));
17
- if (!Layout) {
11
+ const content = React.useMemo(() => {
12
+ if (!spec.properties || !isObjectLike(spec.properties)) {
13
+ return null;
14
+ }
15
+ const specProperties = inline
16
+ ? filterPropertiesForObjectInline(spec.properties)
17
+ : spec.properties;
18
+ const delimiter = spec.viewSpec.delimiter;
19
+ const orderProperties = spec.viewSpec.order || Object.keys(specProperties);
20
+ return (React.createElement("div", { className: b('content', { inline }) }, orderProperties.map((property) => specProperties[property] ? (React.createElement(React.Fragment, { key: `${name ? name + '.' : ''}${property}` },
21
+ React.createElement(ViewController, { spec: specProperties[property], name: `${name ? name + '.' : ''}${property}` }),
22
+ delimiter && delimiter[property] ? (React.createElement(Text, { className: b('delimiter') }, delimiter[property])) : null)) : null)));
23
+ }, [inline, name, spec.properties, spec.viewSpec.delimiter, spec.viewSpec.order]);
24
+ if (!Layout || !content) {
18
25
  return content;
19
26
  }
20
27
  return (React.createElement(Layout, Object.assign({ spec: spec, name: name }, restProps), content));
@@ -2,7 +2,7 @@ import { __rest } from "tslib";
2
2
  import React from 'react';
3
3
  import cloneDeep from 'lodash/cloneDeep';
4
4
  import { ViewController } from '../../../../core';
5
- const OBJECT_VALUE_PROPERTY_NAME = 'value';
5
+ import { OBJECT_VALUE_PROPERTY_NAME } from '../../../constants/common';
6
6
  export const ObjectValueInputView = (_a) => {
7
7
  var { spec, name, Layout } = _a, restProps = __rest(_a, ["spec", "name", "Layout"]);
8
8
  const childSpec = React.useMemo(() => {
@@ -2,19 +2,30 @@ import React from 'react';
2
2
  import isObjectLike from 'lodash/isObjectLike';
3
3
  import { GroupIndent } from '../../';
4
4
  import { ViewController } from '../../../../core';
5
- import { block } from '../../../utils';
5
+ import { block, objectKeys } from '../../../utils';
6
6
  import './OneOfView.css';
7
7
  const b = block('oneof-view');
8
8
  const OneOfViewComponent = (props) => {
9
+ var _a, _b;
9
10
  const { value = {}, spec, Layout, name } = props;
10
11
  const specProperties = React.useMemo(() => (isObjectLike(spec.properties) ? spec.properties : {}), [spec.properties]);
12
+ const specBooleanMap = React.useMemo(() => { var _a; return (_a = spec.viewSpec.oneOfParams) === null || _a === void 0 ? void 0 : _a.booleanMap; }, [(_a = spec.viewSpec.oneOfParams) === null || _a === void 0 ? void 0 : _a.booleanMap]);
11
13
  const valueKey = React.useMemo(() => Object.keys(value)[0], [value]);
12
14
  const valueName = React.useMemo(() => {
13
- var _a, _b;
14
- return (((_a = spec.description) === null || _a === void 0 ? void 0 : _a[valueKey]) ||
15
- ((_b = specProperties[valueKey]) === null || _b === void 0 ? void 0 : _b.viewSpec.layoutTitle) ||
15
+ var _a, _b, _c;
16
+ if (((_a = spec.viewSpec.oneOfParams) === null || _a === void 0 ? void 0 : _a.toggler) === 'checkbox' && specBooleanMap) {
17
+ return objectKeys(specBooleanMap).find((key) => specBooleanMap[key] === valueKey);
18
+ }
19
+ return (((_b = spec.description) === null || _b === void 0 ? void 0 : _b[valueKey]) ||
20
+ ((_c = specProperties[valueKey]) === null || _c === void 0 ? void 0 : _c.viewSpec.layoutTitle) ||
16
21
  valueKey);
17
- }, [valueKey, spec.description, specProperties]);
22
+ }, [
23
+ valueKey,
24
+ spec.description,
25
+ specProperties,
26
+ (_b = spec.viewSpec.oneOfParams) === null || _b === void 0 ? void 0 : _b.toggler,
27
+ specBooleanMap,
28
+ ]);
18
29
  const wrappedValue = React.useMemo(() => {
19
30
  if (Layout) {
20
31
  return (React.createElement(Layout, Object.assign({}, props, { value: valueName }),
@@ -1,3 +1,4 @@
1
1
  import type { PopoverProps } from '@gravity-ui/uikit';
2
2
  export declare const COMMON_POPOVER_PLACEMENT: PopoverProps['placement'];
3
3
  export declare const COMMON_TITLE_MAX_WIDTH = 533;
4
+ export declare const OBJECT_VALUE_PROPERTY_NAME = "value";
@@ -1,2 +1,3 @@
1
1
  export const COMMON_POPOVER_PLACEMENT = ['bottom', 'top'];
2
2
  export const COMMON_TITLE_MAX_WIDTH = 533;
3
+ export const OBJECT_VALUE_PROPERTY_NAME = 'value';
@@ -11,4 +11,9 @@
11
11
  }
12
12
  .df-use-oneof__card > :first-child {
13
13
  margin-right: 8px;
14
+ }
15
+ .df-use-oneof__checkbox {
16
+ height: 28px;
17
+ display: flex;
18
+ align-items: center;
14
19
  }
@@ -1,16 +1,17 @@
1
1
  import React from 'react';
2
- import { RadioButton, Select } from '@gravity-ui/uikit';
2
+ import { Checkbox, RadioButton, Select } from '@gravity-ui/uikit';
3
3
  import isObjectLike from 'lodash/isObjectLike';
4
4
  import some from 'lodash/some';
5
5
  import { TogglerCard } from '../../components';
6
- import { block } from '../../utils';
6
+ import { block, objectKeys } from '../../utils';
7
7
  import './useOneOf.css';
8
8
  const b = block('use-oneof');
9
9
  const MAX_TAB_TITLE_LENGTH = 20;
10
10
  export const useOneOf = ({ props, onTogglerChange }) => {
11
- var _a;
11
+ var _a, _b;
12
12
  const { name, input, spec, Layout } = props;
13
13
  const specProperties = React.useMemo(() => (isObjectLike(spec.properties) ? spec.properties : {}), [spec.properties]);
14
+ const specBooleanMap = React.useMemo(() => { var _a; return (_a = spec.viewSpec.oneOfParams) === null || _a === void 0 ? void 0 : _a.booleanMap; }, [(_a = spec.viewSpec.oneOfParams) === null || _a === void 0 ? void 0 : _a.booleanMap]);
14
15
  const [oneOfValue, setOneOfValue] = React.useState(() => {
15
16
  let valueKeys;
16
17
  if (isObjectLike(input.value)) {
@@ -28,6 +29,20 @@ export const useOneOf = ({ props, onTogglerChange }) => {
28
29
  onTogglerChange === null || onTogglerChange === void 0 ? void 0 : onTogglerChange(newValue);
29
30
  }
30
31
  }, [setOneOfValue, input.onChange, oneOfValue]);
32
+ const onCheckboxChange = React.useCallback((checked) => {
33
+ if (specBooleanMap) {
34
+ const value = String(checked);
35
+ const newValue = specBooleanMap[value];
36
+ onOneOfChange([newValue]);
37
+ }
38
+ }, [onOneOfChange, specBooleanMap]);
39
+ const checkboxValue = React.useMemo(() => {
40
+ if (specBooleanMap) {
41
+ const keyBooleanMap = objectKeys(specBooleanMap).find((key) => specBooleanMap[key] === oneOfValue);
42
+ return keyBooleanMap === 'true';
43
+ }
44
+ return undefined;
45
+ }, [oneOfValue, specBooleanMap]);
31
46
  const options = React.useMemo(() => (spec.viewSpec.order || Object.keys(specProperties)).map((value) => {
32
47
  var _a, _b;
33
48
  const title = ((_a = spec.description) === null || _a === void 0 ? void 0 : _a[value]) ||
@@ -41,7 +56,7 @@ export const useOneOf = ({ props, onTogglerChange }) => {
41
56
  };
42
57
  }), [spec.description, spec.viewSpec.order, specProperties]);
43
58
  const togglerType = React.useMemo(() => {
44
- var _a, _b, _c;
59
+ var _a, _b, _c, _d;
45
60
  if (((_a = spec.viewSpec.oneOfParams) === null || _a === void 0 ? void 0 : _a.toggler) === 'card' && options.length < 3) {
46
61
  return 'card';
47
62
  }
@@ -51,8 +66,13 @@ export const useOneOf = ({ props, onTogglerChange }) => {
51
66
  some(options, ({ title }) => title.length > MAX_TAB_TITLE_LENGTH))) {
52
67
  return 'select';
53
68
  }
69
+ if (((_d = spec.viewSpec.oneOfParams) === null || _d === void 0 ? void 0 : _d.toggler) === 'checkbox' &&
70
+ options.length === 2 &&
71
+ specBooleanMap) {
72
+ return 'checkbox';
73
+ }
54
74
  return 'radio';
55
- }, [options, (_a = spec.viewSpec.oneOfParams) === null || _a === void 0 ? void 0 : _a.toggler]);
75
+ }, [options, (_b = spec.viewSpec.oneOfParams) === null || _b === void 0 ? void 0 : _b.toggler, specBooleanMap]);
56
76
  const togglerInput = React.useMemo(() => {
57
77
  if (togglerType === 'card') {
58
78
  return (React.createElement("div", { className: b('card') }, options.map(({ value }) => {
@@ -66,15 +86,22 @@ export const useOneOf = ({ props, onTogglerChange }) => {
66
86
  if (togglerType === 'select') {
67
87
  return (React.createElement(Select, { width: "max", value: [oneOfValue], onUpdate: onOneOfChange, options: options, disabled: spec.viewSpec.disabled, filterable: options.length > 7, qa: name }));
68
88
  }
89
+ if (togglerType === 'checkbox') {
90
+ return (React.createElement("div", { className: b('checkbox') },
91
+ React.createElement(Checkbox, { checked: checkboxValue, onUpdate: onCheckboxChange, disabled: spec.viewSpec.disabled, qa: name })));
92
+ }
69
93
  return (React.createElement(RadioButton, { value: oneOfValue, onChange: (event) => onOneOfChange([event.target.value]), disabled: spec.viewSpec.disabled, qa: name }, options.map((option) => (React.createElement(RadioButton.Option, { key: option.value, value: option.value }, option.title)))));
70
94
  }, [
71
95
  togglerType,
72
96
  oneOfValue,
73
97
  spec.viewSpec.disabled,
98
+ spec.description,
74
99
  name,
75
100
  options,
76
101
  onOneOfChange,
77
102
  specProperties,
103
+ onCheckboxChange,
104
+ checkboxValue,
78
105
  ]);
79
106
  const toggler = React.useMemo(() => {
80
107
  if (Layout) {
@@ -2,3 +2,4 @@ export * from './cn';
2
2
  export * from './common';
3
3
  export * from './bigIntMath';
4
4
  export * from './objectInline';
5
+ export * from './objectKeys';
@@ -2,3 +2,4 @@ export * from './cn';
2
2
  export * from './common';
3
3
  export * from './bigIntMath';
4
4
  export * from './objectInline';
5
+ export * from './objectKeys';
@@ -0,0 +1,4 @@
1
+ type Dictionary<T> = Record<string, T>;
2
+ type KeysOf<T> = T extends Dictionary<unknown> ? `${Exclude<keyof T, symbol>}` : never;
3
+ export declare function objectKeys<T extends Dictionary<unknown>>(obj: T): KeysOf<T>[];
4
+ export {};
@@ -0,0 +1,3 @@
1
+ export function objectKeys(obj) {
2
+ return Object.keys(obj);
3
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gravity-ui/dynamic-forms",
3
- "version": "4.0.1",
3
+ "version": "4.1.0",
4
4
  "description": "",
5
5
  "license": "MIT",
6
6
  "main": "build/cjs/index.js",