diff --git a/packages/devextreme/js/__internal/grids/new/card_view/__snapshots__/widget.test.ts.snap b/packages/devextreme/js/__internal/grids/new/card_view/__snapshots__/widget.test.ts.snap
index d64f709fa54e..a3748ebef495 100644
--- a/packages/devextreme/js/__internal/grids/new/card_view/__snapshots__/widget.test.ts.snap
+++ b/packages/devextreme/js/__internal/grids/new/card_view/__snapshots__/widget.test.ts.snap
@@ -5,6 +5,8 @@ exports[`common initial render should be successfull 1`] = `
class="dx-widget dx-cardview"
>
- This is cardView
+
+
+
`;
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 18a673b2714a..90378facc2a4 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
@@ -2,31 +2,46 @@
/* eslint-disable @typescript-eslint/explicit-member-accessibility */
import { combined } from '@ts/core/reactive/index';
import { View } from '@ts/grids/new/grid_core/core/view';
+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
interface MainViewProps {
Toolbar: ComponentType;
+ Pager: ComponentType;
}
-// eslint-disable-next-line no-empty-pattern
function MainViewComponent({
- Toolbar,
+ Toolbar, Pager,
}: MainViewProps): JSX.Element {
return (<>
{/* @ts-expect-error */}
- This is cardView
+
+ {/*
+ TODO:
+ Pager, as renovated component, has strange disposing.
+ See `inferno_renderer.remove` method.
+ It somehow mutates $V prop of parent element.
+ Without this div, CardView would be parent of Pager.
+ In this case all `componentWillUnmount`s aren't called
+ */}
+ {/* @ts-expect-error */}
+
+
>);
}
export class MainView extends View {
protected override component = MainViewComponent;
- public static dependencies = [ToolbarView] as const;
+ public static dependencies = [
+ PagerView, ToolbarView,
+ ] as const;
constructor(
+ private readonly pager: PagerView,
private readonly toolbar: ToolbarView,
) {
super();
@@ -37,6 +52,7 @@ export class MainView extends View {
protected override getProps() {
return combined({
Toolbar: this.toolbar.asInferno(),
+ Pager: this.pager.asInferno(),
});
}
}
diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/inferno_wrappers/pager.ts b/packages/devextreme/js/__internal/grids/new/grid_core/inferno_wrappers/pager.ts
new file mode 100644
index 000000000000..589e4a33dbf7
--- /dev/null
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/inferno_wrappers/pager.ts
@@ -0,0 +1,10 @@
+import type { Properties as PaginationProperties } from '@js/ui/pagination';
+import dxPagination from '@js/ui/pagination';
+
+import { InfernoWrapper } from './widget_wrapper';
+
+export class Pager extends InfernoWrapper {
+ protected getComponentFabric(): typeof dxPagination {
+ return dxPagination;
+ }
+}
diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/options.ts b/packages/devextreme/js/__internal/grids/new/grid_core/options.ts
index b2d6bfc693a1..569f83aece83 100644
--- a/packages/devextreme/js/__internal/grids/new/grid_core/options.ts
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/options.ts
@@ -4,7 +4,8 @@ import type { WidgetOptions } from '@js/ui/widget/ui.widget';
import * as columnsController from './columns_controller/index';
import * as dataController from './data_controller/index';
-import type * as toolbar from './toolbar';
+import * as pager from './pager/index';
+import type * as toolbar from './toolbar/index';
import type { GridCoreNew } from './widget';
/**
@@ -13,12 +14,14 @@ import type { GridCoreNew } from './widget';
export type Options =
& WidgetOptions
& dataController.Options
+ & pager.Options
& columnsController.Options
& toolbar.Options;
export const defaultOptions = {
...dataController.defaultOptions,
...columnsController.defaultOptions,
+ ...pager.defaultOptions,
} satisfies Options;
// TODO: separate by modules
diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/pager/__snapshots__/view.test.ts.snap b/packages/devextreme/js/__internal/grids/new/grid_core/pager/__snapshots__/view.test.ts.snap
new file mode 100644
index 000000000000..fc8eb1ebb701
--- /dev/null
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/pager/__snapshots__/view.test.ts.snap
@@ -0,0 +1,537 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Applying options when allowedPageSizes = 'auto' calculates pageSizes by pageSize 1`] = `
+
+
+
+`;
+
+exports[`Applying options when allowedPageSizes with custom values displays custom values 1`] = `
+
+
+
+`;
+
+exports[`Applying options when changing a visible to 'false' at runtime Pager should be hidden 1`] = `
+
+
+
+`;
+
+exports[`Applying options when changing a visible to 'true' at runtime Pager should be visible 1`] = `
+
+
+
+`;
+
+exports[`Applying options when changing an allowedPageSizes to custom values at runtime applies custom values 1`] = `
+
+
+
+`;
+
+exports[`Applying options when visible = 'auto' and pageCount <= 1 Pager should be hidden 1`] = `
+
+
+
+`;
+
+exports[`Applying options when visible = 'auto' and pageCount > 1 Pager should be visible 1`] = `
+
+
+
+`;
+
+exports[`Applying options when visible = 'false' Pager should be hidden 1`] = `
+
+
+
+`;
+
+exports[`Applying options when visible = 'true' Pager should be visible 1`] = `
+
+
+
+`;
+
+exports[`render PagerView with options 1`] = `
+
+
+
+`;
+
+exports[`render empty PagerView 1`] = `
+
+
+
+`;
diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/pager/index.ts b/packages/devextreme/js/__internal/grids/new/grid_core/pager/index.ts
new file mode 100644
index 000000000000..38129b24f9e3
--- /dev/null
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/pager/index.ts
@@ -0,0 +1,2 @@
+export { defaultOptions, type Options } from './options';
+export { PagerView as View } from './view';
diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/pager/options.ts b/packages/devextreme/js/__internal/grids/new/grid_core/pager/options.ts
new file mode 100644
index 000000000000..2c6a75b63dd4
--- /dev/null
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/pager/options.ts
@@ -0,0 +1,27 @@
+import type { Mode } from '@js/common';
+import messageLocalization from '@js/localization/message';
+import type { PagerBase } from '@js/ui/pagination';
+
+export type PageSize = number | 'all';
+
+export type PageSizes = PageSize[] | Mode;
+
+export type PagerVisible = boolean | Mode;
+
+export interface PagerOptions extends PagerBase {
+ allowedPageSizes?: PageSizes;
+ visible?: PagerVisible;
+}
+
+export interface Options {
+ pager?: PagerOptions;
+}
+
+export const defaultOptions = {
+ pager: {
+ visible: 'auto',
+ showPageSizeSelector: false,
+ allowedPageSizes: 'auto',
+ label: messageLocalization.format('dxPager-ariaLabel'),
+ },
+} satisfies Options;
diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/pager/pager.tsx b/packages/devextreme/js/__internal/grids/new/grid_core/pager/pager.tsx
new file mode 100644
index 000000000000..9cdda688637d
--- /dev/null
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/pager/pager.tsx
@@ -0,0 +1,12 @@
+import type { PagerBase } from '@js/ui/pagination';
+import type { InfernoNode } from 'inferno';
+
+import { Pager } from '../inferno_wrappers/pager';
+
+export type PagerProps = PagerBase & { visible: boolean };
+
+export function PagerView(props: PagerProps): InfernoNode {
+ return (
+ props.visible &&
+ );
+}
diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/pager/utils.test.ts b/packages/devextreme/js/__internal/grids/new/grid_core/pager/utils.test.ts
new file mode 100644
index 000000000000..4073777fed42
--- /dev/null
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/pager/utils.test.ts
@@ -0,0 +1,49 @@
+import { describe, expect, it } from '@jest/globals';
+
+import { calculatePageSizes, isVisible } from './utils';
+
+describe('calculatePageSizes', () => {
+ describe('when pageSizesConfig = \'auto\'', () => {
+ it('calculates pageSizes by pageSize', () => {
+ expect(calculatePageSizes(undefined, 'auto', 6)).toEqual([3, 6, 12]);
+ });
+ });
+
+ describe('when pageSizesConfig with custom values', () => {
+ it('return custom values', () => {
+ expect(calculatePageSizes(undefined, [4, 10, 20], 6)).toEqual([4, 10, 20]);
+ });
+ });
+
+ describe('when there is an initial value of pageSizes and pageSizesConfig = \'auto\'', () => {
+ it('return initial values', () => {
+ expect(calculatePageSizes([3, 6, 12], 'auto', 12)).toEqual([3, 6, 12]);
+ });
+ });
+});
+
+describe('isVisible', () => {
+ describe('when visibleConfig = true', () => {
+ it('visible should be equal to true', () => {
+ expect(isVisible(true, 1)).toBe(true);
+ });
+ });
+
+ describe('when visibleConfig = false', () => {
+ it('visible should be equal to false', () => {
+ expect(isVisible(false, 2)).toBe(false);
+ });
+ });
+
+ describe('when visibleConfig = \'auto\' and pageCount = 1', () => {
+ it('visible should be equal to false', () => {
+ expect(isVisible('auto', 1)).toBe(false);
+ });
+ });
+
+ describe('when visibleConfig = \'auto\' and pageCount > 1', () => {
+ it('visible should be equal to true', () => {
+ expect(isVisible('auto', 2)).toBe(true);
+ });
+ });
+});
diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/pager/utils.ts b/packages/devextreme/js/__internal/grids/new/grid_core/pager/utils.ts
new file mode 100644
index 000000000000..51c877e18d02
--- /dev/null
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/pager/utils.ts
@@ -0,0 +1,30 @@
+import type { PagerVisible, PageSize, PageSizes } from './options';
+
+// TODO: Need to fix case with runtime changes the allowedPageSizes property to 'auto'
+export function calculatePageSizes(
+ allowedPageSizes: PageSize[] | undefined,
+ pageSizesConfig: PageSizes,
+ pageSize: number,
+): PageSizes {
+ if (Array.isArray(pageSizesConfig)) {
+ return pageSizesConfig;
+ }
+ if (Array.isArray(allowedPageSizes) && allowedPageSizes.includes(pageSize)) {
+ return allowedPageSizes;
+ }
+ if (pageSizesConfig && pageSize > 1) {
+ return [Math.floor(pageSize / 2), pageSize, pageSize * 2];
+ }
+
+ return [];
+}
+
+export function isVisible(
+ visibleConfig: PagerVisible,
+ pageCount: number,
+): boolean {
+ if (visibleConfig === 'auto') {
+ return pageCount > 1;
+ }
+ return visibleConfig;
+}
diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/pager/view.test.ts b/packages/devextreme/js/__internal/grids/new/grid_core/pager/view.test.ts
new file mode 100644
index 000000000000..d4977f2f12c4
--- /dev/null
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/pager/view.test.ts
@@ -0,0 +1,213 @@
+/* eslint-disable spellcheck/spell-checker */
+/* eslint-disable @typescript-eslint/dot-notation */
+import { describe, expect, it } from '@jest/globals';
+
+import { DataController } from '../data_controller/data_controller';
+import type { Options } from '../options';
+import { OptionsControllerMock } from '../options_controller/options_controller.mock';
+import { PagerView } from './view';
+
+const createPagerView = (options?: Options) => {
+ const rootElement = document.createElement('div');
+ const optionsController = new OptionsControllerMock(options ?? {
+ dataSource: [],
+ pager: {
+ visible: true,
+ },
+ });
+
+ const dataController = new DataController(optionsController);
+ const pager = new PagerView(dataController, optionsController);
+
+ pager.render(rootElement);
+
+ return {
+ rootElement,
+ optionsController,
+ };
+};
+
+describe('render', () => {
+ it('empty PagerView', () => {
+ const { rootElement } = createPagerView();
+
+ expect(rootElement).toMatchSnapshot();
+ });
+
+ it('PagerView with options', () => {
+ const { rootElement } = createPagerView({
+ dataSource: [...new Array(20)].map((_, index) => ({ field: `test_${index}` })),
+ paging: {
+ pageIndex: 2,
+ },
+ pager: {
+ showPageSizeSelector: true,
+ },
+ });
+
+ expect(rootElement).toMatchSnapshot();
+ });
+});
+
+describe('Applying options', () => {
+ describe('when visible = \'auto\' and pageCount <= 1', () => {
+ it('Pager should be hidden', () => {
+ const { rootElement } = createPagerView({
+ dataSource: [...new Array(4)].map((_, index) => ({ field: `test_${index}` })),
+ paging: {
+ pageIndex: 6,
+ },
+ pager: {
+ visible: 'auto',
+ showPageSizeSelector: true,
+ },
+ });
+
+ expect(rootElement).toMatchSnapshot();
+ });
+ });
+
+ describe('when visible = \'auto\' and pageCount > 1', () => {
+ it('Pager should be visible', () => {
+ const { rootElement } = createPagerView({
+ dataSource: [...new Array(20)].map((_, index) => ({ field: `test_${index}` })),
+ paging: {
+ pageIndex: 6,
+ },
+ pager: {
+ visible: 'auto',
+ showPageSizeSelector: true,
+ },
+ });
+
+ expect(rootElement).toMatchSnapshot();
+ });
+ });
+
+ describe('when visible = \'true\'', () => {
+ it('Pager should be visible', () => {
+ const { rootElement } = createPagerView({
+ dataSource: [...new Array(20)].map((_, index) => ({ field: `test_${index}` })),
+ paging: {
+ pageIndex: 6,
+ },
+ pager: {
+ visible: true,
+ showPageSizeSelector: true,
+ },
+ });
+
+ expect(rootElement).toMatchSnapshot();
+ });
+ });
+
+ describe('when visible = \'false\'', () => {
+ it('Pager should be hidden', () => {
+ const { rootElement } = createPagerView({
+ dataSource: [...new Array(20)].map((_, index) => ({ field: `test_${index}` })),
+ paging: {
+ pageIndex: 6,
+ },
+ pager: {
+ visible: false,
+ showPageSizeSelector: true,
+ },
+ });
+
+ expect(rootElement).toMatchSnapshot();
+ });
+ });
+
+ describe('when changing a visible to \'false\' at runtime', () => {
+ it('Pager should be hidden', () => {
+ const { rootElement, optionsController } = createPagerView({
+ dataSource: [...new Array(4)].map((_, index) => ({ field: `test_${index}` })),
+ paging: {
+ pageIndex: 6,
+ },
+ pager: {
+ visible: true,
+ showPageSizeSelector: true,
+ },
+ });
+
+ optionsController.option('pager.visible', false);
+
+ expect(rootElement).toMatchSnapshot();
+ });
+ });
+
+ describe('when changing a visible to \'true\' at runtime', () => {
+ it('Pager should be visible', () => {
+ const { rootElement, optionsController } = createPagerView({
+ dataSource: [...new Array(4)].map((_, index) => ({ field: `test_${index}` })),
+ paging: {
+ pageIndex: 6,
+ },
+ pager: {
+ visible: false,
+ showPageSizeSelector: true,
+ },
+ });
+
+ optionsController.option('pager.visible', true);
+
+ expect(rootElement).toMatchSnapshot();
+ });
+ });
+
+ describe('when allowedPageSizes = \'auto\'', () => {
+ it('calculates pageSizes by pageSize', () => {
+ const { rootElement } = createPagerView({
+ dataSource: [...new Array(4)].map((_, index) => ({ field: `test_${index}` })),
+ paging: {
+ pageIndex: 6,
+ },
+ pager: {
+ visible: true,
+ allowedPageSizes: 'auto',
+ showPageSizeSelector: true,
+ },
+ });
+
+ expect(rootElement).toMatchSnapshot();
+ });
+ });
+
+ describe('when allowedPageSizes with custom values', () => {
+ it('displays custom values', () => {
+ const { rootElement } = createPagerView({
+ dataSource: [...new Array(20)].map((_, index) => ({ field: `test_${index}` })),
+ paging: {
+ pageIndex: 6,
+ },
+ pager: {
+ visible: 'auto',
+ allowedPageSizes: [4, 10, 20],
+ showPageSizeSelector: true,
+ },
+ });
+
+ expect(rootElement).toMatchSnapshot();
+ });
+ });
+
+ describe('when changing an allowedPageSizes to custom values at runtime', () => {
+ it('applies custom values', () => {
+ const { rootElement, optionsController } = createPagerView({
+ dataSource: [...new Array(20)].map((_, index) => ({ field: `test_${index}` })),
+ paging: {
+ pageIndex: 6,
+ },
+ pager: {
+ allowedPageSizes: 'auto',
+ showPageSizeSelector: true,
+ },
+ });
+
+ optionsController.option('pager.allowedPageSizes', [4, 10, 20]);
+
+ expect(rootElement).toMatchSnapshot();
+ });
+ });
+});
diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/pager/view.tsx b/packages/devextreme/js/__internal/grids/new/grid_core/pager/view.tsx
new file mode 100644
index 000000000000..28f1ec5568da
--- /dev/null
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/pager/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 { View } from '../core/view';
+import { DataController } from '../data_controller/index';
+import { OptionsController } from '../options_controller/options_controller';
+import type { PagerProps } from './pager';
+import { PagerView as Pager } from './pager';
+import { calculatePageSizes, isVisible } from './utils';
+
+export class PagerView extends View {
+ protected override component = Pager;
+
+ public static dependencies = [DataController, OptionsController] as const;
+
+ private readonly pageSizesConfig = this.options.oneWay('pager.allowedPageSizes');
+
+ private readonly allowedPageSizes = computed(
+ (pageSizesConfig, pageSize) => calculatePageSizes(
+ this.allowedPageSizes?.unreactive_get(),
+ pageSizesConfig,
+ pageSize,
+ ),
+ [this.pageSizesConfig, this.dataController.pageSize],
+ );
+
+ private readonly visibleConfig = this.options.oneWay('pager.visible');
+
+ private readonly visible = computed(
+ (visibleConfig, pageCount) => isVisible(visibleConfig, pageCount),
+ [this.visibleConfig, this.dataController.pageCount],
+ );
+
+ constructor(
+ private readonly dataController: DataController,
+ private readonly options: OptionsController,
+ ) {
+ super();
+ }
+
+ protected override getProps(): SubsGets {
+ return combined({
+ itemCount: this.dataController.totalCount,
+ allowedPageSizes: this.allowedPageSizes,
+ visible: this.visible,
+ pageIndex: computed(
+ (pageIndex) => pageIndex + 1,
+ [this.dataController.pageIndex],
+ ),
+ pageIndexChanged: (value): void => this.dataController.pageIndex.update(value - 1),
+ pageSize: this.dataController.pageSize,
+ pageSizeChanged: (value): void => this.dataController.pageSize.update(value),
+ isGridCompatibility: false,
+ pageCount: this.dataController.pageCount,
+ showPageSizeSelector: this.options.oneWay('pager.showPageSizeSelector'),
+ _skipValidation: true,
+ tabIndex: 0,
+ showInfo: this.options.oneWay('pager.showInfo'),
+ showNavigationButtons: this.options.oneWay('pager.showNavigationButtons'),
+ });
+ }
+}
diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/widget.ts b/packages/devextreme/js/__internal/grids/new/grid_core/widget.ts
index 3864b725aa1d..5f68ef5c9b92 100644
--- a/packages/devextreme/js/__internal/grids/new/grid_core/widget.ts
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/widget.ts
@@ -13,6 +13,7 @@ import * as ColumnsControllerModule from './columns_controller/index';
import * as DataControllerModule from './data_controller/index';
import { MainView } from './main_view';
import { defaultOptions, defaultOptionsRules, type Options } from './options';
+import { PagerView } from './pager/view';
import { ToolbarController } from './toolbar/controller';
import { ToolbarView } from './toolbar/view';
@@ -27,6 +28,8 @@ export class GridCoreNewBase<
protected columnsController!: ColumnsControllerModule.ColumnsController;
+ private pagerView!: PagerView;
+
private toolbarController!: ToolbarController;
private toolbarView!: ToolbarView;
@@ -37,6 +40,7 @@ export class GridCoreNewBase<
this.diContext.register(ColumnsControllerModule.ColumnsController);
this.diContext.register(ToolbarController);
this.diContext.register(ToolbarView);
+ this.diContext.register(PagerView);
}
protected _initDIContext(): void {
@@ -44,6 +48,7 @@ export class GridCoreNewBase<
this.columnsController = this.diContext.get(ColumnsControllerModule.ColumnsController);
this.toolbarController = this.diContext.get(ToolbarController);
this.toolbarView = this.diContext.get(ToolbarView);
+ this.pagerView = this.diContext.get(PagerView);
}
protected _init(): void {