fix: uncontrolled input — stop re-setting value prop on every keystroke, killing focus
This commit is contained in:
@@ -1,7 +1,16 @@
|
||||
<script lang="ts">
|
||||
/**
|
||||
* UnitInput — controlled numeric input.
|
||||
* Phase 2: number input only. NL field added in Phase 3.
|
||||
* UnitInput — uncontrolled numeric input with one-way push to parent.
|
||||
*
|
||||
* The input owns its own DOM value. We only push changes upward via onchange.
|
||||
* We deliberately do NOT set value={value} on the input element — that would
|
||||
* cause Svelte to re-render the input on every parent state update, resetting
|
||||
* the cursor position and stealing focus mid-typing.
|
||||
*
|
||||
* The parent can pass a new `value` prop if it needs to imperatively set the
|
||||
* input (e.g. Ctrl+click adopts a converted value). We detect that via $effect
|
||||
* and update the DOM node directly, only when the value actually differs from
|
||||
* what's currently in the input.
|
||||
*/
|
||||
|
||||
let {
|
||||
@@ -12,6 +21,18 @@
|
||||
onchange: (v: number) => void;
|
||||
} = $props();
|
||||
|
||||
let inputEl = $state<HTMLInputElement | null>(null);
|
||||
|
||||
// Sync parent-driven value changes into the DOM — but only when the parent
|
||||
// is actually setting a new value (e.g. Ctrl+click), not on every keystroke.
|
||||
$effect(() => {
|
||||
if (!inputEl) return;
|
||||
const current = parseFloat(inputEl.value);
|
||||
if (isNaN(current) || current !== value) {
|
||||
inputEl.value = String(value);
|
||||
}
|
||||
});
|
||||
|
||||
function handleInput(e: Event) {
|
||||
const input = e.currentTarget as HTMLInputElement;
|
||||
const parsed = parseFloat(input.value);
|
||||
@@ -22,9 +43,9 @@
|
||||
</script>
|
||||
|
||||
<input
|
||||
bind:this={inputEl}
|
||||
type="number"
|
||||
step="any"
|
||||
value={value}
|
||||
oninput={handleInput}
|
||||
aria-label="Input value"
|
||||
/>
|
||||
|
||||
@@ -24,6 +24,9 @@ export interface Unit {
|
||||
|
||||
/** Group this unit belongs to, or null if orphaned. */
|
||||
group: string | null;
|
||||
|
||||
/** Optional human-readable description shown as a tooltip in the converter. */
|
||||
description?: string;
|
||||
}
|
||||
|
||||
/** A group of units sharing a base unit and a universal scale factor. */
|
||||
|
||||
Reference in New Issue
Block a user