mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-20 14:06:27 +00:00
The split and receipt fields rejected the third decimal a KWD or BHD amount seeds, on desktop and in the mobile sheet, which share the currency-aware guard now. Signed amounts get a sign toggle on the numeric input because the iOS decimal pad has no minus key, and the under/over hint no longer swaps direction for a negative total.
This commit is contained in:
@@ -24,6 +24,8 @@ const SANITIZERS: Record<NumericMode, (raw: string) => string> = {
|
||||
},
|
||||
}
|
||||
|
||||
const flipSign = (raw: string) => (raw.startsWith('-') ? raw.slice(1) : `-${raw}`)
|
||||
|
||||
type Props = Omit<InputHTMLAttributes<HTMLInputElement>, 'type' | 'onChange' | 'value'> & {
|
||||
value: string | number | null | undefined
|
||||
/** Receives the sanitized raw string. Callers keep their own state and commit logic. */
|
||||
@@ -31,6 +33,13 @@ type Props = Omit<InputHTMLAttributes<HTMLInputElement>, 'type' | 'onChange' | '
|
||||
mode?: NumericMode
|
||||
/** Escape hatch for a field that must not steal the caret (none today). */
|
||||
selectOnFocus?: boolean
|
||||
/**
|
||||
* Accessible name for a sign toggle rendered beside the field. Set it on a signed
|
||||
* money field: iOS draws inputMode="decimal" as a pad with digits, separator and
|
||||
* backspace only, so a refund (#2176) has no minus key to type. Ignored on the
|
||||
* unsigned modes, where there is no sign to flip.
|
||||
*/
|
||||
signToggleLabel?: string
|
||||
ref?: Ref<HTMLInputElement>
|
||||
}
|
||||
|
||||
@@ -62,34 +71,54 @@ type Props = Omit<InputHTMLAttributes<HTMLInputElement>, 'type' | 'onChange' | '
|
||||
* others on blur. This owns only the part that was uniformly broken.
|
||||
*/
|
||||
export function NumericInput({
|
||||
value, onValueChange, mode = 'integer', selectOnFocus = true, onFocus, inputMode, ref, ...rest
|
||||
value, onValueChange, mode = 'integer', selectOnFocus = true, onFocus, inputMode, signToggleLabel, ref, ...rest
|
||||
}: Props) {
|
||||
// Set while a deferred select() is queued; any input in that window cancels it.
|
||||
const selectPending = useRef(false)
|
||||
const raw = String(value ?? '')
|
||||
const signable = mode === 'signed' || mode === 'signed-decimal'
|
||||
|
||||
return (
|
||||
<input
|
||||
{...rest}
|
||||
ref={ref}
|
||||
type="text"
|
||||
inputMode={inputMode ?? (mode === 'integer' ? 'numeric' : 'decimal')}
|
||||
value={value ?? ''}
|
||||
onChange={e => {
|
||||
selectPending.current = false
|
||||
onValueChange(SANITIZERS[mode](e.target.value))
|
||||
}}
|
||||
onFocus={e => {
|
||||
if (selectOnFocus) {
|
||||
const el = e.currentTarget
|
||||
el.select()
|
||||
selectPending.current = true
|
||||
requestAnimationFrame(() => {
|
||||
if (selectPending.current && document.activeElement === el) el.select()
|
||||
selectPending.current = false
|
||||
})
|
||||
}
|
||||
onFocus?.(e)
|
||||
}}
|
||||
/>
|
||||
<>
|
||||
<input
|
||||
{...rest}
|
||||
ref={ref}
|
||||
type="text"
|
||||
inputMode={inputMode ?? (mode === 'integer' ? 'numeric' : 'decimal')}
|
||||
value={value ?? ''}
|
||||
onChange={e => {
|
||||
selectPending.current = false
|
||||
onValueChange(SANITIZERS[mode](e.target.value))
|
||||
}}
|
||||
onFocus={e => {
|
||||
if (selectOnFocus) {
|
||||
const el = e.currentTarget
|
||||
el.select()
|
||||
selectPending.current = true
|
||||
requestAnimationFrame(() => {
|
||||
if (selectPending.current && document.activeElement === el) el.select()
|
||||
selectPending.current = false
|
||||
})
|
||||
}
|
||||
onFocus?.(e)
|
||||
}}
|
||||
/>
|
||||
{signToggleLabel && signable && (
|
||||
<button
|
||||
type="button"
|
||||
aria-label={signToggleLabel}
|
||||
aria-pressed={raw.startsWith('-')}
|
||||
disabled={rest.disabled}
|
||||
// Keeps the caret (and on a phone the keypad) in the field, so the sign can
|
||||
// be flipped mid-entry and typing carries on where it left off.
|
||||
onMouseDown={e => e.preventDefault()}
|
||||
onClick={() => onValueChange(SANITIZERS[mode](flipSign(raw)))}
|
||||
className="text-content-faint"
|
||||
style={{ border: 0, background: 'none', padding: '0 2px', font: 'inherit', lineHeight: 1, cursor: 'pointer' }}
|
||||
>
|
||||
±
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user