# Decimal / float number inputs Rule: for a field where the user types a **decimal** value (millimetres, rates, any float), use `type="text"` + `inputMode="decimal"` with local draft-string state — **not** `type="number"`. ## Why Two independent failure modes combine: 1. **`type="number"` sanitises partial input.** While the user is typing `12.7`, the intermediate value is `"12."`, which is not a valid number. The browser (and jsdom) report `input.value === ""`. The decimal point appears not to type at all. 2. **A naive `parseFloat` + `isNaN` guard drops the same intermediate.** `parseFloat("12.")` is `12`, so a commit-on-every-change handler rewrites the field to `"12"` and the caret jumps — same visible symptom, different cause. ## The working pattern Keep the raw string in local state while focused; commit only when it parses cleanly; re-sync from the prop only when not focused (so drag/resize updates still flow in). ```jsx const [draft, setDraft] = useState(String(value ?? '')); const [isFocused, setIsFocused] = useState(false); useEffect(() => { if (!isFocused) setDraft(String(value ?? '')); }, [value, isFocused]); // onChange: setDraft(next) always; commit only if next is a complete number // onBlur: if the draft is empty/unparseable, restore String(value); else commit rounded ``` Reference implementation: `MmInput` in `frontend/src/components/ReportEditor/a4/A4Inspector.jsx`. Note the matrix editor's `ObjectInspector.jsx` uses `parseInt` and integer cells, so it does not need this — only float-valued fields do.