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 854f207666c0..aad2ba0fec82 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 @@ -156,25 +156,21 @@ exports[`common initial render should be successfull 1`] = ` class="dx-cardview-headers" >
+ A
- A
-
- -
+
@@ -292,50 +288,7 @@ exports[`common initial render should be successfull 1`] = ` /> - + `; 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 633b53c45c5f..3c80f8f0bd29 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 @@ -4,7 +4,7 @@ import { combined } from '@ts/core/reactive/index'; import { ColumnsChooserView } from '@ts/grids/new/grid_core/columns_chooser/view'; import { View } from '@ts/grids/new/grid_core/core/view'; import { FilterPanelView } from '@ts/grids/new/grid_core/filtering/filter_panel/filter_panel'; -import { PagerView } from '@ts/grids/new/grid_core/pager'; +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'; diff --git a/packages/devextreme/js/__internal/grids/new/data_grid/main_view.tsx b/packages/devextreme/js/__internal/grids/new/data_grid/main_view.tsx index 637a0c481e1a..09c347954c23 100644 --- a/packages/devextreme/js/__internal/grids/new/data_grid/main_view.tsx +++ b/packages/devextreme/js/__internal/grids/new/data_grid/main_view.tsx @@ -4,7 +4,7 @@ import type { Subscribable } from '@ts/core/reactive/index'; import { ColumnsChooserView } from '@ts/grids/new/grid_core/columns_chooser/view'; import { View } from '@ts/grids/new/grid_core/core/view_old'; import { FilterPanelView } from '@ts/grids/new/grid_core/filtering/filter_panel/filter_panel'; -import { PagerView } from '@ts/grids/new/grid_core/pager'; +import { PagerView } from '@ts/grids/new/grid_core/pager/view'; import { ToolbarView } from '@ts/grids/new/grid_core/toolbar/view'; import type { InfernoNode } from 'inferno'; 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 58fa22a4b4f0..67a1e46515ad 100644 --- a/packages/devextreme/js/__internal/grids/new/grid_core/options.ts +++ b/packages/devextreme/js/__internal/grids/new/grid_core/options.ts @@ -5,6 +5,7 @@ import type { WidgetOptions } from '@js/ui/widget/ui.widget'; import * as columnsController from './columns_controller'; import * as dataController from './data_controller'; import { filterPanel } from './filtering'; +import * as pager from './pager'; import type { SearchProperties } from './search/types'; import * as toolbar from './toolbar'; import type { GridCoreNew } from './widget'; @@ -16,6 +17,7 @@ export type Options = & WidgetOptions & dataController.Options & toolbar.Options + & pager.Options & columnsController.Options & filterPanel.Options & SearchProperties @@ -27,6 +29,7 @@ export const defaultOptions = { ...dataController.defaultOptions, ...columnsController.defaultOptions, ...toolbar.defaultOptions, + ...pager.defaultOptions, ...filterPanel.defaultOptions, searchText: '', } satisfies Options; diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/options_controller/options_controller.mock.ts b/packages/devextreme/js/__internal/grids/new/grid_core/options_controller/options_controller.mock.ts index 9aedf84a4c13..18dd6d589a37 100644 --- a/packages/devextreme/js/__internal/grids/new/grid_core/options_controller/options_controller.mock.ts +++ b/packages/devextreme/js/__internal/grids/new/grid_core/options_controller/options_controller.mock.ts @@ -1,6 +1,11 @@ -import type { defaultOptions, Options } from '../options'; +import type { Options } from '../options'; +import { defaultOptions } from '../options'; import { OptionsControllerMock as OptionsControllerBaseMock } from './options_controller_base.mock'; export class OptionsControllerMock extends OptionsControllerBaseMock< Options, typeof defaultOptions -> {} +> { + constructor(options: Options) { + super(options, defaultOptions); + } +} diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/options_controller/options_controller_base.mock.ts b/packages/devextreme/js/__internal/grids/new/grid_core/options_controller/options_controller_base.mock.ts index 5f17604179a3..3a3a1747e202 100644 --- a/packages/devextreme/js/__internal/grids/new/grid_core/options_controller/options_controller_base.mock.ts +++ b/packages/devextreme/js/__internal/grids/new/grid_core/options_controller/options_controller_base.mock.ts @@ -13,9 +13,10 @@ export class OptionsControllerMock< TDefaultProps extends TProps, > extends OptionsController { private readonly componentMock: Component; - constructor(options: TProps) { + constructor(options: TProps, defaultOptions: TDefaultProps) { const componentMock = new Component(options); super(componentMock); + this.defaults = defaultOptions; this.componentMock = componentMock; } 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 4f8cb64fe4ad..8bc4c67b7958 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 @@ -74,7 +74,7 @@ export class OptionsController { private readonly props: SubsGetsUpd; - private readonly defaults: TDefaultProps; + protected defaults: TDefaultProps; public static dependencies = [Component]; diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/pager.tsx b/packages/devextreme/js/__internal/grids/new/grid_core/pager.tsx deleted file mode 100644 index 536af2598eb1..000000000000 --- a/packages/devextreme/js/__internal/grids/new/grid_core/pager.tsx +++ /dev/null @@ -1,36 +0,0 @@ -/* eslint-disable spellcheck/spell-checker */ -import type { Subscribable } 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 type { PagerProps } from './inferno_wrappers/pager'; -import { Pager } from './inferno_wrappers/pager'; - -export class PagerView extends View { - protected override component = Pager; - - public static dependencies = [DataController] as const; - - constructor( - private readonly dataController: DataController, - ) { - super(); - } - - protected override getProps(): Subscribable { - return combined({ - pageIndex: computed( - // TODO: fix the '??' - (pageIndex) => (pageIndex ?? 0) + 1, - [this.dataController.pageIndex], - ), - pageIndexChanged: (value): void => this.dataController.pageIndex.update(value - 1), - pageSize: this.dataController.pageSize, - pageSizeChanged: this.dataController.pageSize.update, - gridCompatibility: false, - pageCount: this.dataController.pageCount, - _skipValidation: true, - }); - } -} 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..289b796bb2e5 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/new/grid_core/pager/pager.tsx @@ -0,0 +1,15 @@ +import type { PagerBase } from '@js/ui/pagination'; +import type { InfernoNode } from 'inferno'; +import { Component } from 'inferno'; + +import { Pager } from '../inferno_wrappers/pager'; + +export type PagerProps = PagerBase & { visible: boolean }; + +export class PagerView extends Component { + public render(): InfernoNode { + return ( + this.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..9bb4d45e6b0d --- /dev/null +++ b/packages/devextreme/js/__internal/grids/new/grid_core/pager/view.tsx @@ -0,0 +1,60 @@ +/* eslint-disable spellcheck/spell-checker */ +import type { Subscribable } 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(): Subscribable { + return combined({ + allowedPageSizes: this.allowedPageSizes, + visible: this.visible, + pageIndex: computed( + // TODO: fix the '??' + (pageIndex) => (pageIndex ?? 0) + 1, + [this.dataController.pageIndex], + ), + pageIndexChanged: (value): void => this.dataController.pageIndex.update(value - 1), + pageSize: this.dataController.pageSize, + pageSizeChanged: this.dataController.pageSize.update, + gridCompatibility: false, + pageCount: this.dataController.pageCount, + showPageSizeSelector: this.options.oneWay('pager.showPageSizeSelector'), + _skipValidation: true, + }); + } +} 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 58da18bd015b..b0cb3582c5a0 100644 --- a/packages/devextreme/js/__internal/grids/new/grid_core/widget.ts +++ b/packages/devextreme/js/__internal/grids/new/grid_core/widget.ts @@ -18,7 +18,7 @@ import { ErrorController } from './error_controller/error_controller'; import { FilterPanelView } from './filtering/filter_panel/filter_panel'; import { MainView } from './main_view'; import { defaultOptions, defaultOptionsRules, type Options } from './options'; -import { PagerView } from './pager'; +import { PagerView } from './pager/view'; import { Search } from './search/controller'; import { ToolbarController } from './toolbar/controller'; import { ToolbarView } from './toolbar/view';