From 353d728eec71d1ba82b42a14d6140f54428298ae Mon Sep 17 00:00:00 2001 From: Bogdan Chadkin Date: Fri, 28 Aug 2026 17:01:42 +0200 Subject: [PATCH 1/7] Add configurable number formatter --- README.md | 23 ++++++++++++ package.json | 6 +++- src/formatters/number.ts | 64 ++++++++++++++++++++++++++++++++++ tests/number-formatter.test.ts | 57 ++++++++++++++++++++++++++++++ tsconfig.json | 3 +- 5 files changed, 151 insertions(+), 2 deletions(-) create mode 100644 src/formatters/number.ts create mode 100644 tests/number-formatter.test.ts diff --git a/README.md b/README.md index bbdddcf..7758f84 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,29 @@ Both `useRifm(options)` and `` accept the following options. Pass both properties to the underlying input. +## Number formatter + +A configurable, precision-safe number formatter is available from the `rifm/formatters/number` entry point. It returns `format` and `accept`, so it can be spread directly into RIFM options: + +```tsx +import { createNumberFormatter } from "rifm/formatters/number"; + +const euro = createNumberFormatter({ + locales: "de-DE", + suffix: " EUR", + maximumFractionDigits: 2, + allowNegative: true, +}); + +const rifm = useRifm({ + value, + onChange: setValue, + ...euro, +}); +``` + +Supported options are `locales`, `prefix`, `suffix`, `useGrouping`, `allowNegative`, `minimumFractionDigits`, and `maximumFractionDigits`. Prefixes and suffixes are inserted literally, so include any desired spacing, for example `prefix: "$"` or `suffix: " EUR"`. Fraction digit limits must be non-negative integers, and the minimum must not exceed the maximum. Locale-specific grouping and decimal separators come from `Intl.NumberFormat`; set `useGrouping` to `false` to disable grouping. The editable fraction remains a string, preserving trailing zeroes, while integer grouping uses `BigInt` to avoid precision loss. + ## Accepted characters and caret behavior RIFM restores the caret by tracking the characters matched by `accept`. The formatter may insert, remove, or move separators, but it should preserve the order of those accepted characters. diff --git a/package.json b/package.json index 04edfbd..fe4196f 100644 --- a/package.json +++ b/package.json @@ -21,10 +21,14 @@ ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" + }, + "./formatters/number": { + "types": "./dist/formatters/number.d.ts", + "import": "./dist/formatters/number.js" } }, "scripts": { - "build": "tsdown src/index.ts --format esm --target es2022 --platform neutral --dts --clean", + "build": "tsdown src/index.ts src/formatters/number.ts --format esm --target es2022 --platform neutral --dts --clean", "test:unit": "vitest run --project node", "test:browser": "vitest run --project browser", "test": "pnpm run test:ts && pnpm run test:unit && pnpm run test:browser", diff --git a/src/formatters/number.ts b/src/formatters/number.ts new file mode 100644 index 0000000..44676f0 --- /dev/null +++ b/src/formatters/number.ts @@ -0,0 +1,64 @@ +export interface NumberFormatterOptions { + locales?: string | string[]; + prefix?: string; + suffix?: string; + allowNegative?: boolean; + useGrouping?: boolean; + maximumFractionDigits?: number; + minimumFractionDigits?: number; +} + +export interface NumberFormatter { + format: (value: string) => string; + accept: RegExp; +} + +/** Creates locale-aware `format` and `accept` props for RIFM. */ +export const createNumberFormatter = (options: NumberFormatterOptions = {}): NumberFormatter => { + const { + locales, + prefix = "", + suffix = "", + allowNegative = false, + useGrouping = true, + maximumFractionDigits, + minimumFractionDigits = 0, + } = options; + + // Latin digits make the result compatible with RIFM's digit-based caret tracking. + const latin = { numberingSystem: "latn" } as Intl.NumberFormatOptions; + const integerFormatter = new Intl.NumberFormat(locales, { + ...latin, + useGrouping, + maximumFractionDigits: 0, + }); + const partFormatter = new Intl.NumberFormat(locales, { + ...latin, + useGrouping: true, + minimumFractionDigits: 1, + }) as Intl.NumberFormat; + const decimalSeparator = + partFormatter.formatToParts(1.1).find((part) => part.type === "decimal")?.value ?? "."; + const acceptsFraction = maximumFractionDigits !== 0; + const accept = new RegExp(`[\\d${acceptsFraction ? decimalSeparator : ""}]`, "g"); + + const format = (value: string): string => { + const decimalIndex = acceptsFraction ? value.indexOf(decimalSeparator) : -1; + const integerSource = decimalIndex < 0 ? value : value.slice(0, decimalIndex); + const fractionSource = decimalIndex < 0 ? "" : value.slice(decimalIndex + 1); + const negative = allowNegative && value.includes("-"); + let integer = integerSource.replace(/\D/g, "").replace(/^0+(?=\d)/, ""); + let fraction = fractionSource.replace(/\D/g, ""); + + if (maximumFractionDigits != null) fraction = fraction.slice(0, maximumFractionDigits); + if (integer === "" && decimalIndex >= 0) integer = "0"; + if (integer === "") return negative ? "-" : ""; + + fraction = fraction.padEnd(minimumFractionDigits, "0"); + const grouped = integerFormatter.format(BigInt(`${negative ? "-" : ""}${integer}`)); + const decimal = decimalIndex >= 0 || minimumFractionDigits > 0; + return `${prefix}${grouped}${decimal ? `${decimalSeparator}${fraction}` : ""}${suffix}`; + }; + + return { format, accept }; +}; diff --git a/tests/number-formatter.test.ts b/tests/number-formatter.test.ts new file mode 100644 index 0000000..4df55e3 --- /dev/null +++ b/tests/number-formatter.test.ts @@ -0,0 +1,57 @@ +import { describe, expect, test } from "vitest"; +import { createNumberFormatter } from "rifm/formatters/number"; + +describe("number formatter", () => { + test("groups integers and preserves editable fractional values", () => { + const formatter = createNumberFormatter({ maximumFractionDigits: 2 }); + + expect(formatter.format("1234567.20")).toBe("1,234,567.20"); + expect(formatter.format("1,234,567.")).toBe("1,234,567."); + expect(formatter.format(".5")).toBe("0.5"); + expect(formatter.format("123.456")).toBe("123.45"); + expect(formatter.format("900719925474099312345")).toBe("900,719,925,474,099,312,345"); + }); + + test("supports locale separators", () => { + const formatter = createNumberFormatter({ + locales: "de-DE", + maximumFractionDigits: 2, + }); + + const formatted = formatter.format("12345,6"); + expect(formatted).toBe("12.345,6"); + expect("12.345,6".match(formatter.accept)?.join("")).toBe("12345,6"); + }); + + test("supports prefixes and suffixes", () => { + const formatter = createNumberFormatter({ + locales: "en-US", + prefix: "$", + suffix: " EUR", + allowNegative: true, + maximumFractionDigits: 2, + }); + + expect(formatter.format("-$1234.5 EUR")).toBe("$-1,234.5 EUR"); + expect(formatter.format("")).toBe(""); + }); + + test("supports signs, fixed precision, custom grouping, and disabled grouping", () => { + expect( + createNumberFormatter({ + locales: "en-IN", + allowNegative: true, + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }).format("-$12345678.9"), + ).toBe("-1,23,45,678.90"); + + expect( + createNumberFormatter({ useGrouping: false, maximumFractionDigits: 0 }).format("12,345"), + ).toBe("12345"); + }); + + test("returns an object which can be spread into Rifm options", () => { + expect(Object.keys(createNumberFormatter())).toEqual(["format", "accept"]); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index d9081a8..7fc8313 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,7 +11,8 @@ "skipLibCheck": true, "baseUrl": ".", "paths": { - "rifm": ["./src"] + "rifm": ["./src"], + "rifm/formatters/number": ["./src/formatters/number"] }, "esModuleInterop": true, "resolveJsonModule": true, From 4122f1defb66ba699fe290b7fcbe62e1b5199f64 Mon Sep 17 00:00:00 2001 From: Bogdan Chadkin Date: Fri, 28 Aug 2026 17:09:46 +0200 Subject: [PATCH 2/7] Export number formatter from main entry --- README.md | 4 ++-- package.json | 6 +----- src/index.ts | 1 + src/{formatters => }/number.ts | 0 tests/number-formatter.test.ts | 2 +- tsconfig.json | 3 +-- 6 files changed, 6 insertions(+), 10 deletions(-) rename src/{formatters => }/number.ts (100%) diff --git a/README.md b/README.md index 7758f84..a0b55bb 100644 --- a/README.md +++ b/README.md @@ -112,10 +112,10 @@ Pass both properties to the underlying input. ## Number formatter -A configurable, precision-safe number formatter is available from the `rifm/formatters/number` entry point. It returns `format` and `accept`, so it can be spread directly into RIFM options: +A configurable, precision-safe number formatter is available from the main `rifm` entry point. It returns `format` and `accept`, so it can be spread directly into RIFM options: ```tsx -import { createNumberFormatter } from "rifm/formatters/number"; +import { createNumberFormatter } from "rifm"; const euro = createNumberFormatter({ locales: "de-DE", diff --git a/package.json b/package.json index fe4196f..04edfbd 100644 --- a/package.json +++ b/package.json @@ -21,14 +21,10 @@ ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" - }, - "./formatters/number": { - "types": "./dist/formatters/number.d.ts", - "import": "./dist/formatters/number.js" } }, "scripts": { - "build": "tsdown src/index.ts src/formatters/number.ts --format esm --target es2022 --platform neutral --dts --clean", + "build": "tsdown src/index.ts --format esm --target es2022 --platform neutral --dts --clean", "test:unit": "vitest run --project node", "test:browser": "vitest run --project browser", "test": "pnpm run test:ts && pnpm run test:unit && pnpm run test:browser", diff --git a/src/index.ts b/src/index.ts index 439d382..3ef7a77 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1 +1,2 @@ export * from "./rifm"; +export * from "./number"; diff --git a/src/formatters/number.ts b/src/number.ts similarity index 100% rename from src/formatters/number.ts rename to src/number.ts diff --git a/tests/number-formatter.test.ts b/tests/number-formatter.test.ts index 4df55e3..e7f8cd9 100644 --- a/tests/number-formatter.test.ts +++ b/tests/number-formatter.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "vitest"; -import { createNumberFormatter } from "rifm/formatters/number"; +import { createNumberFormatter } from "rifm"; describe("number formatter", () => { test("groups integers and preserves editable fractional values", () => { diff --git a/tsconfig.json b/tsconfig.json index 7fc8313..d9081a8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,8 +11,7 @@ "skipLibCheck": true, "baseUrl": ".", "paths": { - "rifm": ["./src"], - "rifm/formatters/number": ["./src/formatters/number"] + "rifm": ["./src"] }, "esModuleInterop": true, "resolveJsonModule": true, From 5c12fd302eb29860a610c74dbb7b1a4b931bc15f Mon Sep 17 00:00:00 2001 From: Bogdan Chadkin Date: Fri, 28 Aug 2026 17:10:45 +0200 Subject: [PATCH 3/7] Expose number formatter from rifm/number --- README.md | 4 ++-- package.json | 6 +++++- src/index.ts | 1 - tests/number-formatter.test.ts | 2 +- tsconfig.json | 3 ++- 5 files changed, 10 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index a0b55bb..638cde7 100644 --- a/README.md +++ b/README.md @@ -112,10 +112,10 @@ Pass both properties to the underlying input. ## Number formatter -A configurable, precision-safe number formatter is available from the main `rifm` entry point. It returns `format` and `accept`, so it can be spread directly into RIFM options: +A configurable, precision-safe number formatter is available from the `rifm/number` entry point. It returns `format` and `accept`, so it can be spread directly into RIFM options: ```tsx -import { createNumberFormatter } from "rifm"; +import { createNumberFormatter } from "rifm/number"; const euro = createNumberFormatter({ locales: "de-DE", diff --git a/package.json b/package.json index 04edfbd..255ab41 100644 --- a/package.json +++ b/package.json @@ -21,10 +21,14 @@ ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" + }, + "./number": { + "types": "./dist/number.d.ts", + "import": "./dist/number.js" } }, "scripts": { - "build": "tsdown src/index.ts --format esm --target es2022 --platform neutral --dts --clean", + "build": "tsdown src/index.ts src/number.ts --format esm --target es2022 --platform neutral --dts --clean", "test:unit": "vitest run --project node", "test:browser": "vitest run --project browser", "test": "pnpm run test:ts && pnpm run test:unit && pnpm run test:browser", diff --git a/src/index.ts b/src/index.ts index 3ef7a77..439d382 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,2 +1 @@ export * from "./rifm"; -export * from "./number"; diff --git a/tests/number-formatter.test.ts b/tests/number-formatter.test.ts index e7f8cd9..6edf279 100644 --- a/tests/number-formatter.test.ts +++ b/tests/number-formatter.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "vitest"; -import { createNumberFormatter } from "rifm"; +import { createNumberFormatter } from "rifm/number"; describe("number formatter", () => { test("groups integers and preserves editable fractional values", () => { diff --git a/tsconfig.json b/tsconfig.json index d9081a8..ba99e30 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,7 +11,8 @@ "skipLibCheck": true, "baseUrl": ".", "paths": { - "rifm": ["./src"] + "rifm": ["./src"], + "rifm/number": ["./src/number"] }, "esModuleInterop": true, "resolveJsonModule": true, From 32bfc5a945c63f65012e5ff7810b7e5b5cc90862 Mon Sep 17 00:00:00 2001 From: Bogdan Chadkin Date: Fri, 28 Aug 2026 17:15:45 +0200 Subject: [PATCH 4/7] Expand number formatter documentation --- README.md | 41 +++++++++++++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 638cde7..5b46b5c 100644 --- a/README.md +++ b/README.md @@ -112,26 +112,51 @@ Pass both properties to the underlying input. ## Number formatter -A configurable, precision-safe number formatter is available from the `rifm/number` entry point. It returns `format` and `accept`, so it can be spread directly into RIFM options: +Import `createNumberFormatter` from `rifm/number`. With no options, it uses the runtime's default locale, groups thousands, accepts positive numbers, and preserves any number of fractional digits: ```tsx import { createNumberFormatter } from "rifm/number"; +const number = createNumberFormatter(); +const rifm = useRifm({ + value, + onChange: setValue, + ...number, +}); +``` + +The returned `{ format, accept }` object can be spread into either `useRifm` or ``. Formatting is string-based, so editable states such as `1.` and `1.20` are preserved and large integers do not lose precision. + +### Number formatter options + +The examples below use the `en-US` locale unless another locale is specified. + +| Option | Default | Description | Example | +| ----------------------- | --------------- | ---------------------------------------------------------- | -------------------------------------------------- | +| `locales` | Runtime default | Locale or locale fallback list used by `Intl.NumberFormat` | `{ locales: "de-DE" }`: `12345,6` → `12.345,6` | +| `prefix` | `""` | Text inserted before a non-empty formatted number | `{ prefix: "$" }`: `1234.5` → `$1,234.5` | +| `suffix` | `""` | Text inserted after a non-empty formatted number | `{ suffix: " EUR" }`: `1234.5` → `1,234.5 EUR` | +| `useGrouping` | `true` | Enables locale-specific integer grouping | `{ useGrouping: false }`: `1234.5` → `1234.5` | +| `allowNegative` | `false` | Preserves a minus sign when present | `{ allowNegative: true }`: `-1234.5` → `-1,234.5` | +| `minimumFractionDigits` | `0` | Pads the fractional part with zeroes | `{ minimumFractionDigits: 2 }`: `12.5` → `12.50` | +| `maximumFractionDigits` | Unlimited | Truncates the fractional part to this length | `{ maximumFractionDigits: 2 }`: `12.345` → `12.34` | + +Options can be combined: + +```ts const euro = createNumberFormatter({ locales: "de-DE", suffix: " EUR", - maximumFractionDigits: 2, allowNegative: true, + minimumFractionDigits: 2, + maximumFractionDigits: 2, }); -const rifm = useRifm({ - value, - onChange: setValue, - ...euro, -}); +euro.format("-12345,6"); +// "-12.345,60 EUR" ``` -Supported options are `locales`, `prefix`, `suffix`, `useGrouping`, `allowNegative`, `minimumFractionDigits`, and `maximumFractionDigits`. Prefixes and suffixes are inserted literally, so include any desired spacing, for example `prefix: "$"` or `suffix: " EUR"`. Fraction digit limits must be non-negative integers, and the minimum must not exceed the maximum. Locale-specific grouping and decimal separators come from `Intl.NumberFormat`; set `useGrouping` to `false` to disable grouping. The editable fraction remains a string, preserving trailing zeroes, while integer grouping uses `BigInt` to avoid precision loss. +Prefixes and suffixes are literal, so include any desired spacing and avoid digits or the locale's decimal separator. Fraction digit limits must be non-negative integers, and the minimum must not exceed the maximum. ## Accepted characters and caret behavior From 419924a763b619065fc236754f45ea6328284433 Mon Sep 17 00:00:00 2001 From: Bogdan Chadkin Date: Sat, 29 Aug 2026 15:45:38 +0200 Subject: [PATCH 5/7] remove typecasts and paths --- src/number.ts | 4 ++-- tsconfig.json | 5 ----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/number.ts b/src/number.ts index 44676f0..7d4d3dd 100644 --- a/src/number.ts +++ b/src/number.ts @@ -26,7 +26,7 @@ export const createNumberFormatter = (options: NumberFormatterOptions = {}): Num } = options; // Latin digits make the result compatible with RIFM's digit-based caret tracking. - const latin = { numberingSystem: "latn" } as Intl.NumberFormatOptions; + const latin = { numberingSystem: "latn" }; const integerFormatter = new Intl.NumberFormat(locales, { ...latin, useGrouping, @@ -36,7 +36,7 @@ export const createNumberFormatter = (options: NumberFormatterOptions = {}): Num ...latin, useGrouping: true, minimumFractionDigits: 1, - }) as Intl.NumberFormat; + }); const decimalSeparator = partFormatter.formatToParts(1.1).find((part) => part.type === "decimal")?.value ?? "."; const acceptsFraction = maximumFractionDigits !== 0; diff --git a/tsconfig.json b/tsconfig.json index ba99e30..d748755 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,11 +9,6 @@ "strict": true, "noEmit": true, "skipLibCheck": true, - "baseUrl": ".", - "paths": { - "rifm": ["./src"], - "rifm/number": ["./src/number"] - }, "esModuleInterop": true, "resolveJsonModule": true, "isolatedModules": true, From 66ea9ae6ab17b68ac09fa8bbd5f4216d6c09072a Mon Sep 17 00:00:00 2001 From: Bogdan Chadkin Date: Sat, 29 Aug 2026 16:08:30 +0200 Subject: [PATCH 6/7] Remove prefix and suffix to avoid supporting poor UX --- README.md | 7 ++----- src/number.ts | 11 +++++------ tests/number-formatter.test.ts | 13 ------------- tests/rifm-format.browser.test.tsx | 17 +++++++++++++++++ 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 5b46b5c..b4957bc 100644 --- a/README.md +++ b/README.md @@ -134,8 +134,6 @@ The examples below use the `en-US` locale unless another locale is specified. | Option | Default | Description | Example | | ----------------------- | --------------- | ---------------------------------------------------------- | -------------------------------------------------- | | `locales` | Runtime default | Locale or locale fallback list used by `Intl.NumberFormat` | `{ locales: "de-DE" }`: `12345,6` → `12.345,6` | -| `prefix` | `""` | Text inserted before a non-empty formatted number | `{ prefix: "$" }`: `1234.5` → `$1,234.5` | -| `suffix` | `""` | Text inserted after a non-empty formatted number | `{ suffix: " EUR" }`: `1234.5` → `1,234.5 EUR` | | `useGrouping` | `true` | Enables locale-specific integer grouping | `{ useGrouping: false }`: `1234.5` → `1234.5` | | `allowNegative` | `false` | Preserves a minus sign when present | `{ allowNegative: true }`: `-1234.5` → `-1,234.5` | | `minimumFractionDigits` | `0` | Pads the fractional part with zeroes | `{ minimumFractionDigits: 2 }`: `12.5` → `12.50` | @@ -146,17 +144,16 @@ Options can be combined: ```ts const euro = createNumberFormatter({ locales: "de-DE", - suffix: " EUR", allowNegative: true, minimumFractionDigits: 2, maximumFractionDigits: 2, }); euro.format("-12345,6"); -// "-12.345,60 EUR" +// "-12.345,60" ``` -Prefixes and suffixes are literal, so include any desired spacing and avoid digits or the locale's decimal separator. Fraction digit limits must be non-negative integers, and the minimum must not exceed the maximum. +Fraction digit limits must be non-negative integers, and the minimum must not exceed the maximum. ## Accepted characters and caret behavior diff --git a/src/number.ts b/src/number.ts index 7d4d3dd..ba5059c 100644 --- a/src/number.ts +++ b/src/number.ts @@ -1,7 +1,5 @@ export interface NumberFormatterOptions { locales?: string | string[]; - prefix?: string; - suffix?: string; allowNegative?: boolean; useGrouping?: boolean; maximumFractionDigits?: number; @@ -17,8 +15,6 @@ export interface NumberFormatter { export const createNumberFormatter = (options: NumberFormatterOptions = {}): NumberFormatter => { const { locales, - prefix = "", - suffix = "", allowNegative = false, useGrouping = true, maximumFractionDigits, @@ -40,7 +36,10 @@ export const createNumberFormatter = (options: NumberFormatterOptions = {}): Num const decimalSeparator = partFormatter.formatToParts(1.1).find((part) => part.type === "decimal")?.value ?? "."; const acceptsFraction = maximumFractionDigits !== 0; - const accept = new RegExp(`[\\d${acceptsFraction ? decimalSeparator : ""}]`, "g"); + const accept = new RegExp( + `[\\d${acceptsFraction ? decimalSeparator : ""}${allowNegative ? "\\-" : ""}]`, + "g", + ); const format = (value: string): string => { const decimalIndex = acceptsFraction ? value.indexOf(decimalSeparator) : -1; @@ -57,7 +56,7 @@ export const createNumberFormatter = (options: NumberFormatterOptions = {}): Num fraction = fraction.padEnd(minimumFractionDigits, "0"); const grouped = integerFormatter.format(BigInt(`${negative ? "-" : ""}${integer}`)); const decimal = decimalIndex >= 0 || minimumFractionDigits > 0; - return `${prefix}${grouped}${decimal ? `${decimalSeparator}${fraction}` : ""}${suffix}`; + return `${grouped}${decimal ? `${decimalSeparator}${fraction}` : ""}`; }; return { format, accept }; diff --git a/tests/number-formatter.test.ts b/tests/number-formatter.test.ts index 6edf279..229e78b 100644 --- a/tests/number-formatter.test.ts +++ b/tests/number-formatter.test.ts @@ -23,19 +23,6 @@ describe("number formatter", () => { expect("12.345,6".match(formatter.accept)?.join("")).toBe("12345,6"); }); - test("supports prefixes and suffixes", () => { - const formatter = createNumberFormatter({ - locales: "en-US", - prefix: "$", - suffix: " EUR", - allowNegative: true, - maximumFractionDigits: 2, - }); - - expect(formatter.format("-$1234.5 EUR")).toBe("$-1,234.5 EUR"); - expect(formatter.format("")).toBe(""); - }); - test("supports signs, fixed precision, custom grouping, and disabled grouping", () => { expect( createNumberFormatter({ diff --git a/tests/rifm-format.browser.test.tsx b/tests/rifm-format.browser.test.tsx index 553edf2..09bc198 100644 --- a/tests/rifm-format.browser.test.tsx +++ b/tests/rifm-format.browser.test.tsx @@ -1,5 +1,6 @@ import { afterEach, expect, it, test } from "vitest"; import { formatFixedPointNumber, formatFloatingPointNumber, formatPhone } from "./format"; +import { createNumberFormatter } from "../src/number"; import { BrowserExec, createBrowserExec } from "./utils/browser-exec"; let exec: BrowserExec | null = null; @@ -34,6 +35,22 @@ test("format works with real browser input", async () => { expect(await exec({ type: "PUT_SYMBOL", payload: "x" })).toBe("1|2’345"); }); +test("keeps the caret after a negative sign", async () => { + exec = createBrowserExec({ + ...createNumberFormatter({ allowNegative: true, maximumFractionDigits: 0 }), + }); + + expect(await exec({ type: "PUT_SYMBOL", payload: "-" })).toBe("-|"); + + exec.cleanup(); + exec = createBrowserExec({ + ...createNumberFormatter({ allowNegative: true, maximumFractionDigits: 0 }), + initialValue: "123", + }); + + expect(await exec({ type: "PUT_SYMBOL", payload: "-" })).toBe("-|123"); +}); + test("format with custom accept works with real browser input", async () => { exec = createBrowserExec({ accept: /[\d.]/gi, From a0470760f165d5fbcf9215f65db01e75c22d72ab Mon Sep 17 00:00:00 2001 From: Bogdan Chadkin Date: Sat, 29 Aug 2026 16:11:01 +0200 Subject: [PATCH 7/7] Import formatter explicitly --- tests/number-formatter.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/number-formatter.test.ts b/tests/number-formatter.test.ts index 229e78b..64c53b3 100644 --- a/tests/number-formatter.test.ts +++ b/tests/number-formatter.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "vitest"; -import { createNumberFormatter } from "rifm/number"; +import { createNumberFormatter } from "../src/number"; describe("number formatter", () => { test("groups integers and preserves editable fractional values", () => {