Skip to content

Commit b7b2ef2

Browse files
author
annaha
committed
Merge branch 'fix-context-menu-keyboard-support' into pre-release
2 parents 408a543 + 8b65628 commit b7b2ef2

4 files changed

Lines changed: 142 additions & 102 deletions

File tree

packages/core/src/data-editor/data-editor-keybindings.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ export interface ConfigurableKeybinds {
6767
readonly selectAll: Keybind;
6868
readonly selectRow: Keybind;
6969
readonly selectColumn: Keybind;
70+
71+
readonly contextMenu: Keybind;
7072
}
7173

7274
export type Keybinds = ConfigurableKeybinds & ForcedKeybinds & Partial<BackCompatKeybinds>;
@@ -118,6 +120,7 @@ export const keybindingDefaults: Keybinds = {
118120
selectGrowRight: true,
119121
selectGrowDown: true,
120122
selectGrowLeft: true,
123+
contextMenu: true,
121124
};
122125

123126
function realizeKeybind(keybind: Keybind, defaultVal: string): string {
@@ -174,6 +177,7 @@ export function realizeKeybinds(keybinds: Keybinds): RealizedKeybinds {
174177
selectToLastCell: realizeKeybind(keybinds.selectToLastCell, "primary+shift+End"),
175178
selectToLastColumn: realizeKeybind(keybinds.selectToLastColumn, "primary+shift+ArrowRight"),
176179
selectToLastRow: realizeKeybind(keybinds.selectToLastRow, "primary+shift+ArrowDown"),
180+
contextMenu: realizeKeybind(keybinds.contextMenu, "shift+F10"),
177181
};
178182
}
179183

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

Lines changed: 66 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3220,6 +3220,42 @@ const DataEditorImpl: React.ForwardRefRenderFunction<DataEditorRef, DataEditorPr
32203220

32213221
const overlayOpen = overlay !== undefined;
32223222

