Files
voyage/frontend/src/lib/components/DateTimeInput.svelte
alex 04fb1dfb40 fix: replace native date inputs with custom DateInput/DateTimeInput components
Native <input type='date'> and <input type='datetime-local'> render
their display format (mm/dd/yyyy vs dd/mm/yyyy, 12h vs 24h) based on
browser/OS locale, ignoring HTML lang attributes in Firefox and
inconsistently in Chrome. The previous lang=en-GB fix was unreliable.

Create DateInput.svelte and DateTimeInput.svelte components that show
dd/mm/yyyy (and DD/MM/YYYY HH:MM for datetime) by formatting the ISO
value in JS, while delegating the actual picker to a hidden native
input triggered via showPicker(). Supported in Chrome 99+, Firefox
101+, Safari 16+ (covers all modern browsers).

Updated 8 component files across CollectionModal, ChecklistModal,
NoteModal, ImmichSelect, CollectionMap, TransportationDetails,
LodgingDetails, and LocationVisits.
2026-03-06 15:14:02 +00:00

73 lines
2.2 KiB
Svelte

<script lang="ts">
import { createEventDispatcher } from 'svelte';
export let value: string | null = ''; // ISO yyyy-mm-ddTHH:MM
export let id: string = '';
export let name: string = '';
export let inputClass: string = 'input input-bordered w-full';
export let disabled: boolean = false;
export let min: string | null | undefined = undefined;
export let max: string | null | undefined = undefined;
export let required: boolean = false;
export let readonly: boolean = false;
const dispatch = createEventDispatcher<{ change: Event }>();
let nativeInput: HTMLInputElement;
$: normalizedValue = value ?? '';
$: normalizedMin = min ?? undefined;
$: normalizedMax = max ?? undefined;
$: displayDateTime = formatDateTime(normalizedValue);
function formatDateTime(iso: string): string {
if (!iso) return '';
const [datePart, timePart] = iso.split('T');
if (!datePart) return '';
const [y, m, d] = datePart.split('-');
if (!y || !m || !d) return '';
const timeStr = timePart ? timePart.slice(0, 5) : '';
return timeStr ? `${d}/${m}/${y} ${timeStr}` : `${d}/${m}/${y}`;
}
function openPicker() {
if (!disabled && !readonly) nativeInput?.showPicker?.();
}
function handleChange(e: Event) {
value = (e.currentTarget as HTMLInputElement).value;
dispatch('change', e);
}
</script>
<div class="relative w-full">
<button
type="button"
class="{inputClass} text-left flex items-center justify-between"
on:click={openPicker}
disabled={disabled || readonly}
>
<span class={displayDateTime ? '' : 'opacity-40'}>{displayDateTime || 'DD/MM/YYYY HH:MM'}</span>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 opacity-60" viewBox="0 0 24 24" fill="currentColor">
<path d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 18H5V8h14v13zM7 10h5v5H7z"/>
</svg>
</button>
<input
bind:this={nativeInput}
type="datetime-local"
{id}
{name}
value={normalizedValue}
min={normalizedMin}
max={normalizedMax}
{required}
{readonly}
on:change={handleChange}
{disabled}
tabindex="-1"
aria-hidden="true"
style="position:absolute;opacity:0;width:1px;height:1px;top:0;left:0;pointer-events:none;z-index:-1"
/>
</div>