diff --git a/src/lib/TimeInput.svelte b/src/lib/TimeInput.svelte index 55d1346..837cf29 100644 --- a/src/lib/TimeInput.svelte +++ b/src/lib/TimeInput.svelte @@ -15,6 +15,7 @@ required?: boolean; invalidMessage?: string; showConfirm?: boolean; + compact?: boolean; class?: ClassValue | null | undefined; } @@ -26,19 +27,24 @@ required, invalidMessage = 'Please select a time', showConfirm = false, + compact = false, class: classValue }: Props = $props(); + type ampmKey = 'AM' | 'PM'; + type componentsKey = 'hour' | 'minute'; + const id = $derived(generateIdentifier('time-input', name)); - let ampm: 'AM' | 'PM' = $state('AM'); - let type: HTMLInputElement['type'] = $state('number'); + let ampm: ampmKey = $state('AM'); let valid: boolean = $state(true); let hiddenInput: HTMLInputElement; - let hourInput = $state(null); - let minuteInput = $state(null); - let amButton: HTMLButtonElement; - let pmButton: HTMLButtonElement; + + let btnrefs: Partial> = $state({}); + const inrefs: Record = $state({ + hour: null, + minute: null + }); const hour24Pattern = /^(0?[0-9]|1[0-9]|2[0-3])$/; const minutePattern = /^(0?[0-9]|[1-5][0-9])$/; @@ -64,6 +70,7 @@ */ const incrementValue = (input: HTMLInputElement, max: number, start: number): boolean => { const f = () => { + console.log('incrementing', input); if (input.value.length === 0) { input.value = start.toString(); return true; @@ -123,7 +130,9 @@ const selectEnd = (input: HTMLInputElement) => { if (input) { setTimeout(() => { + console.log('selecting end', input, input.isConnected); input.setSelectionRange(input.value.length, input.value.length); + console.log('selected end vibes'); }, 0); } }; @@ -144,11 +153,11 @@ * If any component is invalid or blank, it sets `value` to an empty string. */ const updateValue = () => { - if (!hourInput || !minuteInput) return; + if (!inrefs.hour || !inrefs.minute) return; let ampmLocal = ampm; - let hourValue = parseInt(hourInput.value); - let minuteValue = parseInt(minuteInput.value); + let hourValue = parseInt(inrefs.hour.value); + let minuteValue = parseInt(inrefs.minute.value); if (isNaN(hourValue)) { value = ''; @@ -184,17 +193,123 @@ }; onMount(() => { - if (hourInput) { - liveValidator(hourInput, keydownValidatorOpts); + if (inrefs.hour) { + liveValidator(inrefs.hour, keydownValidatorOpts); } - if (minuteInput) { - liveValidator(minuteInput, keydownValidatorOpts); - } - - if ('userAgentData' in navigator) { - if (!(navigator.userAgentData as { mobile: boolean }).mobile) type = 'text'; + if (inrefs.minute) { + liveValidator(inrefs.minute, keydownValidatorOpts); } }); + + const components: Record< + componentsKey, + { + max: number; + pattern: RegExp; + onkeydown: (e: KeyboardEvent) => void; + oninput: () => void; + onblur: () => void; + divider?: string; + placeholder?: string; + } + > = { + /** Hour Input */ + hour: { + max: 24, + pattern: hour24Pattern, + onkeydown: (e: KeyboardEvent) => { + if (!inrefs.hour) return; + + if (e.key === ':' && inrefs.hour.value.length !== 0) { + inrefs.minute?.focus(); + e.preventDefault(); + } + + if (e.key === 'ArrowRight' && inrefs.hour.selectionEnd === inrefs.hour.value.length) { + inrefs.minute?.focus(); + } + + if (e.key === 'ArrowUp') { + if (!incrementValue(inrefs.hour, 12, 1)) { + toggleAMPM(); + } + } + if (e.key === 'ArrowDown') { + if (!decrementValue(inrefs.hour, 12, 1)) { + toggleAMPM(); + } + } + if (e.key === 'ArrowUp' || e.key === 'ArrowDown') { + e.preventDefault(); + } + }, + oninput: () => { + updateValue(); + + if (inrefs.hour?.value.length === 2) { + inrefs.minute?.focus(); + } + }, + onblur: () => { + if (!inrefs.hour) return; + const hourValue = parseInt(inrefs.hour.value); + + if (hourValue > 12) { + inrefs.hour.value = (hourValue - 12).toString(); + ampm = 'PM'; + } + + updateValue(); + }, + divider: ':' + }, + /** Minute Input */ + minute: { + max: 59, + pattern: minutePattern, + onkeydown: (e: KeyboardEvent) => { + if (!inrefs.minute) return; + const target = e.target as HTMLInputElement; + + console.log( + e.key, + inrefs.minute.selectionStart, + inrefs.minute.selectionEnd, + target.selectionStart + ); + + if (e.key === 'ArrowLeft' && inrefs.minute.selectionStart === 0) { + inrefs.hour?.focus(); + } else if ( + e.key === 'ArrowRight' && + inrefs.minute.selectionEnd === inrefs.minute.value.length + ) { + btnrefs.AM?.focus(); + } else if (e.key === 'Backspace' && inrefs.minute.value.length === 0) { + inrefs.hour?.focus(); + } + + if (e.key === 'ArrowUp') { + incrementValue(target, 59, 0); + } + if (e.key === 'ArrowDown') { + decrementValue(inrefs.minute, 59, 0); + } + if (e.key === 'ArrowUp' || e.key === 'ArrowDown') { + e.preventDefault(); + return; + } + }, + oninput: () => { + updateValue(); + }, + onblur: () => { + if (inrefs.minute?.value.length === 1) { + inrefs.minute.value = '0' + inrefs.minute.value; + } + } + } + };
@@ -213,141 +328,67 @@ />
- - { - if (!hourInput) return; + + {#each ['hour', 'minute'] as componentsKey[] as key} + {@const value = components[key]} + - if (e.key === ':' && hourInput.value.length !== 0) { - minuteInput?.focus(); - e.preventDefault(); - } - - if (e.key === 'ArrowRight' && hourInput.selectionEnd === hourInput.value.length) { - minuteInput?.focus(); - } - - if (e.key === 'ArrowUp') { - if (!incrementValue(hourInput, 12, 1)) { - toggleAMPM(); - } - } - if (e.key === 'ArrowDown') { - if (!decrementValue(hourInput, 12, 1)) { - toggleAMPM(); - } - } - if (e.key === 'ArrowUp' || e.key === 'ArrowDown') { - e.preventDefault(); - } - }} - oninput={() => { - updateValue(); - - if (hourInput?.value.length === 2) { - minuteInput?.focus(); - } - }} - onblur={() => { - if (!hourInput) return; - const hourValue = parseInt(hourInput.value); - - if (hourValue > 12) { - hourInput.value = (hourValue - 12).toString(); - ampm = 'PM'; - } - - updateValue(); - }} - bind:ref={hourInput} - /> - - : - - - { - if (!minuteInput) return; - - if (e.key === 'ArrowLeft' && minuteInput.selectionStart === 0) { - hourInput?.focus(); - } else if ( - e.key === 'ArrowRight' && - minuteInput.selectionEnd === minuteInput.value.length - ) { - amButton?.focus(); - } else if (e.key === 'Backspace' && minuteInput.value.length === 0) { - hourInput?.focus(); - } - - if (e.key === 'ArrowUp') { - incrementValue(minuteInput, 59, 0); - } - if (e.key === 'ArrowDown') { - decrementValue(minuteInput, 59, 0); - } - if (e.key === 'ArrowUp' || e.key === 'ArrowDown') { - e.preventDefault(); - return; - } - }} - oninput={() => { - updateValue(); - }} - onblur={() => { - if (minuteInput?.value.length === 1) { - minuteInput.value = '0' + minuteInput.value; - } - }} - bind:ref={minuteInput} - /> + {#if value.divider} + {value.divider} + {/if} + {/each} -
- - +
+ {#each ['AM', 'PM'] as (typeof ampm)[] as shade, index} + + e.preventDefault(); + }} + > + {shade} + + {/each}
@@ -378,12 +419,4 @@ @apply m-0 appearance-none; } } - - .ampm { - @apply border-sui-accent dark:border-sui-accent/50 cursor-pointer px-3 py-1 font-medium; - - &.selected { - @apply bg-sui-accent text-sui-background dark:bg-sui-accent/30 dark:text-sui-background/90 ring-sui-text ring-1 dark:ring-blue-300; - } - } diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index e3961bc..1235887 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -259,7 +259,10 @@

Time Input

- + + + +
@@ -417,7 +420,7 @@ } }} onopen={(dialog) => { - dialog.error(ErrorMessage.from('Example error message!')); + dialog.error('Example error message!'); dialog.loading(); setTimeout(() => { dialog.loaded();