All files / src/shared/GiveInputs GiveRangeInputs.tsx

24.44% Statements 11/45
7.14% Branches 3/42
42.85% Functions 3/7
24.44% Lines 11/45

Press n or j to go to the next uncovered block, b, p or k for the previous block.

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 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125                                        20x 20x 20x   20x                                               20x                                                                             20x                     20x 2x     20x 2x     20x                                    
import { Box } from "@mui/material";
import React, { useEffect, useState, useRef } from "react";
import { GiveInput } from "./GiveInput";
import { debounce } from "lodash";
 
function GiveRangeInputs({
  value: defaultValue,
  onChange,
  validate = true,
  isError: isInputError = false,
  currency = "USD",
  maxDigitsPerSide = 14,
}: {
  value: string;
  onChange: (val: string) => void;
  validate?: boolean;
  isError?: boolean;
  currency?: string;
  maxDigitsPerSide?: number;
}) {
  const [value, setValue] = useState(defaultValue);
  const [isError, setIsError] = useState(isInputError);
  const inputRef = useRef<HTMLInputElement | null>(null);
 
  const validateInput = (newValue: string) => {
    if (!newValue) {
      setIsError(true);
      return;
    }
 
    const parts = newValue.split("-");
    if (parts.length === 1) {
      setIsError(true);
      return;
    }
 
    const [minStr, maxStr] = parts;
    const min = minStr ? Number(minStr) : NaN;
    const max = maxStr ? Number(maxStr) : NaN;
 
    if (minStr === "" || maxStr === "") {
      setIsError(true);
      return;
    }
 
    setIsError(!isNaN(min) && !isNaN(max) && max < min);
  };
 
  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    let newValue = event.target.value;
 
    // Allow digits and optional decimals, with optional range (e.g. "10", "10.5", "10-20.5")
    if (/^\d*\.?\d*(-\d*\.?\d*)?$/.test(newValue) || newValue === "-") {
      const parts = newValue.split("-");
 
      // Enforce max digits on each side (excluding dot) and limit to 2 decimal places
      const sanitizePart = (part: string) => {
        // Split into integer and decimal parts
        const [integerPart, decimalPart] = part.split(".");
 
        // Limit integer part digits
        const digitsOnly = integerPart.replace(/\D/g, "");
        const limitedInteger =
          digitsOnly.length > maxDigitsPerSide
            ? digitsOnly.slice(0, maxDigitsPerSide)
            : digitsOnly;
 
        // If there's a decimal part, limit to 2 digits
        if (decimalPart !== undefined) {
          const limitedDecimal = decimalPart.slice(0, 2);
          return `${limitedInteger}.${limitedDecimal}`;
        }
 
        return limitedInteger;
      };
 
      if (parts.length === 1) {
        newValue = sanitizePart(parts[0]);
      } else if (parts.length === 2) {
        newValue = `${sanitizePart(parts[0])}-${sanitizePart(parts[1])}`;
      }
 
      setValue(newValue);
      validate && validateInput(newValue);
    }
  };
 
  const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
    if (
      (event.key === "Enter" || event.key === " " || event.key === "-") &&
      value &&
      !value.includes("-")
    ) {
      event.preventDefault();
      setValue(value + "-");
    }
  };
 
  const handleUpdateOnChange = debounce(() => {
    onChange?.(value);
  }, 300);
 
  useEffect(() => {
    handleUpdateOnChange();
  }, [isError, value]);
 
  return (
    <Box sx={{ width: "100%" }}>
      <GiveInput
        inputMode="numeric"
        fullWidth
        currency={currency}
        value={value}
        onChange={handleChange}
        onKeyDown={handleKeyDown}
        error={isError || isInputError}
        inputRef={inputRef}
        data-testid="range-input-id"
      />
    </Box>
  );
}
 
export default GiveRangeInputs;