diff --git a/frontend/src/lib/person/genealogy/StammbaumBottomSheet.svelte b/frontend/src/lib/person/genealogy/StammbaumBottomSheet.svelte new file mode 100644 index 00000000..22cf3234 --- /dev/null +++ b/frontend/src/lib/person/genealogy/StammbaumBottomSheet.svelte @@ -0,0 +1,74 @@ + + + + + + diff --git a/frontend/src/lib/person/genealogy/StammbaumBottomSheet.svelte.test.ts b/frontend/src/lib/person/genealogy/StammbaumBottomSheet.svelte.test.ts new file mode 100644 index 00000000..a0c1f72c --- /dev/null +++ b/frontend/src/lib/person/genealogy/StammbaumBottomSheet.svelte.test.ts @@ -0,0 +1,46 @@ +import { describe, it, expect, vi } from 'vitest'; +import { render } from 'vitest-browser-svelte'; +import StammbaumBottomSheet from './StammbaumBottomSheet.svelte'; + +const node = { id: 'p-1', displayName: 'Anna Schmidt', familyMember: true }; + +describe('StammbaumBottomSheet (#692)', () => { + it('renders as a dialog with the person name as its accessible name', async () => { + render(StammbaumBottomSheet, { node, canWrite: false, onClose: () => {} }); + const dialog = document.querySelector('[role="dialog"]')!; + expect(dialog).toBeTruthy(); + expect(dialog.getAttribute('aria-label')).toBe('Anna Schmidt'); + }); + + it('dismisses on Escape', async () => { + const onClose = vi.fn(); + render(StammbaumBottomSheet, { node, canWrite: false, onClose }); + const dialog = document.querySelector('[role="dialog"]') as HTMLElement; + dialog.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true })); + expect(onClose).toHaveBeenCalled(); + }); + + it('dismisses when the backdrop is tapped', async () => { + const onClose = vi.fn(); + render(StammbaumBottomSheet, { node, canWrite: false, onClose }); + const backdrop = document.querySelector('button[aria-label]') as HTMLButtonElement; + backdrop.click(); + expect(onClose).toHaveBeenCalled(); + }); + + it('dismisses on a downward swipe past the threshold', async () => { + const onClose = vi.fn(); + render(StammbaumBottomSheet, { node, canWrite: false, onClose }); + const handle = document.querySelector('[role="dialog"] > div') as HTMLElement; + handle.dispatchEvent( + new PointerEvent('pointerdown', { pointerId: 1, clientY: 100, bubbles: true }) + ); + handle.dispatchEvent( + new PointerEvent('pointermove', { pointerId: 1, clientY: 220, bubbles: true }) + ); + handle.dispatchEvent( + new PointerEvent('pointerup', { pointerId: 1, clientY: 220, bubbles: true }) + ); + expect(onClose).toHaveBeenCalled(); + }); +}); diff --git a/frontend/src/routes/stammbaum/+page.svelte b/frontend/src/routes/stammbaum/+page.svelte index 8d8923e2..084f60db 100644 --- a/frontend/src/routes/stammbaum/+page.svelte +++ b/frontend/src/routes/stammbaum/+page.svelte @@ -5,6 +5,7 @@ import { page } from '$app/state'; import { replaceState } from '$app/navigation'; import StammbaumTree from '$lib/person/genealogy/StammbaumTree.svelte'; import StammbaumSidePanel from '$lib/person/genealogy/StammbaumSidePanel.svelte'; +import StammbaumBottomSheet from '$lib/person/genealogy/StammbaumBottomSheet.svelte'; import StammbaumControls from '$lib/person/genealogy/StammbaumControls.svelte'; import { type PanZoomState, @@ -127,16 +128,12 @@ $effect(() => { onClose={() => (selectedId = null)} /> - -
- (selectedId = null)} - /> -
+ + (selectedId = null)} + /> {/if} {/if}