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
44 changes: 29 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -317,12 +317,26 @@ function MyGrid() {

###### `onFill?: Maybe<(event: FillEvent<R>) => R>`

###### `onCellClick?: Maybe<(args: CellClickArgs<R, SR>, event: CellMouseEvent) => void>`
###### `onCellMouseDown: Maybe<(args: CellMouseArgs<R, SR>, event: CellMouseEvent) => void>`

Callback triggered when a cell is clicked. The default behavior is to select the cell. Call `preventGridDefault` to prevent the default behavior
Callback triggered when a pointer becomes active in a cell. The default behavior is to select the cell. Call `preventGridDefault` to prevent the default behavior.

```tsx
function onCellClick(args: CellClickArgs<R, SR>, event: CellMouseEvent) {
function onCellMouseDown(args: CellMouseDownArgs<R, SR>, event: CellMouseEvent) {
if (args.column.key === 'id') {
event.preventGridDefault();
}
}

<DataGrid rows={rows} columns={columns} onCellMouseDown={onCellMouseDown} />;
```

###### `onCellClick?: Maybe<(args: CellMouseArgs<R, SR>, event: CellMouseEvent) => void>`

Callback triggered when a cell is clicked.

```tsx
function onCellClick(args: CellMouseArgs<R, SR>, event: CellMouseEvent) {
if (args.column.key === 'id') {
event.preventGridDefault();
}
Expand All @@ -334,17 +348,16 @@ function onCellClick(args: CellClickArgs<R, SR>, event: CellMouseEvent) {
This event can be used to open cell editor on single click

```tsx
function onCellClick(args: CellClickArgs<R, SR>, event: CellMouseEvent) {
function onCellClick(args: CellMouseArgs<R, SR>, event: CellMouseEvent) {
if (args.column.key === 'id') {
event.preventGridDefault();
args.selectCell(true);
}
}
```

Arguments:

`args: CellClickArgs<R, SR>`
`args: CellMouseArgs<R, SR>`

- `args.rowIdx`: `number` - row index of the currently selected cell
- `args.row`: `R` - row object of the currently selected cell
Expand All @@ -356,12 +369,12 @@ Arguments:
- `event.preventGridDefault:`: `() => void`
- `event.isGridDefaultPrevented`: `boolean`

###### `onCellDoubleClick?: Maybe<(args: CellClickArgs<R, SR>, event: CellMouseEvent) => void>`
###### `onCellDoubleClick?: Maybe<(args: CellMouseArgs<R, SR>, event: CellMouseEvent) => void>`

Callback triggered when a cell is double-clicked. The default behavior is to open the editor if the cell is editable. Call `preventGridDefault` to prevent the default behavior
Callback triggered when a cell is double-clicked. The default behavior is to open the editor if the cell is editable. Call `preventGridDefault` to prevent the default behavior.

```tsx
function onCellDoubleClick(args: CellClickArgs<R, SR>, event: CellMouseEvent) {
function onCellDoubleClick(args: CellMouseArgs<R, SR>, event: CellMouseEvent) {
if (args.column.key === 'id') {
event.preventGridDefault();
}
Expand All @@ -370,14 +383,15 @@ function onCellDoubleClick(args: CellClickArgs<R, SR>, event: CellMouseEvent) {
<DataGrid rows={rows} columns={columns} onCellDoubleClick={onCellDoubleClick} />;
```

###### `onCellContextMenu?: Maybe<(args: CellClickArgs<R, SR>, event: CellMouseEvent) => void>`
###### `onCellContextMenu?: Maybe<(args: CellMouseArgs<R, SR>, event: CellMouseEvent) => void>`

Callback triggered when a cell is right-clicked. The default behavior is to select the cell. Call `preventGridDefault` to prevent the default behavior
Callback triggered when a cell is right-clicked.

```tsx
function onCellContextMenu(args: CellClickArgs<R, SR>, event: CellMouseEvent) {
function onCellContextMenu(args: CellMouseArgs<R, SR>, event: CellMouseEvent) {
if (args.column.key === 'id') {
event.preventGridDefault();
event.preventDefault();
// open custom context menu
}
}

Expand Down Expand Up @@ -412,11 +426,11 @@ function onCellKeyDown(args: CellKeyDownArgs<R, SR>, event: CellKeyboardEvent) {

Check [more examples](website/routes/CellNavigation.tsx)

###### `onCellCopy?: Maybe<(args: CellCopyEvent<NoInfer<R>, NoInfer<SR>>, event: CellClipboardEvent) => void>`
###### `onCellCopy?: Maybe<(args: CellCopyArgs<NoInfer<R>, NoInfer<SR>>, event: CellClipboardEvent) => void>`

Callback triggered when a cell's content is copied.

###### `onCellPaste?: Maybe<(args: CellPasteEvent<NoInfer<R>, NoInfer<SR>>, event: CellClipboardEvent) => void>`
###### `onCellPaste?: Maybe<(args: CellPasteArgs<NoInfer<R>, NoInfer<SR>>, event: CellClipboardEvent) => void>`

Callback triggered when content is pasted into a cell.

Expand Down
56 changes: 37 additions & 19 deletions src/Cell.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { memo } from 'react';
import { memo, type MouseEvent } from 'react';
import { css } from '@linaria/core';

import { useRovingTabIndex } from './hooks';
import { createCellEvent, getCellClassname, getCellStyle, isCellEditableUtil } from './utils';
import type { CellRendererProps } from './types';
import type { CellMouseEventHandler, CellRendererProps } from './types';

const cellDraggedOver = css`
@layer rdg.Cell {
Expand All @@ -21,9 +21,14 @@ function Cell<R, SR>({
row,
rowIdx,
className,
onMouseDown,
onCellMouseDown,
onClick,
onCellClick,
onDoubleClick,
onCellDoubleClick,
onContextMenu,
onCellContextMenu,
onRowChange,
selectCell,
style,
Expand All @@ -46,31 +51,43 @@ function Cell<R, SR>({
selectCell({ rowIdx, idx: column.idx }, openEditor);
}

function handleClick(event: React.MouseEvent<HTMLDivElement>) {
if (onClick) {
function handleMouseEvent(
event: React.MouseEvent<HTMLDivElement>,
eventHandler?: CellMouseEventHandler<R, SR>
) {
let eventHandled = false;
if (eventHandler) {
const cellEvent = createCellEvent(event);
onClick({ rowIdx, row, column, selectCell: selectCellWrapper }, cellEvent);
if (cellEvent.isGridDefaultPrevented()) return;
eventHandler({ rowIdx, row, column, selectCell: selectCellWrapper }, cellEvent);
eventHandled = cellEvent.isGridDefaultPrevented();
}
selectCellWrapper();
return eventHandled;
}

function handleContextMenu(event: React.MouseEvent<HTMLDivElement>) {
if (onContextMenu) {
const cellEvent = createCellEvent(event);
onContextMenu({ rowIdx, row, column, selectCell: selectCellWrapper }, cellEvent);
if (cellEvent.isGridDefaultPrevented()) return;
function handleMouseDown(event: MouseEvent<HTMLDivElement>) {
onMouseDown?.(event);
if (!handleMouseEvent(event, onCellMouseDown)) {
// select cell if the event is not prevented
selectCellWrapper();
}
selectCellWrapper();
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

onClick and onContextMenu events do not have any default behavior now but we are keeping the same signature as users may want to edit on single click or right click

}

function handleDoubleClick(event: React.MouseEvent<HTMLDivElement>) {
if (onDoubleClick) {
const cellEvent = createCellEvent(event);
onDoubleClick({ rowIdx, row, column, selectCell: selectCellWrapper }, cellEvent);
if (cellEvent.isGridDefaultPrevented()) return;
function handleClick(event: MouseEvent<HTMLDivElement>) {
onClick?.(event);
handleMouseEvent(event, onCellClick);
}

function handleDoubleClick(event: MouseEvent<HTMLDivElement>) {
onDoubleClick?.(event);
if (!handleMouseEvent(event, onCellDoubleClick)) {
// go into edit mode if the event is not prevented
selectCellWrapper(true);
}
selectCellWrapper(true);
}

function handleContextMenu(event: MouseEvent<HTMLDivElement>) {
onContextMenu?.(event);
handleMouseEvent(event, onCellContextMenu);
}

function handleRowChange(newRow: R) {
Expand All @@ -91,6 +108,7 @@ function Cell<R, SR>({
...style
}}
onClick={handleClick}
onMouseDown={handleMouseDown}
onDoubleClick={handleDoubleClick}
onContextMenu={handleContextMenu}
onFocus={onFocus}
Expand Down
48 changes: 20 additions & 28 deletions src/DataGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,13 @@ import {
} from './utils';
import type {
CalculatedColumn,
CellClickArgs,
CellClipboardEvent,
CellCopyEvent,
CellCopyArgs,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed to match other types

CellKeyboardEvent,
CellKeyDownArgs,
CellMouseEvent,
CellMouseEventHandler,
CellNavigationMode,
CellPasteEvent,
CellPasteArgs,
CellSelectArgs,
Column,
ColumnOrColumnGroup,
Expand Down Expand Up @@ -186,29 +185,25 @@ export interface DataGridProps<R, SR = unknown, K extends Key = Key> extends Sha
/**
* Event props
*/
/** Callback triggered when a pointer becomes active in a cell */
onCellMouseDown?: CellMouseEventHandler<R, SR>;
/** Callback triggered when a cell is clicked */
onCellClick?: Maybe<
(args: CellClickArgs<NoInfer<R>, NoInfer<SR>>, event: CellMouseEvent) => void
>;
onCellClick?: CellMouseEventHandler<R, SR>;
/** Callback triggered when a cell is double-clicked */
onCellDoubleClick?: Maybe<
(args: CellClickArgs<NoInfer<R>, NoInfer<SR>>, event: CellMouseEvent) => void
>;
onCellDoubleClick?: CellMouseEventHandler<R, SR>;
/** Callback triggered when a cell is right-clicked */
onCellContextMenu?: Maybe<
(args: CellClickArgs<NoInfer<R>, NoInfer<SR>>, event: CellMouseEvent) => void
>;
onCellContextMenu?: CellMouseEventHandler<R, SR>;
/** Callback triggered when a key is pressed in a cell */
onCellKeyDown?: Maybe<
(args: CellKeyDownArgs<NoInfer<R>, NoInfer<SR>>, event: CellKeyboardEvent) => void
>;
/** Callback triggered when a cell's content is copied */
onCellCopy?: Maybe<
(args: CellCopyEvent<NoInfer<R>, NoInfer<SR>>, event: CellClipboardEvent) => void
(args: CellCopyArgs<NoInfer<R>, NoInfer<SR>>, event: CellClipboardEvent) => void
>;
/** Callback triggered when content is pasted into a cell */
onCellPaste?: Maybe<
(args: CellPasteEvent<NoInfer<R>, NoInfer<SR>>, event: CellClipboardEvent) => NoInfer<R>
(args: CellPasteArgs<NoInfer<R>, NoInfer<SR>>, event: CellClipboardEvent) => NoInfer<R>
>;
/** Function called whenever cell selection is changed */
onSelectedCellChange?: Maybe<(args: CellSelectArgs<NoInfer<R>, NoInfer<SR>>) => void>;
Expand Down Expand Up @@ -274,6 +269,7 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
onSortColumnsChange,
defaultColumnOptions,
// Event props
onCellMouseDown,
onCellClick,
onCellDoubleClick,
onCellContextMenu,
Expand Down Expand Up @@ -493,6 +489,7 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
const handleColumnResizeEndLatest = useLatestFunc(handleColumnResizeEnd);
const onColumnsReorderLastest = useLatestFunc(onColumnsReorder);
const onSortColumnsChangeLatest = useLatestFunc(onSortColumnsChange);
const onCellMouseDownLatest = useLatestFunc(onCellMouseDown);
const onCellClickLatest = useLatestFunc(onCellClick);
const onCellDoubleClickLatest = useLatestFunc(onCellDoubleClick);
const onCellContextMenuLatest = useLatestFunc(onCellContextMenu);
Expand Down Expand Up @@ -525,23 +522,17 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
/**
* effects
*/
useLayoutEffect(() => {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We set shouldFocusCell so both effects were running when focus sink needs to be focused. I have combined them

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This effect didn't depend on shouldFocusCell, am I missing something?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This effect was not checking shouldFocusCell but it was set to true and we were running both effects. I think it is better to only focus when shouldFocusCell flag is true so there are no surprises

Copy link
Collaborator

@nstepien nstepien May 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was it never running when shouldFocusCell was false?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't believe so

if (
focusSinkRef.current !== null &&
selectedCellIsWithinSelectionBounds &&
selectedPosition.idx === -1
) {
focusSinkRef.current.focus({ preventScroll: true });
scrollIntoView(focusSinkRef.current);
}
}, [selectedCellIsWithinSelectionBounds, selectedPosition]);

useLayoutEffect(() => {
if (shouldFocusCell) {
if (focusSinkRef.current !== null && selectedPosition.idx === -1) {
focusSinkRef.current.focus({ preventScroll: true });
scrollIntoView(focusSinkRef.current);
} else {
focusCellOrCellContent();
}
setShouldFocusCell(false);
focusCellOrCellContent();
}
}, [shouldFocusCell, focusCellOrCellContent]);
}, [shouldFocusCell, focusCellOrCellContent, selectedPosition.idx]);

useImperativeHandle(ref, () => ({
element: gridRef.current,
Expand Down Expand Up @@ -1141,6 +1132,7 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
viewportColumns: rowColumns,
isRowSelectionDisabled: isRowSelectionDisabled?.(row) ?? false,
isRowSelected,
onCellMouseDown: onCellMouseDownLatest,
onCellClick: onCellClickLatest,
onCellDoubleClick: onCellDoubleClickLatest,
onCellContextMenu: onCellContextMenuLatest,
Expand Down
4 changes: 4 additions & 0 deletions src/GroupCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ function GroupCell<R, SR>({
...getCellStyle(column),
cursor: isLevelMatching ? 'pointer' : 'default'
}}
onMouseDown={(event) => {
// prevents clicking on the cell from stealing focus from focusSink
event.preventDefault();
}}
onClick={isLevelMatching ? toggleGroup : undefined}
onFocus={onFocus}
>
Expand Down
2 changes: 1 addition & 1 deletion src/GroupRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ function GroupedRow<R, SR>({
selectedCellIdx === -1 && rowSelectedClassname,
className
)}
onClick={handleSelectGroup}
onMouseDown={handleSelectGroup}
style={getRowStyle(gridRowStart)}
{...props}
>
Expand Down
4 changes: 2 additions & 2 deletions src/GroupedColumnHeaderCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export default function GroupedColumnHeaderCell<R, SR>({
const rowSpan = getHeaderCellRowSpan(column, rowIdx);
const index = column.idx + 1;

function onClick() {
function onMouseDown() {
selectCell({ idx: column.idx, rowIdx });
}

Expand All @@ -46,7 +46,7 @@ export default function GroupedColumnHeaderCell<R, SR>({
gridColumnEnd: index + colSpan
}}
onFocus={onFocus}
onClick={onClick}
onMouseDown={onMouseDown}
>
{column.name}
</div>
Expand Down
5 changes: 4 additions & 1 deletion src/HeaderCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,11 @@ export default function HeaderCell<R, SR>({
}
}

function onClick(event: React.MouseEvent<HTMLSpanElement>) {
function onMouseDown() {
selectCell({ idx: column.idx, rowIdx });
}

function onClick(event: React.MouseEvent<HTMLSpanElement>) {
if (sortable) {
onSort(event.ctrlKey || event.metaKey);
}
Expand Down Expand Up @@ -250,6 +252,7 @@ export default function HeaderCell<R, SR>({
...getHeaderCellStyle(column, rowIdx, rowSpan),
...getCellStyle(column, colSpan)
}}
onMouseDown={onMouseDown}
onFocus={onFocus}
onClick={onClick}
onKeyDown={onKeyDown}
Expand Down
8 changes: 5 additions & 3 deletions src/Row.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ function Row<R, SR>({
row,
viewportColumns,
selectedCellEditor,
onCellMouseDown,
onCellClick,
onCellDoubleClick,
onCellContextMenu,
Expand Down Expand Up @@ -67,9 +68,10 @@ function Row<R, SR>({
rowIdx,
isDraggedOver: draggedOverCellIdx === idx,
isCellSelected,
onClick: onCellClick,
onDoubleClick: onCellDoubleClick,
onContextMenu: onCellContextMenu,
onCellMouseDown,
onCellClick,
onCellDoubleClick,
onCellContextMenu,
onRowChange: handleRowChange,
selectCell
})
Expand Down
Loading