Repository: DevExpress/devextreme-react
Pull request #94 (2018-05-17 11:29:51)

Add configuration components basic implementation


Patch:
diff --git a/build.config.js b/build.config.js
index e7b3052..9d107af 100644
--- a/build.config.js
+++ b/build.config.js
@@ -19,5 +19,6 @@ module.exports = {
   metadataPath: './tools/integration-data.json',
   componentFolder: './src/ui',
   indexFileName: './src/index.ts',
-  baseComponent: '../core/component'
+  baseComponent: '../core/component',
+  configComponent: '../core/configuration-component'
 }
\ No newline at end of file
diff --git a/example/data-grid-example.tsx b/example/data-grid-example.tsx
index 63b6bbc..6537e7b 100644
--- a/example/data-grid-example.tsx
+++ b/example/data-grid-example.tsx
@@ -2,11 +2,12 @@ import * as React from "react";
 import Example from "./example-block";
 
 import { CheckBox } from "../src/ui/check-box";
-import { DataGrid } from "../src/ui/data-grid";
+import { DataGrid, DataGridPaging as Paging } from "../src/ui/data-grid";
+import { NumberBox } from "../src/ui/number-box";
 
 import { sales } from "./data";
 
-export default class extends React.Component<any, { expandAll: boolean }> {
+export default class extends React.Component<any, { expandAll: boolean, pageIndex: number }> {
 
     private columns = [
         {
@@ -39,10 +40,12 @@ export default class extends React.Component<any, { expandAll: boolean }> {
     constructor(props: any) {
         super(props);
         this.state = {
-            expandAll: true
+            expandAll: true,
+            pageIndex: 1
         };
 
-        this.handleChange = this.handleChange.bind(this);
+        this.handleExpandAllChange = this.handleExpandAllChange.bind(this);
+        this.handlePageIndexChange = this.handlePageIndexChange.bind(this);
     }
 
     public render() {
@@ -54,7 +57,6 @@ export default class extends React.Component<any, { expandAll: boolean }> {
                     allowColumnReordering={true}
                     grouping={{ autoExpandAll: this.state.expandAll }}
                     groupPanel={{ visible: true }}
-                    defaultPaging={{ pageSize: 10 }}
                     pager={{
                         showPageSizeSelector: true,
                         allowedPageSizes: [
@@ -67,20 +69,37 @@ export default class extends React.Component<any, { expandAll: boolean }> {
                     selection={{ mode: "multiple" }}
                     filterRow={{ visible: true }}
                     defaultColumns={this.columns}
-                />
+                >
+                    <Paging
+                        pageSize={5}
+                        pageIndex={this.state.pageIndex}
+                    />
+                </DataGrid>
                 <br />
                 <CheckBox
                     text="Expand All Groups"
                     value={this.state.expandAll}
-                    onValueChanged={this.handleChange}
+                    onValueChanged={this.handleExpandAllChange}
+                />
+                <br/>
+                <br/>
+                <NumberBox
+                    showSpinButtons={true}
+                    value={this.state.pageIndex}
+                    onValueChanged={this.handlePageIndexChange}
                 />
             </Example>
         );
     }
 
-    private handleChange(e: any) {
+    private handleExpandAllChange(e: any) {
         this.setState({
             expandAll: e.value
         });
     }
+    private handlePageIndexChange(e: any) {
+        this.setState({
+            pageIndex: e.value
+        });
+    }
 }
diff --git a/gulpfile.js b/gulpfile.js
index 39117ab..bc3427a 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -69,6 +69,7 @@ gulp.task(GEN_RUN, (done) => {
   generateSync(
     JSON.parse(fs.readFileSync(config.metadataPath).toString()),
     config.baseComponent,
+    config.configComponent,
     {
       componentsDir: config.componentFolder,
       indexFileName: config.indexFileName
diff --git a/src/core/component.ts b/src/core/component.ts
index 5262ae8..ff616a3 100644
--- a/src/core/component.ts
+++ b/src/core/component.ts
@@ -2,25 +2,35 @@ import * as React from "react";
 
 import * as events from "devextreme/events";
 
-import { ReactElement } from "react";
-import { Template } from "./template";
+import { createConfigurationComponent } from "./configuration-component";
+import { addPrefixToKeys, getNestedValue } from "./helpers";
+import { splitProps } from "./props-preprocessor";
+import { ITemplateMeta } from "./template";
 import { prepareTemplate } from "./template-provider";
 
 const DX_REMOVE_EVENT = "dxremove";
 
-interface ITemplateMeta {
-  tmplOption: string;
-  render: string;
-  component: string;
+interface IChildComponent {
+  type: {
+    IsCollectionItem: boolean;
+    OwnerType: any;
+    OptionName: string;
+  };
+  props: object;
 }
 
-class Component<P> extends React.PureComponent<P, any> {
+interface IState {
+  templates: {};
+}
+
+class Component<P> extends React.PureComponent<P, IState> {
 
   protected _WidgetClass: any;
   protected _instance: any;
   protected readonly _defaults: Record<string, string>;
 
-  protected _templateProps: ITemplateMeta[] = [];
+  protected readonly _templateProps: ITemplateMeta[] = [];
+  protected _nestedOptionIdPrefix: string;
 
   private readonly _guards: Record<string, number> = {};
   private _element: any;
@@ -29,17 +39,17 @@ class Component<P> extends React.PureComponent<P, any> {
   constructor(props: P) {
     super(props);
     this._optionChangedHandler = this._optionChangedHandler.bind(this);
-    this._splitComponentProps = this._splitComponentProps.bind(this);
+    this._prepareProps = this._prepareProps.bind(this);
     this.state = {
       templates: {}
     };
   }
 
   public componentWillUpdate(nextProps: P) {
-    const splitProps = this._splitComponentProps(nextProps);
+    const preparedProps = this._prepareProps(nextProps);
     const options: Record<string, any> = {
-      ...splitProps.options,
-      ...this._getIntegrationOptions(splitProps.templates, splitProps.nestedTemplates)
+      ...preparedProps.options,
+      ...this._getIntegrationOptions(preparedProps.templates, preparedProps.nestedTemplates)
     };
 
     this._processChangedValues(options, this.props);
@@ -50,25 +60,44 @@ class Component<P> extends React.PureComponent<P, any> {
         "div",
         { ref: (element: any) => this._element = element }
       ];
+
       if (!!this.props.children) {
-        args.push(this.props.children);
+        if (Array.isArray(this.props.children)) {
+          this.props.children.forEach((c) => args.push(this.wrapConfigComponent(c)));
+        } else {
+          args.push(this.wrapConfigComponent(this.props.children));
+        }
       }
+
       const templates = Object.getOwnPropertyNames(this.state.templates);
       if (templates.length) {
-        args.push(templates.map((m: any) => this.state.templates[m]()));
+        templates.forEach((t) => args.push(this.state.templates[t]()));
       }
+
       return React.createElement.apply(this, args);
   }
 
+  public updateNested(
+    optionName: string,
+    newProps: Record<string, any>,
+    prevProps: Record<string, any>
+  ): void {
+    this._processChangedValues(
+      addPrefixToKeys(newProps, optionName + "."),
+      addPrefixToKeys(prevProps, optionName + ".")
+    );
+  }
+
   public componentDidMount() {
-    const props: Record<string, any> = this.props;
-    const splitProps = this._splitComponentProps(props);
+    const props = this.getActualProps();
+
+    const preparedProps = this._prepareProps(props);
 
     const options: Record<string, any> = {
       templatesRenderAsynchronously: true,
-      ...splitProps.defaults,
-      ...splitProps.options,
-      ...this._getIntegrationOptions(splitProps.templates, splitProps.nestedTemplates)
+      ...preparedProps.defaults,
+      ...preparedProps.options,
+      ...this._getIntegrationOptions(preparedProps.templates, preparedProps.nestedTemplates)
     };
 
     this._instance = new this._WidgetClass(this._element, options);
@@ -80,14 +109,14 @@ class Component<P> extends React.PureComponent<P, any> {
     this._instance.dispose();
   }
 
-  private _optionChangedHandler(e: { name: string, value: any }) {
-    const optionName = e.name;
-    const optionValue = (this.props as any)[optionName];
-
+  private _optionChangedHandler(e: { fullName: string, value: any }) {
     if (this._updatingProps) {
       return;
     }
 
+    const optionName = e.fullName;
+    const optionValue = getNestedValue(this.getActualProps(), optionName.split("."));
+
     if (optionValue === undefined || optionValue === null) {
       return;
     }
@@ -105,65 +134,42 @@ class Component<P> extends React.PureComponent<P, any> {
     this._guards[optionName] = guardId;
   }
 
-  private _splitComponentProps(props: Record<string, any>): {
+  private _prepareProps(rawProps: Record<string, any>): {
     defaults: Record<string, any>,
     options: Record<string, any>,
     templates: Record<string, any>,
     nestedTemplates: Record<string, any>
   } {
-    const defaults: Record<string, any> = {};
-    const options: Record<string, any> = {};
-    const templates: Record<string, any> = {};
-    const nestedTemplates: Record<string, any> = {};
-
-    const knownTemplates: Record<string, any> = {};
-
-    this._templateProps.forEach((value) => {
-      knownTemplates[value.component] = true;
-      knownTemplates[value.render] = true;
-    });
-
-    Object.keys(props).forEach((key) => {
-      const defaultOptionName = this._defaults ? this._defaults[key] : null;
-
-      if (defaultOptionName) {
-        defaults[defaultOptionName] = props[key];
-      } else if (knownTemplates[key]) {
-        templates[key] = props[key];
-      } else if (key === "children") {
-        React.Children.forEach(props[key], (child: ReactElement<any>) => {
-            if (child.type === Template) {
-                nestedTemplates[child.props.name] = {
-                    render: child.props.render,
-                    component: child.props.component
-                };
-            }
-        });
-      } else {
-        options[key] = this._wrapEventHandler(props, key);
-      }
-    });
+    const props = splitProps(rawProps, this._defaults, this._templateProps);
+    const options = props.options;
+    props.options = {};
+
+    if (options) {
+      Object.keys(options).forEach((key) => {
+        props.options[key] = this._wrapEventHandler(options[key], key);
+      });
+    }
 
-    return { defaults, options, templates, nestedTemplates };
+    return props;
   }
 
-  private _wrapEventHandler(options: Record<string, any>, key: string): any {
-    if (key.substr(0, 2) === "on" && typeof options[key] === "function") {
+  private _wrapEventHandler(optionValue: any, optionName: string): any {
+    if (optionName.substr(0, 2) === "on" && typeof optionValue === "function") {
       return (...args: any[]) => {
         if (!this._updatingProps) {
-          options[key](...args);
+          optionValue(...args);
         }
       };
     }
 
-    return options[key];
+    return optionValue;
   }
 
-  private _processChangedValues(props: Record<string, any>, prevProps: Record<string, any>): void {
+  private _processChangedValues(newProps: Record<string, any>, prevProps: Record<string, any>): void {
     this._updatingProps = false;
 
-    for (const optionName of Object.keys(props)) {
-      if (props[optionName] !== prevProps[optionName]) {
+    for (const optionName of Object.keys(newProps)) {
+      if (newProps[optionName] !== prevProps[optionName]) {
         if (this._guards[optionName]) {
           window.clearTimeout(this._guards[optionName]);
           delete this._guards[optionName];
@@ -173,7 +179,7 @@ class Component<P> extends React.PureComponent<P, any> {
           this._instance.beginUpdate();
           this._updatingProps = true;
         }
-        this._instance.option(optionName, props[optionName]);
+        this._instance.option(optionName, newProps[optionName]);
       }
     }
 
@@ -214,7 +220,57 @@ class Component<P> extends React.PureComponent<P, any> {
       return result;
     }
   }
+
+  private wrapConfigComponent(component: any): any {
+    const configComponent = component as IChildComponent;
+    if (
+      configComponent && configComponent.type &&
+      configComponent.type.OptionName &&
+      configComponent.type.OwnerType && this instanceof configComponent.type.OwnerType
+    ) {
+      const optionName = configComponent.type.OptionName;
+      return createConfigurationComponent(
+        component,
+        (newProps, prevProps) => this.updateNested(optionName, newProps, prevProps)
+      );
+    }
+
+    return component;
+  }
+
+  private getActualProps(): Record<string, any> {
+
+    const nestedOptions: Record<string, any> = {};
+    const children: any = this.props.children;
+    if (!!children) {
+
+      const nested: IChildComponent[] = Array.isArray(children)
+        ? children as IChildComponent[]
+        : [children as IChildComponent];
+
+      nested.forEach((child) => {
+        if (child && child.type && child.type.OwnerType && this instanceof child.type.OwnerType) {
+
+          if (child.type.IsCollectionItem) {
+
+            if (nestedOptions[child.type.OptionName] === null || nestedOptions[child.type.OptionName] === undefined) {
+              nestedOptions[child.type.OptionName] = [];
+            }
+
+            nestedOptions[child.type.OptionName].push(child.props);
+          } else {
+            nestedOptions[child.type.OptionName] = child.props;
+          }
+        }
+      });
+    }
+
+    return {
+      ...(this.props as Record<string, any>),
+      ...nestedOptions
+    };
+  }
 }
 
 export default Component;
-export { ITemplateMeta };
+export { IState, ITemplateMeta };
diff --git a/src/core/configuration-component.ts b/src/core/configuration-component.ts
new file mode 100644
index 0000000..8e67767
--- /dev/null
+++ b/src/core/configuration-component.ts
@@ -0,0 +1,37 @@
+import * as React from "react";
+// import { splitProps } from "./props-preprocessor";
+
+type UpdateFunc = (newProps, prevProps) => void;
+
+interface IUpdater {
+    update?: UpdateFunc;
+}
+
+class ConfigurationComponent<P> extends React.PureComponent<P & IUpdater, any> {
+
+    public render() {
+        return null;
+    }
+
+    public componentWillUpdate(nextProps: P & IUpdater) {
+        const newProps = { ...(nextProps as {} & IUpdater) };
+        delete newProps.update;
+
+        const prevProps = { ...(this.props as {} & IUpdater) };
+        delete prevProps.update;
+        if (nextProps.update) {
+            nextProps.update(newProps, prevProps);
+        }
+    }
+}
+
+function createConfigurationComponent<P>(baseElement: React.ReactElement<P>, updateFunc: UpdateFunc) {
+    const extraProps: IUpdater = {
+        update: updateFunc
+    };
+
+    return React.cloneElement(baseElement, extraProps);
+}
+
+export default ConfigurationComponent;
+export { createConfigurationComponent, IUpdater, UpdateFunc };
diff --git a/src/core/helpers.ts b/src/core/helpers.ts
new file mode 100644
index 0000000..e775dcd
--- /dev/null
+++ b/src/core/helpers.ts
@@ -0,0 +1,17 @@
+export function getNestedValue(obj: object, keyParts: string[]): any {
+    let current = obj;
+    keyParts.some((part) => {
+        current = current[part];
+
+        return current === undefined || current === null;
+    });
+
+    return current;
+}
+
+export function addPrefixToKeys(obj: Record<string, any>, prefix: string): Record<string, any> {
+    const result = {};
+    Object.keys(obj).forEach((key) => result[prefix + key] = obj[key]);
+
+    return result;
+}
diff --git a/src/core/props-preprocessor.ts b/src/core/props-preprocessor.ts
new file mode 100644
index 0000000..c841655
--- /dev/null
+++ b/src/core/props-preprocessor.ts
@@ -0,0 +1,50 @@
+import { Children, ReactElement } from "react";
+import { ITemplateMeta, Template } from "./template";
+
+function splitProps(
+    props: Record<string, any>,
+    defaultsProps: Record<string, string>,
+    templateProps: ITemplateMeta[]
+): {
+    defaults: Record<string, any>,
+    options: Record<string, any>,
+    templates: Record<string, any>,
+    nestedTemplates: Record<string, any>
+} {
+    const defaults: Record<string, any> = {};
+    const options: Record<string, any> = {};
+    const templates: Record<string, any> = {};
+    const nestedTemplates: Record<string, any> = {};
+
+    const knownTemplates: Record<string, any> = {};
+
+    templateProps.forEach((value) => {
+        knownTemplates[value.component] = true;
+        knownTemplates[value.render] = true;
+    });
+
+    Object.keys(props).forEach((key) => {
+        const defaultOptionName = defaultsProps ? defaultsProps[key] : null;
+
+        if (defaultOptionName) {
+            defaults[defaultOptionName] = props[key];
+        } else if (knownTemplates[key]) {
+            templates[key] = props[key];
+        } else if (key === "children") {
+            Children.forEach(props[key], (child: ReactElement<any>) => {
+                if (child.type === Template) {
+                    nestedTemplates[child.props.name] = {
+                        render: child.props.render,
+                        component: child.props.component
+                    };
+                }
+            });
+        } else {
+            options[key] = props[key];
+        }
+    });
+
+    return { defaults, options, templates, nestedTemplates };
+}
+
+export { splitProps };
diff --git a/src/core/template-provider.ts b/src/core/template-provider.ts
index bef8b95..857ab0e 100644
--- a/src/core/template-provider.ts
+++ b/src/core/template-provider.ts
@@ -1,5 +1,6 @@
 import * as React from "react";
 
+import Component, { IState } from "./component";
 import TemplateWrapper from "./template-wrapper";
 
 const generateID = () => Math.random().toString(36).substr(2);
@@ -12,12 +13,12 @@ export interface ITemplateData {
   index?: any;
 }
 
-export function prepareTemplate(tmplFn: any, component: React.Component): { render: (data: ITemplateData) => any } {
+export function prepareTemplate(tmplFn: any, component: Component<any>): { render: (data: ITemplateData) => any } {
   return {
     render: (data: ITemplateData) => {
       const templateId = "__template_" + generateID();
       const removedHandler = () => {
-      component.setState((state: any) => {
+      component.setState((state: IState) => {
           const updatedTemplates = {...state.templates};
           delete updatedTemplates[templateId];
           return {
@@ -34,7 +35,7 @@ export function prepareTemplate(tmplFn: any, component: React.Component): { rend
           key: templateId
         });
 
-      component.setState((state: any) => {
+      component.setState((state: IState) => {
         const updatedTemplates = {...state.templates};
         updatedTemplates[templateId] = templateWrapper;
         return {
diff --git a/src/core/template.ts b/src/core/template.ts
index 2f3a6d9..c2d7ed0 100644
--- a/src/core/template.ts
+++ b/src/core/template.ts
@@ -1,6 +1,12 @@
 import { PropTypes } from "prop-types";
 import * as React from "react";
 
+export interface ITemplateMeta {
+    tmplOption: string;
+    component: string;
+    render: string;
+}
+
 export class Template extends React.PureComponent<{
     name: string;
     component?: any;
diff --git a/tools/src/component-generator.ts b/tools/src/component-generator.ts
index 70422a8..acaf938 100644
--- a/tools/src/component-generator.ts
+++ b/tools/src/component-generator.ts
@@ -1,18 +1,22 @@
-import { createKeyComparator, lowercaseFirst, uppercaseFirst } from "./helpers";
+import { createKeyComparator, isNotEmptyArray, lowercaseFirst, uppercaseFirst } from "./helpers";
 import createTempate from "./template";
 
 interface IComponent {
     name: string;
     baseComponentPath: string;
+    configComponentPath: string;
     dxExportPath: string;
     subscribableOptions?: IOption[];
+    nestedOptions?: IOption[];
     templates?: string[];
     propTypings?: IPropTyping[];
 }
 
 interface IOption {
     name: string;
-    type: string;
+    type?: string;
+    nested?: IOption[];
+    isCollectionItem?: boolean;
 }
 
 interface IPropTyping {
@@ -41,17 +45,40 @@ function generate(component: IComponent): string {
             .map((t) => renderPropTyping(createPropTypingModel(t)))
         : null;
 
+    const nestedOptions = component.nestedOptions
+        ? component.nestedOptions
+            .sort(createKeyComparator<IOption>((o) => o.name))
+            .map((o) => ({
+                name: o.name,
+                className: component.name + uppercaseFirst(o.name),
+                interfaceName: `I${uppercaseFirst(o.name)}Options`,
+                rendered: renderObject(o.nested, 0),
+                isCollectionItem: o.isCollectionItem
+            }))
+        : null;
+
+    const optionsName = `I${component.name}Options`;
+    const exportNames = [ component.name, optionsName ];
+    if (isNotEmptyArray(nestedOptions)) {
+        nestedOptions.forEach((opt) => {
+            exportNames.push(opt.className);
+        });
+    }
+
     return renderComponent({
         name: component.name,
         baseComponentPath: component.baseComponentPath,
+        configComponentPath: component.configComponentPath,
         dxExportPath: component.dxExportPath,
 
         widgetName: `dx${uppercaseFirst(component.name)}`,
-        optionsName: `I${component.name}Options`,
+        optionsName,
         hasExtraOptions: !!templates || !!subscribableOptions,
         subscribableOptions,
         templates,
-        renderedPropTypings
+        nestedOptions,
+        renderedPropTypings,
+        renderedExports: renderExports(exportNames)
     });
 }
 
@@ -82,7 +109,7 @@ function createSubscribableOptionModel(option: IOption) {
 
 function createPropTypingModel(typing: IPropTyping): IRenderedPropTyping {
     const types = typing.types.map((t) => "PropTypes." + t);
-    if (typing.acceptableValues && typing.acceptableValues.length > 0) {
+    if (isNotEmptyArray(typing.acceptableValues)) {
         types.push(`PropTypes.oneOf([${typing.acceptableValues.join(", ")}])`);
     }
     return {
@@ -97,6 +124,7 @@ const renderComponent: (model: {
     widgetName: string;
     optionsName: string;
     baseComponentPath: string;
+    configComponentPath: string;
     dxExportPath: string;
     hasExtraOptions: boolean;
     templates: {
@@ -109,13 +137,22 @@ const renderComponent: (model: {
         type: string,
         renderedProp: string
     }[]; // tslint:disable-line:array-type
-    renderedPropTypings: string[]
+    nestedOptions: {
+        name: string;
+        className: string;
+        interfaceName: string;
+        rendered: string;
+        isCollectionItem: boolean;
+    }[]; // tslint:disable-line:array-type
+    renderedPropTypings: string[],
+    renderedExports: string
 }
 ) => string = createTempate(`
 import <#= it.widgetName #>, { IOptions <#? !it.hasExtraOptions #>as <#= it.optionsName #> <#?#>} from "devextreme/<#= it.dxExportPath #>";<#? it.renderedPropTypings #>
 import { PropTypes } from "prop-types";<#?#>
 import BaseComponent from "<#= it.baseComponentPath #>";
-<#? it.hasExtraOptions #>
+<#? it.nestedOptions #>import ConfigurationComponent from "<#= it.configComponentPath #>";
+<#?#><#? it.hasExtraOptions #>
 interface <#= it.optionsName #> extends IOptions {<#~ it.templates :template #>
   <#= template.render #>?: (props: any) => React.ReactNode;
   <#= template.component #>?: React.ComponentType<any>;<#~#><#~ it.subscribableOptions :option #>
@@ -136,8 +173,18 @@ class <#= it.name #> extends BaseComponent<<#= it.optionsName #>> {
   protected _templateProps = [<#= it.templates.map(t => t.renderedProp).join(', ') #>];
 <#?#>}<#? it.renderedPropTypings #>
 (<#= it.name #> as any).propTypes = {<#= it.renderedPropTypings.join(',') #>
-};<#?#>
-export { <#= it.name #>, <#= it.optionsName #> };
+};<#?#><#? it.nestedOptions #>
+// tslint:disable:max-classes-per-file<#~ it.nestedOptions :opt #>
+
+class <#= opt.className #> extends ConfigurationComponent<<#= opt.rendered #>> {<#? opt.isCollectionItem #>
+  public static IsCollectionItem = true;<#?#>
+  public static OwnerType = <#= it.name #>;
+  public static OptionName = "<#= opt.name #>";
+}<#~#>
+<#?#>
+export {
+<#= it.renderedExports #>
+};
 `.trimLeft());
 // tslint:enable:max-line-length
 
@@ -153,13 +200,6 @@ const renderTemplateOption: (model: {
   }
 `.trim());
 
-const renderObjectEntry: (model: {
-    key: string;
-    value: string;
-}) => string = createTempate(`
-    <#= it.key #>: "<#= it.value #>"
-`.trimRight());
-
 // tslint:disable:max-line-length
 const renderPropTyping: (model: IRenderedPropTyping) => string = createTempate(`
   <#= it.propName #>: <#? it.renderedTypes.length === 1 #><#= it.renderedTypes[0] #><#??#>PropTypes.oneOfType([
@@ -168,6 +208,43 @@ const renderPropTyping: (model: IRenderedPropTyping) => string = createTempate(`
 `.trimRight());
 // tslint:enable:max-line-length
 
+const renderObjectEntry: (model: {
+    key: string;
+    value: string;
+}) => string = createTempate(`
+    <#= it.key #>: "<#= it.value #>"
+`.trimRight());
+
+function renderObject(props: IOption[], indent: number): string {
+    let result = "{";
+
+    indent += 1;
+
+    props.forEach((opt) => {
+        result += "\n" + getIndent(indent) + opt.name + "?: ";
+        if (isNotEmptyArray(opt.nested)) {
+            result += renderObject(opt.nested, indent);
+        } else {
+            result += opt.type;
+        }
+        result += ";";
+    });
+
+    indent -= 1;
+    result +=  "\n" + getIndent(indent) + "}";
+    return result;
+}
+
+function getIndent(indent: number) {
+    return Array(indent * 2 + 1).join(" ");
+}
+
+function renderExports(exportsNames: string[]) {
+    return exportsNames
+        .map((exportName) => getIndent(1) + exportName)
+        .join(",\n");
+}
+
 export default generate;
 export {
     IComponent,
diff --git a/tools/src/generator.ts b/tools/src/generator.ts
index 40204e4..a234aa8 100644
--- a/tools/src/generator.ts
+++ b/tools/src/generator.ts
@@ -3,12 +3,13 @@ import { dirname as getDirName, join as joinPaths, relative as getRelativePath,
 import { IOption as IRawOption, ITypeDescriptor, IWidget } from "../integration-data-model";
 import generateComponent, { IComponent, IOption, IPropTyping } from "./component-generator";
 import { convertTypes } from "./converter";
-import { removeElement, removeExtension, removePrefix, toKebabCase } from "./helpers";
+import { isNotEmptyArray, removeElement, removeExtension, removePrefix, toKebabCase } from "./helpers";
 import generateIndex from "./index-generator";
 
 function generate(
   rawData: IWidget[],
   baseComponent: string,
+  configComponent: string,
   out: {
     componentsDir: string,
     indexFileName: string
@@ -17,7 +18,7 @@ function generate(
   const modulePaths: string[] = [];
 
   rawData.forEach((data) => {
-    const widgetFile = mapWidget(data, baseComponent);
+    const widgetFile = mapWidget(data, baseComponent, configComponent);
     const widgetFilePath = joinPaths(out.componentsDir, widgetFile.fileName);
     const indexFileDir = getDirName(out.indexFileName);
 
@@ -30,32 +31,49 @@ function generate(
   writeFile(out.indexFileName, generateIndex(modulePaths), { encoding: "utf8" });
 }
 
-function mapWidget(raw: IWidget, baseComponent: string): {
+function mapWidget(raw: IWidget, baseComponent: string, configComponent: string): {
   fileName: string;
   component: IComponent
 } {
   const name = removePrefix(raw.name, "dx");
   const subscribableOptions: IOption[] = raw.options
     .filter((o) => o.isSubscribable)
-    .map((o) => ({
-      name: o.name,
-      type: "any"
-    }));
+    .map(mapOption);
+
+  const nestedOptions: IOption[] = raw.options
+    .filter((o) => o.isSubscribable && isNotEmptyArray(o.options))
+    .map(mapOption);
 
   return {
     fileName: `${toKebabCase(name)}.ts`,
     component: {
       name,
       baseComponentPath: baseComponent,
+      configComponentPath: configComponent,
       dxExportPath: raw.exportPath,
       templates: raw.templates,
-      subscribableOptions:  subscribableOptions.length > 0 ? subscribableOptions : null,
+      subscribableOptions: subscribableOptions.length > 0 ? subscribableOptions : null,
+      nestedOptions: nestedOptions.length > 0 ? nestedOptions : null,
       propTypings: extractPropTypings(raw.options)
     }
   };
 }
 
-function extractPropTypings(options: IRawOption[]): IPropTyping[]  {
+function mapOption(opt: IRawOption): IOption {
+  const result: IOption = {
+    isCollectionItem: false,
+    name: opt.name,
+    type: "any"
+  };
+
+  if (isNotEmptyArray(opt.options)) {
+    result.nested = opt.options.map(mapOption);
+  }
+
+  return result;
+}
+
+function extractPropTypings(options: IRawOption[]): IPropTyping[] {
   return options
     .map(createPropTyping)
     .filter((t) => t != null);
diff --git a/tools/src/helpers.ts b/tools/src/helpers.ts
index a074de7..620dd3f 100644
--- a/tools/src/helpers.ts
+++ b/tools/src/helpers.ts
@@ -35,3 +35,11 @@ export function removeElement<T>(array: T[], element: T) {
         array.splice(index, 1);
     }
 }
+
+export function isNotEmptyArray(array: any[]): boolean {
+    return !isEmptyArray(array);
+}
+
+export function isEmptyArray(array: any[]): boolean {
+    return array === undefined || array === null || array.length === 0;
+}


Test patch:
diff --git a/src/core/component.test.tsx b/src/core/component.test.tsx
index 9e0038a..75d2357 100644
--- a/src/core/component.test.tsx
+++ b/src/core/component.test.tsx
@@ -3,6 +3,7 @@ import { configure as configureEnzyme, mount, shallow } from "enzyme";
 import * as Adapter from "enzyme-adapter-react-16";
 import * as React from "react";
 import Component from "../core/component";
+import ConfigurationComponent from "../core/configuration-component";
 import { Template } from "../core/template";
 
 const eventHandlers: { [index: string]: (e?: any) => void}  = {};
@@ -20,6 +21,8 @@ const WidgetClass = jest.fn(() => Widget);
 
 class TestComponent<P = any> extends Component<P> {
 
+    public _nestedOptionIdPrefix: string = "testComponent";
+
     constructor(props: P) {
         super(props);
 
@@ -272,14 +275,14 @@ describe("templates", () => {
     // tslint:disable-next-line:max-classes-per-file
     class ComponentWithTemplates extends TestComponent {
 
+        protected _templateProps = [{
+            tmplOption: "item",
+            render: "itemRender",
+            component: "itemComponent"
+        }];
+
         constructor(props: any) {
             super(props);
-
-            this._templateProps = [{
-                tmplOption: "item",
-                render: "itemRender",
-                component: "itemComponent"
-            }];
         }
     }
 });
@@ -372,41 +375,95 @@ describe("controlled mode", () => {
             <ControlledComponent everyOption={123} />
         );
 
-        eventHandlers.optionChanged({ name: "everyOption", value: 234});
+        eventHandlers.optionChanged({ fullName: "everyOption", value: 234});
         jest.runAllTimers();
         expect(Widget.option.mock.calls.length).toBe(1);
         expect(Widget.option.mock.calls[0]).toEqual([ "everyOption", 123 ]);
     });
 
+    it("rolls nested option value back", () => {
+        mount(
+            <ControlledComponent>
+                <NestedComponent a={123} />
+            </ControlledComponent>
+        );
+
+        eventHandlers.optionChanged({ fullName: "nestedOption.a", value: 234});
+        jest.runAllTimers();
+        expect(Widget.option.mock.calls.length).toBe(1);
+        expect(Widget.option.mock.calls[0]).toEqual([ "nestedOption.a", 123 ]);
+    });
+
     it("rolls option value back if value has no changes", () => {
         const component = mount(
             <ControlledComponent everyOption={123} anotherOption={"const"}/>
         );
 
-        eventHandlers.optionChanged({ name: "anotherOption", value: "changed"});
+        eventHandlers.optionChanged({ fullName: "anotherOption", value: "changed"});
         component.setProps({everyOption: 234});
         jest.runAllTimers();
         expect(Widget.option.mock.calls.length).toBe(2);
+        expect(Widget.option.mock.calls[0]).toEqual([ "everyOption", 234 ]);
         expect(Widget.option.mock.calls[1]).toEqual([ "anotherOption", "const" ]);
     });
 
-    it("apply option change if option really change", () => {
+    it("rolls nested option value back if value has no changes", () => {
+        const component = shallow(
+            <ControlledComponent>
+                <NestedComponent a={123} b="const" />
+            </ControlledComponent>
+        );
+        const nested = component.find(NestedComponent).dive();
+
+        eventHandlers.optionChanged({ fullName: "nestedOption.b", value: "changed"});
+        nested.setProps({a: 234});
+        jest.runAllTimers();
+        expect(Widget.option.mock.calls.length).toBe(2);
+        expect(Widget.option.mock.calls[0]).toEqual([ "nestedOption.a", 234 ]);
+        expect(Widget.option.mock.calls[1]).toEqual([ "nestedOption.b", "const" ]);
+    });
+
+    it("apply option change if value really change", () => {
         const component = mount(
             <ControlledComponent everyOption={123}/>
         );
 
-        eventHandlers.optionChanged({ name: "everyOption", value: 234});
+        eventHandlers.optionChanged({ fullName: "everyOption", value: 234});
         component.setProps({everyOption: 234});
         jest.runAllTimers();
         expect(Widget.option.mock.calls.length).toBe(1);
         expect(Widget.option.mock.calls[0]).toEqual([ "everyOption", 234 ]);
     });
 
+    it("apply nested option change if value really change", () => {
+        const component = shallow(
+            <ControlledComponent>
+                <NestedComponent a={123} b="const" />
+            </ControlledComponent>
+        );
+        const nested = component.find(NestedComponent).dive();
+
+        eventHandlers.optionChanged({ fullName: "nestedOption.a", value: 234});
+        nested.setProps({a: 234});
+        jest.runAllTimers();
+        expect(Widget.option.mock.calls.length).toBe(1);
+        expect(Widget.option.mock.calls[0]).toEqual([ "nestedOption.a", 234 ]);
+    });
+
     it("pass default values to widget", () => {
         mount(
             <ControlledComponent defaultControlledOption={"default"}/>
         );
 
+        expect(WidgetClass.mock.calls[0][1].controlledOption).toBe("default");
+        expect(WidgetClass.mock.calls[0][1]).not.toHaveProperty("defaultControlledOption");
+    });
+
+    it("pass nested default values to widget", () => {
+        mount(
+            <ControlledComponent defaultControlledOption={"default"}/>
+        );
+
         expect(WidgetClass.mock.calls[0][1].controlledOption).toBe("default");
     });
 
@@ -414,12 +471,12 @@ describe("controlled mode", () => {
         mount(
             <ControlledComponent defaultControlledOption={"default"}/>
         );
-        eventHandlers.optionChanged({ name: "controlledOption", value: "changed"});
+        eventHandlers.optionChanged({ fullName: "controlledOption", value: "changed"});
         jest.runAllTimers();
         expect(Widget.option.mock.calls.length).toBe(0);
     });
 
-    it("ignores changes in default props", () => {
+    it("ignores 3rd-party changes in default props", () => {
         const component = mount(
             <ControlledComponent defaultControlledOption={"default"}/>
         );
@@ -470,13 +527,22 @@ describe("controlled mode", () => {
         anotherOption?: string;
     }
 
-    // tslint:disable-next-line:max-classes-per-file
     class ControlledComponent extends TestComponent<IControlledComponentProps> {
 
         protected _defaults = { // tslint:disable-line:variable-name
             defaultControlledOption : "controlledOption"
         };
-    }
+    } // tslint:disable-line:max-classes-per-file
+
+    class NestedComponent extends ConfigurationComponent<{
+        a?: number;
+        b?: string;
+        c?: string;
+        defaultC?: string;
+    }> {
+        public static OwnerType = ControlledComponent;
+        public static OptionName = "nestedOption";
+    } // tslint:disable-line:max-classes-per-file
 });
 
 describe("disposing", () => {
@@ -504,6 +570,146 @@ describe("disposing", () => {
     });
 });
 
+describe("nested objects", () => {
+
+    it("pulls options from a single nested component", () => {
+        mount(
+            <TestComponent>
+                <NestedComponent1 a={123} />
+            </TestComponent>
+        );
+
+        expect(WidgetClass.mock.calls[0][1]).toEqual({
+            templatesRenderAsynchronously: true,
+            option1: {
+                a: 123
+            }
+        });
+    });
+
+    it("doesn't pull options from wrong component", () => {
+        mount(
+            <TestComponent>
+                <NestedComponent1 a={123} />
+                <WrongNestedComponent x={456} />
+            </TestComponent>
+        );
+
+        expect(WidgetClass.mock.calls[0][1]).toEqual({
+            templatesRenderAsynchronously: true,
+            option1: {
+                a: 123
+            }
+        });
+    });
+
+    it("pulls overriden options from the same nested component", () => {
+        mount(
+            <TestComponent>
+                <NestedComponent1 a={123} />
+                <NestedComponent1 a={456} />
+            </TestComponent>
+        );
+
+        expect(WidgetClass.mock.calls[0][1]).toEqual({
+            templatesRenderAsynchronously: true,
+            option1: {
+                a: 456
+            }
+        });
+    });
+
+    it("pulls array options from a nested component", () => {
+        mount(
+            <TestComponent>
+                <CollectionNestedComponent c={123} d="abc" />
+            </TestComponent>
+        );
+
+        expect(WidgetClass.mock.calls[0][1]).toEqual({
+            templatesRenderAsynchronously: true,
+            itemOptions: [
+                { c: 123, d: "abc" }
+            ]
+        });
+    });
+
+    it("pulls array options from several nested components", () => {
+        mount(
+            <TestComponent>
+                <CollectionNestedComponent c={123} d="abc" />
+                <CollectionNestedComponent c={456} />
+                <CollectionNestedComponent d="def" />
+            </TestComponent>
+        );
+
+        expect(WidgetClass.mock.calls[0][1]).toEqual({
+            templatesRenderAsynchronously: true,
+            itemOptions: [
+                { c: 123, d: "abc" },
+                { c: 456 },
+                { d: "def" }
+            ]
+        });
+    });
+
+    it("pulls options from several nested components", () => {
+        mount(
+            <TestComponent>
+                <NestedComponent1 a={123} />
+                <WrongNestedComponent x={456} />
+                <NestedComponent2 b="abc" />
+            </TestComponent>
+        );
+
+        expect(WidgetClass.mock.calls[0][1]).toEqual({
+            templatesRenderAsynchronously: true,
+            option1: {
+                a: 123
+            },
+            option2: {
+                b: "abc"
+            }
+        });
+    });
+
+    it("pulls updated options", () => {
+
+        const component = shallow(
+            <TestComponent>
+                <NestedComponent1 a={123} />
+            </TestComponent>
+        );
+        const nested = component.find(NestedComponent1).dive();
+
+        nested.setProps({ a: 456 });
+        jest.runAllTimers();
+        expect(Widget.option.mock.calls.length).toBe(1);
+        expect(Widget.option.mock.calls[0]).toEqual(["option1.a", 456]);
+    });
+
+    class NestedComponent1 extends ConfigurationComponent<{ a: number }> {
+        public static OwnerType = TestComponent;
+        public static OptionName = "option1";
+    } // tslint:disable-line:max-classes-per-file
+
+    class NestedComponent2 extends ConfigurationComponent<{ b: string }> {
+        public static OwnerType = TestComponent;
+        public static OptionName = "option2";
+    } // tslint:disable-line:max-classes-per-file
+
+    class CollectionNestedComponent extends ConfigurationComponent<{ c?: number, d?: string }> {
+        public static IsCollectionItem = true;
+        public static OwnerType = TestComponent;
+        public static OptionName = "itemOptions";
+    } // tslint:disable-line:max-classes-per-file
+
+    class WrongNestedComponent extends ConfigurationComponent<{ x: number }> {
+        public static OwnerType = WrongNestedComponent;
+        public static OptionName = "optionW";
+    } // tslint:disable-line:max-classes-per-file
+});
+
 it("calls option method on props update", () => {
     const component = mount(
         <TestComponent />
diff --git a/tools/src/component-generator.test.ts b/tools/src/component-generator.test.ts
index 45d01a7..8e31efc 100644
--- a/tools/src/component-generator.test.ts
+++ b/tools/src/component-generator.test.ts
@@ -14,7 +14,10 @@ class CLASS_NAME extends BaseComponent<ICLASS_NAMEOptions> {
 
   protected _WidgetClass = dxCLASS_NAME;
 }
-export { CLASS_NAME, ICLASS_NAMEOptions };
+export {
+  CLASS_NAME,
+  ICLASS_NAMEOptions
+};
 `.trimLeft();
     //#endregion
 
@@ -22,6 +25,7 @@ export { CLASS_NAME, ICLASS_NAMEOptions };
         generate({
             name: "CLASS_NAME",
             baseComponentPath: "BASE_COMPONENT_PATH",
+            configComponentPath: null,
             dxExportPath: "DX/WIDGET/PATH"
         })
     ).toBe(EXPECTED);
@@ -54,7 +58,10 @@ class CLASS_NAME extends BaseComponent<ICLASS_NAMEOptions> {
     component: "optionComponent"
   }];
 }
-export { CLASS_NAME, ICLASS_NAMEOptions };
+export {
+  CLASS_NAME,
+  ICLASS_NAMEOptions
+};
 `.trimLeft();
         //#endregion
 
@@ -62,6 +69,7 @@ export { CLASS_NAME, ICLASS_NAMEOptions };
             generate({
                 name: "CLASS_NAME",
                 baseComponentPath: "BASE_COMPONENT_PATH",
+                configComponentPath: null,
                 dxExportPath: "DX/WIDGET/PATH",
                 templates: ["optionTemplate"]
             })
@@ -99,7 +107,10 @@ class CLASS_NAME extends BaseComponent<ICLASS_NAMEOptions> {
     component: "anotherOptionComponent"
   }];
 }
-export { CLASS_NAME, ICLASS_NAMEOptions };
+export {
+  CLASS_NAME,
+  ICLASS_NAMEOptions
+};
 `.trimLeft();
         //#endregion
 
@@ -107,6 +118,7 @@ export { CLASS_NAME, ICLASS_NAMEOptions };
             generate({
                 name: "CLASS_NAME",
                 baseComponentPath: "BASE_COMPONENT_PATH",
+                configComponentPath: null,
                 dxExportPath: "DX/WIDGET/PATH",
                 templates: ["optionTemplate", "anotherOptionTemplate"]
             })
@@ -138,7 +150,10 @@ class CLASS_NAME extends BaseComponent<ICLASS_NAMEOptions> {
     component: "component"
   }];
 }
-export { CLASS_NAME, ICLASS_NAMEOptions };
+export {
+  CLASS_NAME,
+  ICLASS_NAMEOptions
+};
 `.trimLeft();
         //#endregion
 
@@ -146,6 +161,7 @@ export { CLASS_NAME, ICLASS_NAMEOptions };
             generate({
                 name: "CLASS_NAME",
                 baseComponentPath: "BASE_COMPONENT_PATH",
+                configComponentPath: null,
                 dxExportPath: "DX/WIDGET/PATH",
                 templates: ["template"]
             })
@@ -177,7 +193,10 @@ class CLASS_NAME extends BaseComponent<ICLASS_NAMEOptions> {
     defaultOption1: "option1"
   };
 }
-export { CLASS_NAME, ICLASS_NAMEOptions };
+export {
+  CLASS_NAME,
+  ICLASS_NAMEOptions
+};
 `.trimLeft();
         //#endregion
 
@@ -185,6 +204,7 @@ export { CLASS_NAME, ICLASS_NAMEOptions };
             generate({
                 name: "CLASS_NAME",
                 baseComponentPath: "BASE_COMPONENT_PATH",
+                configComponentPath: null,
                 dxExportPath: "DX/WIDGET/PATH",
                 subscribableOptions: [
                     { name: "option1", type: "someType" }
@@ -217,7 +237,10 @@ class CLASS_NAME extends BaseComponent<ICLASS_NAMEOptions> {
     defaultOption2: "option2"
   };
 }
-export { CLASS_NAME, ICLASS_NAMEOptions };
+export {
+  CLASS_NAME,
+  ICLASS_NAMEOptions
+};
 `.trimLeft();
         //#endregion
 
@@ -225,6 +248,7 @@ export { CLASS_NAME, ICLASS_NAMEOptions };
             generate({
                 name: "CLASS_NAME",
                 baseComponentPath: "BASE_COMPONENT_PATH",
+                configComponentPath: null,
                 dxExportPath: "DX/WIDGET/PATH",
                 subscribableOptions: [
                     { name: "option1", type: "someType" },
@@ -234,6 +258,138 @@ export { CLASS_NAME, ICLASS_NAMEOptions };
         ).toBe(EXPECTED);
     });
 
+    it("processes nested options", () => {
+        //#region EXPECTED
+        const EXPECTED = `
+import dxCLASS_NAME, { IOptions as ICLASS_NAMEOptions } from "devextreme/DX/WIDGET/PATH";
+import BaseComponent from "BASE_COMPONENT_PATH";
+import ConfigurationComponent from "CONFIG_COMPONENT_PATH";
+
+class CLASS_NAME extends BaseComponent<ICLASS_NAMEOptions> {
+
+  public get instance(): dxCLASS_NAME {
+    return this._instance;
+  }
+
+  protected _WidgetClass = dxCLASS_NAME;
+}
+// tslint:disable:max-classes-per-file
+
+class CLASS_NAMEOpt_1 extends ConfigurationComponent<{
+  sub_opt_2?: TYPE_1;
+  sub_opt_3?: {
+    sub_sub_opt_4?: TYPE_2;
+    sub_sub_opt_5?: {
+      sub_sub_sub_opt_6?: TYPE_3;
+    };
+  };
+}> {
+  public static OwnerType = CLASS_NAME;
+  public static OptionName = "opt_1";
+}
+
+export {
+  CLASS_NAME,
+  ICLASS_NAMEOptions,
+  CLASS_NAMEOpt_1
+};
+`.trimLeft();
+        //#endregion
+
+        expect(
+            generate({
+                name: "CLASS_NAME",
+                baseComponentPath: "BASE_COMPONENT_PATH",
+                configComponentPath: "CONFIG_COMPONENT_PATH",
+                dxExportPath: "DX/WIDGET/PATH",
+                nestedOptions: [
+                    {
+                        name: "opt_1",
+                        nested: [
+                            {
+                                name: "sub_opt_2",
+                                type: "TYPE_1"
+                            },
+                            {
+                                name: "sub_opt_3",
+                                nested: [
+                                    {
+                                        name: "sub_sub_opt_4",
+                                        type: "TYPE_2"
+                                    },
+                                    {
+                                        name: "sub_sub_opt_5",
+                                        nested: [
+                                            {
+                                                name: "sub_sub_sub_opt_6",
+                                                type: "TYPE_3"
+                                            }
+                                        ]
+                                    }
+                                ]
+                            }
+                        ]
+                    }
+                ]
+            })
+        ).toBe(EXPECTED);
+    });
+
+    it("processes nested array options", () => {
+        //#region EXPECTED
+        const EXPECTED = `
+import dxCLASS_NAME, { IOptions as ICLASS_NAMEOptions } from "devextreme/DX/WIDGET/PATH";
+import BaseComponent from "BASE_COMPONENT_PATH";
+import ConfigurationComponent from "CONFIG_COMPONENT_PATH";
+
+class CLASS_NAME extends BaseComponent<ICLASS_NAMEOptions> {
+
+  public get instance(): dxCLASS_NAME {
+    return this._instance;
+  }
+
+  protected _WidgetClass = dxCLASS_NAME;
+}
+// tslint:disable:max-classes-per-file
+
+class CLASS_NAMEOpt_1 extends ConfigurationComponent<{
+  sub_opt_2?: TYPE_1;
+}> {
+  public static IsCollectionItem = true;
+  public static OwnerType = CLASS_NAME;
+  public static OptionName = "opt_1";
+}
+
+export {
+  CLASS_NAME,
+  ICLASS_NAMEOptions,
+  CLASS_NAMEOpt_1
+};
+`.trimLeft();
+        //#endregion
+
+        expect(
+            generate({
+                name: "CLASS_NAME",
+                baseComponentPath: "BASE_COMPONENT_PATH",
+                configComponentPath: "CONFIG_COMPONENT_PATH",
+                dxExportPath: "DX/WIDGET/PATH",
+                nestedOptions: [
+                    {
+                        name: "opt_1",
+                        nested: [
+                            {
+                                name: "sub_opt_2",
+                                type: "TYPE_1"
+                            }
+                        ],
+                        isCollectionItem: true
+                    }
+                ]
+            })
+        ).toBe(EXPECTED);
+    });
+
     it("adds check for single type", () => {
         //#region EXPECTED
         const EXPECTED = `
@@ -252,7 +408,10 @@ class CLASS_NAME extends BaseComponent<ICLASS_NAMEOptions> {
 (CLASS_NAME as any).propTypes = {
   PROP1: PropTypes.SOME_TYPE
 };
-export { CLASS_NAME, ICLASS_NAMEOptions };
+export {
+  CLASS_NAME,
+  ICLASS_NAMEOptions
+};
 `.trimLeft();
         //#endregion
 
@@ -260,6 +419,7 @@ export { CLASS_NAME, ICLASS_NAMEOptions };
             generate({
                 name: "CLASS_NAME",
                 baseComponentPath: "BASE_COMPONENT_PATH",
+                configComponentPath: null,
                 dxExportPath: "DX/WIDGET/PATH",
                 propTypings: [
                     {
@@ -289,7 +449,10 @@ class CLASS_NAME extends BaseComponent<ICLASS_NAMEOptions> {
 (CLASS_NAME as any).propTypes = {
   PROP1: PropTypes.oneOf(["VALUE_1", "VALUE_2"])
 };
-export { CLASS_NAME, ICLASS_NAMEOptions };
+export {
+  CLASS_NAME,
+  ICLASS_NAMEOptions
+};
 `.trimLeft();
         //#endregion
 
@@ -297,6 +460,7 @@ export { CLASS_NAME, ICLASS_NAMEOptions };
             generate({
                 name: "CLASS_NAME",
                 baseComponentPath: "BASE_COMPONENT_PATH",
+                configComponentPath: null,
                 dxExportPath: "DX/WIDGET/PATH",
                 propTypings: [
                     {
@@ -330,7 +494,10 @@ class CLASS_NAME extends BaseComponent<ICLASS_NAMEOptions> {
     PropTypes.ANOTHER_TYPE
   ])
 };
-export { CLASS_NAME, ICLASS_NAMEOptions };
+export {
+  CLASS_NAME,
+  ICLASS_NAMEOptions
+};
 `.trimLeft();
         //#endregion
 
@@ -338,6 +505,7 @@ export { CLASS_NAME, ICLASS_NAMEOptions };
             generate({
                 name: "CLASS_NAME",
                 baseComponentPath: "BASE_COMPONENT_PATH",
+                configComponentPath: null,
                 dxExportPath: "DX/WIDGET/PATH",
                 propTypings: [
                     {
@@ -373,7 +541,10 @@ class CLASS_NAME extends BaseComponent<ICLASS_NAMEOptions> {
   B-PROP: PropTypes.TYPE_4,
   c-PROP: PropTypes.TYPE_2
 };
-export { CLASS_NAME, ICLASS_NAMEOptions };
+export {
+  CLASS_NAME,
+  ICLASS_NAMEOptions
+};
 `.trimLeft();
         //#endregion
 
@@ -381,6 +552,7 @@ export { CLASS_NAME, ICLASS_NAMEOptions };
             generate({
                 name: "CLASS_NAME",
                 baseComponentPath: "BASE_COMPONENT_PATH",
+                configComponentPath: null,
                 dxExportPath: "DX/WIDGET/PATH",
                 propTypings: [
                     {