diff --git a/packages/devextreme/js/__internal/grids/grid_core/header_panel/m_header_panel.ts b/packages/devextreme/js/__internal/grids/grid_core/header_panel/m_header_panel.ts
index c4682b9c663d..57b5dd0307ec 100644
--- a/packages/devextreme/js/__internal/grids/grid_core/header_panel/m_header_panel.ts
+++ b/packages/devextreme/js/__internal/grids/grid_core/header_panel/m_header_panel.ts
@@ -2,12 +2,12 @@
import messageLocalization from '@js/common/core/localization/message';
import $ from '@js/core/renderer';
import { getPathParts } from '@js/core/utils/data';
-import { extend } from '@js/core/utils/extend';
-import { isDefined, isString } from '@js/core/utils/type';
+import { isDefined } from '@js/core/utils/type';
import type { Properties as ToolbarProperties } from '@js/ui/toolbar';
import Toolbar from '@js/ui/toolbar';
import type { EditingController } from '@ts/grids/grid_core/editing/m_editing';
import type { HeaderFilterController } from '@ts/grids/grid_core/header_filter/m_header_filter';
+import { normalizeToolbarItems } from '@ts/grids/new/grid_core/toolbar/utils';
import type { ModuleType } from '../m_types';
import { ColumnsView } from '../views/m_columns_view';
@@ -72,7 +72,11 @@ export class HeaderPanel extends ColumnsView {
};
const userItems = userToolbarOptions?.items;
- options.toolbarOptions.items = this._normalizeToolbarItems(options.toolbarOptions.items, userItems);
+ options.toolbarOptions.items = normalizeToolbarItems(
+ options.toolbarOptions.items,
+ userItems,
+ DEFAULT_TOOLBAR_ITEM_NAMES,
+ );
this.executeAction('onToolbarPreparing', options);
@@ -84,51 +88,6 @@ export class HeaderPanel extends ColumnsView {
return options.toolbarOptions;
}
- private _normalizeToolbarItems(defaultItems, userItems) {
- defaultItems.forEach((button) => {
- if (!DEFAULT_TOOLBAR_ITEM_NAMES.includes(button.name)) {
- throw new Error(`Default toolbar item '${button.name}' is not added to DEFAULT_TOOLBAR_ITEM_NAMES`);
- }
- });
-
- const defaultProps = {
- location: 'after',
- };
-
- const isArray = Array.isArray(userItems);
-
- if (!isDefined(userItems)) {
- return defaultItems;
- }
-
- if (!isArray) {
- userItems = [userItems];
- }
-
- const defaultButtonsByNames = {};
- defaultItems.forEach((button) => {
- defaultButtonsByNames[button.name] = button;
- });
-
- const normalizedItems = userItems.map((button) => {
- if (isString(button)) {
- button = { name: button };
- }
-
- if (isDefined(button.name)) {
- if (isDefined(defaultButtonsByNames[button.name])) {
- button = extend(true, {}, defaultButtonsByNames[button.name], button);
- } else if (DEFAULT_TOOLBAR_ITEM_NAMES.includes(button.name)) {
- button = { ...button, visible: false };
- }
- }
-
- return extend(true, {}, defaultProps, button);
- });
-
- return isArray ? normalizedItems : normalizedItems[0];
- }
-
protected _renderCore() {
if (!this._toolbar) {
const $headerPanel = this.element();
@@ -217,7 +176,11 @@ export class HeaderPanel extends ColumnsView {
this._invalidate();
} else if (parts.length === 3) {
// `toolbar.items[i]` case
- const normalizedItem = this._normalizeToolbarItems(this._getToolbarItems(), args.value);
+ const normalizedItem = normalizeToolbarItems(
+ this._getToolbarItems(),
+ [args.value],
+ DEFAULT_TOOLBAR_ITEM_NAMES,
+ )[0];
this._toolbar?.option(optionName, normalizedItem);
} else if (parts.length >= 4) {
// `toolbar.items[i].prop` case
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 b8a3b92dc104..d64f709fa54e 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
@@ -4,6 +4,7 @@ exports[`common initial render should be successfull 1`] = `
+
This is cardView
`;
diff --git a/packages/devextreme/js/__internal/grids/new/card_view/main_view.tsx b/packages/devextreme/js/__internal/grids/new/card_view/main_view.tsx
index 55b5b65437d2..18a673b2714a 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
@@ -1,18 +1,22 @@
/* eslint-disable spellcheck/spell-checker */
/* eslint-disable @typescript-eslint/explicit-member-accessibility */
-import { state } from '@ts/core/reactive/index';
+import { combined } from '@ts/core/reactive/index';
import { View } from '@ts/grids/new/grid_core/core/view';
+import { ToolbarView } from '@ts/grids/new/grid_core/toolbar/view';
+import type { ComponentType } from 'inferno';
// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface MainViewProps {
-
+ Toolbar: ComponentType;
}
// eslint-disable-next-line no-empty-pattern
function MainViewComponent({
-
+ Toolbar,
}: MainViewProps): JSX.Element {
return (<>
+ {/* @ts-expect-error */}
+
This is cardView
>);
}
@@ -20,11 +24,19 @@ function MainViewComponent({
export class MainView extends View {
protected override component = MainViewComponent;
- public static dependencies = [] as const;
+ public static dependencies = [ToolbarView] as const;
+
+ constructor(
+ private readonly toolbar: ToolbarView,
+ ) {
+ super();
+ }
// eslint-disable-next-line max-len
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/explicit-function-return-type
protected override getProps() {
- return state({});
+ return combined({
+ Toolbar: this.toolbar.asInferno(),
+ });
}
}
diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/inferno_wrappers/toolbar.ts b/packages/devextreme/js/__internal/grids/new/grid_core/inferno_wrappers/toolbar.ts
new file mode 100644
index 000000000000..c9e2d216081e
--- /dev/null
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/inferno_wrappers/toolbar.ts
@@ -0,0 +1,40 @@
+/* eslint-disable @typescript-eslint/no-non-null-assertion */
+import '@js/ui/button';
+import '@js/ui/check_box';
+
+import dxToolbar from '@js/ui/toolbar';
+
+import type { ToolbarProps } from '../toolbar/types';
+import { InfernoWrapper } from './widget_wrapper';
+
+export class Toolbar extends InfernoWrapper {
+ protected getComponentFabric(): typeof dxToolbar {
+ return dxToolbar;
+ }
+
+ protected updateComponentOptions(prevProps: ToolbarProps, props: ToolbarProps): void {
+ if (
+ Array.isArray(props.items)
+ && Array.isArray(prevProps.items)
+ && props.items.length === prevProps.items.length
+ ) {
+ props.items?.forEach((item, index) => {
+ if (props.items![index] !== prevProps.items![index]) {
+ const prevItem = prevProps.items![index];
+
+ Object.keys(item).forEach((key) => {
+ if (item[key] !== prevItem[key]) {
+ this.component?.option(`items[${index}].${key}`, props.items![index][key]);
+ }
+ });
+ }
+ });
+
+ const { items, ...propsToUpdate } = props;
+
+ super.updateComponentOptions(prevProps, propsToUpdate);
+ } else {
+ super.updateComponentOptions(prevProps, props);
+ }
+ }
+}
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 b3fbbf69a803..b2d6bfc693a1 100644
--- a/packages/devextreme/js/__internal/grids/new/grid_core/options.ts
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/options.ts
@@ -4,6 +4,7 @@ import type { WidgetOptions } from '@js/ui/widget/ui.widget';
import * as columnsController from './columns_controller/index';
import * as dataController from './data_controller/index';
+import type * as toolbar from './toolbar';
import type { GridCoreNew } from './widget';
/**
@@ -12,7 +13,8 @@ import type { GridCoreNew } from './widget';
export type Options =
& WidgetOptions
& dataController.Options
- & columnsController.Options;
+ & columnsController.Options
+ & toolbar.Options;
export const defaultOptions = {
...dataController.defaultOptions,
diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/toolbar/__snapshots__/options.test.ts.snap b/packages/devextreme/js/__internal/grids/new/grid_core/toolbar/__snapshots__/options.test.ts.snap
new file mode 100644
index 000000000000..5fa658a56425
--- /dev/null
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/toolbar/__snapshots__/options.test.ts.snap
@@ -0,0 +1,200 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Options disabled when it is 'false' Toolbar should not be disabled 1`] = `
+
+`;
+
+exports[`Options disabled when it is 'true' Toolbar should be disabled 1`] = `
+
+`;
+
+exports[`Options items when these are not set Toolbar should be hidden 1`] = `
+
+
+
+`;
+
+exports[`Options items when these are set Toolbar should be visible 1`] = `
+
+`;
+
+exports[`Options visilbe when changing it to 'false' at runtime Toolbar should be hidden 1`] = `
+
+
+
+`;
+
+exports[`Options visilbe when changing it to 'true' at runtime Toolbar should be visible 1`] = `
+
+`;
+
+exports[`Options visilbe when it is 'false' Toolbar should be hidden 1`] = `
+
+
+
+`;
+
+exports[`Options visilbe when it is 'true' Toolbar should be visible 1`] = `
+
+`;
diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/toolbar/controller.test.ts b/packages/devextreme/js/__internal/grids/new/grid_core/toolbar/controller.test.ts
new file mode 100644
index 000000000000..57418a60a462
--- /dev/null
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/toolbar/controller.test.ts
@@ -0,0 +1,86 @@
+/* eslint-disable spellcheck/spell-checker */
+/* eslint-disable @typescript-eslint/dot-notation */
+import { describe, expect, it } from '@jest/globals';
+
+import type { Options } from '../options';
+import { OptionsControllerMock } from '../options_controller/options_controller.mock';
+import { ToolbarController } from './controller';
+
+const createToolbarController = (options?: Options): {
+ toolbarController: ToolbarController;
+ optionsController: OptionsControllerMock;
+} => {
+ const optionsController = new OptionsControllerMock(options ?? {
+ toolbar: {
+ visible: true,
+ },
+ });
+
+ const toolbarController = new ToolbarController(optionsController);
+
+ return {
+ toolbarController,
+ optionsController,
+ };
+};
+
+describe('ToolbarController', () => {
+ describe('items', () => {
+ describe('when user items are specified', () => {
+ it('should contain processed toolbar items', () => {
+ const { toolbarController } = createToolbarController({
+ toolbar: {
+ items: [{ location: 'before' }],
+ },
+ });
+
+ expect(toolbarController.items.unreactive_get()).toStrictEqual([{ location: 'before' }]);
+ });
+ });
+
+ describe('when default items and user items are specified', () => {
+ it('should contain processed toolbar items', () => {
+ const { toolbarController } = createToolbarController({
+ toolbar: {
+ items: ['searchPanel', { location: 'before' }],
+ },
+ });
+
+ toolbarController.addDefaultItem({ name: 'searchPanel', location: 'after' });
+
+ expect(toolbarController.items.unreactive_get()).toStrictEqual([
+ { name: 'searchPanel', location: 'after' },
+ { location: 'before' },
+ ]);
+ });
+ });
+ });
+
+ describe('addDefaultItem', () => {
+ it('should add new default item to items', () => {
+ const { toolbarController } = createToolbarController();
+
+ toolbarController.addDefaultItem({ name: 'searchPanel', location: 'after' });
+
+ expect(toolbarController.items.unreactive_get()).toStrictEqual([
+ { name: 'searchPanel', location: 'after' },
+ ]);
+ });
+ });
+
+ describe('removeDefaultItem', () => {
+ it('should remove given default item from items', () => {
+ const { toolbarController } = createToolbarController();
+
+ toolbarController.addDefaultItem({ name: 'searchPanel', location: 'after' });
+
+ expect(toolbarController.items.unreactive_get()).toStrictEqual([
+ { name: 'searchPanel', location: 'after' },
+ ]);
+
+ toolbarController.removeDefaultItem('searchPanel');
+
+ expect(toolbarController.items.unreactive_get()).toStrictEqual([]);
+ });
+ });
+});
diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/toolbar/controller.ts b/packages/devextreme/js/__internal/grids/new/grid_core/toolbar/controller.ts
new file mode 100644
index 000000000000..ef12e1ba7e69
--- /dev/null
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/toolbar/controller.ts
@@ -0,0 +1,62 @@
+/* eslint-disable @typescript-eslint/no-dynamic-delete */
+/* eslint-disable @typescript-eslint/no-shadow */
+/* eslint-disable spellcheck/spell-checker */
+import type {
+ MaybeSubscribable, Subscribable, Subscription, SubsGets,
+} from '@ts/core/reactive/index';
+import { computed, state, toSubscribable } from '@ts/core/reactive/index';
+
+import { OptionsController } from '../options_controller/options_controller';
+import { DEFAULT_TOOLBAR_ITEMS } from './defaults';
+import type { PredefinedToolbarItem, ToolbarItem, ToolbarItems } from './types';
+import { normalizeToolbarItems } from './utils';
+
+export class ToolbarController {
+ private readonly itemSubscriptions: Record = {};
+
+ private readonly defaultItems = state>({});
+
+ private readonly userItems: Subscribable;
+
+ public items: SubsGets;
+
+ public static dependencies = [OptionsController] as const;
+
+ constructor(
+ private readonly options: OptionsController,
+ ) {
+ this.userItems = this.options.oneWay('toolbar.items');
+ this.items = computed(
+ (defaultItems, userItems) => normalizeToolbarItems(
+ Object.values(defaultItems),
+ userItems,
+ DEFAULT_TOOLBAR_ITEMS,
+ ),
+ [this.defaultItems, this.userItems],
+ );
+ }
+
+ public addDefaultItem(
+ item: MaybeSubscribable,
+ ): void {
+ const itemObs = toSubscribable(item);
+ // @ts-expect-error
+ const { name } = itemObs.unreactive_get();
+
+ this.itemSubscriptions[name] = itemObs.subscribe((item) => {
+ this.defaultItems.updateFunc((oldDefaultItems) => ({
+ ...oldDefaultItems,
+ [item.name]: item,
+ }));
+ });
+ }
+
+ public removeDefaultItem(name: typeof DEFAULT_TOOLBAR_ITEMS[number]): void {
+ this.defaultItems.updateFunc((oldDefaultItems) => {
+ const defaultItems = { ...oldDefaultItems };
+ delete defaultItems[name];
+ return defaultItems;
+ });
+ this.itemSubscriptions[name].unsubscribe();
+ }
+}
diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/toolbar/defaults.tsx b/packages/devextreme/js/__internal/grids/new/grid_core/toolbar/defaults.tsx
new file mode 100644
index 000000000000..5e8280f1393d
--- /dev/null
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/toolbar/defaults.tsx
@@ -0,0 +1,6 @@
+/**
+ * used for defining default toolbar items and their default order in header panel
+ */
+export const DEFAULT_TOOLBAR_ITEMS = [
+ 'searchPanel',
+] as const;
diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/toolbar/index.ts b/packages/devextreme/js/__internal/grids/new/grid_core/toolbar/index.ts
new file mode 100644
index 000000000000..f1059a6060ec
--- /dev/null
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/toolbar/index.ts
@@ -0,0 +1,3 @@
+export { ToolbarController as Controller } from './controller';
+export { type Options } from './options';
+export { ToolbarView as View } from './view';
diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/toolbar/options.test.ts b/packages/devextreme/js/__internal/grids/new/grid_core/toolbar/options.test.ts
new file mode 100644
index 000000000000..060c8769fb57
--- /dev/null
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/toolbar/options.test.ts
@@ -0,0 +1,152 @@
+/* eslint-disable spellcheck/spell-checker */
+/* eslint-disable @typescript-eslint/dot-notation */
+import { describe, expect, it } from '@jest/globals';
+
+import type { Options } from '../options';
+import { OptionsControllerMock } from '../options_controller/options_controller.mock';
+import { ToolbarController } from './controller';
+import { ToolbarView } from './view';
+
+const createToolbarView = (options?: Options): {
+ rootElement: HTMLElement;
+ optionsController: OptionsControllerMock;
+} => {
+ const rootElement = document.createElement('div');
+ const optionsController = new OptionsControllerMock(options ?? {
+ toolbar: {
+ visible: true,
+ },
+ });
+
+ const toolbarController = new ToolbarController(optionsController);
+ const toolbar = new ToolbarView(toolbarController, optionsController);
+
+ toolbar.render(rootElement);
+
+ return {
+ rootElement,
+ optionsController,
+ };
+};
+
+describe('Options', () => {
+ describe('visilbe', () => {
+ describe('when it is \'true\'', () => {
+ it('Toolbar should be visible', () => {
+ const { rootElement } = createToolbarView({
+ toolbar: {
+ visible: true,
+ },
+ });
+
+ expect(rootElement).toMatchSnapshot();
+ });
+ });
+
+ describe('when it is \'false\'', () => {
+ it('Toolbar should be hidden', () => {
+ const { rootElement } = createToolbarView({
+ toolbar: {
+ visible: false,
+ },
+ });
+
+ expect(rootElement).toMatchSnapshot();
+ });
+ });
+
+ describe('when changing it to \'false\' at runtime', () => {
+ it('Toolbar should be hidden', () => {
+ const { rootElement, optionsController } = createToolbarView({
+ toolbar: {
+ visible: true,
+ },
+ });
+
+ optionsController.option('toolbar.visible', false);
+
+ expect(rootElement).toMatchSnapshot();
+ });
+ });
+
+ describe('when changing it to \'true\' at runtime', () => {
+ it('Toolbar should be visible', () => {
+ const { rootElement, optionsController } = createToolbarView({
+ toolbar: {
+ visible: false,
+ },
+ });
+
+ optionsController.option('toolbar.visible', true);
+
+ expect(rootElement).toMatchSnapshot();
+ });
+ });
+ });
+
+ describe('items', () => {
+ describe('when these are not set', () => {
+ it('Toolbar should be hidden', () => {
+ const { rootElement } = createToolbarView({
+ toolbar: {
+ items: [],
+ },
+ });
+
+ expect(rootElement).toMatchSnapshot();
+ });
+ });
+
+ describe('when these are set', () => {
+ it('Toolbar should be visible', () => {
+ const { rootElement } = createToolbarView({
+ toolbar: {
+ items: [{
+ location: 'before',
+ widget: 'dxButton',
+ options: {
+ text: 'button1',
+ },
+ }, {
+ location: 'after',
+ widget: 'dxButton',
+ options: {
+ text: 'button2',
+ },
+ }],
+ },
+ });
+
+ expect(rootElement).toMatchSnapshot();
+ });
+ });
+ });
+
+ describe('disabled', () => {
+ describe('when it is \'true\'', () => {
+ it('Toolbar should be disabled', () => {
+ const { rootElement } = createToolbarView({
+ toolbar: {
+ visible: true,
+ disabled: true,
+ },
+ });
+
+ expect(rootElement).toMatchSnapshot();
+ });
+ });
+
+ describe('when it is \'false\'', () => {
+ it('Toolbar should not be disabled', () => {
+ const { rootElement } = createToolbarView({
+ toolbar: {
+ visible: true,
+ disabled: false,
+ },
+ });
+
+ expect(rootElement).toMatchSnapshot();
+ });
+ });
+ });
+});
diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/toolbar/options.ts b/packages/devextreme/js/__internal/grids/new/grid_core/toolbar/options.ts
new file mode 100644
index 000000000000..cfbdaf634790
--- /dev/null
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/toolbar/options.ts
@@ -0,0 +1,5 @@
+import type { ToolbarProps } from './types';
+
+export interface Options {
+ toolbar?: ToolbarProps;
+}
diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/toolbar/toolbar.tsx b/packages/devextreme/js/__internal/grids/new/grid_core/toolbar/toolbar.tsx
new file mode 100644
index 000000000000..9604512cf8e6
--- /dev/null
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/toolbar/toolbar.tsx
@@ -0,0 +1,10 @@
+import type { InfernoNode } from 'inferno';
+
+import { Toolbar } from '../inferno_wrappers/toolbar';
+import type { ToolbarProps } from './types';
+
+export function ToolbarView(props: ToolbarProps): InfernoNode {
+ return (
+ !!props.visible &&
+ );
+}
diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/toolbar/types.ts b/packages/devextreme/js/__internal/grids/new/grid_core/toolbar/types.ts
new file mode 100644
index 000000000000..994bf1791e7f
--- /dev/null
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/toolbar/types.ts
@@ -0,0 +1,18 @@
+import type { Item as BaseToolbarItem } from '@js/ui/toolbar';
+
+import type { DEFAULT_TOOLBAR_ITEMS } from './defaults';
+
+type DefaultToolbarItem = typeof DEFAULT_TOOLBAR_ITEMS[number];
+
+export interface ToolbarItem extends BaseToolbarItem {
+ name?: DefaultToolbarItem | string;
+}
+
+export type PredefinedToolbarItem = ToolbarItem & { name: DefaultToolbarItem };
+export type ToolbarItems = (ToolbarItem | DefaultToolbarItem)[];
+
+export interface ToolbarProps {
+ items?: ToolbarItems;
+ visible?: boolean | undefined;
+ disabled?: boolean;
+}
diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/toolbar/utils.test.ts b/packages/devextreme/js/__internal/grids/new/grid_core/toolbar/utils.test.ts
new file mode 100644
index 000000000000..4ce02da03eeb
--- /dev/null
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/toolbar/utils.test.ts
@@ -0,0 +1,97 @@
+import { describe, expect, it } from '@jest/globals';
+
+import { isVisible, normalizeToolbarItems } from './utils';
+
+describe('isVisible', () => {
+ describe('when visibleConfig = true', () => {
+ it('should be equal to true', () => {
+ expect(isVisible(true, [])).toBe(true);
+ });
+ });
+
+ describe('when visibleConfig = false', () => {
+ it('should be equal to false', () => {
+ expect(isVisible(false, [{ name: 'toolbarItem1' }])).toBe(false);
+ });
+ });
+
+ describe('when visibleConfig = undefined and there are items', () => {
+ it('should be equal to false', () => {
+ expect(isVisible(undefined, [])).toBe(false);
+ });
+ });
+
+ describe('when visibleConfig = undefined and there are no items', () => {
+ it('should be equal to true', () => {
+ expect(isVisible(undefined, [
+ { name: 'toolbarItem1' },
+ { name: 'toolbarItem2' },
+ ])).toBe(true);
+ });
+ });
+});
+
+describe('normalizeToolbarItems', () => {
+ describe('when only default items are specified', () => {
+ it('should return default items', () => {
+ expect(normalizeToolbarItems(
+ [{ name: 'toolbarItem1' }],
+ undefined,
+ ['toolbarItem1'],
+ )).toStrictEqual([{ name: 'toolbarItem1' }]);
+ });
+ });
+
+ describe('when only custom items are specified', () => {
+ it('should return processed custom items', () => {
+ expect(normalizeToolbarItems(
+ [],
+ [{ name: 'customToolbarItem1' }],
+ ['toolbarItem1'],
+ )).toStrictEqual([{ name: 'customToolbarItem1', location: 'after' }]);
+ });
+ });
+
+ describe('when default items and custom items are specified', () => {
+ it('should return processed custom items', () => {
+ expect(normalizeToolbarItems(
+ [{ name: 'toolbarItem1' }],
+ [{ name: 'customToolbarItem1' }],
+ ['toolbarItem1'],
+ )).toStrictEqual([{ name: 'customToolbarItem1', location: 'after' }]);
+ });
+ });
+
+ describe('when custom items override default items', () => {
+ it('should return default items merged with custom items', () => {
+ expect(normalizeToolbarItems(
+ [{ name: 'toolbarItem1', location: 'before' }],
+ [{ name: 'toolbarItem1', location: 'after' }],
+ ['toolbarItem1'],
+ )).toStrictEqual([{ name: 'toolbarItem1', location: 'after' }]);
+ });
+ });
+
+ describe('when default items are set in custom items', () => {
+ it('should return both default and custom items', () => {
+ expect(normalizeToolbarItems(
+ [{ name: 'toolbarItem1', location: 'before' }],
+ ['toolbarItem1', { name: 'customToolbarItem1' }],
+ ['toolbarItem1'],
+ )).toStrictEqual([
+ { name: 'toolbarItem1', location: 'before' },
+ { name: 'customToolbarItem1', location: 'after' },
+ ]);
+ });
+ });
+
+ describe('when there are no default items but they are specified in custom items', () => {
+ it('should return processed default items', () => {
+ expect(normalizeToolbarItems(
+ [],
+ ['toolbarItem1'],
+ ['toolbarItem1'],
+ )).toStrictEqual([{ name: 'toolbarItem1', location: 'after', visible: false }]);
+ });
+ });
+});
diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/toolbar/utils.ts b/packages/devextreme/js/__internal/grids/new/grid_core/toolbar/utils.ts
new file mode 100644
index 000000000000..8affcc918e27
--- /dev/null
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/toolbar/utils.ts
@@ -0,0 +1,65 @@
+import { extend } from '@js/core/utils/extend';
+import { isDefined, isString } from '@js/core/utils/type';
+import type { Item as BaseToolbarItem } from '@js/ui/toolbar';
+
+import type { ToolbarItems } from './types';
+
+export function isVisible(
+ visibleConfig: boolean | undefined,
+ items: ToolbarItems,
+): boolean {
+ if (visibleConfig === undefined) {
+ return items.length > 0;
+ }
+
+ return visibleConfig;
+}
+
+interface ToolbarItem extends BaseToolbarItem {
+ name?: string;
+}
+
+function normalizeToolbarItem(
+ item: ToolbarItem | string,
+ defaultButtonsMap: { [key: string]: ToolbarItem },
+ defaultItemNames: readonly string[],
+): ToolbarItem {
+ let button = item;
+ const defaultProps: ToolbarItem = { location: 'after' };
+
+ if (isString(button)) {
+ button = { name: button };
+ }
+
+ if (isDefined(button.name)) {
+ if (isDefined(defaultButtonsMap[button.name])) {
+ button = extend(true, {}, defaultButtonsMap[button.name], button);
+ } else if (defaultItemNames.includes(button.name)) {
+ button = { ...button, visible: false };
+ }
+ }
+
+ return extend(true, {}, defaultProps, button) as ToolbarItem;
+}
+
+export function normalizeToolbarItems(
+ defaultItems: (ToolbarItem & { name: string })[],
+ userItems: (ToolbarItem | string)[] | undefined,
+ defaultItemNames: readonly string[],
+): ToolbarItem[] {
+ if (!isDefined(userItems)) {
+ return defaultItems;
+ }
+
+ const defaultButtonsMap = {};
+
+ defaultItems.forEach((button) => {
+ defaultButtonsMap[button.name] = button;
+ });
+
+ return userItems.map(
+ (
+ item: ToolbarItem | string,
+ ) => normalizeToolbarItem(item, defaultButtonsMap, defaultItemNames),
+ );
+}
diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/toolbar/view.tsx b/packages/devextreme/js/__internal/grids/new/grid_core/toolbar/view.tsx
new file mode 100644
index 000000000000..e6bc9c8ecd4a
--- /dev/null
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/toolbar/view.tsx
@@ -0,0 +1,38 @@
+/* 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 { OptionsController } from '../options_controller/options_controller';
+import { ToolbarController } from './controller';
+import { ToolbarView as Toolbar } from './toolbar';
+import type { ToolbarProps } from './types';
+import { isVisible } from './utils';
+
+export class ToolbarView extends View {
+ protected override component = Toolbar;
+
+ private readonly visibleConfig = this.options.oneWay('toolbar.visible');
+
+ private readonly visible = computed(
+ (visibleConfig, pageCount) => isVisible(visibleConfig, pageCount),
+ [this.visibleConfig, this.controller.items],
+ );
+
+ public static dependencies = [ToolbarController, OptionsController] as const;
+
+ constructor(
+ private readonly controller: ToolbarController,
+ private readonly options: OptionsController,
+ ) {
+ super();
+ }
+
+ protected override getProps(): Subscribable {
+ return combined({
+ visible: this.visible,
+ items: this.controller.items,
+ disabled: this.options.oneWay('toolbar.disabled'),
+ });
+ }
+}
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 0ad060d8e9b4..09c7980625d5 100644
--- a/packages/devextreme/js/__internal/grids/new/grid_core/widget.ts
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/widget.ts
@@ -13,6 +13,8 @@ import * as ColumnsControllerModule from './columns_controller/index';
import * as DataControllerModule from './data_controller/index';
import { MainView } from './main_view';
import { defaultOptions, defaultOptionsRules, type Options } from './options';
+import { ToolbarController } from './toolbar/controller';
+import { ToolbarView } from './toolbar/view';
export class GridCoreNewBase<
TProperties extends Options = Options,
@@ -25,15 +27,23 @@ export class GridCoreNewBase<
protected columnsController!: ColumnsControllerModule.ColumnsController;
+ private toolbarController!: ToolbarController;
+
+ private toolbarView!: ToolbarView;
+
protected _registerDIContext(): void {
this.diContext = new DIContext();
this.diContext.register(DataControllerModule.DataController);
this.diContext.register(ColumnsControllerModule.ColumnsController);
+ this.diContext.register(ToolbarController);
+ this.diContext.register(ToolbarView);
}
protected _initDIContext(): void {
this.dataController = this.diContext.get(DataControllerModule.DataController);
this.columnsController = this.diContext.get(ColumnsControllerModule.ColumnsController);
+ this.toolbarController = this.diContext.get(ToolbarController);
+ this.toolbarView = this.diContext.get(ToolbarView);
}
protected _init(): void {
diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/headerPanel.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/headerPanel.tests.js
index 970db3d94b22..f9e6c8e1da6e 100644
--- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/headerPanel.tests.js
+++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/headerPanel.tests.js
@@ -938,20 +938,4 @@ QUnit.module('Draw buttons in header panel', {
assert.strictEqual($customToolbarItem.length, 1, 'item is rendered');
assert.ok($customToolbarItem.is(':visible'), 'item is visible');
});
-
- QUnit.test('The error should be raised if new default toolbar item is not added to DEFAULT_TOOLBAR_ITEM_NAMES', function(assert) {
- // arrange
- const headerPanel = this.headerPanel;
- const $testElement = $('#container');
-
- // act
- headerPanel._getToolbarItems = () => [{ name: 'new' }];
-
- assert.throws(function() {
- headerPanel.init();
- headerPanel.render($testElement);
- }, function(e) {
- return e.message === 'Default toolbar item \'new\' is not added to DEFAULT_TOOLBAR_ITEM_NAMES';
- }, 'exception');
- });
});