{
render(): JSX.Element {
this.cardRefs = new Array(this.props.items.length).fill(undefined).map(() => createRef());
+ const className = combineClasses({
+ [CLASSES.content]: true,
+ [CLASSES.grid]: true,
+ [CLASSES.selectCheckBoxesHidden]: !!this.props.needToHiddenCheckBoxes,
+ });
return (
diff --git a/packages/devextreme/js/__internal/grids/new/card_view/content_view/types.ts b/packages/devextreme/js/__internal/grids/new/card_view/content_view/types.ts
new file mode 100644
index 000000000000..a871461422f9
--- /dev/null
+++ b/packages/devextreme/js/__internal/grids/new/card_view/content_view/types.ts
@@ -0,0 +1,12 @@
+import type { DataRow } from '@ts/grids/new/grid_core/columns_controller/types';
+
+export interface SelectCardOptions {
+ control?: boolean;
+ shift?: boolean;
+ needToUpdateCheckboxes?: boolean;
+}
+
+export interface CardHoldEvent {
+ event?: MouseEvent;
+ row: DataRow;
+}
diff --git a/packages/devextreme/js/__internal/grids/new/card_view/content_view/view.tsx b/packages/devextreme/js/__internal/grids/new/card_view/content_view/view.tsx
index ac12d5ee4440..b4bec55041ef 100644
--- a/packages/devextreme/js/__internal/grids/new/card_view/content_view/view.tsx
+++ b/packages/devextreme/js/__internal/grids/new/card_view/content_view/view.tsx
@@ -4,11 +4,13 @@ import { compileGetter } from '@js/core/utils/data';
import { isDefined } from '@js/core/utils/type';
import { combined, computed, state } from '@ts/core/reactive/index';
import type { OptionsController } from '@ts/grids/new/card_view/options_controller';
+import type { DataRow } from '@ts/grids/new/grid_core/columns_controller/types';
import { ContentView as ContentViewBase } from '../../grid_core/content_view/view';
import type { DataObject } from '../../grid_core/data_controller/types';
import type { ContentViewProps } from './content_view';
import { ContentView as ContentViewComponent } from './content_view';
+import type { CardHoldEvent, SelectCardOptions } from './types';
import { factors } from './utils';
export class ContentView extends ContentViewBase {
@@ -43,13 +45,16 @@ export class ContentView extends ContentViewBase {
...this.getBaseProps(),
contentProps: combined({
items: this.itemsController.items,
- // items: computed((virtualState) => virtualState.virtualItems, [this.virtualState]),
+ needToHiddenCheckBoxes: this.selectionController.needToHiddenCheckBoxes,
fieldTemplate: this.options.template('fieldTemplate'),
cardsPerRow: this.cardsPerRow,
onRowHeightChange: this.rowHeight.update.bind(this.rowHeight),
cardProps: combined({
minWidth: this.cardMinWidth,
maxWidth: this.options.oneWay('cardMaxWidth'),
+ isCheckBoxesRendered: this.selectionController.isCheckBoxesRendered,
+ allowSelectOnClick: this.selectionController.allowSelectOnClick,
+ onHold: this.onCardHold.bind(this),
onClick: this.options.action('onCardClick'),
onDblClick: this.options.action('onCardDblClick'),
onHoverChanged: this.options.action('onCardHoverChanged'),
@@ -68,6 +73,7 @@ export class ContentView extends ContentViewBase {
}),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
toolbar: this.options.oneWay('cardHeader.items') as any,
+ selectCard: this.selectCard.bind(this),
}),
}),
});
@@ -82,4 +88,16 @@ export class ContentView extends ContentViewBase {
// @ts-expect-error
return compileGetter(expr);
}
+
+ private selectCard(row: DataRow, options: SelectCardOptions) {
+ if (options.needToUpdateCheckboxes) {
+ this.selectionController.updateSelectionCheckBoxesVisible(true);
+ }
+
+ this.selectionController.changeCardSelection(row.index, options);
+ }
+
+ private onCardHold(e: CardHoldEvent) {
+ this.selectionController.processLongTap(e.row);
+ }
}
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 bc0cf045db20..ee36cd4e12fe 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
@@ -3,7 +3,7 @@ import type { ColumnBase } from '@js/common/grids';
import type { HeaderFilterColumnOptions } from '@ts/grids/new/grid_core/filtering/header_filter/index';
import type { ComponentType } from 'inferno';
-import type { DataObject } from '../data_controller/types';
+import type { DataObject, Key } from '../data_controller/types';
import type { HighlightedTextItem } from '../search/types';
type InheritedColumnProps =
@@ -71,9 +71,11 @@ export interface Cell {
export interface DataRow {
cells: Cell[];
- key: unknown;
+ key: Key;
- data: unknown;
+ data: DataObject;
+
+ isSelected?: boolean;
index: number;
}
diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/content_view/view.tsx b/packages/devextreme/js/__internal/grids/new/grid_core/content_view/view.tsx
index 08158ba4b6bb..936bddfde638 100644
--- a/packages/devextreme/js/__internal/grids/new/grid_core/content_view/view.tsx
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/content_view/view.tsx
@@ -7,6 +7,7 @@ import { ColumnsController } from '@ts/grids/new/grid_core/columns_controller/co
import { View } from '@ts/grids/new/grid_core/core/view';
import { DataController } from '@ts/grids/new/grid_core/data_controller/index';
import { ErrorController } from '@ts/grids/new/grid_core/error_controller/error_controller';
+import { SelectionController } from '@ts/grids/new/grid_core/selection/controller';
import { createRef } from 'inferno';
import { ItemsController } from '../items_controller/items_controller';
@@ -33,6 +34,7 @@ export abstract class ContentView extends View {
OptionsController,
ErrorController,
ColumnsController,
+ SelectionController,
ItemsController,
] as const;
@@ -41,6 +43,7 @@ export abstract class ContentView extends View {
protected readonly options: OptionsController,
protected readonly errorController: ErrorController,
protected readonly columnsController: ColumnsController,
+ protected readonly selectionController: SelectionController,
protected readonly itemsController: ItemsController,
) {
super();
diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/data_controller/data_controller.ts b/packages/devextreme/js/__internal/grids/new/grid_core/data_controller/data_controller.ts
index b3a2682450b0..ae4614b13a36 100644
--- a/packages/devextreme/js/__internal/grids/new/grid_core/data_controller/data_controller.ts
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/data_controller/data_controller.ts
@@ -65,6 +65,8 @@ export class DataController {
[this.totalCount, this.pageSize],
);
+ public readonly isLoaded = state(false);
+
private readonly normalizedRemoteOptions = computed(
(remoteOperations, dataSource) => {
const store = dataSource.store();
@@ -88,6 +90,7 @@ export class DataController {
effect(
(dataSource) => {
const changedCallback = (e?): void => {
+ this.isLoaded.update(true);
this.onChanged(dataSource, e);
};
const loadingChangedCallback = (): void => {
diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/items_controller/__snapshots__/items_controller.test.ts.snap b/packages/devextreme/js/__internal/grids/new/grid_core/items_controller/__snapshots__/items_controller.test.ts.snap
index 4b5f7b23564e..25afd10b1355 100644
--- a/packages/devextreme/js/__internal/grids/new/grid_core/items_controller/__snapshots__/items_controller.test.ts.snap
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/items_controller/__snapshots__/items_controller.test.ts.snap
@@ -80,6 +80,7 @@ exports[`ItemsController createDataRow should process data object to data row us
"id": 1,
},
"index": 0,
+ "isSelected": false,
"key": 1,
}
`;
@@ -164,6 +165,65 @@ exports[`ItemsController createDataRow should process data object to data row us
"id": 1,
},
"index": 0,
+ "isSelected": true,
"key": 1,
}
`;
+
+exports[`ItemsController setSelectionState should update the select state of the item 1`] = `
+InterruptableComputed {
+ "callbacks": Set {},
+ "depInitialized": [
+ true,
+ true,
+ true,
+ true,
+ ],
+ "depValues": [
+ [
+ {
+ "a": "my a value",
+ "id": 1,
+ },
+ ],
+ [],
+ [
+ 1,
+ ],
+ {
+ "caseSensitive": false,
+ "enabled": true,
+ "searchStr": "",
+ },
+ ],
+ "isInitialized": true,
+ "subscriptions": SubscriptionBag {
+ "subscriptions": [
+ {
+ "unsubscribe": [Function],
+ },
+ {
+ "unsubscribe": [Function],
+ },
+ {
+ "unsubscribe": [Function],
+ },
+ {
+ "unsubscribe": [Function],
+ },
+ ],
+ },
+ "value": [
+ {
+ "cells": [],
+ "data": {
+ "a": "my a value",
+ "id": 1,
+ },
+ "index": 0,
+ "isSelected": true,
+ "key": 1,
+ },
+ ],
+}
+`;
diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/items_controller/items_controller.test.ts b/packages/devextreme/js/__internal/grids/new/grid_core/items_controller/items_controller.test.ts
index c99b849d34fd..34c6a954dcc5 100644
--- a/packages/devextreme/js/__internal/grids/new/grid_core/items_controller/items_controller.test.ts
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/items_controller/items_controller.test.ts
@@ -61,8 +61,21 @@ describe('ItemsController', () => {
});
const columns = columnsController.columns.unreactive_get();
- const dataRow = itemsController.createDataRow(dataObject, columns, 0);
+ const dataRow = itemsController.createDataRow(dataObject, columns, 0, [1]);
expect(dataRow).toMatchSnapshot();
});
});
+
+ describe('setSelectionState', () => {
+ it('should update the select state of the item', () => {
+ const { itemsController } = setup({
+ keyExpr: 'id',
+ dataSource: [{ id: 1, a: 'my a value' }],
+ });
+
+ itemsController.setSelectionState([1]);
+
+ expect(itemsController.items).toMatchSnapshot();
+ });
+ });
});
diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/items_controller/items_controller.ts b/packages/devextreme/js/__internal/grids/new/grid_core/items_controller/items_controller.ts
index 821852754286..1f3ff3cc1609 100644
--- a/packages/devextreme/js/__internal/grids/new/grid_core/items_controller/items_controller.ts
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/items_controller/items_controller.ts
@@ -1,13 +1,16 @@
+import { equalByValue } from '@js/core/utils/common';
import formatHelper from '@js/format_helper';
-import { computed } from '@ts/core/reactive/index';
+import { computed, state } from '@ts/core/reactive/index';
import { ColumnsController } from '@ts/grids/new/grid_core/columns_controller/columns_controller';
import { DataController } from '@ts/grids/new/grid_core/data_controller/data_controller';
import { SearchController } from '@ts/grids/new/grid_core/search/index';
import type { Column, DataRow } from '../columns_controller/types';
-import type { DataObject } from '../data_controller/types';
+import type { DataObject, Key } from '../data_controller/types';
export class ItemsController {
+ private readonly selectedCardKeys = state([]);
+
public static dependencies = [
DataController,
ColumnsController,
@@ -18,6 +21,7 @@ export class ItemsController {
(
dataItems,
columns: Column[],
+ selectedCardKeys,
// NOTE: We should trigger computed by search options change
// But all work with these options encapsulated in SearchHighlightTextProcessor
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -27,11 +31,13 @@ export class ItemsController {
item,
columns,
itemIndex,
+ selectedCardKeys,
),
),
[
this.dataController.items,
this.columnsController.visibleColumns,
+ this.selectedCardKeys,
this.searchController.highlightTextOptions,
],
);
@@ -42,10 +48,15 @@ export class ItemsController {
private readonly searchController: SearchController,
) {}
+ public setSelectionState(keys: Key[]): void {
+ this.selectedCardKeys.update(keys);
+ }
+
public createDataRow(
data: DataObject,
columns: Column[],
itemIndex: number,
+ selectedCardKeys?: Key[],
): DataRow {
const itemKey = this.dataController.getDataKey(data);
@@ -71,7 +82,15 @@ export class ItemsController {
}),
key: itemKey,
index: itemIndex,
+ isSelected: !!selectedCardKeys?.includes(itemKey),
data,
};
}
+
+ public getRowByKey(key: Key): DataRow | undefined {
+ // eslint-disable-next-line spellcheck/spell-checker
+ const items = this.items.unreactive_get();
+
+ return items.find((item) => equalByValue(item.key, key));
+ }
}
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 5fc9346d722d..75b9bfae7f1e 100644
--- a/packages/devextreme/js/__internal/grids/new/grid_core/options.ts
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/options.ts
@@ -11,6 +11,7 @@ import { filterPanel } from './filtering/index';
import * as pager from './pager/index';
import * as searchPanel from './search/index';
import type { SearchProperties } from './search/types';
+import * as selection from './selection/index';
import * as sortingController from './sorting_controller/index';
import type * as toolbar from './toolbar/index';
import type { GridCoreNew } from './widget';
@@ -29,6 +30,7 @@ export type Options =
& headerFilter.Options
& contentView.Options
& searchPanel.Options
+ & selection.Options
// TODO: Remove this mock search options during search implementation
& SearchProperties
& toolbar.Options;
@@ -42,6 +44,7 @@ export const defaultOptions = {
...headerFilter.defaultOptions,
...contentView.defaultOptions,
...searchPanel.defaultOptions,
+ ...selection.defaultOptions,
searchText: '',
} satisfies Options;
diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/selection/__snapshots__/controller.test.ts.snap b/packages/devextreme/js/__internal/grids/new/grid_core/selection/__snapshots__/controller.test.ts.snap
new file mode 100644
index 000000000000..6237a65341e9
--- /dev/null
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/selection/__snapshots__/controller.test.ts.snap
@@ -0,0 +1,343 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`SelectionController changeCardSelection when the control arg equal to false should update the select state of the item 1`] = `
+InterruptableComputed {
+ "callbacks": Set {},
+ "depInitialized": [
+ true,
+ true,
+ true,
+ true,
+ ],
+ "depValues": [
+ [
+ {
+ "id": 1,
+ "value": "test",
+ },
+ ],
+ [],
+ [
+ 1,
+ ],
+ {
+ "caseSensitive": false,
+ "enabled": true,
+ "searchStr": "",
+ },
+ ],
+ "isInitialized": true,
+ "subscriptions": SubscriptionBag {
+ "subscriptions": [
+ {
+ "unsubscribe": [Function],
+ },
+ {
+ "unsubscribe": [Function],
+ },
+ {
+ "unsubscribe": [Function],
+ },
+ {
+ "unsubscribe": [Function],
+ },
+ ],
+ },
+ "value": [
+ {
+ "cells": [],
+ "data": {
+ "id": 1,
+ "value": "test",
+ },
+ "index": 0,
+ "isSelected": true,
+ "key": 1,
+ },
+ ],
+}
+`;
+
+exports[`SelectionController changeCardSelection when the control arg equal to true should update the select state of the item 1`] = `
+InterruptableComputed {
+ "callbacks": Set {},
+ "depInitialized": [
+ true,
+ true,
+ true,
+ true,
+ ],
+ "depValues": [
+ [
+ {
+ "id": 1,
+ "value": "test",
+ },
+ ],
+ [],
+ [],
+ {
+ "caseSensitive": false,
+ "enabled": true,
+ "searchStr": "",
+ },
+ ],
+ "isInitialized": true,
+ "subscriptions": SubscriptionBag {
+ "subscriptions": [
+ {
+ "unsubscribe": [Function],
+ },
+ {
+ "unsubscribe": [Function],
+ },
+ {
+ "unsubscribe": [Function],
+ },
+ {
+ "unsubscribe": [Function],
+ },
+ ],
+ },
+ "value": [
+ {
+ "cells": [],
+ "data": {
+ "id": 1,
+ "value": "test",
+ },
+ "index": 0,
+ "isSelected": false,
+ "key": 1,
+ },
+ ],
+}
+`;
+
+exports[`SelectionController deselectCards should deselect item 1`] = `
+InterruptableComputed {
+ "callbacks": Set {},
+ "depInitialized": [
+ true,
+ true,
+ true,
+ true,
+ ],
+ "depValues": [
+ [
+ {
+ "id": 1,
+ "value": "test",
+ },
+ ],
+ [],
+ [],
+ {
+ "caseSensitive": false,
+ "enabled": true,
+ "searchStr": "",
+ },
+ ],
+ "isInitialized": true,
+ "subscriptions": SubscriptionBag {
+ "subscriptions": [
+ {
+ "unsubscribe": [Function],
+ },
+ {
+ "unsubscribe": [Function],
+ },
+ {
+ "unsubscribe": [Function],
+ },
+ {
+ "unsubscribe": [Function],
+ },
+ ],
+ },
+ "value": [
+ {
+ "cells": [],
+ "data": {
+ "id": 1,
+ "value": "test",
+ },
+ "index": 0,
+ "isSelected": false,
+ "key": 1,
+ },
+ ],
+}
+`;
+
+exports[`SelectionController deselectCardsByIndexes should deselect item 1`] = `
+InterruptableComputed {
+ "callbacks": Set {},
+ "depInitialized": [
+ true,
+ true,
+ true,
+ true,
+ ],
+ "depValues": [
+ [
+ {
+ "id": 1,
+ "value": "test",
+ },
+ ],
+ [],
+ [],
+ {
+ "caseSensitive": false,
+ "enabled": true,
+ "searchStr": "",
+ },
+ ],
+ "isInitialized": true,
+ "subscriptions": SubscriptionBag {
+ "subscriptions": [
+ {
+ "unsubscribe": [Function],
+ },
+ {
+ "unsubscribe": [Function],
+ },
+ {
+ "unsubscribe": [Function],
+ },
+ {
+ "unsubscribe": [Function],
+ },
+ ],
+ },
+ "value": [
+ {
+ "cells": [],
+ "data": {
+ "id": 1,
+ "value": "test",
+ },
+ "index": 0,
+ "isSelected": false,
+ "key": 1,
+ },
+ ],
+}
+`;
+
+exports[`SelectionController selectCards should select item 1`] = `
+InterruptableComputed {
+ "callbacks": Set {},
+ "depInitialized": [
+ true,
+ true,
+ true,
+ true,
+ ],
+ "depValues": [
+ [
+ {
+ "id": 1,
+ "value": "test",
+ },
+ ],
+ [],
+ [
+ 1,
+ ],
+ {
+ "caseSensitive": false,
+ "enabled": true,
+ "searchStr": "",
+ },
+ ],
+ "isInitialized": true,
+ "subscriptions": SubscriptionBag {
+ "subscriptions": [
+ {
+ "unsubscribe": [Function],
+ },
+ {
+ "unsubscribe": [Function],
+ },
+ {
+ "unsubscribe": [Function],
+ },
+ {
+ "unsubscribe": [Function],
+ },
+ ],
+ },
+ "value": [
+ {
+ "cells": [],
+ "data": {
+ "id": 1,
+ "value": "test",
+ },
+ "index": 0,
+ "isSelected": true,
+ "key": 1,
+ },
+ ],
+}
+`;
+
+exports[`SelectionController selectCardsByIndexes should select item 1`] = `
+InterruptableComputed {
+ "callbacks": Set {},
+ "depInitialized": [
+ true,
+ true,
+ true,
+ true,
+ ],
+ "depValues": [
+ [
+ {
+ "id": 1,
+ "value": "test",
+ },
+ ],
+ [],
+ [
+ 1,
+ ],
+ {
+ "caseSensitive": false,
+ "enabled": true,
+ "searchStr": "",
+ },
+ ],
+ "isInitialized": true,
+ "subscriptions": SubscriptionBag {
+ "subscriptions": [
+ {
+ "unsubscribe": [Function],
+ },
+ {
+ "unsubscribe": [Function],
+ },
+ {
+ "unsubscribe": [Function],
+ },
+ {
+ "unsubscribe": [Function],
+ },
+ ],
+ },
+ "value": [
+ {
+ "cells": [],
+ "data": {
+ "id": 1,
+ "value": "test",
+ },
+ "index": 0,
+ "isSelected": true,
+ "key": 1,
+ },
+ ],
+}
+`;
diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/selection/__snapshots__/options.test.ts.snap b/packages/devextreme/js/__internal/grids/new/grid_core/selection/__snapshots__/options.test.ts.snap
new file mode 100644
index 000000000000..450dc1044be7
--- /dev/null
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/selection/__snapshots__/options.test.ts.snap
@@ -0,0 +1,204 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Options selectedCardKeys when given should set the select state of the item 1`] = `
+InterruptableComputed {
+ "callbacks": Set {},
+ "depInitialized": [
+ true,
+ true,
+ true,
+ true,
+ ],
+ "depValues": [
+ [
+ {
+ "id": 1,
+ "value": "test",
+ },
+ ],
+ [],
+ [
+ 1,
+ ],
+ {
+ "caseSensitive": false,
+ "enabled": true,
+ "searchStr": "",
+ },
+ ],
+ "isInitialized": true,
+ "subscriptions": SubscriptionBag {
+ "subscriptions": [
+ {
+ "unsubscribe": [Function],
+ },
+ {
+ "unsubscribe": [Function],
+ },
+ {
+ "unsubscribe": [Function],
+ },
+ {
+ "unsubscribe": [Function],
+ },
+ ],
+ },
+ "value": [
+ {
+ "cells": [],
+ "data": {
+ "id": 1,
+ "value": "test",
+ },
+ "index": 0,
+ "isSelected": true,
+ "key": 1,
+ },
+ ],
+}
+`;
+
+exports[`Options selection allowSelectAll when it is false and selection mode is 'multiple' selection should not work 1`] = `[]`;
+
+exports[`Options selection allowSelectAll when it is true and selection mode is 'multiple' selection should not work 1`] = `
+[
+ {
+ "locateInMenu": "auto",
+ "location": "before",
+ "name": "selectAllButton",
+ "options": {
+ "disabled": false,
+ "icon": "selectall",
+ "onClick": [Function],
+ "text": "Select All",
+ },
+ "widget": "dxButton",
+ },
+ {
+ "locateInMenu": "auto",
+ "location": "before",
+ "name": "clearSelectionButton",
+ "options": {
+ "disabled": true,
+ "icon": "close",
+ "onClick": [Function],
+ "text": "Clear selection",
+ },
+ "widget": "dxButton",
+ },
+]
+`;
+
+exports[`Options selection allowSelectAll when it is true and selection mode isn't 'multiple' selection should not work 1`] = `[]`;
+
+exports[`Options selection mode when it is 'none' and the selectedCardKeys is specified selection should not apply 1`] = `
+InterruptableComputed {
+ "callbacks": Set {},
+ "depInitialized": [
+ true,
+ true,
+ true,
+ true,
+ ],
+ "depValues": [
+ [
+ {
+ "id": 1,
+ "value": "test",
+ },
+ ],
+ [],
+ [],
+ {
+ "caseSensitive": false,
+ "enabled": true,
+ "searchStr": "",
+ },
+ ],
+ "isInitialized": true,
+ "subscriptions": SubscriptionBag {
+ "subscriptions": [
+ {
+ "unsubscribe": [Function],
+ },
+ {
+ "unsubscribe": [Function],
+ },
+ {
+ "unsubscribe": [Function],
+ },
+ {
+ "unsubscribe": [Function],
+ },
+ ],
+ },
+ "value": [
+ {
+ "cells": [],
+ "data": {
+ "id": 1,
+ "value": "test",
+ },
+ "index": 0,
+ "isSelected": false,
+ "key": 1,
+ },
+ ],
+}
+`;
+
+exports[`Options selection mode when it is 'none' selection should not work 1`] = `
+InterruptableComputed {
+ "callbacks": Set {},
+ "depInitialized": [
+ true,
+ true,
+ true,
+ true,
+ ],
+ "depValues": [
+ [
+ {
+ "id": 1,
+ "value": "test",
+ },
+ ],
+ [],
+ [],
+ {
+ "caseSensitive": false,
+ "enabled": true,
+ "searchStr": "",
+ },
+ ],
+ "isInitialized": true,
+ "subscriptions": SubscriptionBag {
+ "subscriptions": [
+ {
+ "unsubscribe": [Function],
+ },
+ {
+ "unsubscribe": [Function],
+ },
+ {
+ "unsubscribe": [Function],
+ },
+ {
+ "unsubscribe": [Function],
+ },
+ ],
+ },
+ "value": [
+ {
+ "cells": [],
+ "data": {
+ "id": 1,
+ "value": "test",
+ },
+ "index": 0,
+ "isSelected": false,
+ "key": 1,
+ },
+ ],
+}
+`;
diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/selection/const.ts b/packages/devextreme/js/__internal/grids/new/grid_core/selection/const.ts
new file mode 100644
index 000000000000..0664d40bec41
--- /dev/null
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/selection/const.ts
@@ -0,0 +1,17 @@
+export enum SelectionMode {
+ Multiple = 'multiple',
+
+ Single = 'single',
+
+ None = 'none',
+}
+
+export enum ShowCheckBoxesMode {
+ Always = 'always',
+
+ OnClick = 'onClick',
+
+ OnLongTap = 'onLongTap',
+
+ None = 'none',
+}
diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/selection/controller.test.ts b/packages/devextreme/js/__internal/grids/new/grid_core/selection/controller.test.ts
new file mode 100644
index 000000000000..fbed51d7649f
--- /dev/null
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/selection/controller.test.ts
@@ -0,0 +1,847 @@
+/* eslint-disable spellcheck/spell-checker */
+import {
+ describe, expect, it, jest,
+} from '@jest/globals';
+
+import { ColumnsController } from '../columns_controller/columns_controller';
+import { DataController } from '../data_controller';
+import { FilterController } from '../filtering/filter_controller';
+import { ItemsController } from '../items_controller/items_controller';
+import type { Options } from '../options';
+import { OptionsControllerMock } from '../options_controller/options_controller.mock';
+import { SearchController } from '../search/controller';
+import { SortingController } from '../sorting_controller/sorting_controller';
+import { ToolbarController } from '../toolbar/controller';
+import { SelectionController } from './controller';
+
+const setup = (config: Options = {}) => {
+ const optionsController = new OptionsControllerMock({
+ selection: {
+ mode: 'single',
+ },
+ selectedCardKeys: [],
+ ...config,
+ });
+
+ const filterController = new FilterController(optionsController);
+ const columnsController = new ColumnsController(optionsController);
+ const sortingController = new SortingController(optionsController, columnsController);
+
+ const dataController = new DataController(optionsController, sortingController, filterController);
+
+ const searchController = new SearchController(optionsController);
+ const itemsController = new ItemsController(dataController, columnsController, searchController);
+ const toolbarController = new ToolbarController(optionsController);
+
+ const selectionController = new SelectionController(
+ optionsController,
+ dataController,
+ itemsController,
+ toolbarController,
+ );
+
+ return {
+ optionsController,
+ selectionController,
+ itemsController,
+ };
+};
+
+describe('SelectionController', () => {
+ // Public methods
+
+ describe('selectCards', () => {
+ it('should select item', () => {
+ const {
+ selectionController,
+ itemsController,
+ } = setup({
+ keyExpr: 'id',
+ dataSource: [{ id: 1, value: 'test' }],
+ });
+
+ selectionController.selectCards([1]);
+ expect(itemsController.items).toMatchSnapshot();
+ });
+ });
+
+ describe('deselectCards', () => {
+ it('should deselect item', () => {
+ const {
+ selectionController,
+ itemsController,
+ } = setup({
+ keyExpr: 'id',
+ dataSource: [{ id: 1, value: 'test' }],
+ selectedCardKeys: [1],
+ });
+
+ selectionController.deselectCards([1]);
+ expect(itemsController.items).toMatchSnapshot();
+ });
+ });
+
+ describe('selectCardsByIndexes', () => {
+ it('should select item', () => {
+ const {
+ selectionController,
+ itemsController,
+ } = setup({
+ keyExpr: 'id',
+ dataSource: [{ id: 1, value: 'test' }],
+ });
+
+ selectionController.selectCardsByIndexes([0]);
+ expect(itemsController.items).toMatchSnapshot();
+ });
+ });
+
+ describe('deselectCardsByIndexes', () => {
+ it('should deselect item', () => {
+ const {
+ selectionController,
+ itemsController,
+ } = setup({
+ keyExpr: 'id',
+ dataSource: [{ id: 1, value: 'test' }],
+ selectedCardKeys: [1],
+ });
+
+ selectionController.deselectCardsByIndexes([0]);
+ expect(itemsController.items).toMatchSnapshot();
+ });
+ });
+
+ describe('changeCardSelection', () => {
+ describe('when the control arg equal to false', () => {
+ it('should update the select state of the item', () => {
+ const {
+ selectionController,
+ itemsController,
+ } = setup({
+ keyExpr: 'id',
+ dataSource: [{ id: 1, value: 'test' }],
+ });
+
+ selectionController.changeCardSelection(0, { control: false });
+ expect(itemsController.items).toMatchSnapshot();
+ });
+ });
+
+ describe('when the control arg equal to true', () => {
+ it('should update the select state of the item', () => {
+ const {
+ selectionController,
+ itemsController,
+ } = setup({
+ keyExpr: 'id',
+ dataSource: [{ id: 1, value: 'test' }],
+ selectedCardKeys: [1],
+ });
+
+ selectionController.changeCardSelection(0, { control: true });
+ expect(itemsController.items).toMatchSnapshot();
+ });
+ });
+
+ describe('when item is selected and multiple selection enabled', () => {
+ it('should update the select state of the item', () => {
+ const {
+ selectionController,
+ } = setup({
+ keyExpr: 'id',
+ dataSource: [{ id: 1, value: 'test' }],
+ selectedCardKeys: [1],
+ selection: {
+ mode: 'multiple',
+ showCheckBoxesMode: 'always',
+ },
+ });
+
+ selectionController.changeCardSelection(0);
+ expect(selectionController.getSelectedCardKeys()).toEqual([]);
+ });
+ });
+ });
+
+ describe('isCardSelected', () => {
+ describe('when the selectedCardKeys is specified', () => {
+ it('should return true', () => {
+ const {
+ selectionController,
+ } = setup({
+ keyExpr: 'id',
+ dataSource: [{ id: 1, value: 'test' }],
+ selectedCardKeys: [1],
+ });
+
+ expect(selectionController.isCardSelected(1)).toBe(true);
+ });
+ });
+
+ describe('when the selectedCardKeys isn\'t specified', () => {
+ it('should return false', () => {
+ const {
+ selectionController,
+ } = setup({
+ keyExpr: 'id',
+ dataSource: [{ id: 1, value: 'test' }],
+ });
+
+ expect(selectionController.isCardSelected(1)).toBe(false);
+ });
+ });
+ });
+
+ describe('getSelectedCardKeys', () => {
+ it('should return the selected card keys', () => {
+ const {
+ selectionController,
+ } = setup({
+ keyExpr: 'id',
+ dataSource: [{ id: 1, value: 'test' }],
+ selectedCardKeys: [1],
+ });
+
+ expect(selectionController.getSelectedCardKeys()).toEqual([1]);
+ });
+ });
+
+ describe('getSelectedCards', () => {
+ it('should return the selected card keys', () => {
+ const {
+ selectionController,
+ itemsController,
+ } = setup({
+ keyExpr: 'id',
+ dataSource: [{ id: 1, value: 'test' }],
+ selectedCardKeys: [1],
+ });
+
+ expect(selectionController.getSelectedCards())
+ .toEqual(itemsController.items.unreactive_get());
+ });
+ });
+
+ describe('clearSelection', () => {
+ it('should clear the selection', () => {
+ const {
+ selectionController,
+ } = setup({
+ keyExpr: 'id',
+ dataSource: [{ id: 1, value: 'test' }],
+ selectedCardKeys: [1],
+ });
+
+ selectionController.clearSelection();
+ expect(selectionController.getSelectedCardKeys().length).toBe(0);
+ });
+ });
+
+ describe('updateSelectionCheckBoxesVisible', () => {
+ describe('when the selection mode is equal to \'multiple\' and the showCheckBoxesMode is equal to \'onClick\'', () => {
+ it('should show the selection checkboxes', () => {
+ const {
+ selectionController,
+ } = setup({
+ keyExpr: 'id',
+ dataSource: [{ id: 1, value: 'test' }],
+ selection: {
+ mode: 'multiple',
+ showCheckBoxesMode: 'onClick',
+ },
+ });
+
+ selectionController.updateSelectionCheckBoxesVisible(true);
+ expect(selectionController.isCheckBoxesVisible.unreactive_get()).toBe(true);
+ });
+
+ it('should hide the selection checkboxes', () => {
+ const {
+ selectionController,
+ } = setup({
+ keyExpr: 'id',
+ dataSource: [{ id: 1, value: 'test' }],
+ selection: {
+ mode: 'multiple',
+ showCheckBoxesMode: 'onClick',
+ },
+ });
+
+ selectionController.updateSelectionCheckBoxesVisible(false);
+ expect(selectionController.isCheckBoxesVisible.unreactive_get()).toBe(false);
+ });
+ });
+ });
+
+ describe('processLongTap', () => {
+ describe('when the selection mode is equal to \'multiple\' and the showCheckBoxesMode is equal to \'onLongTap\'', () => {
+ it('should render the selection checkbox', () => {
+ const {
+ selectionController,
+ } = setup({
+ keyExpr: 'id',
+ dataSource: [{ id: 1, value: 'test' }],
+ selection: {
+ mode: 'multiple',
+ showCheckBoxesMode: 'onLongTap',
+ },
+ });
+
+ // @ts-expect-error
+ selectionController.processLongTap({ index: 0 });
+ expect(selectionController.isCheckBoxesRendered.unreactive_get()).toBe(true);
+ });
+ });
+
+ describe('when the selection mode is equal to \'multiple\' and the showCheckBoxesMode is equal to \'onClick\'', () => {
+ it('should show the selection checkbox', () => {
+ const {
+ selectionController,
+ } = setup({
+ keyExpr: 'id',
+ dataSource: [{ id: 1, value: 'test' }],
+ selection: {
+ mode: 'multiple',
+ showCheckBoxesMode: 'onClick',
+ },
+ });
+
+ // @ts-expect-error
+ selectionController.processLongTap({ index: 0 });
+ expect(selectionController.isCheckBoxesVisible.unreactive_get()).toBe(true);
+ });
+ });
+
+ describe('when the selection mode is equal to \'multiple\' and the showCheckBoxesMode is equal to \'none\'', () => {
+ it('should select a first item', () => {
+ const {
+ selectionController,
+ } = setup({
+ keyExpr: 'id',
+ dataSource: [{ id: 1, value: 'test' }],
+ selection: {
+ mode: 'multiple',
+ showCheckBoxesMode: 'none',
+ },
+ });
+
+ // @ts-expect-error
+ selectionController.processLongTap({ index: 0 });
+ expect(selectionController.getSelectedCardKeys()).toEqual([1]);
+ });
+ });
+
+ describe('when the selection mode is equal to \'multiple\' and the showCheckBoxesMode is equal to \'none\'', () => {
+ it('should not select a first item', () => {
+ const {
+ selectionController,
+ } = setup({
+ keyExpr: 'id',
+ dataSource: [{ id: 1, value: 'test' }],
+ selection: {
+ mode: 'multiple',
+ showCheckBoxesMode: 'always',
+ },
+ });
+
+ // @ts-expect-error
+ selectionController.processLongTap({ index: 0 });
+ expect(selectionController.getSelectedCardKeys()).toEqual([]);
+ });
+ });
+ });
+
+ // Public properties
+ describe('isCheckBoxesRendered', () => {
+ describe('when the selection mode is equal to \'none\'', () => {
+ it('should return false', () => {
+ const {
+ selectionController,
+ } = setup({
+ keyExpr: 'id',
+ dataSource: [{ id: 1, value: 'test' }],
+ selection: {
+ mode: 'none',
+ },
+ });
+
+ expect(selectionController.isCheckBoxesRendered.unreactive_get()).toBe(false);
+ });
+ });
+
+ describe('when the selection mode is equal to \'multiple\' and the showCheckBoxesMode is equal to \'always\'', () => {
+ it('should return true', () => {
+ const {
+ selectionController,
+ } = setup({
+ keyExpr: 'id',
+ dataSource: [{ id: 1, value: 'test' }],
+ selection: {
+ mode: 'multiple',
+ showCheckBoxesMode: 'always',
+ },
+ });
+
+ expect(selectionController.isCheckBoxesRendered.unreactive_get()).toBe(true);
+ });
+ });
+
+ describe('when the selection mode is equal to \'multiple\' and the showCheckBoxesMode is equal to \'onClick\'', () => {
+ it('should return true', () => {
+ const {
+ selectionController,
+ } = setup({
+ keyExpr: 'id',
+ dataSource: [{ id: 1, value: 'test' }],
+ selection: {
+ mode: 'multiple',
+ showCheckBoxesMode: 'onClick',
+ },
+ });
+
+ expect(selectionController.isCheckBoxesRendered.unreactive_get()).toBe(true);
+ });
+ });
+
+ describe('when the selection mode is equal to \'multiple\' and the showCheckBoxesMode is equal to \'onLongTap\'', () => {
+ it('should return false', () => {
+ const {
+ selectionController,
+ } = setup({
+ keyExpr: 'id',
+ dataSource: [{ id: 1, value: 'test' }],
+ selection: {
+ mode: 'multiple',
+ showCheckBoxesMode: 'onLongTap',
+ },
+ });
+
+ expect(selectionController.isCheckBoxesRendered.unreactive_get()).toBe(false);
+ });
+ });
+ });
+
+ describe('isCheckBoxesVisible', () => {
+ describe('when the selection mode is equal to \'multiple\' and the showCheckBoxesMode is equal to \'onClick\'', () => {
+ it('should return false', () => {
+ const {
+ selectionController,
+ } = setup({
+ keyExpr: 'id',
+ dataSource: [{ id: 1, value: 'test' }],
+ selection: {
+ mode: 'multiple',
+ showCheckBoxesMode: 'onClick',
+ },
+ });
+
+ expect(selectionController.isCheckBoxesVisible.unreactive_get()).toBe(false);
+ });
+ });
+
+ describe('when selecting one card', () => {
+ it('should return false', () => {
+ const {
+ selectionController,
+ } = setup({
+ keyExpr: 'id',
+ dataSource: [{ id: 1, value: 'test1' }, { id: 2, value: 'test2' }, { id: 3, value: 'test3' }],
+ selection: {
+ mode: 'multiple',
+ showCheckBoxesMode: 'onClick',
+ },
+ });
+
+ selectionController.selectCards([1]);
+ expect(selectionController.isCheckBoxesVisible.unreactive_get()).toBe(false);
+ });
+ });
+
+ describe('when selecting two cards', () => {
+ it('should return true', () => {
+ const {
+ selectionController,
+ } = setup({
+ keyExpr: 'id',
+ dataSource: [{ id: 1, value: 'test1' }, { id: 2, value: 'test2' }, { id: 3, value: 'test3' }],
+ selection: {
+ mode: 'multiple',
+ showCheckBoxesMode: 'onClick',
+ },
+ });
+
+ selectionController.selectCards([1, 2]);
+ expect(selectionController.isCheckBoxesVisible.unreactive_get()).toBe(true);
+ });
+ });
+
+ describe('when deselecting all cards', () => {
+ it('should return false', () => {
+ const {
+ selectionController,
+ } = setup({
+ keyExpr: 'id',
+ dataSource: [{ id: 1, value: 'test1' }, { id: 2, value: 'test2' }, { id: 3, value: 'test3' }],
+ selection: {
+ mode: 'multiple',
+ showCheckBoxesMode: 'onClick',
+ },
+ selectedCardKeys: [1, 2],
+ });
+
+ selectionController.deselectCards([1, 2]);
+ expect(selectionController.isCheckBoxesVisible.unreactive_get()).toBe(false);
+ });
+ });
+ });
+
+ describe('needToHiddenCheckBoxes', () => {
+ describe('when the selection mode is equal to \'multiple\' and the showCheckBoxesMode is equal to \'onClick\'', () => {
+ it('should return true', () => {
+ const {
+ selectionController,
+ } = setup({
+ keyExpr: 'id',
+ dataSource: [{ id: 1, value: 'test' }],
+ selection: {
+ mode: 'multiple',
+ showCheckBoxesMode: 'onClick',
+ },
+ });
+
+ expect(selectionController.needToHiddenCheckBoxes.unreactive_get()).toBe(true);
+ });
+ });
+
+ describe('when the selection mode is equal to \'multiple\' and the showCheckBoxesMode is equal to \'always\'', () => {
+ it('should return false', () => {
+ const {
+ selectionController,
+ } = setup({
+ keyExpr: 'id',
+ dataSource: [{ id: 1, value: 'test' }],
+ selection: {
+ mode: 'multiple',
+ showCheckBoxesMode: 'always',
+ },
+ });
+
+ expect(selectionController.needToHiddenCheckBoxes.unreactive_get()).toBe(false);
+ });
+ });
+ });
+
+ // Events
+
+ describe('onSelectionChanging', () => {
+ describe('when selecting a card', () => {
+ it('should be called', () => {
+ const selectionChangingMockFn = jest.fn();
+ const cardData = { id: 1, value: 'test' };
+ const {
+ selectionController,
+ } = setup({
+ keyExpr: 'id',
+ dataSource: [cardData],
+ selection: {
+ mode: 'multiple',
+ },
+ onSelectionChanging: selectionChangingMockFn,
+ });
+
+ selectionController.selectCards([1]);
+
+ expect(selectionChangingMockFn.mock.calls).toHaveLength(1);
+ expect(selectionChangingMockFn.mock.lastCall).toMatchObject([{
+ cancel: false,
+ currentDeselectedCardKeys: [],
+ currentSelectedCardKeys: [1],
+ isDeselectAll: false,
+ isSelectAll: false,
+ selectedCardKeys: [1],
+ selectedCardsData: [cardData],
+ }]);
+ });
+ });
+
+ describe('when deselecting a card', () => {
+ it('should be called', () => {
+ const selectionChangingMockFn = jest.fn();
+ const cardData = { id: 1, value: 'test' };
+ const {
+ selectionController,
+ } = setup({
+ keyExpr: 'id',
+ dataSource: [cardData],
+ selection: {
+ mode: 'multiple',
+ },
+ selectedCardKeys: [1],
+ onSelectionChanging: selectionChangingMockFn,
+ });
+
+ selectionController.deselectCards([1]);
+
+ expect(selectionChangingMockFn.mock.calls).toHaveLength(1);
+ expect(selectionChangingMockFn.mock.lastCall).toMatchObject([{
+ cancel: false,
+ currentDeselectedCardKeys: [1],
+ currentSelectedCardKeys: [],
+ isDeselectAll: false,
+ isSelectAll: false,
+ selectedCardKeys: [],
+ selectedCardsData: [],
+ }]);
+ });
+ });
+
+ describe('when selecting all cards', () => {
+ it('should be called', () => {
+ const selectionChangingMockFn = jest.fn();
+ const data = [{ id: 1, value: 'test1' }, { id: 2, value: 'test2' }];
+ const {
+ selectionController,
+ } = setup({
+ keyExpr: 'id',
+ dataSource: data,
+ selection: {
+ mode: 'multiple',
+ allowSelectAll: true,
+ },
+ onSelectionChanging: selectionChangingMockFn,
+ });
+
+ selectionController.selectAll();
+
+ expect(selectionChangingMockFn.mock.calls).toHaveLength(1);
+ expect(selectionChangingMockFn.mock.lastCall).toMatchObject([{
+ cancel: false,
+ currentDeselectedCardKeys: [],
+ currentSelectedCardKeys: [1, 2],
+ isDeselectAll: false,
+ isSelectAll: false,
+ selectedCardKeys: [1, 2],
+ selectedCardsData: data,
+ }]);
+ });
+ });
+
+ describe('when deselecting all cards', () => {
+ it('should be called', () => {
+ const selectionChangingMockFn = jest.fn();
+ const data = [{ id: 1, value: 'test1' }, { id: 2, value: 'test2' }];
+ const {
+ selectionController,
+ } = setup({
+ keyExpr: 'id',
+ dataSource: data,
+ selection: {
+ mode: 'multiple',
+ allowSelectAll: true,
+ },
+ selectedCardKeys: [1, 2],
+ onSelectionChanging: selectionChangingMockFn,
+ });
+
+ selectionController.deselectAll();
+
+ expect(selectionChangingMockFn.mock.calls).toHaveLength(1);
+ expect(selectionChangingMockFn.mock.lastCall).toMatchObject([{
+ cancel: false,
+ currentDeselectedCardKeys: [1, 2],
+ currentSelectedCardKeys: [],
+ isDeselectAll: false,
+ isSelectAll: false,
+ selectedCardKeys: [],
+ selectedCardsData: [],
+ }]);
+ });
+ });
+
+ describe('when a cancel arg is specified as true', () => {
+ it('should be called', () => {
+ const selectionChangingMockFn = jest.fn((e: any) => { e.cancel = true; });
+ const cardData = { id: 1, value: 'test' };
+ const {
+ selectionController,
+ } = setup({
+ keyExpr: 'id',
+ dataSource: [cardData],
+ selection: {
+ mode: 'multiple',
+ },
+ onSelectionChanging: selectionChangingMockFn,
+ });
+
+ selectionController.selectCards([1]);
+
+ expect(selectionChangingMockFn.mock.calls).toHaveLength(1);
+ expect(selectionChangingMockFn.mock.lastCall).toMatchObject([{
+ cancel: true,
+ currentDeselectedCardKeys: [],
+ currentSelectedCardKeys: [1],
+ isDeselectAll: false,
+ isSelectAll: false,
+ selectedCardKeys: [1],
+ selectedCardsData: [cardData],
+ }]);
+ expect(selectionController.getSelectedCardKeys()).toEqual([]);
+ });
+ });
+
+ describe('when a cancel arg is specified as Promise', () => {
+ it('should be called', () => {
+ const cancel = Promise.resolve(true);
+ const selectionChangingMockFn = jest.fn((e: any) => { e.cancel = cancel; });
+ const cardData = { id: 1, value: 'test' };
+ const {
+ selectionController,
+ } = setup({
+ keyExpr: 'id',
+ dataSource: [cardData],
+ selection: {
+ mode: 'multiple',
+ },
+ onSelectionChanging: selectionChangingMockFn,
+ });
+
+ selectionController.selectCards([1]);
+
+ expect(selectionChangingMockFn.mock.calls).toHaveLength(1);
+ expect(selectionChangingMockFn.mock.lastCall).toMatchObject([{
+ cancel,
+ currentDeselectedCardKeys: [],
+ currentSelectedCardKeys: [1],
+ isDeselectAll: false,
+ isSelectAll: false,
+ selectedCardKeys: [1],
+ selectedCardsData: [cardData],
+ }]);
+ expect(selectionController.getSelectedCardKeys()).toEqual([]);
+ });
+ });
+ });
+
+ describe('onSelectionChanged', () => {
+ describe('when selecting a card', () => {
+ it('should be called', () => {
+ const selectionChangedMockFn = jest.fn();
+ const cardData = { id: 1, value: 'test' };
+ const {
+ selectionController,
+ } = setup({
+ keyExpr: 'id',
+ dataSource: [cardData],
+ selection: {
+ mode: 'multiple',
+ },
+ onSelectionChanged: selectionChangedMockFn,
+ });
+
+ selectionController.selectCards([1]);
+
+ expect(selectionChangedMockFn.mock.calls).toHaveLength(1);
+ expect(selectionChangedMockFn.mock.lastCall).toMatchObject([{
+ currentDeselectedCardKeys: [],
+ currentSelectedCardKeys: [1],
+ isDeselectAll: false,
+ isSelectAll: false,
+ selectedCardKeys: [1],
+ selectedCardsData: [cardData],
+ }]);
+ });
+ });
+
+ describe('when deselecting a card', () => {
+ it('should be called', () => {
+ const selectionChangedMockFn = jest.fn();
+ const cardData = { id: 1, value: 'test' };
+ const {
+ selectionController,
+ } = setup({
+ keyExpr: 'id',
+ dataSource: [cardData],
+ selection: {
+ mode: 'multiple',
+ },
+ selectedCardKeys: [1],
+ onSelectionChanged: selectionChangedMockFn,
+ });
+
+ selectionController.deselectCards([1]);
+
+ expect(selectionChangedMockFn.mock.calls).toHaveLength(1);
+ expect(selectionChangedMockFn.mock.lastCall).toMatchObject([{
+ currentDeselectedCardKeys: [1],
+ currentSelectedCardKeys: [],
+ isDeselectAll: false,
+ isSelectAll: false,
+ selectedCardKeys: [],
+ selectedCardsData: [],
+ }]);
+ });
+ });
+
+ describe('when selecting all cards', () => {
+ it('should be called', () => {
+ const selectionChangedMockFn = jest.fn();
+ const data = [{ id: 1, value: 'test1' }, { id: 2, value: 'test2' }];
+ const {
+ selectionController,
+ } = setup({
+ keyExpr: 'id',
+ dataSource: data,
+ selection: {
+ mode: 'multiple',
+ allowSelectAll: true,
+ },
+ onSelectionChanged: selectionChangedMockFn,
+ });
+
+ selectionController.selectAll();
+
+ expect(selectionChangedMockFn.mock.calls).toHaveLength(1);
+ expect(selectionChangedMockFn.mock.lastCall).toMatchObject([{
+ currentDeselectedCardKeys: [],
+ currentSelectedCardKeys: [1, 2],
+ isDeselectAll: false,
+ isSelectAll: false,
+ selectedCardKeys: [1, 2],
+ selectedCardsData: data,
+ }]);
+ });
+ });
+
+ describe('when deselecting all cards', () => {
+ it('should be called', () => {
+ const selectionChangedMockFn = jest.fn();
+ const data = [{ id: 1, value: 'test1' }, { id: 2, value: 'test2' }];
+ const {
+ selectionController,
+ } = setup({
+ keyExpr: 'id',
+ dataSource: data,
+ selection: {
+ mode: 'multiple',
+ allowSelectAll: true,
+ },
+ selectedCardKeys: [1, 2],
+ onSelectionChanged: selectionChangedMockFn,
+ });
+
+ selectionController.deselectAll();
+
+ expect(selectionChangedMockFn.mock.calls).toHaveLength(1);
+ expect(selectionChangedMockFn.mock.lastCall).toMatchObject([{
+ currentDeselectedCardKeys: [1, 2],
+ currentSelectedCardKeys: [],
+ isDeselectAll: false,
+ isSelectAll: false,
+ selectedCardKeys: [],
+ selectedCardsData: [],
+ }]);
+ });
+ });
+ });
+});
diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/selection/controller.ts b/packages/devextreme/js/__internal/grids/new/grid_core/selection/controller.ts
new file mode 100644
index 000000000000..184cdc48643f
--- /dev/null
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/selection/controller.ts
@@ -0,0 +1,397 @@
+/* eslint-disable @typescript-eslint/no-unsafe-return */
+/* eslint-disable @typescript-eslint/explicit-function-return-type */
+/* eslint-disable spellcheck/spell-checker */
+import type { DeferredObj } from '@js/core/utils/deferred';
+import messageLocalization from '@js/localization/message';
+import type { SubsGets } from '@ts/core/reactive/index';
+import { computed, effect, state } from '@ts/core/reactive/index';
+import { DataController } from '@ts/grids/new/grid_core/data_controller/index';
+import { ShowCheckBoxesMode } from '@ts/grids/new/grid_core/selection/const';
+import Selection from '@ts/ui/selection/m_selection';
+
+import type { DataRow } from '../columns_controller/types';
+import type { Key } from '../data_controller/types';
+import { ItemsController } from '../items_controller/items_controller';
+import { OptionsController } from '../options_controller/options_controller';
+import { ToolbarController } from '../toolbar/controller';
+import { SelectionMode } from './const';
+import type {
+ SelectedCardKeys, SelectionEventInfo, SelectionOptions,
+} from './types';
+
+export class SelectionController {
+ public static dependencies = [
+ OptionsController,
+ DataController,
+ ItemsController,
+ ToolbarController,
+ ] as const;
+
+ private readonly selectedCardKeys = this.options.twoWay('selectedCardKeys');
+
+ private readonly selectionOption: SubsGets = this.options.oneWay('selection');
+
+ private readonly selectionHelper: SubsGets;
+
+ private readonly _isCheckBoxesRendered = state(false);
+
+ private readonly onSelectionChanging = this.options.action('onSelectionChanging');
+
+ private readonly onSelectionChanged = this.options.action('onSelectionChanged');
+
+ public readonly isCheckBoxesRendered = computed(
+ (selectionMode, showCheckBoxesMode, _isCheckBoxesRendered) => {
+ if (selectionMode === SelectionMode.Multiple) {
+ switch (showCheckBoxesMode) {
+ case ShowCheckBoxesMode.Always:
+ case ShowCheckBoxesMode.OnClick:
+ return true;
+ case ShowCheckBoxesMode.OnLongTap:
+ return _isCheckBoxesRendered;
+ default:
+ return false;
+ }
+ }
+
+ return false;
+ },
+ [
+ this.options.oneWay('selection.mode'),
+ this.options.oneWay('selection.showCheckBoxesMode'),
+ this._isCheckBoxesRendered,
+ ],
+ );
+
+ public readonly _isCheckBoxesVisible = state(false);
+
+ public readonly isCheckBoxesVisible = computed(
+ (selectionOption, _isCheckBoxesVisible) => {
+ const { mode, showCheckBoxesMode } = selectionOption;
+
+ if (mode === SelectionMode.Multiple) {
+ return showCheckBoxesMode !== ShowCheckBoxesMode.OnClick || _isCheckBoxesVisible;
+ }
+
+ return false;
+ },
+ [
+ this.selectionOption,
+ this._isCheckBoxesVisible,
+ ],
+ );
+
+ public readonly needToHiddenCheckBoxes = computed(
+ (isCheckBoxesVisible, selectionOption) => {
+ const { mode, showCheckBoxesMode } = selectionOption;
+
+ if (mode === SelectionMode.Multiple && showCheckBoxesMode === ShowCheckBoxesMode.OnClick) {
+ return !isCheckBoxesVisible;
+ }
+
+ return false;
+ },
+ [
+ this.isCheckBoxesVisible,
+ this.selectionOption,
+ ],
+ );
+
+ public readonly allowSelectOnClick = computed(
+ (selectionOption) => {
+ const { mode, showCheckBoxesMode } = selectionOption;
+
+ return mode !== SelectionMode.Multiple || showCheckBoxesMode !== ShowCheckBoxesMode.Always;
+ },
+ [this.selectionOption],
+ );
+
+ public readonly needToAddSelectionButtons = computed(
+ (selectionMode, allowSelectAll) => selectionMode === SelectionMode.Multiple && allowSelectAll,
+ [
+ this.options.oneWay('selection.mode'),
+ this.options.oneWay('selection.allowSelectAll'),
+ ],
+ );
+
+ constructor(
+ private readonly options: OptionsController,
+ private readonly dataController: DataController,
+ private readonly itemsController: ItemsController,
+ private readonly toolbarController: ToolbarController,
+ ) {
+ this.selectionHelper = computed(
+ (
+ dataSource,
+ selectionOption,
+ ) => {
+ if (selectionOption.mode === SelectionMode.None) {
+ return undefined;
+ }
+
+ const selectionConfig = this.getSelectionConfig(
+ dataSource,
+ selectionOption,
+ );
+
+ return new Selection(selectionConfig);
+ },
+ [
+ this.dataController.dataSource,
+ this.selectionOption,
+ ],
+ );
+
+ effect((selectedCardKeys, selectionOption) => {
+ if (selectionOption.mode !== SelectionMode.None) {
+ this.itemsController.setSelectionState(selectedCardKeys);
+
+ if (selectedCardKeys.length > 1) {
+ this._isCheckBoxesVisible.update(true);
+ } else if (selectedCardKeys.length === 0) {
+ this._isCheckBoxesVisible.update(false);
+ }
+ }
+ }, [this.selectedCardKeys, this.selectionOption]);
+
+ effect((isLoaded) => {
+ if (isLoaded) {
+ const selectedCardKeys = this.selectedCardKeys.unreactive_get();
+
+ this.selectCards(selectedCardKeys);
+ }
+ }, [this.dataController.isLoaded]);
+
+ effect((selectedCardKeys) => {
+ this.updateSelectionToolbarButtons(selectedCardKeys);
+ }, [this.selectedCardKeys, this.dataController.items]);
+ }
+
+ private getSelectionConfig(dataSource, selectionOption): object {
+ const selectedCardKeys = this.selectedCardKeys.unreactive_get();
+
+ return {
+ selectedKeys: selectedCardKeys,
+ mode: selectionOption.mode,
+ maxFilterLengthInRequest: selectionOption.maxFilterLengthInRequest,
+ ignoreDisabledItems: true,
+ key() {
+ return dataSource.key();
+ },
+ keyOf(item) {
+ return dataSource.store().keyOf(item);
+ },
+ dataFields() {
+ return dataSource.select();
+ },
+ load(options) {
+ return dataSource.store().load(options);
+ },
+ plainItems() {
+ return dataSource.items();
+ },
+ filter() {
+ // TODO Salimov: Need to take combined filter
+ return dataSource.filter();
+ },
+ totalCount: () => dataSource.totalCount(),
+ onSelectionChanging: this.selectionChanging.bind(this),
+ onSelectionChanged: this.selectionChanged.bind(this),
+ };
+ }
+
+ private getSelectionEventArgs(e): SelectionEventInfo {
+ return {
+ currentSelectedCardKeys: [...e.addedItemKeys],
+ currentDeselectedCardKeys: [...e.removedItemKeys],
+ selectedCardKeys: [...e.selectedItemKeys],
+ selectedCardsData: [...e.selectedItems],
+ isSelectAll: false,
+ isDeselectAll: false,
+ };
+ }
+
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ private selectionChanging(e: any): void {
+ if (e.addedItemKeys.length || e.removedItemKeys.length) {
+ const onSelectionChanging = this.onSelectionChanging.unreactive_get();
+ const eventArgs = {
+ ...this.getSelectionEventArgs(e),
+ cancel: false,
+ };
+
+ // @ts-expect-error
+ onSelectionChanging?.(eventArgs);
+ e.cancel = eventArgs.cancel;
+ }
+ }
+
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ private selectionChanged(e: any): void {
+ if (e.addedItemKeys.length || e.removedItemKeys.length) {
+ const onSelectionChanged = this.onSelectionChanged.unreactive_get();
+ const eventArgs = this.getSelectionEventArgs(e);
+
+ this.selectedCardKeys.update([...e.selectedItemKeys]);
+ // @ts-expect-error
+ onSelectionChanged?.(eventArgs);
+ }
+ }
+
+ private isOnePageSelectAll(): boolean {
+ const selectionOption = this.selectionOption.unreactive_get();
+
+ return selectionOption?.selectAllMode === 'page';
+ }
+
+ private isSelectAll(): boolean | undefined {
+ const selectionHelper = this.selectionHelper.unreactive_get();
+
+ return selectionHelper?.getSelectAllState(this.isOnePageSelectAll());
+ }
+
+ private updateSelectionToolbarButtons(
+ selectedCardKeys: SelectedCardKeys,
+ ) {
+ const isSelectAll = this.isSelectAll();
+ const isOnePageSelectAll = this.isOnePageSelectAll();
+
+ this.toolbarController.addDefaultItem({
+ name: 'selectAllButton',
+ widget: 'dxButton',
+ options: {
+ icon: 'selectall',
+ onClick: () => {
+ this.selectAll();
+ },
+ disabled: !!isSelectAll,
+ text: messageLocalization.format('dxCardView-selectAll'),
+ },
+ location: 'before',
+ locateInMenu: 'auto',
+ }, this.needToAddSelectionButtons);
+ this.toolbarController.addDefaultItem({
+ name: 'clearSelectionButton',
+ widget: 'dxButton',
+ options: {
+ icon: 'close',
+ onClick: () => {
+ this.deselectAll();
+ },
+ disabled: isOnePageSelectAll ? isSelectAll === false : selectedCardKeys.length === 0,
+ text: messageLocalization.format('dxCardView-clearSelection'),
+ },
+ location: 'before',
+ locateInMenu: 'auto',
+ }, this.needToAddSelectionButtons);
+ }
+
+ private getItemKeysByIndexes(indexes: number[]): Key[] {
+ const items = this.itemsController.items.unreactive_get();
+
+ return indexes
+ .map((index) => items[index]?.key)
+ .filter((key) => key !== undefined);
+ }
+
+ public changeCardSelection(
+ cardIndex: number,
+ options?: { control?: boolean; shift?: boolean },
+ ): void {
+ const selectionHelper = this.selectionHelper?.unreactive_get();
+ const isCheckBoxesVisible = this.isCheckBoxesVisible.unreactive_get();
+ const keys = options ?? {};
+
+ if (isCheckBoxesVisible) {
+ keys.control = isCheckBoxesVisible;
+ }
+
+ selectionHelper?.changeItemSelection(cardIndex, keys, false);
+ }
+
+ public selectCards(keys: Key[], preserve = false): DeferredObj | undefined {
+ const selectionHelper = this.selectionHelper?.unreactive_get();
+
+ return selectionHelper?.selectedItemKeys(keys, preserve);
+ }
+
+ public selectCardsByIndexes(indexes: number[]): DeferredObj | undefined {
+ const keys = this.getItemKeysByIndexes(indexes);
+
+ return this.selectCards(keys);
+ }
+
+ public deselectCards(keys: Key[]): DeferredObj | undefined {
+ const selectionHelper = this.selectionHelper?.unreactive_get();
+
+ return selectionHelper?.selectedItemKeys(keys, true, true);
+ }
+
+ public deselectCardsByIndexes(indexes: number[]): DeferredObj | undefined {
+ const keys = this.getItemKeysByIndexes(indexes);
+
+ return this.deselectCards(keys);
+ }
+
+ public isCardSelected(key: Key): boolean {
+ const selectedCardKeys = this.selectedCardKeys.unreactive_get();
+
+ return selectedCardKeys.includes(key);
+ }
+
+ public selectAll(): DeferredObj | undefined {
+ const selectionHelper = this.selectionHelper.unreactive_get();
+
+ return selectionHelper?.selectAll(this.isOnePageSelectAll());
+ }
+
+ public deselectAll(): DeferredObj | undefined {
+ const selectionHelper = this.selectionHelper.unreactive_get();
+
+ return selectionHelper?.deselectAll(this.isOnePageSelectAll());
+ }
+
+ public clearSelection(): DeferredObj | undefined {
+ const selectionHelper = this.selectionHelper.unreactive_get();
+
+ return selectionHelper?.clearSelection();
+ }
+
+ public getSelectedCards(): DataRow[] {
+ const selectedCardKey = this.getSelectedCardKeys();
+
+ return selectedCardKey
+ .map((key) => this.itemsController.getRowByKey(key))
+ .filter((item): item is DataRow => !!item);
+ }
+
+ public getSelectedCardKeys(): Key[] {
+ return this.selectedCardKeys.unreactive_get();
+ }
+
+ private toggleSelectionCheckBoxes(): void {
+ const isCheckBoxesRendered = this._isCheckBoxesRendered.unreactive_get();
+
+ this._isCheckBoxesRendered.update(!isCheckBoxesRendered);
+ }
+
+ public updateSelectionCheckBoxesVisible(value: boolean): void {
+ this._isCheckBoxesVisible.update(value);
+ }
+
+ public processLongTap(row: DataRow): void {
+ const { mode, showCheckBoxesMode } = this.selectionOption.unreactive_get();
+
+ if (mode !== SelectionMode.None) {
+ if (showCheckBoxesMode === ShowCheckBoxesMode.OnLongTap) {
+ this.toggleSelectionCheckBoxes();
+ } else {
+ if (showCheckBoxesMode === ShowCheckBoxesMode.OnClick) {
+ this._isCheckBoxesVisible.update(true);
+ }
+ if (showCheckBoxesMode !== ShowCheckBoxesMode.Always) {
+ this.changeCardSelection(row.index, { control: true });
+ }
+ }
+ }
+ }
+}
diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/selection/index.ts b/packages/devextreme/js/__internal/grids/new/grid_core/selection/index.ts
new file mode 100644
index 000000000000..afd0cbb3be7d
--- /dev/null
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/selection/index.ts
@@ -0,0 +1,3 @@
+export { SelectionController as Controller } from './controller';
+export { defaultOptions, type Options } from './options';
+export { PublicMethods } from './public_methods';
diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/selection/options.test.ts b/packages/devextreme/js/__internal/grids/new/grid_core/selection/options.test.ts
new file mode 100644
index 000000000000..2b763c0f86b7
--- /dev/null
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/selection/options.test.ts
@@ -0,0 +1,154 @@
+/* eslint-disable spellcheck/spell-checker */
+import { describe, expect, it } from '@jest/globals';
+
+import { ColumnsController } from '../columns_controller/columns_controller';
+import { DataController } from '../data_controller';
+import { FilterController } from '../filtering/filter_controller';
+import { ItemsController } from '../items_controller/items_controller';
+import type { Options } from '../options';
+import { OptionsControllerMock } from '../options_controller/options_controller.mock';
+import { SearchController } from '../search/controller';
+import { SortingController } from '../sorting_controller/sorting_controller';
+import { ToolbarController } from '../toolbar/controller';
+import { SelectionController } from './controller';
+
+const setup = (config: Options = {}) => {
+ const optionsController = new OptionsControllerMock({
+ selection: {
+ mode: 'single',
+ },
+ ...config,
+ });
+
+ const filterController = new FilterController(optionsController);
+ const columnsController = new ColumnsController(optionsController);
+ const sortingController = new SortingController(optionsController, columnsController);
+
+ const dataController = new DataController(optionsController, sortingController, filterController);
+
+ const searchController = new SearchController(optionsController);
+ const itemsController = new ItemsController(dataController, columnsController, searchController);
+ const toolbarController = new ToolbarController(optionsController);
+
+ const selectionController = new SelectionController(
+ optionsController,
+ dataController,
+ itemsController,
+ toolbarController,
+ );
+
+ return {
+ selectionController,
+ itemsController,
+ toolbarController,
+ };
+};
+
+describe('Options', () => {
+ describe('selectedCardKeys', () => {
+ describe('when given', () => {
+ it('should set the select state of the item', () => {
+ const {
+ itemsController,
+ } = setup({
+ keyExpr: 'id',
+ dataSource: [{ id: 1, value: 'test' }],
+ selectedCardKeys: [1],
+ });
+
+ expect(itemsController.items).toMatchSnapshot();
+ });
+ });
+ });
+
+ describe('selection', () => {
+ describe('mode', () => {
+ describe('when it is \'none\'', () => {
+ it('selection should not work', () => {
+ const {
+ itemsController,
+ selectionController,
+ } = setup({
+ keyExpr: 'id',
+ dataSource: [{ id: 1, value: 'test' }],
+ selection: {
+ mode: 'none',
+ },
+ });
+
+ selectionController.selectCards([1]);
+ expect(itemsController.items).toMatchSnapshot();
+ });
+ });
+ describe('when it is \'none\' and the selectedCardKeys is specified', () => {
+ it('selection should not apply', () => {
+ const {
+ itemsController,
+ } = setup({
+ keyExpr: 'id',
+ dataSource: [{ id: 1, value: 'test' }],
+ selectedCardKeys: [1],
+ selection: {
+ mode: 'none',
+ },
+ });
+
+ expect(itemsController.items).toMatchSnapshot();
+ });
+ });
+ });
+
+ describe('allowSelectAll', () => {
+ describe('when it is true and selection mode is \'multiple\'', () => {
+ it('selection should not work', () => {
+ const {
+ toolbarController,
+ } = setup({
+ keyExpr: 'id',
+ dataSource: [{ id: 1, value: 'test' }],
+ selection: {
+ mode: 'multiple',
+ allowSelectAll: true,
+ },
+ });
+
+ expect(toolbarController.items.unreactive_get()).toMatchSnapshot();
+ });
+ });
+
+ describe('when it is false and selection mode is \'multiple\'', () => {
+ it('selection should not work', () => {
+ const {
+ toolbarController,
+ } = setup({
+ keyExpr: 'id',
+ dataSource: [{ id: 1, value: 'test' }],
+ selection: {
+ mode: 'multiple',
+ allowSelectAll: false,
+ },
+ });
+
+ expect(toolbarController.items.unreactive_get()).toMatchSnapshot();
+ });
+ });
+
+ describe('when it is true and selection mode isn\'t \'multiple\'', () => {
+ it('selection should not work', () => {
+ const {
+ toolbarController,
+ } = setup({
+ keyExpr: 'id',
+ dataSource: [{ id: 1, value: 'test' }],
+ selection: {
+ mode: 'single',
+ allowSelectAll: true,
+ },
+ });
+
+ expect(toolbarController.items.unreactive_get()).toMatchSnapshot();
+ });
+ });
+ });
+ });
+});
diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/selection/options.ts b/packages/devextreme/js/__internal/grids/new/grid_core/selection/options.ts
new file mode 100644
index 000000000000..f8378f15251e
--- /dev/null
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/selection/options.ts
@@ -0,0 +1,23 @@
+import type {
+ SelectedCardKeys,
+ SelectionChangedEvent,
+ SelectionChangingEvent,
+ SelectionOptions,
+} from './types';
+
+export interface Options {
+ selectedCardKeys?: SelectedCardKeys;
+ selection?: SelectionOptions;
+ onSelectionChanging?: ((e: SelectionChangingEvent) => void);
+ onSelectionChanged?: ((e: SelectionChangedEvent) => void);
+}
+
+export const defaultOptions: Options = {
+ selectedCardKeys: [],
+ selection: {
+ mode: 'none',
+ showCheckBoxesMode: 'always',
+ allowSelectAll: true,
+ selectAllMode: 'allPages',
+ },
+};
diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/selection/public_methods.ts b/packages/devextreme/js/__internal/grids/new/grid_core/selection/public_methods.ts
new file mode 100644
index 000000000000..8d1ac090b53f
--- /dev/null
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/selection/public_methods.ts
@@ -0,0 +1,52 @@
+/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
+/* eslint-disable @typescript-eslint/explicit-function-return-type */
+import type { DeferredObj } from '@js/core/utils/deferred';
+
+import type { DataRow } from '../columns_controller/types';
+import type { Key } from '../data_controller/types';
+import type { Constructor } from '../types';
+import type { GridCoreNewBase } from '../widget';
+
+export function PublicMethods>(GridCore: TBase) {
+ return class GridCoreWithSelectionController extends GridCore {
+ public isCardSelected(key: Key): boolean {
+ return this.selectionController.isCardSelected(key);
+ }
+
+ public getSelectedCardKeys(): Key[] {
+ return this.selectionController.getSelectedCardKeys();
+ }
+
+ public getSelectedCards(): DataRow[] {
+ return this.selectionController.getSelectedCards();
+ }
+
+ public selectCards(keys: Key[], preserve = false): DeferredObj | undefined {
+ return this.selectionController.selectCards(keys, preserve);
+ }
+
+ public deselectCards(keys: Key[]): DeferredObj | undefined {
+ return this.selectionController.deselectCards(keys);
+ }
+
+ public selectCardsByIndexes(indexes: number[]): DeferredObj | undefined {
+ return this.selectionController.selectCardsByIndexes(indexes);
+ }
+
+ public deselectCardsByIndexes(indexes: number[]): DeferredObj | undefined {
+ return this.selectionController.deselectCardsByIndexes(indexes);
+ }
+
+ public selectAll(): DeferredObj | undefined {
+ return this.selectionController.selectAll();
+ }
+
+ public deselectAll(): DeferredObj | undefined {
+ return this.selectionController.deselectAll();
+ }
+
+ public clearSelection(): void {
+ this.selectionController.clearSelection();
+ }
+ };
+}
diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/selection/types.ts b/packages/devextreme/js/__internal/grids/new/grid_core/selection/types.ts
new file mode 100644
index 000000000000..5b36648c5e79
--- /dev/null
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/selection/types.ts
@@ -0,0 +1,42 @@
+import type { SelectAllMode, SingleMultipleOrNone } from '@js/common';
+import type { EventInfo } from '@js/common/core/events';
+import type { SelectionColumnDisplayMode } from '@js/common/grids';
+import type dxCardView from '@js/ui/card_view';
+
+import type { Key } from '../data_controller/types';
+
+export type SelectedCardKeys = Key[];
+
+export interface SelectionEventInfo {
+ readonly currentSelectedCardKeys: TKey[];
+
+ readonly currentDeselectedCardKeys: TKey[];
+
+ readonly selectedCardKeys: TKey[];
+
+ readonly selectedCardsData: TCardData[];
+
+ readonly isSelectAll: boolean;
+
+ readonly isDeselectAll: boolean;
+}
+
+export type SelectionChangingEvent =
+ EventInfo & SelectionEventInfo & {
+ cancel: boolean | PromiseLike | PromiseLike;
+ };
+
+export type SelectionChangedEvent =
+ EventInfo & SelectionEventInfo;
+
+export type { SelectionColumnDisplayMode as ShowCheckBoxesMode };
+
+export interface SelectionOptions {
+ mode: SingleMultipleOrNone;
+
+ showCheckBoxesMode?: SelectionColumnDisplayMode;
+
+ allowSelectAll?: boolean;
+
+ selectAllMode?: SelectAllMode;
+}
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 ff415c5fce5a..7d3fa0f48a11 100644
--- a/packages/devextreme/js/__internal/grids/new/grid_core/widget.ts
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/widget.ts
@@ -24,6 +24,7 @@ import { MainView } from './main_view';
import { defaultOptions, defaultOptionsRules, type Options } from './options';
import { PagerView } from './pager/view';
import { SearchController } from './search/controller';
+import * as SelectionControllerModule from './selection/index';
import * as SortingControllerModule from './sorting_controller/index';
import type { SortingController } from './sorting_controller/sorting_controller';
import { ToolbarController } from './toolbar/controller';
@@ -45,6 +46,8 @@ export class GridCoreNewBase<
protected sortingController!: SortingController;
+ protected selectionController!: SelectionControllerModule.Controller;
+
private pagerView!: PagerView;
private toolbarController!: ToolbarController;
@@ -67,6 +70,7 @@ export class GridCoreNewBase<
this.diContext.register(DataControllerModule.CompatibilityDataController);
this.diContext.register(ItemsController);
this.diContext.register(ColumnsControllerModule.ColumnsController);
+ this.diContext.register(SelectionControllerModule.Controller);
this.diContext.register(ColumnsControllerModule.CompatibilityColumnsController);
this.diContext.register(SortingControllerModule.SortingController);
this.diContext.register(ToolbarController);
@@ -94,6 +98,7 @@ export class GridCoreNewBase<
this.dataController = this.diContext.get(DataControllerModule.DataController);
this.columnsController = this.diContext.get(ColumnsControllerModule.ColumnsController);
this.sortingController = this.diContext.get(SortingControllerModule.SortingController);
+ this.selectionController = this.diContext.get(SelectionControllerModule.Controller);
this.itemsController = this.diContext.get(ItemsController);
this.toolbarController = this.diContext.get(ToolbarController);
this.toolbarView = this.diContext.get(ToolbarView);
@@ -162,7 +167,9 @@ export class GridCoreNew extends ColumnsControllerModule.PublicMethods(
DataControllerModule.PublicMethods(
SortingControllerModule.PublicMethods(
FilterControllerModule.PublicMethods(
- GridCoreNewBase,
+ SelectionControllerModule.PublicMethods(
+ GridCoreNewBase,
+ ),
),
),
),
diff --git a/packages/devextreme/js/localization/messages/ar.json b/packages/devextreme/js/localization/messages/ar.json
index 1b8f770c4a06..870f54c9af89 100644
--- a/packages/devextreme/js/localization/messages/ar.json
+++ b/packages/devextreme/js/localization/messages/ar.json
@@ -351,6 +351,9 @@
"dxCalendar-selectedDateRangeCount": "There are {0} selected date ranges",
"dxCalendar-readOnlyLabel": "Read-only calendar",
+ "dxCardView-selectAll": "Select All",
+ "dxCardView-clearSelection": "Clear selection",
+
"dxAvatar-defaultImageAlt": "Avatar",
"dxChat-elementAriaLabel": "Chat",
diff --git a/packages/devextreme/js/localization/messages/bg.json b/packages/devextreme/js/localization/messages/bg.json
index 805e4603b231..620bbc0162ea 100644
--- a/packages/devextreme/js/localization/messages/bg.json
+++ b/packages/devextreme/js/localization/messages/bg.json
@@ -351,6 +351,9 @@
"dxCalendar-selectedDateRangeCount": "There are {0} selected date ranges",
"dxCalendar-readOnlyLabel": "Read-only calendar",
+ "dxCardView-selectAll": "Select All",
+ "dxCardView-clearSelection": "Clear selection",
+
"dxAvatar-defaultImageAlt": "Avatar",
"dxChat-elementAriaLabel": "Chat",
diff --git a/packages/devextreme/js/localization/messages/ca.json b/packages/devextreme/js/localization/messages/ca.json
index 1d0473c18f45..dcfaec2a937f 100644
--- a/packages/devextreme/js/localization/messages/ca.json
+++ b/packages/devextreme/js/localization/messages/ca.json
@@ -351,6 +351,9 @@
"dxCalendar-selectedDateRangeCount": "There are {0} selected date ranges",
"dxCalendar-readOnlyLabel": "Read-only calendar",
+ "dxCardView-selectAll": "Select All",
+ "dxCardView-clearSelection": "Clear selection",
+
"dxAvatar-defaultImageAlt": "Avatar",
"dxChat-elementAriaLabel": "Chat",
diff --git a/packages/devextreme/js/localization/messages/cs.json b/packages/devextreme/js/localization/messages/cs.json
index 1be21868564f..b31a21b08354 100644
--- a/packages/devextreme/js/localization/messages/cs.json
+++ b/packages/devextreme/js/localization/messages/cs.json
@@ -351,6 +351,9 @@
"dxCalendar-selectedDateRangeCount": "There are {0} selected date ranges",
"dxCalendar-readOnlyLabel": "Read-only calendar",
+ "dxCardView-selectAll": "Select All",
+ "dxCardView-clearSelection": "Clear selection",
+
"dxAvatar-defaultImageAlt": "Avatar",
"dxChat-elementAriaLabel": "Chat",
diff --git a/packages/devextreme/js/localization/messages/da.json b/packages/devextreme/js/localization/messages/da.json
index b8e0734fddbc..aa7aeb170eea 100644
--- a/packages/devextreme/js/localization/messages/da.json
+++ b/packages/devextreme/js/localization/messages/da.json
@@ -351,6 +351,9 @@
"dxCalendar-selectedDateRangeCount": "Der er {0} valgte datointervaller",
"dxCalendar-readOnlyLabel": "Skrivebeskyttet kalender",
+ "dxCardView-selectAll": "Select All",
+ "dxCardView-clearSelection": "Clear selection",
+
"dxAvatar-defaultImageAlt": "Avatar",
"dxChat-elementAriaLabel": "Snak",
diff --git a/packages/devextreme/js/localization/messages/de.json b/packages/devextreme/js/localization/messages/de.json
index 3bd8df5d3642..6feb67ae7122 100644
--- a/packages/devextreme/js/localization/messages/de.json
+++ b/packages/devextreme/js/localization/messages/de.json
@@ -351,6 +351,9 @@
"dxCalendar-selectedDateRangeCount": "There are {0} selected date ranges",
"dxCalendar-readOnlyLabel": "Read-only calendar",
+ "dxCardView-selectAll": "Select All",
+ "dxCardView-clearSelection": "Clear selection",
+
"dxAvatar-defaultImageAlt": "Avatar",
"dxChat-elementAriaLabel": "Chat",
diff --git a/packages/devextreme/js/localization/messages/el.json b/packages/devextreme/js/localization/messages/el.json
index 0efc0cf8911b..9a1b4bbae1ed 100644
--- a/packages/devextreme/js/localization/messages/el.json
+++ b/packages/devextreme/js/localization/messages/el.json
@@ -351,6 +351,9 @@
"dxCalendar-selectedDateRangeCount": "There are {0} selected date ranges",
"dxCalendar-readOnlyLabel": "Read-only calendar",
+ "dxCardView-selectAll": "Select All",
+ "dxCardView-clearSelection": "Clear selection",
+
"dxAvatar-defaultImageAlt": "Avatar",
"dxChat-elementAriaLabel": "Chat",
diff --git a/packages/devextreme/js/localization/messages/en.json b/packages/devextreme/js/localization/messages/en.json
index 5b3fe8808bcf..41dd3b1098e8 100644
--- a/packages/devextreme/js/localization/messages/en.json
+++ b/packages/devextreme/js/localization/messages/en.json
@@ -351,6 +351,9 @@
"dxCalendar-selectedDateRangeCount": "There are {0} selected date ranges",
"dxCalendar-readOnlyLabel": "Read-only calendar",
+ "dxCardView-selectAll": "Select All",
+ "dxCardView-clearSelection": "Clear selection",
+
"dxAvatar-defaultImageAlt": "Avatar",
"dxChat-elementAriaLabel": "Chat",
diff --git a/packages/devextreme/js/localization/messages/es.json b/packages/devextreme/js/localization/messages/es.json
index ff2fe9750a2e..2e74c9eff049 100644
--- a/packages/devextreme/js/localization/messages/es.json
+++ b/packages/devextreme/js/localization/messages/es.json
@@ -351,6 +351,9 @@
"dxCalendar-selectedDateRangeCount": "There are {0} selected date ranges",
"dxCalendar-readOnlyLabel": "Read-only calendar",
+ "dxCardView-selectAll": "Select All",
+ "dxCardView-clearSelection": "Clear selection",
+
"dxAvatar-defaultImageAlt": "Avatar",
"dxChat-elementAriaLabel": "Chat",
diff --git a/packages/devextreme/js/localization/messages/fa.json b/packages/devextreme/js/localization/messages/fa.json
index 40a5dad9a2cb..cc0e477a513c 100644
--- a/packages/devextreme/js/localization/messages/fa.json
+++ b/packages/devextreme/js/localization/messages/fa.json
@@ -351,6 +351,9 @@
"dxCalendar-selectedDateRangeCount": "There are {0} selected date ranges",
"dxCalendar-readOnlyLabel": "Read-only calendar",
+ "dxCardView-selectAll": "Select All",
+ "dxCardView-clearSelection": "Clear selection",
+
"dxAvatar-defaultImageAlt": "Avatar",
"dxChat-elementAriaLabel": "Chat",
diff --git a/packages/devextreme/js/localization/messages/fi.json b/packages/devextreme/js/localization/messages/fi.json
index 9162df88b756..13364e955684 100644
--- a/packages/devextreme/js/localization/messages/fi.json
+++ b/packages/devextreme/js/localization/messages/fi.json
@@ -351,6 +351,9 @@
"dxCalendar-selectedDateRangeCount": "There are {0} selected date ranges",
"dxCalendar-readOnlyLabel": "Read-only calendar",
+ "dxCardView-selectAll": "Select All",
+ "dxCardView-clearSelection": "Clear selection",
+
"dxAvatar-defaultImageAlt": "Avatar",
"dxChat-elementAriaLabel": "Chat",
diff --git a/packages/devextreme/js/localization/messages/fr.json b/packages/devextreme/js/localization/messages/fr.json
index 85e32f9f4242..aaa00d91f56c 100644
--- a/packages/devextreme/js/localization/messages/fr.json
+++ b/packages/devextreme/js/localization/messages/fr.json
@@ -351,6 +351,9 @@
"dxCalendar-selectedDateRangeCount": "There are {0} selected date ranges",
"dxCalendar-readOnlyLabel": "Read-only calendar",
+ "dxCardView-selectAll": "Select All",
+ "dxCardView-clearSelection": "Clear selection",
+
"dxAvatar-defaultImageAlt": "Avatar",
"dxChat-elementAriaLabel": "Chat",
diff --git a/packages/devextreme/js/localization/messages/hu.json b/packages/devextreme/js/localization/messages/hu.json
index 8c4bb9fb456d..da7ca2f1a25b 100644
--- a/packages/devextreme/js/localization/messages/hu.json
+++ b/packages/devextreme/js/localization/messages/hu.json
@@ -351,6 +351,9 @@
"dxCalendar-selectedDateRangeCount": "There are {0} selected date ranges",
"dxCalendar-readOnlyLabel": "Read-only calendar",
+ "dxCardView-selectAll": "Select All",
+ "dxCardView-clearSelection": "Clear selection",
+
"dxAvatar-defaultImageAlt": "Avatar",
"dxChat-elementAriaLabel": "Chat",
diff --git a/packages/devextreme/js/localization/messages/it.json b/packages/devextreme/js/localization/messages/it.json
index 8818c03856ea..d71477c9fb79 100644
--- a/packages/devextreme/js/localization/messages/it.json
+++ b/packages/devextreme/js/localization/messages/it.json
@@ -351,6 +351,9 @@
"dxCalendar-selectedDateRangeCount": "There are {0} selected date ranges",
"dxCalendar-readOnlyLabel": "Read-only calendar",
+ "dxCardView-selectAll": "Select All",
+ "dxCardView-clearSelection": "Clear selection",
+
"dxAvatar-defaultImageAlt": "Avatar",
"dxChat-elementAriaLabel": "Chat",
diff --git a/packages/devextreme/js/localization/messages/ja.json b/packages/devextreme/js/localization/messages/ja.json
index 413ecc36836b..b53ae4e1978d 100644
--- a/packages/devextreme/js/localization/messages/ja.json
+++ b/packages/devextreme/js/localization/messages/ja.json
@@ -351,6 +351,9 @@
"dxCalendar-selectedDateRangeCount": "There are {0} selected date ranges",
"dxCalendar-readOnlyLabel": "Read-only calendar",
+ "dxCardView-selectAll": "Select All",
+ "dxCardView-clearSelection": "Clear selection",
+
"dxAvatar-defaultImageAlt": "Avatar",
"dxChat-elementAriaLabel": "Chat",
diff --git a/packages/devextreme/js/localization/messages/lt.json b/packages/devextreme/js/localization/messages/lt.json
index 2b8fd5893edf..c25359d0fe7a 100644
--- a/packages/devextreme/js/localization/messages/lt.json
+++ b/packages/devextreme/js/localization/messages/lt.json
@@ -351,6 +351,9 @@
"dxCalendar-selectedDateRangeCount": "There are {0} selected date ranges",
"dxCalendar-readOnlyLabel": "Read-only calendar",
+ "dxCardView-selectAll": "Select All",
+ "dxCardView-clearSelection": "Clear selection",
+
"dxAvatar-defaultImageAlt": "Avatar",
"dxChat-elementAriaLabel": "Chat",
diff --git a/packages/devextreme/js/localization/messages/lv.json b/packages/devextreme/js/localization/messages/lv.json
index aa7e2f62eef8..a4b76c2c8903 100644
--- a/packages/devextreme/js/localization/messages/lv.json
+++ b/packages/devextreme/js/localization/messages/lv.json
@@ -351,6 +351,9 @@
"dxCalendar-selectedDateRangeCount": "There are {0} selected date ranges",
"dxCalendar-readOnlyLabel": "Read-only calendar",
+ "dxCardView-selectAll": "Select All",
+ "dxCardView-clearSelection": "Clear selection",
+
"dxAvatar-defaultImageAlt": "Avatar",
"dxChat-elementAriaLabel": "Chat",
diff --git a/packages/devextreme/js/localization/messages/nb.json b/packages/devextreme/js/localization/messages/nb.json
index f863253d9035..7148c0d989b2 100644
--- a/packages/devextreme/js/localization/messages/nb.json
+++ b/packages/devextreme/js/localization/messages/nb.json
@@ -351,6 +351,9 @@
"dxCalendar-selectedDateRangeCount": "There are {0} selected date ranges",
"dxCalendar-readOnlyLabel": "Read-only calendar",
+ "dxCardView-selectAll": "Select All",
+ "dxCardView-clearSelection": "Clear selection",
+
"dxAvatar-defaultImageAlt": "Avatar",
"dxChat-elementAriaLabel": "Chat",
diff --git a/packages/devextreme/js/localization/messages/nl.json b/packages/devextreme/js/localization/messages/nl.json
index bc3575d934a0..14f6027245fd 100644
--- a/packages/devextreme/js/localization/messages/nl.json
+++ b/packages/devextreme/js/localization/messages/nl.json
@@ -351,6 +351,9 @@
"dxCalendar-selectedDateRangeCount": "There are {0} selected date ranges",
"dxCalendar-readOnlyLabel": "Read-only calendar",
+ "dxCardView-selectAll": "Select All",
+ "dxCardView-clearSelection": "Clear selection",
+
"dxAvatar-defaultImageAlt": "Avatar",
"dxChat-elementAriaLabel": "Chat",
diff --git a/packages/devextreme/js/localization/messages/pl.json b/packages/devextreme/js/localization/messages/pl.json
index 0d08f56723ac..8c8757d21101 100644
--- a/packages/devextreme/js/localization/messages/pl.json
+++ b/packages/devextreme/js/localization/messages/pl.json
@@ -351,6 +351,9 @@
"dxCalendar-selectedDateRangeCount": "There are {0} selected date ranges",
"dxCalendar-readOnlyLabel": "Read-only calendar",
+ "dxCardView-selectAll": "Select All",
+ "dxCardView-clearSelection": "Clear selection",
+
"dxAvatar-defaultImageAlt": "Avatar",
"dxChat-elementAriaLabel": "Chat",
diff --git a/packages/devextreme/js/localization/messages/pt.json b/packages/devextreme/js/localization/messages/pt.json
index b7bf11b8edfc..28dd201482ac 100644
--- a/packages/devextreme/js/localization/messages/pt.json
+++ b/packages/devextreme/js/localization/messages/pt.json
@@ -351,6 +351,9 @@
"dxCalendar-selectedDateRangeCount": "Há {0} intervalos de datas selecionados",
"dxCalendar-readOnlyLabel": "Calendário somente leitura",
+ "dxCardView-selectAll": "Select All",
+ "dxCardView-clearSelection": "Clear selection",
+
"dxAvatar-defaultImageAlt": "Avatar",
"dxChat-elementAriaLabel": "Chat",
diff --git a/packages/devextreme/js/localization/messages/ro.json b/packages/devextreme/js/localization/messages/ro.json
index 1f6a4037fff2..c61a0ce4d261 100644
--- a/packages/devextreme/js/localization/messages/ro.json
+++ b/packages/devextreme/js/localization/messages/ro.json
@@ -351,6 +351,9 @@
"dxCalendar-selectedDateRangeCount": "There are {0} selected date ranges",
"dxCalendar-readOnlyLabel": "Read-only calendar",
+ "dxCardView-selectAll": "Select All",
+ "dxCardView-clearSelection": "Clear selection",
+
"dxAvatar-defaultImageAlt": "Avatar",
"dxChat-elementAriaLabel": "Chat",
diff --git a/packages/devextreme/js/localization/messages/ru.json b/packages/devextreme/js/localization/messages/ru.json
index 42d766898a62..900a1b45c546 100644
--- a/packages/devextreme/js/localization/messages/ru.json
+++ b/packages/devextreme/js/localization/messages/ru.json
@@ -351,6 +351,9 @@
"dxCalendar-selectedDateRangeCount": "There are {0} selected date ranges",
"dxCalendar-readOnlyLabel": "Read-only calendar",
+ "dxCardView-selectAll": "Select All",
+ "dxCardView-clearSelection": "Clear selection",
+
"dxAvatar-defaultImageAlt": "Avatar",
"dxChat-elementAriaLabel": "Chat",
diff --git a/packages/devextreme/js/localization/messages/sl.json b/packages/devextreme/js/localization/messages/sl.json
index 738bd79979b6..d6468b884399 100644
--- a/packages/devextreme/js/localization/messages/sl.json
+++ b/packages/devextreme/js/localization/messages/sl.json
@@ -351,6 +351,9 @@
"dxCalendar-selectedDateRangeCount": "There are {0} selected date ranges",
"dxCalendar-readOnlyLabel": "Read-only calendar",
+ "dxCardView-selectAll": "Select All",
+ "dxCardView-clearSelection": "Clear selection",
+
"dxAvatar-defaultImageAlt": "Avatar",
"dxChat-elementAriaLabel": "Chat",
diff --git a/packages/devextreme/js/localization/messages/sv.json b/packages/devextreme/js/localization/messages/sv.json
index 931610488846..be637b61841b 100644
--- a/packages/devextreme/js/localization/messages/sv.json
+++ b/packages/devextreme/js/localization/messages/sv.json
@@ -351,6 +351,9 @@
"dxCalendar-selectedDateRangeCount": "There are {0} selected date ranges",
"dxCalendar-readOnlyLabel": "Read-only calendar",
+ "dxCardView-selectAll": "Select All",
+ "dxCardView-clearSelection": "Clear selection",
+
"dxAvatar-defaultImageAlt": "Avatar",
"dxChat-elementAriaLabel": "Chat",
diff --git a/packages/devextreme/js/localization/messages/tr.json b/packages/devextreme/js/localization/messages/tr.json
index 8773c91a180b..91412065cc59 100644
--- a/packages/devextreme/js/localization/messages/tr.json
+++ b/packages/devextreme/js/localization/messages/tr.json
@@ -351,6 +351,9 @@
"dxCalendar-selectedDateRangeCount": "There are {0} selected date ranges",
"dxCalendar-readOnlyLabel": "Read-only calendar",
+ "dxCardView-selectAll": "Select All",
+ "dxCardView-clearSelection": "Clear selection",
+
"dxAvatar-defaultImageAlt": "Avatar",
"dxChat-elementAriaLabel": "Chat",
diff --git a/packages/devextreme/js/localization/messages/uk.json b/packages/devextreme/js/localization/messages/uk.json
index 38ebce74f552..66ed94581ee1 100644
--- a/packages/devextreme/js/localization/messages/uk.json
+++ b/packages/devextreme/js/localization/messages/uk.json
@@ -351,6 +351,9 @@
"dxCalendar-selectedDateRangeCount": "There are {0} selected date ranges",
"dxCalendar-readOnlyLabel": "Read-only calendar",
+ "dxCardView-selectAll": "Select All",
+ "dxCardView-clearSelection": "Clear selection",
+
"dxAvatar-defaultImageAlt": "Avatar",
"dxChat-elementAriaLabel": "Chat",
diff --git a/packages/devextreme/js/localization/messages/vi.json b/packages/devextreme/js/localization/messages/vi.json
index 5cbed5bdc18e..dcfc96c3a20d 100644
--- a/packages/devextreme/js/localization/messages/vi.json
+++ b/packages/devextreme/js/localization/messages/vi.json
@@ -351,6 +351,9 @@
"dxCalendar-selectedDateRangeCount": "There are {0} selected date ranges",
"dxCalendar-readOnlyLabel": "Read-only calendar",
+ "dxCardView-selectAll": "Select All",
+ "dxCardView-clearSelection": "Clear selection",
+
"dxAvatar-defaultImageAlt": "Avatar",
"dxChat-elementAriaLabel": "Chat",
diff --git a/packages/devextreme/js/localization/messages/zh-tw.json b/packages/devextreme/js/localization/messages/zh-tw.json
index 6f77ed41eba6..2c2650fe92e7 100644
--- a/packages/devextreme/js/localization/messages/zh-tw.json
+++ b/packages/devextreme/js/localization/messages/zh-tw.json
@@ -351,6 +351,9 @@
"dxCalendar-selectedDateRangeCount": "There are {0} selected date ranges",
"dxCalendar-readOnlyLabel": "Read-only calendar",
+ "dxCardView-selectAll": "Select All",
+ "dxCardView-clearSelection": "Clear selection",
+
"dxAvatar-defaultImageAlt": "Avatar",
"dxChat-elementAriaLabel": "Chat",
diff --git a/packages/devextreme/js/localization/messages/zh.json b/packages/devextreme/js/localization/messages/zh.json
index dcff92620e93..506948fe1c2d 100644
--- a/packages/devextreme/js/localization/messages/zh.json
+++ b/packages/devextreme/js/localization/messages/zh.json
@@ -351,6 +351,9 @@
"dxCalendar-selectedDateRangeCount": "There are {0} selected date ranges",
"dxCalendar-readOnlyLabel": "Read-only calendar",
+ "dxCardView-selectAll": "Select All",
+ "dxCardView-clearSelection": "Clear selection",
+
"dxAvatar-defaultImageAlt": "Avatar",
"dxChat-elementAriaLabel": "Chat",