Show number fields with three decimals and snap dragging to the step

Number fields derived their display precision from `step`, so `step: 1`
(X/Y/Z, width, height, font size) rendered a stored 100.234 as "100" — and
because `onChange` parses the text, touching the field wrote the rounded
value back into the project. `formatNumber` now renders every number with
three decimals, independent of `step`, which is left to control the scrub
and arrow-key increment only.

Dragging now snaps to the increment's grid (`snapToStep`), so a scrub at
`step: 1` always lands on whole numbers even from a fractional start, while
Alt (x0.1) and the 0.1-step fields keep their precision. Typed values and
arrow keys are untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
rootandClaude Opus 5 committed 2026-09-08 11:48:32 +02:00
1 parent 15ffcd5739
commit 6168df41f8
6 files changed
+104 -21

No files matched your search

+27
View File
@@ -8,6 +8,33 @@ export function centreY(containerHeight: number, itemHeight: number): number {
return Math.round((containerHeight - itemHeight) / 2);
}
/** Decimal places every number field shows. */
export const NUMBER_DECIMALS = 3;
/**
* Display text for a number. Always shows `decimals` places, so a value can never *look* like
* a whole number when it is not: precision used to follow the field's step, which hid the
* decimals that dragging in the viewport or a graph node had put into the value.
* A value that rounds to zero never carries a minus sign.
*/
export function formatNumber(value: number, decimals = NUMBER_DECIMALS): string {
const zero = (0).toFixed(decimals);
const text = value.toFixed(decimals);
return text === '-' + zero ? zero : text;
}
/**
* Rounds `value` onto the grid of `stepSize`, so scrubbing a field lands on clean values:
* with step 1 on whole numbers, with step 0.1 on tenths. Needed because neither input is
* whole – the pointer delta comes in fractional device pixels (a 150 % display yields
* thirds) and the starting value may already be fractional from a viewport drag.
* The `toFixed` pass removes binary-float noise (0.1 × 3 = 0.30000000000000004).
*/
export function snapToStep(value: number, stepSize: number): number {
if (!(stepSize > 0) || !Number.isFinite(value)) return value;
return Number((Math.round(value / stepSize) * stepSize).toFixed(NUMBER_DECIMALS));
}
/**
* Zoom multiplier for one wheel event. Exponential, so scrolling up and back down by the same
* amount lands exactly where it started. The caller clamps the result into its own range –