Badges: auto-focus input when field accordion opens; add Revert button to field forms

This commit is contained in:
Scott Idem
2026-03-12 15:28:37 -04:00
parent 9e64815d62
commit 9888374d86

View File

@@ -22,7 +22,7 @@
import { ae_api, ae_loc } from '$lib/stores/ae_stores';
import { events_func } from '$lib/ae_events_functions';
import { browser } from '$app/environment';
import { Pencil, Check, X, LoaderCircle, ChevronDown, Printer } from 'lucide-svelte';
import { Pencil, Check, X, LoaderCircle, ChevronDown, Printer, RotateCcw } from 'lucide-svelte';
interface Props {
event_id: string;
@@ -283,6 +283,33 @@
?? $lq__event_badge_obj?.badge_type_code
?? ''
);
// --- Focus management: focus the input when its accordion opens ---
// rAF gives the CSS accordion one repaint tick before focus() is called,
// avoiding jumping to an invisible (height: 0) element.
let input_ref_name: HTMLInputElement | undefined;
let input_ref_title: HTMLInputElement | undefined;
let input_ref_affiliations: HTMLTextAreaElement | undefined;
let input_ref_location: HTMLInputElement | undefined;
let input_ref_pronouns: HTMLInputElement | undefined;
let input_ref_allow_tracking: HTMLInputElement | undefined;
let select_ref_badge_type: HTMLSelectElement | undefined;
$effect(() => {
const field = active_field;
if (!field) return;
requestAnimationFrame(() => {
switch (field) {
case 'name': input_ref_name?.focus(); break;
case 'title': input_ref_title?.focus(); break;
case 'affiliations': input_ref_affiliations?.focus(); break;
case 'location': input_ref_location?.focus(); break;
case 'pronouns': input_ref_pronouns?.focus(); break;
case 'allow_tracking': input_ref_allow_tracking?.focus(); break;
case 'badge_type': select_ref_badge_type?.focus(); break;
}
});
});
</script>
<!-- ============================================================
@@ -332,9 +359,21 @@
</div>
{/snippet}
<!-- Save/cancel row (inside accordion edit forms) -->
{#snippet field_actions(field_key: string, on_save: () => void, on_cancel: () => void)}
<!-- Save/cancel row (inside accordion edit forms).
on_revert is optional — only passed when an override is currently saved;
clears the override back to the base imported value. Layout: [Revert] [Save] [X] -->
{#snippet field_actions(field_key: string, on_save: () => void, on_cancel: () => void, on_revert?: () => void)}
<div class="flex gap-2 mt-2">
{#if on_revert}
<button
type="button"
class="btn btn-sm preset-tonal-warning shrink-0"
onclick={on_revert}
disabled={field_save_status[field_key] === 'saving'}
title="Remove override — restore original imported value"
aria-label="Revert to original value"
><RotateCcw size="13" /></button>
{/if}
<button
type="button"
class="btn btn-sm flex-1"
@@ -453,13 +492,17 @@
name="ctrl-full-name"
type="text"
class="input w-full"
bind:this={input_ref_name}
bind:value={edit_full_name_override}
placeholder={$lq__event_badge_obj?.full_name ?? 'Full name'}
/>
{@render field_actions(
'name',
() => save_field('name', { full_name_override: edit_full_name_override || null }),
() => cancel_field('name')
() => cancel_field('name'),
$lq__event_badge_obj?.full_name_override
? () => save_field('name', { full_name_override: null })
: undefined
)}
</div>
</div>
@@ -501,13 +544,17 @@
name="ctrl-pro-title"
type="text"
class="input w-full"
bind:this={input_ref_title}
bind:value={edit_professional_title_override}
placeholder={$lq__event_badge_obj?.professional_title ?? 'Professional title'}
/>
{@render field_actions(
'title',
() => save_field('title', { professional_title_override: edit_professional_title_override || null }),
() => cancel_field('title')
() => cancel_field('title'),
$lq__event_badge_obj?.professional_title_override
? () => save_field('title', { professional_title_override: null })
: undefined
)}
</div>
</div>
@@ -549,13 +596,17 @@
name="ctrl-affiliations"
class="textarea w-full"
rows="2"
bind:this={input_ref_affiliations}
bind:value={edit_affiliations_override}
placeholder={$lq__event_badge_obj?.affiliations ?? 'Organization / affiliations'}
></textarea>
{@render field_actions(
'affiliations',
() => save_field('affiliations', { affiliations_override: edit_affiliations_override || null }),
() => cancel_field('affiliations')
() => cancel_field('affiliations'),
$lq__event_badge_obj?.affiliations_override
? () => save_field('affiliations', { affiliations_override: null })
: undefined
)}
</div>
</div>
@@ -597,13 +648,17 @@
name="ctrl-location"
type="text"
class="input w-full"
bind:this={input_ref_location}
bind:value={edit_location_override}
placeholder={$lq__event_badge_obj?.location ?? 'City, State / Country'}
/>
{@render field_actions(
'location',
() => save_field('location', { location_override: edit_location_override || null }),
() => cancel_field('location')
() => cancel_field('location'),
$lq__event_badge_obj?.location_override
? () => save_field('location', { location_override: null })
: undefined
)}
</div>
</div>
@@ -640,6 +695,7 @@
name="ctrl-allow-tracking"
type="checkbox"
class="checkbox"
bind:this={input_ref_allow_tracking}
bind:checked={edit_allow_tracking}
/>
<span class="text-xs">Allow exhibitor lead scanning</span>
@@ -685,13 +741,17 @@
name="ctrl-pronouns"
type="text"
class="input w-full"
bind:this={input_ref_pronouns}
bind:value={edit_pronouns_override}
placeholder={$lq__event_badge_obj?.pronouns ?? 'e.g. they/them'}
/>
{@render field_actions(
'pronouns',
() => save_field('pronouns', { pronouns_override: edit_pronouns_override || null }),
() => cancel_field('pronouns')
() => cancel_field('pronouns'),
$lq__event_badge_obj?.pronouns_override
? () => save_field('pronouns', { pronouns_override: null })
: undefined
)}
</div>
</div>
@@ -739,6 +799,7 @@
id="ctrl-badge-type"
name="ctrl-badge-type"
class="select w-full"
bind:this={select_ref_badge_type}
bind:value={edit_badge_type_code}
>
<option value={null}> Select badge type —</option>
@@ -756,7 +817,10 @@
? (badge_type_code_li.find(item => item.code === edit_badge_type_code)?.name ?? edit_badge_type_code)
: null
}),
() => cancel_field('badge_type')
() => cancel_field('badge_type'),
$lq__event_badge_obj?.badge_type_code_override
? () => save_field('badge_type', { badge_type_code_override: null, badge_type_override: null })
: undefined
)}
</div>
</div>