From 9600ece33202ead5b56b4ffa1a81ed999ecdb4e9 Mon Sep 17 00:00:00 2001 From: "ilia.brauer" Date: Wed, 29 Jul 2026 16:44:59 +0200 Subject: [PATCH 1/4] [input-number] init type casting --- semcore/input-number/src/InputNumber.tsx | 68 ++++++++++++++------ semcore/input-number/src/InputNumber.type.ts | 20 +++--- 2 files changed, 59 insertions(+), 29 deletions(-) diff --git a/semcore/input-number/src/InputNumber.tsx b/semcore/input-number/src/InputNumber.tsx index 9a1cc7e7f5..a311b8923b 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'; @@ -35,8 +36,8 @@ export function parseValueWithMinMax( return Math.max(min, Math.min(max, value)); } -class InputNumber extends Component< - Intergalactic.InternalTypings.InferComponentProps, +class InputNumber extends Component< + Intergalactic.InternalTypings.InferComponentProps>, typeof InputNumber.enhance, {}, WithI18nEnhanceProps, @@ -91,14 +92,15 @@ class InputNumber extends Component< } } -class Value extends Component< - Intergalactic.InternalTypings.InferChildComponentProps, - [], - NSInputNumber.Value.Handlers, - {}, - NSInputNumber.Value.State, - NSInputNumber.Value.DefaultProps -> { +@propsObserver(['value']) +class Value extends Component< + Intergalactic.InternalTypings.InferChildComponentProps, typeof InputNumber, 'Value'>, + [], + 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: '', }; @@ -114,7 +118,7 @@ class Value extends Component< cursorPosition: number | null = -1; - uncontrolledProps(): NSInputNumber.Value.Handlers { + uncontrolledProps(): NSInputNumber.Value.Handlers { return { value: [ null, @@ -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.CalculatedValue { + if (this.valueType === 'string') { + return value as NSInputNumber.CalculatedValue; + } + + if (value === '') { + return null as NSInputNumber.CalculatedValue; + } + + return Number(value) as NSInputNumber.CalculatedValue; + } + render() { const SValue = Root; const { styles, min, max, step, forwardRef, inputRef } = this.asProps; @@ -578,7 +606,7 @@ Controls.style = style; * {@link https://developer.semrush.com/intergalactic/components/input-number/input-number-api/|API} | {@link https://developer.semrush.com/intergalactic/components/input-number/input-number-code/|Examples} */ export default createComponent< - NSInputNumber.Component, + NSInputNumber.Component, typeof InputNumber >(InputNumber, { Value, diff --git a/semcore/input-number/src/InputNumber.type.ts b/semcore/input-number/src/InputNumber.type.ts index c814ae5023..fe3ed9b4ad 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: ''; @@ -54,14 +56,14 @@ declare namespace NSInputNumber { type State = { displayValue: NSInputNumber.Value; }; - type Handlers = { + type Handlers = { value: [ null, - (value: Props['value'], event: React.SyntheticEvent | WheelEvent) => void, + (value: Props['value'], event: React.SyntheticEvent | WheelEvent) => void, ]; }; - type Component = Intergalactic.Component<'input', Props>; + type Component = Intergalactic.Component<'input', Props>; } namespace Controls { @@ -79,8 +81,8 @@ declare namespace NSInputNumber { type Component = Intergalactic.Component<'div', NSInput.Addon.Props>; } - type Component = Intergalactic.Component<'div', Props, Ctx> & { - Value: Value.Component; + type Component = Intergalactic.Component<'div', Props, Ctx> & { + Value: Value.Component; Controls: Controls.Component; Addon: Addon.Component; }; @@ -93,7 +95,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. */ From 12d1dc4805c9048b114b61f5736317fd1ce3b1e0 Mon Sep 17 00:00:00 2001 From: "ilia.brauer" Date: Wed, 29 Jul 2026 17:11:03 +0200 Subject: [PATCH 2/4] [input-number] init type casting --- semcore/input-number/src/InputNumber.tsx | 22 +++++++++---------- semcore/input-number/src/InputNumber.type.ts | 19 +++++++++++----- .../tests/examples/basic_example.tsx | 8 +++---- 3 files changed, 29 insertions(+), 20 deletions(-) diff --git a/semcore/input-number/src/InputNumber.tsx b/semcore/input-number/src/InputNumber.tsx index a311b8923b..31ae30179e 100644 --- a/semcore/input-number/src/InputNumber.tsx +++ b/semcore/input-number/src/InputNumber.tsx @@ -36,8 +36,8 @@ export function parseValueWithMinMax( return Math.max(min, Math.min(max, value)); } -class InputNumber extends Component< - Intergalactic.InternalTypings.InferComponentProps>, +class InputNumber extends Component< + Intergalactic.InternalTypings.InferComponentProps, typeof InputNumber.enhance, {}, WithI18nEnhanceProps, @@ -93,10 +93,10 @@ class InputNumber ext } @propsObserver(['value']) -class Value extends Component< - Intergalactic.InternalTypings.InferChildComponentProps, typeof InputNumber, 'Value'>, +class Value extends Component< + Intergalactic.InternalTypings.InferChildComponentProps, [], - NSInputNumber.Value.Handlers, + NSInputNumber.Value.Handlers, {}, NSInputNumber.Value.State, NSInputNumber.Value.DefaultProps @@ -118,7 +118,7 @@ class Value extends C cursorPosition: number | null = -1; - uncontrolledProps(): NSInputNumber.Value.Handlers { + uncontrolledProps(): NSInputNumber.Value.Handlers { return { value: [ null, @@ -529,16 +529,16 @@ class Value extends C this.handlers.value(this.processedValue(this.getDisplayValue(nextValue)), event); }; - processedValue(value: string): NSInputNumber.CalculatedValue { + processedValue(value: string): NSInputNumber.Value | NSInputNumber.ValueNumber { if (this.valueType === 'string') { - return value as NSInputNumber.CalculatedValue; + return value; } if (value === '') { - return null as NSInputNumber.CalculatedValue; + return null; } - return Number(value) as NSInputNumber.CalculatedValue; + return Number(value); } render() { @@ -606,7 +606,7 @@ Controls.style = style; * {@link https://developer.semrush.com/intergalactic/components/input-number/input-number-api/|API} | {@link https://developer.semrush.com/intergalactic/components/input-number/input-number-code/|Examples} */ export default createComponent< - NSInputNumber.Component, + NSInputNumber.Component, typeof InputNumber >(InputNumber, { Value, diff --git a/semcore/input-number/src/InputNumber.type.ts b/semcore/input-number/src/InputNumber.type.ts index fe3ed9b4ad..c4892b7e22 100644 --- a/semcore/input-number/src/InputNumber.type.ts +++ b/semcore/input-number/src/InputNumber.type.ts @@ -56,14 +56,23 @@ declare namespace NSInputNumber { type State = { displayValue: NSInputNumber.Value; }; - type Handlers = { + 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 { @@ -81,8 +90,8 @@ declare namespace NSInputNumber { type Component = Intergalactic.Component<'div', NSInput.Addon.Props>; } - type Component = Intergalactic.Component<'div', Props, Ctx> & { - Value: Value.Component; + type Component = Intergalactic.Component<'div', Props, Ctx> & { + Value: Value.Component; Controls: Controls.Component; Addon: Addon.Component; }; 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} From 57332051d13b86d2232a882a98cdf2797593354b Mon Sep 17 00:00:00 2001 From: "ilia.brauer" Date: Mon, 3 Aug 2026 09:36:58 +0200 Subject: [PATCH 3/4] [inline-input] fixed types --- semcore/inline-input/src/InlineInput.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/semcore/inline-input/src/InlineInput.tsx b/semcore/inline-input/src/InlineInput.tsx index 4c02f10af5..1f25fda0ae 100644 --- a/semcore/inline-input/src/InlineInput.tsx +++ b/semcore/inline-input/src/InlineInput.tsx @@ -70,7 +70,7 @@ type ConfirmControlAsProps = ControlAsProps & { type CancelControlAsProps = ControlAsProps & { onCancel?: OnCancel; }; -type NumberValueAsProps = NSInputNumber.Value.Props & { +type NumberValueAsProps = NSInputNumber.Value.Props & { increment?: (event: WheelEvent) => void; decrement?: (event: WheelEvent) => void; }; From 1b3e724c767978e62df6cdaeff2ca304957dcc11 Mon Sep 17 00:00:00 2001 From: "ilia.brauer" Date: Mon, 3 Aug 2026 15:36:20 +0200 Subject: [PATCH 4/4] [input-number] init type casting --- semcore/input-number/src/InputNumber.type.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/semcore/input-number/src/InputNumber.type.ts b/semcore/input-number/src/InputNumber.type.ts index 1611cdd88a..d64a63c175 100644 --- a/semcore/input-number/src/InputNumber.type.ts +++ b/semcore/input-number/src/InputNumber.type.ts @@ -64,7 +64,7 @@ declare namespace NSInputNumber { }; type Component = (< - V extends Value | ValueNumber, + V extends Value | ValueNumber = Value, Tag extends Intergalactic.Tag = 'input', >( props: Intergalactic.InternalTypings.EfficientOmit<