Skip to content

Commit f16418d

Browse files
feat: cell activation info (#1069)
* feat: cell activation info * fix spelling error * Make activation prop optional --------- Co-authored-by: lukasmasuch <[email protected]>
1 parent 7c10240 commit f16418d

File tree

7 files changed

+136
-30
lines changed

7 files changed

+136
-30
lines changed

packages/core/API.md

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -624,13 +624,14 @@ export type ProvideEditorCallbackResult<T extends InnerGridCell> =
624624
| undefined;
625625

626626
export type ProvideEditorCallback<T extends InnerGridCell> = (
627-
cell: T & { location?: Item }
627+
cell: T & { location?: Item; activation?: CellActivatedEventArgs }
628628
) => ProvideEditorCallbackResult<T>;
629629

630630
provideEditor?: ProvideEditorCallback<GridCell>;
631631
```
632632

633633
When provided the `provideEditor` callbacks job is to be a constructor for functional components which have the correct properties to be used by the data grid as an editor. The editor must implement `onChange` and `onFinishedEditing` callbacks as well support the `isHighlighted` flag which tells the editor to begin with any editable text pre-selected so typing will immediately begin to overwrite it.
634+
The `cell` passed to this callback includes a `location` of the activated cell and an `activation` event describing how the editor was opened.
634635

635636
---
636637

@@ -1249,10 +1250,18 @@ onCellClicked?: (cell: Item) => void;
12491250
## onCellActivated
12501251

12511252
```ts
1252-
onCellActivated?: (cell: Item) => void;
1253+
onCellActivated?: (
1254+
cell: Item,
1255+
event: CellActivatedEventArgs
1256+
) => void;
12531257
```
12541258

1255-
`onCellActivated` is called whenever the user double clicks, presses Enter or Space, or begins typing with a cell selected.
1259+
`onCellActivated` is called whenever the user double clicks, presses Enter or Space, or begins typing with a cell selected. The second argument describes how the activation occurred.
1260+
1261+
The `event` parameter is one of:
1262+
1263+
- `KeyboardCellActivatedEvent` – contains `inputType: "keyboard"` and a `key` field with the physical key pressed.
1264+
- `PointerCellActivatedEvent` – contains `inputType: "pointer"`, a `pointerActivation` reason such as `"double-click"` or `"single-click"`, and an optional `pointerType` (`"mouse"`, `"touch"`, or `"pen"`).
12561265

12571266
---
12581267

packages/core/src/data-editor/data-editor.tsx

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import {
3131
BooleanIndeterminate,
3232
type FillHandleDirection,
3333
type EditListItem,
34-
type CellActiviationBehavior,
34+
type CellActivationBehavior,
3535
} from "../internal/data-grid/data-grid-types.js";
3636
import DataGridSearch, { type DataGridSearchProps } from "../internal/data-grid-search/data-grid-search.js";
3737
import { browserIsOSX } from "../common/browser-detect.js";
@@ -78,6 +78,7 @@ import {
7878
type GridDragEventArgs,
7979
mouseEventArgsAreEqual,
8080
type GridKeyEventArgs,
81+
type CellActivatedEventArgs,
8182
} from "../internal/data-grid/event-args.js";
8283
import { type Keybinds, useKeybindingsWithDefaults } from "./data-editor-keybindings.js";
8384
import type { Highlight } from "../internal/data-grid/render/data-grid-render.cells.js";
@@ -241,7 +242,7 @@ export interface DataEditorProps extends Props, Pick<DataGridSearchProps, "image
241242
/** Emitted when a cell is activated, by pressing Enter, Space or double clicking it.
242243
* @group Events
243244
*/
244-
readonly onCellActivated?: (cell: Item) => void;
245+
readonly onCellActivated?: (cell: Item, event: CellActivatedEventArgs) => void;
245246

246247
/**
247248
* Emitted whenever the user initiats a pattern fill using the fill handle. This event provides both
@@ -663,7 +664,7 @@ export interface DataEditorProps extends Props, Pick<DataGridSearchProps, "image
663664
* Determines when a cell is considered activated and will emit the `onCellActivated` event. Generally an activated
664665
* cell will open to edit mode.
665666
*/
666-
readonly cellActivationBehavior?: CellActiviationBehavior;
667+
readonly cellActivationBehavior?: CellActivationBehavior;
667668

668669
/**
669670
* Controls if focus will trap inside the data grid when doing tab and caret navigation.
@@ -771,6 +772,7 @@ const DataEditorImpl: React.ForwardRefRenderFunction<DataEditorRef, DataEditorPr
771772
cell: Item;
772773
highlight: boolean;
773774
forceEditMode: boolean;
775+
activation: CellActivatedEventArgs;
774776
}>();
775777
const searchInputRef = React.useRef<HTMLInputElement | null>(null);
776778
const canvasRef = React.useRef<HTMLCanvasElement | null>(null);
@@ -1431,7 +1433,7 @@ const DataEditorImpl: React.ForwardRefRenderFunction<DataEditorRef, DataEditorPr
14311433
);
14321434

14331435
const reselect = React.useCallback(
1434-
(bounds: Rectangle, fromKeyboard: boolean, initialValue?: string) => {
1436+
(bounds: Rectangle, activation: CellActivatedEventArgs, initialValue?: string) => {
14351437
if (gridSelection.current === undefined) return;
14361438

14371439
const [col, row] = gridSelection.current.cell;
@@ -1466,8 +1468,9 @@ const DataEditorImpl: React.ForwardRefRenderFunction<DataEditorRef, DataEditorPr
14661468
cell: [col, row],
14671469
highlight: initialValue === undefined,
14681470
forceEditMode: initialValue !== undefined,
1471+
activation,
14691472
});
1470-
} else if (c.kind === GridCellKind.Boolean && fromKeyboard && c.readonly !== true) {
1473+
} else if (c.kind === GridCellKind.Boolean && activation.inputType === "keyboard" && c.readonly !== true) {
14711474
mangledOnCellsEdited([
14721475
{
14731476
location: gridSelection.current.cell,
@@ -1502,6 +1505,7 @@ const DataEditorImpl: React.ForwardRefRenderFunction<DataEditorRef, DataEditorPr
15021505
highlight: true,
15031506
cell: [col, row],
15041507
forceEditMode: true,
1508+
activation: { inputType: "keyboard", key: "Enter" },
15051509
});
15061510
},
15071511
[getMangledCellContent, scrollRef, setOverlaySimple]
@@ -2058,6 +2062,7 @@ const DataEditorImpl: React.ForwardRefRenderFunction<DataEditorRef, DataEditorPr
20582062
mapper,
20592063
lastRowSticky,
20602064
setCurrent,
2065+
headerRowMarkerDisabled,
20612066
setSelectedColumns,
20622067
setGridSelection,
20632068
onSelectionCleared,
@@ -2387,8 +2392,16 @@ const DataEditorImpl: React.ForwardRefRenderFunction<DataEditorRef, DataEditorPr
23872392
}
23882393
}
23892394
if (shouldActivate) {
2390-
onCellActivated?.([col - rowMarkerOffset, row]);
2391-
reselect(a.bounds, false);
2395+
const act = a.isDoubleClick === true
2396+
? "double-click"
2397+
: (c.activationBehaviorOverride ?? cellActivationBehavior);
2398+
const activationEvent: CellActivatedEventArgs = {
2399+
inputType: "pointer",
2400+
pointerActivation: act,
2401+
pointerType: a.isTouch ? "touch" : "mouse",
2402+
};
2403+
onCellActivated?.([col - rowMarkerOffset, row], activationEvent);
2404+
reselect(a.bounds, activationEvent);
23922405
return true;
23932406
}
23942407
}
@@ -3265,8 +3278,12 @@ const DataEditorImpl: React.ForwardRefRenderFunction<DataEditorRef, DataEditorPr
32653278
void appendRow(customTargetColumn ?? col);
32663279
}, 0);
32673280
} else {
3268-
onCellActivated?.([col - rowMarkerOffset, row]);
3269-
reselect(bounds, true);
3281+
const activationEvent: CellActivatedEventArgs = {
3282+
inputType: "keyboard",
3283+
key: event.key,
3284+
};
3285+
onCellActivated?.([col - rowMarkerOffset, row], activationEvent);
3286+
reselect(bounds, activationEvent);
32703287
}
32713288
} else if (gridSelection.current.range.height > 1 && isHotkey(keys.downFill, event, details)) {
32723289
fillDown();
@@ -3477,8 +3494,12 @@ const DataEditorImpl: React.ForwardRefRenderFunction<DataEditorRef, DataEditorPr
34773494
) {
34783495
return;
34793496
}
3480-
onCellActivated?.([col - rowMarkerOffset, row]);
3481-
reselect(event.bounds, true, event.key);
3497+
const activationEvent: CellActivatedEventArgs = {
3498+
inputType: "keyboard",
3499+
key: event.key,
3500+
};
3501+
onCellActivated?.([col - rowMarkerOffset, row], activationEvent);
3502+
reselect(event.bounds, activationEvent, event.key);
34823503
event.stopPropagation();
34833504
event.preventDefault();
34843505
}
@@ -3543,7 +3564,7 @@ const DataEditorImpl: React.ForwardRefRenderFunction<DataEditorRef, DataEditorPr
35433564
formatted?: string | string[]
35443565
): EditListItem | undefined {
35453566
const stringifiedRawValue =
3546-
typeof rawValue === "object" ? rawValue?.join("\n") ?? "" : rawValue?.toString() ?? "";
3567+
typeof rawValue === "object" ? (rawValue?.join("\n") ?? "") : (rawValue?.toString() ?? "");
35473568

35483569
if (!isInnerOnlyCell(inner) && isReadWriteCell(inner) && inner.readonly !== true) {
35493570
const coerced = coercePasteValue?.(stringifiedRawValue, inner);

packages/core/src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ export * from "./internal/data-grid/data-grid-types.js";
1111
export type {
1212
BaseGridMouseEventArgs,
1313
CellClickedEventArgs,
14+
CellActivatedEventArgs,
15+
KeyboardCellActivatedEvent,
16+
PointerCellActivatedEvent,
1417
DragHandler,
1518
FillPatternEventArgs,
1619
GridDragEventArgs,

packages/core/src/internal/data-grid-overlay-editor/data-grid-overlay-editor.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import {
1616
type Rectangle,
1717
type ValidatedGridCell,
1818
} from "../data-grid/data-grid-types.js";
19+
20+
import type { CellActivatedEventArgs } from "../data-grid/event-args.js";
1921
import { DataGridOverlayEditorStyle } from "./data-grid-overlay-editor-style.js";
2022
import type { OverlayImageEditorProps } from "./private/image-overlay-editor.js";
2123
import { useStayOnScreen } from "./use-stay-on-screen.js";
@@ -39,6 +41,7 @@ interface DataGridOverlayEditorProps {
3941
readonly getCellRenderer: GetCellRendererCallback;
4042
readonly markdownDivCreateNode?: (content: string) => DocumentFragment;
4143
readonly provideEditor?: ProvideEditorCallback<GridCell>;
44+
readonly activation: CellActivatedEventArgs;
4245
readonly validateCell?: (
4346
cell: Item,
4447
newValue: EditableGridCell,
@@ -69,6 +72,7 @@ const DataGridOverlayEditor: React.FunctionComponent<DataGridOverlayEditorProps>
6972
provideEditor,
7073
isOutsideClick,
7174
customEventTarget,
75+
activation,
7276
} = p;
7377

7478
const [tempValue, setTempValueRaw] = React.useState<GridCell | undefined>(forceEditMode ? content : undefined);
@@ -160,13 +164,14 @@ const DataGridOverlayEditor: React.FunctionComponent<DataGridOverlayEditorProps>
160164

161165
const [editorProvider, useLabel] = React.useMemo((): [ProvideEditorCallbackResult<GridCell>, boolean] | [] => {
162166
if (isInnerOnlyCell(content)) return [];
163-
const cellWithLocation = { ...content, location: cell } as GridCell & {
167+
const cellWithLocation = { ...content, location: cell, activation } as GridCell & {
164168
location: Item;
169+
activation: CellActivatedEventArgs;
165170
};
166171
const external = provideEditor?.(cellWithLocation);
167172
if (external !== undefined) return [external, false];
168173
return [getCellRenderer(content)?.provideEditor?.(cellWithLocation), false];
169-
}, [cell, content, getCellRenderer, provideEditor]);
174+
}, [cell, content, getCellRenderer, provideEditor, activation]);
170175

171176
const { ref, style: stayOnScreenStyle } = useStayOnScreen();
172177

@@ -187,6 +192,7 @@ const DataGridOverlayEditor: React.FunctionComponent<DataGridOverlayEditorProps>
187192
<CustomEditor
188193
portalElementRef={portalElementRef}
189194
isHighlighted={highlight}
195+
activation={activation}
190196
onChange={setTempValue}
191197
value={targetValue}
192198
initialValue={initialValue}

packages/core/src/internal/data-grid/data-grid-types.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import type { Theme } from "../../common/styles.js";
55
import { assertNever, proveType } from "../../common/support.js";
66
import type { OverlayImageEditorProps } from "../data-grid-overlay-editor/private/image-overlay-editor.js";
77
import type { SpriteManager } from "./data-grid-sprites.js";
8-
import type { BaseGridMouseEventArgs } from "./event-args.js";
8+
import type { BaseGridMouseEventArgs, CellActivatedEventArgs } from "./event-args.js";
99
import type { ImageWindowLoader } from "./image-window-loader-interface.js";
1010

1111
// Thoughts:
@@ -305,7 +305,7 @@ export function isRectangleEqual(a: Rectangle | undefined, b: Rectangle | undefi
305305
return a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height;
306306
}
307307

308-
export type CellActiviationBehavior = "double-click" | "single-click" | "second-click";
308+
export type CellActivationBehavior = "double-click" | "single-click" | "second-click";
309309

310310
/** @category Cells */
311311
export interface BaseGridCell {
@@ -317,7 +317,7 @@ export interface BaseGridCell {
317317
readonly contentAlign?: "left" | "right" | "center";
318318
readonly cursor?: CSSProperties["cursor"];
319319
readonly copyData?: string;
320-
readonly activationBehaviorOverride?: CellActiviationBehavior;
320+
readonly activationBehaviorOverride?: CellActivationBehavior;
321321
}
322322

323323
/** @category Cells */
@@ -396,6 +396,7 @@ export type ProvideEditorComponent<T extends InnerGridCell> = React.FunctionComp
396396
readonly isValid?: boolean;
397397
readonly theme: Theme;
398398
readonly portalElementRef?: React.RefObject<HTMLElement>;
399+
readonly activation?: CellActivatedEventArgs;
399400
}>;
400401

401402
type ObjectEditorCallbackResult<T extends InnerGridCell> = {
@@ -424,7 +425,7 @@ export function isObjectEditorCallbackResult<T extends InnerGridCell>(
424425

425426
/** @category Renderers */
426427
export type ProvideEditorCallback<T extends InnerGridCell> = (
427-
cell: T & { location?: Item }
428+
cell: T & { location?: Item; activation?: CellActivatedEventArgs }
428429
) => ProvideEditorCallbackResult<T>;
429430

430431
/** @category Cells */

packages/core/src/internal/data-grid/event-args.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { Item, Rectangle } from "./data-grid-types.js";
1+
import type { Item, Rectangle, CellActivationBehavior } from "./data-grid-types.js";
22

33
/** @category Types */
44
export interface BaseGridMouseEventArgs {
@@ -101,6 +101,26 @@ export interface HeaderClickedEventArgs extends GridMouseHeaderEventArgs, Preven
101101
/** @category Types */
102102
export interface GroupHeaderClickedEventArgs extends GridMouseGroupHeaderEventArgs, PreventableEvent {}
103103

104+
export interface BaseCellActivatedEvent {}
105+
106+
/** Keyboard-initiated activation */
107+
export interface KeyboardCellActivatedEvent extends BaseCellActivatedEvent {
108+
readonly inputType: "keyboard";
109+
readonly key: string;
110+
}
111+
112+
/** Pointer-initiated activation */
113+
export interface PointerCellActivatedEvent extends BaseCellActivatedEvent {
114+
readonly inputType: "pointer";
115+
readonly pointerActivation: CellActivationBehavior;
116+
readonly pointerType?: "mouse" | "touch" | "pen";
117+
}
118+
119+
/** The public event type the grid emits */
120+
export type CellActivatedEventArgs =
121+
| KeyboardCellActivatedEvent
122+
| PointerCellActivatedEvent;
123+
104124
export interface FillPatternEventArgs extends PreventableEvent {
105125
patternSource: Rectangle;
106126
fillDestination: Rectangle;

0 commit comments

Comments
 (0)