File size: 2,555 Bytes
2dddd1f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
import * as React from "react"
import { Input } from "./input"

interface AmountInputProps extends Omit<React.ComponentProps<typeof Input>, 'value' | 'onChange'> {
  value?: string | number;
  onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
}

const AmountInput = React.forwardRef<HTMLInputElement, AmountInputProps>(
  ({ value, onChange, name, ...props }, ref) => {
    const formatNumber = (val: string) => {
      if (!val) return "";
      // Remove all commas first
      const clean = val.replace(/,/g, "");
      const parts = clean.split(".");
      // Format the integer part
      parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
      return parts.join(".");
    };

    const [cursor, setCursor] = React.useState<number | null>(null);
    const inputRef = React.useRef<HTMLInputElement | null>(null);

    // Sync ref
    React.useImperativeHandle(ref, () => inputRef.current!);

    const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
      const inputVal = e.target.value;
      
      // Allow only numbers, dots and commas
      if (/[^\d.,]/.test(inputVal)) return;

      const stripped = inputVal.replace(/,/g, "");
      const formatted = formatNumber(stripped);

      // Track cursor position changes due to formatting
      const selectionStart = e.target.selectionStart || 0;
      const commasBefore = (inputVal.substring(0, selectionStart).match(/,/g) || []).length;
      const commasAfter = (formatted.substring(0, selectionStart).match(/,/g) || []).length;
      
      const diff = commasAfter - commasBefore;
      setCursor(selectionStart + diff);

      if (onChange) {
        // We pass the stripped value back to handlers (like react-hook-form)
        // so they can treat it as a normal numeric string
        const event = {
          ...e,
          target: {
            ...e.target,
            name: name,
            value: stripped
          }
        } as React.ChangeEvent<HTMLInputElement>;
        onChange(event);
      }
    };

    // Restore cursor position after render
    React.useLayoutEffect(() => {
      if (inputRef.current && cursor !== null) {
        inputRef.current.setSelectionRange(cursor, cursor);
      }
    });

    const displayValue = formatNumber(value?.toString() || "");

    return (
      <Input
        {...props}
        ref={inputRef}
        type="text"
        inputMode="decimal"
        value={displayValue}
        onChange={handleChange}
      />
    );
  }
);

AmountInput.displayName = "AmountInput";

export { AmountInput };