Skip to content

Commit 0182a9a

Browse files
committed
fix(Field.Currency): set limit of safe big number
Fixes #3124
1 parent 2ca216a commit 0182a9a

File tree

10 files changed

+102
-89
lines changed

10 files changed

+102
-89
lines changed

packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Number/properties.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ import DataValueReadwriteProperties from '../../data-value-readwrite-properties.
1717
| `percent` | `boolean` | _(optional)_ Format a number as percentage. |
1818
| `prefix` | `string` | _(optional)_ Text added before the value input. |
1919
| `suffix` | `string` | _(optional)_ Text added after the value input. |
20-
| `minimum` | `number` | _(optional)_ Validation for inclusive minimum number value (greater than or equal). |
21-
| `maximum` | `number` | _(optional)_ Validation for inclusive maximum number value (less than or equal). |
20+
| `minimum` | `number` | _(optional)_ Validation for inclusive minimum number value (greater than or equal). Defaults to `-Number.MAX_SAFE_INTEGER`. |
21+
| `maximum` | `number` | _(optional)_ Validation for inclusive maximum number value (less than or equal). Defaults to `Number.MAX_SAFE_INTEGER`. |
2222
| `exclusiveMinimum` | `number` | _(optional)_ Validation for exclusive minimum number value (greater than). |
2323
| `exclusiveMaximum` | `number` | _(optional)_ Validation for exclusive maximum number value (less than). |
2424
| `multipleOf` | `number` | _(optional)_ Validation that requires the number to be a multiple of given value. |

packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/create-component/useDataValue/info.mdx

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -173,22 +173,16 @@ The transformers are hooks to transform the value on different stages.
173173

174174
They should return a transformed value: `(value) => value`
175175

176-
- `toInput` transforms the value before it gets returned by the hook:
176+
- `toInput` transforms the value before it gets returned as the `value`:
177177

178-
```ts
179-
const { value } = useDataValue(props)
180-
```
181-
182-
- `fromInput` transforms the value given by `handleChange` before it is used in the further process flow.
183-
184-
```ts
185-
handleChange(value)
186-
```
178+
- `fromInput` transforms the value given by `handleChange` before it is used in the further process flow. Use it to destruct the value form the original event object.
187179

188180
- `toEvent` transforms the internal value before it gets returned by even callbacks such as `onChange`, `onFocus` and `onBlur`.
189181

190182
- `fromExternal` transforms the given props `value` before any other step gets entered.
191183

184+
- `transformValue` transforms the value given by `handleChange` after `fromInput` and before `updateValue` and `toEvent`.
185+
192186
#### Additional features
193187

194188
| Property | Type | Description |

