Opening this first as CONTRIBUTING.md asks ("For pull requests that change the API or
implementation, discuss with maintainers first by opening an issue"). Feature requests are routed
to Discussions in .github/ISSUE_TEMPLATE/config.yml, but Discussions is not enabled on the repo,
so an issue seemed the only channel available. Happy to move this anywhere you prefer.
The gap
NumericText formats through locale, useGrouping, minimumFractionDigits and
maximumFractionDigits. There is no way to render money, which is the case the README's own
opening line names first: "counters, balances, prices, scores, dashboards".
The proposal
One format prop shaped like Intl.NumberFormatOptions, resolved natively on each side:
NumberFormatter on iOS, android.icu on Android, Intl on web.
<NumericText value={total} currency="USD" /> // $1,234.50
<NumericText value={total} locale="de-DE" currency="EUR" /> // 1.234,50 €
<NumericText value={rate} format={{ style: 'percent' }} /> // 42%
The existing four props stay as shorthands, so it is additive.
The shape is borrowed from number-flow, which answers
the same question on the web with one options object rather than a growing row of flat props.
Two things it forces, which are the parts worth your opinion
1. The affix has to be keyed structurally. TransitionLogic keys a non-digit token by its
offset in the string (O$i), so $999 → $1,000 would kill the $ at offset 0 and create a new
one at offset 0: a born/died cycle where it should slide. The fix is to count outward from the
nearest end of the number, P0 before the first digit and X0 after the last. Unreachable today,
since a plain grouped decimal only produces digits, group and decimal marks, and a sign.
2. The bundled font has no currency glyphs. The subset carries digits, separators and signs
only: no $, no €, no letters. A currency format would drive canRender into falling the whole
line back to the platform font, silently losing the rounded face. Regenerating with
.agent/tools/subset_font.sh costs ~97 KB → ~300 KB across the nine weights. Digit and separator
advances come out unchanged to four decimals, so the measureBox constants still hold, but the
size increase is a real trade and yours to accept or reject.
There is also a rounding question: Intl rounds halves away from zero, NumberFormatter and ICU
both round half-to-even, so 2.5 at zero decimals currently reads 3 on web, 2 on iOS and 2
on Android. I think that should be pinned to Intl's rule on all three rather than exposed as an
option, but it is a behaviour change to existing output.
Status
I have this implemented and verified (39 JS tests, 25 Kotlin tests, compileDebugKotlin, a full
iOS xcodebuild, and both renderers driven by hand on a simulator through every format). Happy to
open the PR, or to drop it entirely if the direction is wrong for the library. It is not
verified against a ground-truth recording, so the affix's motion during a carry is reasoned-about
rather than measured, which I know is not the bar .agent/AGENTS.md sets.
Opening this first as CONTRIBUTING.md asks ("For pull requests that change the API or
implementation, discuss with maintainers first by opening an issue"). Feature requests are routed
to Discussions in
.github/ISSUE_TEMPLATE/config.yml, but Discussions is not enabled on the repo,so an issue seemed the only channel available. Happy to move this anywhere you prefer.
The gap
NumericTextformats throughlocale,useGrouping,minimumFractionDigitsandmaximumFractionDigits. There is no way to render money, which is the case the README's ownopening line names first: "counters, balances, prices, scores, dashboards".
The proposal
One
formatprop shaped likeIntl.NumberFormatOptions, resolved natively on each side:NumberFormatteron iOS,android.icuon Android,Intlon web.The existing four props stay as shorthands, so it is additive.
The shape is borrowed from
number-flow, which answersthe same question on the web with one options object rather than a growing row of flat props.
Two things it forces, which are the parts worth your opinion
1. The affix has to be keyed structurally.
TransitionLogickeys a non-digit token by itsoffset in the string (
O$i), so$999→$1,000would kill the$at offset 0 and create a newone at offset 0: a born/died cycle where it should slide. The fix is to count outward from the
nearest end of the number,
P0before the first digit andX0after the last. Unreachable today,since a plain grouped decimal only produces digits, group and decimal marks, and a sign.
2. The bundled font has no currency glyphs. The subset carries digits, separators and signs
only: no
$, no€, no letters. A currency format would drivecanRenderinto falling the wholeline back to the platform font, silently losing the rounded face. Regenerating with
.agent/tools/subset_font.shcosts ~97 KB → ~300 KB across the nine weights. Digit and separatoradvances come out unchanged to four decimals, so the
measureBoxconstants still hold, but thesize increase is a real trade and yours to accept or reject.
There is also a rounding question:
Intlrounds halves away from zero,NumberFormatterand ICUboth round half-to-even, so
2.5at zero decimals currently reads3on web,2on iOS and2on Android. I think that should be pinned to
Intl's rule on all three rather than exposed as anoption, but it is a behaviour change to existing output.
Status
I have this implemented and verified (39 JS tests, 25 Kotlin tests,
compileDebugKotlin, a fulliOS
xcodebuild, and both renderers driven by hand on a simulator through every format). Happy toopen the PR, or to drop it entirely if the direction is wrong for the library. It is not
verified against a ground-truth recording, so the affix's motion during a carry is reasoned-about
rather than measured, which I know is not the bar
.agent/AGENTS.mdsets.