diff --git a/semcore/input-number/src/InputNumber.tsx b/semcore/input-number/src/InputNumber.tsx index 9a1cc7e7f5..31ae30179e 100644 --- a/semcore/input-number/src/InputNumber.tsx +++ b/semcore/input-number/src/InputNumber.tsx @@ -1,5 +1,6 @@ import type { Intergalactic } from '@semcore/core'; import { Component, createComponent, Root, sstyled } from '@semcore/core'; +import propsObserver from '@semcore/core/lib/decorators/propsObserver'; import { callAllEventHandlers } from '@semcore/core/lib/utils/assignProps'; import type { WithI18nEnhanceProps } from '@semcore/core/lib/utils/enhances/i18nEnhance'; import i18nEnhance from '@semcore/core/lib/utils/enhances/i18nEnhance'; @@ -91,14 +92,15 @@ class InputNumber extends Component< } } +@propsObserver(['value']) class Value extends Component< - Intergalactic.InternalTypings.InferChildComponentProps, - [], - NSInputNumber.Value.Handlers, - {}, - NSInputNumber.Value.State, - NSInputNumber.Value.DefaultProps -> { + Intergalactic.InternalTypings.InferChildComponentProps, + [], + NSInputNumber.Value.Handlers, + {}, + NSInputNumber.Value.State, + NSInputNumber.Value.DefaultProps + > { static style = style; static defaultProps = { defaultValue: '', @@ -106,6 +108,8 @@ class Value extends Component< step: 1, } as const; + valueType: 'string' | 'number' = typeof this.props.value === 'string' ? 'string' : 'number'; + state: NSInputNumber.Value.State = { displayValue: '', }; @@ -148,6 +152,18 @@ class Value extends Component< return numberFormatter.format(1111).replace(/\d/g, ''); } + onPropsChange(changedProps: { value?: string | number | null }) { + const { value } = changedProps; + + if (value !== undefined) { + if (typeof value === 'string') { + this.valueType = 'string'; + } else { + this.valueType = 'number'; + } + } + } + getFormattedValue = (value: string) => { return value .replace(new RegExp(`[${this.separatorThousands}]`, 'g'), '') @@ -155,8 +171,8 @@ class Value extends Component< }; valueParser = ( - value: typeof this.asProps['value'], - prevValue: typeof this.asProps['value'], + value: string | number, + prevValue: string, prevDisplayValue: NSInputNumber.Value.State['displayValue'], ) => { const { numberFormatter } = this.props; @@ -221,7 +237,7 @@ class Value extends Component< if (Number.isNaN(value) || Number.isNaN(Number.parseFloat(parsedValue))) { event.currentTarget.value = ''; - this.handlers.value('', event); + this.handlers.value(this.processedValue(''), event); } else { let numberValue = parseValueWithMinMax(Number.parseFloat(parsedValue), min, max); const rounded = this.round(numberValue % step); @@ -233,7 +249,7 @@ class Value extends Component< } } - this.handlers.value(this.getDisplayValue(numberValue), event); + this.handlers.value(this.processedValue(this.getDisplayValue(numberValue)), event); } }; @@ -308,7 +324,7 @@ class Value extends Component< this.setState({ displayValue: numberFormatter.format(value as `${number}`) + this.separatorDecimal }); return false; } else { - this.handlers.value(value.slice(0, -1), event); + this.handlers.value(this.processedValue(value.slice(0, -1)), event); return false; } } @@ -316,7 +332,7 @@ class Value extends Component< const digits = /^[0-9.-]+$/.test(value); if (digits || value === '') { - this.handlers.value(this.limitDecimals(value), event); + this.handlers.value(this.processedValue(this.limitDecimals(value)), event); } }; @@ -492,7 +508,7 @@ class Value extends Component< const nextValue = Math.min(numberValue + step, max); - this.handlers.value(this.getDisplayValue(nextValue), event); + this.handlers.value(this.processedValue(this.getDisplayValue(nextValue)), event); }; stepDown = (event: StepEvent) => { @@ -510,9 +526,21 @@ class Value extends Component< const nextValue = Math.max(numberValue - step, min); - this.handlers.value(this.getDisplayValue(nextValue), event); + this.handlers.value(this.processedValue(this.getDisplayValue(nextValue)), event); }; + processedValue(value: string): NSInputNumber.Value | NSInputNumber.ValueNumber { + if (this.valueType === 'string') { + return value; + } + + if (value === '') { + return null; + } + + return Number(value); + } + render() { const SValue = Root; const { styles, min, max, step, forwardRef, inputRef } = this.asProps; diff --git a/semcore/input-number/src/InputNumber.type.ts b/semcore/input-number/src/InputNumber.type.ts index c814ae5023..c4892b7e22 100644 --- a/semcore/input-number/src/InputNumber.type.ts +++ b/semcore/input-number/src/InputNumber.type.ts @@ -6,6 +6,8 @@ import type { LocalizedMessages } from './translations/__intergalactic-dynamic-l declare namespace NSInputNumber { type Value = string; + type ValueNumber = number | null; + type CalculatedValue = V extends Value ? Value : ValueNumber; type Size = 'm' | 'l'; type Props = NSInput.Props & { /** Input size @@ -29,7 +31,7 @@ declare namespace NSInputNumber { }; namespace Value { - type Props = NSInput.Value.Props & { + type Props = Intergalactic.InternalTypings.EfficientOmit & { /** Minimum value * @default Number.MIN_SAFE_INTEGER */ @@ -43,9 +45,9 @@ declare namespace NSInputNumber { */ step?: number; /** Numeric value */ - value?: NSInputNumber.Value; + value?: CalculatedValue; /** Called when the input value changes, it returns its current value in numeric format */ - onChange?: (value: NSInputNumber.Value, event?: React.SyntheticEvent) => void; + onChange?: (value: CalculatedValue, event?: React.SyntheticEvent) => void; }; type DefaultProps = { defaultValue: ''; @@ -57,11 +59,20 @@ declare namespace NSInputNumber { type Handlers = { value: [ null, - (value: Props['value'], event: React.SyntheticEvent | WheelEvent) => void, + (value: Value | ValueNumber | undefined, event: React.SyntheticEvent | WheelEvent) => void, ]; }; - type Component = Intergalactic.Component<'input', Props>; + type Component = (< + V extends Value | ValueNumber, + Tag extends Intergalactic.Tag = 'input', + >( + props: Intergalactic.InternalTypings.EfficientOmit< + Intergalactic.InternalTypings.ComponentProps>, + 'tag' | 'children' + > + ) => Intergalactic.InternalTypings.ComponentRenderingResults) & + Intergalactic.InternalTypings.ComponentAdditive<'input', 'input', Props>; } namespace Controls { @@ -93,7 +104,7 @@ export type InputNumberSize = NSInputNumber.Size; /** @deprecated It will be removed in v18. */ export type InputNumberProps = NSInputNumber.Props; /** @deprecated It will be removed in v18. */ -export type InputNumberValueProps = NSInputNumber.Value.Props; +export type InputNumberValueProps = NSInputNumber.Value.Props; /** @deprecated It will be removed in v18. */ export type InputNumberControlsProps = NSInputNumber.Controls.Props; /** @deprecated It will be removed in v18. */ diff --git a/stories/components/input-number/tests/examples/basic_example.tsx b/stories/components/input-number/tests/examples/basic_example.tsx index e55eeb4fe8..75763075cd 100644 --- a/stories/components/input-number/tests/examples/basic_example.tsx +++ b/stories/components/input-number/tests/examples/basic_example.tsx @@ -5,7 +5,7 @@ import React from 'react'; type BaseExampleType = & NSInputNumber.Props - & NSInputNumber.Value.Props + & NSInputNumber.Value.Props & NSInputNumber.Controls.Props & { disabledValue?: boolean; logChanges?: boolean }; @@ -17,9 +17,9 @@ const getStepPrecision = (step?: number) => { const Demo = (props: BaseExampleType) => { const stepPrecision = getStepPrecision(props.step); - const [lastValue, setLastValue] = React.useState(null); + const [lastValue, setLastValue] = React.useState(null); - const handleChange = (value: string, _event?: React.SyntheticEvent) => { + const handleChange = (value: NSInputNumber.ValueNumber, _event?: React.SyntheticEvent) => { setLastValue(value); if (props.logChanges) { @@ -38,7 +38,7 @@ const Demo = (props: BaseExampleType) => { disabled={props.disabled} locale={props.locale} > - disabled={props.disabledValue} max={props.max} min={props.min}