Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions apps/react-storybook/stories/card_view/CardView.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -298,3 +298,32 @@ export const SelectionStory: Story = {
}
}

export const ContextMenuStory: Story = {
...DefaultMode,
args: {
...DefaultMode.args,
onContextMenuPreparing: (e) => {
e.items = e.items ?? [];

if(e.target === 'toolbar') {
e.items.push({
text: 'show column chooser',
onItemClick: () => e.component.showColumnChooser()
});
}
else if(e.target === 'headerPanel' && e.column) {
e.items.push({
text: `hide ${e.column.caption}`,
disabled: !e.column.visible,
icon: 'eyeclose',
onItemClick: () => e.component.columnOption(e.columnIndex, 'visible', false)
});
}
else if(e.target === 'content' && e.card) {
e.items.push({
text: 'do something with card'
});
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const dependencies: FlatStylesDependencies = {
buttongroup: ['validation', 'button'],
dropdownbutton: ['validation', 'button', 'buttongroup', 'popup', 'loadindicator', 'loadpanel', 'scrollview', 'list'],
calendar: ['validation', 'button'],
cardview: ['box', 'button', 'calendar', 'checkbox', 'datebox', 'filterbuilder', 'list', 'loadindicator', 'loadpanel', 'numberbox', 'popup', 'scrollview', 'selectbox', 'sortable', 'textbox', 'toast', 'toolbar', 'treeview', 'validation'],
cardview: ['box', 'button', 'calendar', 'checkbox', 'contextmenu', 'datebox', 'filterbuilder', 'list', 'loadindicator', 'loadpanel', 'numberbox', 'popup', 'scrollview', 'selectbox', 'sortable', 'textbox', 'toast', 'toolbar', 'treeview', 'validation'],
chat: ['button', 'loadindicator', 'loadpanel', 'scrollview', 'textbox', 'validation'],
checkbox: ['validation'],
numberbox: ['validation', 'button', 'loadindicator'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,5 +182,13 @@ exports[`common initial render should be successfull 1`] = `

</div>

<div
class="dx-cardview-exclude-flexbox"
>
<div
class="dx-context-menu dx-has-context-menu dx-widget dx-visibility-change-handler dx-collection"
role="presentation"
/>
</div>
</div>
`;
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ export interface CardProps {

onPrepared?: (e: CardPreparedEvent) => void;

onContextMenu?: (e: MouseEvent, card?: DataRow, cardIndex?: number) => void;

selectCard?: (row: DataRow, options: SelectCardOptions) => void;
}

Expand Down Expand Up @@ -111,6 +113,7 @@ export class Card extends Component<CardProps> {
onDblClick={this.handleDoubleClick}
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
onContextMenu={this.props.onContextMenu}
>
<CardHeader
row={row}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ export interface ContentProps {

onRowHeightChange?: (value: number) => void;

showContextMenu?: (e: MouseEvent, card?: DataRow, cardIndex?: number) => void;

cardsPerRow?: number;

needToHiddenCheckBoxes?: boolean;
Expand Down Expand Up @@ -88,14 +90,18 @@ export class Content extends Component<ContentProps> {
className={className}
style={this.getCssVariables()}
ref={this.containerRef}
onContextMenu={this.props.showContextMenu}
>
{this.props.items.map((item, i) => (
{this.props.items.map((item, index) => (
<Card
{...this.props.cardProps}
key={getInfernoCardKey(item)}
elementRef={this.cardRefs[i]}
elementRef={this.cardRefs[index]}
row={item}
fieldTemplate={this.props.fieldTemplate}
onContextMenu={(e) => {
this.props.showContextMenu?.(e, item, index);
}}
/>
))}
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import type { ContentViewProps as ContentViewBaseProps } from '@ts/grids/new/grid_core/content_view/content_view';
import { ContentView as ContentViewBase } from '@ts/grids/new/grid_core/content_view/content_view';
import type { InfernoNode } from 'inferno';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export class ContentView extends ContentViewBase<ContentViewProps> {
fieldTemplate: this.options.template('fieldTemplate'),
cardsPerRow: this.cardsPerRow,
onRowHeightChange: this.rowHeight.update.bind(this.rowHeight),
showContextMenu: this.showContextMenu.bind(this),
cardProps: combined({
minWidth: this.cardMinWidth,
maxWidth: this.options.oneWay('cardMaxWidth'),
Expand Down Expand Up @@ -100,4 +101,8 @@ export class ContentView extends ContentViewBase<ContentViewProps> {
private onCardHold(e: CardHoldEvent) {
this.selectionController.processLongTap(e.row);
}

private showContextMenu(e: MouseEvent, card?: DataRow, cardIndex?: number): void {
this.contextMenuController.show(e, 'content', { card, cardIndex });
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { Item as ContextMenuItem } from '@js/ui/context_menu';

import type { ContextInfo, ContextMenuTarget } from '.';
import { ContextMenuController } from '.';

export class ContextMenuControllerMock extends ContextMenuController {
public override getItems(
view: ContextMenuTarget,
targetElement: Element,
contextInfo: ContextInfo = {},
): ContextMenuItem[] | undefined {
return super.getItems(view, targetElement, contextInfo);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import {
describe, expect, it, jest,
} from '@jest/globals';
import dxContextMenu from '@js/ui/context_menu';

import { ColumnsController } from '../../grid_core/columns_controller';
import type { Options } from '../options';
import { OptionsControllerMock } from '../options_controller.mock';
import { ContextMenuControllerMock } from './controller.mock';

const setup = (options: Options) => {
const optionsController = new OptionsControllerMock(options);
const columnsController = new ColumnsController(optionsController);
const controller = new ContextMenuControllerMock(columnsController, optionsController);

const container = document.createElement('div');
// eslint-disable-next-line new-cap
const contextMenu = new dxContextMenu(container, {
onPositioning: controller.onPositioning,
});

// @ts-expect-error
controller.contextMenuRef = { current: contextMenu };

return { controller, contextMenu };
};

describe('ContextMenu', () => {
describe('Controller', () => {
it('onContextMenuPreparing is called on getItems()', () => {
const onContextMenuPreparing = jest.fn();
const { controller } = setup({
onContextMenuPreparing,
});

controller.getItems('content', document.createElement('div'));

expect(onContextMenuPreparing).toHaveBeenCalledTimes(1);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/* eslint-disable spellcheck/spell-checker */
import type { Item as ContextMenuItem, ItemClickEvent } from '@js/ui/context_menu';

import { ColumnsController } from '../../grid_core/columns_controller/index';
import type { Column, DataRow } from '../../grid_core/columns_controller/types';
import { BaseContextMenuController } from '../../grid_core/context_menu/controller';
import { OptionsController } from '../options_controller';
import type { ContextMenuPreparingEvent, ContextMenuTarget } from '.';

export interface ContextInfo {
column?: Column;
columnIndex?: number;
card?: DataRow;
cardIndex?: number;
}

export class ContextMenuController
extends BaseContextMenuController<ContextMenuTarget, ContextInfo> {
public static dependencies = [ColumnsController, OptionsController] as const;

constructor(
private readonly columnsController: ColumnsController,
private readonly options: OptionsController,
) {
super();
}

public override show(
event: MouseEvent,
view: ContextMenuTarget,
contextInfo: ContextInfo = {},
): void {
super.show(event, view, contextInfo);
}

protected override getItems(
view: ContextMenuTarget,
targetElement: Element,
contextInfo: ContextInfo = {},
): ContextMenuItem[] | undefined {
const items: ContextMenuItem[] = [];

if (view === 'headerPanel' && contextInfo.column) {
items.push(...this.getSortingItems(contextInfo.column));
}

// @ts-expect-error
const event: ContextMenuPreparingEvent<DataRow> = {
items: items.length > 0 ? items : undefined,
target: view,
targetElement: targetElement as HTMLElement,
columnIndex: undefined,
card: undefined,
cardIndex: undefined,
column: undefined,

...contextInfo,
};

const callback = this.options.action('onContextMenuPreparing').unreactive_get();

callback(event);

return event.items;
}

private getSortingItems(column: Column): ContextMenuItem[] {
const onItemClick = (e: ItemClickEvent): void => {
this.columnsController.columnOption(column, 'sortOrder', e.itemData?.value);
};

return [
{
text: this.options.oneWay('sorting.ascendingText').unreactive_get(),
value: 'asc',
disabled: column.sortOrder === 'asc',
icon: 'sortuptext',
onItemClick,
},
{
text: this.options.oneWay('sorting.descendingText').unreactive_get(),
value: 'desc',
disabled: column.sortOrder === 'desc',
icon: 'sortdowntext',
onItemClick,
},
{
text: this.options.oneWay('sorting.clearText').unreactive_get(),
value: undefined,
disabled: !column.sortOrder,
icon: 'none',
onItemClick,
},
] as ContextMenuItem[];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './controller';
export type { ContextMenuPreparingEvent, ContextMenuTarget, Options } from './options';
export * from './view';
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/* eslint-disable @typescript-eslint/no-explicit-any */

import type { EventInfo } from '@js/common/core/events';
import type { DxElement } from '@js/core/element';

import type { Column, DataRow } from '../../grid_core/columns_controller/types';

export type ContextMenuTarget = 'toolbar' | 'headerPanel' | 'content';

// eslint-disable-next-line @typescript-eslint/no-unused-vars
export type ContextMenuPreparingEvent<TCardData = unknown, TKey = unknown>
= EventInfo<any> & {
items?: any[];

readonly target: ContextMenuTarget;

readonly targetElement: DxElement;

readonly columnIndex?: number;

readonly column?: Column;

readonly cardIndex?: number;

readonly card?: TCardData;
};

export interface Options<TRowData = DataRow, TKey = unknown> {
onContextMenuPreparing?: (args: ContextMenuPreparingEvent<TRowData, TKey>) => void;
}
Loading
Loading