Skip to content

Commit

Permalink
🐛 fix(number-input): jumping using TAB is inconsistent (#1379)
Browse files Browse the repository at this point in the history
* Create PR for #1369

* fix numer input issues

* fix numer input issues

* add changesets

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Gery Hirschfeld <[email protected]>
  • Loading branch information
github-actions[bot] and hirsch88 authored Apr 12, 2024
1 parent c3b12e7 commit f92a1eb
Show file tree
Hide file tree
Showing 7 changed files with 195 additions and 16 deletions.
5 changes: 5 additions & 0 deletions .changeset/cool-pots-pump.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@baloise/ds-core': minor
---

**number-input**: supports select-all, copy and paste
5 changes: 5 additions & 0 deletions .changeset/dirty-owls-share.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@baloise/ds-core': patch
---

**number-input**: tab navigation to be consistent
5 changes: 5 additions & 0 deletions .changeset/real-buses-approve.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@baloise/ds-core': patch
---

**number-input**: supports autofill format
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export class NumberInput
{
private inputId = `bal-number-input-${numberInputIds++}`
private inheritedAttributes: { [k: string]: any } = {}
private selectTimeout?: NodeJS.Timeout

lastValue = ''
nativeInput?: HTMLInputElement
Expand Down Expand Up @@ -371,11 +372,12 @@ export class NumberInput

private onFocus = (ev: FocusEvent) => {
inputHandleFocus(this, ev)

//
// restore the input with the last user value without the formatting
if (this.nativeInput) {
this.nativeInputValue = mapDecimalSeparator(this.lastValue || '')
clearTimeout(this.selectTimeout)
this.selectTimeout = setTimeout(() => this.nativeInput.select())
}
}

Expand All @@ -387,6 +389,7 @@ export class NumberInput
if (this.nativeInput) {
this.lastValue = toFixedNumber(this.lastValueGetter, this.decimal)
this.nativeInputValue = toUserFormattedNumber(this.lastValueGetter, this.decimal, this.suffix)
this.nativeInput.value = this.nativeInputValue
}

this.inputValue = toNumber(this.lastValueGetter, this.decimal)
Expand All @@ -403,6 +406,8 @@ export class NumberInput
input &&
!validateKeyDown({
key: ev.key,
ctrlKey: ev.ctrlKey,
metaKey: ev.metaKey,
decimal: this.decimal,
newValue,
oldValue,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ describe('bal-number-input', () => {
expect(isNumber('0.1')).toBeTruthy()
expect(isNumber('42')).toBeTruthy()
expect(isNumber('-42')).toBeTruthy()
expect(isNumber("40'000")).toBeTruthy()
expect(isNumber('40`000')).toBeTruthy()
})
})

Expand All @@ -38,6 +40,8 @@ describe('bal-number-input', () => {
expect(isNotNumber('0.1')).toBeFalsy()
expect(isNotNumber('42')).toBeFalsy()
expect(isNotNumber('-42')).toBeFalsy()
expect(isNumber("40'000")).toBeTruthy()
expect(isNumber('40`000')).toBeTruthy()
})
})

Expand Down Expand Up @@ -93,6 +97,16 @@ describe('bal-number-input', () => {
const result = toNumber('.')
expect(result).toBeUndefined()
})

it('should return number and ignore separator', () => {
const result = toNumber("42'000")
expect(result).toBe(42000)
})

it('should return number and ignore autofill separator', () => {
const result = toNumber('42`000')
expect(result).toBe(42000)
})
})