packages/dnb-eufemia/src/extensions/forms/Field/Number/Number.tsx

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,14 +104,32 @@ function NumberComponent(props: Props) {
104104
return external
105105
}, [])
106106
const fromInput = useCallback(
107-
({ value, numberValue }: { value: string; numberValue: number }) => {
108-
if (value === '') {
107+
(event: { value?: string; numberValue: number }) => {
108+
if (typeof event === 'number') {
109+
event = { numberValue: event }
110+
}
111+
112+
if (event?.value === '') {
109113
return props.emptyValue
110114
}
111-
return numberValue
115+
116+
return event?.numberValue
112117
},
113118
[props.emptyValue]
114119
)
120+
const transformValue = useCallback(
121+
(value: number, currentValue: number) => {
122+
if (
123+
value > Number.MAX_SAFE_INTEGER ||
124+
value < -Number.MAX_SAFE_INTEGER
125+
) {
126+
return currentValue
127+
}
128+
129+
return value
130+
},
131+
[]
132+
)
115133

116134
const maskProps: Partial<InputMaskedProps> = useMemo(() => {
117135
if (currency) {
@@ -155,6 +173,7 @@ function NumberComponent(props: Props) {
155173
schema,
156174
toInput,
157175
fromInput,
176+
transformValue,
158177
size:
159178
props.size !== 'small' && props.size !== 'large'
160179
? 'medium'
@@ -174,8 +193,8 @@ function NumberComponent(props: Props) {
174193
labelDescription,
175194
labelSecondary,
176195
value,
177-
minimum,
178-
maximum,
196+
minimum = -Number.MAX_SAFE_INTEGER,
197+
maximum = Number.MAX_SAFE_INTEGER,
179198
disabled,
180199
info,
181200
warning,

packages/dnb-eufemia/src/extensions/forms/Field/Number/__tests__/Number.test.tsx

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,42 @@ describe('Field.Number', () => {
2424
expect(screen.getByLabelText('Number label')).toBeInTheDocument()
2525
})
2626

27+
it('corrects minimum number', () => {
28+
render(<Field.Number value={-Number.MAX_SAFE_INTEGER} />)
29+
30+
const input = document.querySelector('input')
31+
32+
fireEvent.change(input, {
33+
target: {
34+
value: String(-Number.MAX_SAFE_INTEGER - 1),
35+
},
36+
})
37+
38+
expect(input).toHaveValue(String(-Number.MAX_SAFE_INTEGER - 1))
39+
40+
fireEvent.blur(input)
41+
42+
expect(input).toHaveValue(String(-Number.MAX_SAFE_INTEGER))
43+
})
44+
45+
it('corrects maximum number', () => {
46+
render(<Field.Number value={Number.MAX_SAFE_INTEGER} />)
47+
48+
const input = document.querySelector('input')
49+
50+
fireEvent.change(input, {
51+
target: {
52+
value: String(Number.MAX_SAFE_INTEGER + 1),
53+
},
54+
})
55+
56+
expect(input).toHaveValue(String(Number.MAX_SAFE_INTEGER + 1))
57+
58+
fireEvent.blur(input)
59+
60+
expect(input).toHaveValue(String(Number.MAX_SAFE_INTEGER))
61+
})
62+
2763
it('should support disabled prop', () => {
2864
const { rerender } = render(
2965
<Field.Number label="Disabled label" disabled />

packages/dnb-eufemia/src/extensions/forms/Field/String/String.tsx

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import FieldBlock from '../../FieldBlock'
1111
import { useDataValue } from '../../hooks'
1212
import { FieldProps, FieldHelpProps } from '../../types'
1313
import { pickSpacingProps } from '../../../../components/flex/utils'
14+
import { toCapitalized } from '../../../../shared/component-helper'
1415

1516
interface ErrorMessages {
1617
required?: string
@@ -79,12 +80,22 @@ function StringComponent(props: Props) {
7980
},
8081
[props.emptyValue]
8182
)
83+
const transformValue = useCallback(
84+
(value: string) => {
85+
if (props.capitalize) {
86+
value = toCapitalized(String(value || ''))
87+
}
88+
return value
89+
},
90+
[props.capitalize]
91+
)
8292

8393
const preparedProps: Props = {
8494
...props,
8595
errorMessages,
8696
schema,
8797
fromInput,
98+
transformValue,
8899
width: props.width ?? 'large',
89100
}
90101

@@ -102,7 +113,7 @@ function StringComponent(props: Props) {
102113
label,
103114
labelDescription,
104115
labelSecondary,
105-
value,
116+
value: currentValue,
106117
info,
107118
warning,
108119
error,
@@ -123,6 +134,11 @@ function StringComponent(props: Props) {
123134
handleChange,
124135
} = useDataValue(preparedProps)
125136

137+
let value = currentValue?.toString()
138+
if (props.capitalize) {
139+
value = toCapitalized(String(value || ''))
140+
}
141+
126142
const characterCounterElement = characterCounter
127143
? props.maxLength
128144
? `${value?.length ?? '0'}/${props.maxLength}`
@@ -147,7 +163,7 @@ function StringComponent(props: Props) {
147163
stretch: width !== undefined,
148164
inner_ref: innerRef,
149165
status: error || hasError ? 'error' : undefined,
150-
value: value?.toString() ?? '',
166+
value,
151167
}
152168

153169
return (

packages/dnb-eufemia/src/extensions/forms/Field/String/__tests__/String.test.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ describe('Field.String', () => {
8888

8989
const input = document.querySelector('input')
9090

91+
expect(input).toHaveValue('First Word')
92+
9193
await userEvent.type(input, ' second')
9294
expect(input).toHaveValue('First Word Second')
9395

packages/dnb-eufemia/src/extensions/forms/hooks/__tests__/useDataValue.test.tsx

Lines changed: 0 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -548,55 +548,6 @@ describe('useDataValue', () => {
548548
})
549549
})
550550

551-
describe('manipulate string value', () => {
552-
it('should capitalize value', () => {
553-
const onBlur = jest.fn()
554-
const onChange = jest.fn()
555-
556-
const { result } = renderHook(() =>
557-
useDataValue({
558-
value: 'foo',
559-
capitalize: true,
560-
onBlur,
561-
onChange,
562-
})
563-
)
564-
565-
const { handleBlur, handleChange } = result.current
566-
567-
act(() => {
568-
handleBlur()
569-
handleChange('bar')
570-
})
571-
572-
expect(onBlur).toHaveBeenLastCalledWith('Foo')
573-
expect(onChange).toHaveBeenLastCalledWith('Bar')
574-
})
575-
576-
it('should trim value', () => {
577-
const onBlur = jest.fn()
578-
const onChange = jest.fn()
579-
580-
const { result } = renderHook(() =>
581-
useDataValue({
582-
value: ' foo',
583-
trim: true,
584-
onBlur,
585-
onChange,
586-
})
587-
)
588-
589-
const { handleBlur } = result.current
590-
591-
act(() => {
592-
handleBlur()
593-
})
594-
595-
expect(onBlur).toHaveBeenLastCalledWith('foo')
596-
expect(onChange).toHaveBeenLastCalledWith('foo')
597-
})
598-
})
599-
600551
describe('updating internal value', () => {
601552
it('should update the internal value, but not call any event handler', () => {
602553
const onFocus = jest.fn()

packages/dnb-eufemia/src/extensions/forms/hooks/useDataValue.ts

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,7 @@ import { FormError, FieldProps, AdditionalEventArgs } from '../types'
1414
import { Context, ContextState } from '../DataContext'
1515
import FieldBlockContext from '../FieldBlock/FieldBlockContext'
1616
import IterateElementContext from '../Iterate/IterateElementContext'
17-
import {
18-
makeUniqueId,
19-
toCapitalized,
20-
} from '../../../shared/component-helper'
17+
import { makeUniqueId } from '../../../shared/component-helper'
2118
import useMountEffect from './useMountEffect'
2219
import useUpdateEffect from './useUpdateEffect'
2320
import useProcessManager from './useProcessManager'
@@ -59,6 +56,7 @@ export default function useDataValue<
5956
toInput = (value: Value) => value,
6057
fromInput = (value: Value) => value,
6158
toEvent = (value: Value) => value,
59+
transformValue = (value: Value) => value,
6260
fromExternal = (value: Value) => value,
6361
validateRequired = (value: Value, { emptyValue, required }) => {
6462
const res =
@@ -85,6 +83,7 @@ export default function useDataValue<
8583
fromInput,
8684
toEvent,
8785
fromExternal,
86+
transformValue,
8887
validateRequired,
8988
})
9089

@@ -130,14 +129,8 @@ export default function useDataValue<
130129

131130
const externalValue = useMemo(() => {
132131
if (props.value !== undefined) {
133-
let value = transformers.current.fromExternal(props.value)
134-
135-
if (props.capitalize) {
136-
value = toCapitalized(String(value || '')) as Value
137-
}
138-
139132
// Value-prop sent directly to the field has highest priority, overriding any surrounding source
140-
return value
133+
return transformers.current.fromExternal(props.value)
141134
}
142135

143136
if (inIterate && itemPath) {
@@ -484,17 +477,19 @@ export default function useDataValue<
484477
argFromInput: Value,
485478
additionalArgs: AdditionalEventArgs = undefined
486479
) => {
480+
const currentValue = valueRef.current
487481
let newValue = transformers.current.fromInput(argFromInput)
488482

489-
if (newValue === valueRef.current) {
483+
if (newValue === currentValue) {
490484
// Avoid triggering a change if the value was not actually changed. This may be caused by rendering components
491485
// calling onChange even if the actual value did not change.
492486
return
493487
}
494488

495-
if (props.capitalize) {
496-
newValue = toCapitalized(String(newValue || '')) as Value
497-
}
489+
newValue = transformers.current.transformValue(
490+
newValue,
491+
currentValue
492+
)
498493

499494
updateValue(newValue)
500495

@@ -516,7 +511,6 @@ export default function useDataValue<
516511
}
517512
},
518513
[
519-
props.capitalize,
520514
updateValue,
521515
onChange,
522516
itemPath,

packages/dnb-eufemia/src/extensions/forms/types.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -204,12 +204,13 @@ export interface FieldProps<
204204
continuousValidation?: boolean
205205
errorMessages?: ErrorMessages
206206
// Derivatives
207-
toInput?: (external: Value | undefined) => any
208-
fromInput?: (...args: any[]) => Value | undefined
209-
toEvent?: (internal: Value | undefined) => any
210-
fromExternal?: (...args: any[]) => Value | undefined
207+
toInput?: (external: Value | unknown) => Value | unknown
208+
fromInput?: (external: Value | unknown) => Value
209+
toEvent?: (internal: Value) => Value
210+
fromExternal?: (external: Value) => Value
211+
transformValue?: (value: Value, currentValue?: Value) => Value
211212
validateRequired?: (
212-
internal: Value | undefined,
213+
internal: Value,
213214
{
214215
emptyValue,
215216
required,

0 commit comments

Comments
 (0)