date input: add labels, validation, fix NaN checking

This commit is contained in:
Elijah Duffy
2025-07-03 11:08:02 -07:00
parent a307ffee92
commit 7a7fade491
2 changed files with 52 additions and 30 deletions

View File

@@ -2,7 +2,9 @@
import { InputValidatorEvent, liveValidator, validate } from '@svelte-toolkit/validate';
import { CalendarBlank } from 'phosphor-svelte';
import { type Snippet } from 'svelte';
import type { ClassValue, FormEventHandler, KeyboardEventHandler } from 'svelte/elements';
import type { ClassValue, KeyboardEventHandler } from 'svelte/elements';
import { generateIdentifier } from './util.js';
import Label from './Label.svelte';
type FormatString = 'year' | 'month' | 'day';
const blankState = {
@@ -16,7 +18,9 @@
value?: Date;
min?: Date;
max?: Date;
label?: string;
required?: boolean;
invalidMessage?: string;
class?: ClassValue | undefined | null;
format?: FormatString[];
}
@@ -24,13 +28,19 @@
let {
name,
value = $bindable<Date | undefined>(),
/** min specifies lower bounds for the date input (WARNING: NOT IMPLEMENTED) */
min = new Date(1900, 0, 1),
/** max specifies upper bounds for the date input (WARNING: NOT IMPLEMENTED) */
max = new Date(2100, 11, 31),
label,
required = false,
invalidMessage = 'Valid date is required',
class: classList,
format = ['year', 'month', 'day']
}: Props = $props();
const id = $derived(generateIdentifier('dateinput', name));
const inputSnippets = $derived.by(() => {
const found: Partial<Record<FormatString, boolean>> = {};
const arr: Snippet[] = [];
@@ -56,11 +66,8 @@
let valid = $state(true);
let containerElement: HTMLDivElement;
let yearValid = $state(true);
let yearElement = $state<HTMLDivElement | null>(null);
let monthValid = $state(true);
let monthElement = $state<HTMLDivElement | null>(null);
let dayValid = $state(true);
let dayElement = $state<HTMLDivElement | null>(null);
let previousYearValue = $state<string | undefined>(undefined);
let yearValue = $derived.by(() => {
@@ -108,8 +115,8 @@
setPrevious();
previousYearValue = undefined;
value = undefined;
return;
}
return;
}
}
@@ -121,8 +128,8 @@
setPrevious();
previousMonthValue = undefined;
value = undefined;
return;
}
return;
}
}
@@ -134,8 +141,8 @@
setPrevious();
previousDayValue = undefined;
value = undefined;
return;
}
return;
}
}
@@ -193,6 +200,12 @@
};
</script>
<div>
<!-- Date input Label -->
{#if label}
<Label for={id}>{label}</Label>
{/if}
<div
bind:this={containerElement}
class={[
@@ -204,7 +217,13 @@
classList
]}
>
<input type="hidden" {name} value={value?.toISOString() ?? ''} />
<input
type="hidden"
{name}
{id}
value={value?.toISOString() ?? ''}
use:validate={{ required }}
/>
{#each inputSnippets as snippet, i}
{@render snippet()}
@@ -217,6 +236,12 @@
<CalendarBlank size="1.5em" class="ml-auto" />
</div>
<!-- Error message if invalid -->
<div class={['opacity-0 transition-opacity', !valid && 'opacity-100']}>
<Label for={id} error>{invalidMessage}</Label>
</div>
</div>
{#snippet year()}
<div
bind:this={yearElement}
@@ -235,7 +260,6 @@
}}
use:liveValidator={{ constrain: true }}
onvalidate={(e) => {
yearValid = e.detail.valid;
handleComponentValidate(e);
}}
></div>
@@ -259,7 +283,6 @@
}}
use:liveValidator={{ constrain: true }}
onvalidate={(e) => {
monthValid = e.detail.valid;
handleComponentValidate(e);
}}
></div>
@@ -283,7 +306,6 @@
}}
use:liveValidator={{ constrain: true }}
onvalidate={(e) => {
dayValid = e.detail.valid;
handleComponentValidate(e);
}}
></div>

View File

@@ -15,7 +15,7 @@
'transition-fontColor block',
error && !bigError
? 'mt-1 text-sm font-normal text-red-500'
: 'text-text dark:text-background mb-3 text-base font-medium',
: 'text-text dark:text-background mb-2 text-base font-medium',
bigError && 'text-red-500!'
]}
>