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 | 1x 1x 1x 1x 1x 9x 8x 8x 8x 1x 34x 34x 34x 2x 2x 34x 34x 7x 7x 7x 7x 1x 25x 25x 25x 10x 15x 1x 18x 5x 7x 9x 9x 9x 9x 9x 9x 9x 1x 25x 1x | import * as React from 'react'; export interface Props { value?: number; onChange: (value?: number) => void; } export interface State { formattedValue: string; } export interface Options { decimalSeparator: string; thousandSeparator: string; precision: number; allowNegativeValues: boolean; } const defaults: Options = { decimalSeparator: ',', thousandSeparator: ' ', precision: 2, allowNegativeValues: false, }; export function createFormattedNumberInput<ExternalProps>(InputComponent: any, Ioptions: Partial<Options> = {}): React.ComponentClass<ExternalProps | Props> { const opts: Options = { ...defaults, ...options, }; const parse = (value: string) => { if (value) { const cleaned = value .replace(/\s/g, '') .replace(new RegExp(opts.decimalSeparator), '.'); const number = parseFloat(cleaned); return !isNaN(number) ? number : undefined; } } const format = (value: string) => { value = value.replace(opts.allowNegativeValues ? /[^\d.,-]/g : /[^\d.,]/g, ''); // only keep the first decimal separator value = value .replace(/[.,]/, '_') .replace(/[.,]/g, '') .replace(/_/, opts.decimalSeparator); // only keep `opts.precision` fraction digits if (value.indexOf(opts.decimalSeparator) !== -1) { const [integer, fractional] = value.split(opts.decimalSeparator); value = integer + opts.decimalSeparator + fractional.substr(0, opts.precision); } // separate thousands value = value.replace(/\B(?=(\d{3})+(?!\d))/g, opts.thousandSeparator); return value; } return class FormattedNumberInput extends React.Component<Props & ExternalProps, State> { private ref = React.createRef<HTMLInputElement>(); private caretPosition: number = 0; state: State = { formattedValue: '' }; static getDerivedStateFromProps(nextProps: Props & ExternalProps, prevState: State) { const formattedValue = format(String(nextProps.value)); const prevFormattedValueWithoutSpecialCharacters = prevState.formattedValue .replace(new RegExp(`${opts.decimalSeparator}0$`), '') .replace(new RegExp(`[${opts.decimalSeparator}-]`), ''); if (formattedValue !== prevFormattedValueWithoutSpecialCharacters) { return { formattedValue }; } return null; } componentDidUpdate(prevProps: Props & ExternalProps) { if (this.ref.current && prevProps.value !== this.props.value) { this.ref.current.setSelectionRange(this.caretPosition, this.caretPosition); } } private handleChange = (event: React.FormEvent<HTMLInputElement>) => { const inputted = event.currentTarget.value; const formatted = format(inputted); const parsed = parse(formatted); const delta = formatted.length - inputted.length; this.caretPosition = this.ref.current && this.ref.current.selectionEnd ? Math.max(this.ref.current.selectionEnd + delta, 0) : 0; this.setState({ formattedValue: formatted }, () => { this.props.onChange(parsed); }); } render() { return ( <InputComponent {...this.props} ref={this.ref} value={this.state.formattedValue} onChange={this.handleChange} /> ); } } }; |