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:
@@ -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")}
|
||||||
|
|||||||
@@ -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")}
|
||||||
|
|||||||
@@ -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()}
|
||||||
|
|||||||
@@ -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()}
|
||||||
|
|||||||
@@ -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");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user