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