+ { props.status && ICONS[props.status]}
+ {/* @ts-expect-error */}
+ { Template && }
+ { !Template && props.column.caption }
+ {
+ props.column.sortOrder !== undefined && (
+
+ )
+ }
+
+ );
+}
diff --git a/packages/devextreme/js/__internal/grids/new/card_view/header_panel/options.test.ts b/packages/devextreme/js/__internal/grids/new/card_view/header_panel/options.test.ts
new file mode 100644
index 000000000000..671021173c3c
--- /dev/null
+++ b/packages/devextreme/js/__internal/grids/new/card_view/header_panel/options.test.ts
@@ -0,0 +1,150 @@
+/* eslint-disable spellcheck/spell-checker */
+import {
+ describe, expect, it, jest,
+} from '@jest/globals';
+import { rerender } from 'inferno';
+
+import { ColumnsController } from '../../grid_core/columns_controller';
+import { DataController } from '../../grid_core/data_controller';
+import { Sortable } from '../../grid_core/inferno_wrappers/sortable';
+import type { Options } from '../options';
+import { OptionsControllerMock } from '../options_controller.mock';
+import { HeaderPanelView } from './view';
+
+const setup = (options: Options) => {
+ const rootElement = document.createElement('div');
+ rootElement.classList.add('test-container');
+
+ const optionsController = new OptionsControllerMock(options);
+ const dataController = new DataController(optionsController);
+ const columnsController = new ColumnsController(optionsController);
+ const headerPanelView = new HeaderPanelView(columnsController, optionsController);
+
+ headerPanelView.render(rootElement);
+ rerender();
+
+ return {
+ optionsController,
+ dataController,
+ columnsController,
+ headerPanelView,
+ rootElement,
+ };
+};
+
+describe('Options', () => {
+ describe('headerPanel', () => {
+ describe('dragging', () => {
+ it('should pass options to inner Sortable', () => {
+ const renderSpy = jest.spyOn(Sortable.prototype, 'render');
+
+ setup({
+ columns: ['column1'],
+ allowColumnReordering: true,
+ headerPanel: {
+ dragging: {
+ dropFeedbackMode: 'push',
+ scrollSpeed: 555,
+ scrollSensitivity: 111,
+ },
+ },
+ });
+
+ // @ts-expect-error
+ expect(renderSpy.mock.calls[0][0]).toMatchObject({
+ dropFeedbackMode: 'push',
+ scrollSpeed: 555,
+ scrollSensitivity: 111,
+ });
+ });
+ });
+
+ describe('visible', () => {
+ describe('when it is false', () => {
+ it('should hide headerPanel', () => {
+ const { rootElement } = setup({
+ columns: ['column1'],
+ headerPanel: {
+ visible: false,
+ },
+ });
+
+ expect(rootElement).toMatchSnapshot();
+ });
+ });
+ describe('when it is true', () => {
+ it('should show headerPanel', () => {
+ const { rootElement } = setup({
+ columns: ['column1'],
+ headerPanel: {
+ visible: true,
+ },
+ });
+
+ expect(rootElement).toMatchSnapshot();
+ });
+ });
+ });
+
+ describe('itemTemplate', () => {
+ // TODO: fix option controller to enable test
+ it.skip('should override content of headerPanel item', () => {
+ const { rootElement } = setup({
+ columns: ['column1'],
+ headerPanel: {
+ // @ts-expect-error
+ itemTemplate: ({ column }) => $('')
+ .addClass('my-class')
+ .text(column.caption),
+ },
+ });
+
+ expect(rootElement).toMatchSnapshot();
+ });
+ });
+
+ describe('itemCssClass', () => {
+ it('should add css class to headerPanel item', () => {
+ const { rootElement } = setup({
+ columns: ['column1'],
+ headerPanel: {
+ itemCssClass: 'my-class',
+ },
+ });
+
+ expect(rootElement.querySelector('.dx-scrollable')).toMatchSnapshot();
+ });
+ });
+ });
+});
+
+// TODO: update after related column props are extracted from columns_controller
+describe('ColumnProperties', () => {
+ describe('headerItemTemplate', () => {
+ it.skip('should override content of headerPanel item', () => {
+ const { rootElement } = setup({
+ columns: [{
+ dataField: 'column1',
+ // @ts-expect-error
+ headerItemTemplate: ({ column }) => $('
')
+ .addClass('my-class')
+ .text(column.caption),
+ }],
+ });
+
+ expect(rootElement).toMatchSnapshot();
+ });
+ });
+ describe('headerItemCssClass', () => {
+ it('should override content of headerPanel item', () => {
+ const { rootElement } = setup({
+ columns: [{
+ dataField: 'column1',
+ headerItemCssClass: 'my-css-class',
+ }],
+ });
+
+ expect(rootElement).toMatchSnapshot();
+ });
+ });
+});
diff --git a/packages/devextreme/js/__internal/grids/new/card_view/header_panel/options.ts b/packages/devextreme/js/__internal/grids/new/card_view/header_panel/options.ts
new file mode 100644
index 000000000000..7c1d8bb1e557
--- /dev/null
+++ b/packages/devextreme/js/__internal/grids/new/card_view/header_panel/options.ts
@@ -0,0 +1,23 @@
+/* eslint-disable @typescript-eslint/ban-types */
+import type * as Sortable from '@js/ui/sortable';
+
+import type { Column } from '../../grid_core/columns_controller/types';
+import type { Template } from '../../grid_core/types';
+
+type SortableProperties = 'dropFeedbackMode' | 'scrollSpeed' | 'scrollSensitivity' | 'onDragChange' | 'onDragEnd' | 'onDragMove' | 'onDragStart' | 'onRemove' | 'onReorder';
+
+export type DraggingOptions = Pick
;
+export interface Options {
+ headerPanel?: {
+ dragging?: DraggingOptions;
+ visible?: boolean;
+ itemTemplate?: Template<{ column: Column }>;
+ itemCssClass?: string;
+ };
+}
+
+export const defaultOptions = {
+ headerPanel: {
+ visible: true,
+ },
+} satisfies Options;
diff --git a/packages/devextreme/js/__internal/grids/new/card_view/header_panel/view.tsx b/packages/devextreme/js/__internal/grids/new/card_view/header_panel/view.tsx
new file mode 100644
index 000000000000..59cc4295cddd
--- /dev/null
+++ b/packages/devextreme/js/__internal/grids/new/card_view/header_panel/view.tsx
@@ -0,0 +1,63 @@
+/* eslint-disable spellcheck/spell-checker */
+import type { SubsGets } from '@ts/core/reactive/index';
+import { combined, computed } from '@ts/core/reactive/index';
+import { ColumnsController } from '@ts/grids/new/grid_core/columns_controller/columns_controller';
+import { View } from '@ts/grids/new/grid_core/core/view';
+
+import type { Column } from '../../grid_core/columns_controller/types';
+import { OptionsController } from '../options_controller';
+import type { HeaderPanelProps } from './header_panel';
+import { HeaderPanel } from './header_panel';
+
+export class HeaderPanelView extends View {
+ // @ts-expect-error
+ protected component = HeaderPanel;
+
+ public static dependencies = [ColumnsController, OptionsController] as const;
+
+ constructor(
+ private readonly columnsController: ColumnsController,
+ private readonly options: OptionsController,
+ ) {
+ super();
+ }
+
+ protected override getProps(): SubsGets {
+ return combined({
+ columns: computed(
+ (columns) => [...columns].sort((a, b) => a.visibleIndex - b.visibleIndex),
+ [this.columnsController.columns],
+ ),
+ onMove: this.onMove.bind(this),
+ onRemove: this.onRemove.bind(this),
+ allowColumnReordering: this.columnsController.allowColumnReordering,
+ showSortIndexes: computed(
+ (columns) => columns
+ .filter(
+ (column) => column.sortOrder !== undefined,
+ )
+ .length > 1,
+ [this.columnsController.columns],
+ ),
+ onSortClick: this.onSortClick.bind(this),
+ itemTemplate: this.options.template('headerPanel.itemTemplate'),
+ itemCssClass: this.options.oneWay('headerPanel.itemCssClass'),
+ visible: this.options.oneWay('headerPanel.visible'),
+ draggingOptions: this.options.oneWay('headerPanel.dragging'),
+ });
+ }
+
+ public onRemove(column: Column): void {
+ this.columnsController.columnOption(column, 'visible', !column.visible);
+ }
+
+ public onMove(column: Column, toIndex: number): void {
+ this.columnsController.columnOption(column, 'visible', true);
+ this.columnsController.columnOption(column, 'visibleIndex', toIndex);
+ }
+
+ public onSortClick(column: Column): void {
+ this.columnsController.columnOption(column, 'sortOrder', 'asc');
+ this.columnsController.columnOption(column, 'sortIndex', 0);
+ }
+}
diff --git a/packages/devextreme/js/__internal/grids/new/card_view/main_view.tsx b/packages/devextreme/js/__internal/grids/new/card_view/main_view.tsx
index 90378facc2a4..5ae741bb5f14 100644
--- a/packages/devextreme/js/__internal/grids/new/card_view/main_view.tsx
+++ b/packages/devextreme/js/__internal/grids/new/card_view/main_view.tsx
@@ -6,21 +6,24 @@ import { PagerView } from '@ts/grids/new/grid_core/pager/view';
import { ToolbarView } from '@ts/grids/new/grid_core/toolbar/view';
import type { ComponentType } from 'inferno';
-// eslint-disable-next-line @typescript-eslint/no-empty-interface
+import { HeaderPanelView } from './header_panel/view';
+
interface MainViewProps {
Toolbar: ComponentType;
Pager: ComponentType;
+ HeaderPanel: ComponentType;
}
function MainViewComponent({
- Toolbar, Pager,
+ Toolbar, Pager, HeaderPanel,
}: MainViewProps): JSX.Element {
return (<>
{/* @ts-expect-error */}
+ {/* @ts-expect-error */}
+
{/*
- TODO:
Pager, as renovated component, has strange disposing.
See `inferno_renderer.remove` method.
It somehow mutates $V prop of parent element.
@@ -37,12 +40,13 @@ export class MainView extends View
{
protected override component = MainViewComponent;
public static dependencies = [
- PagerView, ToolbarView,
+ PagerView, ToolbarView, HeaderPanelView,
] as const;
constructor(
private readonly pager: PagerView,
private readonly toolbar: ToolbarView,
+ private readonly headerPanel: HeaderPanelView,
) {
super();
}
@@ -53,6 +57,7 @@ export class MainView extends View {
return combined({
Toolbar: this.toolbar.asInferno(),
Pager: this.pager.asInferno(),
+ HeaderPanel: this.headerPanel.asInferno(),
});
}
}
diff --git a/packages/devextreme/js/__internal/grids/new/card_view/options.ts b/packages/devextreme/js/__internal/grids/new/card_view/options.ts
index 2448e49792c3..fe542ae6a742 100644
--- a/packages/devextreme/js/__internal/grids/new/card_view/options.ts
+++ b/packages/devextreme/js/__internal/grids/new/card_view/options.ts
@@ -1,11 +1,15 @@
import * as GridCore from '@ts/grids/new/grid_core/options';
+import * as HeaderPanel from './header_panel/index';
+
/**
* @interface
*/
export type Options =
- & GridCore.Options;
+ & GridCore.Options
+ & HeaderPanel.Options;
export const defaultOptions = {
...GridCore.defaultOptions,
+ ...HeaderPanel.defaultOptions,
} satisfies Options;
diff --git a/packages/devextreme/js/__internal/grids/new/card_view/widget.ts b/packages/devextreme/js/__internal/grids/new/card_view/widget.ts
index 420732885d7d..214da725a293 100644
--- a/packages/devextreme/js/__internal/grids/new/card_view/widget.ts
+++ b/packages/devextreme/js/__internal/grids/new/card_view/widget.ts
@@ -7,17 +7,22 @@ import { MainView as MainViewBase } from '@ts/grids/new/grid_core/main_view';
import { OptionsController as OptionsControllerBase } from '@ts/grids/new/grid_core/options_controller/options_controller';
import { GridCoreNew } from '@ts/grids/new/grid_core/widget';
+import { HeaderPanelView } from './header_panel/view';
import { MainView } from './main_view';
import { defaultOptions } from './options';
import { OptionsController } from './options_controller';
export class CardViewBase extends GridCoreNew {
+ headerPanel!: HeaderPanelView;
+
protected _registerDIContext(): void {
super._registerDIContext();
+ this.diContext.register(HeaderPanelView);
this.diContext.register(MainViewBase, MainView);
const optionsController = new OptionsController(this);
this.diContext.registerInstance(OptionsController, optionsController);
+ // @ts-expect-error
this.diContext.registerInstance(OptionsControllerBase, optionsController);
}
@@ -28,6 +33,7 @@ export class CardViewBase extends GridCoreNew {
protected _initDIContext(): void {
super._initDIContext();
+ this.headerPanel = this.diContext.get(HeaderPanelView);
}
// eslint-disable-next-line max-len
diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/columns_controller/__snapshots__/columns_controller.test.ts.snap b/packages/devextreme/js/__internal/grids/new/grid_core/columns_controller/__snapshots__/columns_controller.test.ts.snap
index da54d5acb102..0cc533e42d1f 100644
--- a/packages/devextreme/js/__internal/grids/new/grid_core/columns_controller/__snapshots__/columns_controller.test.ts.snap
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/columns_controller/__snapshots__/columns_controller.test.ts.snap
@@ -11,6 +11,7 @@ exports[`ColumnsController columns should contain processed column configs 1`] =
"dataField": "a",
"dataType": "string",
"falseText": "false",
+ "headerItemTemplate": undefined,
"name": "a",
"trueText": "true",
"visible": true,
@@ -25,6 +26,7 @@ exports[`ColumnsController columns should contain processed column configs 1`] =
"dataField": "b",
"dataType": "string",
"falseText": "false",
+ "headerItemTemplate": undefined,
"name": "b",
"trueText": "true",
"visible": true,
@@ -39,6 +41,7 @@ exports[`ColumnsController columns should contain processed column configs 1`] =
"dataField": "c",
"dataType": "string",
"falseText": "false",
+ "headerItemTemplate": undefined,
"name": "c",
"trueText": "true",
"visible": false,
@@ -60,6 +63,7 @@ exports[`ColumnsController createDataRow should process data object to data row
"dataField": "a",
"dataType": "string",
"falseText": "false",
+ "headerItemTemplate": undefined,
"name": "a",
"trueText": "true",
"visible": true,
@@ -79,6 +83,7 @@ exports[`ColumnsController createDataRow should process data object to data row
"dataField": "b",
"dataType": "string",
"falseText": "false",
+ "headerItemTemplate": undefined,
"name": "b",
"trueText": "true",
"visible": true,
diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/columns_controller/__snapshots__/options.test.ts.snap b/packages/devextreme/js/__internal/grids/new/grid_core/columns_controller/__snapshots__/options.test.ts.snap
index 472e587f2151..246e1703fee6 100644
--- a/packages/devextreme/js/__internal/grids/new/grid_core/columns_controller/__snapshots__/options.test.ts.snap
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/columns_controller/__snapshots__/options.test.ts.snap
@@ -11,6 +11,7 @@ exports[`Options columns when given as object should be normalized 1`] = `
"dataField": "a",
"dataType": "string",
"falseText": "false",
+ "headerItemTemplate": undefined,
"name": "a",
"trueText": "true",
"visible": true,
@@ -25,6 +26,7 @@ exports[`Options columns when given as object should be normalized 1`] = `
"dataField": "b",
"dataType": "string",
"falseText": "false",
+ "headerItemTemplate": undefined,
"name": "b",
"trueText": "true",
"visible": true,
@@ -39,6 +41,7 @@ exports[`Options columns when given as object should be normalized 1`] = `
"dataField": "c",
"dataType": "string",
"falseText": "false",
+ "headerItemTemplate": undefined,
"name": "c",
"trueText": "true",
"visible": true,
@@ -58,6 +61,7 @@ exports[`Options columns when given as string should be normalized 1`] = `
"dataField": "a",
"dataType": "string",
"falseText": "false",
+ "headerItemTemplate": undefined,
"name": "a",
"trueText": "true",
"visible": true,
@@ -72,6 +76,7 @@ exports[`Options columns when given as string should be normalized 1`] = `
"dataField": "b",
"dataType": "string",
"falseText": "false",
+ "headerItemTemplate": undefined,
"name": "b",
"trueText": "true",
"visible": true,
@@ -86,6 +91,7 @@ exports[`Options columns when given as string should be normalized 1`] = `
"dataField": "c",
"dataType": "string",
"falseText": "false",
+ "headerItemTemplate": undefined,
"name": "c",
"trueText": "true",
"visible": true,
diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/columns_controller/columns_controller.mock.ts b/packages/devextreme/js/__internal/grids/new/grid_core/columns_controller/columns_controller.mock.ts
new file mode 100644
index 000000000000..98e95d651d86
--- /dev/null
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/columns_controller/columns_controller.mock.ts
@@ -0,0 +1,11 @@
+import type { ColumnProperties } from './options';
+import type { Column } from './types';
+import { normalizeColumns, preNormalizeColumns } from './utils';
+
+export function normalizeColumn(column: ColumnProperties): Column {
+ return normalizeColumns(
+ preNormalizeColumns([column]),
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
+ (v) => v,
+ )[0];
+}
diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/columns_controller/columns_controller.ts b/packages/devextreme/js/__internal/grids/new/grid_core/columns_controller/columns_controller.ts
index 3b2649a579ca..8e3cb5d82107 100644
--- a/packages/devextreme/js/__internal/grids/new/grid_core/columns_controller/columns_controller.ts
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/columns_controller/columns_controller.ts
@@ -40,7 +40,10 @@ export class ColumnsController {
);
this.columns = computed(
- (columnsSettings) => normalizeColumns(columnsSettings ?? []),
+ (columnsSettings) => normalizeColumns(
+ columnsSettings ?? [],
+ this.options.normalizeTemplate.bind(this.options),
+ ),
[
this.columnsSettings,
],
diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/columns_controller/options.ts b/packages/devextreme/js/__internal/grids/new/grid_core/columns_controller/options.ts
index bcbe80dc058c..fd14576c8b0e 100644
--- a/packages/devextreme/js/__internal/grids/new/grid_core/columns_controller/options.ts
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/columns_controller/options.ts
@@ -2,12 +2,18 @@
import type { DataType } from '@js/common';
import messageLocalization from '@js/localization/message';
-import type { WithRequired } from '../types';
+import type { Template, WithRequired } from '../types';
import type { Column } from './types';
-export type ColumnSettings = Partial & {
+interface NonNormalizedColumnOptions {
calculateDisplayValue: string | ((this: Column, data: unknown) => unknown);
-}>;
+ headerItemTemplate?: Template<{ column: Column }>;
+}
+
+export type ColumnSettings = Partial<
+Omit
+& NonNormalizedColumnOptions
+>;
export type PreNormalizedColumn = WithRequired;
diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/columns_controller/types.ts b/packages/devextreme/js/__internal/grids/new/grid_core/columns_controller/types.ts
index 442885030e18..e409cc48ab8d 100644
--- a/packages/devextreme/js/__internal/grids/new/grid_core/columns_controller/types.ts
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/columns_controller/types.ts
@@ -1,5 +1,6 @@
-import type { Format } from '@js/common';
+import type { Format, SortOrder } from '@js/common';
import type { ColumnBase } from '@js/common/grids';
+import type { ComponentType } from 'inferno';
type InheritedColumnProps =
| 'alignment'
@@ -14,6 +15,9 @@ type InheritedColumnProps =
export type Column = Pick, InheritedColumnProps> & {
dataField?: string;
+ sortOrder?: SortOrder; // todo: move to sorting module
+ sortIndex?: number; // todo: move to sorting module
+
name: string;
calculateCellValue: (this: Column, data: unknown) => unknown;
@@ -30,6 +34,11 @@ export type Column = Pick, InheritedColumnProps> & {
editorTemplate?: unknown;
fieldTemplate?: unknown;
+
+ // TODO: move to cardview/headerpanel
+ headerItemTemplate?: ComponentType<{ column: Column }>;
+
+ headerItemCssClass?: string;
};
export type VisibleColumn = Column & { visible: true };
diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/columns_controller/utils.ts b/packages/devextreme/js/__internal/grids/new/grid_core/columns_controller/utils.ts
index 00b2e980b482..afc2ec9db176 100644
--- a/packages/devextreme/js/__internal/grids/new/grid_core/columns_controller/utils.ts
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/columns_controller/utils.ts
@@ -1,12 +1,19 @@
import { compileGetter } from '@js/core/utils/data';
import { captionize } from '@js/core/utils/inflector';
import { isDefined, isString } from '@js/core/utils/type';
+import type { ComponentType } from 'inferno';
+import type { Template } from '../types';
import type { ColumnProperties, ColumnSettings, PreNormalizedColumn } from './options';
import { defaultColumnProperties, defaultColumnPropertiesByDataType } from './options';
import type { Column } from './types';
-function normalizeColumn(column: PreNormalizedColumn): Column {
+type TemplateNormalizationFunc = (template: Template) => ComponentType;
+
+function normalizeColumn(
+ column: PreNormalizedColumn,
+ templateNormalizationFunc: TemplateNormalizationFunc,
+): Column {
const dataTypeDefault = defaultColumnPropertiesByDataType[
column.dataType ?? defaultColumnProperties.dataType
];
@@ -25,6 +32,9 @@ function normalizeColumn(column: PreNormalizedColumn): Column {
calculateDisplayValue: isString(colWithDefaults.calculateDisplayValue)
? compileGetter(colWithDefaults.calculateDisplayValue) as (data: unknown) => string
: colWithDefaults.calculateDisplayValue,
+ headerItemTemplate: colWithDefaults.headerItemTemplate
+ ? templateNormalizationFunc(colWithDefaults.headerItemTemplate)
+ : undefined,
};
}
@@ -81,8 +91,11 @@ export function normalizeVisibleIndexes(
return returnIndexes;
}
-export function normalizeColumns(columns: PreNormalizedColumn[]): Column[] {
- const normalizedColumns = columns.map((c) => normalizeColumn(c));
+export function normalizeColumns(
+ columns: PreNormalizedColumn[],
+ templateNormalizationFunc: TemplateNormalizationFunc,
+): Column[] {
+ const normalizedColumns = columns.map((c) => normalizeColumn(c, templateNormalizationFunc));
return normalizedColumns;
}
diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/inferno_wrappers/scrollable.tsx b/packages/devextreme/js/__internal/grids/new/grid_core/inferno_wrappers/scrollable.tsx
new file mode 100644
index 000000000000..42ccf8833b2f
--- /dev/null
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/inferno_wrappers/scrollable.tsx
@@ -0,0 +1,54 @@
+/* eslint-disable @typescript-eslint/no-non-null-assertion */
+import type { Properties as ScrollableProperties } from '@js/ui/scroll_view/ui.scrollable';
+import dxScrollable from '@js/ui/scroll_view/ui.scrollable';
+import { createPortal, type InfernoNode } from 'inferno';
+
+import { InfernoWrapper } from './widget_wrapper';
+
+export interface Props extends ScrollableProperties {
+ scrollTop?: number;
+}
+
+export class Scrollable extends InfernoWrapper {
+ private readonly contentRef: { current?: HTMLDivElement } = {};
+
+ public render(): InfernoNode {
+ return (
+ <>
+ {super.render()}
+ {this.contentRef.current && createPortal(
+ this.props.children,
+ this.contentRef.current,
+ )}
+ >
+ );
+ }
+
+ protected getComponentFabric(): typeof dxScrollable {
+ return dxScrollable;
+ }
+
+ private updateScrollTop(): void {
+ this.component?.scrollTo(this.props.scrollTop);
+ }
+
+ public componentDidMount(): void {
+ if (this.props.useNative === undefined) {
+ delete this.props.useNative;
+ }
+ super.componentDidMount();
+ // @ts-expect-error
+ this.contentRef.current = this.component.$content().get(0);
+ this.setState({});
+ this.updateScrollTop();
+ }
+
+ public componentDidUpdate(prevProps: Props): void {
+ super.componentDidUpdate(prevProps);
+ this.updateScrollTop();
+ }
+
+ public clientHeight(): number {
+ return this.component!.clientHeight();
+ }
+}
diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/inferno_wrappers/sortable.tsx b/packages/devextreme/js/__internal/grids/new/grid_core/inferno_wrappers/sortable.tsx
new file mode 100644
index 000000000000..3806d32afa2c
--- /dev/null
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/inferno_wrappers/sortable.tsx
@@ -0,0 +1,23 @@
+import type { Properties as SortableProperties } from '@js/ui/sortable';
+import dxSortable from '@js/ui/sortable';
+import { type InfernoNode } from 'inferno';
+
+import { InfernoWrapper } from './widget_wrapper';
+
+export interface Props extends SortableProperties {
+
+}
+
+export class Sortable extends InfernoWrapper {
+ public render(): InfernoNode {
+ return (
+
+ {this.props.children}
+
+ );
+ }
+
+ protected getComponentFabric(): typeof dxSortable {
+ return dxSortable;
+ }
+}
diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/options_controller/options_controller_base.ts b/packages/devextreme/js/__internal/grids/new/grid_core/options_controller/options_controller_base.ts
index 98a2b5424748..0bf2649f6741 100644
--- a/packages/devextreme/js/__internal/grids/new/grid_core/options_controller/options_controller_base.ts
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/options_controller/options_controller_base.ts
@@ -154,12 +154,17 @@ export class OptionsController {
};
}
+ public normalizeTemplate(template: Template): ComponentType {
+ // @ts-expect-error
+ return TemplateWrapper(this.component._getTemplate(template)) as any;
+ }
+
public template(
name: TProp,
): SubsGets> {
return computed(
// @ts-expect-error
- (template) => template && TemplateWrapper(this.component._getTemplate(template)) as any,
+ (template) => template && this.normalizeTemplate(template) as any,
[this.oneWay(name)],
);
}