feat(stammbaum): drive viewBox from PanZoomState (pan + zoom) (#692)
Replace the scalar zoom prop with a {x,y,z} PanZoomState. The viewBox centre
is offset by the pan and width/height scaled by zoom; the default {0,0,1}
frames the whole tree (fit-to-screen). Page header buttons now step view.z
through clampZoom over the resolved 0.25–3.0 range.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -36,12 +36,35 @@ function rectsCentroid(svg: SVGElement): { x: number; y: number } {
|
||||
}
|
||||
|
||||
describe('StammbaumTree viewBox', () => {
|
||||
it('offsets the viewBox origin by the pan state (#692)', async () => {
|
||||
render(StammbaumTree, {
|
||||
nodes: [{ id: ID_A, displayName: 'Anna', familyMember: true }],
|
||||
edges: [],
|
||||
selectedId: null,
|
||||
panZoom: { x: 100, y: 40, z: 1 },
|
||||
showGutter: false,
|
||||
onSelect: () => {}
|
||||
});
|
||||
|
||||
const svg = document.querySelector('svg')!;
|
||||
const [x, y, w, h] = parseViewBox(svg);
|
||||
|
||||
// Same dimensions as the unpanned default (z=1)…
|
||||
expect(w).toBe(1200);
|
||||
expect(h).toBe(800);
|
||||
|
||||
// …but the origin is shifted by the pan offset.
|
||||
const unpannedX = -(1200 / 2 - 160 / 2); // single 160-wide node centred
|
||||
expect(x).toBeCloseTo(unpannedX + 100, 6);
|
||||
expect(y).toBeCloseTo(-(800 / 2 - 56 / 2) + 40, 6);
|
||||
});
|
||||
|
||||
it('uses the minimum size and centers a single node', async () => {
|
||||
render(StammbaumTree, {
|
||||
nodes: [{ id: ID_A, displayName: 'Anna', familyMember: true }],
|
||||
edges: [],
|
||||
selectedId: null,
|
||||
zoom: 1,
|
||||
panZoom: { x: 0, y: 0, z: 1 },
|
||||
onSelect: () => {}
|
||||
});
|
||||
|
||||
@@ -114,7 +137,7 @@ describe('StammbaumTree viewBox', () => {
|
||||
}
|
||||
],
|
||||
selectedId: null,
|
||||
zoom: 1,
|
||||
panZoom: { x: 0, y: 0, z: 1 },
|
||||
onSelect: () => {}
|
||||
});
|
||||
|
||||
@@ -174,7 +197,7 @@ describe('StammbaumTree viewBox', () => {
|
||||
}
|
||||
],
|
||||
selectedId: null,
|
||||
zoom: 1,
|
||||
panZoom: { x: 0, y: 0, z: 1 },
|
||||
onSelect: () => {}
|
||||
});
|
||||
|
||||
@@ -277,7 +300,7 @@ describe('StammbaumTree viewBox', () => {
|
||||
}
|
||||
],
|
||||
selectedId: null,
|
||||
zoom: 1,
|
||||
panZoom: { x: 0, y: 0, z: 1 },
|
||||
onSelect: () => {}
|
||||
});
|
||||
|
||||
@@ -335,7 +358,7 @@ describe('StammbaumTree viewBox', () => {
|
||||
}
|
||||
],
|
||||
selectedId: null,
|
||||
zoom: 1,
|
||||
panZoom: { x: 0, y: 0, z: 1 },
|
||||
onSelect: () => {}
|
||||
});
|
||||
|
||||
@@ -368,7 +391,7 @@ describe('StammbaumTree viewBox', () => {
|
||||
}
|
||||
],
|
||||
selectedId: null,
|
||||
zoom: 1,
|
||||
panZoom: { x: 0, y: 0, z: 1 },
|
||||
onSelect: () => {}
|
||||
});
|
||||
|
||||
@@ -393,7 +416,7 @@ describe('StammbaumTree node rendering branches', () => {
|
||||
],
|
||||
edges: [],
|
||||
selectedId: ID_A,
|
||||
zoom: 1,
|
||||
panZoom: { x: 0, y: 0, z: 1 },
|
||||
onSelect: () => {}
|
||||
});
|
||||
|
||||
@@ -409,7 +432,7 @@ describe('StammbaumTree node rendering branches', () => {
|
||||
],
|
||||
edges: [],
|
||||
selectedId: null,
|
||||
zoom: 1,
|
||||
panZoom: { x: 0, y: 0, z: 1 },
|
||||
onSelect: () => {}
|
||||
});
|
||||
|
||||
@@ -422,7 +445,7 @@ describe('StammbaumTree node rendering branches', () => {
|
||||
nodes: [{ id: ID_A, displayName: 'Anna', familyMember: true, deathYear: 1950 }],
|
||||
edges: [],
|
||||
selectedId: null,
|
||||
zoom: 1,
|
||||
panZoom: { x: 0, y: 0, z: 1 },
|
||||
onSelect: () => {}
|
||||
});
|
||||
|
||||
@@ -434,7 +457,7 @@ describe('StammbaumTree node rendering branches', () => {
|
||||
nodes: [{ id: ID_A, displayName: 'Anna', familyMember: true }],
|
||||
edges: [],
|
||||
selectedId: null,
|
||||
zoom: 1,
|
||||
panZoom: { x: 0, y: 0, z: 1 },
|
||||
onSelect: () => {}
|
||||
});
|
||||
|
||||
@@ -447,7 +470,7 @@ describe('StammbaumTree node rendering branches', () => {
|
||||
nodes: [{ id: ID_A, displayName: 'Anna', familyMember: true }],
|
||||
edges: [],
|
||||
selectedId: null,
|
||||
zoom: 1,
|
||||
panZoom: { x: 0, y: 0, z: 1 },
|
||||
onSelect
|
||||
});
|
||||
|
||||
@@ -462,7 +485,7 @@ describe('StammbaumTree node rendering branches', () => {
|
||||
nodes: [{ id: ID_A, displayName: 'Anna', familyMember: true }],
|
||||
edges: [],
|
||||
selectedId: null,
|
||||
zoom: 1,
|
||||
panZoom: { x: 0, y: 0, z: 1 },
|
||||
onSelect
|
||||
});
|
||||
|
||||
@@ -478,7 +501,7 @@ describe('StammbaumTree node rendering branches', () => {
|
||||
nodes: [{ id: ID_A, displayName: 'Anna', familyMember: true }],
|
||||
edges: [],
|
||||
selectedId: null,
|
||||
zoom: 1,
|
||||
panZoom: { x: 0, y: 0, z: 1 },
|
||||
onSelect
|
||||
});
|
||||
|
||||
@@ -493,7 +516,7 @@ describe('StammbaumTree node rendering branches', () => {
|
||||
nodes: [{ id: ID_A, displayName: 'Anna', familyMember: true }],
|
||||
edges: [],
|
||||
selectedId: null,
|
||||
zoom: 1,
|
||||
panZoom: { x: 0, y: 0, z: 1 },
|
||||
onSelect
|
||||
});
|
||||
|
||||
@@ -520,7 +543,7 @@ describe('StammbaumTree node rendering branches', () => {
|
||||
}
|
||||
],
|
||||
selectedId: null,
|
||||
zoom: 1,
|
||||
panZoom: { x: 0, y: 0, z: 1 },
|
||||
onSelect: () => {}
|
||||
});
|
||||
|
||||
@@ -547,7 +570,7 @@ describe('StammbaumTree node rendering branches', () => {
|
||||
}
|
||||
],
|
||||
selectedId: null,
|
||||
zoom: 1,
|
||||
panZoom: { x: 0, y: 0, z: 1 },
|
||||
onSelect: () => {}
|
||||
});
|
||||
|
||||
@@ -575,7 +598,7 @@ describe('StammbaumTree node rendering branches', () => {
|
||||
}
|
||||
],
|
||||
selectedId: null,
|
||||
zoom: 1,
|
||||
panZoom: { x: 0, y: 0, z: 1 },
|
||||
onSelect: () => {}
|
||||
});
|
||||
|
||||
@@ -588,7 +611,7 @@ describe('StammbaumTree node rendering branches', () => {
|
||||
nodes: [{ id: ID_A, displayName: 'Anna', familyMember: true }],
|
||||
edges: [],
|
||||
selectedId: null,
|
||||
zoom: 1,
|
||||
panZoom: { x: 0, y: 0, z: 1 },
|
||||
onSelect: () => {}
|
||||
});
|
||||
|
||||
@@ -607,7 +630,7 @@ describe('StammbaumTree node rendering branches', () => {
|
||||
nodes: [{ id: ID_A, displayName: 'Anna', familyMember: true }],
|
||||
edges: [],
|
||||
selectedId: null,
|
||||
zoom: 1,
|
||||
panZoom: { x: 0, y: 0, z: 1 },
|
||||
onSelect: () => {}
|
||||
});
|
||||
|
||||
@@ -636,7 +659,7 @@ describe('StammbaumTree node rendering branches', () => {
|
||||
],
|
||||
edges: [],
|
||||
selectedId: null,
|
||||
zoom: 1,
|
||||
panZoom: { x: 0, y: 0, z: 1 },
|
||||
onSelect: () => {}
|
||||
});
|
||||
|
||||
@@ -653,7 +676,7 @@ describe('StammbaumTree node rendering branches', () => {
|
||||
],
|
||||
edges: [],
|
||||
selectedId: ID_A,
|
||||
zoom: 1,
|
||||
panZoom: { x: 0, y: 0, z: 1 },
|
||||
onSelect: () => {}
|
||||
});
|
||||
|
||||
@@ -674,7 +697,7 @@ describe('StammbaumTree node rendering branches', () => {
|
||||
],
|
||||
edges: [],
|
||||
selectedId: ID_A,
|
||||
zoom: 1,
|
||||
panZoom: { x: 0, y: 0, z: 1 },
|
||||
onSelect: () => {}
|
||||
});
|
||||
|
||||
@@ -696,7 +719,7 @@ describe('StammbaumTree generation gutter (#689)', () => {
|
||||
],
|
||||
edges: [],
|
||||
selectedId: null,
|
||||
zoom: 1,
|
||||
panZoom: { x: 0, y: 0, z: 1 },
|
||||
onSelect: () => {},
|
||||
showGutter: true
|
||||
});
|
||||
@@ -713,7 +736,7 @@ describe('StammbaumTree generation gutter (#689)', () => {
|
||||
nodes: [{ id: ID_A, displayName: 'Herbert', familyMember: true, generation: 3 }],
|
||||
edges: [],
|
||||
selectedId: null,
|
||||
zoom: 1,
|
||||
panZoom: { x: 0, y: 0, z: 1 },
|
||||
onSelect: () => {},
|
||||
showGutter: true
|
||||
});
|
||||
@@ -730,7 +753,7 @@ describe('StammbaumTree generation gutter (#689)', () => {
|
||||
nodes: [{ id: ID_A, displayName: 'Herbert', familyMember: true, generation: 3 }],
|
||||
edges: [],
|
||||
selectedId: null,
|
||||
zoom: 1,
|
||||
panZoom: { x: 0, y: 0, z: 1 },
|
||||
onSelect: () => {},
|
||||
showGutter: false
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user