describe('toFixedNumber', () => {
Expand Down Expand Up @@ -189,6 +203,8 @@ describe('bal-number-input', () => {
expect(
validateKeyDown({
key: '4',
ctrlKey: false,
metaKey: false,
newValue: '4',
oldValue: '',
selectionStart: 0,
Expand All @@ -200,6 +216,8 @@ describe('bal-number-input', () => {
expect(
validateKeyDown({
key: '2',
ctrlKey: false,
metaKey: false,
newValue: '42',
oldValue: '4',
selectionStart: 1,
Expand All @@ -214,6 +232,8 @@ describe('bal-number-input', () => {
expect(
validateKeyDown({
key: '-',
ctrlKey: false,
metaKey: false,
newValue: '-',
oldValue: '',
selectionStart: 0,
Expand All @@ -225,6 +245,8 @@ describe('bal-number-input', () => {
expect(
validateKeyDown({
key: '2',
ctrlKey: false,
metaKey: false,
newValue: '-2',
oldValue: '-',
selectionStart: 1,
Expand All @@ -236,6 +258,8 @@ describe('bal-number-input', () => {
expect(
validateKeyDown({
key: '.',
ctrlKey: false,
metaKey: false,
newValue: '2.',
oldValue: '2',
selectionStart: 2,
Expand All @@ -249,6 +273,8 @@ describe('bal-number-input', () => {
expect(
validateKeyDown({
key: '4',
ctrlKey: false,
metaKey: false,
newValue: '4',
oldValue: '',
selectionStart: 0,
Expand All @@ -260,6 +286,8 @@ describe('bal-number-input', () => {
expect(
validateKeyDown({
key: '.',
ctrlKey: false,
metaKey: false,
newValue: '4.',
oldValue: '4',
selectionStart: 1,
Expand All @@ -271,6 +299,8 @@ describe('bal-number-input', () => {
expect(
validateKeyDown({
key: '2',
ctrlKey: false,
metaKey: false,
newValue: '4.2',
oldValue: '4.',
selectionStart: 2,
Expand All @@ -284,6 +314,8 @@ describe('bal-number-input', () => {
expect(
validateKeyDown({
key: '-',
ctrlKey: false,
metaKey: false,
newValue: '1-',
oldValue: '1',
selectionStart: 1,
Expand All @@ -295,6 +327,8 @@ describe('bal-number-input', () => {
expect(
validateKeyDown({
key: '-',
ctrlKey: false,
metaKey: false,
newValue: '--',
oldValue: '-',
selectionStart: 1,
Expand All @@ -308,6 +342,8 @@ describe('bal-number-input', () => {
expect(
validateKeyDown({
key: '.',
ctrlKey: false,
metaKey: false,
newValue: '.2.',
oldValue: '.2',
selectionStart: 3,
Expand All @@ -319,6 +355,8 @@ describe('bal-number-input', () => {
expect(
validateKeyDown({
key: '.',
ctrlKey: false,
metaKey: false,
newValue: '..',
oldValue: '.',
selectionStart: 2,
Expand All @@ -332,6 +370,8 @@ describe('bal-number-input', () => {
expect(
validateKeyDown({
key: 'a',
ctrlKey: false,
metaKey: false,
newValue: 'a',
oldValue: '',
selectionStart: 1,
Expand All @@ -343,6 +383,8 @@ describe('bal-number-input', () => {
expect(
validateKeyDown({
key: '!',
ctrlKey: false,
metaKey: false,
newValue: '!',
oldValue: '',
selectionStart: 1,
Expand All @@ -356,6 +398,8 @@ describe('bal-number-input', () => {
expect(
validateKeyDown({
key: '9',
ctrlKey: false,
metaKey: false,
newValue: '1.429',
oldValue: '1.42',
selectionStart: 5,
Expand All @@ -367,6 +411,8 @@ describe('bal-number-input', () => {
expect(
validateKeyDown({
key: 'ArrowLeft',
ctrlKey: false,
metaKey: false,
newValue: '1.42',
oldValue: '1.42',
selectionStart: 5,
Expand All @@ -380,6 +426,8 @@ describe('bal-number-input', () => {
expect(
validateKeyDown({
key: 'ArrowLeft',
ctrlKey: false,
metaKey: false,
newValue: '1.42',
oldValue: '1.42',
selectionStart: 3,
Expand All @@ -391,6 +439,8 @@ describe('bal-number-input', () => {
expect(
validateKeyDown({
key: 'Escape',
ctrlKey: false,
metaKey: false,
newValue: '1.4',
oldValue: '1.42',
selectionStart: 4,
Expand All @@ -402,6 +452,49 @@ describe('bal-number-input', () => {
expect(
validateKeyDown({
key: 'Delete',
ctrlKey: false,
metaKey: false,
newValue: '1.2',
oldValue: '1.42',
selectionStart: 2,
selectionEnd: 2,
decimal: 2,
}),
).toBeTruthy()
})

test('should allow select all, copy and paste', () => {
expect(
validateKeyDown({
key: 'a',
ctrlKey: false,
metaKey: true,
newValue: '1.42',
oldValue: '1.42',
selectionStart: 3,
selectionEnd: 3,
decimal: 2,
}),
).toBeTruthy()

expect(
validateKeyDown({
key: 'c',
ctrlKey: true,
metaKey: false,
newValue: '1.4',
oldValue: '1.42',
selectionStart: 4,
selectionEnd: 4,
decimal: 2,
}),
).toBeTruthy()

expect(
validateKeyDown({
key: 'v',
ctrlKey: false,
metaKey: true,
newValue: '1.2',
oldValue: '1.42',
selectionStart: 2,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import isNil from 'lodash.isnil'
import { ACTION_KEYS, NUMBER_KEYS } from '../../utils/constants/keys.constant'
import { formatLocaleNumber, getDecimalSeparator, getNegativeSymbol } from '../../utils/number'
import { formatLocaleNumber, getDecimalSeparator, getNegativeSymbol, getThousandSeparator } from '../../utils/number'
import isNaN from 'lodash.isnan'

export function isNumber(value: any): boolean {
Expand All @@ -13,31 +13,41 @@ export function isNotNumber(value: any): boolean {
}

export function toNumber(value: any, decimalPoints = 0): number | undefined {
let val = value
if (
value === '' ||
value === undefined ||
value === null ||
isNaN(value) ||
value === getNegativeSymbol() ||
value === getDecimalSeparator() ||
!isNumber(value)
val === '' ||
val === undefined ||
val === null ||
isNaN(val) ||
val === getNegativeSymbol() ||
val === getDecimalSeparator() ||
!isNumber(val)
) {
return undefined
}

return decimalPoints === 0 ? parseInt(value, 10) : parseFloat(value)
if (typeof val === 'string') {
val = val.split(getThousandSeparator()).join('').split('`').join('').split("'").join('')
}

return decimalPoints === 0 ? parseInt(val, 10) : parseFloat(val)
}

export function toFixedNumber(value: string, decimalPoints = 0): string {
if (isNil(value)) {
let val = value
if (isNil(val)) {
return ''
}

if (value.charAt(0) === getDecimalSeparator()) {
value = `0${value}`
if (typeof val === 'string') {
val = val.split(getThousandSeparator()).join('').split('`').join('').split("'").join('')
}

const num = decimalPoints === 0 ? parseInt(value, 10) : parseFloat(value.replace(getDecimalSeparator(), '.'))
if (val.charAt(0) === getDecimalSeparator()) {
val = `0${val}`
}

const num = decimalPoints === 0 ? parseInt(val, 10) : parseFloat(val.replace(getDecimalSeparator(), '.'))
return isNaN(num) ? '' : num.toFixed(decimalPoints)
}

Expand All @@ -62,6 +72,8 @@ export function toUserFormattedNumber(value: string, decimalPoints = 0, suffix =
export type ValidateKeyDownOptions = {
decimal: number
key: string
ctrlKey: boolean
metaKey: boolean
newValue: string
oldValue: string
selectionStart: number | null
Expand All @@ -72,11 +84,19 @@ export const countDecimalSeparators = (value: string) => (value.split(getDecimal

export function validateKeyDown({
key,
ctrlKey,
metaKey,
selectionStart,
selectionEnd,
newValue,
decimal,
}: ValidateKeyDownOptions): boolean {
//
// allow select all, copy and paste
if (['a', 'c', 'v'].includes(key) && (ctrlKey || metaKey)) {
return true
}

//
// only allow negative symbols at the start of the input
if (key === getNegativeSymbol() && selectionStart && selectionStart > 0 && selectionEnd && selectionEnd > 0) {
Expand Down
Loading

0 comments on commit f92a1eb

Please sign in to comment.