feat(stammbaum): edge-fade mask when zoomed past fit (#692)
Permanent 4-edge mask-image gradient cues off-screen content when the tree is zoomed in; nothing fades at fit. Replaces the dropped US-PAN-006 AC3 idle cue. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -133,6 +133,19 @@ const viewBox = $derived.by(() => {
|
||||
return `${cx - w / 2} ${cy - h / 2} ${w} ${h}`;
|
||||
});
|
||||
|
||||
// Permanent edge-fade affordance (#692, replaces US-PAN-006 AC3). When the tree
|
||||
// is zoomed past fit, content is clipped at the viewport edges, so a 24px fade
|
||||
// on all four edges cues that more tree exists off-screen. Zero JS beyond this
|
||||
// reactive style; nothing fades at fit (z <= 1, whole tree visible).
|
||||
const EDGE_FADE = 24;
|
||||
const maskStyle = $derived(
|
||||
panZoom.z > 1
|
||||
? `-webkit-mask-image:linear-gradient(to right,transparent,#000 ${EDGE_FADE}px,#000 calc(100% - ${EDGE_FADE}px),transparent),linear-gradient(to bottom,transparent,#000 ${EDGE_FADE}px,#000 calc(100% - ${EDGE_FADE}px),transparent);` +
|
||||
`mask-image:linear-gradient(to right,transparent,#000 ${EDGE_FADE}px,#000 calc(100% - ${EDGE_FADE}px),transparent),linear-gradient(to bottom,transparent,#000 ${EDGE_FADE}px,#000 calc(100% - ${EDGE_FADE}px),transparent);` +
|
||||
`-webkit-mask-composite:source-in;mask-composite:intersect;`
|
||||
: ''
|
||||
);
|
||||
|
||||
function nodeCenter(id: string): { x: number; y: number } | null {
|
||||
const p = layout.positions.get(id);
|
||||
if (!p) return null;
|
||||
@@ -273,6 +286,7 @@ const parentLinks = $derived.by<ParentLinks>(() => {
|
||||
role="img"
|
||||
aria-label="Stammbaum"
|
||||
tabindex="0"
|
||||
style={maskStyle}
|
||||
onkeydown={handleCanvasKey}
|
||||
use:panZoomGestures={{
|
||||
state: panZoom,
|
||||
|
||||
@@ -610,6 +610,28 @@ describe('StammbaumTree keyboard pan/zoom (#692)', () => {
|
||||
expect(view.z).toBe(1);
|
||||
});
|
||||
|
||||
it('omits the edge-fade mask at fit (z = 1) (#692)', async () => {
|
||||
render(StammbaumTree, {
|
||||
nodes: [{ id: ID_A, displayName: 'Anna', familyMember: true }],
|
||||
edges: [],
|
||||
selectedId: null,
|
||||
panZoom: { x: 0, y: 0, z: 1 },
|
||||
onSelect: () => {}
|
||||
});
|
||||
expect(document.querySelector('svg')!.getAttribute('style') ?? '').not.toContain('mask-image');
|
||||
});
|
||||
|
||||
it('applies the edge-fade mask when zoomed past fit (#692)', async () => {
|
||||
render(StammbaumTree, {
|
||||
nodes: [{ id: ID_A, displayName: 'Anna', familyMember: true }],
|
||||
edges: [],
|
||||
selectedId: null,
|
||||
panZoom: { x: 0, y: 0, z: 2 },
|
||||
onSelect: () => {}
|
||||
});
|
||||
expect(document.querySelector('svg')!.getAttribute('style') ?? '').toContain('mask-image');
|
||||
});
|
||||
|
||||
it('does not call onSelect for other keys', async () => {
|
||||
const onSelect = vi.fn();
|
||||
render(StammbaumTree, {
|
||||
|
||||
Reference in New Issue
Block a user