Conversation
NumberFieldInput rendered NumberField without formatOptions, so
useNumberFieldState formatted with the Intl.NumberFormat default of
maximumFractionDigits: 3 and rounded the committed value through it.
Pass a hoisted { maximumFractionDigits: 20 } so the formatter is lossless;
precision is then governed by step alone.
Fixes Thinkmill#1614
🦋 Changeset detectedLatest commit: f30e3d3 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
This branch has not been deployed
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.
Fixes #1614
Problem
fields.numbersilently rounds committed values to 3 decimal places. If you type51.98771and leave the field,51.988is saved. This breaks latitude/longitude and any other value that needs more than three decimals.Values that are already stored are affected too. If an entry has
5.8123456saved, the field shows5.812. Focusing the field and tabbing out, without typing anything, callsonChange(5.812), so the rounded value is written back the next time the entry is saved.Root cause
NumberFieldInputrenders@keystar/ui'sNumberFieldwithoutformatOptions.useNumberFieldStatebuilds itsNumberFormatter/NumberParserfromformatOptionsalone. On commit it round-trips the value through that formatter, after snapping tostep:With no options,
Intl.NumberFormatuses its defaultmaximumFractionDigits: 3, so the stored value is rounded, not just the text shown.stepdoesn't help, because it isn't part of the formatter options. Astepof0.00001still gets rounded back to 3 decimals.Fix
NumberFieldInputnow passes a module-level constantformatOptions = { maximumFractionDigits: 20 }toNumberField.0.1still displays as0.1,51.98771as51.98771, and integers as before (1,234,567). With this setting the formatter never rounds anything a user can type.stepalready controls precision throughuseNumberFieldState's snap-to-step, and validation already checks it. I considered two alternatives:maximumFractionDigitsfromstep. This fixes only fields that set astep. The common case (nostep) would still round to 3 decimals.formatOptionsoption onfields.number. This adds API surface just to opt out of data loss. That should be the default. If display options (percent, currency, fixed decimals) are wanted later, they can be added as a separate feature on top of this.useNumberFieldStatememoises its parser and formatter onformatOptionsidentity. A new object on every render would rebuild them each time. Older versions compared by identity and reset the input text while the user was typing.fields.integer(IntegerFieldInput) is unchanged. Integers have no fraction digits, so the default doesn't affect them.Tests
packages/keystatic/src/form/fields/number/ui.test.tsxrendersNumberFieldInputin aKeystarProvider(locale="en-US", as the Keystatic app does) and drives it with@testing-library/user-event:51.98771and blurring commits51.98771expected 51.988 to be 51.987715.8123456is displayed in full and not changed by focus + blurexpected '5.812' to be '5.8123456'(with only the display assertion removed:expected [ 5.812 ] to deeply equal [])step: 0.00001,51.98771survivesexpected 51.988 to be 51.987711234567→1,234,567)step: 0.01still snaps1.23456to1.23vitest run packages/keystatic/src/form/fields/number/gives 17/17 passing (also withSTRICT_MODE=1).tsc,eslintandprettier --checkare clean.We also checked this manually in a real admin UI, using an equivalent patch applied to the built
@keystatic/core(a hoistedformatOptionswithmaximumFractionDigits: 6). Typing51.98771and blurring keeps51.98771, and51.98771is what gets saved to the entry's JSON. Without the change it's rounded to 3 decimals.Changeset
@keystatic/core: patch.