import * as React from 'react'; import {debounce as db, isEmpty, repeat} from 'lodash'; import OutsideClickHandler from 'react-outside-click-handler'; import classNames from 'classnames'; import FormComponent from '../FormComponent'; import {getId, nullToString, keyPressStringSTop,} from '../function'; import RenderHtml from '../renderFunction'; import PropsTypes from './types'; import Icon from '../Icon' import * as numeral from 'numeral'; interface State extends PropsTypes { focus: boolean, ddMenuIsActive: boolean, uuid: string, dropdownValue: any, isActiveComponent: boolean, precision: number, useThousandSeparator: boolean, useNegative: boolean, cursorPosition?: { start?: number, end?: number }, numeral: any } function getDropdownValue(dropdown: any) { return dropdown && dropdown.data.filter((item: any) => item[dropdown.displayValue] === dropdown.selected)[0] } function debounceTime(debounce:number) { return debounce ? debounce : 0 } function numberValue({ useThousandSeparator, precision, useNegative }, val:any) { if (!useNegative) { val = (val + '').replace(/,/g, ''); val = Math.abs(val) } if (useThousandSeparator && !precision) { val = numeral(val).format('0,0'); } if (useThousandSeparator && precision) { val = numeral(val).format('0,0.' + repeat('0', precision)) } if (!useThousandSeparator && precision) { val = numeral(val).format('0.' + repeat('0', precision)) } const x = numeral(val); return {_input:'', _value: '', ...x} } export default class NumberInput extends FormComponent { public state: State; private input = React.createRef(); // private caretStart: any; // private caretEnd: any; // private prevFormatMatch: any; private prevThousandsLength: number = 0; constructor(props: PropsTypes, {}) { super(props); const uuid = getId(props.id, this.defaultId); const {value, dropdown, debounce} = props; this.state = { focus: false, ddMenuIsActive: false, uuid: uuid, value: nullToString(value), dropdownValue: getDropdownValue(dropdown), autoFocus: false, isActiveComponent: false, precision: 0, useThousandSeparator: false, useNegative: false, numeral: null, ...props }; this.debounce = db(this.debounce, debounceTime(debounce || 0)); } async componentDidMount() { const {value} = this.state; const {autoFocus} = this.props; const formatted = numberValue(this.state, value); this.setState({ size: this.size(), readOnly: this.readOnly(), cursorPosition: this.getCursorPosition(), numeral: formatted, value: formatted._input }); if (autoFocus) { await this.focus(); } this.validate(value); this.props.element && this.props.element(this.input); } focus() { const {value} = this.state; this.input.current && this.input.current.focus(); if (value) { this.debounce() } this.setState({ focus: true, isActiveComponent: true }) } debounce() { this.dataOnReady() } componentWillReceiveProps(nextProps: any) { if (nextProps.value !== this.props.value) { const formatted = numberValue(this.state, nextProps.value); this.setState({ cursorPosition: this.getCursorPosition(), numeral: formatted, value: formatted._input }, () => { this.updateCursorPosition(); this.dataOnReady(); this.validate(nextProps.value); }); } if (nextProps.useThousandSeparator !== this.props.useThousandSeparator) { const updatedValue = numberValue({...this.state, useThousandSeparator: nextProps.useThousandSeparator }, this.state.numeral._input)._input; this.setState({ useThousandSeparator: nextProps.useThousandSeparator, value: updatedValue }) } if (nextProps.precision !== this.props.precision) { const updatedValue = numberValue({...this.state, precision: nextProps.precision }, this.state.numeral._input)._input; this.setState({ precision: nextProps.precision, value: updatedValue }) } if (nextProps.useNegative !== this.props.useNegative) { const updatedValue = numberValue({...this.state, useNegative: !!nextProps.useNegative }, this.state.numeral._input)._input; this.setState({ useNegative: nextProps.useNegative, value: updatedValue }) } } dataOnReady() { this.props.onChange && this.props.onChange(this.getInputData()['output']) } updateCursorPosition() { const { cursorPosition, value, useThousandSeparator } = this.state; let gap: number = 0; if (useThousandSeparator && typeof value !== 'undefined' && value !== null) { const currentThousandsLength = value.match(/,/g) ? value.split(',').length : 0; if (currentThousandsLength > this.prevThousandsLength) { gap = 1; } else if (currentThousandsLength < this.prevThousandsLength) { gap = -1; } else { gap = 0; } this.prevThousandsLength = currentThousandsLength; } if (this.input && this.input.current && cursorPosition && cursorPosition.start && cursorPosition.end) { this.input.current.selectionStart = cursorPosition.start + gap; this.input.current.selectionEnd = cursorPosition.end + gap; } } getCursorPosition(): { start: number, end: number } { const input = this.input.current; if (input && typeof input.selectionStart !== 'undefined' && typeof input.selectionEnd !== 'undefined' ) { return { start: input.selectionStart || 0, end: input.selectionEnd || 0 } } return { start: 0, end: 0 } } handleChange(e: any) { let inputValue: string = e.target.value; const formatted = numberValue(this.state, inputValue); this.setState({ cursorPosition: this.getCursorPosition(), numeral: formatted, value: formatted._input }, () => { this.updateCursorPosition(); this.dataOnReady(); this.validate(inputValue); }) } handleClick = () => { this.props.onClick && this.props.onClick() }; handlePress = (e: any) => { keyPressStringSTop(e); const { maxLength } = this.props; const ml = (maxLength || 25); let currentVal = e.target.value.substr(0, e.target.value.indexOf('.') !== -1 ? e.target.value.indexOf('.') : e.target.value.length); currentVal = currentVal.replace(/,/g, ''); if (currentVal.length > ml - 1 && e.target.selectionStart === e.target.selectionEnd) { e.preventDefault(); } this.props.onKeyPress && this.props.onKeyPress(e) }; getInputData() { const {value, dropdownValue} = this.state; const {dropdown} = this.props; let result: Object = {}; let dropdownData: Object = {}; let output: any = null; let str: any = numberValue(this.state, value && (value + '').replace(/(,)/g, '')); output = { value: str }; if (dropdown && !isEmpty(dropdown)) { dropdownData = { dropdown: dropdownValue[dropdown.displayValue] }; output = { value: str, ...dropdownData } } result = { value: str, output }; return result } setDropdownValue(val: any) { this.setState({ dropdownValue: val, ddMenuIsActive: false }, () => { this.dataOnReady() }) } renderDropDown(): JSX.Element | null { const {ddMenuIsActive, dropdownValue} = this.state; const {dropdown, disabled} = this.props; if (dropdown && !isEmpty(dropdown)) { return
} return null } outsideClick() { const {isActiveComponent} = this.state; if (isActiveComponent) { this.setState({ focus: false, ddMenuIsActive: false, isActiveComponent: false, }) } } public render(): JSX.Element { const {value, uuid } = this.state; const {label, icon, button, color, disabled, dropdown, maxLength, readOnly, infoText, loading, rounded, caption, noRadius, noBorder} = this.props; const uiClass = classNames(`krax-input ${this.size()} is-${color || 'default'}`, { 'is-rounded': rounded, 'is-danger': this.error(), 'is-focused': this.error(), 'no-border': noBorder, 'no-radius': noRadius, }), containerClass = classNames(`krax-control is-expanded ${this.size()}`, { 'has-icons-left': this.iconLeft(), 'has-icons-right': this.iconRight(), 'is-loading': loading, 'is-data': value }), fieldClass = classNames(`krax-field`, { 'has-addons': label || dropdown || button, }), captionClass = classNames('caption-text-ui', { 'is-data': value }); return ( { this.outsideClick() }}>
this.focus()}>
{RenderHtml.renderLabel(label, 'left', this.size())}
this.handleChange(val)} onClick={() => this.handleClick()} onKeyPress={(e) => { this.handlePress(e) }} spellCheck={false} disabled={disabled} readOnly={readOnly} maxLength={maxLength} ref={this.input} tabIndex={0} autoCorrect={'off'} />
{caption}
{RenderHtml.renderIcon(icon, 'left', this.size())} {!loading ? RenderHtml.renderIcon(icon, 'right', this.size()) : null}
{RenderHtml.renderLabel(label, 'right', this.size())} {this.renderDropDown()} {button ?
{button()}
: null}
{RenderHtml.renderError(this.error())} {isEmpty(this.error()) ?
{infoText}
: null}
); } }