feat(annotations): dim non-active annotations when a block is focused
When activeAnnotationId is set, the active annotation stays at full opacity with a highlight box-shadow, while all other annotations fade to 30% opacity (300ms ease transition). When no block is focused, all annotations show at full opacity. Prop chain: activeAnnotationId flows from PdfViewer → AnnotationLayer. 2 new tests (RED/GREEN): - dims non-active annotations when activeAnnotationId is set - shows all at full opacity when no activeAnnotationId Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,7 @@ let {
|
|||||||
canDraw,
|
canDraw,
|
||||||
color,
|
color,
|
||||||
blockNumbers = {},
|
blockNumbers = {},
|
||||||
|
activeAnnotationId = null,
|
||||||
onDraw,
|
onDraw,
|
||||||
onAnnotationClick
|
onAnnotationClick
|
||||||
}: {
|
}: {
|
||||||
@@ -20,6 +21,7 @@ let {
|
|||||||
canDraw: boolean;
|
canDraw: boolean;
|
||||||
color: string;
|
color: string;
|
||||||
blockNumbers?: Record<string, number>;
|
blockNumbers?: Record<string, number>;
|
||||||
|
activeAnnotationId?: string | null;
|
||||||
onDraw: (rect: DrawRect) => void;
|
onDraw: (rect: DrawRect) => void;
|
||||||
onAnnotationClick?: (id: string) => void;
|
onAnnotationClick?: (id: string) => void;
|
||||||
} = $props();
|
} = $props();
|
||||||
@@ -121,11 +123,12 @@ const containerStyle = $derived(
|
|||||||
top: {annotation.y * 100}%;
|
top: {annotation.y * 100}%;
|
||||||
width: {annotation.width * 100}%;
|
width: {annotation.width * 100}%;
|
||||||
height: {annotation.height * 100}%;
|
height: {annotation.height * 100}%;
|
||||||
background-color: {hexToRgba(annotation.color, hoveredId === annotation.id ? 0.5 : 0.3)};
|
background-color: {hexToRgba(annotation.color, hoveredId === annotation.id || annotation.id === activeAnnotationId ? 0.5 : 0.3)};
|
||||||
box-shadow: {hoveredId === annotation.id ? `inset 0 0 0 2px ${hexToRgba(annotation.color, 0.8)}` : 'none'};
|
box-shadow: {annotation.id === activeAnnotationId ? `inset 0 0 0 2px ${hexToRgba(annotation.color, 0.8)}` : hoveredId === annotation.id ? `inset 0 0 0 2px ${hexToRgba(annotation.color, 0.8)}` : 'none'};
|
||||||
|
opacity: {activeAnnotationId && annotation.id !== activeAnnotationId ? 0.3 : 1};
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background-color 0.15s ease, box-shadow 0.15s ease;
|
transition: background-color 0.15s ease, box-shadow 0.15s ease, opacity 0.3s ease;
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
{#if blockNumbers[annotation.id]}
|
{#if blockNumbers[annotation.id]}
|
||||||
|
|||||||
@@ -69,6 +69,35 @@ describe('AnnotationLayer', () => {
|
|||||||
expect(container.getAttribute('style')).not.toContain('cursor: crosshair');
|
expect(container.getAttribute('style')).not.toContain('cursor: crosshair');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('dims non-active annotations when activeAnnotationId is set', async () => {
|
||||||
|
render(AnnotationLayer, {
|
||||||
|
annotations: [makeAnnotation('ann-1'), makeAnnotation('ann-2')],
|
||||||
|
canDraw: false,
|
||||||
|
color: '#00C7B1',
|
||||||
|
activeAnnotationId: 'ann-1',
|
||||||
|
onDraw: () => {}
|
||||||
|
});
|
||||||
|
|
||||||
|
const active = page.getByTestId('annotation-ann-1').element();
|
||||||
|
const dimmed = page.getByTestId('annotation-ann-2').element();
|
||||||
|
expect(active.style.opacity).toBe('1');
|
||||||
|
expect(dimmed.style.opacity).toBe('0.3');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows all annotations at full opacity when no activeAnnotationId', async () => {
|
||||||
|
render(AnnotationLayer, {
|
||||||
|
annotations: [makeAnnotation('ann-1'), makeAnnotation('ann-2')],
|
||||||
|
canDraw: false,
|
||||||
|
color: '#00C7B1',
|
||||||
|
onDraw: () => {}
|
||||||
|
});
|
||||||
|
|
||||||
|
const el1 = page.getByTestId('annotation-ann-1').element();
|
||||||
|
const el2 = page.getByTestId('annotation-ann-2').element();
|
||||||
|
expect(el1.style.opacity).toBe('1');
|
||||||
|
expect(el2.style.opacity).toBe('1');
|
||||||
|
});
|
||||||
|
|
||||||
it('does not show delete buttons (annotations owned by blocks)', async () => {
|
it('does not show delete buttons (annotations owned by blocks)', async () => {
|
||||||
render(AnnotationLayer, {
|
render(AnnotationLayer, {
|
||||||
annotations: [makeAnnotation('ann-1')],
|
annotations: [makeAnnotation('ann-1')],
|
||||||
|
|||||||
@@ -447,6 +447,7 @@ function zoomOut() {
|
|||||||
canDraw={transcribeMode}
|
canDraw={transcribeMode}
|
||||||
color={TRANSCRIPTION_COLOR}
|
color={TRANSCRIPTION_COLOR}
|
||||||
blockNumbers={blockNumbers}
|
blockNumbers={blockNumbers}
|
||||||
|
activeAnnotationId={activeAnnotationId}
|
||||||
onDraw={handleDraw}
|
onDraw={handleDraw}
|
||||||
onAnnotationClick={handleAnnotationClick}
|
onAnnotationClick={handleAnnotationClick}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user