From 59130470a85aac5cd78e7275958b98db8c63836f Mon Sep 17 00:00:00 2001 From: Lucian Date: Fri, 15 Feb 2019 12:18:43 +0000 Subject: [PATCH] Improve handling of decimal points and trailing zeroes in numbers Connects to #674 #958 Change-type: patch Signed-off-by: Lucian --- src/components/fields/NumberField.js | 85 ++++++++++++++++++-- src/components/widgets/BaseInput.js | 28 ++++++- test/ArrayField_test.js | 8 +- test/Form_test.js | 4 +- test/NumberField_test.js | 112 +++++++++++++++++++++++++-- test/uiSchema_test.js | 4 +- 6 files changed, 215 insertions(+), 26 deletions(-) diff --git a/src/components/fields/NumberField.js b/src/components/fields/NumberField.js index 1f7f7c112d..7b36bcf664 100644 --- a/src/components/fields/NumberField.js +++ b/src/components/fields/NumberField.js @@ -3,14 +3,83 @@ import React from "react"; import * as types from "../../types"; import { asNumber } from "../../utils"; -function NumberField(props) { - const { StringField } = props.registry.fields; - return ( - props.onChange(asNumber(value))} - /> - ); +// Matches a string that ends in a . character, optionally followed by a sequence of +// digits followed by any number of 0 characters up until the end of the line. +// Ensuring that there is at least one prefixed character is important so that +// you don't incorrectly match against "0". +const trailingCharMatcherWithPrefix = /\.([0-9]*0)*$/; + +// This is used for trimming the trailing 0 and . characters without affecting +// the rest of the string. Its possible to use one RegEx with groups for this +// functionality, but it is fairly complex compared to simply defining two +// different matchers. +const trailingCharMatcher = /[0.]0*$/; + +/** + * The NumberField class has some special handling for dealing with trailing + * decimal points and/or zeroes. This logic is designed to allow trailing values + * to be visible in the input element, but not be represented in the + * corresponding form data. + * + * The algorithm is as follows: + * + * 1. When the input value changes the value is cached in the component state + * + * 2. The value is then normalized, removing trailing decimal points and zeros, + * then passed to the "onChange" callback + * + * 3. When the component is rendered, the formData value is checked against the + * value cached in the state. If it matches the cached value, the cached + * value is passed to the input instead of the formData value + */ +class NumberField extends React.Component { + constructor(props) { + super(props); + + this.state = { + lastValue: props.value, + }; + } + + handleChange = value => { + // Cache the original value in component state + this.setState({ lastValue: value }); + + // Check that the value is a string (this can happen if the widget used is a + //