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 };
}
let focusedId = $state<string | null>(null);
function handleNodeKey(event: KeyboardEvent, id: string) {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
@@ -479,6 +481,7 @@ const parentLinks = $derived.by<ParentLinks>(() => {
{@const pos = layout.positions.get(node.id)}
{#if pos}
{@const isSelected = selectedId === node.id}
{@const isFocused = focusedId === node.id}
<g
role="button"
tabindex="0"
@@ -489,8 +492,22 @@ const parentLinks = $derived.by<ParentLinks>(() => {
transform="translate({pos.x}, {pos.y})"
onclick={() => onSelect(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
width={NODE_W}
height={NODE_H}
@@ -504,10 +521,10 @@ const parentLinks = $derived.by<ParentLinks>(() => {
{/if}
<text
x={NODE_W / 2}
y={NODE_H / 2 - 4}
y={NODE_H / 2 - 6}
text-anchor="middle"
font-family="serif"
font-size="14"
font-size="16"
fill={isSelected ? 'var(--c-primary-fg)' : 'var(--c-ink)'}
>
{node.displayName}