feat(ui): add bidirectional scroll-sync with flash animations

Paragraph click flashes the PDF annotation outline (1.5s fade).
Annotation click highlights the paragraph with a background flash.
Both directions scroll the target into view.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-04-07 11:25:23 +02:00
parent e089192d7a
commit 81b14e5026
4 changed files with 33 additions and 1 deletions

View File

@@ -15,6 +15,7 @@ let {
blockNumbers = {},
activeAnnotationId = null,
dimmed = false,
flashAnnotationId = null,
onDraw,
onAnnotationClick
}: {
@@ -24,6 +25,7 @@ let {
blockNumbers?: Record<string, number>;
activeAnnotationId?: string | null;
dimmed?: boolean;
flashAnnotationId?: string | null;
onDraw: (rect: DrawRect) => void;
onAnnotationClick?: (id: string) => void;
} = $props();
@@ -110,6 +112,7 @@ const containerStyle = $derived(
<div
data-testid="annotation-{annotation.id}"
data-annotation
class:annotation-flash={flashAnnotationId === annotation.id}
role="button"
tabindex="0"
aria-label="Block anzeigen"
@@ -175,3 +178,20 @@ const containerStyle = $derived(
></div>
{/if}
</div>
<style>
@keyframes annotation-flash-anim {
0% {
outline: 3px solid rgba(0, 199, 177, 0.8);
outline-offset: 0px;
}
100% {
outline: 3px solid rgba(0, 199, 177, 0);
outline-offset: 2px;
}
}
.annotation-flash {
animation: annotation-flash-anim 1.5s ease-out;
}
</style>

View File

@@ -21,6 +21,7 @@ type Props = {
annotationReloadKey?: number;
activeAnnotationId: string | null;
annotationsDimmed?: boolean;
flashAnnotationId?: string | null;
onAnnotationClick: (id: string) => void;
onTranscriptionDraw?: (rect: DrawRect) => void;
};
@@ -35,6 +36,7 @@ let {
annotationReloadKey = 0,
activeAnnotationId = $bindable(),
annotationsDimmed = false,
flashAnnotationId = null,
onAnnotationClick,
onTranscriptionDraw
}: Props = $props();
@@ -93,6 +95,7 @@ let {
annotationReloadKey={annotationReloadKey}
bind:activeAnnotationId={activeAnnotationId}
annotationsDimmed={annotationsDimmed}
flashAnnotationId={flashAnnotationId}
onAnnotationClick={onAnnotationClick}
onTranscriptionDraw={onTranscriptionDraw}
documentFileHash={doc.fileHash ?? null}

View File

@@ -17,7 +17,8 @@ let {
onAnnotationClick,
onTranscriptionDraw,
documentFileHash,
annotationsDimmed = false
annotationsDimmed = false,
flashAnnotationId = null
}: {
url: string;
documentId?: string;
@@ -29,6 +30,7 @@ let {
onTranscriptionDraw?: (rect: DrawRect) => void;
documentFileHash?: string | null;
annotationsDimmed?: boolean;
flashAnnotationId?: string | null;
} = $props();
let pdfDoc = $state<PDFDocumentProxy | null>(null);
@@ -459,6 +461,7 @@ function zoomOut() {
blockNumbers={blockNumbers}
activeAnnotationId={activeAnnotationId}
dimmed={annotationsDimmed}
flashAnnotationId={flashAnnotationId}
onDraw={handleDraw}
onAnnotationClick={handleAnnotationClick}
/>

View File

@@ -54,6 +54,7 @@ let transcribeMode = $state(false);
let panelMode = $state<'read' | 'edit'>('read');
let activeAnnotationId = $state<string | null>(null);
let highlightBlockId = $state<string | null>(null);
let flashAnnotationId = $state<string | null>(null);
// ── Transcription blocks ─────────────────────────────────────────────────────
@@ -177,6 +178,10 @@ async function handleAnnotationClick(annotationId: string) {
function handleParagraphClick(annotationId: string) {
activeAnnotationId = annotationId;
flashAnnotationId = annotationId;
setTimeout(() => {
flashAnnotationId = null;
}, 1500);
}
// Load blocks when transcribe mode is entered and set default panel mode
@@ -241,6 +246,7 @@ onMount(() => {
blockNumbers={blockNumbers}
annotationReloadKey={annotationReloadKey}
annotationsDimmed={transcribeMode && panelMode === 'read'}
flashAnnotationId={flashAnnotationId}
bind:activeAnnotationId={activeAnnotationId}
onAnnotationClick={handleAnnotationClick}
onTranscriptionDraw={createBlockFromDraw}