diff --git a/packages/morpho-ts/src/format/format/README.md b/packages/morpho-ts/src/format/format/README.md index 9f40278d..73faeb69 100644 --- a/packages/morpho-ts/src/format/format/README.md +++ b/packages/morpho-ts/src/format/format/README.md @@ -22,7 +22,8 @@ The `format` object provides access to different formatters: ### Usage -Each formatter can be accessed through the `format` object and provides chainable methods to customize the output. The formatted value can be optained calling `.of(value)` for `number` or `.of(value, decimals)` for `bigint`. +Each formatter can be accessed through the `format` object and provides chainable methods to customize the output. The formatted value can be obtained calling `.of(value)` for `number` or `.of(value, decimals)` for `bigint`. +The return value will retain the nullability of the input value, unless a `.default()` method is applied (refer to [Number Formatter](#2-number-formatter) for details). > [!Tip] > You can store the populated `of` function in a custom formatter: @@ -33,6 +34,7 @@ Each formatter can be accessed through the `format` object and provides chainabl > formatDollar(123456789n, 4); // "$12.34k" > ``` + ### 1. Hex Formatter Formats a value as a hexadecimal string. @@ -69,6 +71,7 @@ const numberValue = format.number.of(123.45); // "123.45" - `.unit(string)`: Adds a unit to the number (e.g., "$", "%"). - `.locale(string)`: Formats the number according to the specified locale. - `.readable()`: Makes the value more readable for small numbers. +- `.default(string)`: Sets a default value in case value is `null` or `undefined`. ### 3. Commas Formatter diff --git a/packages/morpho-ts/src/format/format/format.test.ts b/packages/morpho-ts/src/format/format/format.test.ts index 7aa2cbcf..72982719 100644 --- a/packages/morpho-ts/src/format/format/format.test.ts +++ b/packages/morpho-ts/src/format/format/format.test.ts @@ -10,6 +10,10 @@ describe("format", () => { it("without option", () => { expect(format.hex.of(number)).toEqual((123456789).toString(16)); }); + it("with a default value", () => { + expect(format.hex.default("default").of(undefined)).toEqual("default"); + expect(format.hex.default("default").of(null)).toEqual("default"); + }); }); describe("should properly format bigint in hex format", () => { it("without option", () => { @@ -17,6 +21,12 @@ describe("format", () => { (123456789).toString(16), ); }); + it("with a default value", () => { + expect(format.hex.default("default").of(undefined, 18)).toEqual( + "default", + ); + expect(format.hex.default("default").of(null, 18)).toEqual("default"); + }); }); }); @@ -60,6 +70,12 @@ describe("format", () => { expect(format.number.of(1.234e30)).toEqual("1234" + "0".repeat(27)); expect(format.number.of(1.234e2)).toEqual("123.4"); }); + it("with a default value", () => { + expect(format.number.default("default").of(undefined)).toEqual( + "default", + ); + expect(format.number.default("default").of(null)).toEqual("default"); + }); }); describe("should properly format bigint in number format", () => { it("without option", () => { @@ -103,6 +119,14 @@ describe("format", () => { "12345,6789", ); }); + it("with a default value", () => { + expect(format.number.default("default").of(undefined, 18)).toEqual( + "default", + ); + expect(format.number.default("default").of(null, 18)).toEqual( + "default", + ); + }); }); }); @@ -140,6 +164,12 @@ describe("format", () => { it("with locale", () => { expect(format.short.locale("fr-FR").of(number)).toEqual("12,3456789k"); }); + it("with a default value", () => { + expect(format.short.default("default").of(undefined)).toEqual( + "default", + ); + expect(format.short.default("default").of(null)).toEqual("default"); + }); }); describe("should properly format bigint in short format", () => { it("without option", () => { @@ -195,6 +225,12 @@ describe("format", () => { // the correct space in fr-FR is narrow no-break space (U+202F) ).toEqual("1\u202F234,56789"); }); + it("with a default value", () => { + expect(format.short.default("default").of(undefined, 18)).toEqual( + "default", + ); + expect(format.short.default("default").of(null, 18)).toEqual("default"); + }); }); }); @@ -229,6 +265,12 @@ describe("format", () => { "12\u202F345,6789", ); }); + it("with a default value", () => { + expect(format.commas.default("default").of(undefined)).toEqual( + "default", + ); + expect(format.commas.default("default").of(null)).toEqual("default"); + }); }); describe("should properly format bigint in commas format", () => { it("without option", () => { @@ -273,6 +315,14 @@ describe("format", () => { "12\u202F345,6789", ); }); + it("with a default value", () => { + expect(format.commas.default("default").of(undefined, 18)).toEqual( + "default", + ); + expect(format.commas.default("default").of(null, 18)).toEqual( + "default", + ); + }); }); }); @@ -306,6 +356,12 @@ describe("format", () => { "1234567,8900", ); }); + it("with a default value", () => { + expect(format.percent.default("default").of(undefined)).toEqual( + "default", + ); + expect(format.percent.default("default").of(null)).toEqual("default"); + }); }); describe("should properly format bigint in percent format", () => { it("without option", () => { @@ -346,6 +402,14 @@ describe("format", () => { "1234567,8900", ); }); + it("with a default value", () => { + expect(format.percent.default("default").of(undefined, 18)).toEqual( + "default", + ); + expect(format.percent.default("default").of(null, 18)).toEqual( + "default", + ); + }); }); }); }); diff --git a/packages/morpho-ts/src/format/format/format.ts b/packages/morpho-ts/src/format/format/format.ts index 4f1f3616..bdb4c5a9 100644 --- a/packages/morpho-ts/src/format/format/format.ts +++ b/packages/morpho-ts/src/format/format/format.ts @@ -8,8 +8,12 @@ enum Format { percent = "percent", } -interface BaseFormatOptions { +interface UniversalFormatOptions { format: Format; + default?: string; +} + +interface BaseFormatOptions extends UniversalFormatOptions { digits?: number; removeTrailingZero?: boolean; min?: number; @@ -24,7 +28,7 @@ interface FormatShortOptions extends BaseFormatOptions { format: Format.short; smallValuesWithCommas?: boolean; } -interface FormatHexOptions { +interface FormatHexOptions extends UniversalFormatOptions { format: Format.hex; prefix?: boolean; } @@ -260,14 +264,32 @@ function formatBI( return formattedValue; } +type FormatterWithDefault = { + of(value: bigint | null | undefined, decimals: number): string; + of(value: number | null | undefined): string; +} & Omit; + export abstract class BaseFormatter { protected abstract _options: FormatOptions; constructor() {} - of(value: bigint, decimals: number): string; - of(value: number): string; + default(_d: string) { + this._options.default = _d; + + return this as FormatterWithDefault; + } + + of( + value: T, + decimals: number, + ): Exclude | string; + of( + value: T, + ): Exclude | string; of(value: bigint | number, decimals?: number) { + if (value == null) return this._options.default ?? value; + if (typeof value === "number") { const str = value.toString(); const [significant, exp] = str.split(/[eE]/);