3223+
const onContextMenu = React.useCallback(
3224+
(args: GridMouseEventArgs, preventDefault: () => void) => {
3225+
const adjustedCol = args.location[0] - rowMarkerOffset;
3226+
if (args.kind === "header") {
3227+
onHeaderContextMenu?.(adjustedCol, { ...args, preventDefault });
3228+
}
3229+
3230+
if (args.kind === groupHeaderKind) {
3231+
if (adjustedCol < 0) {
3232+
return;
3233+
}
3234+
onGroupHeaderContextMenu?.(adjustedCol, { ...args, preventDefault });
3235+
}
3236+
3237+
if (args.kind === "cell") {
3238+
const [col, row] = args.location;
3239+
onCellContextMenu?.([adjustedCol, row], {
3240+
...args,
3241+
preventDefault,
3242+
});
3243+
3244+
if (!gridSelectionHasItem(gridSelection, args.location)) {
3245+
updateSelectedCell(col, row, false, false);
3246+
}
3247+
}
3248+
},
3249+
[
3250+
gridSelection,
3251+
onCellContextMenu,
3252+
onGroupHeaderContextMenu,
3253+
onHeaderContextMenu,
3254+
rowMarkerOffset,
3255+
updateSelectedCell,
3256+
]
3257+
);
3258+
32233259
const handleFixedKeybindings = React.useCallback(
32243260
(event: GridKeyEventArgs): boolean => {
32253261
const cancel = () => {
@@ -3397,6 +3433,35 @@ const DataEditorImpl: React.ForwardRefRenderFunction<DataEditorRef, DataEditorPr
33973433
col = Number.MAX_SAFE_INTEGER;
33983434
} else if (isHotkey(keys.goToFirstColumn, event, details)) {
33993435
col = Number.MIN_SAFE_INTEGER;
3436+
} else if (
3437+
isHotkey(keys.contextMenu, event, details) &&
3438+
bounds !== undefined &&
3439+
event.location !== undefined
3440+
) {
3441+
const {
3442+
location,
3443+
ctrlKey,
3444+
metaKey,
3445+
shiftKey,
3446+
} = event;
3447+
3448+
onContextMenu(
3449+
{
3450+
kind: "cell",
3451+
isFillHandle: false,
3452+
isTouch: false,
3453+
isEdge: false,
3454+
button: 0,
3455+
scrollEdge: [0, 0],
3456+
localEventX: bounds.width / 2,
3457+
localEventY: bounds.height / 2,
3458+
location,
3459+
bounds,
3460+
ctrlKey,
3461+
metaKey,
3462+
shiftKey,
3463+
buttons: 0
3464+
}, cancel)
34003465
} else if (rangeSelect === "rect" || rangeSelect === "multi-rect") {
34013466
if (isHotkey(keys.selectGrowDown, event, details)) {
34023467
adjustSelection([0, 1]);
@@ -3486,6 +3551,7 @@ const DataEditorImpl: React.ForwardRefRenderFunction<DataEditorRef, DataEditorPr
34863551
return didMatch;
34873552
},
34883553
[
3554+
onContextMenu,
34893555
rowGroupingNavBehavior,
34903556
overlayOpen,
34913557
gridSelection,
@@ -3579,42 +3645,6 @@ const DataEditorImpl: React.ForwardRefRenderFunction<DataEditorRef, DataEditorPr
35793645
]
35803646
);
35813647

3582-
const onContextMenu = React.useCallback(
3583-
(args: GridMouseEventArgs, preventDefault: () => void) => {
3584-
const adjustedCol = args.location[0] - rowMarkerOffset;
3585-
if (args.kind === "header") {
3586-
onHeaderContextMenu?.(adjustedCol, { ...args, preventDefault });
3587-
}
3588-
3589-
if (args.kind === groupHeaderKind) {
3590-
if (adjustedCol < 0) {
3591-
return;
3592-
}
3593-
onGroupHeaderContextMenu?.(adjustedCol, { ...args, preventDefault });
3594-
}
3595-
3596-
if (args.kind === "cell") {
3597-
const [col, row] = args.location;
3598-
onCellContextMenu?.([adjustedCol, row], {
3599-
...args,
3600-
preventDefault,
3601-
});
3602-
3603-
if (!gridSelectionHasItem(gridSelection, args.location)) {
3604-
updateSelectedCell(col, row, false, false);
3605-
}
3606-
}
3607-
},
3608-
[
3609-
gridSelection,
3610-
onCellContextMenu,
3611-
onGroupHeaderContextMenu,
3612-
onHeaderContextMenu,
3613-
rowMarkerOffset,
3614-
updateSelectedCell,
3615-
]
3616-
);
3617-
36183648
const onPasteInternal = React.useCallback(
36193649
async (e?: ClipboardEvent) => {
36203650
if (!keybindings.paste) return;

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

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1283,25 +1283,6 @@ const DataGrid: React.ForwardRefRenderFunction<DataGridRef, DataGridProps> = (p,
12831283
);
12841284
useEventListener("contextmenu", onContextMenuImpl, eventTargetRef?.current ?? null, false);
12851285

1286-
const onContextMenuWithKeyboardImpl = React.useCallback(
1287-
(ev: MouseEvent) => {
1288-
const canvas = ref.current;
1289-
const currentSelectedCell = selectionRef.current.current?.cell
1290-
if (canvas === null || onContextMenu === undefined || currentSelectedCell === undefined)
1291-
return;
1292-
const bounds = getBoundsForItem(canvas, ...currentSelectedCell);
1293-
if (bounds) {
1294-
const args = getMouseArgsForPosition(canvas, bounds.x + bounds.width / 2, bounds.y + bounds.height / 2, ev);
1295-
onContextMenu(args, () => {
1296-
if (ev.cancelable) ev.preventDefault();
1297-
});
1298-
}
1299-
},
1300-
[getMouseArgsForPosition, onContextMenu, getBoundsForItem]
1301-
);
1302-
1303-
useEventListener("contextmenu", onContextMenuWithKeyboardImpl, ref?.current ?? null, false);
1304-
13051286
const onAnimationFrame = React.useCallback<StepCallback>(values => {
13061287
damageRegion.current = new CellSet(values.map(x => x.item));
13071288
hoverValues.current = values;

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

Lines changed: 72 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -196,53 +196,6 @@ describe("data-editor", () => {
196196
expect(spySelection).not.toHaveBeenCalled();
197197
});
198198

199-
test("opens contextmenu at selected cell position when fired on canvas", async () => {
200-
const spy = vi.fn();
201-
202-
vi.useFakeTimers();
203-
render(<DataEditor
204-
{...basicProps}
205-
gridSelection={{
206-
columns: CompactSelection.empty(),
207-
rows: CompactSelection.empty(),
208-
current: {
209-
cell: [1, 1],
210-
range: { x: 1, y: 1, width: 1, height: 1 },
211-
rangeStack: [],
212-
},
213-
}}
214-
onCellContextMenu={spy}
215-
/>, { wrapper: Context });
216-
217-
prep();
218-
219-
const canvas = screen.getByTestId("data-grid-canvas");
220-
221-
fireEvent.contextMenu(canvas);
222-
223-
expect(spy).toHaveBeenCalledWith([1, 1], expect.anything());
224-
});
225-
226-
test("does not open contextmenu when no cell selected", async () => {
227-
const spy = vi.fn();
228-
229-
vi.useFakeTimers();
230-
render(<DataEditor
231-
{...basicProps}
232-
gridSelection={{
233-
columns: CompactSelection.empty(),
234-
rows: CompactSelection.empty(),
235-
}}
236-
onCellContextMenu={spy}
237-
/>, { wrapper: Context });
238-
239-
prep();
240-
const canvas = screen.getByTestId("data-grid-canvas");
241-
fireEvent.contextMenu(canvas);
242-
243-
expect(spy).not.toHaveBeenCalled();
244-
});
245-
246199
test("middle click does not change selection", async () => {
247200
const spySelection = vi.fn();
248201

@@ -1196,6 +1149,78 @@ describe("data-editor", () => {
11961149
expect(spy).toHaveBeenCalledWith(expect.objectContaining({ location: [1, 1] }));
11971150
});
11981151

1152+
test("opens context menu with Shift+F10 when cell is selected", async () => {
1153+
const spy = vi.fn();
1154+
1155+
vi.useFakeTimers();
1156+
render(<DataEditor {...basicProps} onCellContextMenu={spy} />, {
1157+
wrapper: Context,
1158+
});
1159+
prep(false);
1160+
1161+
const canvas = screen.getByTestId("data-grid-canvas");
1162+
sendClick(canvas, {
1163+
clientX: 300,
1164+
clientY: 84,
1165+
});
1166+
1167+
fireEvent.keyDown(canvas, {
1168+
key: "F10",
1169+
keyCode: 121,
1170+
shiftKey: true,
1171+
});
1172+
1173+
expect(spy).toHaveBeenCalledWith([1, 1], expect.anything());
1174+
1175+
const eventArgs = spy.mock.calls[0][1];
1176+
expect(eventArgs).toMatchObject({
1177+
kind: "cell",
1178+
shiftKey: true,
1179+
location: [1, 1],
1180+
bounds: expect.objectContaining({
1181+
x: expect.any(Number),
1182+
y: expect.any(Number),
1183+
width: expect.any(Number),
1184+
height: expect.any(Number),
1185+
}),
1186+
localEventX: eventArgs.bounds.width / 2,
1187+
localEventY: eventArgs.bounds.height / 2,
1188+
});
1189+
1190+
});
1191+
1192+
test("does not open context menu with Shift+F10 when no cells are selected", async () => {
1193+
const spy = vi.fn();
1194+
1195+
vi.useFakeTimers();
1196+
render(
1197+
<DataEditor
1198+
{...basicProps}
1199+
onCellContextMenu={spy}
1200+
gridSelection={{
1201+
columns: CompactSelection.empty(),
1202+
rows: CompactSelection.empty(),
1203+
current: undefined,
1204+
}}
1205+
/>,
1206+
{
1207+
wrapper: Context,
1208+
}
1209+
);
1210+
prep(false);
1211+
1212+
const canvas = screen.getByTestId("data-grid-canvas");
1213+
1214+
// Press Shift+F10 without selecting any cell
1215+
fireEvent.keyDown(canvas, {
1216+
key: "F10",
1217+
keyCode: 121,
1218+
shiftKey: true,
1219+
});
1220+
1221+
expect(spy).not.toHaveBeenCalled();
1222+
});
1223+
11991224
test("Delete cell", async () => {
12001225
const spy = vi.fn();
12011226

0 commit comments

Comments
 (0)