feat: open a calendar event's menu with Shift+F10

This commit is contained in:
Linus Rath
2026-10-29 17:53:53 +01:00
parent 76ad807477
commit db596ac2ec
2 changed files with 37 additions and 2 deletions
@@ -1,5 +1,5 @@
import { render, screen, cleanup } from '@testing-library/react';
import { describe, it, expect, afterEach, beforeEach } from 'vitest';
import { render, screen, cleanup, fireEvent } from '@testing-library/react';
import { describe, it, expect, afterEach, beforeEach, vi } from 'vitest';
import { EventCard } from '../event-card';
import { useSettingsStore } from '@/stores/settings-store';
import type { Calendar, CalendarEvent, CalendarParticipant } from '@/lib/jmap/types';
@@ -139,3 +139,20 @@ describe('EventCard colour settings', () => {
expect(calendar.color).toBe('#22c55e');
});
});
describe('EventCard from the keyboard (MA-05)', () => {
afterEach(cleanup);
it.each(['chip', 'span', 'block'] as const)('opens the event menu with Shift+F10 on the %s', (variant) => {
const onContextMenu = vi.fn();
const event = makeOccurrence('a', 'accepted');
render(<EventCard event={event} variant={variant} onContextMenu={onContextMenu} currentUserEmails={me} />);
const card = screen.getByRole('button');
fireEvent.keyDown(card, { key: 'F10' });
expect(onContextMenu).not.toHaveBeenCalled();
fireEvent.keyDown(card, { key: 'F10', shiftKey: true });
expect(onContextMenu).toHaveBeenCalledOnce();
expect(onContextMenu.mock.calls[0][1]).toBe(event);
expect(onContextMenu.mock.calls[0][0].currentTarget).toBe(card);
});
});
+18
View File
@@ -191,6 +191,21 @@ export function EventCard({ event, calendar, variant, onClick, onMouseEnter, onM
preventDefault: () => {}, stopPropagation: () => {},
} as unknown as React.MouseEvent, event);
}, !!onContextMenu);
// Shift+F10 opens the same menu from the keyboard (the menu key already
// fires contextmenu by itself). Prevented here, so a browser that also
// turns Shift+F10 into a contextmenu event doesn't open it twice.
const handleKeyDown = onContextMenu ? (e: React.KeyboardEvent<HTMLElement>) => {
if (!(e.shiftKey && e.key === "F10")) return;
e.preventDefault();
e.stopPropagation();
const target = e.currentTarget;
const rect = target.getBoundingClientRect();
onContextMenu({
type: "contextmenu", clientX: rect.left, clientY: rect.bottom, detail: 0, currentTarget: target, target,
preventDefault: () => {}, stopPropagation: () => {},
} as unknown as React.MouseEvent, event);
} : undefined;
const touchProps = onContextMenu ? {
onTouchStart: (e: React.TouchEvent<HTMLElement>) => { pressedRef.current = e.currentTarget; longPress.onTouchStart(e); },
onTouchMove: longPress.onTouchMove,
@@ -205,6 +220,7 @@ export function EventCard({ event, calendar, variant, onClick, onMouseEnter, onM
onMouseEnter={(e) => onMouseEnter?.(e.currentTarget.getBoundingClientRect())}
onMouseLeave={() => onMouseLeave?.()}
onContextMenu={handleContextMenu}
onKeyDown={handleKeyDown}
{...touchProps}
aria-label={ariaLabel}
tabIndex={tabbable ? undefined : -1}
@@ -236,6 +252,7 @@ export function EventCard({ event, calendar, variant, onClick, onMouseEnter, onM
onMouseEnter={(e) => onMouseEnter?.(e.currentTarget.getBoundingClientRect())}
onMouseLeave={() => onMouseLeave?.()}
onContextMenu={handleContextMenu}
onKeyDown={handleKeyDown}
{...touchProps}
aria-label={ariaLabel}
tabIndex={tabbable ? undefined : -1}
@@ -270,6 +287,7 @@ export function EventCard({ event, calendar, variant, onClick, onMouseEnter, onM
onMouseEnter={(e) => onMouseEnter?.(e.currentTarget.getBoundingClientRect())}
onMouseLeave={() => onMouseLeave?.()}
onContextMenu={handleContextMenu}
onKeyDown={handleKeyDown}
{...touchProps}
aria-label={ariaLabel}
tabIndex={tabbable ? undefined : -1}