Use TextMask on CurrencyInput

This commit is contained in:
Eric Schultz 2018-10-03 13:00:22 -05:00
parent 70e82776e0
commit a5b3d5d198
14 changed files with 566 additions and 53 deletions

View file

@ -8,6 +8,8 @@ import {HoudiniField} from "../../lib/houdini_form";
import ReactInput from "./form/ReactInput"; import ReactInput from "./form/ReactInput";
import ReactSelect from './form/ReactSelect'; import ReactSelect from './form/ReactSelect';
import ReactTextarea from "./form/ReactTextarea"; import ReactTextarea from "./form/ReactTextarea";
import ReactMaskedInput from "./form/ReactMaskedInput";
import createNumberMask from "../../lib/createNumberMask";
export const BasicField = observer((props:{field:Field, placeholder?:string, label?:string, wrapperClassName?:string, inputClassNames?:string}) =>{ export const BasicField = observer((props:{field:Field, placeholder?:string, label?:string, wrapperClassName?:string, inputClassNames?:string}) =>{
@ -44,17 +46,26 @@ export const TextareaField = observer((props:{field:Field, placeholder?:string,
</LabeledFieldComponent> </LabeledFieldComponent>
}) })
export const CurrencyField = observer((props:{field:Field,placeholder?:string, label?:string, currencySymbol?:string, wrapperClassName?:string, inputClassNames?:string}) => { export const CurrencyField = observer((props:{field:Field,placeholder?:string, label?:string, currencySymbol?:string, wrapperClassName?:string, inputClassNames?:string, mustBeNegative?:boolean, allowNegative?:boolean}) => {
let field = props.field as HoudiniField let field = props.field as HoudiniField
let currencySymbolId = props.field.id + "_____currency_symbol" let currencySymbol = props.mustBeNegative ? "-$" : "$"
let allowNegative = props.allowNegative || !props.mustBeNegative
return <LabeledFieldComponent return <LabeledFieldComponent
inputId={props.field.id} labelText={field.label} inError={field.hasError} error={field.error} inputId={props.field.id} labelText={field.label} inError={field.hasError} error={field.error}
inStickyError={field.hasServerError} stickyError={field.serverError} inStickyError={field.hasServerError} stickyError={field.serverError}
className={props.wrapperClassName} > className={props.wrapperClassName} >
<div className="input-group">
<span className="input-group-addon" id={currencySymbolId}>{props.currencySymbol}</span> <ReactMaskedInput field={field} label={props.label} placeholder={props.placeholder}
<ReactInput field={field} label={props.label} placeholder={props.placeholder} className={`form-control ${props.inputClassNames}`} aria-describedby={currencySymbolId}/> className={`form-control ${props.inputClassNames}`} guide={true}
</div> mask={createNumberMask({allowDecimal:true,
requireDecimal:true,
prefix:currencySymbol,
allowNegative:allowNegative,
fixedDecimalScale:true
})}
showMask={true} placeholderChar={'0'}
/>
</LabeledFieldComponent> </LabeledFieldComponent>

View file

@ -0,0 +1,10 @@
// License: LGPL-3.0-or-later
import * as React from 'react';
import 'jest';
import ReactMaskedInput from './ReactMaskedInput'
describe('ReactMaskedInput', () => {
test('your test here', () => {
expect(false).toBe(true)
})
})

View file

@ -0,0 +1,81 @@
// License: LGPL-3.0-or-later
import * as React from 'react';
import { observer } from 'mobx-react';
import {ReactInputProps} from "./react_input_props";
import {InputHTMLAttributes} from "react";
import {action, observable} from "mobx";
import {Field} from "mobx-react-form";
import {castToNullIfUndef} from "../../../lib/utils";
import MaskedInput, {maskArray} from "react-text-mask";
type InputTypes = ReactInputProps &
InputHTMLAttributes<HTMLInputElement> & {
mask?: maskArray | ((value: string) => maskArray);
guide?: boolean;
placeholderChar?: string;
keepCharPositions?: boolean;
pipe?: (
conformedValue: string,
config: any
) => false | string | { value: string; indexesOfPipedChars: number[] };
showMask?: boolean;
}
class ReactMaskedInput extends React.Component<InputTypes, {}> {
constructor(props:InputTypes){
super(props)
}
@observable
field:Field
@action.bound
componentWillMount(){
this.field = this.props.field
this.updateProps()
}
componentWillUnmount(){
}
componentDidUpdate(prevProps: Readonly<InputTypes>, prevState: Readonly<{}>): void {
this.updateProps()
}
@action.bound
updateProps() {
this.field.set('label', castToNullIfUndef(this.props.label))
this.field.set('placeholder', castToNullIfUndef(this.props.placeholder))
}
///Removes the properties we don't want to put into the input element
@action.bound
winnowProps(): InputTypes {
let ourProps = {...this.props}
delete ourProps.field
delete ourProps.value
return ourProps
}
render() {
return <MaskedInput {...this.winnowProps()} {...this.field.bind()}/>
}
}
export default observer(ReactMaskedInput)

View file

@ -7,11 +7,3 @@ exports[`ReactInput gets removed properly 1`] = `
/> />
</form> </form>
`; `;
exports[`ReactInput no children passed in gets removed properly 1`] = `
<form>
<button
onClick={[Function]}
/>
</form>
`;

View file

@ -1,3 +1,4 @@
// License: LGPL-3.0-or-later
import {Field} from "mobx-react-form"; import {Field} from "mobx-react-form";
export interface ReactInputProps export interface ReactInputProps

View file

@ -19,12 +19,14 @@ import {TwoColumnFields} from "../common/layout";
import {Validations} from "../../lib/vjf_rules"; import {Validations} from "../../lib/vjf_rules";
import _ = require("lodash"); import _ = require("lodash");
import {Dedication, parseDedication, serializeDedication} from '../../lib/dedication'; import {Dedication, parseDedication, serializeDedication} from '../../lib/dedication';
import blacklist = require("validator/lib/blacklist");
import {createFieldDefinition} from "../../lib/mobx_utils";
interface Charge { interface Charge {
status: string status: string
} }
interface RecurringDonation { interface RecurringDonation {
interval?: number interval?: number
time_unit?: string time_unit?: string
@ -79,7 +81,6 @@ export interface FundraiserInfo {
class EditPaymentPanelForm extends HoudiniForm { class EditPaymentPanelForm extends HoudiniForm {
} }
@ -160,9 +161,7 @@ class EditPaymentPane extends React.Component<EditPaymentPaneProps & InjectedInt
} }
createDefinition<TInputType>(fieldDef:FieldDefinition<TInputType>) : FieldDefinition<TInputType> {
return fieldDef;
}
@action.bound @action.bound
loadFormFromData() { loadFormFromData() {
@ -171,18 +170,24 @@ class EditPaymentPane extends React.Component<EditPaymentPaneProps & InjectedInt
let params: {[name:string]:FieldDefinition} = { let params: {[name:string]:FieldDefinition} = {
'event': {name: 'event', label: 'Event', value: eventId}, 'event': {name: 'event', label: 'Event', value: eventId},
'campaign': {name: 'campaign', label: 'Campaign', value: campaignId}, 'campaign': {name: 'campaign', label: 'Campaign', value: campaignId},
'gross_amount': {name: 'gross_amount', label: 'Gross Amount', value: centsToDollars(this.props.data.gross_amount)}, 'gross_amount': createFieldDefinition({name: 'gross_amount', label: 'Gross Amount', value: this.props.data.gross_amount,
'fee_total': {name: 'fee_total', label: 'Fees', value: centsToDollars(this.props.data.fee_total)}, input: (amount:number) => centsToDollars(amount),
'date': this.createDefinition({name: 'date', label: 'Date', output: (dollarString:string) => parseFloat(blacklist(dollarString, '$,'))
}),
'fee_total': createFieldDefinition({name: 'fee_total', label: 'Fees', value: this.props.data.fee_total,
input: (amount:number) => centsToDollars(amount),
output: (dollarString:string) => parseFloat(blacklist(dollarString, '$,'))
}),
'date': createFieldDefinition({name: 'date', label: 'Date',
value: this.props.data.date, value: this.props.data.date,
input: (isoTime:string) => this.nonprofitTimezonedDates.readable_date(isoTime), input: (isoTime:string) => this.nonprofitTimezonedDates.readable_date(isoTime),
output:(date:string) => this.nonprofitTimezonedDates.readable_date_time_to_iso(date)}), output:(date:string) => this.nonprofitTimezonedDates.readable_date_time_to_iso(date)}),
'dedication': {name: 'dedication', label: 'Dedication', fields: [ 'dedication': {name: 'dedication', label: 'Dedication', fields: [
this.createDefinition({name:'type', label: 'Dedication Type', value: this.dedication.type}), createFieldDefinition({name:'type', label: 'Dedication Type', value: this.dedication.type}),
this.createDefinition({name: 'supporter_id', type: 'hidden', value: this.dedication.supporter_id}), createFieldDefinition({name: 'supporter_id', type: 'hidden', value: this.dedication.supporter_id}),
this.createDefinition({name:'name', label:'Person dedicated for', value: this.dedication.name}), createFieldDefinition({name:'name', label:'Person dedicated for', value: this.dedication.name}),
this.createDefinition({name: 'contact', type: 'hidden', value: this.dedication.contact}), createFieldDefinition({name: 'contact', type: 'hidden', value: this.dedication.contact}),
this.createDefinition({name: 'note', value: this.dedication.note}) createFieldDefinition({name: 'note', value: this.dedication.note})
]}, ]},
'designation': {name: 'designation', label: 'Designation', value: this.props.data.donation.designation}, 'designation': {name: 'designation', label: 'Designation', value: this.props.data.donation.designation},
'comment': {name: 'comment', label: 'Note', value: this.props.data.donation.comment} 'comment': {name: 'comment', label: 'Note', value: this.props.data.donation.comment}
@ -201,7 +206,7 @@ class EditPaymentPane extends React.Component<EditPaymentPaneProps & InjectedInt
return new EditPaymentPanelForm({fields: _.values(params)}, { return new EditPaymentPanelForm({fields: _.values(params)}, {
hooks: { hooks: {
onSubmit: async (e: Field) => { onSuccess: async (e: Field) => {
await this.updateDonation() await this.updateDonation()
} }
} }
@ -346,7 +351,7 @@ class EditPaymentPane extends React.Component<EditPaymentPaneProps & InjectedInt
<TwoColumnFields> <TwoColumnFields>
<CurrencyField field={this.form.$('gross_amount')} label={"Gross Amount"} currencySymbol={"$"}/> <CurrencyField field={this.form.$('gross_amount')} label={"Gross Amount"} currencySymbol={"$"}/>
<CurrencyField field={this.form.$('fee_total')} label={"Processing Fees"}/> <CurrencyField field={this.form.$('fee_total')} label={"Processing Fees"} mustBeNegative={true}/>
</TwoColumnFields> </TwoColumnFields>

View file

@ -0,0 +1,236 @@
//from: https://github.com/text-mask/text-mask/pull/760/commits/f66c81b62c4894b7da43862bee5943f659dc7537
// under: Unlicense
import createNumberMask from './createNumberMask'
describe('createNumberMask', () => {
it('can returns a configured currency mask', () => {
let numberMask = createNumberMask()
expect(typeof numberMask).toBe('function')
})
it('takes a prefix', () => {
let numberMask = createNumberMask({prefix: '$'})
expect(numberMask('12')).toEqual(['$', /\d/, /\d/])
})
it('takes a suffix', () => {
let numberMask = createNumberMask({suffix: ' $', prefix: ''})
expect(numberMask('12')).toEqual([/\d/, /\d/, ' ', '$'])
})
it('works when the prefix contains numbers', () => {
let numberMask = createNumberMask({prefix: 'm2 '})
expect(numberMask('m2 1')).toEqual(['m', '2', ' ', /\d/])
})
it('works when the suffix contains numbers', () => {
let numberMask = createNumberMask({prefix: '', suffix: ' m2'})
expect(numberMask('1 m2')).toEqual([/\d/, ' ', 'm', '2'])
})
it('works when there is a decimal and the suffix contains numbers', () => {
let numberMask = createNumberMask({prefix: '', suffix: ' m2', allowDecimal: true})
expect(numberMask('1.2 m2')).toEqual([/\d/, '[]', '.', '[]', /\d/, ' ', 'm', '2'])
})
it('can be configured to add a thousands separator or not', () => {
let numberMaskWithoutThousandsSeparator = createNumberMask({includeThousandsSeparator: false})
expect(numberMaskWithoutThousandsSeparator('1000')).toEqual(['$', /\d/, /\d/, /\d/, /\d/])
let numberMaskWithThousandsSeparator = createNumberMask()
expect(numberMaskWithThousandsSeparator('1000')).toEqual(['$', /\d/, ',', /\d/, /\d/, /\d/])
})
it('can be configured with a custom character for the thousands separator', () => {
let numberMask = createNumberMask({thousandsSeparatorSymbol: '.'})
expect(numberMask('1000')).toEqual(['$', /\d/, '.', /\d/, /\d/, /\d/])
})
it('can be configured to accept a fraction and returns the fraction separator with caret traps', () => {
let numberMask = createNumberMask({allowDecimal: true})
expect(numberMask('1000.')).toEqual(['$', /\d/, ',', /\d/, /\d/, /\d/, '[]', '.', '[]'])
})
it('rejects fractions by default', () => {
let numberMask = createNumberMask()
expect(numberMask('1000.')).toEqual(['$', /\d/, ',', /\d/, /\d/, /\d/])
})
it('can be configured with a custom character for the fraction separator', () => {
let numberMask = createNumberMask({
allowDecimal: true,
decimalSymbol: ',',
thousandsSeparatorSymbol: '.'
})
expect(numberMask('1000,')).toEqual(['$', /\d/, '.', /\d/, /\d/, /\d/, '[]', ',', '[]'])
})
it('can limit the length of the fraction', () => {
let numberMask = createNumberMask({allowDecimal: true, decimalLimit: 2})
expect(numberMask('1000.3823')).toEqual(['$', /\d/, ',', /\d/, /\d/, /\d/, '[]', '.', '[]', /\d/, /\d/])
})
it('can require a fraction', () => {
let numberMask = createNumberMask({requireDecimal: true})
expect(numberMask('1000')).toEqual(['$', /\d/, ',', /\d/, /\d/, /\d/, '[]', '.', '[]'])
})
it('accepts negative integers', function() {
let numberMask = createNumberMask({allowNegative: true})
expect(numberMask('-$12')).toEqual([/-/, '$', /\d/, /\d/])
})
it('ignores multiple minus signs', function() {
let numberMask = createNumberMask({allowNegative: true})
expect(numberMask('--$12')).toEqual([/-/, '$', /\d/, /\d/])
})
it('adds a digit placeholder if the input is nothing but a minus sign in order to attract the caret', () => {
let numberMask = createNumberMask({allowNegative: true})
expect(numberMask('-')).toEqual([/-/, '$', /\d/])
})
it('starts with dot should be considered as decimal input', () => {
let numberMask = createNumberMask({prefix: '$', allowDecimal: true})
expect(numberMask('.')).toEqual(['$', '0', '.', /\d/])
numberMask = createNumberMask({prefix: '#', allowDecimal: true})
expect(numberMask('.')).toEqual(['#', '0', '.', /\d/])
numberMask = createNumberMask({prefix: '', allowDecimal: true})
expect(numberMask('.')).toEqual(['0', '.', /\d/])
numberMask = createNumberMask({allowDecimal: false})
expect(numberMask('.')).toEqual(['$'])
numberMask = createNumberMask({prefix: '', suffix: '$', allowDecimal: true})
expect(numberMask('.')).toEqual(['0', '.', /\d/, '$'])
})
it('can allow leading zeroes', function() {
let numberMask = createNumberMask({allowLeadingZeroes: true})
expect(numberMask('012')).toEqual(['$', /\d/, /\d/, /\d/])
})
it('works with large numbers when leading zeroes is false', function() {
let numberMask = createNumberMask({allowLeadingZeroes: false})
expect(numberMask('111111111111111111111111')).toEqual([
'$', /\d/, /\d/, /\d/, ',', /\d/, /\d/, /\d/, ',', /\d/, /\d/, /\d/, ',', /\d/, /\d/, /\d/, ',',
/\d/, /\d/, /\d/, ',', /\d/, /\d/, /\d/, ',', /\d/, /\d/, /\d/, ',', /\d/, /\d/, /\d/
])
})
describe('integer limiting', () => {
it('can limit the length of the integer part', () => {
let numberMask = createNumberMask({integerLimit: 3})
expect(numberMask('1999')).toEqual(['$', /\d/, /\d/, /\d/])
})
it('works when there is a prefix', () => {
let numberMask = createNumberMask({integerLimit: 3, prefix: '$'})
expect(numberMask('$1999')).toEqual(['$', /\d/, /\d/, /\d/])
})
it('works when there is a thousands separator', () => {
expect(createNumberMask({integerLimit: 4, prefix: ''})('1,9995'))
.toEqual([/\d/, ',', /\d/, /\d/, /\d/])
expect(createNumberMask({integerLimit: 7, prefix: ''})('1,000,0001'))
.toEqual([/\d/, ',', /\d/, /\d/, /\d/, ',', /\d/, /\d/, /\d/])
})
it('works when there is a decimal and a prefix', () => {
let numberMask = createNumberMask({integerLimit: 3, allowDecimal: true})
expect(numberMask('$199.34')).toEqual(['$', /\d/, /\d/, /\d/, '[]', '.', '[]', /\d/, /\d/])
})
it('works when there is a decimal and no prefix', () => {
let numberMask = createNumberMask({integerLimit: 3, allowDecimal: true, prefix: ''})
expect(numberMask('199.34')).toEqual([/\d/, /\d/, /\d/, '[]', '.', '[]', /\d/, /\d/])
})
it('works when thousandsSeparatorSymbol is a period', () => {
let numberMask = createNumberMask({
prefix: '',
thousandsSeparatorSymbol: '.',
decimalSymbol: ',',
allowDecimal: true,
requireDecimal: true,
integerLimit: 5,
decimalLimit: 3,
})
expect(numberMask('1234567890,12345678'))
.toEqual([/\d/, /\d/, '.', /\d/, /\d/, /\d/, '[]', ',', '[]', /\d/, /\d/, /\d/])
})
})
describe('numberMask default behavior', () => {
let numberMask:ReturnType<typeof createNumberMask> = null
beforeEach(() => {
numberMask = createNumberMask()
})
it('returns a mask that has the same number of digits as the given number', () => {
expect(numberMask('20382')).toEqual(['$', /\d/, /\d/, ',', /\d/, /\d/, /\d/])
})
it('uses the dollar symbol as the default prefix', () => {
expect(numberMask('1')).toEqual(['$', /\d/])
})
it('adds no suffix by default', () => {
expect(numberMask('1')).toEqual(['$', /\d/])
})
it('returns a mask that appends the currency symbol', () => {
expect(numberMask('1')).toEqual(['$', /\d/])
})
it('adds adds a comma after a thousand', () => {
expect(numberMask('1000')).toEqual(['$', /\d/, ',', /\d/, /\d/, /\d/])
})
it('adds as many commas as needed', () => {
expect(numberMask('23984209342084'))
.toEqual(
['$', /\d/, /\d/, ',', /\d/, /\d/, /\d/, ',', /\d/, /\d/, /\d/, ',', /\d/, /\d/, /\d/, ',', /\d/, /\d/, /\d/]
)
})
it('accepts any string and strips out any non-digit characters', () => {
expect(numberMask('h4x0r sp43k')).toEqual(['$', /\d/, ',', /\d/, /\d/, /\d/])
})
it('does not allow leading zeroes', function() {
let numberMask = createNumberMask()
expect(numberMask('012')).toEqual(['$', /\d/, /\d/])
})
it('allows one leading zero followed by a fraction', function() {
let numberMask = createNumberMask({allowDecimal: true})
expect(numberMask('0.12')).toEqual(['$', /\d/, '[]', '.', '[]', /\d/, /\d/])
})
it('ignores fixedDecimalScale when requireDecimal:false', () => {
let numberMask = createNumberMask({allowDecimal: true, fixedDecimalScale:true})
expect(numberMask('0.1')).toEqual(['$', /\d/, '[]', '.', '[]', /\d/])
})
it('fixedDecimalScale expands decimal', () => {
let numberMask = createNumberMask({requireDecimal: true, fixedDecimalScale:true})
expect(numberMask('0.1')).toEqual(['$', /\d/, '[]', '.', '[]', /\d/, /\d/])
})
})
})

View file

@ -0,0 +1,184 @@
//from: https://github.com/text-mask/text-mask/pull/760/commits/f66c81b62c4894b7da43862bee5943f659dc7537
// under: Unlicense
const dollarSign = '$'
const emptyString = ''
const comma = ','
const period = '.'
const minus = '-'
const minusRegExp = /-/
const nonDigitsRegExp = /\D+/g
const number = 'number'
const digitRegExp = /\d/
const caretTrap = '[]'
interface NumberMaskProps {
prefix?: string
suffix?: string
includeThousandsSeparator?: boolean
thousandsSeparatorSymbol?: string
allowDecimal?:boolean
decimalSymbol?: string
decimalLimit?: number
integerLimit?: number
requireDecimal?: boolean
allowNegative?: boolean
allowLeadingZeroes?: boolean
fixedDecimalScale?:boolean
alwaysNegative?: boolean
}
export default function createNumberMask({
prefix = dollarSign,
suffix = emptyString,
includeThousandsSeparator = true,
thousandsSeparatorSymbol = comma,
allowDecimal = false,
decimalSymbol = period,
decimalLimit = 2,
requireDecimal = false,
allowNegative = false,
allowLeadingZeroes = false,
fixedDecimalScale = false,
integerLimit = null,
alwaysNegative = false
}:NumberMaskProps = {}) {
const prefixLength = prefix && prefix.length || 0
const suffixLength = suffix && suffix.length || 0
const thousandsSeparatorSymbolLength = thousandsSeparatorSymbol && thousandsSeparatorSymbol.length || 0
function numberMask(rawValue = emptyString) {
const rawValueLength = rawValue.length
if (
rawValue === emptyString ||
(rawValue[0] === prefix[0] && rawValueLength === 1)
) {
return stringMaskArray(prefix.split(emptyString)).concat([digitRegExp]).concat(suffix.split(emptyString))
} else if (
rawValue === decimalSymbol &&
allowDecimal
) {
return stringMaskArray(prefix.split(emptyString)).concat(['0', decimalSymbol, digitRegExp]).concat(suffix.split(emptyString))
}
const isNegative = ((rawValue[0] === minus) && allowNegative)
//If negative remove "-" sign
if (isNegative) {
rawValue = rawValue.toString().substr(1)
}
const indexOfLastDecimal = rawValue.lastIndexOf(decimalSymbol)
const hasDecimal = indexOfLastDecimal !== -1
let integer
let fraction
let mask
// remove the suffix
if (rawValue.slice(suffixLength * -1) === suffix) {
rawValue = rawValue.slice(0, suffixLength * -1)
}
if (hasDecimal && (allowDecimal || requireDecimal)) {
integer = rawValue.slice(rawValue.slice(0, prefixLength) === prefix ? prefixLength : 0, indexOfLastDecimal)
fraction = rawValue.slice(indexOfLastDecimal + 1, rawValueLength)
fraction = convertToMask(fraction.replace(nonDigitsRegExp, emptyString))
} else {
if (rawValue.slice(0, prefixLength) === prefix) {
integer = rawValue.slice(prefixLength)
} else {
integer = rawValue
}
}
if (integerLimit && typeof integerLimit === number) {
const thousandsSeparatorRegex = thousandsSeparatorSymbol === '.' ? '[.]' : `${thousandsSeparatorSymbol}`
const numberOfThousandSeparators = (integer.match(new RegExp(thousandsSeparatorRegex, 'g')) || []).length
integer = integer.slice(0, integerLimit + (numberOfThousandSeparators * thousandsSeparatorSymbolLength))
}
integer = integer.replace(nonDigitsRegExp, emptyString)
if (!allowLeadingZeroes) {
integer = integer.replace(/^0+(0$|[^0])/, '$1')
}
integer = (includeThousandsSeparator) ? addThousandsSeparator(integer, thousandsSeparatorSymbol) : integer
mask = convertToMask(integer)
if ((hasDecimal && allowDecimal) || requireDecimal === true) {
if (rawValue[indexOfLastDecimal - 1] !== decimalSymbol) {
mask.push(caretTrap)
}
mask.push(decimalSymbol, caretTrap)
if (fraction) {
if (typeof decimalLimit === number) {
fraction = fraction.slice(0, decimalLimit)
}
mask = mask.concat(fraction)
}
if (requireDecimal === true) {
if (fixedDecimalScale === true) {
const decimalLimitRemaining = fraction ? decimalLimit - fraction.length : decimalLimit
for (var i = 0; i < decimalLimitRemaining; i++) {
mask.push(digitRegExp)
}
} else if (rawValue[indexOfLastDecimal - 1] === decimalSymbol) {
mask.push(digitRegExp)
}
}
}
if (prefixLength > 0) {
let res:(string|RegExp)[] = prefix.split(emptyString)
mask = res.concat(mask)
}
if (isNegative) {
// If user is entering a negative number, add a mask placeholder spot to attract the caret to it.
if (mask.length === prefixLength) {
mask.push(digitRegExp)
}
mask = stringMaskArray([minusRegExp]).concat(mask)
}
if (suffix.length > 0) {
mask = mask.concat(suffix.split(emptyString))
}
return mask
}
return numberMask
}
function stringMaskArray(i:(string|(string|RegExp)[])):(string|RegExp)[] {
if (typeof i === 'string'){
return [i]
}
return i
}
function convertToMask(strNumber:string):(string|RegExp)[] {
return strNumber
.split(emptyString)
.map((char) => digitRegExp.test(char) ? digitRegExp : char)
}
// http://stackoverflow.com/a/10899795/604296
function addThousandsSeparator(n:string, thousandsSeparatorSymbol:string) {
return n.replace(/\B(?=(\d{3})+(?!\d))/g, thousandsSeparatorSymbol)
}

View file

@ -0,0 +1,5 @@
import {FieldDefinition} from "mobx-react-form";
export function createFieldDefinition<TInOut>(fieldDef:FieldDefinition<TInOut>) : FieldDefinition<TInOut> {
return fieldDef;
}

View file

@ -55,7 +55,7 @@ export class Validations {
{ {
return ({field, validator}:ValidationInput) => { return ({field, validator}:ValidationInput) => {
return [ return [
parseFloat(field.value) >= value, parseFloat(field.get('value')) >= value,
`${field.label} must be at least ${value}` `${field.label} must be at least ${value}`
] ]
} }
@ -64,7 +64,7 @@ export class Validations {
static isLessThanOrEqualTo(value:number, flip:boolean=false) : ({field, validator}:ValidationInput) => StringBoolTuple static isLessThanOrEqualTo(value:number, flip:boolean=false) : ({field, validator}:ValidationInput) => StringBoolTuple
{ {
return ({field, validator}:ValidationInput) => { return ({field, validator}:ValidationInput) => {
let float = parseFloat(field.value) let float = field.get('value')
return [ return [
(flip ? -1 * float : float) <= value, (flip ? -1 * float : float) <= value,
`${field.label} must be no more than ${value}` `${field.label} must be no more than ${value}`

9
package-lock.json generated
View file

@ -199,6 +199,15 @@
"@types/react": "*" "@types/react": "*"
} }
}, },
"@types/react-text-mask": {
"version": "5.4.2",
"resolved": "https://registry.npmjs.org/@types/react-text-mask/-/react-text-mask-5.4.2.tgz",
"integrity": "sha512-R4h07wAeOPh6xc6E9qYPMgYpeuUJ1w3rs3oWwp9oWIVPtnAGxPGDuCPEs2+ynlP5syeM7heZeZeM8saAHRgENA==",
"dev": true,
"requires": {
"@types/react": "*"
}
},
"@types/sinon": { "@types/sinon": {
"version": "4.3.3", "version": "4.3.3",
"resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-4.3.3.tgz", "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-4.3.3.tgz",

View file

@ -30,6 +30,7 @@
"@types/react-dom": "^16.0.5", "@types/react-dom": "^16.0.5",
"@types/react-intl": "^2.3.7", "@types/react-intl": "^2.3.7",
"@types/react-test-renderer": "^16.0.1", "@types/react-test-renderer": "^16.0.1",
"@types/react-text-mask": "^5.4.2",
"@types/sinon": "^4.3.3", "@types/sinon": "^4.3.3",
"@types/validator": "^9.4.1", "@types/validator": "^9.4.1",
"babel-core": "^6.26.0", "babel-core": "^6.26.0",

View file

@ -1,20 +0,0 @@
// License: LGPL-3.0-or-later
import {Component} from 'react'
export interface MaskedInputProps
{
mask: Array<any>|Function|Boolean | {mask: Array<any> | Function, pipe: Function}
guide?: Boolean
value?: String| Number,
pipe?: Function,
placeholderChar?: String,
keepCharPositions?: Boolean,
showMask?: Boolean,
[additionalProps: string] : any
}
export class MaskedInput extends Component<MaskedInputProps, {}> {
}

View file

@ -1,2 +0,0 @@
// License: LGPL-3.0-or-later
declare module "text-mask"