feat: add CurrencyTransformer formatter#4
Merged
janicduplessis merged 1 commit intomainfrom Apr 25, 2026
Merged
Conversation
Adds a built-in currency formatter (cents-focused input formatted via Intl.NumberFormat) alongside the existing pattern and phone-number formatters. Configured per-instance with `currency` (ISO 4217) and optional `locale` (BCP 47); the worklet caches Intl.NumberFormat instances on the worklet runtime. The example app's Currency card now uses this built-in instead of an inline transformer, and demonstrates switching between USD/EUR/JPY by changing the `transformer` prop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Adds a built-in currency formatter to the library, alongside the existing
PatternTransformerandPhoneNumberTransformer. Configured per-instance withcurrency(ISO 4217 code) and an optionallocale(BCP 47 tag). The transformer worklet usesIntl.NumberFormat(Hermes ships with Intl by default on iOS and Android) to drive locale-aware formatting — separators, symbol position, and fraction-digit count are all derived from the currency/locale combination rather than hardcoded.The input model is cents-focused: the user types digits only, and the last N (where N =
Intl.NumberFormat.resolvedOptions().maximumFractionDigits) are treated as the fractional part. So with USD a user types `1234567` and sees `$12,345.67`; with JPY (zero decimals) they see `¥1,234,567`; with EUR (`de-DE`) they see `12.345,67 €` with the suffix symbol.Solution
CurrencyTransformerextendsTransformer. The constructor pre-resolvesmaximumFractionDigitsonce viaIntl, and the worklet capturescurrency/localestrings via closure.Intl.NumberFormatinstances are cached per(locale, currency)on the worklet runtime's `globalThis` to avoid reconstructing on every keystroke.The interesting work is cursor placement, which has a few cases that all collapse into one rule. The cents model means leading zeros get stripped by `parseInt` and the formatter pads or shifts digits as the magnitude crosses boundaries — so the digit-position-from-start in raw doesn't match the digit-position-from-start in formatted. The cursor placement formula compensates:
```
targetDigit = cursorDigitIndex + (formattedDigitsCount − rawDigitsCount)
```
Then the cursor goes right after the `targetDigit`-th digit in the formatted output, with two snap rules: past the last digit → snap to right after the last digit (preserves the cents-accumulator UX and excludes any trailing symbol like ` €` from the typing area); before the first digit → snap to the first digit (skips prefix symbols). Backspace next to a thousands or decimal separator drops the digit before the cursor instead of the separator (the formatter would just re-add the separator otherwise — silent no-op without this branch).
The example app's Currency card now uses this built-in instead of the inline transformer it had previously, and switches between USD / EUR / JPY by changing the `transformer` prop. Three module-scope instances, no shared mutable state.
Test plan
The library's jest suite covers the new code (`yarn test` — 33 tests in `CurrencyTransformer.test.ts`, all green; 73 pre-existing tests unchanged). The interesting cases reviewers might want to spot-check: