Handle input number formatting better

Now it doesn't interfer with the user writing until the user leaves the element

Resolves #633
Resolves #632
This commit is contained in:
Donkie
2025-03-18 21:27:00 +01:00
parent 3d1bb70913
commit aa839b08e5
5 changed files with 39 additions and 23 deletions

View File

@@ -8,7 +8,7 @@ import { useEffect, useState } from "react";
import { ExtraFieldFormItem, ParsedExtras, StringifiedExtras } from "../../components/extraFields"; import { ExtraFieldFormItem, ParsedExtras, StringifiedExtras } from "../../components/extraFields";
import { FilamentImportModal } from "../../components/filamentImportModal"; import { FilamentImportModal } from "../../components/filamentImportModal";
import { MultiColorPicker } from "../../components/multiColorPicker"; import { MultiColorPicker } from "../../components/multiColorPicker";
import { numberFormatter, numberParser, numberParserAllowEmpty } from "../../utils/parsing"; import { formatNumberOnUserInput, numberParser, numberParserAllowEmpty } from "../../utils/parsing";
import { ExternalFilament } from "../../utils/queryExternalDB"; import { ExternalFilament } from "../../utils/queryExternalDB";
import { EntityType, useGetFields } from "../../utils/queryFields"; import { EntityType, useGetFields } from "../../utils/queryFields";
import { getCurrencySymbol, useCurrency } from "../../utils/settings"; import { getCurrencySymbol, useCurrency } from "../../utils/settings";
@@ -250,7 +250,7 @@ export const FilamentCreate: React.FC<IResourceComponentsProps & CreateOrClonePr
<InputNumber <InputNumber
addonAfter={getCurrencySymbol(undefined, currency)} addonAfter={getCurrencySymbol(undefined, currency)}
precision={2} precision={2}
formatter={numberFormatter} formatter={formatNumberOnUserInput}
parser={numberParserAllowEmpty} parser={numberParserAllowEmpty}
/> />
</Form.Item> </Form.Item>
@@ -266,7 +266,7 @@ export const FilamentCreate: React.FC<IResourceComponentsProps & CreateOrClonePr
}, },
]} ]}
> >
<InputNumber addonAfter="g/cm³" precision={2} formatter={numberFormatter} parser={numberParser} /> <InputNumber addonAfter="g/cm³" precision={2} formatter={formatNumberOnUserInput} parser={numberParser} />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("filament.fields.diameter")} label={t("filament.fields.diameter")}
@@ -280,7 +280,7 @@ export const FilamentCreate: React.FC<IResourceComponentsProps & CreateOrClonePr
}, },
]} ]}
> >
<InputNumber addonAfter="mm" precision={2} formatter={numberFormatter} parser={numberParser} /> <InputNumber addonAfter="mm" precision={2} formatter={formatNumberOnUserInput} parser={numberParser} />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("filament.fields.weight")} label={t("filament.fields.weight")}

View File

@@ -6,7 +6,7 @@ import dayjs from "dayjs";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { ExtraFieldFormItem, ParsedExtras, StringifiedExtras } from "../../components/extraFields"; import { ExtraFieldFormItem, ParsedExtras, StringifiedExtras } from "../../components/extraFields";
import { MultiColorPicker } from "../../components/multiColorPicker"; import { MultiColorPicker } from "../../components/multiColorPicker";
import { numberFormatter, numberParser, numberParserAllowEmpty } from "../../utils/parsing"; import { formatNumberOnUserInput, numberParser, numberParserAllowEmpty } from "../../utils/parsing";
import { EntityType, useGetFields } from "../../utils/queryFields"; import { EntityType, useGetFields } from "../../utils/queryFields";
import { getCurrencySymbol, useCurrency } from "../../utils/settings"; import { getCurrencySymbol, useCurrency } from "../../utils/settings";
import { IVendor } from "../vendors/model"; import { IVendor } from "../vendors/model";
@@ -64,7 +64,7 @@ export const FilamentEdit: React.FC<IResourceComponentsProps> = () => {
formProps.onFinish = (allValues: IFilamentParsedExtras) => { formProps.onFinish = (allValues: IFilamentParsedExtras) => {
if (allValues !== undefined && allValues !== null) { if (allValues !== undefined && allValues !== null) {
if (colorType == "single") { if (colorType == "single") {
allValues.multi_color_hexes = ''; allValues.multi_color_hexes = "";
} }
// Lot of stupidity here to make types work // Lot of stupidity here to make types work
const stringifiedAllValues = StringifiedExtras<IFilamentParsedExtras>(allValues); const stringifiedAllValues = StringifiedExtras<IFilamentParsedExtras>(allValues);
@@ -228,7 +228,7 @@ export const FilamentEdit: React.FC<IResourceComponentsProps> = () => {
<InputNumber <InputNumber
addonAfter={getCurrencySymbol(undefined, currency)} addonAfter={getCurrencySymbol(undefined, currency)}
precision={2} precision={2}
formatter={numberFormatter} formatter={formatNumberOnUserInput}
parser={numberParserAllowEmpty} parser={numberParserAllowEmpty}
/> />
</Form.Item> </Form.Item>
@@ -244,7 +244,7 @@ export const FilamentEdit: React.FC<IResourceComponentsProps> = () => {
}, },
]} ]}
> >
<InputNumber addonAfter="g/cm³" precision={2} formatter={numberFormatter} parser={numberParser} /> <InputNumber addonAfter="g/cm³" precision={2} formatter={formatNumberOnUserInput} parser={numberParser} />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("filament.fields.diameter")} label={t("filament.fields.diameter")}
@@ -258,7 +258,7 @@ export const FilamentEdit: React.FC<IResourceComponentsProps> = () => {
}, },
]} ]}
> >
<InputNumber addonAfter="mm" precision={2} formatter={numberFormatter} parser={numberParser} /> <InputNumber addonAfter="mm" precision={2} formatter={formatNumberOnUserInput} parser={numberParser} />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("filament.fields.weight")} label={t("filament.fields.weight")}

