feat(transcribe): wire keyboard shortcuts into the document panel (#327)
Attaches the transcribeShortcuts action to the document page and wires every command to existing context setters: j/k walk the sortOrder-sorted regions and set activeAnnotationId, e toggles read/edit, n arms a draw cue (edit only), Delete routes to the existing confirm path, ? opens the cheatsheet, and Esc is now owned solely by the action — the inline onMount Esc listener is removed (decision B1). Renders ShortcutCheatsheet and a draw-armed hint. "t" toggles the document-level KURRENT_RECOGNITION training enrollment (the only training surface that exists; there is no per-region flag yet — see #321) and no-ops unless a region is active. Also reconciles annotation Delete: the shape no longer self-handles the key, with onfocus syncing the active region so the action deletes exactly once. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -43,7 +43,6 @@ describe('AnnotationShape', () => {
|
||||
isHovered: true,
|
||||
isActive: true,
|
||||
showDelete: true,
|
||||
onDeleteRequest: vi.fn(),
|
||||
onclick: () => {},
|
||||
onpointerenter: () => {},
|
||||
onpointerleave: () => {}
|
||||
@@ -57,16 +56,17 @@ describe('AnnotationShape', () => {
|
||||
expect(annotationEl.querySelectorAll('button').length).toBe(0);
|
||||
});
|
||||
|
||||
it('calls onDeleteRequest when Delete key is pressed on the annotation', async () => {
|
||||
const onDeleteRequest = vi.fn();
|
||||
|
||||
// Deletion is owned solely by the transcribeShortcuts action (issue #327,
|
||||
// decision: action is the single Delete owner). The shape must NOT handle
|
||||
// the Delete key itself, or the key would delete twice.
|
||||
it('does not act on the Delete key itself (the action owns deletion)', async () => {
|
||||
const onclick = vi.fn();
|
||||
render(AnnotationShape, {
|
||||
annotation: makeAnnotation(),
|
||||
isHovered: false,
|
||||
isActive: true,
|
||||
showDelete: true,
|
||||
onDeleteRequest,
|
||||
onclick: () => {},
|
||||
onclick,
|
||||
onpointerenter: () => {},
|
||||
onpointerleave: () => {}
|
||||
});
|
||||
@@ -74,26 +74,58 @@ describe('AnnotationShape', () => {
|
||||
const annotationEl = page.getByTestId('annotation-ann-1').element() as HTMLElement;
|
||||
annotationEl.dispatchEvent(new KeyboardEvent('keydown', { key: 'Delete', bubbles: true }));
|
||||
|
||||
expect(onDeleteRequest).toHaveBeenCalledOnce();
|
||||
// No side effect from the shape; it stays in the document for the action to act on.
|
||||
expect(onclick).not.toHaveBeenCalled();
|
||||
await expect.element(page.getByTestId('annotation-ann-1')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does not call onDeleteRequest on Delete key when showDelete is false', async () => {
|
||||
const onDeleteRequest = vi.fn();
|
||||
|
||||
it('announces the Delete affordance via aria when deletion is available', async () => {
|
||||
render(AnnotationShape, {
|
||||
annotation: makeAnnotation(),
|
||||
isHovered: false,
|
||||
isActive: true,
|
||||
showDelete: false,
|
||||
onDeleteRequest,
|
||||
showDelete: true,
|
||||
onclick: () => {},
|
||||
onpointerenter: () => {},
|
||||
onpointerleave: () => {}
|
||||
});
|
||||
|
||||
const annotationEl = page.getByTestId('annotation-ann-1').element() as HTMLElement;
|
||||
annotationEl.dispatchEvent(new KeyboardEvent('keydown', { key: 'Delete', bubbles: true }));
|
||||
expect(annotationEl.getAttribute('aria-keyshortcuts')).toBe('Delete');
|
||||
expect(annotationEl.getAttribute('aria-label')).toContain('Entf');
|
||||
});
|
||||
|
||||
expect(onDeleteRequest).not.toHaveBeenCalled();
|
||||
it('keeps the plain label and no key hint when deletion is unavailable', async () => {
|
||||
render(AnnotationShape, {
|
||||
annotation: makeAnnotation(),
|
||||
isHovered: false,
|
||||
isActive: false,
|
||||
showDelete: false,
|
||||
onclick: () => {},
|
||||
onpointerenter: () => {},
|
||||
onpointerleave: () => {}
|
||||
});
|
||||
|
||||
const annotationEl = page.getByTestId('annotation-ann-1').element() as HTMLElement;
|
||||
expect(annotationEl.getAttribute('aria-keyshortcuts')).toBe(null);
|
||||
expect(annotationEl.getAttribute('aria-label')).toBe('Block anzeigen');
|
||||
});
|
||||
|
||||
it('calls onfocus when the annotation receives focus', async () => {
|
||||
const onfocus = vi.fn();
|
||||
render(AnnotationShape, {
|
||||
annotation: makeAnnotation(),
|
||||
isHovered: false,
|
||||
isActive: false,
|
||||
onfocus,
|
||||
onclick: () => {},
|
||||
onpointerenter: () => {},
|
||||
onpointerleave: () => {}
|
||||
});
|
||||
|
||||
const annotationEl = page.getByTestId('annotation-ann-1').element() as HTMLElement;
|
||||
annotationEl.dispatchEvent(new FocusEvent('focus'));
|
||||
|
||||
expect(onfocus).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user