feat(annotation): add dimmed prop to AnnotationLayer
Hides block number badges and disables hover/active visual feedback when dimmed=true. Click handlers remain active for scroll-sync. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,7 @@ let {
|
|||||||
color,
|
color,
|
||||||
blockNumbers = {},
|
blockNumbers = {},
|
||||||
activeAnnotationId = null,
|
activeAnnotationId = null,
|
||||||
|
dimmed = false,
|
||||||
onDraw,
|
onDraw,
|
||||||
onAnnotationClick
|
onAnnotationClick
|
||||||
}: {
|
}: {
|
||||||
@@ -22,6 +23,7 @@ let {
|
|||||||
color: string;
|
color: string;
|
||||||
blockNumbers?: Record<string, number>;
|
blockNumbers?: Record<string, number>;
|
||||||
activeAnnotationId?: string | null;
|
activeAnnotationId?: string | null;
|
||||||
|
dimmed?: boolean;
|
||||||
onDraw: (rect: DrawRect) => void;
|
onDraw: (rect: DrawRect) => void;
|
||||||
onAnnotationClick?: (id: string) => void;
|
onAnnotationClick?: (id: string) => void;
|
||||||
} = $props();
|
} = $props();
|
||||||
@@ -123,15 +125,15 @@ 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 || annotation.id === activeAnnotationId ? 0.5 : 0.3)};
|
background-color: {hexToRgba(annotation.color, dimmed ? 0.3 : (hoveredId === annotation.id || annotation.id === activeAnnotationId ? 0.5 : 0.3))};
|
||||||
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'};
|
box-shadow: {dimmed ? 'none' : (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};
|
opacity: {dimmed ? 1 : (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, opacity 0.3s ease;
|
transition: background-color 0.15s ease, box-shadow 0.15s ease, opacity 0.3s ease;
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
{#if blockNumbers[annotation.id]}
|
{#if !dimmed && blockNumbers[annotation.id]}
|
||||||
<div
|
<div
|
||||||
style="
|
style="
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|||||||
67
frontend/src/lib/components/AnnotationLayer.svelte.test.ts
Normal file
67
frontend/src/lib/components/AnnotationLayer.svelte.test.ts
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { render } from 'vitest-browser-svelte';
|
||||||
|
import { page } from 'vitest/browser';
|
||||||
|
import AnnotationLayer from './AnnotationLayer.svelte';
|
||||||
|
import type { Annotation } from '$lib/types';
|
||||||
|
|
||||||
|
const annotation: Annotation = {
|
||||||
|
id: 'ann-1',
|
||||||
|
documentId: 'doc-1',
|
||||||
|
pageNumber: 1,
|
||||||
|
x: 0.1,
|
||||||
|
y: 0.2,
|
||||||
|
width: 0.3,
|
||||||
|
height: 0.1,
|
||||||
|
color: '#00c7b1',
|
||||||
|
createdAt: '2026-01-01T00:00:00Z'
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('AnnotationLayer', () => {
|
||||||
|
describe('dimmed prop', () => {
|
||||||
|
it('should hide block number badges when dimmed is true', async () => {
|
||||||
|
render(AnnotationLayer, {
|
||||||
|
annotations: [annotation],
|
||||||
|
canDraw: false,
|
||||||
|
color: '#00c7b1',
|
||||||
|
blockNumbers: { 'ann-1': 1 },
|
||||||
|
dimmed: true,
|
||||||
|
onDraw: () => {}
|
||||||
|
});
|
||||||
|
|
||||||
|
const badge = page.getByText('1');
|
||||||
|
await expect.element(badge).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show block number badges when dimmed is false', async () => {
|
||||||
|
render(AnnotationLayer, {
|
||||||
|
annotations: [annotation],
|
||||||
|
canDraw: false,
|
||||||
|
color: '#00c7b1',
|
||||||
|
blockNumbers: { 'ann-1': 1 },
|
||||||
|
dimmed: false,
|
||||||
|
onDraw: () => {}
|
||||||
|
});
|
||||||
|
|
||||||
|
const badge = page.getByText('1');
|
||||||
|
await expect.element(badge).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should still fire onAnnotationClick when dimmed', async () => {
|
||||||
|
let clickedId: string | undefined;
|
||||||
|
render(AnnotationLayer, {
|
||||||
|
annotations: [annotation],
|
||||||
|
canDraw: false,
|
||||||
|
color: '#00c7b1',
|
||||||
|
dimmed: true,
|
||||||
|
onDraw: () => {},
|
||||||
|
onAnnotationClick: (id: string) => {
|
||||||
|
clickedId = id;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const el = document.querySelector('[data-testid="annotation-ann-1"]')!;
|
||||||
|
el.dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
||||||
|
expect(clickedId).toBe('ann-1');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user