View File

@@ -10,7 +10,7 @@ import { ExtraFieldFormItem, ParsedExtras, StringifiedExtras } from "../../compo
import { useSpoolmanLocations } from "../../components/otherModels"; import { useSpoolmanLocations } from "../../components/otherModels";
import { searchMatches } from "../../utils/filtering"; import { searchMatches } from "../../utils/filtering";
import "../../utils/overrides.css"; import "../../utils/overrides.css";
import { numberFormatter, numberParser, numberParserAllowEmpty } from "../../utils/parsing"; import { formatNumberOnUserInput, numberParser, numberParserAllowEmpty } from "../../utils/parsing";
import { EntityType, useGetFields } from "../../utils/queryFields"; import { EntityType, useGetFields } from "../../utils/queryFields";
import { getCurrencySymbol, useCurrency } from "../../utils/settings"; import { getCurrencySymbol, useCurrency } from "../../utils/settings";
import { createFilamentFromExternal } from "../filaments/functions"; import { createFilamentFromExternal } from "../filaments/functions";
@@ -330,7 +330,7 @@ export const SpoolCreate: React.FC<IResourceComponentsProps & CreateOrCloneProps
<InputNumber <InputNumber
addonAfter={getCurrencySymbol(undefined, currency)} addonAfter={getCurrencySymbol(undefined, currency)}
precision={2} precision={2}
formatter={numberFormatter} formatter={formatNumberOnUserInput}
parser={numberParserAllowEmpty} parser={numberParserAllowEmpty}
/> />
</Form.Item> </Form.Item>
@@ -391,7 +391,7 @@ export const SpoolCreate: React.FC<IResourceComponentsProps & CreateOrCloneProps
min={0} min={0}
addonAfter="g" addonAfter="g"
precision={1} precision={1}
formatter={numberFormatter} formatter={formatNumberOnUserInput}
parser={numberParser} parser={numberParser}
disabled={weightToEnter != WeightToEnter.used_weight} disabled={weightToEnter != WeightToEnter.used_weight}
value={usedWeight} value={usedWeight}
@@ -409,7 +409,7 @@ export const SpoolCreate: React.FC<IResourceComponentsProps & CreateOrCloneProps
min={0} min={0}
addonAfter="g" addonAfter="g"
precision={1} precision={1}
formatter={numberFormatter} formatter={formatNumberOnUserInput}
parser={numberParser} parser={numberParser}
disabled={weightToEnter != WeightToEnter.remaining_weight} disabled={weightToEnter != WeightToEnter.remaining_weight}
value={getRemainingWeight()} value={getRemainingWeight()}
@@ -427,7 +427,7 @@ export const SpoolCreate: React.FC<IResourceComponentsProps & CreateOrCloneProps
min={0} min={0}
addonAfter="g" addonAfter="g"
precision={1} precision={1}
formatter={numberFormatter} formatter={formatNumberOnUserInput}
parser={numberParser} parser={numberParser}
disabled={weightToEnter != WeightToEnter.measured_weight} disabled={weightToEnter != WeightToEnter.measured_weight}
value={getMeasuredWeight()} value={getMeasuredWeight()}

View File

