fix(bulk-upload): no layout shift, no autofocus on date field

Replace JS navHeight measurement with CSS var(--header-height) so the fixed
panel renders in its final position on first paint — no onMount shift.

Add autofocus prop to WhoWhenSection (default true, preserves document-edit
behaviour) and pass autofocus={false} from BulkDocumentEditLayout so the date
field does not steal focus before the user has even dropped any files.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-04-24 19:32:23 +02:00
committed by marcel
parent c1b221412f
commit 01a8654347
2 changed files with 8 additions and 11 deletions

View File

@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { SvelteMap } from 'svelte/reactivity'; import { SvelteMap } from 'svelte/reactivity';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { onMount, onDestroy, untrack } from 'svelte'; import { onDestroy, untrack } from 'svelte';
import { m } from '$lib/paraglide/messages.js'; import { m } from '$lib/paraglide/messages.js';
import BulkDropZone from './BulkDropZone.svelte'; import BulkDropZone from './BulkDropZone.svelte';
import FileSwitcherStrip from './FileSwitcherStrip.svelte'; import FileSwitcherStrip from './FileSwitcherStrip.svelte';
@@ -27,12 +27,6 @@ let {
initialReceivers?: Person[]; initialReceivers?: Person[];
} = $props(); } = $props();
// --- Layout ---
let navHeight = $state(0);
onMount(() => {
navHeight = document.querySelector('header')?.getBoundingClientRect().height ?? 0;
});
// --- File state --- // --- File state ---
let files = new SvelteMap<string, FileEntry>(); let files = new SvelteMap<string, FileEntry>();
let activeId = $state<string | null>(null); let activeId = $state<string | null>(null);
@@ -124,7 +118,7 @@ async function save() {
} }
</script> </script>
<div class="fixed inset-x-0 bottom-0 flex flex-col" style="top: {navHeight}px"> <div class="fixed inset-x-0 bottom-0 flex flex-col" style="top: var(--header-height)">
<!-- Topbar --> <!-- Topbar -->
<div class="flex shrink-0 items-center gap-3 border-b border-line bg-surface px-6 py-3"> <div class="flex shrink-0 items-center gap-3 border-b border-line bg-surface px-6 py-3">
<a <a
@@ -226,6 +220,7 @@ async function save() {
bind:selectedReceivers={selectedReceivers} bind:selectedReceivers={selectedReceivers}
bind:dateIso={dateIso} bind:dateIso={dateIso}
initialSenderName={initialSenderName} initialSenderName={initialSenderName}
autofocus={false}
/> />
<DescriptionSection bind:tags={tags} hideTitle /> <DescriptionSection bind:tags={tags} hideTitle />
</ScopeCard> </ScopeCard>

View File

@@ -16,7 +16,8 @@ let {
initialLocation = '', initialLocation = '',
initialSenderName = '', initialSenderName = '',
suggestedDateIso = '', suggestedDateIso = '',
suggestedSenderName = '' suggestedSenderName = '',
autofocus = true
}: { }: {
senderId?: string; senderId?: string;
selectedReceivers?: Person[]; selectedReceivers?: Person[];
@@ -26,6 +27,7 @@ let {
initialSenderName?: string; initialSenderName?: string;
suggestedDateIso?: string; suggestedDateIso?: string;
suggestedSenderName?: string; suggestedSenderName?: string;
autofocus?: boolean;
} = $props(); } = $props();
let dateDisplay = $state(untrack(() => isoToGerman(initialDateIso))); let dateDisplay = $state(untrack(() => isoToGerman(initialDateIso)));
@@ -69,7 +71,7 @@ $effect(() => {
oninput={handleDateInput} oninput={handleDateInput}
placeholder={m.form_placeholder_date()} placeholder={m.form_placeholder_date()}
maxlength="10" maxlength="10"
autofocus={!initialDateIso} autofocus={autofocus && !initialDateIso}
class="block w-full rounded border border-line p-2 text-sm shadow-sm class="block w-full rounded border border-line p-2 text-sm shadow-sm
{dateInvalid ? 'border-red-400 focus:outline-none focus-visible:ring-2 focus-visible:ring-red-500' : 'focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring'}" {dateInvalid ? 'border-red-400 focus:outline-none focus-visible:ring-2 focus-visible:ring-red-500' : 'focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring'}"
aria-describedby={dateInvalid ? 'date-error' : undefined} aria-describedby={dateInvalid ? 'date-error' : undefined}
@@ -89,7 +91,7 @@ $effect(() => {
bind:value={senderId} bind:value={senderId}
initialName={initialSenderName} initialName={initialSenderName}
suggestedName={suggestedSenderName} suggestedName={suggestedSenderName}
autofocus={!!initialDateIso} autofocus={autofocus && !!initialDateIso}
/> />
</div> </div>