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.
This commit is contained in:
72
frontend/src/lib/components/DateTimeInput.svelte
Normal file
72
frontend/src/lib/components/DateTimeInput.svelte
Normal file
@@ -0,0 +1,72 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user