# ivyforms/0.6/frontend/src/utils/utilNumberFormatter.ts

The Innovative Form Builder – IvyForms, version 0.6. 189 lines.

- Page: https://pluginprobe.com/plugins/ivyforms/0.6/code/frontend/src/utils/utilNumberFormatter.ts
- Raw: https://pluginprobe.com/plugins/ivyforms/0.6/raw/frontend/src/utils/utilNumberFormatter.ts
- Modified: 2025-11-19T18:42:10+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/ivyforms/0.6/code/frontend/src/utils/utilNumberFormatter.ts#L10-L20`.

```typescript
// Utility functions for number formatting and parsing

export type NumberFormat = 'us_decimal' | 'us_int' | 'eu_decimal' | 'eu_int' | ''

export const formatWithThousandsDynamic = (n: number, thousandsSep: string) => {
  const parts = n.toString().split('.')
  let intPart = parts[0]
  const decPart = parts[1] ?? ''
  intPart = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, thousandsSep)
  return { intPart, decPart }
}

export const formatNumber = (
  value: number | null | undefined,
  numberFormat: NumberFormat = '',
): string => {
  if (value === null || value === undefined || isNaN(value)) return ''
  const num = value
  switch (numberFormat) {
    case 'us_decimal': {
      const { intPart, decPart } = formatWithThousandsDynamic(Math.abs(num), ',')
      return (num < 0 ? '-' : '') + (decPart ? `${intPart}.${decPart}` : intPart)
    }
    case 'us_int': {
      const { intPart } = formatWithThousandsDynamic(Math.trunc(Math.abs(num)), ',')
      return (num < 0 ? '-' : '') + intPart
    }
    case 'eu_decimal': {
      const { intPart, decPart } = formatWithThousandsDynamic(Math.abs(num), '.')
      return (num < 0 ? '-' : '') + (decPart ? `${intPart},${decPart}` : intPart)
    }
    case 'eu_int': {
      const { intPart } = formatWithThousandsDynamic(Math.trunc(Math.abs(num)), '.')
      return (num < 0 ? '-' : '') + intPart
    }
    default:
      return String(num)
  }
}

export const sanitizeRaw = (raw: string, numberFormat: NumberFormat = ''): string => {
  if (!raw) return ''
  let v = raw.replace(/[^0-9,.-]/g, '')
  v = v.replace(/-/g, (m, idx) => (idx === 0 ? '-' : ''))
  if (v.length > 1 && v[0] !== '-') {
    v = v.replace(/-/g, '')
  }
  const fmt = numberFormat
  const isEU = fmt === 'eu_decimal' || fmt === 'eu_int'
  const allowDecimal = fmt === 'eu_decimal' || fmt === 'us_decimal' || fmt === ''
  if (isEU) {
    let seenDecimal = false
    let out = ''
    for (const ch of v) {
      if (ch === ',') {
        if (!allowDecimal || seenDecimal) {
          continue
        }
        seenDecimal = true
        out += ch
      } else if (ch === '.') {
        // skip thousands dot
      } else {
        out += ch
      }
    }
    v = out
  } else {
    let seenDecimal = false
    let out = ''
    for (const ch of v) {
      if (ch === '.') {
        if (!allowDecimal || seenDecimal) {
          continue
        }
        seenDecimal = true
        out += ch
      } else if (ch === ',') {
        // skip thousands comma
      } else {
        out += ch
      }
    }
    v = out
  }
  return v
}

export const parseToNumber = (
  raw: string,
  numberFormat: NumberFormat = '',
  min: number = -Infinity,
  max: number = Infinity,
): number | null => {
  if (!raw) return null
  const fmt = numberFormat
  let v = raw.trim()
  if (fmt === 'eu_decimal' || fmt === 'eu_int') {
    v = v.replace(/\./g, '')
    if (fmt === 'eu_decimal') v = v.replace(',', '.')
    else v = v.split(',')[0]
  } else if (fmt === 'us_decimal' || fmt === 'us_int') {
    v = v.replace(/,/g, '')
    if (fmt === 'us_int') v = v.split('.')[0]
  }
  v = v.replace(/[^0-9.+-]/g, '')
  if (v === '-' || v === '' || v === '.' || v === '-.') return null
  const num = Number(v)
  if (isNaN(num)) return null
  return Math.min(Math.max(num, min), max)
}

export const formatNumberForField = (raw: unknown, fmt: NumberFormat): string => {
  if (raw === null || raw === undefined || raw === '') return ''
  let num: number | null = null
  if (typeof raw === 'number') {
    num = isNaN(raw) ? null : raw
  } else if (typeof raw === 'string') {
    const s = raw.trim()
    if (s === '') return ''
    if (/^[0-9]+$/.test(s)) {
      num = parseFloat(s)
    } else if (/^[0-9.,-]+$/.test(s)) {
      let normalized = s
      const lastComma = s.lastIndexOf(',')
      const lastDot = s.lastIndexOf('.')
      if (lastComma !== -1 && lastDot !== -1) {
        if (lastComma > lastDot) {
          normalized = s.replace(/\./g, '').replace(',', '.')
        } else {
          normalized = s.replace(/,/g, '')
        }
      } else if (lastComma !== -1) {
        normalized = s
          .replace(/\./g, '')
          .replace(/,/g, '.')
          .replace(/(\.)(?=.*\.)/g, '')
      } else if (lastDot !== -1) {
        const parts = s.split('.')
        if (parts.length > 2) {
          const dec = parts.pop() as string
          const intPart = parts.join('')
          normalized = intPart + '.' + dec
        } else {
          normalized = s
        }
        normalized = normalized.replace(/,/g, '')
      }
      const parsed = parseFloat(normalized)
      num = isNaN(parsed) ? null : parsed
    } else {
      return String(raw)
    }
  }
  if (num === null) return ''
  const absParts = (n: number, decimals = 2) => {
    const str = n.toFixed(decimals)
    const [i, d] = str.split('.')
    return { i, d: d || '' }
  }
  const addThousands = (intPart: string, sep: string) =>
    intPart.replace(/\B(?=(\d{3})+(?!\d))/g, sep)
  switch (fmt) {
    case 'us_decimal': {
      const { i, d } = absParts(Math.abs(num), 2)
      const intF = addThousands(i, ',')
      return (num < 0 ? '-' : '') + `${intF}.${d}`
    }
    case 'us_int': {
      const t = Math.trunc(Math.abs(num))
      const intF = addThousands(t.toString(), ',')
      return (num < 0 ? '-' : '') + intF
    }
    case 'eu_decimal': {
      const { i, d } = absParts(Math.abs(num), 2)
      const intF = addThousands(i, '.')
      return (num < 0 ? '-' : '') + `${intF},${d}`
    }
    case 'eu_int': {
      const t = Math.trunc(Math.abs(num))
      const intF = addThousands(t.toString(), '.')
      return (num < 0 ? '-' : '') + intF
    }
    default:
      // Show up to 2 decimals if needed, otherwise integer
      return num % 1 === 0 ? num.toString() : num.toFixed(2)
  }
}

```
