Value semantics
The display string is a view. The canonical value is the thing you store. They are never the same object, and onChange only ever gives you the second one.
Canonical types
Money is an integer
CurrencyInput emits 129900, not 1299.00. A price held as a float is a rounding error waiting for a quarterly report, so no float is used at any point in the pipeline — parsing, stepping and formatting all operate on the integer.
The number of minor units comes from Intl for the active currency, so JPY has none and KWD has three. Nothing is hardcoded to 100.
The caret is a count, not an offset
Reformatting a string on every keystroke is what makes masked inputs feel broken: the string length changes, the browser puts the caret back at the old offset, and the offset now points somewhere else.
The caret is tracked as the number of significant characters to its left and restored by counting forward again after the reformat. Separators are not significant, so inserting a digit in the middle of a formatted number leaves the caret exactly where the user put it.