date input: add labels, validation, fix NaN checking
This commit is contained in:
@@ -2,7 +2,9 @@
|
|||||||
import { InputValidatorEvent, liveValidator, validate } from '@svelte-toolkit/validate';
|
import { InputValidatorEvent, liveValidator, validate } from '@svelte-toolkit/validate';
|
||||||
import { CalendarBlank } from 'phosphor-svelte';
|
import { CalendarBlank } from 'phosphor-svelte';
|
||||||
import { type Snippet } from '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';
|
type FormatString = 'year' | 'month' | 'day';
|
||||||
const blankState = {
|
const blankState = {
|
||||||
@@ -16,7 +18,9 @@
|
|||||||
value?: Date;
|
value?: Date;
|
||||||
min?: Date;
|
min?: Date;
|
||||||
max?: Date;
|
max?: Date;
|
||||||
|
label?: string;
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
|
invalidMessage?: string;
|
||||||
class?: ClassValue | undefined | null;
|
class?: ClassValue | undefined | null;
|
||||||
format?: FormatString[];
|
format?: FormatString[];
|
||||||
}
|
}
|
||||||
@@ -24,13 +28,19 @@
|
|||||||
let {
|
let {
|
||||||
name,
|
name,
|
||||||
value = $bindable<Date | undefined>(),
|
value = $bindable<Date | undefined>(),
|
||||||
|
/** min specifies lower bounds for the date input (WARNING: NOT IMPLEMENTED) */
|
||||||
min = new Date(1900, 0, 1),
|
min = new Date(1900, 0, 1),
|
||||||
|
/** max specifies upper bounds for the date input (WARNING: NOT IMPLEMENTED) */
|
||||||
max = new Date(2100, 11, 31),
|
max = new Date(2100, 11, 31),
|
||||||
|
label,
|
||||||
required = false,
|
required = false,
|
||||||
|
invalidMessage = 'Valid date is required',
|
||||||
class: classList,
|
class: classList,
|
||||||
format = ['year', 'month', 'day']
|
format = ['year', 'month', 'day']
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
|
const id = $derived(generateIdentifier('dateinput', name));
|
||||||
|
|
||||||
const inputSnippets = $derived.by(() => {
|
const inputSnippets = $derived.by(() => {
|
||||||
const found: Partial<Record<FormatString, boolean>> = {};
|
const found: Partial<Record<FormatString, boolean>> = {};
|
||||||
const arr: Snippet[] = [];
|
const arr: Snippet[] = [];
|
||||||
@@ -56,11 +66,8 @@
|
|||||||
|
|
||||||
let valid = $state(true);
|
let valid = $state(true);
|
||||||
let containerElement: HTMLDivElement;
|
let containerElement: HTMLDivElement;
|
||||||
let yearValid = $state(true);
|
|
||||||
let yearElement = $state<HTMLDivElement | null>(null);
|
let yearElement = $state<HTMLDivElement | null>(null);
|
||||||
let monthValid = $state(true);
|
|
||||||
let monthElement = $state<HTMLDivElement | null>(null);
|
let monthElement = $state<HTMLDivElement | null>(null);
|
||||||
let dayValid = $state(true);
|
|
||||||
let dayElement = $state<HTMLDivElement | null>(null);
|
let dayElement = $state<HTMLDivElement | null>(null);
|
||||||
let previousYearValue = $state<string | undefined>(undefined);
|
let previousYearValue = $state<string | undefined>(undefined);
|
||||||
let yearValue = $derived.by(() => {
|
let yearValue = $derived.by(() => {
|
||||||
@@ -108,8 +115,8 @@
|
|||||||
setPrevious();
|
setPrevious();
|
||||||
previousYearValue = undefined;
|
previousYearValue = undefined;
|
||||||
value = undefined;
|
value = undefined;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,8 +128,8 @@
|
|||||||
setPrevious();
|
setPrevious();
|
||||||
previousMonthValue = undefined;
|
previousMonthValue = undefined;
|
||||||
value = undefined;
|
value = undefined;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,8 +141,8 @@
|
|||||||
setPrevious();
|
setPrevious();
|
||||||
previousDayValue = undefined;
|
previousDayValue = undefined;
|
||||||
value = undefined;
|
value = undefined;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,7 +200,13 @@
|
|||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div>
|
||||||
|
<!-- Date input Label -->
|
||||||
|
{#if label}
|
||||||
|
<Label for={id}>{label}</Label>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div
|
||||||
bind:this={containerElement}
|
bind:this={containerElement}
|
||||||
class={[
|
class={[
|
||||||
'inline-flex w-54 items-center justify-start gap-1',
|
'inline-flex w-54 items-center justify-start gap-1',
|
||||||
@@ -203,8 +216,14 @@
|
|||||||
!valid && 'border-red-500!',
|
!valid && 'border-red-500!',
|
||||||
classList
|
classList
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<input type="hidden" {name} value={value?.toISOString() ?? ''} />
|
<input
|
||||||
|
type="hidden"
|
||||||
|
{name}
|
||||||
|
{id}
|
||||||
|
value={value?.toISOString() ?? ''}
|
||||||
|
use:validate={{ required }}
|
||||||
|
/>
|
||||||
|
|
||||||
{#each inputSnippets as snippet, i}
|
{#each inputSnippets as snippet, i}
|
||||||
{@render snippet()}
|
{@render snippet()}
|
||||||
@@ -215,6 +234,12 @@
|
|||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
<CalendarBlank size="1.5em" class="ml-auto" />
|
<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>
|
</div>
|
||||||
|
|
||||||
{#snippet year()}
|
{#snippet year()}
|
||||||
@@ -235,7 +260,6 @@
|
|||||||
}}
|
}}
|
||||||
use:liveValidator={{ constrain: true }}
|
use:liveValidator={{ constrain: true }}
|
||||||
onvalidate={(e) => {
|
onvalidate={(e) => {
|
||||||
yearValid = e.detail.valid;
|
|
||||||
handleComponentValidate(e);
|
handleComponentValidate(e);
|
||||||
}}
|
}}
|
||||||
></div>
|
></div>
|
||||||
@@ -259,7 +283,6 @@
|
|||||||
}}
|
}}
|
||||||
use:liveValidator={{ constrain: true }}
|
use:liveValidator={{ constrain: true }}
|
||||||
onvalidate={(e) => {
|
onvalidate={(e) => {
|
||||||
monthValid = e.detail.valid;
|
|
||||||
handleComponentValidate(e);
|
handleComponentValidate(e);
|
||||||
}}
|
}}
|
||||||
></div>
|
></div>
|
||||||
@@ -283,7 +306,6 @@
|
|||||||
}}
|
}}
|
||||||
use:liveValidator={{ constrain: true }}
|
use:liveValidator={{ constrain: true }}
|
||||||
onvalidate={(e) => {
|
onvalidate={(e) => {
|
||||||
dayValid = e.detail.valid;
|
|
||||||
handleComponentValidate(e);
|
handleComponentValidate(e);
|
||||||
}}
|
}}
|
||||||
></div>
|
></div>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
'transition-fontColor block',
|
'transition-fontColor block',
|
||||||
error && !bigError
|
error && !bigError
|
||||||
? 'mt-1 text-sm font-normal text-red-500'
|
? '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!'
|
bigError && 'text-red-500!'
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user