time input: refactor to allow setting value reactively
This commit is contained in:
@@ -20,7 +20,7 @@
|
||||
import StyledRawInput from './StyledRawInput.svelte';
|
||||
import moment from 'moment';
|
||||
import type { ClassValue } from 'svelte/elements';
|
||||
import { generateIdentifier, targetMust } from './util';
|
||||
import { generateIdentifier, prefixZero, targetMust } from './util';
|
||||
import { FocusManager } from './focus';
|
||||
import { Time } from '@internationalized/date';
|
||||
|
||||
@@ -54,9 +54,22 @@
|
||||
type componentKey = 'hour' | 'minute';
|
||||
|
||||
const id = $derived(generateIdentifier('time-input', name));
|
||||
const values: Record<componentKey, string> = $state({
|
||||
hour: '',
|
||||
minute: ''
|
||||
const values: Record<componentKey, string> = $derived.by(() => {
|
||||
let hour = '';
|
||||
if (value) {
|
||||
if (value.hour > 12) {
|
||||
hour = (value.hour - 12).toString();
|
||||
} else if (value.hour === 0) {
|
||||
hour = '12'; // 12 AM case
|
||||
} else {
|
||||
hour = value.hour.toString();
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
hour,
|
||||
minute: !value ? '' : prefixZero(value.minute.toString())
|
||||
};
|
||||
});
|
||||
let ampm: ampmKey = $state('AM');
|
||||
let valid: boolean = $state(true);
|
||||
@@ -87,7 +100,6 @@
|
||||
* @returns true if the value was incremented, false if it looped back to 0
|
||||
*/
|
||||
const incrementValue = (input: HTMLInputElement, max: number, start: number): boolean => {
|
||||
const f = () => {
|
||||
if (input.value.length === 0) {
|
||||
input.value = start.toString();
|
||||
return true;
|
||||
@@ -106,11 +118,6 @@
|
||||
}
|
||||
};
|
||||
|
||||
const res = f();
|
||||
updateValue();
|
||||
return res;
|
||||
};
|
||||
|
||||
/**
|
||||
* decrementValue decrements the value of the input by 1
|
||||
* @param input The input element to decrement
|
||||
@@ -119,7 +126,6 @@
|
||||
* @returns true if the value was decremented, false if it looped back to max
|
||||
*/
|
||||
const decrementValue = (input: HTMLInputElement, max: number, start: number): boolean => {
|
||||
const f = () => {
|
||||
if (input.value.length === 0) {
|
||||
input.value = max.toString();
|
||||
return true;
|
||||
@@ -135,20 +141,15 @@
|
||||
}
|
||||
};
|
||||
|
||||
const res = f();
|
||||
updateValue();
|
||||
return res;
|
||||
};
|
||||
|
||||
/**
|
||||
* updateValue updates `value` with the current time in 24-hour format.
|
||||
* If any component is invalid or blank, it sets `value` to an empty string.
|
||||
*/
|
||||
const updateValue = () => {
|
||||
let ampmLocal = ampm;
|
||||
let hourValue = parseInt(values.hour);
|
||||
let minuteValue = parseInt(values.minute);
|
||||
const updateValue = (override?: Partial<Record<componentKey, string>>) => {
|
||||
let hourValue = parseInt(override?.hour || values.hour);
|
||||
let minuteValue = parseInt(override?.minute || values.minute);
|
||||
|
||||
// if no hour, clear value
|
||||
if (isNaN(hourValue)) {
|
||||
value = null;
|
||||
formattedValue = '';
|
||||
@@ -156,16 +157,21 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// internally convert to 24-hour format
|
||||
// if hour is greater than 12, set to 12-hour format
|
||||
if (hourValue > 12) {
|
||||
hourValue = hourValue - 12;
|
||||
ampmLocal = 'PM';
|
||||
hourValue -= 12;
|
||||
ampm = 'PM';
|
||||
}
|
||||
|
||||
// convert to 24-hour format
|
||||
if (ampm === 'PM' && hourValue < 12) hourValue += 12;
|
||||
else if (ampm === 'AM' && hourValue >= 12) hourValue -= 12;
|
||||
// default minutes to zero if empty
|
||||
if (isNaN(minuteValue)) {
|
||||
minuteValue = 0;
|
||||
}
|
||||
|
||||
value = new Time(hourValue + (ampmLocal === 'PM' ? 12 : 0), minuteValue, 0);
|
||||
value = new Time(hourValue, minuteValue);
|
||||
updateHiddenInput();
|
||||
|
||||
// update formatted value
|
||||
@@ -187,9 +193,9 @@
|
||||
{
|
||||
max: number;
|
||||
pattern: RegExp;
|
||||
onkeydown: (e: KeyboardEvent) => void;
|
||||
oninput: (e: Event) => void;
|
||||
onblur: (e: FocusEvent) => void;
|
||||
onkeydown?: (e: KeyboardEvent) => void;
|
||||
oninput?: (e: Event) => void;
|
||||
onblur?: (e: FocusEvent) => void;
|
||||
divider?: string;
|
||||
placeholder?: string;
|
||||
}
|
||||
@@ -207,37 +213,27 @@
|
||||
}
|
||||
|
||||
if (e.key === 'ArrowUp') {
|
||||
if (!incrementValue(target, 12, 1)) {
|
||||
toggleAMPM();
|
||||
}
|
||||
incrementValue(target, 12, 1);
|
||||
if (target.value === '12') toggleAMPM();
|
||||
} else if (e.key === 'ArrowDown') {
|
||||
if (!decrementValue(target, 12, 1)) {
|
||||
toggleAMPM();
|
||||
}
|
||||
decrementValue(target, 12, 1);
|
||||
if (target.value === '11') toggleAMPM();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
updateValue({ hour: target.value });
|
||||
e.preventDefault();
|
||||
},
|
||||
oninput: (e) => {
|
||||
const target = targetMust<HTMLInputElement>(e);
|
||||
updateValue();
|
||||
|
||||
if (target.value.length === 2) {
|
||||
focusList.focusNext();
|
||||
}
|
||||
},
|
||||
onblur: (e: FocusEvent) => {
|
||||
onblur: (e) => {
|
||||
const target = targetMust<HTMLInputElement>(e);
|
||||
const hourValue = parseInt(target.value);
|
||||
|
||||
if (hourValue > 12) {
|
||||
target.value = (hourValue - 12).toString();
|
||||
ampm = 'PM';
|
||||
}
|
||||
|
||||
updateValue();
|
||||
updateValue({ hour: target.value });
|
||||
},
|
||||
divider: ':'
|
||||
},
|
||||
@@ -256,13 +252,12 @@
|
||||
return;
|
||||
}
|
||||
|
||||
updateValue({ minute: target.value });
|
||||
e.preventDefault();
|
||||
},
|
||||
oninput: () => {
|
||||
updateValue();
|
||||
},
|
||||
onblur: (e: FocusEvent) => {
|
||||
const target = targetMust<HTMLInputElement>(e);
|
||||
updateValue({ minute: target.value });
|
||||
if (target.value.length === 1) {
|
||||
target.value = '0' + target.value;
|
||||
}
|
||||
@@ -295,7 +290,7 @@
|
||||
'text-center text-xl focus:placeholder:text-transparent',
|
||||
compact ? 'h-9 w-16!' : 'h-16 w-24!'
|
||||
]}
|
||||
bind:value={values[key]}
|
||||
value={values[key]}
|
||||
inputmode="numeric"
|
||||
pattern="[0-9]*"
|
||||
placeholder={opts.placeholder ?? '00'}
|
||||
@@ -306,7 +301,7 @@
|
||||
onkeydown={opts.onkeydown}
|
||||
oninput={opts.oninput}
|
||||
onblur={opts.onblur}
|
||||
{@attach focusList.input()}
|
||||
{@attach focusList.input({ selectAll: true })}
|
||||
/>
|
||||
|
||||
{#if opts.divider}
|
||||
|
||||
@@ -266,7 +266,14 @@
|
||||
<TimeInput label="Compact time" compact bind:value={timeValue} />
|
||||
</InputGroup>
|
||||
<InputGroup>
|
||||
<p>Selected time is {formatTime(timeValue, 'undefined')}</p>
|
||||
<p>Selected time is {formatTime(timeValue, 'undefined')} ({timeValue?.toString()})</p>
|
||||
<Button
|
||||
onclick={() => {
|
||||
timeValue = new Time(15, 0);
|
||||
}}
|
||||
>
|
||||
Set 3:00 PM
|
||||
</Button>
|
||||
</InputGroup>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user