fix(transcription): replace broken HTML5 drag with pointer-based drag
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CI / Unit & Component Tests (pull_request) Has been cancelled
CI / Backend Unit Tests (pull_request) Has been cancelled
CI / E2E Tests (pull_request) Has been cancelled

HTML5 drag-and-drop didn't work — the grip handle couldn't initiate
drag properly. Replace with pointer event-based drag:

- Grip handle pointerdown starts drag, captures pointer
- Pointermove tracks offset, shows floaty style (shadow, scale, ring)
- Turquoise drop indicator line appears between blocks at cursor position
- Pointerup finalizes: reorders array and calls PUT /reorder endpoint

Visual feedback:
- Dragged block: shadow-xl, ring-2 ring-turquoise/40, scale 1.02, opacity 0.9
- Drop indicator: turquoise h-1 rounded bar between blocks

6 new TranscriptionEditView tests:
- renders blocks in sort order
- shows next-block CTA
- shows empty state
- move-up disabled on first block
- move-down disabled on last block
- drag handle present on each block

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-04-05 23:07:42 +02:00
parent 7d2d615e0c
commit c22f2e41b1
2 changed files with 142 additions and 35 deletions

View File

@@ -0,0 +1,78 @@
import { describe, it, expect, vi, afterEach } from 'vitest';
import { cleanup, render } from 'vitest-browser-svelte';
import { page } from 'vitest/browser';
import TranscriptionEditView from './TranscriptionEditView.svelte';
afterEach(cleanup);
const block1 = {
id: 'b1',
annotationId: 'a1',
documentId: 'doc-1',
text: 'Block eins',
label: null,
sortOrder: 0,
version: 0
};
const block2 = {
id: 'b2',
annotationId: 'a2',
documentId: 'doc-1',
text: 'Block zwei',
label: null,
sortOrder: 1,
version: 0
};
function renderView(overrides: Record<string, unknown> = {}) {
return render(TranscriptionEditView, {
documentId: 'doc-1',
blocks: [block1, block2],
canComment: true,
currentUserId: 'user-1',
onBlockFocus: vi.fn(),
onSaveBlock: vi.fn(),
onDeleteBlock: vi.fn(),
...overrides
});
}
describe('TranscriptionEditView — rendering', () => {
it('renders blocks in sort order', async () => {
renderView();
const textareas = page.getByRole('textbox').all();
expect(textareas.length).toBeGreaterThanOrEqual(2);
});
it('shows next-block CTA after block list', async () => {
renderView();
await expect.element(page.getByText(/Block 3/)).toBeInTheDocument();
});
it('shows empty state when no blocks', async () => {
renderView({ blocks: [] });
await expect.element(page.getByText(/Markiere einen Bereich/)).toBeInTheDocument();
});
});
describe('TranscriptionEditView — reorder', () => {
it('renders move-up button disabled on first block', async () => {
renderView();
const upButtons = page.getByRole('button', { name: 'Nach oben' }).all();
// First block's up button should be disabled
await expect.element(upButtons[0]).toBeDisabled();
});
it('renders move-down button disabled on last block', async () => {
renderView();
const downButtons = page.getByRole('button', { name: 'Nach unten' }).all();
// Last block's down button should be disabled
await expect.element(downButtons[downButtons.length - 1]).toBeDisabled();
});
it('has a drag handle on each block', async () => {
renderView();
const handles = document.querySelectorAll('[data-drag-handle]');
expect(handles.length).toBe(2);
});
});