Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@soramitsu/soramitsu-js-ui",
"version": "0.8.9",
"version": "0.8.10",
"private": false,
"publishConfig": {
"registry": "https://nexus.iroha.tech/repository/npm-soramitsu/"
Expand Down
82 changes: 71 additions & 11 deletions src/components/Input/SFloatInput.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
<template>
<s-input
v-float
:placeholder="placeholder"
:value="value"
:placeholder="placeholderValue"
:value="formatted"
v-bind="$attrs"
v-on="{
...$listeners,
Expand All @@ -19,6 +18,11 @@ import SInput from './SInput.vue'
import { Float } from '../../directives'

const DEFAULT_VALUE = ''
const DEFAULT_DECIMAL_DELIMITER = '.'
const DEFAULT_DELIMITERS = {
thousand: ',',
decimal: DEFAULT_DECIMAL_DELIMITER
}

const isNumberLikeValue = (value: any): boolean => {
return Number.isFinite(Number(value))
Expand All @@ -36,11 +40,28 @@ const decimalsValidator = x => x === undefined || x >= 0
})
export default class SFloatInput extends Vue {
@Prop({ type: String, default: DEFAULT_VALUE }) readonly value!: string
@Prop({ type: String, default: '0.0' }) readonly placeholder!: string
@Prop({ type: String }) readonly placeholder!: string
@Prop({ type: Number, default: undefined, validator: decimalsValidator }) readonly decimals!: number
@Prop({ type: Boolean, default: false }) readonly hasLocaleString?: boolean
@Prop({ type: Object, default: () => DEFAULT_DELIMITERS }) readonly delimiters?: any
@Prop({ type: [String, Number], default: undefined, validator: isNumberLikeValue }) readonly max!: string | number

get placeholderValue (): string {
return this.placeholder || '0'.concat(this.delimiters.decimal, '0')
}

get formatted (): string {
return this.hasLocaleString ? this.toLocaleString() : this.value
}

handleInput (value: string): void {
if (this.hasLocaleString) {
// Cleanup value's format
value = value.replace(new RegExp('\\' + this.delimiters.thousand, 'g'), '')
if (this.delimiters.decimal !== DEFAULT_DECIMAL_DELIMITER) {
value = value.replace(this.delimiters.decimal, DEFAULT_DECIMAL_DELIMITER)
}
}
const newValue = [
(v) => this.formatNumberField(v, this.decimals),
(v) => isNumberLikeValue(v) ? v : DEFAULT_VALUE,
Expand Down Expand Up @@ -68,15 +89,15 @@ export default class SFloatInput extends Vue {

private trimNeedlessSymbols (value: string): string {
// Trim zeros in the beginning
if (value.indexOf('0') === 0 && value.indexOf('.') !== 1) {
if (value.indexOf('0') === 0 && value.indexOf(DEFAULT_DECIMAL_DELIMITER) !== 1) {
value = value.replace(/^0+/, '')
}
// add leading zero before floating point
if (value.indexOf('.') === 0) {
if (value.indexOf(DEFAULT_DECIMAL_DELIMITER) === 0) {
value = '0' + value
}
// Trim dot in the end
if (value.indexOf('.') === value.length - 1) {
if (value.indexOf(DEFAULT_DECIMAL_DELIMITER) === value.length - 1) {
value = value.slice(0, -1)
}

Expand All @@ -86,25 +107,64 @@ export default class SFloatInput extends Vue {
private formatNumberField (value: string, decimals: number): string {
if (!['string', 'number'].includes(typeof value)) return value

let formatted = String(value).replace(/[^\d.]/g, '')
let formatted = String(value).replace(new RegExp('[^\\d' + DEFAULT_DECIMAL_DELIMITER + ']', 'g'), '')
const decimalDelimiterIndex = formatted.indexOf(DEFAULT_DECIMAL_DELIMITER)

if (formatted.indexOf('.') === 0) {
// Add prefix zero if needed
if (decimalDelimiterIndex === 0) {
formatted = '0' + formatted
}

const lengthLimit = this.valueMaxLength(formatted, decimals)
// Avoid several decimal delimiters
if ((value.match(new RegExp(DEFAULT_DECIMAL_DELIMITER, 'g')) || []).length > 1) {
formatted = formatted.substring(0, decimalDelimiterIndex + 1) + formatted.substring(decimalDelimiterIndex + 1).replace(DEFAULT_DECIMAL_DELIMITER, '')
}

const lengthLimit = this.valueMaxLength(formatted, decimals)
if (lengthLimit && formatted.length > lengthLimit) {
formatted = formatted.slice(0, lengthLimit)
}

return formatted
}

private toLocaleString (): string {
const hasDecimalDelimiter = this.value.indexOf(DEFAULT_DECIMAL_DELIMITER) !== -1
let [integer, decimal] = this.value.split(DEFAULT_DECIMAL_DELIMITER)

if (integer.length > 3) {
const integerReversed = integer.split('').reverse()
const lastIndex = integerReversed.length - 1
integer = integerReversed.reduce((prev, current, index) => {
prev += current
if (++index % 3 === 0 && index !== integerReversed.length) {
// Avoid thousands' delimiter for negative numbers
if (index === lastIndex && integerReversed[lastIndex] === '-') {
return prev
}
prev += this.delimiters.thousand
}
return prev
}).split('').reverse().join('')
}

let localeString = integer

if (hasDecimalDelimiter) {
localeString += this.delimiters.decimal
}

if (decimal) {
localeString += decimal
}

return localeString
}

private valueMaxLength (value: string, decimals: number) {
if (value.length === 0 || decimals === undefined) return undefined

const fpIndex = value.indexOf('.')
const fpIndex = value.indexOf(DEFAULT_DECIMAL_DELIMITER)

return fpIndex !== -1 ? fpIndex + 1 + decimals : undefined
}
Expand Down
18 changes: 16 additions & 2 deletions src/stories/SFloatInput.stories.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { number, text, withKnobs } from '@storybook/addon-knobs'
import { number, text, boolean, object, withKnobs } from '@storybook/addon-knobs'

import { SFloatInput, SRow } from '../components'

Expand All @@ -14,6 +14,8 @@ export const configurable = () => ({
<s-float-input
v-model="model"
:decimals="decimals"
:hasLocaleString="hasLocaleString"
:delimiters="delimiters"
:max="max"
/>
</s-row>`,
Expand All @@ -25,7 +27,19 @@ export const configurable = () => ({
default: number('Decimals', 18)
},
max: {
default: text('Max', '10000')
default: text('Max', '90000000')
},
hasLocaleString: {
default: boolean('Has Locale String', false)
},
delimiters: {
default: object(
'Delimiters',
{
thousand: ',',
decimal: '.'
}
)
}
}
})