feat(stammbaum): family network — graph, badge, edit card, /stammbaum page (#358) #360

Merged
marcel merged 57 commits from feat/stammbaum-issue-358 into main 2026-04-28 19:33:33 +02:00
Showing only changes of commit 93f4a00032 - Show all commits

View File

@@ -286,6 +286,8 @@ function nodeCenter(id: string): { x: number; y: number } | null {
return { x: p.x + NODE_W / 2, y: p.y + NODE_H / 2 }; return { x: p.x + NODE_W / 2, y: p.y + NODE_H / 2 };
} }
let focusedId = $state<string | null>(null);
function handleNodeKey(event: KeyboardEvent, id: string) { function handleNodeKey(event: KeyboardEvent, id: string) {
if (event.key === 'Enter' || event.key === ' ') { if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault(); event.preventDefault();
@@ -479,6 +481,7 @@ const parentLinks = $derived.by<ParentLinks>(() => {
{@const pos = layout.positions.get(node.id)} {@const pos = layout.positions.get(node.id)}
{#if pos} {#if pos}
{@const isSelected = selectedId === node.id} {@const isSelected = selectedId === node.id}
{@const isFocused = focusedId === node.id}
<g <g
role="button" role="button"
tabindex="0" tabindex="0"
@@ -489,8 +492,22 @@ const parentLinks = $derived.by<ParentLinks>(() => {
transform="translate({pos.x}, {pos.y})" transform="translate({pos.x}, {pos.y})"
onclick={() => onSelect(node.id)} onclick={() => onSelect(node.id)}
onkeydown={(e) => handleNodeKey(e, node.id)} onkeydown={(e) => handleNodeKey(e, node.id)}
class="cursor-pointer focus:outline-none focus-visible:ring-2 focus-visible:ring-primary" onfocus={() => (focusedId = node.id)}
onblur={() => (focusedId = null)}
class="cursor-pointer focus:outline-none"
> >
{#if isFocused}
<rect
x="-3"
y="-3"
width={NODE_W + 6}
height={NODE_H + 6}
rx="6"
fill="none"
stroke="var(--c-focus-ring)"
stroke-width="2"
/>
{/if}
<rect <rect
width={NODE_W} width={NODE_W}
height={NODE_H} height={NODE_H}
@@ -504,10 +521,10 @@ const parentLinks = $derived.by<ParentLinks>(() => {
{/if} {/if}
<text <text
x={NODE_W / 2} x={NODE_W / 2}
y={NODE_H / 2 - 4} y={NODE_H / 2 - 6}
text-anchor="middle" text-anchor="middle"
font-family="serif" font-family="serif"
font-size="14" font-size="16"
fill={isSelected ? 'var(--c-primary-fg)' : 'var(--c-ink)'} fill={isSelected ? 'var(--c-primary-fg)' : 'var(--c-ink)'}
> >
{node.displayName} {node.displayName}