@@ -9,7 +9,7 @@ import { useNavigate, useSearchParams } from "react-router";
import { ExtraFieldFormItem, ParsedExtras, StringifiedExtras } from "../../components/extraFields"; import { ExtraFieldFormItem, ParsedExtras, StringifiedExtras } from "../../components/extraFields";
import { useSpoolmanLocations } from "../../components/otherModels"; import { useSpoolmanLocations } from "../../components/otherModels";
import { searchMatches } from "../../utils/filtering"; import { searchMatches } from "../../utils/filtering";
import { numberFormatter, numberParser, numberParserAllowEmpty } from "../../utils/parsing"; import { formatNumberOnUserInput, numberParser, numberParserAllowEmpty } from "../../utils/parsing";
import { EntityType, useGetFields } from "../../utils/queryFields"; import { EntityType, useGetFields } from "../../utils/queryFields";
import { getCurrencySymbol, useCurrency } from "../../utils/settings"; import { getCurrencySymbol, useCurrency } from "../../utils/settings";
import { createFilamentFromExternal } from "../filaments/functions"; import { createFilamentFromExternal } from "../filaments/functions";
@@ -320,7 +320,7 @@ export const SpoolEdit: React.FC<IResourceComponentsProps> = () => {
<InputNumber <InputNumber
addonAfter={getCurrencySymbol(undefined, currency)} addonAfter={getCurrencySymbol(undefined, currency)}
precision={2} precision={2}
formatter={numberFormatter} formatter={formatNumberOnUserInput}
parser={numberParserAllowEmpty} parser={numberParserAllowEmpty}
/> />
</Form.Item> </Form.Item>
@@ -380,7 +380,7 @@ export const SpoolEdit: React.FC<IResourceComponentsProps> = () => {
min={0} min={0}
addonAfter="g" addonAfter="g"
precision={1} precision={1}
formatter={numberFormatter} formatter={formatNumberOnUserInput}
parser={numberParser} parser={numberParser}
disabled={weightToEnter != WeightToEnter.used_weight} disabled={weightToEnter != WeightToEnter.used_weight}
value={usedWeight} value={usedWeight}
@@ -394,7 +394,7 @@ export const SpoolEdit: React.FC<IResourceComponentsProps> = () => {
min={0} min={0}
addonAfter="g" addonAfter="g"
precision={1} precision={1}
formatter={numberFormatter} formatter={formatNumberOnUserInput}
parser={numberParser} parser={numberParser}
disabled={weightToEnter != WeightToEnter.remaining_weight} disabled={weightToEnter != WeightToEnter.remaining_weight}
value={getRemainingWeight()} value={getRemainingWeight()}
@@ -408,7 +408,7 @@ export const SpoolEdit: React.FC<IResourceComponentsProps> = () => {
min={0} min={0}
addonAfter="g" addonAfter="g"
precision={1} precision={1}
formatter={numberFormatter} formatter={formatNumberOnUserInput}
parser={numberParser} parser={numberParser}
disabled={weightToEnter != WeightToEnter.measured_weight} disabled={weightToEnter != WeightToEnter.measured_weight}
value={getMeasuredWeight()} value={getMeasuredWeight()}

View File

@@ -24,6 +24,22 @@ export function formatNumberWithSpaceSeparator(input: string): string {
return formattedNumber; return formattedNumber;
} }
/**
* Number formatter compatible with Ant Design's InputNumber component, handling UX properly.
* @param value
* @param info
* @returns
*/
export function formatNumberOnUserInput(
value: number | string | undefined,
info: { userTyping: boolean; input: string }
): string {
if (info.userTyping) {
return info.input;
}
return numberFormatter(value);
}
/** /**
* Number formatter that nicely formats numbers with correct decimal separator based on locale * Number formatter that nicely formats numbers with correct decimal separator based on locale
* Always uses blank space as thousands separator to prevent confusion with the decimal separator * Always uses blank space as thousands separator to prevent confusion with the decimal separator
@@ -116,10 +132,10 @@ export function enrichText(text: string | undefined) {
*/ */
export function formatWeight(weightInGrams: number, precision: number = 2): string { export function formatWeight(weightInGrams: number, precision: number = 2): string {
if (weightInGrams >= 1000) { if (weightInGrams >= 1000) {
const kilograms = removeTrailingZeros((weightInGrams / 1000).toFixed(precision)) const kilograms = removeTrailingZeros((weightInGrams / 1000).toFixed(precision));
return `${kilograms} kg`; return `${kilograms} kg`;
} else { } else {
const grams = removeTrailingZeros(weightInGrams.toFixed(precision)) const grams = removeTrailingZeros(weightInGrams.toFixed(precision));
return `${grams} g`; return `${grams} g`;
} }
} }
@@ -133,7 +149,7 @@ export function formatWeight(weightInGrams: number, precision: number = 2): stri
*/ */
export function formatLength(lengthInMillimeter: number, precision: number = 2): string { export function formatLength(lengthInMillimeter: number, precision: number = 2): string {
if (lengthInMillimeter >= 1000) { if (lengthInMillimeter >= 1000) {
const meters = removeTrailingZeros((lengthInMillimeter / 1000).toFixed(precision)) const meters = removeTrailingZeros((lengthInMillimeter / 1000).toFixed(precision));
return `${meters} m`; return `${meters} m`;
} else { } else {
return `${lengthInMillimeter} mm`; return `${lengthInMillimeter} mm`;
@@ -158,5 +174,5 @@ export function formatLength(lengthInMillimeter: number, precision: number = 2):
* ``` * ```
*/ */
function removeTrailingZeros(num: string): string { function removeTrailingZeros(num: string): string {
return num.replace(/(\.\d*?[1-9])0+|\.0*$/, '$1'); return num.replace(/(\.\d*?[1-9])0+|\.0*$/, "$1");
} }