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 new file mode 100644 index 000000000000..b8a3b92dc104 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/new/card_view/__snapshots__/widget.test.ts.snap @@ -0,0 +1,9 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +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 new file mode 100644 index 000000000000..55b5b65437d2 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/new/card_view/main_view.tsx @@ -0,0 +1,30 @@ +/* eslint-disable spellcheck/spell-checker */ +/* eslint-disable @typescript-eslint/explicit-member-accessibility */ +import { state } from '@ts/core/reactive/index'; +import { View } from '@ts/grids/new/grid_core/core/view'; + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +interface MainViewProps { + +} + +// eslint-disable-next-line no-empty-pattern +function MainViewComponent({ + +}: MainViewProps): JSX.Element { + return (<> + This is cardView + ); +} + +export class MainView extends View { + protected override component = MainViewComponent; + + public static dependencies = [] as const; + + // 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({}); + } +} diff --git a/packages/devextreme/js/__internal/grids/new/card_view/options.ts b/packages/devextreme/js/__internal/grids/new/card_view/options.ts new file mode 100644 index 000000000000..2448e49792c3 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/new/card_view/options.ts @@ -0,0 +1,11 @@ +import * as GridCore from '@ts/grids/new/grid_core/options'; + +/** + * @interface + */ +export type Options = + & GridCore.Options; + +export const defaultOptions = { + ...GridCore.defaultOptions, +} satisfies Options; diff --git a/packages/devextreme/js/__internal/grids/new/card_view/widget.test.ts b/packages/devextreme/js/__internal/grids/new/card_view/widget.test.ts new file mode 100644 index 000000000000..baede2d8bbc0 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/new/card_view/widget.test.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { describe, expect, it } from '@jest/globals'; + +import { CardView } from './widget'; + +describe('common', () => { + describe('initial render', () => { + it('should be successfull', () => { + const container = document.createElement('div'); + const cardView = new CardView(container, {}); + + expect(container).toMatchSnapshot(); + }); + }); +}); diff --git a/packages/devextreme/js/__internal/grids/new/card_view/widget.ts b/packages/devextreme/js/__internal/grids/new/card_view/widget.ts new file mode 100644 index 000000000000..f444199a2015 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/new/card_view/widget.ts @@ -0,0 +1,42 @@ +/* eslint-disable max-classes-per-file */ +/* eslint-disable spellcheck/spell-checker */ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +import registerComponent from '@js/core/component_registrator'; +import $ from '@js/core/renderer'; +import { MainView as MainViewBase } from '@ts/grids/new/grid_core/main_view'; +import { GridCoreNew } from '@ts/grids/new/grid_core/widget'; + +import { MainView } from './main_view'; +import { defaultOptions } from './options'; + +export class CardViewBase extends GridCoreNew { + protected _registerDIContext(): void { + super._registerDIContext(); + this.diContext.register(MainViewBase, MainView); + } + + protected _initMarkup(): void { + super._initMarkup(); + $(this.$element()).addClass('dx-cardview'); + } + + protected _initDIContext(): void { + super._initDIContext(); + } + + // eslint-disable-next-line max-len + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type, @typescript-eslint/explicit-module-boundary-types + protected _getDefaultOptions() { + return { + ...super._getDefaultOptions(), + ...defaultOptions, + }; + } +} + +export class CardView extends CardViewBase {} + +// @ts-expect-error +registerComponent('dxCardView', CardView); + +export default CardView; diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/core/view.tsx b/packages/devextreme/js/__internal/grids/new/grid_core/core/view.tsx new file mode 100644 index 000000000000..497d30813c43 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/new/grid_core/core/view.tsx @@ -0,0 +1,64 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +/* eslint-disable @typescript-eslint/no-this-alias */ +/* eslint-disable @typescript-eslint/no-use-before-define */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/ban-types */ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +/* eslint-disable max-classes-per-file */ +/* eslint-disable spellcheck/spell-checker */ +import type { Subscribable, Subscription } from '@ts/core/reactive/index'; +import { toSubscribable } from '@ts/core/reactive/index'; +import { Component, type ComponentType, render } from 'inferno'; + +export abstract class View { + private inferno: undefined | ComponentType; + + protected abstract component: ComponentType; + + protected abstract getProps(): Subscribable; + + public render(root: Element): Subscription { + const ViewComponent = this.component; + return toSubscribable(this.getProps()).subscribe((props: T) => { + // @ts-expect-error + render(, root); + }); + } + + public asInferno(): ComponentType { + // @ts-expect-error fixed in inferno v8 + // eslint-disable-next-line no-return-assign + return this.inferno ??= this._asInferno(); + } + + private _asInferno() { + const view = this; + + interface State { + props: T; + } + + return class InfernoView extends Component<{}, State> { + private readonly subscription: Subscription; + + constructor() { + super(); + this.subscription = toSubscribable(view.getProps()).subscribe((props) => { + this.state ??= { + props, + }; + + if (this.state.props !== props) { + this.setState({ props }); + } + }); + } + + public render(): JSX.Element | undefined { + const ViewComponent = view.component; + // @ts-expect-error + return ; + } + }; + } +} diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/main_view.tsx b/packages/devextreme/js/__internal/grids/new/grid_core/main_view.tsx new file mode 100644 index 000000000000..bd1c6ba4d326 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/new/grid_core/main_view.tsx @@ -0,0 +1,7 @@ +/* eslint-disable @typescript-eslint/ban-types */ +/* eslint-disable spellcheck/spell-checker */ +/* eslint-disable @typescript-eslint/explicit-member-accessibility */ + +import { View } from './core/view'; + +export abstract class MainView extends View {} diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/options.ts b/packages/devextreme/js/__internal/grids/new/grid_core/options.ts new file mode 100644 index 000000000000..7020661d76d5 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/new/grid_core/options.ts @@ -0,0 +1,54 @@ +import browser from '@js/core/utils/browser'; +import { isMaterialBased } from '@js/ui/themes'; +import type { WidgetOptions } from '@js/ui/widget/ui.widget'; + +import type { GridCoreNew } from './widget'; + +/** + * @interface + */ +export type Options = + & WidgetOptions; + +export const defaultOptions = { +} satisfies Options; + +// TODO: separate by modules +// TODO: add typing for defaultOptionRules +export const defaultOptionsRules = [ + { + device(): boolean { + // @ts-expect-error + return isMaterialBased(); + }, + options: { + headerFilter: { + height: 315, + }, + editing: { + useIcons: true, + }, + selection: { + showCheckBoxesMode: 'always', + }, + }, + }, + { + device(): boolean | undefined { + return browser.webkit; + }, + options: { + loadingTimeout: 30, // T344031 + loadPanel: { + animation: { + show: { + easing: 'cubic-bezier(1, 0, 1, 0)', + duration: 500, + from: { opacity: 0 }, + to: { opacity: 1 }, + }, + }, + }, + }, + }, +]; diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/widget.ts b/packages/devextreme/js/__internal/grids/new/grid_core/widget.ts new file mode 100644 index 000000000000..3c1a6fd2d661 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/new/grid_core/widget.ts @@ -0,0 +1,68 @@ +/* eslint-disable @typescript-eslint/ban-types */ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +/* eslint-disable spellcheck/spell-checker */ +// eslint-disable-next-line max-classes-per-file +import Widget from '@js/ui/widget/ui.widget'; +import { DIContext } from '@ts/core/di/index'; +import type { Subscription } from '@ts/core/reactive/index'; +import { render } from 'inferno'; + +import { MainView } from './main_view'; +import { defaultOptions, defaultOptionsRules, type Options } from './options'; + +export class GridCoreNewBase< + TProperties extends Options = Options, +> extends Widget { + protected renderSubscription?: Subscription; + + protected diContext!: DIContext; + + protected _registerDIContext(): void { + this.diContext = new DIContext(); + } + + protected _initDIContext(): void { + } + + protected _init(): void { + // @ts-expect-error + super._init(); + this._registerDIContext(); + this._initDIContext(); + } + + // eslint-disable-next-line max-len + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type, @typescript-eslint/explicit-module-boundary-types + protected _getDefaultOptions() { + return { + // @ts-expect-error + ...super._getDefaultOptions() as {}, + ...defaultOptions, + }; + } + + protected _defaultOptionsRules() { + // @ts-expect-error + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return super._defaultOptionsRules().concat(defaultOptionsRules); + } + + protected _initMarkup(): void { + // @ts-expect-error + super._initMarkup(); + // @ts-expect-error + this.renderSubscription = this.diContext.get(MainView).render(this.$element().get(0)); + } + + protected _clean(): void { + this.renderSubscription?.unsubscribe(); + // @ts-expect-error + render(null, this.$element().get(0)); + // @ts-expect-error + super._clean(); + } +} + +export class GridCoreNew extends GridCoreNewBase {} diff --git a/packages/devextreme/js/bundles/modules/parts/widgets-web.js b/packages/devextreme/js/bundles/modules/parts/widgets-web.js index e8d6d70fd681..959a240fde2a 100644 --- a/packages/devextreme/js/bundles/modules/parts/widgets-web.js +++ b/packages/devextreme/js/bundles/modules/parts/widgets-web.js @@ -9,6 +9,7 @@ ui.dxAccordion = require('../../../ui/accordion'); ui.dxContextMenu = require('../../../ui/context_menu'); ui.dxDataGrid = require('../../../ui/data_grid'); ui.dxTreeList = require('../../../ui/tree_list'); +ui.dxCardView = require('../../../ui/card_view'); ui.dxMenu = require('../../../ui/menu'); ui.dxPivotGrid = require('../../../ui/pivot_grid'); ui.dxPivotGridFieldChooser = require('../../../ui/pivot_grid_field_chooser'); diff --git a/packages/devextreme/js/ui/card_view.js b/packages/devextreme/js/ui/card_view.js new file mode 100644 index 000000000000..663a6aca8521 --- /dev/null +++ b/packages/devextreme/js/ui/card_view.js @@ -0,0 +1 @@ +export { default } from '../__internal/grids/new/card_view/widget';